diff --git a/SaCommonVS.sln b/SaCommonVS.sln
new file mode 100644
index 0000000..c7b3b39
--- /dev/null
+++ b/SaCommonVS.sln
@@ -0,0 +1,49 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31112.23
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardBase", "SacsStandardBase\SacsStandardBase.csproj", "{3A8EE4B3-2335-420B-9619-D1622AF511FA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardDbas", "SacsStandardDbas\SacsStandardDbas.csproj", "{14768EB5-6FA5-4F62-9347-44A667A760C1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardMob", "SacsStandardMob\SacsStandardMob.csproj", "{E507AB63-28E3-4FF4-9EA8-2E87C42FFC20}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SacsCoreWeb", "SacsCoreWeb\SacsCoreWeb.csproj", "{AE8FCC39-D13D-4E32-995E-C69352F9F95E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SacsCoreUnitTests", "SacsCoreUnitTests\SacsCoreUnitTests.csproj", "{70F1302F-E8F2-49CB-A09D-889DA59681DD}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3A8EE4B3-2335-420B-9619-D1622AF511FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3A8EE4B3-2335-420B-9619-D1622AF511FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3A8EE4B3-2335-420B-9619-D1622AF511FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3A8EE4B3-2335-420B-9619-D1622AF511FA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {14768EB5-6FA5-4F62-9347-44A667A760C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {14768EB5-6FA5-4F62-9347-44A667A760C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {14768EB5-6FA5-4F62-9347-44A667A760C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {14768EB5-6FA5-4F62-9347-44A667A760C1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E507AB63-28E3-4FF4-9EA8-2E87C42FFC20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E507AB63-28E3-4FF4-9EA8-2E87C42FFC20}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E507AB63-28E3-4FF4-9EA8-2E87C42FFC20}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E507AB63-28E3-4FF4-9EA8-2E87C42FFC20}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AE8FCC39-D13D-4E32-995E-C69352F9F95E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AE8FCC39-D13D-4E32-995E-C69352F9F95E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AE8FCC39-D13D-4E32-995E-C69352F9F95E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AE8FCC39-D13D-4E32-995E-C69352F9F95E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {70F1302F-E8F2-49CB-A09D-889DA59681DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {70F1302F-E8F2-49CB-A09D-889DA59681DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {70F1302F-E8F2-49CB-A09D-889DA59681DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {70F1302F-E8F2-49CB-A09D-889DA59681DD}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {19DF326A-93A0-4990-9F4B-C0B0629FC8B3}
+ EndGlobalSection
+EndGlobal
diff --git a/SacsCoreUnitTests/SacsCoreUnitTests.csproj b/SacsCoreUnitTests/SacsCoreUnitTests.csproj
new file mode 100644
index 0000000..ba72c32
--- /dev/null
+++ b/SacsCoreUnitTests/SacsCoreUnitTests.csproj
@@ -0,0 +1,29 @@
+
+
+
+ netcoreapp3.1
+
+ false
+
+ Sacs.UnitTests.Net
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SacsCoreUnitTests/Standard/Base/App/SacsCommandLineInfo_Tester.cs b/SacsCoreUnitTests/Standard/Base/App/SacsCommandLineInfo_Tester.cs
new file mode 100644
index 0000000..f7f258c
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/App/SacsCommandLineInfo_Tester.cs
@@ -0,0 +1,35 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Sarand.Net.Standard.Base.App;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Sacs.UnitTests.Net.Standard.Base.App
+{
+ //=======================================================================
+ [TestClass]
+ public class SacsCommandLineInfo_Tester
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void SacsCommandLineInfo_CTR()
+ {
+ SacsCommandLineInfo cmdLineInfo = new SacsCommandLineInfo(new String[]
+ {
+ "-ABC",
+ "/DEF",
+ "Andy",
+ "-SaProcess:ExportData",
+ });
+ Assert.IsNotNull(cmdLineInfo);
+ Assert.AreEqual(4, cmdLineInfo.ParamCount);
+ Assert.AreEqual(1, cmdLineInfo.ProcessDefinitions.Count);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsCoreUnitTests/Standard/Base/App/SacsProcessDefinition_Tester.cs b/SacsCoreUnitTests/Standard/Base/App/SacsProcessDefinition_Tester.cs
new file mode 100644
index 0000000..7b41443
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/App/SacsProcessDefinition_Tester.cs
@@ -0,0 +1,52 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Sarand.Net.Standard.Base.App;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Sacs.UnitTests.Net.Standard.Base.App
+{
+ //=======================================================================
+ [TestClass]
+ public class SacsProcessDefinition_Tester
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void SacsProcessDefinition_CTR()
+ {
+ // Valid process definitions.
+ Assert.IsNotNull(new SacsProcessDefinition("SaProcess:ExportData"));
+ Assert.IsNotNull(new SacsProcessDefinition("SaProcess:Proc=ExportData"));
+ Assert.IsNotNull(new SacsProcessDefinition("SaProcess:ExportData;User=System"));
+
+ // Invalid process definition - blank argument.
+ try
+ {
+ SacsProcessDefinition processDfn = new SacsProcessDefinition("");
+ }
+ catch (Exception exc)
+ {
+ Assert.AreEqual(typeof(NotSupportedException), exc.GetType());
+ }
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void SacsProcessDefinition_IsValidProcessDefinition()
+ {
+ // Valid process params.
+ Assert.AreEqual(true, SacsProcessDefinition.IsValidProcessDefinition("SaProcess:ExportData"));
+
+ // Invalid process params.
+ Assert.AreEqual(false, SacsProcessDefinition.IsValidProcessDefinition(""));
+ Assert.AreEqual(false, SacsProcessDefinition.IsValidProcessDefinition("Abc"));
+ Assert.AreEqual(false, SacsProcessDefinition.IsValidProcessDefinition("SaProcess"));
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsCoreUnitTests/Standard/Base/Bus/SysId_Tester.cs b/SacsCoreUnitTests/Standard/Base/Bus/SysId_Tester.cs
new file mode 100644
index 0000000..ceb6a13
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/Bus/SysId_Tester.cs
@@ -0,0 +1,179 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Sarand.Net.Standard.Base.Bus;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+
+namespace Sacs.UnitTests.Net.Standard.Base.Bus
+{
+ [TestClass]
+ public class SysId_Tester
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ private readonly SysId idNull = null;
+ private readonly SysId id_ = new SysId();
+ private readonly SysId id0 = new SysId(0);
+ private readonly SysId id1 = new SysId(1);
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void SysId_CTR_Void ()
+ {
+ Assert.IsNotNull(new SysId());
+ Assert.AreEqual(new SysId().ToInt(), 0);
+ Assert.IsTrue(String.IsNullOrEmpty(new SysId().ToString()));
+ Assert.IsTrue(StringUtil.IsEmpty(new SysId().ToString()));
+ Assert.AreEqual(new SysId().ToString(), "");
+ Assert.AreEqual(new SysId().ToString(), string.Empty);
+ }
+
+ [TestMethod]
+ public void SysId_CTR_Long ()
+ {
+ // Zero - invalid.
+ Assert.IsNotNull(new SysId(0));
+
+ // 1 - Valid.
+ Assert.IsNotNull(new SysId(1));
+ Assert.IsNotNull(new SysId(-1));
+ }
+
+ //-------------------------------------------------------------------
+ // Is Valid.
+ [TestMethod]
+ public void SysId_IsValid ()
+ {
+ Assert.IsFalse(SysId.IsValid(null));
+ Assert.IsFalse(SysId.IsValid(new SysId()));
+ Assert.IsFalse(SysId.IsValid(new SysId(0)));
+ Assert.IsTrue(SysId.IsValid(new SysId(1)));
+ Assert.IsTrue(SysId.IsValid(new SysId(-1)));
+ }
+
+ //-------------------------------------------------------------------
+ // Invalid on null.
+ [TestMethod]
+ public void SysId_InvalidOnNull ()
+ {
+ Assert.IsNotNull(SysId.InvalidOnNull(null));
+ Assert.IsFalse(SysId.IsValid(SysId.InvalidOnNull(null)));
+ Assert.IsFalse(SysId.IsValid(SysId.InvalidOnNull(new SysId())));
+ Assert.IsFalse(SysId.IsValid(SysId.InvalidOnNull(new SysId(0))));
+ Assert.IsTrue(SysId.IsValid(SysId.InvalidOnNull(new SysId(1))));
+ Assert.IsTrue(SysId.IsValid(SysId.InvalidOnNull(new SysId(-1))));
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void SysId_Equals_Object ()
+ {
+ Assert.IsFalse(id_.Equals(null));
+ Assert.IsFalse(id0.Equals(null));
+ Assert.IsFalse(id1.Equals(null));
+ Assert.IsFalse(id_.Equals((Object)id_));
+ Assert.IsFalse(id_.Equals((Object)id0));
+ Assert.IsFalse(id_.Equals((Object)id1));
+ Assert.IsFalse(id0.Equals((Object)id_));
+ Assert.IsFalse(id0.Equals((Object)id0));
+ Assert.IsFalse(id0.Equals((Object)id1));
+ Assert.IsFalse(id1.Equals((Object)id_));
+ Assert.IsFalse(id1.Equals((Object)id0));
+ Assert.IsTrue(id1.Equals((Object)id1));
+ }
+ [TestMethod]
+ public void SysId_Equals_SysId ()
+ {
+ // Invalid values always unequal.
+ Assert.IsFalse(id_.Equals(null));
+ Assert.IsFalse(id_.Equals(idNull));
+ Assert.IsFalse(id_.Equals(id_));
+ Assert.IsFalse(id_.Equals(id0));
+ Assert.IsFalse(id_.Equals(new SysId()));
+
+ // Valid values.
+ Assert.IsTrue(id1.Equals(new SysId(1)));
+
+ for (int i = 1; i <= 1000; i++)
+ {
+ SysId idI = new SysId(i);
+ for (int j = 1; j <= 1000; j++)
+ {
+ SysId idJ = new SysId(j);
+ if (i == j) Assert.IsTrue(idI.Equals(idJ));
+ else Assert.IsFalse(idI.Equals(idJ));
+
+ }
+ }
+ }
+ //-------------------------------------------------------------------
+
+ //-------------------------------------------------------------------
+ // S y s I d L i s t
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void SysId_List_GetIdCount ()
+ {
+ List ids = new List()
+ {
+ new SysId(),
+ new SysId(1),
+ new SysId(2),
+ new SysId(3),
+ new SysId(),
+ new SysId(3),
+ new SysId(2),
+ new SysId(1),
+ new SysId(),
+ };
+ // Unique, valid only.
+ Assert.AreEqual(3, ids.GetIdCount());
+ Assert.AreEqual(3, ids.GetIdCount(true,true));
+ // Unique including invalid.
+ Assert.AreEqual(4, ids.GetIdCount(false));
+ Assert.AreEqual(4, ids.GetIdCount(false,true));
+ // Duplicate, valid only.
+ Assert.AreEqual(6, ids.GetIdCount(true, false));
+ // Duplicate including invalid.
+ Assert.AreEqual(9, ids.GetIdCount(false,false));
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void SysId_List_HasValidIds ()
+ {
+ List ids = new List();
+
+ // Single valid Id.
+ ids.Clear();
+ ids.Add(new SysId(123));
+ Assert.AreEqual(true, ids.HasValidIds());
+
+ // Several valid ids.
+ ids.Clear();
+ ids.AddRange(new List()
+ {
+ new SysId(1),
+ new SysId(2),
+ new SysId(3),
+ });
+ Assert.AreEqual(true, ids.HasValidIds());
+
+ // Invalid only.
+ ids.Clear();
+ ids.AddRange(new List()
+ {
+ new SysId(),
+ new SysId(),
+ new SysId(),
+ });
+ Assert.AreEqual(false, ids.HasValidIds());
+
+ // Add valid Id.
+ ids.Add(new SysId(123));
+ Assert.AreEqual(true, ids.HasValidIds());
+ }
+
+ //-------------------------------------------------------------------
+ }
+}
diff --git a/SacsCoreUnitTests/Standard/Base/Data/SacsAesIvCrypter_Tester.cs b/SacsCoreUnitTests/Standard/Base/Data/SacsAesIvCrypter_Tester.cs
new file mode 100644
index 0000000..f5b4998
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/Data/SacsAesIvCrypter_Tester.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Text;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Linq;
+using Sarand.Net.Standard.Base.Data;
+
+namespace Sacs.UnitTests.Net.Standard.Base.Data
+{
+ //=======================================================================
+ [TestClass]
+ public class SacsAesIvCrypter_Tester
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ private static Random random = new Random();
+ private TestContext testContextInstance;
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SacsAesIvCrypter_Tester ()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ ///
+ ///Gets or sets the test context which provides
+ ///information about and functionality for the current test run.
+ ///
+ public TestContext TestContext
+ {
+ get
+ {
+ return testContextInstance;
+ }
+ set
+ {
+ testContextInstance = value;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ #region Additional test attributes
+ //
+ // You can use the following additional attributes as you write your tests:
+ //
+ // Use ClassInitialize to run code before running the first test in the class
+ // [ClassInitialize()]
+ // public static void MyClassInitialize(TestContext testContext) { }
+ //
+ // Use ClassCleanup to run code after all tests in a class have run
+ // [ClassCleanup()]
+ // public static void MyClassCleanup() { }
+ //
+ // Use TestInitialize to run code before running each test
+ // [TestInitialize()]
+ // public void MyTestInitialize() { }
+ //
+ // Use TestCleanup to run code after each test has run
+ // [TestCleanup()]
+ // public void MyTestCleanup() { }
+ //
+ #endregion
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void SacsAesIvCrypter_Test ()
+ {
+ for (int i = 0; i < 200; i++)
+ {
+ String data = RandomString(random.Next(10, 2048), true);
+ int dataLength = data.Length;
+ String key = RandomString(random.Next(10, 128), true);
+ int keyLength = key.Length;
+ String encrypted = SacsAesIvCrypter.Encrypt(data, key);
+ int encryptedLength = encrypted.Length;
+ Assert.AreEqual(data, SacsAesIvCrypter.Decrypt(encrypted, key));
+ }
+ }
+
+ //-------------------------------------------------------------------
+ /// Generate random alphanumeric string
+ public static String RandomString (int length, bool includeLowerCase = true, bool includeSymbols = true)
+ {
+ String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ if (includeLowerCase) chars += "abcdefghijklmnopqrstuvwxyz";
+ if (includeSymbols) chars += "`¬!\"£$%^&*()-=_+{}[];:'@#~<>?/.,\\| ";
+ return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsCoreUnitTests/Standard/Base/Data/SacsCsvHandler_Tester.cs b/SacsCoreUnitTests/Standard/Base/Data/SacsCsvHandler_Tester.cs
new file mode 100644
index 0000000..484e302
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/Data/SacsCsvHandler_Tester.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Sarand.Net.Standard.Base.Data;
+
+namespace Sacs.UnitTests.Net.Standard.Base.Data
+{
+ //=======================================================================
+ [TestClass]
+ public class SacsCsvHandler_Tester
+ {
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void ParseCsvRecord_FieldCount ()
+ {
+ Assert.AreEqual(0, SacsCsvHandler.ParseCsvRecord(null).Count);
+ Assert.AreEqual(1, SacsCsvHandler.ParseCsvRecord("").Count);
+ Assert.AreEqual(2, SacsCsvHandler.ParseCsvRecord(",").Count);
+ Assert.AreEqual(3, SacsCsvHandler.ParseCsvRecord("A,B,C").Count);
+ Assert.AreEqual(5, SacsCsvHandler.ParseCsvRecord("A,B,C,D,E").Count);
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void ParseCsvRecord_Simple_NoText ()
+ {
+ String data = "A,B,C";
+ List fields = SacsCsvHandler.ParseCsvRecord(data);
+ Assert.AreEqual(3, fields.Count);
+ Assert.AreEqual("B", fields[1]);
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void ParseCsvRecord_Text_Simple ()
+ {
+ List fields = SacsCsvHandler.ParseCsvRecord("A,\"B\",C");
+ Assert.AreEqual(3, fields.Count);
+ Assert.AreEqual("A", fields[0]);
+ Assert.AreEqual("B", fields[1]);
+ Assert.AreEqual("C", fields[2]);
+
+ fields = SacsCsvHandler.ParseCsvRecord("A,\"B\",\"C\"");
+ Assert.AreEqual(3, fields.Count);
+ Assert.AreEqual("A", fields[0]);
+ Assert.AreEqual("B", fields[1]);
+ Assert.AreEqual("C", fields[2]);
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void ParseCsvRecord_Text_WithFieldDelim ()
+ {
+ List fields = SacsCsvHandler.ParseCsvRecord("A,\",\",C");
+ Assert.AreEqual(3, fields.Count);
+ Assert.AreEqual("A", fields[0]);
+ Assert.AreEqual(",", fields[1]);
+ Assert.AreEqual("C", fields[2]);
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void ParseCsvRecord_Text_EmbeddedTextDelims ()
+ {
+ List fields = SacsCsvHandler.ParseCsvRecord("A,\"\"\"\",C");
+ Assert.AreEqual(3, fields.Count);
+ Assert.AreEqual("A", fields[0]);
+ Assert.AreEqual("\"", fields[1]);
+ Assert.AreEqual("C", fields[2]);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsCoreUnitTests/Standard/Base/Data/SacsDataFn_Tester.cs b/SacsCoreUnitTests/Standard/Base/Data/SacsDataFn_Tester.cs
new file mode 100644
index 0000000..05f544e
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/Data/SacsDataFn_Tester.cs
@@ -0,0 +1,85 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Sarand.Net.Standard.Base.Data;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sacs.UnitTests.Net.Standard.Base.Data
+{
+ //=======================================================================
+ [TestClass]
+ public class SacsDataFn_Tester
+ {
+ //-------------------------------------------------------------------
+ // Data memebrs.
+ private static Random random = new Random();
+
+ //-------------------------------------------------------------------
+ /// Create random alphanumeric string of specified length.
+ public static string RandomString (int length)
+ {
+ const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ return new string(Enumerable.Repeat(chars, length)
+ .Select(s => s[random.Next(s.Length)]).ToArray());
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void Sapphire ()
+ {
+ String encodedEncrypted = "6NP R78:P.8$2Q";
+ String raw = SacsDataFn._sadcs (encodedEncrypted, new SacsData("SaItemOptionMgr_Odbc"));
+ Assert.AreEqual("sarand", raw);
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void Encode ()
+ {
+ // Test the Encoding algorythm.
+ for (int i = 0; i < 1000; i++)
+ {
+ String rawData = RandomString(random.Next(10,21));
+ SacsData dataEnc = SacsDataFn._saecd (new SacsData(rawData).DataBuffer);
+ Assert.AreNotEqual(rawData, dataEnc.NullTerminatedString);
+ SacsData dataDec = SacsDataFn._sadcd (dataEnc.DataBuffer);
+ Assert.AreEqual(rawData, dataDec.NullTerminatedString);
+ }
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void Encrypt_String ()
+ {
+ // Test the Encryption algorythm.
+ for (int i = 0; i < 1000; i++)
+ {
+ String rawData = RandomString(random.Next(10,21));
+ String dataEnc = SacsDataFn._saecs (rawData);
+ Assert.AreNotEqual(rawData, dataEnc);
+ String dataDec = SacsDataFn._sadcs (dataEnc);
+ Assert.AreEqual(rawData, dataDec);
+ }
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void RC4Encrypt_String ()
+ {
+ // Test the RC4 algorythm:
+ for (int i = 0; i < 1000; i++)
+ {
+ String rawData = RandomString(random.Next(10,21));
+ SacsData dataEnc = SacsDataFn.RC4 (new SacsData(rawData).DataBuffer);
+ Assert.AreNotEqual(rawData, dataEnc.NullTerminatedString);
+ SacsData dataDec = SacsDataFn.RC4 (dataEnc.DataBuffer);
+ Assert.AreEqual(rawData, dataDec.NullTerminatedString);
+ }
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsCoreUnitTests/Standard/Base/Data/SacsData_Tester.cs b/SacsCoreUnitTests/Standard/Base/Data/SacsData_Tester.cs
new file mode 100644
index 0000000..46b8b7f
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/Data/SacsData_Tester.cs
@@ -0,0 +1,61 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Sarand.Net.Standard.Base.Data;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Sacs.UnitTests.Net.Standard.Base.Data
+{
+ //=======================================================================
+ [TestClass]
+ public class SacsData_Tester
+ {
+ //-------------------------------------------------------------------
+ // Data memebrs.
+ private static Random random = new Random();
+
+ //-------------------------------------------------------------------
+ /// Create random alphanumeric string of specified length.
+ public static string RandomString (int length)
+ {
+ const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ return new string(Enumerable.Repeat(chars, length)
+ .Select(s => s[random.Next(s.Length)]).ToArray());
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void CTR_String ()
+ {
+ for (int i = 0; i < 1000; i++)
+ {
+ String rawValue = RandomString(random.Next(10,21));
+ // Default (ASCII) encoding.
+ Assert.AreEqual(rawValue, (String)new SacsData(rawValue));
+ // UniCode encoding.
+ Assert.AreEqual(rawValue, (String)new SacsData(rawValue, Encoding.Unicode));
+ }
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void CTR_Copy ()
+ {
+ for (int i = 0; i < 1000; i++)
+ {
+ String rawValue = RandomString(random.Next(10,21));
+ // Default (ASCII) encoding.
+ Assert.AreEqual(rawValue, (String)new SacsData(new SacsData(rawValue)));
+ // UTF-8 encoding.
+ Assert.AreEqual(rawValue, (String)new SacsData(new SacsData(rawValue, Encoding.UTF8)));
+ // UniCode encoding.
+ Assert.AreEqual(rawValue, (String)new SacsData(new SacsData(rawValue, Encoding.Unicode)));
+ }
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsCoreUnitTests/Standard/Base/Generic/ObjectReferences_Tester.cs b/SacsCoreUnitTests/Standard/Base/Generic/ObjectReferences_Tester.cs
new file mode 100644
index 0000000..ac8ae96
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/Generic/ObjectReferences_Tester.cs
@@ -0,0 +1,86 @@
+using System.Drawing;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Sacs.UnitTests.Net.Standard.Base.Generic
+{
+ [TestClass]
+ public class ObjectReferences_Tester
+ {
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void ObjectReferences_ChangeValuesOnReferencedObject ()
+ {
+ // size2 starts as a reference to size1.
+ Size size1 = new Size(10, 20);
+ Size size2 = size1;
+ Size size3 = new Size(10, 20);
+ Assert.AreEqual(size1, size2);
+ Assert.AreEqual(size1, size3);
+ Assert.AreEqual(size2, size3);
+ // Change size1 values - doesn't change size2.
+ size1.Width = 100;
+ size1.Height = 200;
+ Assert.AreNotEqual(size1, size2);
+ Assert.AreNotEqual(size1, size3);
+ Assert.AreEqual(size2, size3);
+ }
+ [TestMethod]
+ public void ObjectReferences_ChangeValuesOnObjectReference ()
+ {
+ // size2 starts as a reference to size1.
+ Size size1 = new Size(10, 20);
+ Size size2 = size1;
+ Size size3 = new Size(10, 20);
+ Assert.AreEqual(size1, size2);
+ Assert.AreEqual(size1, size3);
+ Assert.AreEqual(size2, size3);
+ // Change size2 values - doesn't change size1.
+ size2.Width = 100;
+ size2.Height = 200;
+ Assert.AreNotEqual(size1, size2);
+ Assert.AreEqual(size1, size3);
+ Assert.AreNotEqual(size2, size3);
+ }
+ //-------------------------------------------------------------------
+ // Modify class value.
+ class TestData
+ {
+ public int a = 0;
+ public int B { get; set; }
+ public Size size1 = new Size(10,20);
+ public Size Size2 { get; set; } = new Size(30, 40);
+ }
+ [TestMethod]
+ public void ObjectReferences_ChangeClassValues ()
+ {
+ TestData td1 = new TestData();
+ TestData td2 = td1;
+ Assert.AreEqual(td1, td2);
+ td1.a++; // td2 is still a reference to td1 with the same values.
+ Assert.AreEqual(td1, td2);
+ Assert.AreEqual(td1.a, td2.a);
+ Assert.AreEqual(td1.B, td2.B);
+ Assert.AreEqual(td1.size1, td2.size1);
+ Assert.AreEqual(td1.Size2, td2.Size2);
+ td1.B++; // td2 is still a reference to td1 with the same values.
+ Assert.AreEqual(td1, td2);
+ Assert.AreEqual(td1.a, td2.a);
+ Assert.AreEqual(td1.B, td2.B);
+ Assert.AreEqual(td1.size1, td2.size1);
+ Assert.AreEqual(td1.Size2, td2.Size2);
+ td1.size1.Width += 10; // td2 is still a reference to td1 with the same values.
+ Assert.AreEqual(td1, td2);
+ Assert.AreEqual(td1.a, td2.a);
+ Assert.AreEqual(td1.B, td2.B);
+ Assert.AreEqual(td1.size1, td2.size1);
+ Assert.AreEqual(td1.Size2, td2.Size2);
+ td1.Size2 = new Size(td1.Size2.Width, td1.Size2.Height + 10); // td2 is still a reference to td1 with the same values.
+ Assert.AreEqual(td1, td2);
+ Assert.AreEqual(td1.a, td2.a);
+ Assert.AreEqual(td1.B, td2.B);
+ Assert.AreEqual(td1.size1, td2.size1);
+ Assert.AreEqual(td1.Size2, td2.Size2);
+ }
+ //-------------------------------------------------------------------
+ }
+}
diff --git a/SacsCoreUnitTests/Standard/Base/Generic/Regex_Tester.cs b/SacsCoreUnitTests/Standard/Base/Generic/Regex_Tester.cs
new file mode 100644
index 0000000..38e5cff
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/Generic/Regex_Tester.cs
@@ -0,0 +1,19 @@
+using System.Text.RegularExpressions;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Sacs.UnitTests.Net.Standard.Base.Generic
+{
+ [TestClass]
+ public class Regex_Tester
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void Regex_FolderPaths ()
+ {
+ Assert.AreEqual(true, Regex.IsMatch("abcabcabc\\.metadata\\.plugins\\abcabcabc", ".*?\\\\\\.metadata\\\\\\.plugins\\\\.*?"));
+ }
+ //-------------------------------------------------------------------
+ }
+}
diff --git a/SacsCoreUnitTests/Standard/Base/Helpers/SacsCsvHelper_Tester.cs b/SacsCoreUnitTests/Standard/Base/Helpers/SacsCsvHelper_Tester.cs
new file mode 100644
index 0000000..c8128b9
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/Helpers/SacsCsvHelper_Tester.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Sarand.Net.Standard.Base.Helpers;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sacs.UnitTests.Net.Standard.Base.Helpers
+{
+ //=======================================================================
+ [TestClass]
+ public class SacsCsvHelper_Tester
+ {
+ //-------------------------------------------------------------------
+ // C o n v e r t F r o m C S V
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void ParseCsv_Data ()
+ {
+ List data = new List()
+ {
+ "A,B,C",
+ "D,\"E\",F,\"\"\"G\"\"\",H,\"I,\"",
+ "W,X,Y,Z"
+ };
+
+ // Parser configurations - all for 'standard' conversion.
+ List parserConfigs = new List()
+ {
+ null,
+ new SacsCsvHelper.ParserConfig() { BufferSize = 1024 },
+ new SacsCsvHelper.ParserConfig() { CountBytes = true },
+ new SacsCsvHelper.ParserConfig() { Encoding = Encoding.ASCII },
+ new SacsCsvHelper.ParserConfig() { LineBreakInQuotedFieldIsBadData = true },
+ new SacsCsvHelper.ParserConfig() { Comment = '\'' },
+ new SacsCsvHelper.ParserConfig() { AllowComments = true },
+ new SacsCsvHelper.ParserConfig() { IgnoreBlankLines = false },
+ new SacsCsvHelper.ParserConfig() { IgnoreQuotes = false },
+ new SacsCsvHelper.ParserConfig() { Quote = '"' },
+ new SacsCsvHelper.ParserConfig() { Delimiter = "," },
+ new SacsCsvHelper.ParserConfig() { Escape = '"' },
+ //new SacsCsvHelper.ParserConfig() { TrimOptions = TrimOptions.Trim },
+ };
+
+ foreach (SacsCsvHelper.ParserConfig parserConfig in parserConfigs)
+ {
+ List records = SacsCsvHelper.ParseCsv(data.Concat(Environment.NewLine), parserConfig);
+
+ Assert.IsNotNull(records);
+ // Record count.
+ Assert.AreEqual(data.Count, records.Count);
+ // Field counts.
+ Assert.AreEqual(3, records[0].Length);
+ Assert.AreEqual(6, records[1].Length);
+ Assert.AreEqual(4, records[2].Length);
+ // Field values.
+ Assert.AreEqual("B", records[0][1]);
+ Assert.AreEqual("E", records[1][1]);
+ Assert.AreEqual("\"G\"", records[1][3]);
+ Assert.AreEqual("I,", records[1][5]);
+ Assert.AreEqual("X", records[2][1]);
+ }
+ }
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void ParseCsv_Data_InvalidQuotes ()
+ {
+ List data = new List()
+ {
+ "A,B,C",
+ "D,\"E,G,F,G,H,I",
+ "W,X,Y,Z"
+ };
+ SacsCsvHelper.ParserConfig config = new SacsCsvHelper.ParserConfig();
+ List records = SacsCsvHelper.ParseCsv(data.Concat(Environment.NewLine));
+
+ Assert.IsNotNull(records);
+ // 1 invalid record ignored.
+ Assert.AreEqual(data.Count - 1, records.Count);
+ }
+
+ //-------------------------------------------------------------------
+ // C o n v e r t T o C S V
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void ToCsv_Fields ()
+ {
+ //SaCsvHelper csvHelper = new SaCsvHelper();
+
+ // Single record, alternative collections.
+ Assert.AreEqual("A,B,C", SacsCsvHelper.ToCsv(new String[] { "A", "B", "C" }));
+ Assert.AreEqual("A,B,C", SacsCsvHelper.ToCsv(new Collection() { "A", "B", "C" }));
+ Assert.AreEqual("A,B,C", SacsCsvHelper.ToCsv(new List() { "A", "B", "C" }));
+ Assert.AreEqual("D,E,F", SacsCsvHelper.ToCsv(new String[] { "D", "E", "F" }));
+
+ // Embedded delimiter.
+ Assert.AreEqual("\"A,\",B,C", SacsCsvHelper.ToCsv(new String[] { "A,", "B", "C" }));
+ Assert.AreEqual("A,\",B\",C", SacsCsvHelper.ToCsv(new String[] { "A", ",B", "C" }));
+
+ // Embedded quotes.
+ Assert.AreEqual("\"\"\"A\"\"\",B,C", SacsCsvHelper.ToCsv(new String[] { "\"A\"", "B", "C" }));
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void ToCsv_Records ()
+ {
+ //SaCsvHelper csvHelper = new SaCsvHelper();
+
+ List dataRecords = new List
+ {
+ new String[]{ "A", "B", "C" },
+ new String[]{ "D", "E", "F", "G", "H", "I" },
+ new String[]{ "W", "X", "Y", "Z" }
+ };
+ List records = SacsCsvHelper.ToCsv(dataRecords);
+
+ // Multiple records, multiple times to verify clearing of data each time.
+ for (int i = 0; i < 10; i++)
+ {
+ Assert.AreEqual(3, records.Count);
+ Assert.AreEqual("A,B,C", records[0]);
+ Assert.AreEqual("D,E,F,G,H,I", records[1]);
+ Assert.AreEqual("W,X,Y,Z", records[2]);
+ }
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsCoreUnitTests/Standard/Base/Utils/DateUtil_Tester.cs b/SacsCoreUnitTests/Standard/Base/Utils/DateUtil_Tester.cs
new file mode 100644
index 0000000..6dc66b3
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/Utils/DateUtil_Tester.cs
@@ -0,0 +1,259 @@
+using System;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sarand.UnitTests.Core.Base.Utils
+{
+ //=======================================================================
+ [TestClass]
+ public class DateUtil_Tester
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ private Random random = new Random();
+ private DateTime today = DateTime.Today;
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void DateTime_CTR_Void ()
+ {
+ Assert.IsNotNull(new DateTime());
+ Assert.AreEqual(new DateTime(), DateTime.MinValue);
+ }
+
+ //-------------------------------------------------------------------
+ // S e t T i m e
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void SetTime_DateTime ()
+ {
+ for (int i = 0; i < 1000; i++)
+ {
+ DateTime date = new DateTime(random.Next(1970, 2100), random.Next(1, 13), random.Next(1, 28), random.Next(0, 24), random.Next(0, 60), random.Next(0, 60));
+ DateTime time = new DateTime(random.Next(1970, 2100), random.Next(1, 13), random.Next(1, 28), random.Next(0, 24), random.Next(0, 60), random.Next(0, 60));
+ Assert.AreEqual(DateUtil.FormatXmlDate(date), DateUtil.FormatXmlDate(date.SetTime(time)));
+ Assert.AreEqual(DateUtil.FormatXmlTime(time), DateUtil.FormatXmlTime(date.SetTime(time)));
+ }
+ }
+ [TestMethod]
+ public void SetTime_TimeSpan ()
+ {
+ for (int i = 0; i < 1000; i++)
+ {
+ DateTime date = new DateTime(random.Next(1970, 2100), random.Next(1, 13), random.Next(1, 28), random.Next(0, 24), random.Next(0, 60), random.Next(0, 60));
+ TimeSpan time = new TimeSpan(random.Next(0, 24), random.Next(0, 60), random.Next(0, 60));
+ Assert.AreEqual(DateUtil.FormatXmlDate(date), DateUtil.FormatXmlDate(date.SetTime(time)));
+ Assert.AreEqual(DateUtil.FormatXmlTime(today + time), DateUtil.FormatXmlTime(date.SetTime(time)));
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // R o u n d T i m e
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void RoundTime ()
+ {
+ for (int i = 0; i < 2000; i++)
+ {
+ DateTime datetime = new DateTime(random.Next(1970, 2100), random.Next(1, 13), random.Next(1, 28), random.Next(0, 24), random.Next(0, 60), random.Next(0, 60));
+ ushort intervalMins = (ushort)random.Next(1,61);
+ {
+ // Round up without seconds.
+ int expectedMins = ((datetime.Minute / intervalMins) + (datetime.Minute % intervalMins > 0 ? 1 : 0)) * intervalMins;
+ DateTime expected = datetime.Date + new TimeSpan(datetime.Hour,expectedMins,0);
+ DateTime rounded = DateUtil.RoundTime(datetime,intervalMins,DateUtil.RoundingDirection.Up,true);
+ Assert.AreEqual(DateUtil.FormatXmlDateTime(expected), DateUtil.FormatXmlDateTime(rounded));
+ }
+ {
+ // Round up with seconds.
+ int expectedMins = ((datetime.Minute / intervalMins) + ((datetime.Minute * 60 + datetime.Second) % (intervalMins * 60) > 0 ? 1 : 0)) * intervalMins;
+ DateTime expected = datetime.Date + new TimeSpan(datetime.Hour,expectedMins,0);
+ DateTime rounded = DateUtil.RoundTime(datetime,intervalMins,DateUtil.RoundingDirection.Up,false);
+ Assert.AreEqual(DateUtil.FormatXmlDateTime(expected), DateUtil.FormatXmlDateTime(rounded));
+ }
+ {
+ // Round down without seconds.
+ int expectedMins = (datetime.Minute / intervalMins) * intervalMins;
+ DateTime expected = datetime.Date + new TimeSpan(datetime.Hour,expectedMins,0);
+ DateTime rounded = DateUtil.RoundTime(datetime,intervalMins,DateUtil.RoundingDirection.Down,true);
+ Assert.AreEqual(DateUtil.FormatXmlDateTime(expected), DateUtil.FormatXmlDateTime(rounded));
+ }
+ {
+ // Round down with seconds.
+ int expectedMins = (datetime.Minute / intervalMins) * intervalMins;
+ DateTime expected = datetime.Date + new TimeSpan(datetime.Hour,expectedMins,0);
+ DateTime rounded = DateUtil.RoundTime(datetime,intervalMins,DateUtil.RoundingDirection.Down,false);
+ Assert.AreEqual(DateUtil.FormatXmlDateTime(expected), DateUtil.FormatXmlDateTime(rounded));
+ }
+ {
+ // Nearest without seconds.
+ int expectedMins = ((datetime.Minute / intervalMins) + (datetime.Minute % intervalMins > (intervalMins/2) ? 1 : 0)) * intervalMins;
+ DateTime expected = datetime.Date + new TimeSpan(datetime.Hour,expectedMins,0);
+ DateTime rounded = DateUtil.RoundTime(datetime,intervalMins,DateUtil.RoundingDirection.Nearest,true);
+ if (!DateUtil.FormatXmlDateTime(expected).Equals(DateUtil.FormatXmlDateTime(rounded)))
+ {
+ //String s = "";
+ }
+ Assert.AreEqual(DateUtil.FormatXmlDateTime(expected), DateUtil.FormatXmlDateTime(rounded));
+ }
+ {
+ // Nearest with seconds.
+ int expectedMins = ((datetime.Minute / intervalMins) + ((datetime.Minute * 60 + datetime.Second) % (intervalMins * 60) > (intervalMins*60/2) ? 1 : 0)) * intervalMins;
+ DateTime expected = datetime.Date + new TimeSpan(datetime.Hour,expectedMins,0);
+ DateTime rounded = DateUtil.RoundTime(datetime,intervalMins,DateUtil.RoundingDirection.Nearest,false);
+ if (!DateUtil.FormatXmlDateTime(expected).Equals(DateUtil.FormatXmlDateTime(rounded)))
+ {
+ //String s = "";
+ }
+ Assert.AreEqual(DateUtil.FormatXmlDateTime(expected), DateUtil.FormatXmlDateTime(rounded));
+ }
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // C o n v e r t F r o m S t r i n g
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void DateTime_ParseXmlDate ()
+ {
+ String xmlDate = "2015-05-28";
+ Assert.AreEqual(new DateTime(2015, 5, 28), DateUtil.ParseXmlDate(xmlDate));
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void DateTime_ParseXmlTime ()
+ {
+ String xmlTime = "16:25:57";
+ Assert.AreEqual(new DateTime(today.Year, today.Month, today.Day, 16, 25, 57), DateUtil.ParseXmlTime(xmlTime));
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void DateTime_ParseXmlDateTime ()
+ {
+ String xmlDateTime = "2010-05-28T17:05:01";
+ Assert.AreEqual(new DateTime(2010, 5, 28, 17, 5, 1), DateUtil.ParseXmlDateTime(xmlDateTime));
+ }
+
+ //-------------------------------------------------------------------------
+ // V a l i d a t i o n
+ //-------------------------------------------------------------------------
+ [TestMethod]
+ public void DateTime_IsValid ()
+ {
+ // Valid.
+ Assert.AreEqual(true, DateUtil.IsValid(DateTime.Now));
+ Assert.AreEqual(true, DateUtil.IsValid(DateTime.Today));
+ Assert.AreEqual(true, DateUtil.IsValid(new DateTime(2018, 2, 12, 10, 32, 56)));
+
+ // Invalid.
+ Assert.AreEqual(false, DateUtil.IsValid(DateTime.MinValue));
+ Assert.AreEqual(false, DateUtil.IsValid(DateTime.MaxValue));
+ Assert.AreEqual(false, DateUtil.IsValid(new DateTime(0)));
+ Assert.AreEqual(false, DateUtil.IsValid(new DateTime(1, 1, 1)));
+
+ // Nullable.
+ Assert.AreEqual(false, DateUtil.IsValid(null));
+ }
+
+ //-------------------------------------------------------------------
+ // Relative dates - Week.
+ // DateUtil.StartOfWeek
+ [TestMethod]
+ public void DateTime_StartOfWeek ()
+ {
+ for (int i = 0; i < 1000; i++)
+ {
+ // Random date between 0AD and 3000AD (or thereabouts):
+ DateTime date = new DateTime(random.Next(0, 365 * 3000) * TimeSpan.TicksPerDay);
+ //Trace.WriteLine("DateUtil_Tester.DateTime_StartOfWeek Date=" + date.Format("ddd d-MMM-yyyy") + ", Start of week=" + date.StartOfWeek().Format("ddd d-MMM-yyyy"));
+ Assert.AreEqual(date.StartOfWeek().Format("ddd"), "Sun");
+ }
+ }
+ // DateUtil.StartOfWeek
+ [TestMethod]
+ public void DateTime_StartOfWorkingWeek ()
+ {
+ for (int i = 0; i < 1000; i++)
+ {
+ // Random date between 0AD and 3000AD (or thereabouts):
+ DateTime date = new DateTime(random.Next(0, 365 * 3000) * TimeSpan.TicksPerDay);
+ //Trace.WriteLine("DateUtil_Tester.DateTime_StartOfWorkingWeek Date=" + date.Format("ddd d-MMM-yyyy") + ", Start of week=" + date.StartOfWorkingWeek().Format("ddd d-MMM-yyyy"));
+ Assert.AreEqual(date.StartOfWorkingWeek().Format("ddd"), "Mon");
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // Relative dates - Month.
+ // DateUtil.StartOfMonth
+ [TestMethod]
+ public void DateTime_StartOfMonth ()
+ {
+ DateTime dt20160723 = new DateTime(2016, 7, 23);
+ Assert.AreEqual(dt20160723.StartOfMonth(), new DateTime(2016, 7, 1));
+ }
+ // DateUtil.EndOfMonth
+ [TestMethod]
+ public void DateTime_DaysInMonth ()
+ {
+ DateTime dt20160723 = new DateTime(2016, 7, 23);
+ Assert.AreEqual(dt20160723.DaysInMonth(), 31);
+ }
+ // DateUtil.EndOfMonth
+ [TestMethod]
+ public void DateTime_EndOfMonth ()
+ {
+ DateTime dt20160723 = new DateTime(2016, 7, 23);
+ Assert.AreEqual(dt20160723.EndOfMonth(), new DateTime(2016, 7, 31));
+ }
+
+ //-------------------------------------------------------------------
+ // F o r m a t t i n g
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void DateTime_Format_Void ()
+ {
+ DateTime dt20160723 = new DateTime(2016, 7, 23);
+ DateTime dt20160723172829 = new DateTime(2016, 7, 23, 17, 28, 39);
+
+ Assert.AreEqual(dt20160723.Format("dd/MM/yyyy"), "23/07/2016");
+ Assert.AreEqual(dt20160723.Format("dd/MM/yyyy HH:mm:ss"), "23/07/2016 00:00:00");
+
+ Assert.AreEqual(dt20160723172829.Format("dd/MM/yyyy"), "23/07/2016");
+ Assert.AreEqual(dt20160723172829.Format("dd/MM/yyyy HH:mm:ss"), "23/07/2016 17:28:39");
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void DateTime_FormatXmlDate ()
+ {
+ DateTime dt20160723 = new DateTime(2016, 7, 23);
+ DateTime dt20160723172829 = new DateTime(2016, 7, 23, 17, 28, 39);
+
+ Assert.AreEqual("2016-07-23", DateUtil.FormatXmlDate(dt20160723));
+ Assert.AreEqual("2016-07-23", DateUtil.FormatXmlDate(dt20160723172829));
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void DateTime_FormatXmlTime ()
+ {
+ DateTime dt20160723172829 = new DateTime(2016, 7, 23, 17, 28, 39);
+
+ Assert.AreEqual("17:28:39", DateUtil.FormatXmlTime(dt20160723172829));
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void DateTime_FormatXmlDateTime ()
+ {
+ DateTime dt20160723172829 = new DateTime(2016, 7, 23, 17, 28, 39);
+
+ Assert.AreEqual("2016-07-23T17:28:39", DateUtil.FormatXmlDateTime(dt20160723172829));
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsCoreUnitTests/Standard/Base/Utils/EnumUtil_Tester.cs b/SacsCoreUnitTests/Standard/Base/Utils/EnumUtil_Tester.cs
new file mode 100644
index 0000000..b0fec91
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/Utils/EnumUtil_Tester.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sarand.UnitTests.Core.Base.Utils
+{
+ [TestClass]
+ public class EnumUtil_Tester
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void EnumUtil_IsValid ()
+ {
+ Assert.IsTrue(EnumUtil.IsValid(0));
+ Assert.IsTrue(EnumUtil.IsValid(1));
+ Assert.IsTrue(EnumUtil.IsValid(2));
+ Assert.IsTrue(EnumUtil.IsValid(3));
+ Assert.IsTrue(EnumUtil.IsValid(4));
+ Assert.IsTrue(EnumUtil.IsValid(5));
+ }
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void EnumUtil_GetName ()
+ {
+ Assert.AreEqual("Info", EnumUtil.GetName(SaMessageLevel.Info));
+ Assert.AreEqual("Debug", EnumUtil.GetName(SaMessageLevel.Debug));
+ }
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void EnumUtil_GetValues ()
+ {
+ Assert.AreEqual(9,EnumUtil.GetValues().ToList().Count);
+ Assert.AreEqual(SaMessageLevel.Info,EnumUtil.GetValues().ToArray()[6]);
+ }
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void EnumUtil_ParseEnum ()
+ {
+ Assert.AreEqual(SaMessageLevel.Info, EnumUtil.ParseEnum("Info"));
+ Assert.AreEqual(SaMessageLevel.Debug, EnumUtil.ParseEnum("Debug"));
+ }
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentException), "Requested value 'ABC' was not found.")]
+ public void EnumUtil_ParseEnum_Exception ()
+ {
+ SaMessageLevel messageLevel = EnumUtil.ParseEnum("ABC");
+ }
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void EnumUtil_StartsWith ()
+ {
+ Assert.AreEqual(SaMessageLevel.Info, EnumUtil.StartsWith("I", SaMessageLevel.Error));
+ Assert.AreEqual(SaMessageLevel.Info, EnumUtil.StartsWith("i", SaMessageLevel.Error));
+ Assert.AreEqual(SaMessageLevel.Info, EnumUtil.StartsWith("inf", SaMessageLevel.Error));
+ Assert.AreEqual(SaMessageLevel.Debug, EnumUtil.StartsWith("D", SaMessageLevel.Error));
+ Assert.AreEqual(SaMessageLevel.Debug, EnumUtil.StartsWith("d", SaMessageLevel.Error));
+ Assert.AreEqual(SaMessageLevel.Debug, EnumUtil.StartsWith("DE", SaMessageLevel.Error));
+ Assert.AreEqual(SaMessageLevel.Debug, EnumUtil.StartsWith("DEB", SaMessageLevel.Error));
+ Assert.AreEqual(SaMessageLevel.Debug, EnumUtil.StartsWith("DEBU", SaMessageLevel.Error));
+ Assert.AreEqual(SaMessageLevel.Debug, EnumUtil.StartsWith("DEBUG", SaMessageLevel.Error));
+ Assert.AreEqual(EnumUtil.StartsWith("Err", SaMessageLevel.Error), SaMessageLevel.Error);
+ Assert.AreEqual(SaMessageLevel.None, EnumUtil.StartsWith("", SaMessageLevel.Error));
+ Assert.AreEqual(SaMessageLevel.Error, EnumUtil.StartsWith("ABC", SaMessageLevel.Error));
+ }
+ //-------------------------------------------------------------------
+ }
+}
diff --git a/SacsCoreUnitTests/Standard/Base/Utils/FtpUtil_Tester.cs b/SacsCoreUnitTests/Standard/Base/Utils/FtpUtil_Tester.cs
new file mode 100644
index 0000000..2e1b7fe
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/Utils/FtpUtil_Tester.cs
@@ -0,0 +1,63 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+
+namespace Sarand.UnitTests.Core.Base.Utils
+
+{
+ [TestClass]
+ public class FtpUtil_Tester
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ private Random random = new Random();
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void FtpUtil_CleansePath ()
+ {
+ Assert.AreEqual("abc", FtpUtil.CleansePath("abc"));
+ Assert.AreEqual("abc", FtpUtil.CleansePath("/abc"));
+ Assert.AreEqual("abc", FtpUtil.CleansePath("\\abc"));
+ Assert.AreEqual("abc", FtpUtil.CleansePath("/abc/"));
+ Assert.AreEqual("abc", FtpUtil.CleansePath("\\abc\\"));
+ Assert.AreEqual("abc/def", FtpUtil.CleansePath("abc/def"));
+ Assert.AreEqual("abc/def", FtpUtil.CleansePath("abc\\def"));
+ Assert.AreEqual("abc/def", FtpUtil.CleansePath("/abc/def"));
+ Assert.AreEqual("abc/def", FtpUtil.CleansePath("\\abc\\def"));
+ Assert.AreEqual("abc/def", FtpUtil.CleansePath("abc/def/"));
+ Assert.AreEqual("abc/def", FtpUtil.CleansePath("abc\\def\\"));
+ Assert.AreEqual("abc/def", FtpUtil.CleansePath("/abc/def/"));
+ Assert.AreEqual("abc/def", FtpUtil.CleansePath("\\abc\\def\\"));
+ Assert.AreEqual("abc/def", FtpUtil.CleansePath("/abc//def/"));
+ Assert.AreEqual("abc/def", FtpUtil.CleansePath("\\abc\\\\def\\"));
+ Assert.AreEqual("abc-def", FtpUtil.CleansePath("abc-def"));
+ Assert.AreEqual("abc.def", FtpUtil.CleansePath("abc.def"));
+ }
+
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void FtpUtil_ParentFolder ()
+ {
+ Assert.AreEqual("", FtpUtil.GetParentFolder("abc"));
+ Assert.AreEqual("", FtpUtil.GetParentFolder("/abc"));
+ Assert.AreEqual("", FtpUtil.GetParentFolder("\\abc"));
+ Assert.AreEqual("", FtpUtil.GetParentFolder("/abc/"));
+ Assert.AreEqual("", FtpUtil.GetParentFolder("\\abc\\"));
+ Assert.AreEqual("abc", FtpUtil.GetParentFolder("abc/def"));
+ Assert.AreEqual("abc", FtpUtil.GetParentFolder("abc\\def"));
+ Assert.AreEqual("abc", FtpUtil.GetParentFolder("/abc/def"));
+ Assert.AreEqual("abc", FtpUtil.GetParentFolder("\\abc\\def"));
+ Assert.AreEqual("abc", FtpUtil.GetParentFolder("abc/def/"));
+ Assert.AreEqual("abc", FtpUtil.GetParentFolder("abc\\def\\"));
+ Assert.AreEqual("abc", FtpUtil.GetParentFolder("/abc/def/"));
+ Assert.AreEqual("abc", FtpUtil.GetParentFolder("\\abc\\def\\"));
+ Assert.AreEqual("abc", FtpUtil.GetParentFolder("/abc//def/"));
+ Assert.AreEqual("abc", FtpUtil.GetParentFolder("\\abc\\\\def\\"));
+ Assert.AreEqual("", FtpUtil.GetParentFolder("abc-def"));
+ Assert.AreEqual("", FtpUtil.GetParentFolder("abc.def"));
+ }
+
+ //-------------------------------------------------------------------
+ }
+}
diff --git a/SacsCoreUnitTests/Standard/Base/Utils/StringListUtil_Tester.cs b/SacsCoreUnitTests/Standard/Base/Utils/StringListUtil_Tester.cs
new file mode 100644
index 0000000..aeca396
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/Utils/StringListUtil_Tester.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sarand.UnitTests.Core.Base.Utils
+{
+ [TestClass]
+ public class StringListUtil_Tester
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ private Random random = new Random();
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void StringList_CreateList ()
+ {
+ List values = StringListUtil.CreateList("ABC",3);
+ Assert.AreEqual(3, values.Count);
+ Assert.AreEqual("ABC,ABC,ABC", values.Concat(","));
+ }
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void StringList_RemoveBlanks ()
+ {
+ List values = new List();
+ values.Add("");
+ values.Add("A");
+ values.Add(" ");
+ values.Add("B");
+ values.Add("");
+ values.Add("C");
+ Assert.AreEqual(6, values.Count);
+
+ // Remove blanks without trimming.
+ values.RemoveBlanks(false);
+ Assert.AreEqual(4, values.Count);
+
+ // Remove blanks with trimming.
+ values.RemoveBlanks(true);
+ Assert.AreEqual(3, values.Count);
+
+ // Only blanks in list.
+ values.Clear();
+ values.Add("");
+ values.Add(" ");
+ values.Add("");
+ values.Add(" ");
+ values.Add("");
+ Assert.AreEqual(5, values.Count);
+ values.RemoveBlanks();
+ Assert.AreEqual(0, values.Count);
+ }
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void StringList_RemoveDuplicatesCI ()
+ {
+ List values = new List();
+ values.Add("C");
+ values.Add("A");
+ values.Add("c");
+ values.Add("B");
+ values.Add("b");
+ values.Add("C");
+ Assert.AreEqual(6, values.Count);
+ Assert.AreEqual("C,A,C,B,B,C", values.Concat(",").ToUpper());
+
+ // Remove blanks without trimming.
+ values.RemoveDuplicatesCI();
+ Assert.AreEqual(3, values.Count);
+ Assert.AreEqual("C,A,B", values.Concat(","));
+ }
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void StringList_Split ()
+ {
+ // Simple delimiter.
+ List values = StringListUtil.Split("A:B:C",":");
+ Assert.AreEqual(3, values.Count);
+ Assert.AreEqual("A,B,C", values.Concat(","));
+
+ // Simple delimiter, null value.
+ values = StringListUtil.Split(null, ":");
+ Assert.AreEqual(0, values.Count);
+ Assert.AreEqual("", values.Concat(","));
+
+ // Simple delimiter, empty value.
+ values = StringListUtil.Split("", ":");
+ Assert.AreEqual(1, values.Count);
+ Assert.AreEqual("", values.Concat(","));
+
+ // Null delimiter.
+ values = StringListUtil.Split("A:B:C", null);
+ Assert.AreEqual(1, values.Count);
+ Assert.AreEqual("A:B:C", values.Concat(","));
+
+ // Blank delimiter.
+ values = StringListUtil.Split("A:B:C", "");
+ Assert.AreEqual(1, values.Count);
+ Assert.AreEqual("A:B:C", values.Concat(","));
+
+ // Blank values.
+ values = StringListUtil.Split("A::B: :C:::", ":", true, true);
+ Assert.AreEqual(3, values.Count);
+ Assert.AreEqual("A,B,C", values.Concat(","));
+ }
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void StringList_Concat ()
+ {
+ List values = new List();
+ values.Add("");
+ values.Add("A");
+ values.Add(" ");
+ values.Add("B");
+ values.Add("");
+ values.Add("C");
+ values.Add(" ");
+ Assert.AreEqual(7, values.Count);
+ Assert.AreEqual(",A,,B,,C,", values.Concat(","));
+ Assert.AreEqual(",A, ,B,,C, ", values.Concat(",", "", "", false, false));
+ Assert.AreEqual(",A,,B,,C,", values.Concat(",", "", "", true, false));
+ Assert.AreEqual("A,B,C", values.Concat(",", "", "", true, true));
+ }
+ //-------------------------------------------------------------------
+ }
+}
diff --git a/SacsCoreUnitTests/Standard/Base/Utils/StringUtil_Tester.cs b/SacsCoreUnitTests/Standard/Base/Utils/StringUtil_Tester.cs
new file mode 100644
index 0000000..dcdd71e
--- /dev/null
+++ b/SacsCoreUnitTests/Standard/Base/Utils/StringUtil_Tester.cs
@@ -0,0 +1,99 @@
+using System;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sarand.UnitTests.Core.Base.Utils
+{
+ [TestClass]
+ public class StringUtil_Tester
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ private Random random = new Random();
+
+ //-------------------------------------------------------------------
+ // P a d d i n g
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void StringUtil_PadLeft ()
+ {
+ // Leading spaces.
+ Assert.AreEqual(" ", "".PadLeft(3, ' '));
+ Assert.AreEqual(" A", "A".PadLeft(3, ' '));
+ Assert.AreEqual(" AB", "AB".PadLeft(3, ' '));
+ Assert.AreEqual("ABC", "ABC".PadLeft(3, ' '));
+ Assert.AreEqual("ABCD", "ABCD".PadLeft(3, ' '));
+
+ // Leading zeroes.
+ Assert.AreEqual("00", "".PadLeft(2, '0'));
+ Assert.AreEqual("01", "1".PadLeft(2, '0'));
+ Assert.AreEqual("123", "123".PadLeft(3, '0'));
+ Assert.AreEqual("0123", "123".PadLeft(4, '0'));
+ Assert.AreEqual("00123", "123".PadLeft(5, '0'));
+ }
+ [TestMethod]
+ public void StringUtil_PadRight ()
+ {
+ // Leading spaces.
+ Assert.AreEqual(" ", "".PadRight(3, ' '));
+ Assert.AreEqual("A ", "A".PadRight(3, ' '));
+ Assert.AreEqual("AB ", "AB".PadRight(3, ' '));
+ Assert.AreEqual("ABC", "ABC".PadRight(3, ' '));
+ Assert.AreEqual("ABCD", "ABCD".PadRight(3, ' '));
+
+ // Trailing zeroes.
+ Assert.AreEqual("00", "".PadRight(2, '0'));
+ Assert.AreEqual("10", "1".PadRight(2, '0'));
+ Assert.AreEqual("12", "12".PadRight(2, '0'));
+ Assert.AreEqual("123", "123".PadRight(3, '0'));
+ Assert.AreEqual("1230", "123".PadRight(4, '0'));
+ Assert.AreEqual("12300", "123".PadRight(5, '0'));
+ }
+
+ //-------------------------------------------------------------------
+ // S u b S t r i n g
+ //-------------------------------------------------------------------
+ [TestMethod]
+ public void StringUtil_SubstringSafe ()
+ {
+ Assert.AreEqual(StringUtil.SubstringSafe(null, 0), "");
+ Assert.AreEqual(StringUtil.SubstringSafe("", 0), "");
+ Assert.AreEqual("".SubstringSafe(0), "");
+
+ // Right portion from start position.
+ Assert.AreEqual("abc".SubstringSafe(-3), "abc");
+ Assert.AreEqual("abc".SubstringSafe(-2), "abc");
+ Assert.AreEqual("abc".SubstringSafe(-1), "abc");
+ Assert.AreEqual("abc".SubstringSafe(0), "abc");
+ Assert.AreEqual("abc".SubstringSafe(1), "bc");
+ Assert.AreEqual("abc".SubstringSafe(2), "c");
+ Assert.AreEqual("abc".SubstringSafe(3), "");
+
+ // Zero length.
+ Assert.AreEqual(StringUtil.SubstringSafe(null, 0, 0), "");
+ Assert.AreEqual(StringUtil.SubstringSafe("", 0, 0), "");
+ Assert.AreEqual("".SubstringSafe(0, 0), "");
+ Assert.AreEqual("abc".SubstringSafe(-1, 0), "");
+ Assert.AreEqual("abc".SubstringSafe(0, 0), "");
+ Assert.AreEqual("abc".SubstringSafe(1, 0), "");
+ Assert.AreEqual("abc".SubstringSafe(2, 0), "");
+ Assert.AreEqual("abc".SubstringSafe(3, 0), "");
+ Assert.AreEqual("abc".SubstringSafe(4, 0), "");
+
+ // Mid portion.
+ Assert.AreEqual("abcde".SubstringSafe(0, 3), "abc");
+ Assert.AreEqual("abcde".SubstringSafe(1, 3), "bcd");
+ Assert.AreEqual("abcde".SubstringSafe(2, 3), "cde");
+
+ // Excessive length.
+ Assert.AreEqual("abcde".SubstringSafe(-1, 12), "abcde");
+ Assert.AreEqual("abcde".SubstringSafe(0, 12), "abcde");
+ Assert.AreEqual("abcde".SubstringSafe(1, 12), "bcde");
+ Assert.AreEqual("abcde".SubstringSafe(2, 12), "cde");
+ Assert.AreEqual("abcde".SubstringSafe(3, 12), "de");
+ Assert.AreEqual("abcde".SubstringSafe(4, 12), "e");
+ Assert.AreEqual("abcde".SubstringSafe(5, 12), "");
+ }
+ //-------------------------------------------------------------------
+ }
+}
diff --git a/SacsCoreWeb/SacsCoreWeb.csproj b/SacsCoreWeb/SacsCoreWeb.csproj
new file mode 100644
index 0000000..e3c7980
--- /dev/null
+++ b/SacsCoreWeb/SacsCoreWeb.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netcoreapp3.1
+ Sacs.Net.Core
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SacsStandardBase/Base/App/SaLogger.cs b/SacsStandardBase/Base/App/SaLogger.cs
new file mode 100644
index 0000000..172a60f
--- /dev/null
+++ b/SacsStandardBase/Base/App/SaLogger.cs
@@ -0,0 +1,208 @@
+using System;
+using System.IO;
+using Sarand.Net.Standard.Base.App;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.IO;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sarand.Net.Standard.Base.App
+{
+ //=======================================================================
+ public static class SaLogger
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ private static String logDirectoryPath = String.Empty;
+ private static String logFilenamePrefix = String.Empty;
+
+ //-------------------------------------------------------------------
+ // Construction.
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public static SaMessageLevel ConsoleLevel { get; set; } = SaMessageLevel.Debug;
+ public static SaMessageLevel LogFileLevel { get; set; } = SaMessageLevel.Info;
+ public static String LogFolder
+ {
+ get { return logDirectoryPath; }
+ set
+ {
+ logDirectoryPath = "";
+ if (FileUtil.ValidateDirectory(value, true))
+ logDirectoryPath = value;
+ }
+ }
+ public static bool LoggingToFile
+ {
+ get { return !StringUtil.IsEmpty(logDirectoryPath); }
+ }
+ public static String LogFilenamePrefix
+ {
+ get { return StringUtil.EmptyOnNull(logFilenamePrefix); }
+ set { logFilenamePrefix = value; }
+ }
+ public static bool IncludeHostInFileMessage { get; set; } = true;
+ public static bool IncludeUserInFileMessage { get; set; } = true;
+
+ //-------------------------------------------------------------------
+ // Logging.
+ // TODO - Enhanced logging:
+ // Include: System.Runtime.CompilerServices
+ // public static void Error (String message,
+ // [System.Runtime.CompilerServices.CallerFilePath] string file = "",
+ // [System.Runtime.CompilerServices.CallerMemberName] string member = "",
+ // [System.Runtime.CompilerServices.CallerLineNumber] int line = 0)
+ // {
+ // Debug.WriteLine("{0}_{1}({2}): {3}", Path.GetFileName(file), member, line, message);
+ // }
+ public static bool Error (String message, bool logToFile = false)
+ {
+ Log(message, SaMessageLevel.Error, logToFile);
+ return false;
+ }
+ public static bool Warn (String message, bool logToFile = false)
+ {
+ Log(message, SaMessageLevel.Warning, logToFile);
+ return false;
+ }
+ public static void Info (String message, bool logToFile = false)
+ {
+ Log(message, SaMessageLevel.Info, logToFile);
+ }
+ public static void Debug (String message, bool logToFile = false)
+ {
+ Log(message, SaMessageLevel.Debug, logToFile);
+ }
+ public static void Trace (String message, bool logToFile = false)
+ {
+ Log(message, SaMessageLevel.Trace, logToFile);
+ }
+
+ //-------------------------------------------------------------------
+ ///
+ /// Log message.
+ ///
+ /// Error and warning messages are forced to file unless logToFile is explicitly false
+ ///
+ public static void Log (String message, SaMessageLevel level = SaMessageLevel.Info, bool? logToFile = null)
+ {
+ if (level == SaMessageLevel.None) return;
+ message = message.Trim();
+ WriteToConsole(FormatMessage(message, level), level);
+ if (LogToFile(level, logToFile))
+ WriteToLogFile(FormatMessage(message, level, true), level);
+ }
+ private static bool LogToFile (SaMessageLevel level, bool? logToFile)
+ {
+ switch (level)
+ {
+ case SaMessageLevel.Fatal:
+ case SaMessageLevel.Exception:
+ case SaMessageLevel.Error:
+ case SaMessageLevel.Warning:
+ return true;
+ }
+ return (logToFile != null && logToFile == true);
+ }
+ private static String FormatMessage (String message, SaMessageLevel level, bool forFile = false)
+ {
+ DateTime now = DateTime.Now;
+ if (forFile)
+ return
+ now.ToString("yyyyMMdd:HHmmss ") + GetLevelAbbrFile(level) + " " +
+ (IncludeHostInFileMessage ? SacsSystem.ComputerName.PadRight(12) + " " : "") +
+ (IncludeUserInFileMessage ? SacsSystem.UserName.PadRight(16) + " " : "") +
+ message;
+ return now.ToString("yyyyMMdd:HHmmss ") + GetLevelAbbrFile(level) + " " + message;
+ }
+
+ //-------------------------------------------------------------------
+ /// Message level abbreviation.
+ public static String GetLevelAbbr (SaMessageLevel level)
+ {
+ String abbr = SacsSystem.GetMessageLevelCode(level);
+ return (!StringUtil.IsEmpty(abbr) ? abbr : "UNK");
+ }
+ private static String GetLevelAbbrFile (SaMessageLevel level)
+ {
+ return GetLevelAbbr(level).PadRight(3, ' ').Substring(0, 3).ToUpper();
+ }
+ private static String GetLevelAbbrConsole (SaMessageLevel level)
+ {
+ // Visual Studio Output window colour coded snippets.
+ if (level == SaMessageLevel.Fatal) return "Error [Fatal]";
+ if (level == SaMessageLevel.Exception) return "Error [Exception]";
+ if (level == SaMessageLevel.Error) return "Error";
+ if (level == SaMessageLevel.Warning) return "Warning";
+ return GetLevelAbbr(level).PadRight(3, ' ').Substring(0, 3).ToUpper();
+ }
+
+ //-------------------------------------------------------------------
+ public static void WriteBlankLine (bool logToFile = false)
+ {
+ WriteToConsole("", SaMessageLevel.Error);
+ if (logToFile)
+ WriteToLogFile("", SaMessageLevel.Error);
+ }
+ public static void WriteBlankLines(int lineCount, bool logToFile = false)
+ {
+ for (int i = 0; i < Math.Min(10, lineCount); i++)
+ {
+ WriteToConsole("", SaMessageLevel.Error);
+ if (logToFile)
+ WriteToLogFile("", SaMessageLevel.Error);
+ }
+ }
+
+ //-------------------------------------------------------------------
+ private static void WriteToConsole (String message, SaMessageLevel level = SaMessageLevel.Info)
+ {
+#if DEBUG
+ // NB There should be a way to colour error/warning messages.
+ // Ref. https://stackoverflow.com/questions/2426609/how-do-i-write-color-text-to-the-visual-studio-output-window-from-c
+ // Console output.
+ if (level.CompareTo(ConsoleLevel) <= 0)
+ {
+ // Use Debug.Write rather than Console.Write.
+ System.Diagnostics.Debug.WriteLine(message);
+ //System.Console.WriteLine(message));
+ }
+#endif
+ }
+ // Ref. https://stackoverflow.com/questions/6195084/thread-safe-logging-class-implementation
+ private static readonly object _syncObject = new object();
+ private static void WriteToLogFile (String message, SaMessageLevel level = SaMessageLevel.Info)
+ {
+ // File output - skip if not logging to file or message level is higher than that for logging to file.
+ if (!LoggingToFile || level.CompareTo(LogFileLevel) > 0) return;
+ // Only one thread can own the lock, so other threads entering
+ // this method will wait here until lock is available.
+ lock (_syncObject)
+ {
+ String logFolder = GetLogFolder();
+ if (FileUtil.ValidateDirectory(logFolder, true))
+ {
+ String logPath = logFolder + Path.DirectorySeparatorChar + GetLogFilename();
+ if (!StringUtil.IsEmpty(message) || File.Exists(logPath))
+ {
+ using (StreamWriter file = File.AppendText(logPath))
+ file.WriteLine(message);
+ }
+ }
+ }
+ }
+
+ //-------------------------------------------------------------------
+ private static String GetLogFolder ()
+ {
+ return logDirectoryPath + Path.DirectorySeparatorChar + DateTime.Today.Format("yyyy") + Path.DirectorySeparatorChar + DateTime.Today.Format("yyyyMM");
+ }
+ private static String GetLogFilename ()
+ {
+ return FileUtil.FormatFilename(LogFilenamePrefix + (!StringUtil.IsEmpty(LogFilenamePrefix) ? "-" : "") + DateTime.Today.Format("yyyyMMdd") + ".log");
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/App/SacsApplication.cs b/SacsStandardBase/Base/App/SacsApplication.cs
new file mode 100644
index 0000000..1a68e76
--- /dev/null
+++ b/SacsStandardBase/Base/App/SacsApplication.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Sarand.Net.Standard.Base.App
+{
+ //=======================================================================
+ ///
+ /// Base application object.
+ ///
+ public class SacsApplication
+ {
+ //-------------------------------------------------------------------
+ // Protected construction.
+ protected SacsApplication()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/App/SacsCommandLineInfo.cs b/SacsStandardBase/Base/App/SacsCommandLineInfo.cs
new file mode 100644
index 0000000..7a2a245
--- /dev/null
+++ b/SacsStandardBase/Base/App/SacsCommandLineInfo.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sarand.Net.Standard.Base.App
+{
+ //=======================================================================
+ ///
+ /// Aids in parsing the command line at application startup.
+ /// Ref. CCommandLineInfo Class
+ ///
+ public class SacsCommandLineInfo
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ public class Param
+ {
+ internal Param (String name, String value)
+ {
+ Name = StringUtil.EmptyOnNull(name);
+ Value = StringUtil.EmptyOnNull(value);
+ }
+ public String Name { get; }
+ public String Value { get; }
+ }
+ public enum ShellCommand
+ {
+ /// Indicates that no file name was found on the command line.
+ FileNew,
+ /// Indicates that a file name was found on the command line and that none of the following flags were found on the command line: /p, /pt, /dde.
+ FileOpen,
+ /// Indicates that the /p flag was found on the command line.
+ FilePrint,
+ /// Indicates that the /pt flag was found on the command line.
+ FilePrintTo,
+ /// Indicates that the /dde flag was found on the command line.
+ FileDDE,
+ /// Indicates that the /Register or /Regserver flag was found on the command line and the application was asked to register.
+ AppRegister,
+ /// Indicates that the /Unregister or /Unregserver application was asked to unregister.
+ AppUnregister,
+ /// Indicates that the application was restarted by the restart manager.
+ RestartByRestartManager,
+ FileNothing = -1
+ };
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private readonly List parameters = new List();
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SacsCommandLineInfo (string[] args)
+ {
+ // Intercept specific arguments.
+ foreach (String arg in args)
+ {
+ if (arg.EqualsCI("/Automation")) RunAutomated = true;
+ if (arg.EqualsCI("/Embedding")) RunEmbedded = true;
+ }
+
+ // Store raw parameters.
+ parameters.AddRange(args);
+
+ // Extract process definitions.
+ foreach (String arg in args)
+ {
+ if (arg.StartsWith("-") || arg.StartsWith("/"))
+ {
+ String processDfnArg = StringUtil.SubstringSafe(arg,1);
+ if (SacsProcessDefinition.IsValidProcessDefinition(processDfnArg))
+ {
+ try
+ {
+ processDefinitions.Add(new SacsProcessDefinition(processDfnArg));
+ }
+ catch (Exception exc)
+ {
+ SaLogger.Warn(GetType().Name + ".CTR Invalid process definition: " + exc.Message, true);
+ }
+ }
+ }
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ /// Indicates that the /Automation flag was found on the command line.
+ public bool RunAutomated { get; private set; } = false;
+ /// Indicates that the /Embedding flag was found on the command line.
+ public bool RunEmbedded { get; private set; } = false;
+ /// Indicates that the splash screen should be displayed.
+ public bool ShowSplash { get; private set; } = false;
+
+ //-------------------------------------------------------------------
+ // Printing properties.
+ /// Indicates the file name to be opened or printed; empty if the shell command is New or DDE.The value of the first non-flag parameter on the command line.
+ public String FileName { get; private set; } = "";
+ /// Indicates the printer name if the shell command is Print To; otherwise empty.The value of the second non-flag parameter on the command line.
+ public String PrinterName { get; private set; } = "";
+ /// Indicates the driver name if the shell command is Print To; otherwise empty.The value of the third non-flag parameter on the command line.
+ public String DriverName { get; private set; } = "";
+ /// Indicates the port name if the shell command is Print To; otherwise empty.The value of the fourth non-flag parameter on the command line.
+ public String PortName { get; private set; } = "";
+
+ //-------------------------------------------------------------------
+ // Restart properties.
+ ///
+ /// Indicates the unique restart identifier for the restart manager if the restart manager restarted the application.
+ /// The restart identifier is unique for each instance of the application.
+ /// If the restart manager exits the application and is configured to restart it, the restart manager executes the application from the command line with the restart identifier as an optional parameter.
+ /// When the restart manager uses the restart identifier, the application can reopen the previously open documents and recover autosaved files.
+ ///
+ public String RestartIdentifier { get; private set; } = "";
+
+ //-------------------------------------------------------------------
+ // Application parameters.
+ public int ParamCount { get { return parameters.Count; } }
+ public List Params { get { return new List(parameters); } }
+
+ // Redirected from.
+ public String RedirectedFrom { get; private set; } = "";
+
+ //-------------------------------------------------------------------
+ // Process definitions.
+ public readonly List processDefinitions = new List();
+ public List ProcessDefinitions { get { return new List(processDefinitions); } }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/App/SacsProcessDefinition.cs b/SacsStandardBase/Base/App/SacsProcessDefinition.cs
new file mode 100644
index 0000000..3922555
--- /dev/null
+++ b/SacsStandardBase/Base/App/SacsProcessDefinition.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sarand.Net.Standard.Base.App
+{
+ //=======================================================================
+ ///
+ /// Process definitions found on command line.
+ /// Used to automatically run process on start up, and then close application.
+ /// Command line examples:
+ /// app /Process:ExportData
+ /// app /SaProc:ExportData
+ /// app /Proc:ProcId=ExportData;User=uat
+ /// app /SaProcess:Proc=ExportData;User=uat
+ ///
+ public class SacsProcessDefinition
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ private static List validProcessPrefix = StringListUtil.Split("Proc,Process,SaProc,SaProcess",",");
+ private static List validProcessNameParam = StringListUtil.Split("Proc,Process,ProcId,ProcessId",",");
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SacsProcessDefinition (String commandLineArg)
+ {
+ // First field should be a valid process prefix.
+ if (!IsValidProcessDefinition(commandLineArg)) throw new NotSupportedException("SacsProcessDefinition Invalid process command line arg: '" + commandLineArg + "'");
+
+ // A single colon is expected.
+ String[] fields = commandLineArg.Split(':');
+ if (fields.Length != 2) throw new NotSupportedException("SacsProcessDefinition Colon expected in command line arg: '" + commandLineArg + "'");
+
+ // Extract process params.
+ String[] processParams = fields[1].Split(';');
+ foreach (String processParam in processParams)
+ {
+ // Each parameter is either delimited as 'name=value' or is interpretted as the process name if no equals.
+ String[] paramValues = processParam.Split('=');
+ if (paramValues.Length == 1 || (paramValues.Length == 2 && validProcessNameParam.ContainsCI(paramValues[0])))
+ {
+ // Parameter is the process name.
+ String processName = (paramValues.Length == 1 ? paramValues[0] : paramValues[1]);
+ if (!StringUtil.IsEmpty(ProcessName)) throw new NotSupportedException("SacsProcessDefinition Multiple process names in command line arg: '" + commandLineArg + "'");
+ ProcessName = processName;
+ }
+ else
+ {
+ if (Params.HasValue(paramValues[0])) throw new NotSupportedException("SacsProcessDefinition Duplicate param name '"+ paramValues[0] + "' in command line arg: '" + commandLineArg + "'");
+ Params.AddValue(paramValues[0], new DataValue(paramValues[1]));
+ }
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public String ProcessName { get; }
+ public NamedValue.Map Params { get; } = new NamedValue.Map();
+
+ //-------------------------------------------------------------------
+ // Utilities.
+ public static bool IsValidProcessDefinition (String commandLineArg)
+ {
+ if (StringUtil.IsEmpty(commandLineArg)) return false;
+ foreach (String validPrefix in validProcessPrefix)
+ {
+ if (commandLineArg.StartsWithCI(validPrefix + ":"))
+ return true;
+ }
+ return false;
+ }
+
+ //-------------------------------------------------------------------
+ }
+
+ //=======================================================================
+ public static class SacsProcessDefinitionList
+ {
+ //-------------------------------------------------------------------
+ // Locate process in the list.
+ public static SacsProcessDefinition GetProcessDefinition (this List values, String name)
+ {
+ return values.Find(item => item.ProcessName.EqualsCI(name));
+ }
+
+ //-------------------------------------------------------------------
+ }
+
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/App/SacsSessionConfig.cs b/SacsStandardBase/Base/App/SacsSessionConfig.cs
new file mode 100644
index 0000000..f860608
--- /dev/null
+++ b/SacsStandardBase/Base/App/SacsSessionConfig.cs
@@ -0,0 +1,19 @@
+namespace Sarand.Net.Standard.Base.App
+{
+ //=======================================================================
+ //[Serializable()]
+ public class SacsSessionConfig
+ {
+ //-------------------------------------------------------------------
+ // Construction.
+ protected SacsSessionConfig ()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/App/SacsSessionContext.cs b/SacsStandardBase/Base/App/SacsSessionContext.cs
new file mode 100644
index 0000000..c223093
--- /dev/null
+++ b/SacsStandardBase/Base/App/SacsSessionContext.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Sarand.Net.Standard.Base.App
+{
+ //=======================================================================
+ public class SacsSessionContext
+ {
+ //-------------------------------------------------------------------
+ /// Hidden construction for deserialisation.
+ protected SacsSessionContext()
+ {
+ }
+ // Protected construction - use derived object.
+ protected SacsSessionContext(ushort sessionIdx)
+ {
+ Index = sessionIdx;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public ushort Index { get; protected set; }
+
+ //-------------------------------------------------------------------
+ // Comparison.
+ public bool Equals(SacsSessionContext sessionContext)
+ {
+ if (sessionContext.Index != Index) return false;
+ return true;
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/App/SacsSystem.cs b/SacsStandardBase/Base/App/SacsSystem.cs
new file mode 100644
index 0000000..9ff2032
--- /dev/null
+++ b/SacsStandardBase/Base/App/SacsSystem.cs
@@ -0,0 +1,201 @@
+using System;
+using System.Collections.Generic;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sarand.Net.Standard.Base.App
+{
+ //=======================================================================
+ public partial class SacsSystem
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ private static readonly List statusCodes = new List();
+ private static readonly List messageLevels = new List();
+ private static readonly List interfaceCategories = new List();
+ private static readonly List interfaceProcessDirections = new List();
+
+ //-------------------------------------------------------------------
+ // Static initialisation.
+ static SacsSystem ()
+ {
+ // Status codes.
+ statusCodes.Add(SaStatus.Current, "CUR", "Current");
+ statusCodes.Add(SaStatus.Redundant, "RED", "Redundant");
+ statusCodes.Add(SaStatus.Superseded, "SUP", "Superseded");
+ statusCodes.Add(SaStatus.Discontinued, "DSC", "Discontinued");
+ statusCodes.Add(SaStatus.Provisional, "PRO", "Provisional");
+ statusCodes.Add(SaStatus.Confirmed, "CON", "Confirmed");
+ statusCodes.Add(SaStatus.Enabled, "ENA", "Enabled");
+ statusCodes.Add(SaStatus.Disabled, "DIS", "Disabled");
+ statusCodes.Add(SaStatus.Created, "CRE", "Created");
+ statusCodes.Add(SaStatus.Updated, "UPD", "Updated");
+ statusCodes.Add(SaStatus.Reinstated, "REI", "Reinstated");
+ statusCodes.Add(SaStatus.Deleted, "DEL", "Deleted");
+ statusCodes.Add(SaStatus.Required, "REQ", "Required");
+ statusCodes.Add(SaStatus.Awaiting, "AWA", "Awaiting");
+ statusCodes.Add(SaStatus.Preparation, "PRE", "Preparation");
+ statusCodes.Add(SaStatus.Raised, "RAI", "Raised");
+ statusCodes.Add(SaStatus.InProgress, "WIP", "In progress");
+ statusCodes.Add(SaStatus.Active, "ATV", "Active");
+ statusCodes.Add(SaStatus.Inactive, "INA", "Inactive");
+ statusCodes.Add(SaStatus.OnHold, "HLD", "On Hold");
+ statusCodes.Add(SaStatus.Imported, "IMP", "Imported");
+ statusCodes.Add(SaStatus.Exported, "EXP", "Exported");
+ statusCodes.Add(SaStatus.Open, "OPE", "Open");
+ statusCodes.Add(SaStatus.Closed, "CLO", "Closed");
+ statusCodes.Add(SaStatus.Completed, "COM", "Completed");
+ statusCodes.Add(SaStatus.Invoiced, "INV", "Invoiced");
+ statusCodes.Add(SaStatus.Cancelled, "CAN", "Cancelled");
+ statusCodes.Add(SaStatus.Successful, "SUC", "Successful");
+ statusCodes.Add(SaStatus.Failed, "FAI", "Failed");
+ statusCodes.Add(SaStatus.Actioned, "ACT", "Actioned");
+ statusCodes.Add(SaStatus.Authorised, "AUT", "Authorised");
+ statusCodes.Add(SaStatus.Accepted, "ACC", "Accepted");
+ statusCodes.Add(SaStatus.Rejected, "REJ", "Rejected");
+ statusCodes.Add(SaStatus.Submitted, "SUB", "Submitted");
+ statusCodes.Add(SaStatus.Queried, "QUE", "Queried");
+ statusCodes.Add(SaStatus.Granted, "GRA", "Granted");
+ statusCodes.Add(SaStatus.Denied, "DEN", "Denied");
+ statusCodes.Add(SaStatus.Logged, "LOG", "Logged");
+
+ // Message levels.
+ messageLevels.Add(SaMessageLevel.Fatal, "FAT", "Fatal");
+ messageLevels.Add(SaMessageLevel.Exception, "EXC", "Exception");
+ messageLevels.Add(SaMessageLevel.Error, "ERR", "Error");
+ messageLevels.Add(SaMessageLevel.Warning, "WRN", "Warning");
+ messageLevels.Add(SaMessageLevel.Info, "INF", "Info");
+ messageLevels.Add(SaMessageLevel.Debug, "DBG", "Debug");
+ messageLevels.Add(SaMessageLevel.Trace, "TRC", "Trace");
+
+ // Interface categories.
+ interfaceCategories.Add(SaInterfaceCategory.Website, "WEB", "Website");
+ interfaceCategories.Add(SaInterfaceCategory.Accounts, "ACC", "Accounts");
+ interfaceCategories.Add(SaInterfaceCategory.System, "SYS", "System");
+ interfaceCategories.Add(SaInterfaceCategory.File, "FIL", "File");
+
+ // Interface process directions.
+ interfaceProcessDirections.Add(SaInterfaceProcessDirection.Inbound, "INB", "Inbound");
+ interfaceProcessDirections.Add(SaInterfaceProcessDirection.Outbound, "OUT", "Outbound");
+ interfaceProcessDirections.Add(SaInterfaceProcessDirection.Both, "BTH", "In/Out");
+ }
+
+ //-------------------------------------------------------------------
+ // Protected Construction.
+ protected SacsSystem () : base()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // E n v i r o n m e n t
+ //-------------------------------------------------------------------
+ public enum AppMode { Debug, Release, }
+ /// Debug or Release.
+ public static AppMode Mode { get; } =
+#if !DEBUG
+ AppMode.Release;
+#else
+ AppMode.Debug;
+#endif
+ public static bool DebugMode { get { return Mode == AppMode.Debug; } }
+ public static bool ReleaseMode { get { return Mode == AppMode.Release; } }
+ public static bool IsDevHost ()
+ {
+ return ComputerName.StartsWithCI("sarand");
+ }
+
+ //-------------------------------------------------------------------
+ ///
+ /// Computer name
+ /// Windows: System.Environment.MachineName
+ /// Web: HttpContext.Current.Server.MachineName
+ /// FQDN: System.Net.Dns.GetHostName()
+ ///
+ public static String ComputerName { get; protected set; } = String.Empty;
+
+ //-------------------------------------------------------------------
+ /// To be populated by applicationWindows: Environment.UserName
+ public static String UserName { get; protected set; } = String.Empty;
+
+ //-------------------------------------------------------------------
+ ///
+ /// Application Path.
+ /// Windows: System.Reflection.Assembly.GetEntryAssembly().Location;
+ ///
+ public static String ApplicationPath { get; protected set; } = String.Empty;
+
+ //-------------------------------------------------------------------
+ ///
+ /// Application folder.
+ /// Windows: System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
+ ///
+ public static String ApplicationFolder { get; protected set; } = String.Empty;
+
+ //-------------------------------------------------------------------
+ ///
+ /// DateTime when the executing assembly was built.
+ ///
+ public static DateTime AssemblyBuildAt { get; set; } = DateTime.Now;
+
+ //-------------------------------------------------------------------
+ // C o d e L i s t s
+ //-------------------------------------------------------------------
+ public static List GetCodesOfType (SaSysCodeType codeType)
+ {
+ switch (codeType)
+ {
+ case SaSysCodeType.MessageLevel:
+ return new List(messageLevels);
+ case SaSysCodeType.InterfaceCategory:
+ return new List(interfaceCategories);
+ case SaSysCodeType.InterfaceProcessDirection:
+ return new List(interfaceProcessDirections);
+ }
+ return null;
+ }
+
+ //-------------------------------------------------------------------
+ // Status utilities.
+ public static SaStatus GetStatusForCode (String status)
+ {
+ return (SaStatus)statusCodes.GetTypeForCode(status,(int)SaStatus.Unknown);
+ }
+ public static String GetStatusCode (SaStatus status)
+ {
+ return statusCodes.GetCodeForType(status, status.ToString());
+ }
+ public static String GetStatusDesc (SaStatus status)
+ {
+ return statusCodes.GetDescForType(status, status.ToString());
+ }
+ public static String GetStatusDescForCode (String status)
+ {
+ return statusCodes.GetDescForCode(status, status);
+ }
+ public static List GetStatusCodeList (List statuses)
+ {
+ List statusCodeList = new List();
+ foreach (var status in statuses)
+ {
+ TypeCodeDesc statusCode = statusCodes.Find(x => x.Type==(int)status);
+ if (statusCode != null)
+ statusCodeList.Add(statusCode);
+ }
+ return statusCodeList;
+ }
+
+ //-------------------------------------------------------------------
+ // Message level utilities.
+ public static SaMessageLevel GetMessageLevelFromCode (String code)
+ {
+ return (SaMessageLevel)messageLevels.GetTypeForCode(code);
+ }
+ public static String GetMessageLevelCode (SaMessageLevel level)
+ {
+ return messageLevels.GetCodeForType(level);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/App/SacsVersion.cs b/SacsStandardBase/Base/App/SacsVersion.cs
new file mode 100644
index 0000000..51bb70f
--- /dev/null
+++ b/SacsStandardBase/Base/App/SacsVersion.cs
@@ -0,0 +1,75 @@
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Sarand.Net.Standard.Base.App
+{
+ //=======================================================================
+ public class SacsVersion
+ {
+ //-------------------------------------------------------------------
+ // Construction.
+ public SacsVersion (String name, String version, DateTime buildAt)
+ {
+ Name = name;
+ Version = version;
+ BuildAt = buildAt;
+
+ try
+ {
+ String[] versionInts = version.Split(new char[] { ',' }, 4);
+ if (versionInts.Length > 0) Ver1 = (UInt16)StringUtil.ToShort(versionInts[0]);
+ if (versionInts.Length > 1) Ver2 = (UInt16)StringUtil.ToShort(versionInts[1]);
+ if (versionInts.Length > 2) Ver3 = (UInt16)StringUtil.ToShort(versionInts[2]);
+ if (versionInts.Length > 3) Ver4 = (UInt16)StringUtil.ToShort(versionInts[3]);
+ }
+ catch (Exception exc)
+ {
+ SaLogger.Warn(GetType().Name + " " + exc.GetType().Name + " on parsing version: " + exc.Message);
+ }
+ VersionInt = Ver1 << 48 + Ver2 << 32 + Ver3 << 16 + Ver4;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public String Name { get; }
+ public String Version { get; }
+ public Int64 VersionInt { get; }
+ public UInt16 Ver1 { get; }
+ public UInt16 Ver2 { get; }
+ public UInt16 Ver3 { get; }
+ public UInt16 Ver4 { get; }
+ public DateTime BuildAt { get; }
+
+ //-------------------------------------------------------------------
+ // Comparison.
+ public override bool Equals (object obj)
+ {
+ if (obj is SacsVersion) return Equals(obj as SacsVersion);
+ return base.Equals(obj);
+ }
+ public override int GetHashCode ()
+ {
+ var hashCode = 2121240903;
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Version);
+ hashCode = hashCode * -1521134295 + VersionInt.GetHashCode();
+ return hashCode;
+ }
+ public bool Equals (SacsVersion version)
+ {
+ if (VersionInt == version.VersionInt) return true;
+ return false;
+ }
+ public int CompareTo (SacsVersion version, bool includeName = false)
+ {
+ int versionCompare = VersionInt.CompareTo(version.VersionInt);
+ if (!includeName || versionCompare != 0) return versionCompare;
+ return Name.CompareTo(version.Name);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/Bus/ItemId.cs b/SacsStandardBase/Base/Bus/ItemId.cs
new file mode 100644
index 0000000..6b05a4d
--- /dev/null
+++ b/SacsStandardBase/Base/Bus/ItemId.cs
@@ -0,0 +1,109 @@
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Xml.Serialization;
+
+namespace Sarand.Net.Standard.Base.Bus
+{
+ //=======================================================================
+ [Serializable()]
+ public class ItemId
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ public enum ItemIdType { Unknown, SysId, String }
+ private ItemIdType idType = ItemIdType.Unknown;
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public ItemId() { }
+ public ItemId(short itemType, SysId id, String caption = "")
+ {
+ Type = itemType;
+ Id = SysId.InvalidOnNull(id);
+ idType = ItemIdType.SysId;
+ Caption = StringUtil.EmptyOnNull(caption);
+ }
+ public ItemId(short itemType, String stringId, String caption = "")
+ {
+ Type = itemType;
+ StringId = StringUtil.EmptyOnNull(stringId);
+ idType = ItemIdType.String;
+ Caption = StringUtil.EmptyOnNull(caption);
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ [XmlAttribute]
+ public short Type { get; private set; } = 0;
+ [XmlAttribute]
+ public SysId Id { get; private set; } = new SysId();
+ public String StringId { get; private set; } = "";
+ public ItemIdType IdType { get { return idType; } }
+ // Caption - display text to identify this item in a list.
+ public String Caption { get; set; } = "";
+ // Data value - additional information for this item.
+ public String Data { get; set; } = "";
+
+ //-------------------------------------------------------------------
+ // Utilities.
+ public static bool IsValid(ItemId itemId)
+ {
+ if (itemId == null) return false;
+ if (itemId.Type < 1) return false;
+ if (itemId.idType == ItemIdType.SysId && !SysId.IsValid(itemId.Id)) return false;
+ if (itemId.idType == ItemIdType.String && StringUtil.IsEmpty(itemId.StringId)) return false;
+ return true;
+ }
+
+ //-------------------------------------------------------------------
+ // Comparison.
+ public override bool Equals(object obj)
+ {
+ if (obj == null) return false;
+ return Equals(obj as ItemId);
+ }
+ public bool Equals(ItemId itemId)
+ {
+ if (itemId == null) return false;
+ if (this.Type != itemId.Type) return false;
+ if (this.idType != itemId.idType) return false;
+ if (idType == ItemIdType.SysId && SysId.ToInt(Id) != SysId.ToInt(itemId.Id)) return false;
+ if (idType == ItemIdType.String && !StringId.EqualsCI(itemId.StringId)) return false;
+ return true;
+ }
+ public override int GetHashCode()
+ {
+ return SysId.ToInt(Id);
+ }
+
+ //-------------------------------------------------------------------
+ // Conversion.
+ public override String ToString()
+ {
+ return ToString(this);
+ }
+ // Null-safe static versions.
+ public static String ToString(ItemId itemId)
+ {
+ return ToString(itemId, String.Empty);
+ }
+ public static String ToString(ItemId itemId, String invalidValue)
+ {
+ if (itemId == null || !IsValid(itemId)) return invalidValue;
+ return itemId.Type.ToString() + (SysId.IsValid(itemId.Id) ? ":" + SysId.ToString(itemId.Id) : "");
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+ public static class ItemIdList
+ {
+ public static void Add(this List values, short type, SysId id)
+ {
+ values.Add(new ItemId(type, id));
+ }
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/Bus/SaErrorStateItem.cs b/SacsStandardBase/Base/Bus/SaErrorStateItem.cs
new file mode 100644
index 0000000..288e7c1
--- /dev/null
+++ b/SacsStandardBase/Base/Bus/SaErrorStateItem.cs
@@ -0,0 +1,105 @@
+using System;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sarand.Net.Standard.Base.Bus
+{
+ //=======================================================================
+ public abstract class SaErrorStateItem : SacsDisposable, ISacsStateItem
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ public enum ErrorMessage
+ {
+ // Item validation.
+ ItemNotSpecified,
+ ValiditemId,
+ InvalidItemId,
+ // Common properties.
+ InvalidStatus,
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Static initialisation.
+ static SaErrorStateItem ()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Construction.
+ protected SaErrorStateItem ()
+ {
+ }
+ protected override void Dispose (bool disposing)
+ {
+ if (disposing)
+ {
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ //-------------------------------------------------------------------
+ // E r r o r S t a t e
+ //-------------------------------------------------------------------
+ private SacsErrorState errorState = null;
+ public bool HasErrorState ()
+ {
+ return errorState != null;
+ }
+ public SacsErrorState ErrorState
+ {
+ get { return errorState; }
+ }
+ public String ErrorStateMessage
+ {
+ get { return (errorState != null ? errorState.Message : String.Empty); }
+ }
+ ///
+ /// Record the error state message.
+ /// Blank message does not set the error state - leaves previous state intact.
+ /// Return value is fale unless the message is blank.
+ ///
+ public bool SetErrorState (String message)
+ {
+ if (StringUtil.IsEmpty(message)) return true;
+ this.errorState = new SacsErrorState(message);
+ return false;
+ }
+ /// Clear the error state.
+ public void ClearErrorState ()
+ {
+ errorState = null;
+ }
+
+ //-------------------------------------------------------------------
+ // Error state population.
+ protected bool SetErrorState (ErrorMessage errorMessage)
+ {
+ String message = errorMessage.ToString();
+ switch (errorMessage)
+ {
+ case ErrorMessage.ItemNotSpecified:
+ message = "Item not specified.";
+ break;
+ case ErrorMessage.ValiditemId:
+ message = "Item has a valid id.";
+ break;
+ case ErrorMessage.InvalidItemId:
+ message = "Invalid item id.";
+ break;
+ case ErrorMessage.InvalidStatus:
+ message = "Invalid status.";
+ break;
+ }
+ return SetErrorState(message);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/Bus/SacsHierarchyItem.cs b/SacsStandardBase/Base/Bus/SacsHierarchyItem.cs
new file mode 100644
index 0000000..21a9f02
--- /dev/null
+++ b/SacsStandardBase/Base/Bus/SacsHierarchyItem.cs
@@ -0,0 +1,466 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Sarand.Net.Standard.Base.App;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Dbas;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sarand.Net.Standard.Base.Bus
+{
+ //=======================================================================
+ public class UpdateStamp
+ {
+ private DateTime on = DateTime.Now;
+ private SysId by = new SysId();
+ private String host = String.Empty;
+ public DateTime On
+ {
+ get { return on; }
+ set { on = value; }
+ }
+ public SysId By
+ {
+ get { return by; }
+ set { by = SysId.InvalidOnNull(value); }
+ }
+ public String Host
+ {
+ get { return host; }
+ set { host = StringUtil.EmptyOnNull(value); }
+ }
+ }
+ //=======================================================================
+ public class SacsHierarchyItem : SaErrorStateItem
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ public class Data
+ {
+ //---------------------------------------------------------------
+ // Common properties.
+ private SysId id = new SysId();
+ private String notes = String.Empty;
+ /// Item identifier.
+ public SysId Id
+ {
+ get { return SysId.InvalidOnNull(id); }
+ set
+ {
+ CheckChanged(id, value);
+ id = SysId.InvalidOnNull(value);
+ }
+ }
+ /// Item notes.
+ public String Notes
+ {
+ get { return StringUtil.EmptyOnNull(notes); }
+ set
+ {
+ CheckChanged(notes, value);
+ notes = StringUtil.EmptyOnNull(value);
+ }
+ }
+
+ //---------------------------------------------------------------
+ // Flags.
+ private UInt16 flags = 0;
+ /// Item flags.Up to 16 flags supported.
+ public UInt16 Flags
+ {
+ get { return flags; }
+ set
+ {
+ CheckChanged(flags, value);
+ flags = value;
+ }
+ }
+ /// Get flag value - position 1 - 16
+ public bool GetFlag (Byte flagPos)
+ {
+ return SacsHierarchyItem.GetFlag(Flags, flagPos);
+ }
+ /// Set flag value - position 1 - 16
+ public void SetFlag (Byte flagPos, bool value)
+ {
+ CheckChanged(GetFlag(flagPos), value);
+ Flags = SacsHierarchyItem.SetFlag(Flags, flagPos, value);
+ }
+
+ //---------------------------------------------------------------
+ // Status.
+ private SaStatus status = SaStatus.Unknown;
+ /// Item status.
+ public SaStatus Status
+ {
+ get { return status; }
+ set
+ {
+ CheckChangedEnum(status, value);
+ status = value;
+ }
+ }
+ public String StatusCode
+ {
+ get { return SacsSystem.GetStatusCode(Status); }
+ set { Status = SacsSystem.GetStatusForCode(value); }
+ }
+ public String StatusDesc
+ {
+ get { return SacsSystem.GetStatusDesc(Status); }
+ }
+ public bool IsStatusCurrent { get { return Status == SaStatus.Current; } }
+ public bool IsStatusActive { get { return Status == SaStatus.Active; } }
+ public bool IsStatusEnabled { get { return Status == SaStatus.Enabled; } }
+ public bool IsStatusCompleted { get { return Status == SaStatus.Completed; } }
+ public bool IsStatusCancelled { get { return Status == SaStatus.Cancelled; } }
+ public bool IsStatusDeleted { get { return Status == SaStatus.Deleted; } }
+
+ //---------------------------------------------------------------
+ // Update stamps.
+ public UpdateStamp CreateStamp { get; protected set; } = new UpdateStamp();
+ public UpdateStamp UpdateStamp { get; protected set; } = new UpdateStamp();
+
+ //---------------------------------------------------------------
+ // Population.
+ /// Copy common properties to this object.
+ protected void PopulateFrom (Data sourceData)
+ {
+ Id = sourceData.Id;
+ Notes = sourceData.Notes;
+ Status = sourceData.Status;
+ Flags = sourceData.Flags;
+ CreateStamp = sourceData.CreateStamp;
+ UpdateStamp = sourceData.UpdateStamp;
+ }
+ /// Copy comon properties to the target object.
+ internal void PopulateTo (Data targetData)
+ {
+ targetData.PopulateFrom(this);
+ }
+
+ //---------------------------------------------------------------
+ // Property change tracking.
+ private bool changed = false;
+ /// Flag to indicate whether any property has been modified.
+ public bool Changed
+ {
+ get { return changed; }
+ set
+ {
+ if (changed == value) return;
+ changed = value;
+ }
+ }
+ /// Clear 'Changed' flag.
+ public void AcceptChanges ()
+ {
+ Changed = false;
+ }
+
+ //---------------------------------------------------------------
+ // Compare old and new property values and set 'Changed' if different.
+ protected void CheckChanged (SysId oldValue, SysId newValue)
+ {
+ if (Changed) return;
+ // Compare by numeric value so that invalid values are equal.
+ Changed = SysId.ToInt(oldValue) != SysId.ToInt(newValue);
+ }
+ protected void CheckChanged (ItemId oldValue, ItemId newValue)
+ {
+ if (Changed) return;
+ Changed = (oldValue.Type != newValue.Type) || (!oldValue.Id.Equals(newValue.Id));
+ }
+ protected void CheckChanged (String oldValue, String newValue)
+ {
+ if (Changed) return;
+ Changed = !StringUtil.EmptyOnNull(oldValue).Equals(StringUtil.EmptyOnNull(newValue));
+ }
+ protected void CheckChanged (bool oldValue, bool newValue)
+ {
+ if (Changed) return;
+ Changed = (oldValue != newValue);
+ }
+ protected void CheckChangedEnum(T oldValue, T newValue) where T : struct, IConvertible
+ {
+ if (!(oldValue is Enum) || !(newValue is Enum))
+ throw new ArgumentException(GetType().Name+ ".CheckChangedEnum Parameters must be enumerated types. Type supplied: " + oldValue.GetType().Name + ", " + newValue.GetType().Name);
+ if (Changed) return;
+ Changed = ((int)(oldValue as IConvertible) != (int)(newValue as IConvertible));
+ }
+ protected void CheckChanged (int oldValue, int newValue)
+ {
+ if (Changed) return;
+ Changed = (oldValue != newValue);
+ }
+ protected void CheckChanged (double oldValue, double newValue)
+ {
+ if (Changed) return;
+ Changed = (oldValue != newValue);
+ }
+ protected void CheckChanged (decimal oldValue, decimal newValue)
+ {
+ if (Changed) return;
+ Changed = (oldValue != newValue);
+ }
+ protected void CheckChanged (DateTime oldValue, DateTime newValue)
+ {
+ if (Changed) return;
+ Changed = !oldValue.Equals(newValue);
+ }
+ protected void CheckChanged (List oldValues, List newValues)
+ {
+ if (Changed) return;
+ Changed = !oldValues.ContentEquals(newValues);
+ }
+ protected void CheckChanged (List oldValues, List newValues)
+ {
+ if (Changed) return;
+ if (oldValues.Count() != newValues.Count()) Changed = true;
+ else
+ {
+ for (int idx = 0; idx < oldValues.Count(); idx++)
+ {
+ if (!oldValues[idx].Equals(newValues[idx]))
+ {
+ Changed = true;
+ return;
+ }
+ }
+ }
+ }
+ protected void CheckChanged (byte[] oldValue, byte[] newValue)
+ {
+ if (Changed) return;
+ if ((oldValue == null) != (newValue != null)) Changed = true;
+ else if (oldValue.Length != newValue.Length) Changed = true;
+ if (oldValue != null && newValue != null)
+ Changed = !oldValue.SequenceEqual(newValue);
+ }
+
+ //---------------------------------------------------------------
+ }
+
+ //-------------------------------------------------------------------
+ // Property validation.
+ public enum ValidValue
+ {
+ /// Property is valid (or has not been validate).
+ Valid,
+ /// Property is invalid.
+ Invalid,
+ /// Property is to be ignored during validation.
+ Ignore,
+ }
+ public class ErrField
+ {
+ // Identifies the field that has invalid data.
+ public ValidValue Id { get; set; } = ValidValue.Valid;
+ public ValidValue Notes { get; set; } = ValidValue.Valid;
+ public ValidValue Status { get; set; } = ValidValue.Valid;
+ public ValidValue Flags { get; set; } = ValidValue.Valid;
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+ // Data object holds object business properties.
+ // Starts as internal class, replaced on construction by object specific class.
+ private Data data = new Data();
+
+ //-------------------------------------------------------------------
+ // Construction.
+ protected SacsHierarchyItem ()
+ {
+ }
+ protected SacsHierarchyItem (SysId id)
+ {
+ data.Id = id;
+ }
+
+ //-------------------------------------------------------------------
+ // Disposal.
+ protected override void Dispose (bool disposing)
+ {
+ if (disposing)
+ {
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public virtual bool ItemHasId { get { return true; } }
+ public SysId Id
+ {
+ get
+ {
+ if (!ItemHasId) throw new NotSupportedException(GetType().Name + ".Id Item has no Id property");
+ return data.Id;
+ }
+ set
+ {
+ if (!ItemHasId && SysId.IsValid(data.Id)) throw new NotSupportedException(GetType().Name + ".Id Item has no Id property");
+ if (SysId.IsValid(data.Id) && (!SysId.IsValid(value) || !data.Id.Equals(value)))
+ throw new NotSupportedException("SacsHierarchyItem.Id.set Id already has a valid value");
+ data.Id = value;
+ }
+ }
+ public String Notes
+ {
+ get { return StringUtil.EmptyOnNull(data.Notes); }
+ set { data.Notes = value; }
+ }
+
+ //-------------------------------------------------------------------
+ // Status.
+ public SaStatus Status
+ {
+ get { return data.Status; }
+ set { data.Status = value; }
+ }
+ public String StatusCode
+ {
+ get { return data.StatusCode; }
+ set { data.StatusCode = value; }
+ }
+ public String StatusDesc
+ {
+ get { return data.StatusDesc; }
+ }
+ public bool IsStatusCurrent { get { return data.IsStatusCurrent; } }
+ public bool IsStatusActive { get { return data.IsStatusActive; } }
+ public bool IsStatusEnabled { get { return data.IsStatusEnabled; } }
+ public bool IsStatusCompleted { get { return data.IsStatusCompleted; } }
+ public bool IsStatusCancelled { get { return data.IsStatusCancelled; } }
+ public bool IsStatusDeleted { get { return data.IsStatusDeleted; } }
+
+ //-------------------------------------------------------------------
+ // Flags.
+ public UInt16 Flags
+ {
+ get { return data.Flags; }
+ protected set { data.Flags = value; }
+ }
+ /// Flags are positioned from 1, up to 16 allowed.
+ public static bool GetFlag (UInt16 flags, Byte flagPos)
+ {
+ bool flag = false;
+ if (flagPos > 0 && flagPos <= 16)
+ flag = (((flags >> (flagPos - 1)) & 0x0001) == 1 ? true : false);
+ return flag;
+ }
+ /// Flags are positioned from 1, up to 16 allowed.
+ public bool GetFlag (Byte flagPos)
+ {
+ return GetFlag(data.Flags, flagPos);
+ }
+ /// Set flag value - position 1 - 16
+ public static UInt16 SetFlag (UInt16 flags, Byte flagPos, bool value)
+ {
+ if (flagPos > 0 && flagPos <= 16)
+ {
+ UInt16 mask = (UInt16)(1 << (flagPos - 1));
+ if (value)
+ flags |= mask;
+ else
+ flags &= (UInt16)(~mask);
+ }
+ return flags;
+ }
+ /// Set flag value - position 1 - 16
+ public void SetFlag (Byte flagPos, bool value)
+ {
+ data.Flags = SetFlag(data.Flags, flagPos, value);
+ }
+
+ //-------------------------------------------------------------------
+ // Update stamps.
+ public UpdateStamp CreateStamp { get { return data.CreateStamp; } }
+ public void SetCreateStamp (DateTime on, SysId by, String host)
+ {
+ CreateStamp.On = on;
+ CreateStamp.By = by;
+ CreateStamp.Host = host;
+ }
+ public UpdateStamp UpdateStamp { get { return data.UpdateStamp; } }
+ public void SetUpdateStamp (DateTime on, SysId by, String host)
+ {
+ UpdateStamp.On = on;
+ UpdateStamp.By = by;
+ UpdateStamp.Host = host;
+ }
+
+ //-------------------------------------------------------------------
+ // Populate data item - only once on construction.
+ protected void SetData (Data data)
+ {
+ String fn = GetType().Name + ".SetData";
+ // Can only be replaced if currently internal class.
+ if (this.data.GetType() != typeof(Data))
+ throw new NotSupportedException(fn + " Data object has already been specified");
+ // If Id has already been specified (on construction) and supplied Id is not valid, set it to the existing value.
+ if (ItemHasId && SysId.IsValid(Id) && !SysId.IsValid(data.Id)) data.Id = Id;
+ // Properties that cannot be changed if previously specified.
+ if (ItemHasId && SysId.IsValid(Id) && SysId.ToInt(Id) != SysId.ToInt(data.Id))
+ throw new NotSupportedException(fn + " Id cannot be changed");
+ this.data.PopulateTo(data);
+ this.data = data;
+ }
+
+ //-------------------------------------------------------------------
+ // Property change tracking.
+ /// Flag to indicate whether any property has been modified.
+ public bool Changed
+ {
+ get { return data.Changed; }
+ set
+ {
+ if (data.Changed == value) return;
+ data.Changed = value;
+ }
+ }
+ /// Clear the 'Changed' flag to indicate that no changes have been made.
+ public void AcceptChanges ()
+ {
+ Changed = false;
+ }
+
+ //-------------------------------------------------------------------
+ // Format value for display.
+ public static String FormatForDisplay (String value, int maxLength = 0)
+ {
+ if (StringUtil.IsEmpty(value)) return String.Empty;
+ // Replace non-display characters.
+ value = value.Replace("\t", " ").Replace("\r", " ").Replace("\n", " ");
+ // Remove leading, trailing and multiple internal spaces.
+ value = value.Trim().Replace(" +", " ");
+ // Restrict to maximum length.
+ if (maxLength > 0 && value.Length > maxLength)
+ {
+ value = value.Left(maxLength);
+ if (maxLength > 20) value += "...";
+ }
+ return value;
+ }
+
+ //-------------------------------------------------------------------
+ // Parameters.
+ public class Params
+ {
+ // Construction.
+ protected Params ()
+ {
+ }
+ /// Primary table alias to use if different from default.
+ public String TableAlias { get; set; }
+ /// Flag to indicate whether default filter ('1=1') is included if no other filter.
+ public bool IncludeDefaultFilter { get; set; } = true;
+ /// Results list paging.
+ public DbQuery.Paging ResultsPaging { get; } = new DbQuery.Paging();
+ /// Sort columns.
+ public List SortColumns { get; } = new List();
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/Bus/SacsItemMgr.cs b/SacsStandardBase/Base/Bus/SacsItemMgr.cs
new file mode 100644
index 0000000..928ec23
--- /dev/null
+++ b/SacsStandardBase/Base/Bus/SacsItemMgr.cs
@@ -0,0 +1,63 @@
+using System;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sarand.Net.Standard.Base.Bus
+{
+ //=======================================================================
+ public abstract class SacsItemMgr : SaErrorStateItem
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Static initialisation.
+ static SacsItemMgr ()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Construction.
+ protected SacsItemMgr ()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Disposal.
+ protected override void Dispose (bool disposing)
+ {
+ if (disposing)
+ {
+ }
+ base.Dispose(disposing);
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+
+ //-------------------------------------------------------------------
+ // V a l i d a t i o n
+ //-------------------------------------------------------------------
+ // String attribute validation (mandatory and maximum length.
+ // If the value fails the validation, an exception is set:
+ protected bool IsValidValue (String value, String attributeName, long maxLen, bool mandatory = false, bool fixedLen = false)
+ {
+ // Check for mandatory values:
+ if (mandatory && StringUtil.IsEmpty(value))
+ return SetErrorState(attributeName + " is blank");
+ // Check that value does not exceed the maximum number of characters:
+ if (maxLen > 0 && StringUtil.GetLength(value) > maxLen)
+ return SetErrorState(attributeName + " must not exceed " + maxLen.ToString() + " character" + ((maxLen > 1) ? "s" : ""));
+ // Check that fixed length values are the specified length:
+ if (fixedLen && maxLen > 0 && (mandatory || !value.IsEmpty()) && StringUtil.GetLength(value) != maxLen)
+ return SetErrorState(attributeName + " must be " + maxLen.ToString() + " character" + ((maxLen > 1) ? "s" : "") + " in length");
+ return true;
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/Bus/SysId.cs b/SacsStandardBase/Base/Bus/SysId.cs
new file mode 100644
index 0000000..004b2fa
--- /dev/null
+++ b/SacsStandardBase/Base/Bus/SysId.cs
@@ -0,0 +1,176 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sarand.Net.Standard.Base.Bus
+{
+ //=======================================================================
+ //[Serializable()]
+ public class SysId
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ private int id = 0;
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SysId () { }
+ public SysId (int id)
+ {
+ this.id = id;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties - for serialisation.
+ //[XmlAttribute]
+ public int Id
+ {
+ get { return id; }
+ set
+ {
+ if (id != 0 && value != 0 && value != id) throw new NotSupportedException("SysId value cannot be changed once valid");
+ id = value;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // Utilities.
+ public static bool IsValid (SysId sysId)
+ {
+ return (sysId != null && sysId.id != 0);
+ }
+ public static SysId InvalidOnNull (SysId sysId)
+ {
+ return (sysId != null ? sysId : new SysId());
+ }
+
+ //-------------------------------------------------------------------
+ // Comparison.
+ public override bool Equals (object obj)
+ {
+ if (obj == null) return false;
+ return Equals(obj as SysId);
+ }
+ public bool Equals (SysId sysId)
+ {
+ if (sysId == null) return false;
+ // False if either value invalid.
+ if (id == 0) return false;
+ return ToInt().Equals(InvalidOnNull(sysId).ToInt());
+ }
+ public override int GetHashCode ()
+ {
+ return id;
+ }
+
+ //-------------------------------------------------------------------
+ // Conversion.
+ public int ToInt ()
+ {
+ return ToInt(this);
+ }
+ public override String ToString ()
+ {
+ return ToString(this);
+ }
+ public String ToString (String invalidValue)
+ {
+ return ToString(this, invalidValue);
+ }
+ // Null-safe static versions.
+ public static int ToInt (SysId sysId)
+ {
+ return (IsValid(sysId) ? sysId.id : 0);
+ }
+ public static String ToString (SysId sysId)
+ {
+ return ToString(sysId, String.Empty);
+ }
+ public static String ToString (SysId sysId, String invalidValue)
+ {
+ return (IsValid(sysId) ? sysId.id.ToString() : invalidValue);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+ public static class SysIdList
+ {
+ //-------------------------------------------------------------------
+ // List contents.
+ public static int GetIdCount (this List values, bool ignoreInvalid = true, bool ignoreDuplicates = true)
+ {
+ if (ignoreDuplicates)
+ {
+ values = values.Select(id => id).Distinct().ToList();
+ // SysId.Equals treats invalid ids as inequal so 'Distinct' doesn't remove them.
+ if (values.FindIndex(id => !SysId.IsValid(id)) >= 0)
+ {
+ // Remove all invalid entries and put one back to correct the count.
+ values.RemoveAll(id => !SysId.IsValid(id));
+ values.Add(new SysId());
+ }
+ }
+ return values.Count(id => !ignoreInvalid || SysId.IsValid(id));
+ }
+ public static bool HasValidIds (this List values)
+ {
+ return (values.Find(id => SysId.IsValid(id)) != null);
+ }
+ public static bool Equals (this List values, List values2)
+ {
+ return values.OrderBy(id => id.Id).SequenceEqual(values2.OrderBy(id => id.Id));
+ }
+
+ //-------------------------------------------------------------------
+ // Populate list.
+ public static void AddId (this List values, SysId sysId, bool ignoreInvalid = true, bool ignoreDuplicates = true)
+ {
+ if (ignoreInvalid && !SysId.IsValid(sysId)) return;
+ if (ignoreDuplicates && values.Contains(sysId)) return;
+ values.Add(sysId);
+ }
+ public static void AddIds (this List values, List sysIds, bool ignoreInvalid = true, bool ignoreDuplicates = true)
+ {
+ foreach (SysId id in sysIds)
+ values.AddId(id, ignoreInvalid, ignoreDuplicates);
+ }
+
+ //-------------------------------------------------------------------
+ // Extract from list.
+ /// Remove specified number of entries from the list and return them.
+ ///
+ public static List ExtractIds (this List values, int numberToRemove)
+ {
+ List ids = new List();
+ while (values.Count > 0 && ids.Count < numberToRemove)
+ {
+ SysId id = values.ElementAt(0);
+ values.RemoveAt(0);
+ ids.Add(id);
+ }
+ return ids;
+ }
+
+ //-------------------------------------------------------------------
+ ///
+ /// Combine items in list as a delimited string.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static String Concat (this List values, String delimiter, bool ignoreInvalid = true)
+ {
+ List ids = new List();
+ foreach (SysId id in values.FindAll(id => !ignoreInvalid || SysId.IsValid(id)))
+ ids.Add(id.ToString());
+ return ids.Concat(delimiter);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/Bus/SysIdString.cs b/SacsStandardBase/Base/Bus/SysIdString.cs
new file mode 100644
index 0000000..a7d508e
--- /dev/null
+++ b/SacsStandardBase/Base/Bus/SysIdString.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+
+namespace Sarand.Net.Standard.Base.Bus
+{
+ //=======================================================================
+ //[Serializable()]
+ public class SysIdString
+ {
+ //-------------------------------------------------------------------
+ // Construction.
+ public SysIdString (SysId id, String value)
+ {
+ Id = id;
+ Value = value;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties - for serialisation.
+ //[XmlAttribute]
+ public SysId Id { get; private set; } = new SysId();
+ public String Value { get; private set; } = String.Empty;
+
+ //-------------------------------------------------------------------
+ // Utilities.
+
+ //-------------------------------------------------------------------
+ // Comparison.
+ public override bool Equals (object obj)
+ {
+ if (obj == null) return false;
+ if (!(obj is SysIdString)) return false;
+ return Equals(obj as SysIdString);
+ }
+ public bool Equals (SysIdString value)
+ {
+ if (value == null) return false;
+ // False if either Id value invalid.
+ if (SysId.IsValid(this.Id) != SysId.IsValid(value.Id)) return false;
+ return (SysId.ToInt(Id).Equals(SysId.ToInt(value.Id)) && Value.Equals(value.Value));
+ }
+ public override int GetHashCode ()
+ {
+ return SysId.ToInt(Id);
+ }
+
+ //-------------------------------------------------------------------
+ // Conversion.
+
+ //-------------------------------------------------------------------
+ }
+
+ //=======================================================================
+ public static class SysIdStringList
+ {
+ public static void Add(this List values, SysId id, String value)
+ {
+ values.Add(new SysIdString(id, value));
+ }
+ public static String GetValueForId(this List values, SysId id)
+ {
+ if (SysId.IsValid(id))
+ {
+ foreach (SysIdString value in values)
+ if (value.Id.Equals(id))
+ return value.Value;
+ }
+ return String.Empty;
+ }
+ }
+
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/CodeTypes/SaCode.cs b/SacsStandardBase/Base/CodeTypes/SaCode.cs
new file mode 100644
index 0000000..84cadc0
--- /dev/null
+++ b/SacsStandardBase/Base/CodeTypes/SaCode.cs
@@ -0,0 +1,260 @@
+using Sarand.Net.Standard.Base.Bus;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+
+namespace Sarand.Net.Standard.Base.CodeTypes
+{
+ //=======================================================================
+ public class SaCode : SacsHierarchyItem
+ {
+ //-------------------------------------------------------------------
+ // Columns.
+ public enum Column
+ {
+ Pos = 0,
+ // Code type properties.
+ CodeTypeId,
+ CodeType_Code,
+ CodeType_Desc,
+ // Code properties.
+ Id,
+ Code,
+ Description,
+ Sequence,
+ DataValue,
+ Flag1_Y,
+ Flag1_Tick,
+ Flag2_Y,
+ Flag2_Tick,
+ // Common properties.
+ Notes,
+ Flags,
+ Status,
+ Status_Desc,
+ Status_Ind,
+ CreateOn_Date,
+ UpdateOn_Date,
+ }
+
+ //-------------------------------------------------------------------
+ /// Properties other than common.
+ public class DataProperties
+ {
+ public SysId CodeTypeId { get; set; } = new SysId();
+ public string Code { get; set; } = string.Empty;
+ public string Description { get; set; } = string.Empty;
+ public ushort Sequence { get; set; } = 0;
+ public string DataValue { get; set; } = string.Empty;
+ }
+
+ //-------------------------------------------------------------------
+ // Data.
+ public new class Data : SacsHierarchyItem.Data
+ {
+ // Data members.
+ private readonly DataProperties data = new DataProperties();
+ // Construction.
+ public Data ()
+ {
+ Status = DefaultStatus;
+ }
+ // Properties.
+ public SysId CodeTypeId
+ {
+ get { return data.CodeTypeId; }
+ set { CheckChanged(data.CodeTypeId, value); data.CodeTypeId = value; }
+ }
+ public string Code
+ {
+ get { return data.Code; }
+ set { CheckChanged(data.Code, value); data.Code = value; }
+ }
+ public string Description
+ {
+ get { return data.Description; }
+ set { CheckChanged(data.Description, value); data.Description = value; }
+ }
+ public ushort Sequence
+ {
+ get { return data.Sequence; }
+ set { CheckChanged(data.Sequence, value); data.Sequence = value; }
+ }
+ public string DataValue
+ {
+ get { return data.DataValue; }
+ set { CheckChanged(data.DataValue, value); data.DataValue = value; }
+ }
+ // Population.
+ internal void PopulateFrom (Data data)
+ {
+ CodeTypeId = data.CodeTypeId;
+ Code = data.Code;
+ Description = data.Description;
+ Sequence = data.Sequence;
+ DataValue = data.DataValue;
+ base.PopulateFrom(data);
+ }
+ public Data Clone ()
+ {
+ Data data = new Data();
+ data.PopulateFrom(this);
+ return data;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // Error field - identify properties with validation error.
+ public new class ErrField : SacsHierarchyItem.ErrField
+ {
+ // Identifies the field that has invalid data.
+ public ValidValue CodeType { get; set; } = ValidValue.Valid;
+ public ValidValue Code { get; set; } = ValidValue.Valid;
+ public ValidValue Description { get; set; } = ValidValue.Valid;
+ public ValidValue Sequence { get; set; } = ValidValue.Valid;
+ public ValidValue DataValue { get; set; } = ValidValue.Valid;
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private readonly Data data = new Data();
+ // Flags.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SaCode (SysId id, SysId codeTypeId, string code) :
+ base(id)
+ {
+ base.SetData(data);
+ CodeTypeId = codeTypeId;
+ Code = code;
+ Status = DefaultStatus;
+ }
+
+ //-------------------------------------------------------------------
+ // Statuses.
+ private static readonly List validStatuses = new List()
+ {
+ SaStatus.Current,
+ SaStatus.Redundant
+ };
+ public static SaStatus DefaultStatus { get { return ActiveStatus; } }
+ public static SaStatus ActiveStatus { get { return SaStatus.Current; } }
+ public static SaStatus InactiveStatus { get { return SaStatus.Redundant; } }
+ public static List ValidStatuses { get { return new List(validStatuses); } }
+ public static DataValue.TriStateType GetStatusTriState (SaStatus status)
+ {
+ switch (status)
+ {
+ case SaStatus.Current:
+ return Core.DataValue.TriStateType.Green;
+ case SaStatus.Redundant:
+ return Core.DataValue.TriStateType.Red;
+ case SaStatus.Deleted:
+ return Core.DataValue.TriStateType.Purple;
+ }
+ return Core.DataValue.TriStateType.Unknown;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public Data GetData () { return data.Clone(); }
+ public void SetData (Data data)
+ {
+ string fn = GetType().Name + ".SetData";
+ // Properties that cannot be changed if previously specified.
+ if (SysId.IsValid(Id) && SysId.ToInt(Id) != SysId.ToInt(data.Id))
+ throw new NotSupportedException(fn + " Id cannot be changed");
+ if (!StringUtil.IsEmpty(Code) && !Code.Equals(data.Code))
+ throw new NotSupportedException(fn + " Code cannot be changed");
+ // Populate individual properties to ensure that 'Changed' indicator is kept up to date.
+ this.data.PopulateFrom(data);
+ }
+ public SysId CodeTypeId
+ {
+ get { return data.CodeTypeId; }
+ set { data.CodeTypeId = value; }
+ }
+ public string Code
+ {
+ get { return data.Code; }
+ set { data.Code = value; }
+ }
+ public string Description
+ {
+ get { return data.Description; }
+ set { data.Description = value; }
+ }
+ public ushort Sequence
+ {
+ get { return data.Sequence; }
+ set { data.Sequence = value; }
+ }
+ public string DataValue
+ {
+ get { return data.DataValue; }
+ set { data.DataValue = value; }
+ }
+ //-------------------------------------------------------------------
+ // Flags - specific to code type definition.
+
+ //-------------------------------------------------------------------
+ // List parameters.
+ public enum StatusCat
+ {
+ Any,
+ ActiveOnly,
+ InactiveOnly,
+ }
+ ///
+ /// Item list parameters.
+ ///
+ public new class Params : SacsHierarchyItem.Params
+ {
+ public List IncludedIds { get; } = new List();
+ public List ExcludedIds { get; } = new List();
+ public List CodeTypeIds { get; } = new List();
+ public List CodeTypeCodes { get; } = new List();
+ public string Code { get; set; }
+ public string CodePartial { get; set; }
+ public string Desc { get; set; }
+ public string DescPartial { get; set; }
+ public StatusCat StatusCat { get; set; } = StatusCat.ActiveOnly;
+ }
+
+ //-------------------------------------------------------------------
+ }
+
+ //=======================================================================
+ public static class SaCodeList
+ {
+ // Contents.
+ public static bool ContainsCode (this List codes, String code, bool currentOnly = true)
+ {
+ if (StringUtil.IsEmpty(code)) return false;
+ return codes.Exists(item => item.Code.EqualsCI(code) && (!currentOnly || item.IsStatusCurrent));
+ }
+
+ // Retrieve code.
+ public static SaCode GetCodeById (this List codes, SysId id)
+ {
+ return codes.Find(item => item.Id.Equals(id));
+ }
+ public static SaCode GetCodeByCode (this List codes, string code)
+ {
+ return codes.Find(item => item.Code.EqualsCI(code));
+ }
+
+ // Retrieve code's description.
+ public static string GetDescForCode (this List codes, string code, String defaultDesc = "")
+ {
+ SaCode saCode = codes.Find(item => item.Code.EqualsCI(code));
+ if (saCode != null)
+ return saCode.Description;
+ return defaultDesc;
+ }
+ }
+
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/CodeTypes/SaCodeDfn.cs b/SacsStandardBase/Base/CodeTypes/SaCodeDfn.cs
new file mode 100644
index 0000000..7594e04
--- /dev/null
+++ b/SacsStandardBase/Base/CodeTypes/SaCodeDfn.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+
+namespace Sarand.Net.Standard.Base.CodeTypes
+{
+ //=======================================================================
+ public class SaCodeDfn
+ {
+ //-------------------------------------------------------------------
+ public SaCodeDfn (String code, String description)
+ {
+ Code = code;
+ Description = description;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public String Code { get; private set; } = String.Empty;
+ public String Description { get; private set; } = String.Empty;
+ public String Data { get; set; } = String.Empty;
+ public String Notes { get; set; } = String.Empty;
+ public ushort Sequence { get; private set; } = 0;
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+ public static class SaCodeDfnList
+ {
+ public static void Add(this List values, String code, String description)
+ {
+ values.Add(new SaCodeDfn(code, description));
+ }
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/CodeTypes/SaCodeType.cs b/SacsStandardBase/Base/CodeTypes/SaCodeType.cs
new file mode 100644
index 0000000..a84c87b
--- /dev/null
+++ b/SacsStandardBase/Base/CodeTypes/SaCodeType.cs
@@ -0,0 +1,229 @@
+using Sarand.Net.Standard.Base.Bus;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+
+namespace Sarand.Net.Standard.Base.CodeTypes
+{
+ //=======================================================================
+ public class SaCodeType : SacsHierarchyItem
+ {
+ //-------------------------------------------------------------------
+ // Columns.
+ public enum Column
+ {
+ Pos = 0,
+ // Code type properties.
+ Id,
+ Code,
+ Description,
+ MaxCodeLen,
+ CodeFormat,
+ NextCode,
+ AutoAssignCode_Y,
+ AutoAssignCode_Tick,
+ Codes_Count,
+ // Common properties.
+ Notes,
+ Flags,
+ Status,
+ Status_Desc,
+ Status_Ind,
+ CreateOn_Date,
+ UpdateOn_Date,
+ }
+
+ //-------------------------------------------------------------------
+ /// Properties other than common.
+ public class DataProperties
+ {
+ public String Code { get; set; } = String.Empty;
+ public String Description { get; set; } = String.Empty;
+ public byte MaxCodeLen { get; set; } = 0;
+ public String CodeFormat { get; set; } = String.Empty;
+ public String NextCode { get; set; } = String.Empty;
+ }
+
+ //-------------------------------------------------------------------
+ // Data.
+ public new class Data : SacsHierarchyItem.Data
+ {
+ // Data members.
+ private readonly DataProperties data = new DataProperties();
+ // Construction.
+ public Data ()
+ {
+ Status = DefaultStatus;
+ }
+ // Properties.
+ public String Code
+ {
+ get { return data.Code; }
+ set { CheckChanged(data.Code, value); data.Code = value; }
+ }
+ public String Description
+ {
+ get { return data.Description; }
+ set { CheckChanged(data.Description, value); data.Description = value; }
+ }
+ public byte MaxCodeLen
+ {
+ get { return data.MaxCodeLen; }
+ set { CheckChanged(data.MaxCodeLen, value); data.MaxCodeLen = value; }
+ }
+ public String CodeFormat
+ {
+ get { return data.CodeFormat; }
+ set { CheckChanged(data.CodeFormat, value); data.CodeFormat = value; }
+ }
+ public String NextCode
+ {
+ get { return data.NextCode; }
+ set { CheckChanged(data.NextCode, value); data.NextCode = value; }
+ }
+ // Population.
+ internal void PopulateFrom (Data data)
+ {
+ Code = data.Code;
+ Description = data.Description;
+ MaxCodeLen = data.MaxCodeLen;
+ CodeFormat = data.CodeFormat;
+ NextCode = data.NextCode;
+ base.PopulateFrom(data);
+ }
+ public Data Clone ()
+ {
+ Data data = new Data();
+ data.PopulateFrom(this);
+ return data;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // Error field - identify properties with validation error.
+ public new class ErrField : SacsHierarchyItem.ErrField
+ {
+ // Identifies the field that has invalid data.
+ public ValidValue Code { get; set; } = ValidValue.Valid;
+ public ValidValue Description { get; set; } = ValidValue.Valid;
+ public ValidValue MaxCodeLen { get; set; } = ValidValue.Valid;
+ public ValidValue CodeFormat { get; set; } = ValidValue.Valid;
+ public ValidValue NextCode { get; set; } = ValidValue.Valid;
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private readonly Data data = new Data();
+ // Flags.
+ public static readonly Byte Flag_AutoAssignCode = 0x01;
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public SaCodeType (SysId id, String code) :
+ base(id)
+ {
+ base.SetData(data);
+ Code = code;
+ Status = DefaultStatus;
+ }
+
+ //-------------------------------------------------------------------
+ // Statuses.
+ private static readonly List validStatuses = new List()
+ {
+ SaStatus.Current
+ };
+ public static SaStatus DefaultStatus { get { return CurrentStatus; } }
+ public static SaStatus CurrentStatus { get { return SaStatus.Current; } }
+ public static List ValidStatuses { get { return new List(validStatuses); } }
+ public static DataValue.TriStateType GetStatusTriState (SaStatus status)
+ {
+ switch (status)
+ {
+ case SaStatus.Current:
+ return Core.DataValue.TriStateType.Green;
+ case SaStatus.Deleted:
+ return Core.DataValue.TriStateType.Purple;
+ }
+ return Core.DataValue.TriStateType.Unknown;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public Data GetData () { return data.Clone(); }
+ public void SetData (Data data)
+ {
+ String fn = GetType().Name + ".SetData";
+ // Properties that cannot be changed if previously specified.
+ if (SysId.IsValid(Id) && SysId.ToInt(Id) != SysId.ToInt(data.Id))
+ throw new NotSupportedException(fn + " Id cannot be changed");
+ if (!StringUtil.IsEmpty(Code) && !Code.Equals(data.Code))
+ throw new NotSupportedException(fn + " Code cannot be changed");
+ // Populate individual properties to ensure that 'Changed' indicator is kept up to date.
+ this.data.PopulateFrom(data);
+ }
+ public String Code
+ {
+ get { return data.Code; }
+ set { data.Code = value; }
+ }
+ public String Description
+ {
+ get { return data.Description; }
+ set { data.Description = value; }
+ }
+ public byte MaxCodeLen
+ {
+ get { return data.MaxCodeLen; }
+ set { data.MaxCodeLen = value; }
+ }
+ public String CodeFormat
+ {
+ get { return data.CodeFormat; }
+ set { data.CodeFormat = value; }
+ }
+ public String NextCode
+ {
+ get { return data.NextCode; }
+ set { data.NextCode = value; }
+ }
+ //-------------------------------------------------------------------
+ // Flags.
+ public bool AutoAssignCode
+ {
+ get { return GetFlag(Flag_AutoAssignCode); }
+ set { SetFlag(Flag_AutoAssignCode, value); }
+ }
+
+ //-------------------------------------------------------------------
+ // List parameters.
+ public enum StatusCat
+ {
+ Any,
+ ActiveOnly,
+ }
+ ///
+ /// Item list parameters.
+ ///
+ public new class Params : SacsHierarchyItem.Params
+ {
+ public List IncludedIds { get; } = new List();
+ public List ExcludedIds { get; } = new List();
+ public String Code { get; set; }
+ public String CodePartial { get; set; }
+ public String Desc { get; set; }
+ public String DescPartial { get; set; }
+ public StatusCat StatusCat { get; set; } = StatusCat.ActiveOnly;
+ }
+
+ //-------------------------------------------------------------------
+ }
+
+ //=======================================================================
+ public static class SaCodeTypeList
+ {
+ }
+
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/CodeTypes/SaCodeTypeConsole.cs b/SacsStandardBase/Base/CodeTypes/SaCodeTypeConsole.cs
new file mode 100644
index 0000000..0acab30
--- /dev/null
+++ b/SacsStandardBase/Base/CodeTypes/SaCodeTypeConsole.cs
@@ -0,0 +1,149 @@
+using Sarand.Net.Standard.Base.Bus;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Dbas;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Standard.Base.CodeTypes
+{
+ //=======================================================================
+ public abstract class SaCodeTypeConsole : SaErrorStateItem
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ protected readonly List codeTypeDfns = new List();
+
+ //-------------------------------------------------------------------
+ // Static initialisation.
+ static SaCodeTypeConsole ()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Construction.
+ protected SaCodeTypeConsole ()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public abstract bool DatabaseIsValid { get; }
+ public abstract bool CanUpdateDatabase { get; }
+
+ //-------------------------------------------------------------------
+ // C o d e T y p e D e f i n i t i o n s
+ //-------------------------------------------------------------------
+ public List CodeTypeDefinitions { get { return new List(codeTypeDfns); } }
+ public SaCodeTypeDfn GetCodeTypeDefinition (String codeTypeCode)
+ {
+ ClearErrorState();
+ foreach (SaCodeTypeDfn codeTypeDfn in codeTypeDfns)
+ if (codeTypeDfn.Code.EqualsCI(codeTypeCode))
+ return codeTypeDfn;
+ return null;
+ }
+ public abstract SaCodeTypeDfn GetCodeTypeDefinition (SysId codeTypeId);
+ public virtual String GetDescForCodeType (String codeTypeCode)
+ {
+ ClearErrorState();
+ SaCodeTypeDfn codeTypeDfn = GetCodeTypeDefinition(codeTypeCode);
+ if (codeTypeDfn != null) return codeTypeDfn.Description;
+ return "";
+ }
+ public bool IsCodeTypeSequenceAllowed (String codeTypeCode)
+ {
+ ClearErrorState();
+ SaCodeTypeDfn codeTypeDfn = GetCodeTypeDefinition(codeTypeCode);
+ if (codeTypeDfn != null) return codeTypeDfn.AllowSequence;
+ return false;
+ }
+
+ //-------------------------------------------------------------------
+ // Code Type registration - for use in validation and population of default database.
+ public bool RegisterCodeType (SaCodeTypeDfn codeTypeDfn)
+ {
+ ClearErrorState();
+ if (codeTypeDfns.GetCodeTypeDfnByCode(codeTypeDfn.Code) != null) return false;
+ codeTypeDfns.Add(codeTypeDfn);
+ return true;
+ }
+ public bool RegisterCodesOfType (String codeTypeCode, List codeDfns)
+ {
+ ClearErrorState();
+ SaCodeTypeDfn codeTypeDfn = GetCodeTypeDefinition(codeTypeCode);
+ if (codeTypeDfn == null) return false;
+ codeTypeDfn.DefaultCodeDefinitions.Clear();
+ foreach (CodeDesc codeDFn in codeDfns)
+ {
+ codeTypeDfn.DefaultCodeDefinitions.Add(new SaCodeDfn(codeDFn.Code,codeDFn.Desc));
+ }
+ return true;
+ }
+
+ //*******************************************************************
+ // C o d e T y p e s
+ //*******************************************************************
+ public abstract bool CodeTypeExists (String codeTypeCode);
+ public abstract int GetCodeTypeCount (SaCodeType.Params prmCodeType);
+ public abstract SaCodeType GetCodeType (SaCodeType.Params prmCodeType);
+ public abstract List GetCodeTypeList (SaCodeType.Params prmCodeType);
+ // Code type list.
+ public abstract ColumnDfn GetCodeTypeColumnDfn (SaCodeType.Column columnId, String caption = "", int width = 0);
+ public abstract DbResultSet ExecuteCodeTypeQuery (List columns, SaCodeType.Params prm);
+ ///
+ /// Extract and format result set data value where specific handling is required.
+ /// Where response is an invalid data value, format value using column.FormatValue(values).
+ ///
+ public abstract DataValue GetCodeTypeDataValue (ColumnDfn column, NamedValue.Map values);
+ // Population.
+ public abstract bool PopulateCodeType (String codeTypeCode, DbUpdateInfo dbUpdateInfo);
+ // Maintain code types - returns true if changes made.
+ public abstract bool MaintainCodeTypes (bool userCanManageCodeTypes, DbUpdateInfo dbUpdateInfo);
+ public abstract bool MaintainCodeType (String codeTypeCode, DbUpdateInfo dbUpdateInfo);
+
+ //*******************************************************************
+ // C o d e s
+ //*******************************************************************
+ // Check whether code exists.
+ public abstract bool CodeExistsForCodeType (String codeTypeCode, String code, bool activeOnly = true);
+ // Codes for code type.
+ public abstract bool CodeTypeHasCodes (String codeTypeCode, bool activeOnly = true);
+ public abstract int GetCodeCountForCodeType (String codeTypeCode, bool activeOnly = true);
+ public abstract SaCode GetCodeForCodeType (String codeTypeCode, String code, bool activeOnly = true);
+ public abstract List GetCodeDescsForCodeType (String codeTypeCode, bool activeOnly = true);
+ public abstract List GetCodeListForCodeType (String codeTypeCode, bool activeOnly = true);
+ // Code list.
+ public abstract ColumnDfn GetCodeColumnDfn (SaCode.Column columnId, String caption = "", int width = 0);
+ public abstract List GetCodeList (SaCode.Params prmCode);
+ public abstract DbResultSet ExecuteCodeQuery (List columns, SaCode.Params prm);
+ ///
+ /// Extract and format result set data value where specific handling is required.
+ /// Where response is an invalid data value, format value using column.FormatValue(values).
+ ///
+ public abstract DataValue GetCodeDataValue (ColumnDfn column, NamedValue.Map values);
+ // Codes by filter.
+ public abstract int GetCodeCount (SaCode.Params prmCode);
+ public abstract SaCode GetCode (SaCode.Params prmCode);
+ /// Get filtered codes, indexed by Code Type code.
+ public abstract Dictionary> GetCodesByCodeType (SaCode.Params prmCode);
+ public abstract Task>> GetCodesByCodeTypeAsync (SaCode.Params prmCode);
+ /// Get filtered codes and descriptions, indexed by Code Type code.
+ public abstract Dictionary> GetCodeDescsByCodeType (SaCode.Params prmCode);
+
+ //-------------------------------------------------------------------
+ // Create codes.
+ public abstract bool CreateCodeOfType (SysId codeTypeId, String code, String desc, DbUpdateInfo dbUpdateInfo);
+ public abstract bool CreateCodeOfType (String codeTypeCode, String code, String desc, DbUpdateInfo dbUpdateInfo);
+ // Population.
+ public abstract bool PopulateCode (String codeTypeCode, String code, String desc, bool active, DbUpdateInfo dbUpdateInfo);
+
+ //-------------------------------------------------------------------
+ // Select codes.
+ public abstract List SelectCodes (String codeTypeCode, List codesSelected);
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/CodeTypes/SaCodeTypeDbDfn.cs b/SacsStandardBase/Base/CodeTypes/SaCodeTypeDbDfn.cs
new file mode 100644
index 0000000..054ded1
--- /dev/null
+++ b/SacsStandardBase/Base/CodeTypes/SaCodeTypeDbDfn.cs
@@ -0,0 +1,351 @@
+using Sarand.Net.Standard.Base.Dbas;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+//---------------------------------------------------------------------------
+// Code generated by Sarand Database Utilities Thu 3-Oct-2019 15:10:33
+//---------------------------------------------------------------------------
+namespace Sarand.Net.Standard.Base.CodeTypes
+{
+ public class SaCodeTypeDbDfn
+ {
+ //-------------------------------------------------------------------
+ // Table identifiers.
+ public enum TableIdx
+ {
+ Unknown = 0,
+ SaCode,
+ SaCodeType,
+ }
+
+ //-------------------------------------------------------------------
+ // Column identifiers.
+ public enum ColumnIdx
+ {
+ Unknown = 0,
+ SaCode_Id,
+ SaCode_CodeTypeId,
+ SaCode_Code,
+ SaCode_Description,
+ SaCode_Seq,
+ SaCode_Data,
+ SaCode_Notes,
+ SaCode_Flags,
+ SaCode_Status,
+ SaCode_CreateOn,
+ SaCode_CreateBy,
+ SaCode_CreateHost,
+ SaCode_UpdateOn,
+ SaCode_UpdateBy,
+ SaCode_UpdateHost,
+ SaCodeType_Id,
+ SaCodeType_Code,
+ SaCodeType_Name,
+ SaCodeType_MaxCodeLen,
+ SaCodeType_CodeFormat,
+ SaCodeType_NextCode,
+ SaCodeType_Notes,
+ SaCodeType_Flags,
+ SaCodeType_Status,
+ SaCodeType_CreateOn,
+ SaCodeType_CreateBy,
+ SaCodeType_CreateHost,
+ SaCodeType_UpdateOn,
+ SaCodeType_UpdateBy,
+ SaCodeType_UpdateHost,
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private static readonly int tableCount = 2;
+ private static readonly int columnCount = 30;
+ private static ulong maxTextLen = 0;
+
+ //-------------------------------------------------------------------
+ // Retrieve Table details (eg. name: 'Company').
+ public static int GetTableCount ()
+ {
+ return tableCount;
+ }
+ public static String GetTableName (TableIdx table, bool delimitReservedWords = true)
+ {
+ int tableIdx = (int)table - 1;
+ if (tableIdx < 0 || tableIdx >= tableCount) return String.Empty;
+ String tableName = tables[(int)tableIdx];
+ if (delimitReservedWords) tableName = DelimitReservedWord(tableName);
+ return tableName;
+ }
+
+ //-------------------------------------------------------------------
+ // Identify the table that corresponds with the specified column.
+ public static TableIdx GetTableForColumn (ColumnIdx column)
+ {
+ int columnIdx = (int)column - 1;
+ if (columnIdx < 0 || columnIdx >= columnCount) return TableIdx.Unknown;
+ return columns[columnIdx].TableIdx;
+ }
+
+ //-------------------------------------------------------------------
+ // Retrieve the specified Column's name (Qualified include table name: '(.Column>)').
+ public static String GetColumnName (ColumnIdx column, bool delimitReservedWords = true)
+ {
+ int columnIdx = (int)column - 1;
+ if (columnIdx < 0 || columnIdx >= columnCount) return String.Empty;
+ String columnName = columns[columnIdx].Name;
+ if (delimitReservedWords) columnName = DelimitReservedWord(columnName);
+ return columnName;
+ }
+ public static String GetColumnNameQual (ColumnIdx column, String tableAlias = "", bool braces = false)
+ {
+ String columnName = GetColumnName(column);
+ if (!String.IsNullOrEmpty(columnName))
+ {
+ String tableName = StringUtil.EmptyOnNull(tableAlias);
+ if (tableName.IsEmpty()) tableName = GetTableName(GetTableForColumn(column));
+ columnName = tableName + (tableName.Length > 0 ? "." : "") + columnName;
+ if (braces)
+ columnName = "(" + columnName + ")";
+ }
+ return columnName;
+ }
+
+ //-------------------------------------------------------------------
+ // Reserved words.
+ private static DbReservedWordDelim reservedWordDelim = DbReservedWordDelim.SquareBrackets;
+ public static void SetReservedWordDelim (DbReservedWordDelim reservedWordDelim)
+ {
+ SaCodeTypeDbDfn.reservedWordDelim = reservedWordDelim;
+ }
+ public static bool IsReservedWord (String word)
+ {
+ if (String.IsNullOrEmpty(word)) return false;
+ if (reservedWords.Contains(word, StringComparer.OrdinalIgnoreCase)) return true;
+ return false;
+ }
+ public static String DelimitReservedWord (String word)
+ {
+ if (!IsReservedWord(word)) return word;
+ switch (reservedWordDelim)
+ {
+ case DbReservedWordDelim.None:
+ break;
+ case DbReservedWordDelim.DoubleQuotes:
+ word = "\"" + word + "\"";
+ break;
+ case DbReservedWordDelim.SquareBrackets:
+ default:
+ word = "[" + word + "]";
+ break;
+ }
+ return word;
+ }
+
+ //-------------------------------------------------------------------
+ // Retrieve information about the specified Column.
+ public static DbColumnDataType GetColDataType (ColumnIdx column)
+ {
+ int columnIdx = (int)column - 1;
+ if (columnIdx < 0 || columnIdx >= columnCount) return DbColumnDataType.Unknown;
+ return columns[columnIdx].DataType;
+ }
+ public static short GetColNativeDataType (ColumnIdx column)
+ {
+ int columnIdx = (int)column - 1;
+ if (columnIdx < 0 || columnIdx >= columnCount) return 0;
+ return columns[columnIdx].NativeDataType;
+ }
+ public static void SetMaxTextLen (ulong maxTextLen)
+ {
+ SaCodeTypeDbDfn.maxTextLen = maxTextLen;
+ }
+ public static bool IsColFmtText (ColumnIdx column)
+ {
+ int columnIdx = (int)column - 1;
+ if (columnIdx < 0 || columnIdx >= columnCount) return false;
+ return (columns[columnIdx].DataType == DbColumnDataType.VarChar);
+ }
+ public static ulong GetColTextLen (ColumnIdx column)
+ {
+ int columnIdx = (int)column - 1;
+ if (columnIdx < 0 || columnIdx >= columnCount) return 0;
+ if (columns[columnIdx].DataType == DbColumnDataType.VarChar)
+ {
+ ulong textLen = columns[columnIdx].DataLen;
+ if (maxTextLen > 0 && textLen > maxTextLen) textLen = maxTextLen;
+ return textLen;
+ }
+ return 0;
+ }
+ public static bool IsColFmtBool (ColumnIdx column)
+ {
+ int columnIdx = (int)column - 1;
+ if (columnIdx < 0 || columnIdx >= columnCount) return false;
+ return (columns[columnIdx].DataType == DbColumnDataType.Bit);
+ }
+ public static bool IsColFmtNumeric (ColumnIdx column)
+ {
+ int columnIdx = (int)column - 1;
+ if (columnIdx < 0 || columnIdx >= columnCount) return false;
+ switch (columns[columnIdx].DataType)
+ {
+ case DbColumnDataType.TinyInt:
+ case DbColumnDataType.SmallInt:
+ case DbColumnDataType.Int:
+ case DbColumnDataType.BigInt:
+ case DbColumnDataType.Single:
+ case DbColumnDataType.Double:
+ case DbColumnDataType.Decimal:
+ return true;
+ }
+ return false;
+ }
+
+ //-------------------------------------------------------------------
+ // Table definitions.
+ private static readonly String[] tables =
+ {
+ "SaCode",
+ "SaCodeType",
+ };
+
+ //-------------------------------------------------------------------
+ // Table column definitions.
+ internal class ColumnDfn
+ {
+ internal TableIdx TableIdx { get; set; }
+ internal String Name { get; set; }
+ internal DbColumnDataType DataType { get; set; }
+ internal short NativeDataType { get; set; }
+ internal bool IsIdentity { get; set; }
+ internal ulong DataLen { get; set; }
+ internal ColumnDfn (int tableIdx, String name, int dataType, short nativeDataType, bool isIdentity, ulong dataLen)
+ {
+ this.TableIdx = (TableIdx)tableIdx;
+ this.Name = name;
+ this.DataType = (DbColumnDataType)dataType;
+ this.NativeDataType = nativeDataType;
+ this.IsIdentity = isIdentity;
+ this.DataLen = dataLen;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ private static readonly ColumnDfn[] columns =
+ {
+ new ColumnDfn(1,"Id",4,4,false,10),
+ new ColumnDfn(1,"CodeTypeId",4,4,false,10),
+ new ColumnDfn(1,"Code",0,-9,false,32),
+ new ColumnDfn(1,"Description",0,-9,false,128),
+ new ColumnDfn(1,"Seq",3,5,false,5),
+ new ColumnDfn(1,"Data",0,-9,false,255),
+ new ColumnDfn(1,"Notes",0,-9,false,255),
+ new ColumnDfn(1,"Flags",3,5,false,5),
+ new ColumnDfn(1,"Status",0,-9,false,3),
+ new ColumnDfn(1,"CreateOn",9,93,false,23),
+ new ColumnDfn(1,"CreateBy",4,4,false,10),
+ new ColumnDfn(1,"CreateHost",0,-9,false,64),
+ new ColumnDfn(1,"UpdateOn",9,93,false,23),
+ new ColumnDfn(1,"UpdateBy",4,4,false,10),
+ new ColumnDfn(1,"UpdateHost",0,-9,false,64),
+ new ColumnDfn(2,"Id",4,4,false,10),
+ new ColumnDfn(2,"Code",0,-9,false,32),
+ new ColumnDfn(2,"Name",0,-9,false,128),
+ new ColumnDfn(2,"MaxCodeLen",2,-6,false,3),
+ new ColumnDfn(2,"CodeFormat",0,-9,false,32),
+ new ColumnDfn(2,"NextCode",0,-9,false,32),
+ new ColumnDfn(2,"Notes",0,-9,false,255),
+ new ColumnDfn(2,"Flags",3,5,false,5),
+ new ColumnDfn(2,"Status",0,-9,false,3),
+ new ColumnDfn(2,"CreateOn",9,93,false,23),
+ new ColumnDfn(2,"CreateBy",4,4,false,10),
+ new ColumnDfn(2,"CreateHost",0,-9,false,64),
+ new ColumnDfn(2,"UpdateOn",9,93,false,23),
+ new ColumnDfn(2,"UpdateBy",4,4,false,10),
+ new ColumnDfn(2,"UpdateHost",0,-9,false,64),
+ };
+
+ //-------------------------------------------------------------------
+ // Reserved words.
+ private static readonly List reservedWords = new List()
+ {
+ "ADD",
+ "ALL",
+ "ALTER",
+ "AND",
+ "ANY",
+ "AS",
+ "ASC",
+ "AVG",
+ "BEGIN",
+ "BETWEEN",
+ "BY",
+ "CASE",
+ "CAST",
+ "COLUMN",
+ "COMMIT",
+ "CONSTRAINT",
+ "CONVERT",
+ "COUNT",
+ "CREATE",
+ "CURRENCY",
+ "DEFAULT",
+ "DELETE",
+ "DESC",
+ "DISTINCT",
+ "DROP",
+ "ELSE",
+ "END",
+ "EXECUTE",
+ "EXISTS",
+ "FALSE",
+ "FROM",
+ "GRANT",
+ "GROUP",
+ "HAVING",
+ "IN",
+ "INDEX",
+ "INNER",
+ "INSERT",
+ "INTO",
+ "IS",
+ "JOIN",
+ "KEY",
+ "LEFT",
+ "LIKE",
+ "MAX",
+ "MIN",
+ "NOT",
+ "NULL",
+ "ON",
+ "OR",
+ "ORDER",
+ "OUTER",
+ "OUTPUT",
+ "PRIMARY",
+ "PROCEDURE",
+ "REVOKE",
+ "ROLLBACK",
+ "ROWCOUNT",
+ "SELECT",
+ "SET",
+ "SQL",
+ "SUM",
+ "TABLE",
+ "THEN",
+ "TRANSACTION",
+ "TRUE",
+ "UNION",
+ "UNIQUE",
+ "UPDATE",
+ "USER",
+ "VALUES",
+ "VIEW",
+ "WHEN",
+ "WHERE",
+ };
+
+ //-------------------------------------------------------------------
+ }
+}
diff --git a/SacsStandardBase/Base/CodeTypes/SaCodeTypeDfn.cs b/SacsStandardBase/Base/CodeTypes/SaCodeTypeDfn.cs
new file mode 100644
index 0000000..98bc0bc
--- /dev/null
+++ b/SacsStandardBase/Base/CodeTypes/SaCodeTypeDfn.cs
@@ -0,0 +1,53 @@
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+
+namespace Sarand.Net.Standard.Base.CodeTypes
+{
+ //=======================================================================
+ public class SaCodeTypeDfn
+ {
+ //-------------------------------------------------------------------
+ public SaCodeTypeDfn (String code, String description, byte maxCodeLen = 0, String notes = "")
+ {
+ Code = code;
+ Description = description;
+ MaxCodeLen = maxCodeLen;
+ Notes = notes;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public String Code { get; } = String.Empty;
+ public String Description { get; } = String.Empty;
+ public byte MaxCodeLen { get; } = 0;
+ public String Notes { get; } = String.Empty;
+ public bool AllowMixedCode { get; set; } = false;
+ public bool AllowSequence { get; set; } = false;
+ public bool AllowData { get; set; } = false;
+ public bool AllowMixedData { get; set; } = false;
+ public String DataLabel { get; set; } = String.Empty;
+ public List FlagDefinitions { get; } = new List();
+ public List DefaultCodeDefinitions { get; } = new List();
+
+ //-------------------------------------------------------------------
+ }
+
+ //=======================================================================
+ public static class SaCodeTypeDfnList
+ {
+ // Retrieve definition from the list.
+ public static SaCodeTypeDfn GetCodeTypeDfnByCode (this List values, String code)
+ {
+ return values.Find(item => item.Code.EqualsCI(code));
+ }
+ // Add definition to the list.
+ public static void Add(this List values, String code, String description)
+ {
+ values.Add(new SaCodeTypeDfn(code, description));
+ }
+ }
+
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/CodeTypes/SaCodeTypeMgr.cs b/SacsStandardBase/Base/CodeTypes/SaCodeTypeMgr.cs
new file mode 100644
index 0000000..a3684b2
--- /dev/null
+++ b/SacsStandardBase/Base/CodeTypes/SaCodeTypeMgr.cs
@@ -0,0 +1,319 @@
+using Sarand.Net.Standard.Base.App;
+using Sarand.Net.Standard.Base.Bus;
+using Sarand.Net.Standard.Base.Core;
+using Sarand.Net.Standard.Base.Dbas;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Sarand.Net.Standard.Base.CodeTypes
+{
+ //=======================================================================
+ public abstract class SaCodeTypeMgr : SacsItemMgr
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Static initialisation.
+ static SaCodeTypeMgr ()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Construction.
+ protected SaCodeTypeMgr ()
+ {
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public abstract bool DatabaseIsValid { get; }
+ public abstract bool CanUpdateDatabase { get; }
+
+ //*******************************************************************
+ // C o d e T y p e s
+ //*******************************************************************
+ // Associated data.
+ public abstract bool CodeTypeHasCodes (SysId codeTypeId);
+
+ //-------------------------------------------------------------------
+ // Create Item.
+ public abstract SaCodeType CreateCodeType (String code);
+
+ //-------------------------------------------------------------------
+ // Retrieve individual Items.
+ public abstract bool CodeTypeExistsById (SysId id);
+ public abstract bool CodeTypeExistsByCode (String code);
+ public abstract SaCodeType GetCodeType (SaCodeType.Params prmCodeType);
+ public abstract SaCodeType GetCodeTypeById (SysId id);
+ public abstract SaCodeType GetCodeTypeByCode (String code);
+
+ //-------------------------------------------------------------------
+ // Validate Code Type.
+ public bool IsValidCodeType (SaCodeType codeType)
+ {
+ ClearErrorState();
+ if (codeType == null) return SetErrorState(ErrorMessage.ItemNotSpecified);
+ return IsValidCodeTypeData(codeType.GetData(), new SaCodeType.ErrField());
+ }
+ public virtual bool IsValidCodeTypeData (SaCodeType.Data codeTypeData, SaCodeType.ErrField errField)
+ {
+ ClearErrorState();
+ if (!IsValidValue(codeTypeData.Code, "code", (long)SaCodeTypeDbDfn.GetColTextLen(SaCodeTypeDbDfn.ColumnIdx.SaCodeType_Code), true))
+ {
+ errField.Code = SacsHierarchyItem.ValidValue.Invalid;
+ return false;
+ }
+ if (!IsValidValue(codeTypeData.Description, "description", (long)SaCodeTypeDbDfn.GetColTextLen(SaCodeTypeDbDfn.ColumnIdx.SaCodeType_Name), true))
+ {
+ errField.Description = SacsHierarchyItem.ValidValue.Invalid;
+ return false;
+ }
+ if (!IsValidValue(codeTypeData.CodeFormat, "code format", (long)SaCodeTypeDbDfn.GetColTextLen(SaCodeTypeDbDfn.ColumnIdx.SaCodeType_CodeFormat)))
+ {
+ errField.CodeFormat = SacsHierarchyItem.ValidValue.Invalid;
+ return false;
+ }
+ if (!IsValidValue(codeTypeData.NextCode, "next code", (long)SaCodeTypeDbDfn.GetColTextLen(SaCodeTypeDbDfn.ColumnIdx.SaCodeType_NextCode)))
+ {
+ errField.NextCode = SacsHierarchyItem.ValidValue.Invalid;
+ return false;
+ }
+ // Common properties.
+ if (!IsValidValue(codeTypeData.Notes, "notes", (long)SaCodeTypeDbDfn.GetColTextLen(SaCodeTypeDbDfn.ColumnIdx.SaCodeType_Notes)))
+ {
+ errField.Notes = SacsHierarchyItem.ValidValue.Invalid;
+ return false;
+ }
+ if (!SaCodeType.ValidStatuses.Contains(codeTypeData.Status))
+ {
+ errField.Status = SacsHierarchyItem.ValidValue.Invalid;
+ return SetErrorState(ErrorMessage.InvalidStatus);
+ }
+ return true;
+ }
+
+ //-------------------------------------------------------------------
+ // CodeType database interface.
+ public abstract bool InsertCodeType (SaCodeType codeType, DbUpdateInfo dbUpdateInfo);
+ public abstract bool UpdateCodeType (SaCodeType codeType, DbUpdateInfo dbUpdateInfo);
+ public virtual bool CodeTypeCanBeDeleted (SaCodeType codeType)
+ {
+ ClearErrorState();
+ if (CodeTypeHasCodes(codeType.Id)) return SetErrorState("code type has codes");
+ return true;
+ }
+ public abstract bool DeleteCodeType (SaCodeType codeType, DbUpdateInfo dbUpdateInfo);
+
+ //-------------------------------------------------------------------
+ // Code Type list.
+ public abstract ColumnDfn GetCodeTypeColumnDfn (SaCodeType.Column columnId, String caption = "", int width = 0);
+ public abstract List GetCodeTypeQueryFilter (SaCodeType.Params prm);
+ public abstract long GetCodeTypeCount (SaCodeType.Params prm);
+ public abstract List GetCodeTypeList (SaCodeType.Params prm);
+ public abstract Task> GetCodeTypeListAsync (SaCodeType.Params prm);
+ public abstract DbResultSet ExecuteCodeTypeQuery (List columns, SaCodeType.Params prm);
+ public abstract Task ExecuteCodeTypeQueryAsync (List columns, SaCodeType.Params prm);
+
+ //-------------------------------------------------------------------
+ ///
+ /// Extract and format result set data value where specific handling is required.
+ /// Where response is an invalid data value, format value using column.FormatValue(values).
+ ///
+ public virtual DataValue GetCodeTypeDataValue (ColumnDfn column, NamedValue.Map values)
+ {
+ DataValue value = new DataValue();
+ switch ((SaCodeType.Column)column.Id)
+ {
+ //-----------------------------------------------------------
+ // Item type.
+ case SaCodeType.Column.AutoAssignCode_Y:
+ case SaCodeType.Column.AutoAssignCode_Tick:
+ {
+ ushort flags = (ushort)values.GetValue(column.DataName).AsLong();
+ bool autoAssignCode = SacsHierarchyItem.GetFlag(flags, SaCodeType.Flag_AutoAssignCode);
+ if (column.Id == (int)SaCodeType.Column.AutoAssignCode_Y)
+ value = new DataValue(autoAssignCode ? "Y" : "");
+ else
+ value = new DataValue(autoAssignCode);
+ }
+ break;
+
+ //-----------------------------------------------------------
+ // Notes.
+ case SaCodeType.Column.Notes:
+ value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200));
+ break;
+
+ //-----------------------------------------------------------
+ // Status.
+ case SaCodeType.Column.Status_Desc:
+ case SaCodeType.Column.Status_Ind:
+ {
+ String statusCode = values.GetValue(column.DataName).ToString();
+ SaStatus status = SacsSystem.GetStatusForCode(statusCode);
+ if (column.Id == (int)SaCodeType.Column.Status_Desc)
+ {
+ String statusDesc = SacsSystem.GetStatusDescForCode(statusCode);
+ value = new DataValue(statusDesc);
+ }
+ else if (column.Id == (int)SaCodeType.Column.Status_Ind)
+ {
+ DataValue.TriStateType triState = DataValue.TriStateType.Unknown;
+ if (status == SaStatus.Current || status == SaStatus.Active)
+ triState = DataValue.TriStateType.Green;
+ value = new DataValue(triState);
+ }
+ }
+ break;
+
+ //-----------------------------------------------------------
+ }
+ return value;
+ }
+
+ //*******************************************************************
+ // C o d e s
+ //*******************************************************************
+ // Retrieve individual codes.
+ public abstract bool CodeExistsById (SysId id);
+ public abstract SaCode GetCode (SaCode.Params prmCode);
+ public abstract SaCode GetCodeById (SysId id);
+ public abstract SaCode GetCodeByCodeTypeCode (String codeTypeCode, String code);
+
+ // Create code.
+ public abstract SaCode CreateCode (SysId codeTypeId, String code);
+
+ //-------------------------------------------------------------------
+ // Validate code data.
+ public bool IsValidCode (SaCode code)
+ {
+ ClearErrorState();
+ if (code == null) return SetErrorState(ErrorMessage.ItemNotSpecified);
+ return IsValidCodeData(code.GetData(), new SaCode.ErrField());
+ }
+ public virtual bool IsValidCodeData (SaCode.Data codeData, SaCode.ErrField errField)
+ {
+ ClearErrorState();
+ if (!SysId.IsValid(codeData.CodeTypeId))
+ {
+ errField.CodeType = SacsHierarchyItem.ValidValue.Invalid;
+ return SetErrorState("code type must be specified");
+ }
+ if (!IsValidValue(codeData.Code, "code", (long)SaCodeTypeDbDfn.GetColTextLen(SaCodeTypeDbDfn.ColumnIdx.SaCode_Code), true))
+ {
+ errField.Code = SacsHierarchyItem.ValidValue.Invalid;
+ return false;
+ }
+ if (!IsValidValue(codeData.Description, "description", (long)SaCodeTypeDbDfn.GetColTextLen(SaCodeTypeDbDfn.ColumnIdx.SaCode_Description), true))
+ {
+ errField.Description = SacsHierarchyItem.ValidValue.Invalid;
+ return false;
+ }
+ if (codeData.Sequence < 0)
+ {
+ errField.Sequence = SacsHierarchyItem.ValidValue.Invalid;
+ return SetErrorState("sequence must be positive");
+ }
+ if (!IsValidValue(codeData.DataValue, "data value", (long)SaCodeTypeDbDfn.GetColTextLen(SaCodeTypeDbDfn.ColumnIdx.SaCode_Data)))
+ {
+ errField.DataValue = SacsHierarchyItem.ValidValue.Invalid;
+ return false;
+ }
+ // Common properties.
+ if (!IsValidValue(codeData.Notes, "notes", (long)SaCodeTypeDbDfn.GetColTextLen(SaCodeTypeDbDfn.ColumnIdx.SaCodeType_Notes)))
+ {
+ errField.Notes = SacsHierarchyItem.ValidValue.Invalid;
+ return false;
+ }
+ if (!SaCode.ValidStatuses.Contains(codeData.Status))
+ {
+ errField.Status = SacsHierarchyItem.ValidValue.Invalid;
+ return SetErrorState(ErrorMessage.InvalidStatus);
+ }
+ return true;
+ }
+
+ //-------------------------------------------------------------------
+ // Code database interface.
+ public abstract bool InsertCode (SaCode code, DbUpdateInfo dbUpdateInfo);
+ public abstract bool UpdateCode (SaCode code, DbUpdateInfo dbUpdateInfo);
+ public virtual bool CodeCanBeDeleted (SaCode code)
+ {
+ ClearErrorState();
+ return true;
+ }
+ public abstract bool DeleteCode (SaCode code, DbUpdateInfo dbUpdateInfo);
+
+ //-------------------------------------------------------------------
+ // Code list.
+ public abstract ColumnDfn GetCodeColumnDfn (SaCode.Column columnId, String caption = "", int width = 0);
+ public abstract List GetCodeQueryFilter (SaCode.Params prm);
+ public abstract long GetCodeCount (SaCode.Params prm);
+ public abstract List GetCodeList (SaCode.Params prm);
+ public abstract Task> GetCodeListAsync (SaCode.Params prm);
+ public abstract DbResultSet ExecuteCodeQuery (List columns, SaCode.Params prm);
+ public abstract Task ExecuteCodeQueryAsync (List columns, SaCode.Params prm);
+
+ //-------------------------------------------------------------------
+ ///
+ /// Extract and format result set data value where specific handling is required.
+ /// Where response is an invalid data value, format value using column.FormatValue(values).
+ ///
+ public virtual DataValue GetCodeDataValue (ColumnDfn column, NamedValue.Map values)
+ {
+ DataValue value = new DataValue();
+ SaCode.Column colId = (SaCode.Column)column.Id;
+ switch (colId)
+ {
+ //-----------------------------------------------------------
+ // Context Item description.
+ case SaCode.Column.Flag1_Y:
+ case SaCode.Column.Flag1_Tick:
+ case SaCode.Column.Flag2_Y:
+ case SaCode.Column.Flag2_Tick:
+ {
+ ushort flags = (ushort)values.GetValue(column.DataName).AsLong();
+ bool flag1 = SacsHierarchyItem.GetFlag(flags, 1);
+ bool flag2 = SacsHierarchyItem.GetFlag(flags, 2);
+ if (column.Id == (int)SaCode.Column.Flag1_Y)
+ value = new DataValue(flag1 ? "Y" : "");
+ else if (column.Id == (int)SaCode.Column.Flag1_Tick)
+ value = new DataValue(flag1);
+ else if (column.Id == (int)SaCode.Column.Flag2_Y)
+ value = new DataValue(flag2 ? "Y" : "");
+ else if (column.Id == (int)SaCode.Column.Flag2_Tick)
+ value = new DataValue(flag2);
+ }
+ break;
+
+ //-----------------------------------------------------------
+ // Notes.
+ case SaCode.Column.Notes:
+ value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200));
+ break;
+
+ //-----------------------------------------------------------
+ // Status.
+ case SaCode.Column.Status_Desc:
+ case SaCode.Column.Status_Ind:
+ {
+ String statusValue = values.GetValue(column.DataName).ToString();
+ SaStatus status = SacsSystem.GetStatusForCode(statusValue);
+ if (column.Id == (int)SaCode.Column.Status_Desc)
+ value = new DataValue(SacsSystem.GetStatusDescForCode(statusValue));
+ else if (column.Id == (int)SaCode.Column.Status_Ind)
+ value = new DataValue(SaCode.GetStatusTriState(status));
+ }
+ break;
+
+ //-----------------------------------------------------------
+ }
+ return value;
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/Core/CodeDesc.cs b/SacsStandardBase/Base/Core/CodeDesc.cs
new file mode 100644
index 0000000..f121021
--- /dev/null
+++ b/SacsStandardBase/Base/Core/CodeDesc.cs
@@ -0,0 +1,123 @@
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+
+namespace Sarand.Net.Standard.Base.Core
+{
+ //=======================================================================
+ public class CodeDesc
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ //-------------------------------------------------------------------
+ public CodeDesc (String code, String desc, String comment = "")
+ {
+ Code = StringUtil.EmptyOnNull(code);
+ Desc = StringUtil.EmptyOnNull(desc);
+ Comment = StringUtil.EmptyOnNull(comment);
+ }
+ //-------------------------------------------------------------------
+ // Properties.
+ public String Code { get; }
+ public String Desc { get; }
+ public String Comment { get; }
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+ public static class CodeDescList
+ {
+ //-------------------------------------------------------------------
+ // Population.
+ public static void Add (this List values, String code, string desc, String comment = "")
+ {
+ values.Add(new CodeDesc(code, desc, comment));
+ }
+ public static void Insert (this List values, int idx, String code, string desc, String comment = "")
+ {
+ values.Insert (idx, new CodeDesc(code, desc, comment));
+ }
+
+ //-------------------------------------------------------------------
+ // Sorting.
+ public static void SortByCode (this List values)
+ {
+ values.Sort(
+ delegate (CodeDesc codeDesc1, CodeDesc codeDesc2)
+ {
+ return codeDesc1.Code.CompareTo(codeDesc2.Code);
+ }
+ );
+ }
+ public static void SortByDesc (this List values)
+ {
+ values.Sort(
+ delegate (CodeDesc codeDesc1, CodeDesc codeDesc2)
+ {
+ return codeDesc1.Desc.CompareTo(codeDesc2.Desc);
+ }
+ );
+ }
+
+ //-------------------------------------------------------------------
+ // Conversion.
+ public static List GetCodeList (this List values)
+ {
+ List codes = new List();
+ foreach (var value in values)
+ if (!codes.Contains(value.Code)) codes.Add(value.Code);
+ return codes;
+ }
+ public static List GetDescList (this List values)
+ {
+ List descs = new List();
+ foreach (var value in values)
+ if (!descs.Contains(value.Desc)) descs.Add(value.Desc);
+ return descs;
+ }
+
+ //-------------------------------------------------------------------
+ // Lookup.
+ public static bool FindCode (this List values, String code)
+ {
+ return values.Exists(item => item.Code.EqualsCI(code));
+ }
+ public static CodeDesc GetByCode (this List values, String code)
+ {
+ return values.Find(item => item.Code.EqualsCI(code));
+ }
+ public static CodeDesc GetByNumericCode (this List values, String code)
+ {
+ if (!StringUtil.IsNumeric(code)) return null;
+ return values.Find(item => StringUtil.IsNumeric(item.Code) && StringUtil.ToInt(item.Code) == StringUtil.ToInt(code));
+ }
+
+ //-------------------------------------------------------------------
+ // Lookup code.
+ /// Return code matching supplied code (case-insenstive) - to ensure case matches list.
+ public static String GetCodeForCode (this List values, String code, String defaultValue = "")
+ {
+ foreach (var value in values)
+ if (value.Code.EqualsCI(code)) return value.Code;
+ return StringUtil.EmptyOnNull(defaultValue);
+ }
+ /// Return code matching supplied description (case-insenstive).
+ public static String GetCodeForDesc (this List values, String desc, String defaultValue = "")
+ {
+ foreach (var value in values)
+ if (value.Desc.EqualsCI(desc)) return value.Code;
+ return StringUtil.EmptyOnNull(defaultValue);
+ }
+
+ //-------------------------------------------------------------------
+ // Lookup desc.
+ public static String GetDescForCode (this List values, String code, String defaultValue = "")
+ {
+ foreach (var value in values)
+ if (value.Code.EqualsCI(code)) return value.Desc;
+ return StringUtil.EmptyOnNull(defaultValue);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/Core/CodeParam.cs b/SacsStandardBase/Base/Core/CodeParam.cs
new file mode 100644
index 0000000..a6ee771
--- /dev/null
+++ b/SacsStandardBase/Base/Core/CodeParam.cs
@@ -0,0 +1,66 @@
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Sarand.Net.Standard.Base.Core
+{
+ //=======================================================================
+ ///
+ /// Code parameter value with flag to indicate whether the value
+ /// is included (valid as a filter) or excluded (explicitly not matched).
+ ///
+ public class CodeParam
+ {
+ //-------------------------------------------------------------------
+ // Construction.
+ public CodeParam (String code, bool including)
+ {
+ Code = StringUtil.EmptyOnNull(code);
+ Including = including;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ /// Code parameter.
+ public String Code { get; }
+ /// Whether the value is included (valid as a filter) or excluded (explicitly not matched).
+ public bool Including { get; }
+
+ //-------------------------------------------------------------------
+ /// Determine if the two code params 'match' - taking code and 'sense' flag into account.
+ public bool Matches (CodeParam cp)
+ {
+ // Values match if either code is blank:
+ if (StringUtil.IsEmpty(Code) || StringUtil.IsEmpty(cp.Code)) return true;
+
+ // Values match if both 'senses' are negative:
+ if (!Including && !cp.Including) return true;
+
+ // Values match if both 'senses' are positive and the codes are the same:
+ if (Including && cp.Including) return Code.EqualsCI(cp.Code);
+
+ // Values match if 'senses' and codes are different:
+ if (Including != cp.Including) return !Code.EqualsCI(cp.Code);
+
+ return false;
+ }
+
+ //-------------------------------------------------------------------
+ /// Compare code params - first on code then on flag.
+ public int Compare (CodeParam cp)
+ {
+ if (Code.EqualsCI(cp.Code))
+ {
+ // Compare 'Including' flags:
+ if (!Including && cp.Including) return 1;
+ if (Including && !cp.Including) return -1;
+ return 0;
+ }
+ return Code.CompareTo(cp.Code);
+ }
+
+ //-------------------------------------------------------------------
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/Core/ColumnDfn.cs b/SacsStandardBase/Base/Core/ColumnDfn.cs
new file mode 100644
index 0000000..333d919
--- /dev/null
+++ b/SacsStandardBase/Base/Core/ColumnDfn.cs
@@ -0,0 +1,428 @@
+using Sarand.Net.Standard.Base.Utils;
+using System;
+using System.Collections.Generic;
+
+namespace Sarand.Net.Standard.Base.Core
+{
+ //=======================================================================
+ // Definitions.
+ public enum ColumnAlignment { Default, Left, Center, Right }
+ public enum ColumnBoolFormat
+ {
+ /// Text: 'Y' (true) or 'N' (false).
+ YorN,
+ /// Text: 'Y' (true).
+ Y,
+ /// Text: 'N' (false).
+ N,
+ /// Text: '1' (true) or '0' (false).
+ OneOrZero,
+ /// Text: 'True' or 'False'.
+ TrueFalse,
+ /// Graphic icons: Tick (true) or Cross (false).
+ TickCross,
+ /// Graphic icon: Tick (true).
+ Tick,
+ /// Graphic icon: Cross (false).
+ Cross,
+ /// Graphic icons (inverted): Tick (false) or Cross (true).
+ TickCross_Inv,
+ /// Graphic icon (inverted): Tick (false).
+ Tick_Inv,
+ /// Graphic icon (inverted): Cross (true).
+ Cross_Inv
+ }
+ public enum ColumnTriStateFormat
+ {
+ /// Text: R, A, G etc.
+ RAG,
+ /// Text: 1, 2, 3 etc.
+ Numeric,
+ /// Graphic icons.
+ Lights
+ }
+
+ //=======================================================================
+ public class ColumnDfn
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ public enum ColumnSortMode
+ {
+ Off, On, AscendOnly, DescendOnly
+ }
+
+ //-------------------------------------------------------------------
+ // Data members.
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public ColumnDfn
+ (int id, String caption, int width = 0, int dbColumn = 0,
+ String dataName = "", String sortDataName = "",
+ DataValue.DataType dataType = DataValue.DataType.String, ColumnAlignment alignment = ColumnAlignment.Default)
+ {
+ this.Id = id;
+ this.Caption = caption;
+ this.Width = width;
+ this.DbColumn = dbColumn;
+ this.DataName = dataName;
+ this.SortDataName = sortDataName;
+ this.DataType = dataType;
+ this.Alignment = alignment;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public int Id { get; set; } = 0;
+ /// Database column identity (DbDfn.ColumnIdx etc).
+ public int DbColumn { get; set; } = 0;
+ /// Column code - eg. for configuration options.
+ public String Code { get; set; } = String.Empty;
+ /// Column header text.
+ public String Caption { get; set; } = String.Empty;
+ /// Column width (device dependent).
+ public int Width { get; set; } = 0;
+ public ColumnAlignment Alignment { get; set; } = ColumnAlignment.Default;
+ /// Data value name (may be an alias).
+ public String DataName { get; set; } = String.Empty;
+ public byte NumDecimals { get; set; } = 2;
+ public char NumThousands { get; set; } = ',';
+ /// Default: Zero values hidden.
+ public bool ShowZeroValues { get; set; } = false;
+ public ColumnBoolFormat BoolFormat { get; set; } = ColumnBoolFormat.YorN;
+ public ColumnTriStateFormat TriStateFormat { get; set; } = ColumnTriStateFormat.RAG;
+ /// Null date - values on or before are considered invalid.
+ public DateTime NullDate { get; set; } = DateTime.MinValue;
+ public String DateFormat { get; set; } = "d-MMM-yy";
+ public String DateTimeSeparator { get; set; } = " ";
+ public String TimeFormat { get; set; } = "HH:mm";
+ public DataValue.DataType DataType { get; set; } = DataValue.DataType.String;
+ public bool Resizable { get; set; } = true;
+ public bool Hidden { get; set; } = false;
+ public bool Frozen { get; set; } = false;
+ // Drop-down configuration.
+ public List Values { get; } = new List();
+ /// User must pick from the supplied list (default: true). Non-exclusive lists (editable) have same value and display members.
+ public bool ExclusiveValues { get; set; } = true;
+ /// User entered values (Exclusive=false) are converted if corresponding value found in list.
+ public bool MatchCase { get; set; } = true;
+ // Edit configuration:
+ public int MaxLength { get; set; } = 0;
+ public bool ReadOnly { get; set; } = false;
+ public bool Uppercase { get; set; } = false;
+ public bool Lowercase { get; set; } = false;
+ public bool Numeric { get; set; } = false;
+ public bool Password { get; set; } = false;
+ // Sort attributes.
+ /// Sort mode. Default: 'On'.
+ public ColumnSortMode SortMode { get; set; } = ColumnSortMode.On;
+ /// Data value name for sort (fully qualified).
+ public String SortDataName { get; set; } = String.Empty;
+ public bool SortAscending { get; set; } = true;
+
+ //-------------------------------------------------------------------
+ // Population.
+ public void Set
+ (String caption, int width, int dbColumn = 0,
+ String dataName = "", String sortDataName = "",
+ DataValue.DataType dataType = DataValue.DataType.String, ColumnAlignment alignment = ColumnAlignment.Default)
+ {
+ this.Caption = caption;
+ this.Width = width;
+ this.DbColumn = dbColumn;
+ this.DataName = dataName;
+ this.SortDataName = sortDataName;
+ this.DataType = dataType;
+ this.Alignment = alignment;
+ }
+
+ //-------------------------------------------------------------------
+ // Formatting.
+ public String FormatValue (NamedValue.Map values)
+ {
+ String value = String.Empty;
+ if (DataName.IsEmpty()) return value;
+ DataValue dataValue = values.GetValue(DataName);
+ if (dataValue == null || !dataValue.IsValid()) return value;
+ return FormatValue(dataValue);
+ }
+ public String FormatValue (DataValue dataValue)
+ {
+ String value = String.Empty;
+ if (dataValue == null || !dataValue.IsValid()) return value;
+ switch (DataType)
+ {
+ case DataValue.DataType.Date:
+ if (dataValue.IsDate())
+ {
+ DateTime date = dataValue.AsDate();
+ if (DateUtil.IsValid(date, NullDate))
+ value = date.Format(DateFormat);
+ }
+ else value = dataValue.ToString();
+ break;
+ case DataValue.DataType.Time:
+ if (dataValue.IsTime())
+ {
+ DateTime time = dataValue.AsTime();
+ if (DateUtil.IsValid(time, NullDate))
+ value = time.Format(TimeFormat);
+ }
+ else value = dataValue.ToString();
+ break;
+ case DataValue.DataType.DateTime:
+ if (dataValue.IsDateTime())
+ {
+ DateTime dateTime = dataValue.AsDateTime();
+ if (DateUtil.IsValid(dateTime, NullDate))
+ value = dateTime.Format(DateFormat + DateTimeSeparator + TimeFormat);
+ }
+ else value = dataValue.ToString();
+ break;
+ case DataValue.DataType.Long:
+ if (dataValue.IsNumeric())
+ {
+ long longValue = 0;
+ if (dataValue.GetDataType() == DataValue.DataType.Long) longValue = dataValue.AsLong();
+ else longValue = (long)dataValue.ToNumeric();
+ if (longValue != 0 || ShowZeroValues)
+ value = longValue.ToString();
+ }
+ else value = dataValue.ToString();
+ break;
+ case DataValue.DataType.Double:
+ if (dataValue.IsNumeric())
+ {
+ double doubleValue = dataValue.ToNumeric();
+ if (doubleValue != 0.0 || ShowZeroValues)
+ value = StringUtil.FormatNumeric(doubleValue, NumDecimals, 0, NumThousands);
+ }
+ else value = dataValue.ToString();
+ break;
+ case DataValue.DataType.Decimal:
+ if (dataValue.IsNumeric())
+ {
+ decimal decimalValue = 0.0m;
+ if (dataValue.GetDataType() == DataValue.DataType.Decimal) decimalValue = dataValue.AsDecimal();
+ else decimalValue = (decimal)dataValue.ToNumeric();
+ if (decimalValue != 0.0m || ShowZeroValues)
+ value = StringUtil.FormatNumeric(decimalValue, NumDecimals, 0, NumThousands);
+ }
+ else value = dataValue.ToString();
+ break;
+ case DataValue.DataType.Bool:
+ switch (BoolFormat)
+ {
+ case ColumnBoolFormat.YorN:
+ value = (dataValue.ToBool() ? "Y" : "N");
+ break;
+ case ColumnBoolFormat.Y:
+ value = (dataValue.ToBool() ? "Y" : "");
+ break;
+ case ColumnBoolFormat.N:
+ value = (dataValue.ToBool() ? "" : "N");
+ break;
+ case ColumnBoolFormat.TrueFalse:
+ value = (dataValue.ToBool() ? "True" : "False");
+ break;
+ default:
+ value = (dataValue.ToBool() ? "1" : "0");
+ break;
+ }
+ break;
+ case DataValue.DataType.TriState:
+ switch (TriStateFormat)
+ {
+ case ColumnTriStateFormat.RAG:
+ if (dataValue.AsTriState() == DataValue.TriStateType.Red)
+ value = "R";
+ else if (dataValue.AsTriState() == DataValue.TriStateType.Amber)
+ value = "A";
+ else if (dataValue.AsTriState() == DataValue.TriStateType.Green)
+ value = "G";
+ else if (dataValue.AsTriState() == DataValue.TriStateType.Blue)
+ value = "B";
+ else if (dataValue.AsTriState() == DataValue.TriStateType.Purple)
+ value = "P";
+ else if (dataValue.AsTriState() == DataValue.TriStateType.Yellow)
+ value = "Y";
+ else if (dataValue.AsTriState() == DataValue.TriStateType.Unknown)
+ value = "";
+ break;
+ default:
+ if (dataValue.AsTriState() == DataValue.TriStateType.Red)
+ value = "1";
+ else if (dataValue.AsTriState() == DataValue.TriStateType.Amber)
+ value = "2";
+ else if (dataValue.AsTriState() == DataValue.TriStateType.Green)
+ value = "3";
+ else if (dataValue.AsTriState() == DataValue.TriStateType.Blue)
+ value = "4";
+ else if (dataValue.AsTriState() == DataValue.TriStateType.Purple)
+ value = "5";
+ else if (dataValue.AsTriState() == DataValue.TriStateType.Yellow)
+ value = "6";
+ else if (dataValue.AsTriState() == DataValue.TriStateType.Unknown)
+ value = "";
+ break;
+ }
+ break;
+ default:
+ value = dataValue.ToString();
+ break;
+ }
+ return value.TrimEnd(' ');
+ }
+
+ //-------------------------------------------------------------------
+ }
+
+ //=======================================================================
+ public static class ColumnDfnList
+ {
+ //-------------------------------------------------------------------
+ // C o n t a i n s
+ //-------------------------------------------------------------------
+ /// Determine whether list contains column with Id matching specified enum value (integer comparison).
+ public static bool HasId (this List values, T id) where T : struct, IConvertible
+ {
+ if (!(id is Enum))
+ throw new ArgumentException("HasId parameter must be an enumerated type. Type supplied: " + id.GetType().Name);
+ return values.HasId((int)(id as IConvertible));
+ }
+ /// Determine whether list contains column with Id matching specified integer value.
+ public static bool HasId (this List values, int id)
+ {
+ foreach (var column in values)
+ if (column.Id == id) return true;
+ return false;
+ }
+ /// Determine whether list contains column with Db Column matching specified enum value (integer comparison).
+ public static bool HasDbColumn (this List values, T id) where T : struct, IConvertible
+ {
+ if (!(id is Enum))
+ throw new ArgumentException("HasDbColumn parameter must be an enumerated type. Type supplied: " + id.GetType().Name);
+ return values.HasDbColumn((int)(id as IConvertible));
+ }
+ /// Determine whether list contains column with Db Column matching specified integer value.
+ public static bool HasDbColumn (this List values, int dbColumn)
+ {
+ foreach (var column in values)
+ if (column.DbColumn == dbColumn) return true;
+ return false;
+ }
+
+ //-------------------------------------------------------------------
+ // Columns in list.
+ public static int GetPosForId (this List values, T id) where T : struct, IConvertible
+ {
+ if (!(id is Enum))
+ throw new ArgumentException("HasId parameter must be an enumerated type. Type supplied: " + id.GetType().Name);
+ return values.GetPosForId((int)(id as IConvertible));
+ }
+ public static int GetPosForId (this List values, int id)
+ {
+ for (int pos = 0; pos < values.Count; pos++)
+ if (values[pos].Id == id) return pos + 1;
+ return 0;
+ }
+ public static List GetColumnsWithNonZeroWidth (this List values)
+ {
+ return values.FindAll(item => item.Width > 0);
+ }
+
+ //-------------------------------------------------------------------
+ // C o l u m n W i d t h s
+ //-------------------------------------------------------------------
+ // Combined width of all columns.
+ /// Get the width of all non-zero width columns separated by the specified spacing
+ public static int GetFullWidth (this List values, int columnSpacing)
+ {
+ List columnsNonZeroWidth = values.GetColumnsWithNonZeroWidth();
+ if (columnsNonZeroWidth.Count < 1) return 0;
+ int width = 0;
+ foreach (var column in values)
+ width += column.Width;
+ return width + (columnSpacing * (columnsNonZeroWidth.Count - 1));
+ }
+ // Column widths are adjusted to keep their relative width constant.
+ /// Expand/contract non-zero width columns to fill the specified width with the specified space in between each.
+ public static bool FillWidth (this List values, long width, int columnSpacing)
+ {
+ List columnsNonZeroWidth = values.GetColumnsWithNonZeroWidth();
+ if (columnsNonZeroWidth.Count < 1) return false;
+ long fullWidthNoSpacing = values.GetFullWidth(0);
+ if (fullWidthNoSpacing < 1) return false;
+ double columnWidthFactor = ((double)width - (columnSpacing * (columnsNonZeroWidth.Count - 1))) / fullWidthNoSpacing;
+ foreach (ColumnDfn column in columnsNonZeroWidth)
+ column.Width = Math.Max(1, (int)Math.Ceiling(column.Width * columnWidthFactor));
+ return true;
+ }
+ /// Calculate the left position for each non-zero width column with the specified spacing.
+ public static int[] GetColumnLeftPositions (this List values, int leftPosition, int columnSpacing)
+ {
+ List columnsNonZeroWidth = values.GetColumnsWithNonZeroWidth();
+ if (columnsNonZeroWidth.Count < 1) return new int[0];
+ int[] columnPositions = new int[columnsNonZeroWidth.Count];
+ int columnLeftPos = leftPosition;
+ for (int columnIdx = 0; columnIdx < columnsNonZeroWidth.Count; columnIdx++)
+ {
+ ColumnDfn column = columnsNonZeroWidth[columnIdx];
+ columnPositions[columnIdx] = columnLeftPos;
+ columnLeftPos += column.Width + columnSpacing;
+ }
+ return columnPositions;
+ }
+ ///
+ /// Calculate the horizontal print position for each non-zero width column with the specified spacing.
+ /// NB Print position will take alignment (left/centre/right) into consideration.
+ ///
+ public static int[] GetColumnPrintPositions (this List values, int leftPosition, int columnSpacing)
+ {
+ List columnsNonZeroWidth = values.GetColumnsWithNonZeroWidth();
+ if (columnsNonZeroWidth.Count < 1) return new int[0];
+ int[] columnPositions = new int[columnsNonZeroWidth.Count];
+ int columnLeftPos = leftPosition;
+ for (int columnIdx = 0; columnIdx < columnsNonZeroWidth.Count; columnIdx++)
+ {
+ ColumnDfn column = columnsNonZeroWidth[columnIdx];
+ int columnPos = columnLeftPos;
+ if (column.Alignment == ColumnAlignment.Center)
+ columnPos = columnLeftPos + (column.Width / 2);
+ else if (column.Alignment == ColumnAlignment.Right)
+ columnPos = columnLeftPos + column.Width;
+ columnPositions[columnIdx] = columnPos;
+ columnLeftPos += column.Width + columnSpacing;
+ }
+ return columnPositions;
+ }
+
+ //-------------------------------------------------------------------
+ // E x t r a c t C o n t e n t s
+ //-------------------------------------------------------------------
+ /// Compile list of captions from the columns.
+ public static List GetCaptions (this List values)
+ {
+ List captions = new List();
+ foreach (ColumnDfn column in values)
+ captions.Add(StringUtil.EmptyOnNull(column.Caption));
+ return captions;
+ }
+ ///
+ /// Convert column list to strings representing sort columns.
+ /// NB Column name is populated as DataName and may not be the same as used in a database query in all cases.
+ ///
+ public static List GetSortColumns (this List values)
+ {
+ List sortColumns = new List();
+ foreach (ColumnDfn column in values)
+ sortColumns.Add(column.DataName + (column.SortAscending ? " ASC" : " DESC"));
+ return sortColumns;
+ }
+
+ //-------------------------------------------------------------------
+ }
+
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/Core/DataItem.cs b/SacsStandardBase/Base/Core/DataItem.cs
new file mode 100644
index 0000000..7400a39
--- /dev/null
+++ b/SacsStandardBase/Base/Core/DataItem.cs
@@ -0,0 +1,120 @@
+using Sarand.Net.Standard.Base.Core;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Sarand.Net.Standard.Base.Bus
+{
+ //=======================================================================
+ public class DataItem
+ {
+ //-------------------------------------------------------------------
+ // Data members.
+ private readonly NamedValue.Map values = new NamedValue.Map();
+ private readonly List childItems = new List();
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public DataItem (int itemType = 0)
+ {
+ this.ItemType = itemType;
+ }
+ public DataItem (NamedValue.Map values, int itemType = 0)
+ {
+ this.ItemType = itemType;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public int ItemType { get; }
+
+ //-------------------------------------------------------------------
+ // Values.
+ public bool HasValue (String name)
+ {
+ return values.HasValue(name);
+ }
+ public DataValue.DataType GetValueType (String name)
+ {
+ return GetValue(name)?.GetDataType() ?? DataValue.DataType.Unknown;
+ }
+ public DataValue GetValue (String name)
+ {
+ return values.GetValue(name);
+ }
+ public NamedValue.Map GetValues ()
+ {
+ return new NamedValue.Map(values);
+ }
+ public bool SetValue (String name, DataValue value)
+ {
+ values.AddValue(name, value);
+ return HasValue(name);
+ }
+ public bool SetValue (NamedValue value)
+ {
+ return SetValue(value.Name, value.Value);
+ }
+ public bool SetValues (NamedValue.Map values)
+ {
+ return this.values.AddValues(values);
+ }
+ public bool SetValues (ICollection values)
+ {
+ return this.values.AddValues(values);
+ }
+ public bool RenameValue (String nameOld, String nameNew)
+ {
+ return values.RenameValue(nameOld, nameNew);
+ }
+
+ //-------------------------------------------------------------------
+ // Child items.
+ public long GetChildItemCount ()
+ {
+ return childItems.Count;
+ }
+ public DataItem GetChildItemByPos (int itemIdx)
+ {
+ if (itemIdx <= childItems.Count)
+ return childItems[itemIdx];
+ return null;
+ }
+ public DataItem GetChildItemByType (int itemType)
+ {
+ return childItems.Find(item => item.ItemType == itemType);
+ }
+ public List GetChildItemsByType (int itemType)
+ {
+ List items = new List();
+ foreach (DataItem item in childItems)
+ {
+ if (item != null && item.ItemType == itemType)
+ items.Add(item);
+ }
+ return items;
+ }
+
+ // Update child items.
+ public bool ClearChildItems ()
+ {
+ childItems.Clear();
+ return true;
+ }
+ public bool AddChildItem (DataItem item)
+ {
+ if (item != null)
+ childItems.Add(item);
+ return item != null;
+ }
+
+ //-------------------------------------------------------------------
+ }
+
+ //=======================================================================
+ public static class SacsDataItemList
+ {
+
+ }
+ //=======================================================================
+}
diff --git a/SacsStandardBase/Base/Core/DataValue.cs b/SacsStandardBase/Base/Core/DataValue.cs
new file mode 100644
index 0000000..d9bf244
--- /dev/null
+++ b/SacsStandardBase/Base/Core/DataValue.cs
@@ -0,0 +1,449 @@
+using System;
+using System.Collections.Generic;
+using Sarand.Net.Standard.Base.App;
+using Sarand.Net.Standard.Base.Utils;
+
+namespace Sarand.Net.Standard.Base.Core
+{
+ //=======================================================================
+ public class DataValue
+ {
+ //-------------------------------------------------------------------
+ // Definitions.
+ public enum DataType { Unknown = 0, Bool, TriState, Long, Double, Decimal, String, Date, Time, DateTime, Blob };
+ public enum TriStateType { Unknown = 0, Red, Amber, Green, Blue, Purple, Yellow };
+
+ //-------------------------------------------------------------------
+ // Data members.
+ private DataType dataType;
+ private Object value;
+
+ //-------------------------------------------------------------------
+ // Construction.
+ public DataValue () { }
+ public DataValue (DataValue dataValue)
+ {
+ if (dataValue != null)
+ {
+ dataType = dataValue.GetDataType();
+ value = dataValue.value;
+ }
+ }
+ public DataValue (bool value)
+ {
+ this.value = value;
+ this.dataType = DataType.Bool;
+ }
+ public DataValue (TriStateType value)
+ {
+ this.value = value;
+ this.dataType = DataType.TriState;
+ }
+ public DataValue (long value)
+ {
+ this.value = value;
+ this.dataType = DataType.Long;
+ }
+ public DataValue (double value)
+ {
+ this.value = value;
+ this.dataType = DataType.Double;
+ }
+ public DataValue (decimal value)
+ {
+ this.value = value;
+ this.dataType = DataType.Decimal;
+ }
+ public DataValue (String value)
+ {
+ this.value = value;
+ this.dataType = DataType.String;
+ }
+ public DataValue (DateTime value, DataType type = DataType.DateTime)
+ {
+ this.value = value;
+ switch (type)
+ {
+ case DataType.Date:
+ case DataType.Time:
+ case DataType.DateTime:
+ this.dataType = type;
+ break;
+ default:
+ this.dataType = DataType.DateTime;
+ break;
+ }
+ }
+ public DataValue (Byte[] value)
+ {
+ this.value = value;
+ this.dataType = DataType.Blob;
+ }
+
+ //-------------------------------------------------------------------
+ // Properties.
+ public static readonly DataValue NullValue = new DataValue();
+ public DataType GetDataType ()
+ {
+ return dataType;
+ }
+ public static String GetTypeDesc (DataType type)
+ {
+ // Converts type to string.
+ switch (type)
+ {
+ case DataType.Bool: return "Bool";
+ case DataType.TriState: return "TriState";
+ case DataType.Long: return "Long";
+ case DataType.Double: return "Double";
+ case DataType.Decimal: return "Decimal";
+ case DataType.String: return "String";
+ case DataType.Date: return "Date";
+ case DataType.Time: return "Time";
+ case DataType.DateTime: return "DateTime";
+ case DataType.Blob: return "Blob";
+ }
+ return "";
+ }
+ public String GetTypeDesc ()
+ {
+ return GetTypeDesc(dataType);
+ }
+ public bool IsValid ()
+ {
+ return (dataType != DataType.Unknown);
+ }
+ public bool IsNumeric ()
+ {
+ return (dataType == DataType.Long || dataType == DataType.Double || dataType == DataType.Decimal);
+ }
+ public bool IsDate ()
+ {
+ return (dataType == DataType.Date || dataType == DataType.DateTime);
+ }
+ public bool IsTime ()
+ {
+ return (dataType == DataType.Time || dataType == DataType.DateTime);
+ }
+ public bool IsDateTime ()
+ {
+ return (dataType == DataType.Date || dataType == DataType.Time || dataType == DataType.DateTime);
+ }
+ public bool IsBlob ()
+ {
+ return (dataType == DataType.Blob);
+ }
+
+ //-------------------------------------------------------------------
+ // Conversion.
+ public override String ToString ()
+ {
+ if (value == null) return String.Empty;
+ // Forces any data type to string.
+ switch (dataType)
+ {
+ case DataType.Bool: return ((bool)value ? "Y" : "N");
+ case DataType.TriState: return ((TriStateType)value).ToString();
+ case DataType.Long: return ((long)value).ToString();
+ case DataType.Double: return ((double)value).ToString();
+ case DataType.Decimal: return ((decimal)value).ToString();
+ case DataType.String: return (String)value;
+ case DataType.Date: return ((DateTime)value).ToString("yyyyMMdd");
+ case DataType.Time: return ((DateTime)value).ToString("HH:mm:ss");
+ case DataType.DateTime: return ((DateTime)value).ToString("yyyyMMddHHmmss");
+ }
+ return String.Empty;
+ }
+ public double ToNumeric ()
+ {
+ if (value == null) return 0.0;
+ // Convert any type to double numeric.
+ switch (dataType)
+ {
+ case DataType.Bool: return ((bool)value ? 1 : 0);
+ case DataType.TriState: return (int)(TriStateType)value;
+ case DataType.Long: return (long)value;
+ case DataType.Double: return (double)value;
+ case DataType.Decimal: return (double)(decimal)value;
+ case DataType.String: return StringUtil.ToDouble((String)value);
+ case DataType.Date: return ((DateTime)value).Ticks;
+ case DataType.Time: return ((DateTime)value).Ticks;
+ case DataType.DateTime: return ((DateTime)value).Ticks;
+ }
+ return 0.0;
+ }
+ public bool ToBool ()
+ {
+ if (value == null) return false;
+ // Numeric <> 0, String: 'Yes', 'True', numeric value.
+ switch (dataType)
+ {
+ case DataType.Bool: return (bool)value;
+ case DataType.TriState: return (TriStateType)value != TriStateType.Unknown;
+ case DataType.Long: return (long)value != 0;
+ case DataType.Double: return (double)value != 0.0;
+ case DataType.Decimal: return (decimal)value != 0.0m;
+ case DataType.String: return ((String)value).ToBool();
+ case DataType.Date: return ((DateTime)value).Ticks != 0;
+ case DataType.Time: return ((DateTime)value).Ticks != 0;
+ case DataType.DateTime: return ((DateTime)value).Ticks != 0;
+ }
+ return false;
+ }
+ public bool AsBool ()
+ {
+ // Convert to specific type of data - fails if type is not correct:
+ if (dataType == DataType.Bool) return (bool)value;
+ if (dataType == DataType.Unknown) return false;
+#if DEBUG
+ SaLogger.Debug(GetType().Name + ".AsBool Incorrect data type: " + dataType.ToString());
+#endif
+ return false;
+ }
+ public TriStateType AsTriState ()
+ {
+ if (dataType == DataType.TriState) return (TriStateType)value;
+ if (dataType == DataType.Unknown) return TriStateType.Unknown;
+ if (SacsSystem.DebugMode) SaLogger.Debug(GetType().Name + ".AsTriState Incorrect data type: " + dataType.ToString());
+ return TriStateType.Unknown;
+ }
+ public long AsLong ()
+ {
+ if (dataType == DataType.Long) return (long)value;
+ if (dataType == DataType.Unknown) return 0;
+ if (SacsSystem.DebugMode) SaLogger.Debug(GetType().Name + ".AsLong Incorrect data type: " + dataType.ToString());
+ return 0;
+ }
+ public double AsDouble ()
+ {
+ if (dataType == DataType.Double) return (double)value;
+ if (dataType == DataType.Unknown) return 0.0;
+ // Aug-2018 Temporarily allow decimal values to be returned as double.
+ if (dataType == DataType.Decimal)
+ {
+ SaLogger.Warn(GetType().Name + ".AsDouble Data type: " + dataType.ToString());
+ return (double)(decimal)value;
+ }
+ if (SacsSystem.DebugMode) SaLogger.Debug(GetType().Name + ".AsDouble Incorrect data type: " + dataType.ToString());
+ return 0.0;
+ }
+ public decimal AsDecimal ()
+ {
+ if (dataType == DataType.Decimal) return (decimal)value;
+ if (dataType == DataType.Unknown) return 0.00m;
+ if (SacsSystem.DebugMode) SaLogger.Debug(GetType().Name + ".AsDecimal Incorrect data type: " + dataType.ToString());
+ return 0.00m;
+ }
+ public String AsString ()
+ {
+ if (dataType == DataType.String) return (String)value;
+ if (dataType == DataType.Unknown) return "";
+ if (SacsSystem.DebugMode) SaLogger.Debug(GetType().Name + ".AsString Incorrect data type: " + dataType.ToString());
+ return "";
+ }
+ public DateTime AsDateTime ()
+ {
+ // NB DateTime.Ticks is NotFiniteNumberException of 100-nanosecond intervals since beginning of 1st January 0001.
+ if (dataType == DataType.Date || dataType == DataType.Time || dataType == DataType.DateTime) return (DateTime)value;
+ if (dataType == DataType.Unknown) return DateTime.MinValue;
+ if (SacsSystem.DebugMode) SaLogger.Debug(GetType().Name + ".AsDateTime Incorrect data type: " + dataType.ToString());
+ return DateTime.MinValue;
+ }
+ public DateTime AsDate ()
+ {
+ if (dataType == DataType.Date || dataType == DataType.DateTime) return ((DateTime)value).Date;
+ if (dataType == DataType.Unknown) return DateTime.MinValue;
+ if (SacsSystem.DebugMode) SaLogger.Debug(GetType().Name + ".AsDate Incorrect data type: " + dataType.ToString());
+ return DateTime.MinValue;
+ }
+ public DateTime AsTime ()
+ {
+ if (dataType == DataType.Time || dataType == DataType.DateTime) return (DateTime)value;
+ if (dataType == DataType.Unknown) return DateTime.MinValue;
+ if (SacsSystem.DebugMode) SaLogger.Debug(GetType().Name + ".AsTime Incorrect data type: " + dataType.ToString());
+ return DateTime.MinValue;
+ }
+ public Byte[] AsBlob ()
+ {
+ if (dataType == DataType.Blob) return (Byte[])value;
+ if (dataType == DataType.Unknown) return null;
+ if (SacsSystem.DebugMode) SaLogger.Debug(GetType().Name + ".AsBlob Incorrect data type: " + dataType.ToString());
+ return null;
+ }
+
+ //-------------------------------------------------------------------
+ // Comparison.
+ public bool Equals (DataValue value)
+ {
+ return false;
+ }
+ public int CompareTo (DataValue value)
+ {
+ return -1;
+ }
+
+ //-------------------------------------------------------------------
+ // Utilities.
+ public void Clear ()
+ {
+ dataType = DataType.Unknown;
+ value = null;
+ }
+
+ //-------------------------------------------------------------------
+ // Arithmeic.
+ public void Increment (DataValue value)
+ {
+ if (dataType == value.GetDataType())
+ {
+ switch (dataType)
+ {
+ case DataType.Long:
+ this.value = (long)(this.value) + value.AsLong();
+ break;
+ case DataType.Double:
+ this.value = (double)(this.value) + value.AsDouble();
+ break;
+ case DataType.Decimal:
+ this.value = (decimal)(this.value) + value.AsDecimal();
+ break;
+ }
+ }
+ }
+ public void Decrement (DataValue value)
+ {
+ if (dataType == value.GetDataType())
+ {
+ switch (dataType)
+ {
+ case DataType.Long:
+ this.value = (long)(this.value) - value.AsLong();
+ break;
+ case DataType.Double:
+ this.value = (double)(this.value) - value.AsDouble();
+ break;
+ case DataType.Decimal:
+ this.value = (decimal)(this.value) - value.AsDecimal();
+ break;
+ }
+ }
+ }
+ public void MultiplyBy (DataValue value)
+ {
+ if (dataType == value.GetDataType())
+ {
+ switch (dataType)
+ {
+ case DataType.Long:
+ this.value = (long)(this.value) * value.AsLong();
+ break;
+ case DataType.Double:
+ this.value = (double)(this.value) * value.AsDouble();
+ break;
+ case DataType.Decimal:
+ this.value = (decimal)(this.value) * value.AsDecimal();
+ break;
+ }
+ }
+ }
+ public void DivideBy (DataValue value)
+ {
+ if (dataType == value.GetDataType())
+ {
+ switch (dataType)
+ {
+ case DataType.Long:
+ this.value = (long)(this.value) / value.AsLong();
+ break;
+ case DataType.Double:
+ this.value = (double)(this.value) / value.AsDouble();
+ break;
+ case DataType.Decimal:
+ this.value = (decimal)(this.value) / value.AsDecimal();
+ break;
+ }
+ }
+ }
+
+ //-------------------------------------------------------------------
+ }
+
+ //=======================================================================
+ public static class DataValueList
+ {
+ //-------------------------------------------------------------------
+ // Conversion.
+ /// Convert all data values to strings.
+ public static List ToStringList (this List values)
+ {
+ List stringValues = new List();
+ foreach (var dataValue in values)
+ stringValues.Add(values.ToString());
+ return stringValues;
+ }
+ ///
+ /// Convert the data values to raw objects.
+ /// eg. For population to Excel spreadsheet cells.
+ ///
+ public static List