From a4cf706eb324ffbb70d7880510ecbd793f55f61a Mon Sep 17 00:00:00 2001 From: Andy Howard Date: Wed, 5 May 2021 15:36:28 +0100 Subject: [PATCH] Add project files. --- SaCommonVS.sln | 49 + SacsCoreUnitTests/SacsCoreUnitTests.csproj | 29 + .../Base/App/SacsCommandLineInfo_Tester.cs | 35 + .../Base/App/SacsProcessDefinition_Tester.cs | 52 + .../Standard/Base/Bus/SysId_Tester.cs | 179 ++ .../Base/Data/SacsAesIvCrypter_Tester.cs | 94 + .../Base/Data/SacsCsvHandler_Tester.cs | 75 + .../Standard/Base/Data/SacsDataFn_Tester.cs | 85 + .../Standard/Base/Data/SacsData_Tester.cs | 61 + .../Base/Generic/ObjectReferences_Tester.cs | 86 + .../Standard/Base/Generic/Regex_Tester.cs | 19 + .../Base/Helpers/SacsCsvHelper_Tester.cs | 133 ++ .../Standard/Base/Utils/DateUtil_Tester.cs | 259 +++ .../Standard/Base/Utils/EnumUtil_Tester.cs | 71 + .../Standard/Base/Utils/FtpUtil_Tester.cs | 63 + .../Base/Utils/StringListUtil_Tester.cs | 128 ++ .../Standard/Base/Utils/StringUtil_Tester.cs | 99 + SacsCoreWeb/SacsCoreWeb.csproj | 16 + SacsStandardBase/Base/App/SaLogger.cs | 208 ++ SacsStandardBase/Base/App/SacsApplication.cs | 22 + .../Base/App/SacsCommandLineInfo.cs | 133 ++ .../Base/App/SacsProcessDefinition.cs | 92 + .../Base/App/SacsSessionConfig.cs | 19 + .../Base/App/SacsSessionContext.cs | 36 + SacsStandardBase/Base/App/SacsSystem.cs | 201 ++ SacsStandardBase/Base/App/SacsVersion.cs | 75 + SacsStandardBase/Base/Bus/ItemId.cs | 109 + SacsStandardBase/Base/Bus/SaErrorStateItem.cs | 105 + .../Base/Bus/SacsHierarchyItem.cs | 466 ++++ SacsStandardBase/Base/Bus/SacsItemMgr.cs | 63 + SacsStandardBase/Base/Bus/SysId.cs | 176 ++ SacsStandardBase/Base/Bus/SysIdString.cs | 73 + SacsStandardBase/Base/CodeTypes/SaCode.cs | 260 +++ SacsStandardBase/Base/CodeTypes/SaCodeDfn.cs | 35 + SacsStandardBase/Base/CodeTypes/SaCodeType.cs | 229 ++ .../Base/CodeTypes/SaCodeTypeConsole.cs | 149 ++ .../Base/CodeTypes/SaCodeTypeDbDfn.cs | 351 +++ .../Base/CodeTypes/SaCodeTypeDfn.cs | 53 + .../Base/CodeTypes/SaCodeTypeMgr.cs | 319 +++ SacsStandardBase/Base/Core/CodeDesc.cs | 123 ++ SacsStandardBase/Base/Core/CodeParam.cs | 66 + SacsStandardBase/Base/Core/ColumnDfn.cs | 428 ++++ SacsStandardBase/Base/Core/DataItem.cs | 120 ++ SacsStandardBase/Base/Core/DataValue.cs | 449 ++++ .../Base/Core/ElapsedTimeLogger.cs | 59 + SacsStandardBase/Base/Core/FlagDfn.cs | 42 + .../Base/Core/ISacsMessageConsole.cs | 24 + SacsStandardBase/Base/Core/NamedValue.cs | 264 +++ SacsStandardBase/Base/Core/PropertySet.cs | 133 ++ SacsStandardBase/Base/Core/SacsCommand.cs | 70 + SacsStandardBase/Base/Core/SacsDfn.cs | 168 ++ SacsStandardBase/Base/Core/SacsDisposable.cs | 52 + SacsStandardBase/Base/Core/SacsErrorState.cs | 22 + SacsStandardBase/Base/Core/SacsMessage.cs | 34 + SacsStandardBase/Base/Core/SacsStateItem.cs | 58 + SacsStandardBase/Base/Core/StackLimited.cs | 57 + SacsStandardBase/Base/Core/StringPair.cs | 108 + SacsStandardBase/Base/Core/TypeCodeDesc.cs | 213 ++ .../Base/Data/SacsAesIvCrypter.cs | 117 + SacsStandardBase/Base/Data/SacsCsvHandler.cs | 117 + SacsStandardBase/Base/Data/SacsData.cs | 92 + SacsStandardBase/Base/Data/SacsDataFn.cs | 325 +++ SacsStandardBase/Base/Data/SacsShaHasher.cs | 79 + SacsStandardBase/Base/Dbas/DbDfnColumn.cs | 203 ++ SacsStandardBase/Base/Dbas/DbDfnDatabase.cs | 37 + SacsStandardBase/Base/Dbas/DbDfnIndex.cs | 96 + .../Base/Dbas/DbDfnIndexColumn.cs | 61 + SacsStandardBase/Base/Dbas/DbDfnTable.cs | 160 ++ SacsStandardBase/Base/Dbas/DbQuery.cs | 751 +++++++ SacsStandardBase/Base/Dbas/DbResultSet.cs | 176 ++ SacsStandardBase/Base/Dbas/DbUpdateInfo.cs | 29 + SacsStandardBase/Base/Dbas/DbUtil.cs | 10 + SacsStandardBase/Base/Dbas/IDbDataItem.cs | 54 + SacsStandardBase/Base/Dbas/IDbDataTable.cs | 9 + SacsStandardBase/Base/Dbas/IDbResultSet.cs | 37 + SacsStandardBase/Base/Dbas/ISacsDatabase.cs | 94 + SacsStandardBase/Base/Dbas/OdbcUtil.cs | 257 +++ SacsStandardBase/Base/Diary/SaDiaryConsole.cs | 42 + .../Base/Email/SaEmailAttachmentData.cs | 39 + SacsStandardBase/Base/Email/SaEmailConfig.cs | 46 + SacsStandardBase/Base/Email/SaEmailConsole.cs | 194 ++ SacsStandardBase/Base/Email/SaEmailData.cs | 24 + .../Base/Helpers/SacsContentTypeHelper.cs | 239 +++ .../Base/Helpers/SacsCsvHelper.cs | 266 +++ .../Base/Helpers/SacsDataValidationHelper.cs | 87 + .../Base/Helpers/SacsEncryptionHelper.cs | 128 ++ .../Base/Helpers/SacsHttpClientHelper.cs | 253 +++ .../Base/Helpers/SacsJsonHelper.cs | 20 + .../Base/Helpers/SacsRandomHelper.cs | 55 + .../Base/Helpers/SacsResourceHelper.cs | 243 +++ SacsStandardBase/Base/IO/DirectoryUtil.cs | 146 ++ SacsStandardBase/Base/IO/FileUtil.cs | 361 ++++ .../Base/Interfaces/SaInterface.cs | 287 +++ .../Base/Interfaces/SaInterfaceConsole.cs | 211 ++ .../Base/Interfaces/SaInterfaceDataItem.cs | 383 ++++ .../Base/Interfaces/SaInterfaceDbDfn.cs | 455 ++++ .../SaInterfaceMgr-InterfaceDataItems.cs | 204 ++ ...aInterfaceMgr-InterfaceProcessDataItems.cs | 15 + ...SaInterfaceMgr-InterfaceProcessMessages.cs | 166 ++ .../SaInterfaceMgr-InterfaceProcesses.cs | 238 +++ .../Interfaces/SaInterfaceMgr-Interfaces.cs | 210 ++ .../Base/Interfaces/SaInterfaceMgr.cs | 50 + .../Base/Interfaces/SaInterfaceProcess.cs | 298 +++ .../Interfaces/SaInterfaceProcessDataItem.cs | 201 ++ .../Interfaces/SaInterfaceProcessMessage.cs | 238 +++ SacsStandardBase/Base/ItemData/SaItemData.cs | 222 ++ .../Base/ItemData/SaItemDataConsole.cs | 69 + .../Base/ItemData/SaItemDataDbDfn.cs | 317 +++ .../Base/ItemData/SaItemDataMgr.cs | 171 ++ SacsStandardBase/Base/ItemDocs/SaItemDoc.cs | 313 +++ .../Base/ItemDocs/SaItemDocConsole.cs | 85 + .../Base/ItemDocs/SaItemDocMgr.cs | 207 ++ .../Base/ItemDocs/SaItemDocsDbDfn.cs | 327 +++ .../Base/ItemOptions/SaItemOption.cs | 279 +++ .../Base/ItemOptions/SaItemOptionConsole.cs | 86 + .../Base/ItemOptions/SaItemOptionDbDfn.cs | 317 +++ .../Base/ItemOptions/SaItemOptionDfn.cs | 148 ++ .../Base/ItemOptions/SaItemOptionMgr.cs | 176 ++ SacsStandardBase/Base/Procs/SacsProcess.cs | 244 +++ SacsStandardBase/Base/Procs/SacsReport.cs | 66 + .../Base/ReferenceData/SaCurrency.cs | 302 +++ .../Base/ReferenceData/SaLanguage.cs | 214 ++ .../SaReferenceDataConsole-Currency.cs | 35 + .../SaReferenceDataConsole-Language.cs | 35 + .../SaReferenceDataConsole-Region.cs | 47 + .../SaReferenceDataConsole-TaxCode.cs | 47 + .../SaReferenceDataConsole-UnitMeasure.cs | 35 + .../ReferenceData/SaReferenceDataConsole.cs | 265 +++ .../ReferenceData/SaReferenceDataDbDfn.cs | 466 ++++ .../Base/ReferenceData/SaReferenceDataMgr.cs | 36 + .../SaReferenceDataMgr_Currency.cs | 153 ++ .../SaReferenceDataMgr_Language.cs | 115 + .../SaReferenceDataMgr_Region.cs | 155 ++ .../SaReferenceDataMgr_TaxCode.cs | 179 ++ .../SaReferenceDataMgr_UnitMeasure.cs | 188 ++ .../Base/ReferenceData/SaRegion.cs | 207 ++ .../Base/ReferenceData/SaTaxCode.cs | 330 +++ .../Base/ReferenceData/SaUnitMeasure.cs | 352 +++ .../Base/Security/SaSecurityConsole.cs | 285 +++ .../Base/Security/SaSecurityDbDfn.cs | 360 ++++ .../Base/Security/SaSecurityItem.cs | 389 ++++ .../Base/Security/SaSecurityLink.cs | 286 +++ .../Security/SaSecurityMgr-SecurityItem.cs | 166 ++ .../Security/SaSecurityMgr-SecurityLink.cs | 126 ++ .../Base/Security/SaSecurityMgr.cs | 42 + .../Base/Security/SaSecurityProfile.cs | 215 ++ .../Base/Security/SaSecurityRight.cs | 37 + SacsStandardBase/Base/Tasks/SacsTaskData.cs | 76 + .../Base/UpdateLog/SaUpdateLogConsole.cs | 84 + .../Base/UpdateLog/SaUpdateLogDbDfn.cs | 320 +++ .../Base/UpdateLog/SaUpdateLogEntry.cs | 226 ++ .../Base/UpdateLog/SaUpdateLogItemMonitor.cs | 163 ++ .../Base/UpdateLog/SaUpdateLogMgr.cs | 205 ++ SacsStandardBase/Base/Utils/ArrayUtil.cs | 23 + SacsStandardBase/Base/Utils/AssemblyUtil.cs | 66 + SacsStandardBase/Base/Utils/DateUtil.cs | 651 ++++++ SacsStandardBase/Base/Utils/DictionaryUtil.cs | 80 + SacsStandardBase/Base/Utils/EnumUtil.cs | 95 + SacsStandardBase/Base/Utils/EnumerableUtil.cs | 30 + SacsStandardBase/Base/Utils/ExceptionUtil.cs | 84 + SacsStandardBase/Base/Utils/FtpUtil.cs | 155 ++ SacsStandardBase/Base/Utils/ListUtil.cs | 75 + SacsStandardBase/Base/Utils/LoggingUtils.cs | 46 + SacsStandardBase/Base/Utils/MathUtil.cs | 148 ++ .../Base/Utils/SerializableUtil.cs | 82 + SacsStandardBase/Base/Utils/StringListUtil.cs | 183 ++ SacsStandardBase/Base/Utils/StringUtil.cs | 469 ++++ SacsStandardBase/Base/Utils/SysUtil.cs | 14 + SacsStandardBase/Base/Utils/WebUtil.cs | 45 + SacsStandardBase/Base/Utils/XmlUtil.cs | 48 + SacsStandardBase/Base/Xml/SacsSoapUtil.cs | 59 + SacsStandardBase/Base/Xml/SacsXmlUtil.cs | 180 ++ SacsStandardBase/Resources/html/sa-base.css | 141 ++ SacsStandardBase/Resources/html/sa-base.js | 33 + .../images/IconShock/add_green_72.png | Bin 0 -> 2232 bytes .../images/IconShock/arrow_down_purple_72.png | Bin 0 -> 1912 bytes .../IconShock/arrow_right_purple_72.png | Bin 0 -> 1789 bytes .../images/IconShock/checkmark_green_72.png | Bin 0 -> 953 bytes .../images/IconShock/close_red_72.png | Bin 0 -> 2656 bytes .../images/IconShock/navicon_darkgrey_72.png | Bin 0 -> 536 bytes .../images/IconShock/pencil_orange_72.png | Bin 0 -> 906 bytes .../images/IconShock/trashcan_grey_72.png | Bin 0 -> 598 bytes .../icons/arrows/arrow_left_black_16.png | Bin 0 -> 415 bytes .../icons/arrows/arrow_left_black_24.png | Bin 0 -> 610 bytes .../arrows/arrow_left_double_black_16.png | Bin 0 -> 570 bytes .../arrows/arrow_left_double_black_24.png | Bin 0 -> 909 bytes .../icons/arrows/arrow_right_black_16.png | Bin 0 -> 420 bytes .../icons/arrows/arrow_right_black_24.png | Bin 0 -> 613 bytes .../arrows/arrow_right_double_black_16.png | Bin 0 -> 573 bytes .../arrows/arrow_right_double_black_24.png | Bin 0 -> 919 bytes .../images/icons/burgers/burger_black_128.png | Bin 0 -> 857 bytes .../images/icons/burgers/burger_black_220.png | Bin 0 -> 1637 bytes .../images/icons/burgers/burger_black_32.png | Bin 0 -> 285 bytes .../images/icons/burgers/burger_black_48.png | Bin 0 -> 359 bytes .../images/icons/burgers/burger_black_64.png | Bin 0 -> 438 bytes .../images/icons/burgers/burger_black_96.png | Bin 0 -> 627 bytes .../Resources/jquery/jquery-1.12.1.min.js | 5 + .../Resources/jquery/jquery-ui-1.12.1.min.css | 7 + .../Resources/jquery/jquery-ui-1.12.1.min.js | 13 + SacsStandardBase/SacsStandardBase.csproj | 20 + .../Dbas/Base/DatabaseDefinitionGenerator.cs | 807 +++++++ .../Dbas/Base/SaDbDfnConnection.cs | 48 + .../Dbas/Base/SaDbDfnOdbcDataSource.cs | 39 + .../Dbas/Base/SaDbDfnOdbcDriver.cs | 58 + SacsStandardDbas/Dbas/Base/SacsDatabase.cs | 267 +++ .../Dbas/Base/SacsDatabaseConsole.cs | 301 +++ .../CodeTypes/Internal/SaCodeTypeDbDfn.xml | 683 ++++++ .../Dbas/CodeTypes/Internal/SaCodeType_Db.cs | 226 ++ .../Dbas/CodeTypes/Internal/SaCode_Db.cs | 224 ++ .../Dbas/CodeTypes/SaCodeTypeConsole_Dbas.cs | 494 +++++ .../CodeTypes/SaCodeTypeDbDfnGenerator.cs | 113 + .../Dbas/CodeTypes/SaCodeTypeInternal.cs | 13 + .../Dbas/CodeTypes/SaCodeTypeMgr_Dbas.cs | 1117 ++++++++++ .../Dbas/CodeTypes/SaCodeType_Dbas.cs | 21 + .../Dbas/CodeTypes/SaCode_Dbas.cs | 21 + SacsStandardDbas/Dbas/Data/DbDataItem.cs | 539 +++++ SacsStandardDbas/Dbas/Data/DbDataTable.cs | 28 + .../Dbas/Data/DbResultSet_DataReader.cs | 279 +++ .../Dbas/Data/DbResultSet_DataTableReader.cs | 200 ++ .../Dbas/Data/SacsDatabaseOdbc.cs | 910 ++++++++ .../Dbas/Diary/SaDiaryConsole_Dbas.cs | 39 + .../Internal/SaInterfaceDataItem_Db.cs | 258 +++ .../Interfaces/Internal/SaInterfaceDbDfn.xml | 1893 +++++++++++++++++ .../Internal/SaInterfaceProcessDataItem_Db.cs | 211 ++ .../Internal/SaInterfaceProcessMessage_Db.cs | 238 +++ .../Internal/SaInterfaceProcess_Db.cs | 242 +++ .../Interfaces/Internal/SaInterface_Db.cs | 225 ++ .../Interfaces/SaInterfaceConsole_Dbas.cs | 364 ++++ .../Interfaces/SaInterfaceDataItem_Dbas.cs | 22 + .../Interfaces/SaInterfaceDbDfnGenerator.cs | 122 ++ .../Dbas/Interfaces/SaInterfaceInternal.cs | 13 + .../SaInterfaceMgr_Dbas-InterfaceDataItems.cs | 618 ++++++ ...rfaceMgr_Dbas-InterfaceProcessDataItems.cs | 13 + ...erfaceMgr_Dbas-InterfaceProcessMessages.cs | 484 +++++ .../SaInterfaceMgr_Dbas-InterfaceProcesses.cs | 828 +++++++ .../SaInterfaceMgr_Dbas-Interfaces.cs | 577 +++++ .../Dbas/Interfaces/SaInterfaceMgr_Dbas.cs | 81 + .../SaInterfaceProcessMessage_Dbas.cs | 20 + .../Interfaces/SaInterfaceProcess_Db_Local.cs | 187 ++ .../Interfaces/SaInterfaceProcess_Dbas.cs | 22 + .../Dbas/Interfaces/SaInterface_Dbas.cs | 22 + .../ItemData/Internal/SaItemDataDbDfn.xml | 272 +++ .../Dbas/ItemData/Internal/SaItemData_Db.cs | 219 ++ .../Dbas/ItemData/SaItemDataConsole_Dbas.cs | 130 ++ .../Dbas/ItemData/SaItemDataDbDfnGenerator.cs | 113 + .../Dbas/ItemData/SaItemDataMgr_Dbas.cs | 576 +++++ .../Dbas/ItemData/SaItemData_Dbas.cs | 25 + .../Dbas/ItemDocs/Internal/SaItemDoc_Db.cs | 248 +++ .../ItemDocs/Internal/SaItemDocsDbDfn.xml | 382 ++++ .../Dbas/ItemDocs/SaItemDocConsole_Dbas.cs | 236 ++ .../Dbas/ItemDocs/SaItemDocMgr_Dbas.cs | 620 ++++++ .../Dbas/ItemDocs/SaItemDoc_Dbas.cs | 25 + .../Dbas/ItemDocs/SaItemDocsDbDfnGenerator.cs | 116 + .../Internal/SaItemOptionDbDfn.xml | 362 ++++ .../ItemOptions/Internal/SaItemOption_Db.cs | 225 ++ .../ItemOptions/SaItemOptionConsole_Dbas.cs | 179 ++ .../Dbas/ItemOptions/SaItemOptionInternal.cs | 13 + .../Dbas/ItemOptions/SaItemOptionMgr_Dbas.cs | 565 +++++ .../Dbas/ItemOptions/SaItemOption_Dbas.cs | 21 + .../SaItemOptionsDbDfnGenerator.cs | 112 + .../SaUpdateLogItemMonitor_ItemOption.cs | 59 + .../SaDbProcess_CompareDatabaseSchemas.cs | 408 ++++ .../Proc/SaDbProcess_ConvertDbDefinition.cs | 438 ++++ .../ReferenceData/Internal/Currency_Db.cs | 230 ++ .../ReferenceData/Internal/Language_Db.cs | 207 ++ .../Dbas/ReferenceData/Internal/Region_Db.cs | 212 ++ .../Internal/SaReferenceDataDbDfn.xml | 1773 +++++++++++++++ .../Dbas/ReferenceData/Internal/TaxCode_Db.cs | 255 +++ .../ReferenceData/Internal/UnitMeasure_Db.cs | 273 +++ .../Dbas/ReferenceData/SaCurrency_Dbas.cs | 28 + .../Dbas/ReferenceData/SaLanguage_Dbas.cs | 29 + .../SaReferenceDataConsole_Dbas-Currency.cs | 58 + .../SaReferenceDataConsole_Dbas-Language.cs | 58 + .../SaReferenceDataConsole_Dbas-Region.cs | 58 + .../SaReferenceDataConsole_Dbas-TaxCode.cs | 58 + ...SaReferenceDataConsole_Dbas-UnitMeasure.cs | 58 + .../SaReferenceDataConsole_Dbas.cs | 54 + .../SaReferenceDataDbDfnGenerator.cs | 120 ++ .../ReferenceData/SaReferenceDataInternal.cs | 13 + .../Dbas/ReferenceData/SaRegion_Dbas.cs | 37 + .../Dbas/ReferenceData/SaTaxCode_Dbas.cs | 29 + .../Dbas/ReferenceData/SaUnitMeasure_Dbas.cs | 29 + .../SbmReferenceDataMgr_Dbas-Currency.cs | 524 +++++ .../SbmReferenceDataMgr_Dbas-Language.cs | 522 +++++ .../SbmReferenceDataMgr_Dbas-Region.cs | 495 +++++ .../SbmReferenceDataMgr_Dbas-TaxCode.cs | 550 +++++ .../SbmReferenceDataMgr_Dbas-UnitMeasure.cs | 563 +++++ .../ReferenceData/SbmReferenceDataMgr_Dbas.cs | 45 + .../SaUpdateLogItemMonitor_Currency.cs | 65 + .../SaUpdateLogItemMonitor_Language.cs | 61 + .../SaUpdateLogItemMonitor_ReferenceData.cs | 72 + .../SaUpdateLogItemMonitor_Region.cs | 64 + .../SaUpdateLogItemMonitor_TaxCode.cs | 66 + .../SaUpdateLogItemMonitor_UnitMeasure.cs | 74 + .../Dbas/Security/Internal/SaSecItem_Db.cs | 255 +++ .../Dbas/Security/Internal/SaSecLink_Db.cs | 221 ++ .../Security/Internal/SaSecurityDbDfn.xml | 776 +++++++ .../Dbas/Security/SaSecurityConsole_Dbas.cs | 594 ++++++ .../Dbas/Security/SaSecurityDbDfnGenerator.cs | 116 + .../Dbas/Security/SaSecurityInternal.cs | 13 + .../Dbas/Security/SaSecurityItem_Dbas.cs | 20 + .../Dbas/Security/SaSecurityLink_Dbas.cs | 20 + .../SaSecurityMgr_Dbas-SecurityItem.cs | 638 ++++++ .../SaSecurityMgr_Dbas-SecurityLink.cs | 506 +++++ .../Dbas/Security/SaSecurityMgr_Dbas.cs | 52 + .../SaUpdateLogItemMonitor_SecurityItem.cs | 67 + .../SaUpdateLogItemMonitor_SecurityLink.cs | 63 + .../UpdateLog/Internal/SaAttrUpdate_Db.cs | 231 ++ .../UpdateLog/Internal/SaUpdateLogDbDfn.xml | 359 ++++ .../Dbas/UpdateLog/SaUpdateLogConsole_Dbas.cs | 133 ++ .../UpdateLog/SaUpdateLogDbDfnGenerator.cs | 112 + .../Dbas/UpdateLog/SaUpdateLogEntry_Dbas.cs | 17 + .../Dbas/UpdateLog/SaUpdateLogInternal.cs | 15 + .../Dbas/UpdateLog/SaUpdateLogMgr_Dbas.cs | 649 ++++++ SacsStandardDbas/Dbas/Utils/SystemUtil.cs | 23 + SacsStandardDbas/SacsStandardDbas.csproj | 22 + SacsStandardMob/Mobile/App/SaxApplication.cs | 49 + .../Mobile/Forms/Base/SaxComponents.cs | 53 + .../Mobile/Forms/Controls/SaxButton.cs | 35 + .../Mobile/Forms/Controls/SaxDatePicker.cs | 221 ++ .../Forms/Controls/SaxDateTimePicker.cs | 161 ++ .../Mobile/Forms/Controls/SaxEditor.cs | 77 + .../Mobile/Forms/Controls/SaxEntry.cs | 152 ++ .../Mobile/Forms/Controls/SaxImageButton.cs | 33 + .../Mobile/Forms/Controls/SaxLabel.cs | 37 + .../Mobile/Forms/Controls/SaxPicker.cs | 171 ++ .../Mobile/Forms/Controls/SaxTabControl.cs | 188 ++ .../Mobile/Forms/Controls/SaxTimePicker.cs | 53 + .../Mobile/Forms/Controls/SaxWebView.cs | 99 + .../Mobile/Forms/Layouts/SaxAbsoluteLayout.cs | 29 + .../Mobile/Forms/Layouts/SaxFlexLayout.cs | 29 + .../Mobile/Forms/Layouts/SaxGrid.cs | 65 + .../Mobile/Forms/Layouts/SaxStackLayout.cs | 29 + .../Mobile/Forms/Layouts/SaxWrapLayout.cs | 247 +++ .../Mobile/Forms/Pages/SaxContentPage.cs | 92 + .../Mobile/Forms/Panels/SaxPanel.cs | 82 + .../Forms/Panels/SaxPanel_ItemFilter.cs | 170 ++ .../Panels/SaxPanel_ItemFilterProperty.cs | 171 ++ .../Forms/Panels/SaxPanel_ItemListHeader.cs | 286 +++ .../Forms/Panels/SaxPanel_ItemListSummary.cs | 128 ++ .../Mobile/Forms/Panels/SaxPanel_Messages.cs | 75 + .../Mobile/Forms/Panels/SaxPanel_Progress.cs | 93 + .../Forms/Panels/SaxPanel_PropertySummary.cs | 140 ++ .../Mobile/Forms/Panels/SaxTabPanel.cs | 51 + .../Mobile/Forms/Utils/SaxFormsUtil.cs | 38 + .../Mobile/Forms/Utils/SaxPageUtil.cs | 16 + .../Mobile/Forms/Views/SaxBoxView.cs | 29 + .../Mobile/Forms/Views/SaxContentView.cs | 70 + .../Mobile/Forms/Views/SaxFrame.cs | 29 + SacsStandardMob/Mobile/Forms/Views/SaxView.cs | 29 + SacsStandardMob/Mobile/Tasks/SaxThreadUtil.cs | 47 + SacsStandardMob/Mobile/Utils/ImageUtils.cs | 25 + SacsStandardMob/SacsStandardMob.csproj | 17 + 353 files changed, 60029 insertions(+) create mode 100644 SaCommonVS.sln create mode 100644 SacsCoreUnitTests/SacsCoreUnitTests.csproj create mode 100644 SacsCoreUnitTests/Standard/Base/App/SacsCommandLineInfo_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/App/SacsProcessDefinition_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Bus/SysId_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Data/SacsAesIvCrypter_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Data/SacsCsvHandler_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Data/SacsDataFn_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Data/SacsData_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Generic/ObjectReferences_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Generic/Regex_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Helpers/SacsCsvHelper_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Utils/DateUtil_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Utils/EnumUtil_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Utils/FtpUtil_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Utils/StringListUtil_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Utils/StringUtil_Tester.cs create mode 100644 SacsCoreWeb/SacsCoreWeb.csproj create mode 100644 SacsStandardBase/Base/App/SaLogger.cs create mode 100644 SacsStandardBase/Base/App/SacsApplication.cs create mode 100644 SacsStandardBase/Base/App/SacsCommandLineInfo.cs create mode 100644 SacsStandardBase/Base/App/SacsProcessDefinition.cs create mode 100644 SacsStandardBase/Base/App/SacsSessionConfig.cs create mode 100644 SacsStandardBase/Base/App/SacsSessionContext.cs create mode 100644 SacsStandardBase/Base/App/SacsSystem.cs create mode 100644 SacsStandardBase/Base/App/SacsVersion.cs create mode 100644 SacsStandardBase/Base/Bus/ItemId.cs create mode 100644 SacsStandardBase/Base/Bus/SaErrorStateItem.cs create mode 100644 SacsStandardBase/Base/Bus/SacsHierarchyItem.cs create mode 100644 SacsStandardBase/Base/Bus/SacsItemMgr.cs create mode 100644 SacsStandardBase/Base/Bus/SysId.cs create mode 100644 SacsStandardBase/Base/Bus/SysIdString.cs create mode 100644 SacsStandardBase/Base/CodeTypes/SaCode.cs create mode 100644 SacsStandardBase/Base/CodeTypes/SaCodeDfn.cs create mode 100644 SacsStandardBase/Base/CodeTypes/SaCodeType.cs create mode 100644 SacsStandardBase/Base/CodeTypes/SaCodeTypeConsole.cs create mode 100644 SacsStandardBase/Base/CodeTypes/SaCodeTypeDbDfn.cs create mode 100644 SacsStandardBase/Base/CodeTypes/SaCodeTypeDfn.cs create mode 100644 SacsStandardBase/Base/CodeTypes/SaCodeTypeMgr.cs create mode 100644 SacsStandardBase/Base/Core/CodeDesc.cs create mode 100644 SacsStandardBase/Base/Core/CodeParam.cs create mode 100644 SacsStandardBase/Base/Core/ColumnDfn.cs create mode 100644 SacsStandardBase/Base/Core/DataItem.cs create mode 100644 SacsStandardBase/Base/Core/DataValue.cs create mode 100644 SacsStandardBase/Base/Core/ElapsedTimeLogger.cs create mode 100644 SacsStandardBase/Base/Core/FlagDfn.cs create mode 100644 SacsStandardBase/Base/Core/ISacsMessageConsole.cs create mode 100644 SacsStandardBase/Base/Core/NamedValue.cs create mode 100644 SacsStandardBase/Base/Core/PropertySet.cs create mode 100644 SacsStandardBase/Base/Core/SacsCommand.cs create mode 100644 SacsStandardBase/Base/Core/SacsDfn.cs create mode 100644 SacsStandardBase/Base/Core/SacsDisposable.cs create mode 100644 SacsStandardBase/Base/Core/SacsErrorState.cs create mode 100644 SacsStandardBase/Base/Core/SacsMessage.cs create mode 100644 SacsStandardBase/Base/Core/SacsStateItem.cs create mode 100644 SacsStandardBase/Base/Core/StackLimited.cs create mode 100644 SacsStandardBase/Base/Core/StringPair.cs create mode 100644 SacsStandardBase/Base/Core/TypeCodeDesc.cs create mode 100644 SacsStandardBase/Base/Data/SacsAesIvCrypter.cs create mode 100644 SacsStandardBase/Base/Data/SacsCsvHandler.cs create mode 100644 SacsStandardBase/Base/Data/SacsData.cs create mode 100644 SacsStandardBase/Base/Data/SacsDataFn.cs create mode 100644 SacsStandardBase/Base/Data/SacsShaHasher.cs create mode 100644 SacsStandardBase/Base/Dbas/DbDfnColumn.cs create mode 100644 SacsStandardBase/Base/Dbas/DbDfnDatabase.cs create mode 100644 SacsStandardBase/Base/Dbas/DbDfnIndex.cs create mode 100644 SacsStandardBase/Base/Dbas/DbDfnIndexColumn.cs create mode 100644 SacsStandardBase/Base/Dbas/DbDfnTable.cs create mode 100644 SacsStandardBase/Base/Dbas/DbQuery.cs create mode 100644 SacsStandardBase/Base/Dbas/DbResultSet.cs create mode 100644 SacsStandardBase/Base/Dbas/DbUpdateInfo.cs create mode 100644 SacsStandardBase/Base/Dbas/DbUtil.cs create mode 100644 SacsStandardBase/Base/Dbas/IDbDataItem.cs create mode 100644 SacsStandardBase/Base/Dbas/IDbDataTable.cs create mode 100644 SacsStandardBase/Base/Dbas/IDbResultSet.cs create mode 100644 SacsStandardBase/Base/Dbas/ISacsDatabase.cs create mode 100644 SacsStandardBase/Base/Dbas/OdbcUtil.cs create mode 100644 SacsStandardBase/Base/Diary/SaDiaryConsole.cs create mode 100644 SacsStandardBase/Base/Email/SaEmailAttachmentData.cs create mode 100644 SacsStandardBase/Base/Email/SaEmailConfig.cs create mode 100644 SacsStandardBase/Base/Email/SaEmailConsole.cs create mode 100644 SacsStandardBase/Base/Email/SaEmailData.cs create mode 100644 SacsStandardBase/Base/Helpers/SacsContentTypeHelper.cs create mode 100644 SacsStandardBase/Base/Helpers/SacsCsvHelper.cs create mode 100644 SacsStandardBase/Base/Helpers/SacsDataValidationHelper.cs create mode 100644 SacsStandardBase/Base/Helpers/SacsEncryptionHelper.cs create mode 100644 SacsStandardBase/Base/Helpers/SacsHttpClientHelper.cs create mode 100644 SacsStandardBase/Base/Helpers/SacsJsonHelper.cs create mode 100644 SacsStandardBase/Base/Helpers/SacsRandomHelper.cs create mode 100644 SacsStandardBase/Base/Helpers/SacsResourceHelper.cs create mode 100644 SacsStandardBase/Base/IO/DirectoryUtil.cs create mode 100644 SacsStandardBase/Base/IO/FileUtil.cs create mode 100644 SacsStandardBase/Base/Interfaces/SaInterface.cs create mode 100644 SacsStandardBase/Base/Interfaces/SaInterfaceConsole.cs create mode 100644 SacsStandardBase/Base/Interfaces/SaInterfaceDataItem.cs create mode 100644 SacsStandardBase/Base/Interfaces/SaInterfaceDbDfn.cs create mode 100644 SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceDataItems.cs create mode 100644 SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceProcessDataItems.cs create mode 100644 SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceProcessMessages.cs create mode 100644 SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceProcesses.cs create mode 100644 SacsStandardBase/Base/Interfaces/SaInterfaceMgr-Interfaces.cs create mode 100644 SacsStandardBase/Base/Interfaces/SaInterfaceMgr.cs create mode 100644 SacsStandardBase/Base/Interfaces/SaInterfaceProcess.cs create mode 100644 SacsStandardBase/Base/Interfaces/SaInterfaceProcessDataItem.cs create mode 100644 SacsStandardBase/Base/Interfaces/SaInterfaceProcessMessage.cs create mode 100644 SacsStandardBase/Base/ItemData/SaItemData.cs create mode 100644 SacsStandardBase/Base/ItemData/SaItemDataConsole.cs create mode 100644 SacsStandardBase/Base/ItemData/SaItemDataDbDfn.cs create mode 100644 SacsStandardBase/Base/ItemData/SaItemDataMgr.cs create mode 100644 SacsStandardBase/Base/ItemDocs/SaItemDoc.cs create mode 100644 SacsStandardBase/Base/ItemDocs/SaItemDocConsole.cs create mode 100644 SacsStandardBase/Base/ItemDocs/SaItemDocMgr.cs create mode 100644 SacsStandardBase/Base/ItemDocs/SaItemDocsDbDfn.cs create mode 100644 SacsStandardBase/Base/ItemOptions/SaItemOption.cs create mode 100644 SacsStandardBase/Base/ItemOptions/SaItemOptionConsole.cs create mode 100644 SacsStandardBase/Base/ItemOptions/SaItemOptionDbDfn.cs create mode 100644 SacsStandardBase/Base/ItemOptions/SaItemOptionDfn.cs create mode 100644 SacsStandardBase/Base/ItemOptions/SaItemOptionMgr.cs create mode 100644 SacsStandardBase/Base/Procs/SacsProcess.cs create mode 100644 SacsStandardBase/Base/Procs/SacsReport.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaCurrency.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaLanguage.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-Currency.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-Language.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-Region.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-TaxCode.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-UnitMeasure.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaReferenceDataDbDfn.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_Currency.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_Language.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_Region.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_TaxCode.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_UnitMeasure.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaRegion.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaTaxCode.cs create mode 100644 SacsStandardBase/Base/ReferenceData/SaUnitMeasure.cs create mode 100644 SacsStandardBase/Base/Security/SaSecurityConsole.cs create mode 100644 SacsStandardBase/Base/Security/SaSecurityDbDfn.cs create mode 100644 SacsStandardBase/Base/Security/SaSecurityItem.cs create mode 100644 SacsStandardBase/Base/Security/SaSecurityLink.cs create mode 100644 SacsStandardBase/Base/Security/SaSecurityMgr-SecurityItem.cs create mode 100644 SacsStandardBase/Base/Security/SaSecurityMgr-SecurityLink.cs create mode 100644 SacsStandardBase/Base/Security/SaSecurityMgr.cs create mode 100644 SacsStandardBase/Base/Security/SaSecurityProfile.cs create mode 100644 SacsStandardBase/Base/Security/SaSecurityRight.cs create mode 100644 SacsStandardBase/Base/Tasks/SacsTaskData.cs create mode 100644 SacsStandardBase/Base/UpdateLog/SaUpdateLogConsole.cs create mode 100644 SacsStandardBase/Base/UpdateLog/SaUpdateLogDbDfn.cs create mode 100644 SacsStandardBase/Base/UpdateLog/SaUpdateLogEntry.cs create mode 100644 SacsStandardBase/Base/UpdateLog/SaUpdateLogItemMonitor.cs create mode 100644 SacsStandardBase/Base/UpdateLog/SaUpdateLogMgr.cs create mode 100644 SacsStandardBase/Base/Utils/ArrayUtil.cs create mode 100644 SacsStandardBase/Base/Utils/AssemblyUtil.cs create mode 100644 SacsStandardBase/Base/Utils/DateUtil.cs create mode 100644 SacsStandardBase/Base/Utils/DictionaryUtil.cs create mode 100644 SacsStandardBase/Base/Utils/EnumUtil.cs create mode 100644 SacsStandardBase/Base/Utils/EnumerableUtil.cs create mode 100644 SacsStandardBase/Base/Utils/ExceptionUtil.cs create mode 100644 SacsStandardBase/Base/Utils/FtpUtil.cs create mode 100644 SacsStandardBase/Base/Utils/ListUtil.cs create mode 100644 SacsStandardBase/Base/Utils/LoggingUtils.cs create mode 100644 SacsStandardBase/Base/Utils/MathUtil.cs create mode 100644 SacsStandardBase/Base/Utils/SerializableUtil.cs create mode 100644 SacsStandardBase/Base/Utils/StringListUtil.cs create mode 100644 SacsStandardBase/Base/Utils/StringUtil.cs create mode 100644 SacsStandardBase/Base/Utils/SysUtil.cs create mode 100644 SacsStandardBase/Base/Utils/WebUtil.cs create mode 100644 SacsStandardBase/Base/Utils/XmlUtil.cs create mode 100644 SacsStandardBase/Base/Xml/SacsSoapUtil.cs create mode 100644 SacsStandardBase/Base/Xml/SacsXmlUtil.cs create mode 100644 SacsStandardBase/Resources/html/sa-base.css create mode 100644 SacsStandardBase/Resources/html/sa-base.js create mode 100644 SacsStandardBase/Resources/images/IconShock/add_green_72.png create mode 100644 SacsStandardBase/Resources/images/IconShock/arrow_down_purple_72.png create mode 100644 SacsStandardBase/Resources/images/IconShock/arrow_right_purple_72.png create mode 100644 SacsStandardBase/Resources/images/IconShock/checkmark_green_72.png create mode 100644 SacsStandardBase/Resources/images/IconShock/close_red_72.png create mode 100644 SacsStandardBase/Resources/images/IconShock/navicon_darkgrey_72.png create mode 100644 SacsStandardBase/Resources/images/IconShock/pencil_orange_72.png create mode 100644 SacsStandardBase/Resources/images/IconShock/trashcan_grey_72.png create mode 100644 SacsStandardBase/Resources/images/icons/arrows/arrow_left_black_16.png create mode 100644 SacsStandardBase/Resources/images/icons/arrows/arrow_left_black_24.png create mode 100644 SacsStandardBase/Resources/images/icons/arrows/arrow_left_double_black_16.png create mode 100644 SacsStandardBase/Resources/images/icons/arrows/arrow_left_double_black_24.png create mode 100644 SacsStandardBase/Resources/images/icons/arrows/arrow_right_black_16.png create mode 100644 SacsStandardBase/Resources/images/icons/arrows/arrow_right_black_24.png create mode 100644 SacsStandardBase/Resources/images/icons/arrows/arrow_right_double_black_16.png create mode 100644 SacsStandardBase/Resources/images/icons/arrows/arrow_right_double_black_24.png create mode 100644 SacsStandardBase/Resources/images/icons/burgers/burger_black_128.png create mode 100644 SacsStandardBase/Resources/images/icons/burgers/burger_black_220.png create mode 100644 SacsStandardBase/Resources/images/icons/burgers/burger_black_32.png create mode 100644 SacsStandardBase/Resources/images/icons/burgers/burger_black_48.png create mode 100644 SacsStandardBase/Resources/images/icons/burgers/burger_black_64.png create mode 100644 SacsStandardBase/Resources/images/icons/burgers/burger_black_96.png create mode 100644 SacsStandardBase/Resources/jquery/jquery-1.12.1.min.js create mode 100644 SacsStandardBase/Resources/jquery/jquery-ui-1.12.1.min.css create mode 100644 SacsStandardBase/Resources/jquery/jquery-ui-1.12.1.min.js create mode 100644 SacsStandardBase/SacsStandardBase.csproj create mode 100644 SacsStandardDbas/Dbas/Base/DatabaseDefinitionGenerator.cs create mode 100644 SacsStandardDbas/Dbas/Base/SaDbDfnConnection.cs create mode 100644 SacsStandardDbas/Dbas/Base/SaDbDfnOdbcDataSource.cs create mode 100644 SacsStandardDbas/Dbas/Base/SaDbDfnOdbcDriver.cs create mode 100644 SacsStandardDbas/Dbas/Base/SacsDatabase.cs create mode 100644 SacsStandardDbas/Dbas/Base/SacsDatabaseConsole.cs create mode 100644 SacsStandardDbas/Dbas/CodeTypes/Internal/SaCodeTypeDbDfn.xml create mode 100644 SacsStandardDbas/Dbas/CodeTypes/Internal/SaCodeType_Db.cs create mode 100644 SacsStandardDbas/Dbas/CodeTypes/Internal/SaCode_Db.cs create mode 100644 SacsStandardDbas/Dbas/CodeTypes/SaCodeTypeConsole_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/CodeTypes/SaCodeTypeDbDfnGenerator.cs create mode 100644 SacsStandardDbas/Dbas/CodeTypes/SaCodeTypeInternal.cs create mode 100644 SacsStandardDbas/Dbas/CodeTypes/SaCodeTypeMgr_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/CodeTypes/SaCodeType_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/CodeTypes/SaCode_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/Data/DbDataItem.cs create mode 100644 SacsStandardDbas/Dbas/Data/DbDataTable.cs create mode 100644 SacsStandardDbas/Dbas/Data/DbResultSet_DataReader.cs create mode 100644 SacsStandardDbas/Dbas/Data/DbResultSet_DataTableReader.cs create mode 100644 SacsStandardDbas/Dbas/Data/SacsDatabaseOdbc.cs create mode 100644 SacsStandardDbas/Dbas/Diary/SaDiaryConsole_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceDataItem_Db.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceDbDfn.xml create mode 100644 SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcessDataItem_Db.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcessMessage_Db.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcess_Db.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/Internal/SaInterface_Db.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterfaceConsole_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterfaceDataItem_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterfaceDbDfnGenerator.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterfaceInternal.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterfaceMgr_Dbas-InterfaceDataItems.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterfaceMgr_Dbas-InterfaceProcessDataItems.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterfaceMgr_Dbas-InterfaceProcessMessages.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterfaceMgr_Dbas-InterfaceProcesses.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterfaceMgr_Dbas-Interfaces.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterfaceMgr_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterfaceProcessMessage_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterfaceProcess_Db_Local.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterfaceProcess_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/Interfaces/SaInterface_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ItemData/Internal/SaItemDataDbDfn.xml create mode 100644 SacsStandardDbas/Dbas/ItemData/Internal/SaItemData_Db.cs create mode 100644 SacsStandardDbas/Dbas/ItemData/SaItemDataConsole_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ItemData/SaItemDataDbDfnGenerator.cs create mode 100644 SacsStandardDbas/Dbas/ItemData/SaItemDataMgr_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ItemData/SaItemData_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ItemDocs/Internal/SaItemDoc_Db.cs create mode 100644 SacsStandardDbas/Dbas/ItemDocs/Internal/SaItemDocsDbDfn.xml create mode 100644 SacsStandardDbas/Dbas/ItemDocs/SaItemDocConsole_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ItemDocs/SaItemDocMgr_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ItemDocs/SaItemDoc_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ItemDocs/SaItemDocsDbDfnGenerator.cs create mode 100644 SacsStandardDbas/Dbas/ItemOptions/Internal/SaItemOptionDbDfn.xml create mode 100644 SacsStandardDbas/Dbas/ItemOptions/Internal/SaItemOption_Db.cs create mode 100644 SacsStandardDbas/Dbas/ItemOptions/SaItemOptionConsole_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ItemOptions/SaItemOptionInternal.cs create mode 100644 SacsStandardDbas/Dbas/ItemOptions/SaItemOptionMgr_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ItemOptions/SaItemOption_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ItemOptions/SaItemOptionsDbDfnGenerator.cs create mode 100644 SacsStandardDbas/Dbas/ItemOptions/SaUpdateLogItemMonitor_ItemOption.cs create mode 100644 SacsStandardDbas/Dbas/Proc/SaDbProcess_CompareDatabaseSchemas.cs create mode 100644 SacsStandardDbas/Dbas/Proc/SaDbProcess_ConvertDbDefinition.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/Internal/Currency_Db.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/Internal/Language_Db.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/Internal/Region_Db.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/Internal/SaReferenceDataDbDfn.xml create mode 100644 SacsStandardDbas/Dbas/ReferenceData/Internal/TaxCode_Db.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/Internal/UnitMeasure_Db.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SaCurrency_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SaLanguage_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SaReferenceDataConsole_Dbas-Currency.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SaReferenceDataConsole_Dbas-Language.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SaReferenceDataConsole_Dbas-Region.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SaReferenceDataConsole_Dbas-TaxCode.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SaReferenceDataConsole_Dbas-UnitMeasure.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SaReferenceDataConsole_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SaReferenceDataDbDfnGenerator.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SaReferenceDataInternal.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SaRegion_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SaTaxCode_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SaUnitMeasure_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SbmReferenceDataMgr_Dbas-Currency.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SbmReferenceDataMgr_Dbas-Language.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SbmReferenceDataMgr_Dbas-Region.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SbmReferenceDataMgr_Dbas-TaxCode.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SbmReferenceDataMgr_Dbas-UnitMeasure.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/SbmReferenceDataMgr_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/UpdateLog/SaUpdateLogItemMonitor_Currency.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/UpdateLog/SaUpdateLogItemMonitor_Language.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/UpdateLog/SaUpdateLogItemMonitor_ReferenceData.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/UpdateLog/SaUpdateLogItemMonitor_Region.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/UpdateLog/SaUpdateLogItemMonitor_TaxCode.cs create mode 100644 SacsStandardDbas/Dbas/ReferenceData/UpdateLog/SaUpdateLogItemMonitor_UnitMeasure.cs create mode 100644 SacsStandardDbas/Dbas/Security/Internal/SaSecItem_Db.cs create mode 100644 SacsStandardDbas/Dbas/Security/Internal/SaSecLink_Db.cs create mode 100644 SacsStandardDbas/Dbas/Security/Internal/SaSecurityDbDfn.xml create mode 100644 SacsStandardDbas/Dbas/Security/SaSecurityConsole_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/Security/SaSecurityDbDfnGenerator.cs create mode 100644 SacsStandardDbas/Dbas/Security/SaSecurityInternal.cs create mode 100644 SacsStandardDbas/Dbas/Security/SaSecurityItem_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/Security/SaSecurityLink_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/Security/SaSecurityMgr_Dbas-SecurityItem.cs create mode 100644 SacsStandardDbas/Dbas/Security/SaSecurityMgr_Dbas-SecurityLink.cs create mode 100644 SacsStandardDbas/Dbas/Security/SaSecurityMgr_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/Security/SaUpdateLogItemMonitor_SecurityItem.cs create mode 100644 SacsStandardDbas/Dbas/Security/SaUpdateLogItemMonitor_SecurityLink.cs create mode 100644 SacsStandardDbas/Dbas/UpdateLog/Internal/SaAttrUpdate_Db.cs create mode 100644 SacsStandardDbas/Dbas/UpdateLog/Internal/SaUpdateLogDbDfn.xml create mode 100644 SacsStandardDbas/Dbas/UpdateLog/SaUpdateLogConsole_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/UpdateLog/SaUpdateLogDbDfnGenerator.cs create mode 100644 SacsStandardDbas/Dbas/UpdateLog/SaUpdateLogEntry_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/UpdateLog/SaUpdateLogInternal.cs create mode 100644 SacsStandardDbas/Dbas/UpdateLog/SaUpdateLogMgr_Dbas.cs create mode 100644 SacsStandardDbas/Dbas/Utils/SystemUtil.cs create mode 100644 SacsStandardDbas/SacsStandardDbas.csproj create mode 100644 SacsStandardMob/Mobile/App/SaxApplication.cs create mode 100644 SacsStandardMob/Mobile/Forms/Base/SaxComponents.cs create mode 100644 SacsStandardMob/Mobile/Forms/Controls/SaxButton.cs create mode 100644 SacsStandardMob/Mobile/Forms/Controls/SaxDatePicker.cs create mode 100644 SacsStandardMob/Mobile/Forms/Controls/SaxDateTimePicker.cs create mode 100644 SacsStandardMob/Mobile/Forms/Controls/SaxEditor.cs create mode 100644 SacsStandardMob/Mobile/Forms/Controls/SaxEntry.cs create mode 100644 SacsStandardMob/Mobile/Forms/Controls/SaxImageButton.cs create mode 100644 SacsStandardMob/Mobile/Forms/Controls/SaxLabel.cs create mode 100644 SacsStandardMob/Mobile/Forms/Controls/SaxPicker.cs create mode 100644 SacsStandardMob/Mobile/Forms/Controls/SaxTabControl.cs create mode 100644 SacsStandardMob/Mobile/Forms/Controls/SaxTimePicker.cs create mode 100644 SacsStandardMob/Mobile/Forms/Controls/SaxWebView.cs create mode 100644 SacsStandardMob/Mobile/Forms/Layouts/SaxAbsoluteLayout.cs create mode 100644 SacsStandardMob/Mobile/Forms/Layouts/SaxFlexLayout.cs create mode 100644 SacsStandardMob/Mobile/Forms/Layouts/SaxGrid.cs create mode 100644 SacsStandardMob/Mobile/Forms/Layouts/SaxStackLayout.cs create mode 100644 SacsStandardMob/Mobile/Forms/Layouts/SaxWrapLayout.cs create mode 100644 SacsStandardMob/Mobile/Forms/Pages/SaxContentPage.cs create mode 100644 SacsStandardMob/Mobile/Forms/Panels/SaxPanel.cs create mode 100644 SacsStandardMob/Mobile/Forms/Panels/SaxPanel_ItemFilter.cs create mode 100644 SacsStandardMob/Mobile/Forms/Panels/SaxPanel_ItemFilterProperty.cs create mode 100644 SacsStandardMob/Mobile/Forms/Panels/SaxPanel_ItemListHeader.cs create mode 100644 SacsStandardMob/Mobile/Forms/Panels/SaxPanel_ItemListSummary.cs create mode 100644 SacsStandardMob/Mobile/Forms/Panels/SaxPanel_Messages.cs create mode 100644 SacsStandardMob/Mobile/Forms/Panels/SaxPanel_Progress.cs create mode 100644 SacsStandardMob/Mobile/Forms/Panels/SaxPanel_PropertySummary.cs create mode 100644 SacsStandardMob/Mobile/Forms/Panels/SaxTabPanel.cs create mode 100644 SacsStandardMob/Mobile/Forms/Utils/SaxFormsUtil.cs create mode 100644 SacsStandardMob/Mobile/Forms/Utils/SaxPageUtil.cs create mode 100644 SacsStandardMob/Mobile/Forms/Views/SaxBoxView.cs create mode 100644 SacsStandardMob/Mobile/Forms/Views/SaxContentView.cs create mode 100644 SacsStandardMob/Mobile/Forms/Views/SaxFrame.cs create mode 100644 SacsStandardMob/Mobile/Forms/Views/SaxView.cs create mode 100644 SacsStandardMob/Mobile/Tasks/SaxThreadUtil.cs create mode 100644 SacsStandardMob/Mobile/Utils/ImageUtils.cs create mode 100644 SacsStandardMob/SacsStandardMob.csproj 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 ToObjectList (this List values) + { + List cellValues = new List(); + foreach (DataValue value in values) + { + if (value.GetDataType() == DataValue.DataType.Bool) + cellValues.Add(value.AsBool()); + else if (value.GetDataType() == DataValue.DataType.Long) + cellValues.Add(value.AsLong()); + else if (value.GetDataType() == DataValue.DataType.Double) + cellValues.Add(value.AsDouble()); + else if (value.GetDataType() == DataValue.DataType.Decimal) + cellValues.Add(value.AsDecimal()); + else if (value.IsDateTime()) + cellValues.Add(value.AsDateTime()); + else + // Default - String value. + cellValues.Add(value.ToString()); + } + return cellValues; + } + + //------------------------------------------------------------------- + // Numeric - only with compatible numeric values. + /// Minimum numeric value in the list. + public static DataValue GetMinValue (this List values) + { + DataValue minValue = null; + foreach (var dataValue in values) + { + if (dataValue.IsNumeric()) + { + if (minValue == null) + minValue = new DataValue(dataValue.ToNumeric()); + else if (dataValue.ToNumeric() < minValue.ToNumeric()) + minValue = new DataValue(dataValue.ToNumeric()); + } + } + return minValue; + } + /// Maximum numeric value in the list. + public static DataValue GetMaxValue (this List values) + { + DataValue maxValue = null; + foreach (var dataValue in values) + { + if (dataValue.IsNumeric()) + { + if (maxValue == null) + maxValue = new DataValue(dataValue.ToNumeric()); + else if (dataValue.ToNumeric() > maxValue.ToNumeric()) + maxValue = new DataValue(dataValue.ToNumeric()); + } + } + return maxValue; + } + + //------------------------------------------------------------------- + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Core/ElapsedTimeLogger.cs b/SacsStandardBase/Base/Core/ElapsedTimeLogger.cs new file mode 100644 index 0000000..2e70aa2 --- /dev/null +++ b/SacsStandardBase/Base/Core/ElapsedTimeLogger.cs @@ -0,0 +1,59 @@ +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.Core +{ + //======================================================================= + public class ElapsedTimeLogger + { + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Construction. + public ElapsedTimeLogger (String logPrefix, String logSuffix = "") + { + LogPrefix = logPrefix; + LogSuffix = logSuffix; + StartedAt = DateTime.Now; + } + + //------------------------------------------------------------------- + // Properties. + public String LogPrefix { get; } + public String LogSuffix { get; } + public DateTime StartedAt { get; private set; } + + //------------------------------------------------------------------- + // Utilities. + public void Restart () + { + StartedAt = DateTime.Now; + } + + //------------------------------------------------------------------- + // Logging. + public void LogElapsedSecs (bool restart = false) + { + LogElapsedSecs("", restart); + } + public void LogElapsedSecs (String logSuffix, bool restart = false) + { + LogElapsedSecs("", logSuffix, restart); + } + public void LogElapsedSecs (String logPrefix, String logSuffix, bool restart = false) + { + logPrefix = StringUtil.OnEmpty(logPrefix, LogPrefix); + logSuffix = StringUtil.OnEmpty(logSuffix, LogSuffix); + TimeSpan elapsed = DateTime.Now - StartedAt; + SaLogger.Info(StringUtil.TrimAll(logPrefix + " Elapsed: " + elapsed.TotalSeconds.ToString("0.000") + " secs. " + logSuffix)); + if (restart) Restart(); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Core/FlagDfn.cs b/SacsStandardBase/Base/Core/FlagDfn.cs new file mode 100644 index 0000000..0a6d871 --- /dev/null +++ b/SacsStandardBase/Base/Core/FlagDfn.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Core +{ + //======================================================================= + public class FlagDfn + { + //--------------------------------------------------------------- + // Construction. + public FlagDfn (Byte flagPos, String caption, String columnHeading) + { + FlagPos = flagPos; + Caption = caption; + ColumnHeading = columnHeading; + } + + //--------------------------------------------------------------- + // Properties. + public Byte FlagPos { get; private set; } = 0; + public String Caption { get; private set; } = ""; + public String ColumnHeading { get; private set; } = ""; + + //--------------------------------------------------------------- + } + //======================================================================= + public static class FlagDfnList + { + public static void Add (this List values, Byte flagPos, String caption, String columnHeading) + { + values.Add(new FlagDfn(flagPos, caption, columnHeading)); + } + public static FlagDfn GetFlagByPos (this List values, ushort flagPos) + { + foreach (FlagDfn flagDfn in values) + if (flagDfn.FlagPos == flagPos) + return flagDfn; + return null; + } + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Core/ISacsMessageConsole.cs b/SacsStandardBase/Base/Core/ISacsMessageConsole.cs new file mode 100644 index 0000000..d0a2363 --- /dev/null +++ b/SacsStandardBase/Base/Core/ISacsMessageConsole.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.Core +{ + //======================================================================= + public interface ISacsMessageConsole + { + //------------------------------------------------------------------- + // Message Boxes. + /// Information message. + void ShowMessageInfo (String message, String title = ""); + /// Warning message. Always returns false. + bool ShowMessageWarning (String message, String title = ""); + /// Error message. Always returns false. + bool ShowMessageError (String message, String title = ""); + /// Yes/No confirm message. Returns true if 'Yes' clicked. + bool ShowMessageConfirm (String message, String title = ""); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Core/NamedValue.cs b/SacsStandardBase/Base/Core/NamedValue.cs new file mode 100644 index 0000000..69e111f --- /dev/null +++ b/SacsStandardBase/Base/Core/NamedValue.cs @@ -0,0 +1,264 @@ +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Sarand.Net.Standard.Base.Core +{ + //======================================================================= + public class NamedValue + { + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Construction. + private NamedValue (String name, DataValue value) + { + Name = StringUtil.EmptyOnNull(name); + Value = value ?? new DataValue(); + } + + //------------------------------------------------------------------- + // Properties. + public String Name { get; private set; } + public DataValue Value { get; private set; } + + //------------------------------------------------------------------- + /// Associative array of named values, case insensitive. + public class Map + { + //--------------------------------------------------------------- + // Data members. + private readonly Dictionary values = new Dictionary(StringComparer.OrdinalIgnoreCase); + + //--------------------------------------------------------------- + // Construction. + public Map (Dictionary values = null) + { + if (values != null) + this.values.AddRange(values); + } + public Map (NamedValue.Map values) + { + if (values != null) + this.values.AddRange(values.values); + } + + //--------------------------------------------------------------- + // Properties. + /// Optional identifier for the set of values. + public String Id { get; set; } = String.Empty; + /// Retrieve a copy of the value list. + public Dictionary Values { get { return new Dictionary(values); } } + /// Number of values in collection. + public int Count { get { return values.Count; } } + + //--------------------------------------------------------------- + // Update list. + public void Clear () + { + values.Clear(); + } + public bool AddValue (String name, DataValue value) + { + if (String.IsNullOrEmpty(name) || value == null) + { + if (SacsSystem.DebugMode) SaLogger.Debug(GetType().Name + ".AddValue Invalid named value. Name: " + StringUtil.EmptyOnNull(name) + ". Value: " + (value != null ? value.GetDataType().ToString() : "null")); + return false; + } + values[name] = value; + return true; + } + public bool AddValues (NamedValue.Map values) + { + return AddValues(values.ToNamedValueList()); + } + public bool AddValues (ICollection values) + { + bool addValues = true; + foreach (NamedValue value in values) + { + if (!AddValue(value.Name, value.Value)) + addValues = false; + } + return addValues; + } + /// Remove the named value from the list if present. + public bool RemoveValue (String name) + { + if (!HasValue(name)) return false; + return values.Remove(name); + } + /// Change the name of a value in the list. + public bool RenameValue (String nameOld, String nameNew) + { + if (StringUtil.IsEmpty(nameOld) || !HasValue(nameOld) || StringUtil.IsEmpty(nameNew) || nameOld.EqualsCI(nameNew)) return false; + DataValue value = GetValue(nameOld); + RemoveValue(nameOld); + return AddValue(nameNew, value); + } + + //--------------------------------------------------------------- + /// Does the list include a value with the specified name. + public bool HasValue (String name) + { + return values.HasValue(name); + } + + //--------------------------------------------------------------- + /// Retrieve the value with the specified name. + public DataValue GetValue (String name) + { + return values.GetValue(name); + } + /// Retrieve the named value as a String. + public String GetString (String name, String defaultValue = "") + { + return values.GetString(name, defaultValue); + } + /// Retrieve the named value as a boolean. + public bool GetBool (String name, bool defaultValue = false) + { + return values.GetBool(name, defaultValue); + } + /// Retrieve the named value as a long integer. + public long GetLong (String name, long defaultValue = 0) + { + return values.GetLong(name, defaultValue); + } + /// Retrieve the named value as a double. + public double GetDouble (String name, double defaultValue = 0) + { + return values.GetDouble(name, defaultValue); + } + /// Retrieve the named value as a decimal. + public decimal GetDecimal (String name, decimal defaultValue = 0) + { + return values.GetDecimal(name, defaultValue); + } + /// Retrieve the named value as a DateTime. + public DateTime GetDateTime (String name) + { + return values.GetDateTime(name); + } + /// Retrieve the named value as a date. + public DateTime GetDate (String name) + { + return values.GetDate(name); + } + /// Retrieve the named value as a time. + public DateTime GetTime (String name) + { + return values.GetTime(name); + } + + //--------------------------------------------------------------- + // Conversion. + //--------------------------------------------------------------- + public List ToNamedValueList () + { + List values = new List(); + foreach (String name in this.values.Keys) + values.Add(new NamedValue(name, this.values.GetValue(name))); + return values; + } + + /// Concatenate the values with the specified delimiters. + public String Concat (String entryDelim = ".", String valueDelim = "=") + { + return values.Concat(entryDelim, valueDelim); + } + + //--------------------------------------------------------------- + } + + //------------------------------------------------------------------- + } + + //======================================================================= + public static class NamedValueDictionary + { + //------------------------------------------------------------------- + /// Does the list include a value with the specified name, case insensitive. + public static bool HasValue (this Dictionary values, String name) + { + if (String.IsNullOrEmpty(name)) return false; + return values.ContainsKey(name); + } + + //------------------------------------------------------------------- + /// Retrieve the value with the specified name. + public static DataValue GetValue (this Dictionary values, String name) + { + if (StringUtil.IsEmpty(name)) return DataValue.NullValue; + if (values.ContainsKey(name)) + return values[name]; + return DataValue.NullValue; + } + /// Retrieve the named value as a String. + public static String GetString (this Dictionary values, String name, String defaultValue = "") + { + if (values.HasValue(name)) return values.GetValue(name).ToString(); + return defaultValue; + } + /// Retrieve the named value as a boolean. + public static bool GetBool (this Dictionary values, String name, bool defaultValue = false) + { + if (values.HasValue(name)) return values.GetValue(name).AsBool(); + return defaultValue; + } + /// Retrieve the named value as a long integer. + public static long GetLong (this Dictionary values, String name, long defaultValue = 0) + { + if (values.HasValue(name)) return values.GetValue(name).AsLong(); + return defaultValue; + } + /// Retrieve the named value as a double. + public static double GetDouble (this Dictionary values, String name, double defaultValue = 0) + { + if (values.HasValue(name)) return values.GetValue(name).AsDouble(); + return defaultValue; + } + /// Retrieve the named value as a decimal. + public static decimal GetDecimal (this Dictionary values, String name, decimal defaultValue = 0) + { + if (values.HasValue(name)) return values.GetValue(name).AsDecimal(); + return defaultValue; + } + /// Retrieve the named value as a DateTime. + public static DateTime GetDateTime (this Dictionary values, String name) + { + if (values.HasValue(name)) return values.GetValue(name).AsDateTime(); + return DateTime.MinValue; + } + /// Retrieve the named value as a date. + public static DateTime GetDate (this Dictionary values, String name) + { + if (values.HasValue(name)) return values.GetValue(name).AsDate(); + return DateTime.MinValue; + } + /// Retrieve the named value as a time. + public static DateTime GetTime (this Dictionary values, String name) + { + if (values.HasValue(name)) return values.GetValue(name).AsTime(); + return DateTime.MinValue; + } + + //------------------------------------------------------------------- + // Conversion. + /// Concatenate the values with the specified delimiters. + public static String Concat (this Dictionary values, String entryDelim = ".", String valueDelim = "=") + { + List dataValues = new List(); + foreach (String name in values.Keys) + dataValues.Add(name + valueDelim + values[name]); + return dataValues.Concat(entryDelim); + } + + //------------------------------------------------------------------- + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Core/PropertySet.cs b/SacsStandardBase/Base/Core/PropertySet.cs new file mode 100644 index 0000000..f9ed239 --- /dev/null +++ b/SacsStandardBase/Base/Core/PropertySet.cs @@ -0,0 +1,133 @@ +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.IO; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Sarand.Net.Standard.Base.Core +{ + //======================================================================= + public class Property + { + //------------------------------------------------------------------- + // Data members. + private String section = "", name = "", value = ""; + //------------------------------------------------------------------- + // Construction. + public Property (String section, String name, String value) + { + this.section = section; + this.name = name; + this.value = value; + } + //------------------------------------------------------------------- + // Comparison. + public bool Matches (String section, String name) + { + return (StringUtil.EqualsCI(this.section, section) && StringUtil.EqualsCI(this.name, name)); + } + //------------------------------------------------------------------- + // Attributes. + public String GetSection () + { + return section; + } + public String GetName () + { + return name; + } + public String GetValue () + { + return value; + } + //------------------------------------------------------------------- + } + //======================================================================= + public class PropertySet + { + //------------------------------------------------------------------- + // Data members. + private List properties = new List(); + //------------------------------------------------------------------- + // Construction. + public PropertySet () + { + } + //------------------------------------------------------------------- + /// + /// Add property to set. + /// + public void AddProperty (String section, String name, String value) + { + properties.Add(new Property(section, name, value)); + } + //------------------------------------------------------------------- + /// + /// Retrieve value from property set. + /// + public String GetPropertyValue (String section, String name) + { + foreach (var property in properties) + { + if (property.Matches(section, name)) + return property.GetValue(); + } + return ""; + } + //------------------------------------------------------------------- + // Retrieve peoperties from file. + public static PropertySet GetPropertiesFromFile (String filepath) + { + if (StringUtil.IsEmpty(filepath)) + { + SaLogger.Warn("PropertySet.GetPropertiesFromFile Blank file path specified"); + return null; + } + if (!FileUtil.IsFile(filepath)) + { + SaLogger.Warn("PropertySet.GetPropertiesFromFile Invalid file specified: '" + filepath + "'"); + return null; + } + PropertySet propertySet = new PropertySet(); + String section = ""; + foreach (var row in FileUtil.ReadAllFileLines(filepath)) + { + String data = row.Trim(); + // Blank lines: + if (String.IsNullOrEmpty(data)) + continue; + // Check for comment: + else if (data.Left(1) == ";" || data.Left(2) == "//") + continue; + // Check for [Section]: + else if (data[0] == '[') + { + section = data.Substring(1).Split(']')[0]; + } + else + { + // Otherwise it is a named value: + propertySet.AddProperty(section, data.Split('=')[0], string.Join("=", data.Split('=').Skip(1).ToArray())); + } + } + + return propertySet; + } + //------------------------------------------------------------------- + // Utilities. + public void Dump () + { + System.Diagnostics.Debug.WriteLine("PropertySet dump:"); + foreach (var property in properties) + { + string info = property.GetSection(); + if (!String.IsNullOrEmpty(info)) info += "."; + info += property.GetName() + "=" + property.GetValue(); + System.Diagnostics.Debug.WriteLine("\t" + info); + } + } + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Core/SacsCommand.cs b/SacsStandardBase/Base/Core/SacsCommand.cs new file mode 100644 index 0000000..454cd19 --- /dev/null +++ b/SacsStandardBase/Base/Core/SacsCommand.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Input; + +namespace Sarand.Net.Standard.Base.Core +{ + //======================================================================= + /// + /// Code contract for commands - menu options, button actions, keyboard accelerators etc. + /// + /// + public class SacsCommand : ICommand + { + //------------------------------------------------------------------- + // Definitions. + public delegate bool CanExecuteFn (object commandParam); + public delegate void ExecuteFn (object commandParam); + + //------------------------------------------------------------------- + // Data members. + private readonly ExecuteFn executeFn; + private readonly CanExecuteFn canExecuteFn; + + //------------------------------------------------------------------- + // Construction. + public SacsCommand (ExecuteFn executeFn, CanExecuteFn canExecuteFn = null) + { + this.executeFn = executeFn; + this.canExecuteFn = canExecuteFn; + } + + //------------------------------------------------------------------- + // Properties. + public String CommandId { get; set; } + public String Caption { get; set; } + + //------------------------------------------------------------------- + /// + /// CanExecuteChanged event handler. + /// Unclear when this fires as Command won't know when state changes. + /// + public event EventHandler CanExecuteChanged; + protected void FireCanExecuteChanged () + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + + //------------------------------------------------------------------- + /// Determine whether this command can be executed. + public virtual bool CanExecute (object commandParam) + { + return canExecuteFn?.Invoke(commandParam) ?? false; + } + + //------------------------------------------------------------------- + /// + /// Execute command. + /// Only invokes command method if comand can be executed. + /// + public virtual void Execute (object commandParam) + { + if (!CanExecute(commandParam)) return; + executeFn?.Invoke(commandParam); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Core/SacsDfn.cs b/SacsStandardBase/Base/Core/SacsDfn.cs new file mode 100644 index 0000000..7a05d6a --- /dev/null +++ b/SacsStandardBase/Base/Core/SacsDfn.cs @@ -0,0 +1,168 @@ +using System; + +namespace Sarand.Net.Standard.Base.Core +{ + //===================================================================== + // System Code types (static lists). + // Defined by the underlying system and available for use throughout any application. + // Only maintainable by updates to the software, ie. not held on the database. + public enum SaSysCodeType + { + Unknown = 0, + //ItemType, + //ParamCategory, + //Priority, + MessageLevel, + InterfaceCategory, + InterfaceProcessDirection, + }; + + //===================================================================== + // Status. + public enum SaStatus + { + Unknown = 0, + Current = 10, + Redundant, + Superseded, + Discontinued, + Provisional, + Confirmed, + Enabled, + Disabled, + Created, + Updated, + Reinstated, + Deleted, + Required, + Awaiting, + Preparation, + Raised, + InProgress, + Active, + Inactive, + OnHold, + Imported, + Exported, + Open, + Closed, + Completed, + Invoiced, + Cancelled, + Successful, + Failed, + Actioned, + Authorised, + Accepted, + Rejected, + Submitted, + Queried, + Granted, + Denied, + Logged, + } + + //===================================================================== + // Data Item Type. + public enum SaDataItemType + { + Unknown = 0, + // Application data types should start from 1. + // Base number for non-standard data types. + FirstNonStandard = 10000, + // Values are tied to the database - do not change. + CodeType = Int16.MaxValue - 80, + Code = CodeType + 1, + Language = Int16.MaxValue - 60, + Region = Language + 1, + Currency = Region + 1, + TaxCode = Currency + 1, + UnitMeasure = TaxCode + 1, + ItemOption = Int16.MaxValue - 40, + DiaryEntry = Int16.MaxValue - 30, + DiaryLink = DiaryEntry + 1, + Interface = Int16.MaxValue - 20, + SecurityItem = Int16.MaxValue - 10, + SecurityLink = Int16.MaxValue - 11, + }; + + //===================================================================== + public enum SaMessageLevel + { + Unknown = 0, + None = Unknown, + Fatal = 10, + Exception, + Error, + Warning, + Info, + Debug, + Trace, + } + + //===================================================================== + // I n t e r f a c e s + //===================================================================== + // Interface category. + public enum SaInterfaceCategory + { + // Do not change - values are tied to database. + Unknown = 0, + Website = 10, + Accounts = 20, /* Recognised financial systems, eg. Sage Line 50, Quickbooks. */ + System = 30, + File = 40, + }; + // Interface direction. + public enum SaInterfaceProcessDirection + { + // Do not change - values are tied to database. + Unknown = 0, + Inbound = 10, + Outbound = 20, + Both = 30, + }; + + //===================================================================== + public static class Sacs + { + //------------------------------------------------------------------- + /// Mime types. + public enum MimeType + { + Unknown = 0, + // Data. + Text, + Csv, + Xml, + Zip, + Bin, + // Web. + Html, + Css, + Javascript, + Json, + // Images. + Png, + Jpeg, + Bmp, + Gif, + Tif, + // Documents. + Pdf, + // Text documents. + Rtf, + MsDoc, + MsDocx, + OpenDocText, + // Spreadsheets. + MsXls, + MsXlsx, + OpenDocSpreadsheet, + } + + //------------------------------------------------------------------- + } + + //===================================================================== +} diff --git a/SacsStandardBase/Base/Core/SacsDisposable.cs b/SacsStandardBase/Base/Core/SacsDisposable.cs new file mode 100644 index 0000000..3943abd --- /dev/null +++ b/SacsStandardBase/Base/Core/SacsDisposable.cs @@ -0,0 +1,52 @@ +using System; + +namespace Sarand.Net.Standard.Base.Core +{ + //======================================================================= + public abstract class SacsDisposable : IDisposable + { + //------------------------------------------------------------------- + // Data memebrs. + private bool disposed = false; + + //------------------------------------------------------------------- + // Construction. + protected SacsDisposable () + { + } + + //------------------------------------------------------------------- + // Disposal. + ~SacsDisposable () + { + Dispose(false); + } + public void Dispose () + { + if (disposed) return; + Dispose(true); + disposed = true; + GC.SuppressFinalize(this); + } + protected abstract void Dispose (bool disposing); + + //------------------------------------------------------------------- + // Properties. + public bool Disposed { get { return disposed; } } + + //------------------------------------------------------------------- + // Utilities. + + //------------------------------------------------------------------- + } + + //======================================================================= + public static class DisposableUtil + { + //------------------------------------------------------------------- + + //------------------------------------------------------------------- + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Core/SacsErrorState.cs b/SacsStandardBase/Base/Core/SacsErrorState.cs new file mode 100644 index 0000000..0216e92 --- /dev/null +++ b/SacsStandardBase/Base/Core/SacsErrorState.cs @@ -0,0 +1,22 @@ +using Sarand.Net.Standard.Base.Utils; +using System; + +namespace Sarand.Net.Standard.Base.Core +{ + //======================================================================= + public class SacsErrorState + { + //------------------------------------------------------------------- + // Construction. + public SacsErrorState (string message) + { + Message = StringUtil.EmptyOnNull(message).ReplaceAll("\r\n","\n").ReplaceAll("\n"," ").TrimAll(new char[] { ' ', '\t', '\n', '\r' }, " "); + } + //------------------------------------------------------------------- + // Properties. + public String Message { get; private set; } = String.Empty; + //------------------------------------------------------------------- + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Core/SacsMessage.cs b/SacsStandardBase/Base/Core/SacsMessage.cs new file mode 100644 index 0000000..c7bb074 --- /dev/null +++ b/SacsStandardBase/Base/Core/SacsMessage.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.Core +{ + //======================================================================= + public class SacsMessage + { + //------------------------------------------------------------------- + // Construction. + public SacsMessage (String message, SaMessageLevel messageLevel = SaMessageLevel.Info) + { + Message = message; + MessageLevel = messageLevel; + } + + //------------------------------------------------------------------- + // Properties. + public SaMessageLevel MessageLevel { get; } = SaMessageLevel.Info; + public String Message { get; } + + //------------------------------------------------------------------- + // Utilities. + /// Automatic conversion from string to info message. + public static implicit operator SacsMessage (String message) + { + return new SacsMessage(message); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Core/SacsStateItem.cs b/SacsStandardBase/Base/Core/SacsStateItem.cs new file mode 100644 index 0000000..4d53fbe --- /dev/null +++ b/SacsStandardBase/Base/Core/SacsStateItem.cs @@ -0,0 +1,58 @@ +using Sarand.Net.Standard.Base.Utils; +using System; + +namespace Sarand.Net.Standard.Base.Core +{ + //======================================================================= + /// State Item interface. + public interface ISacsStateItem + { + bool HasErrorState (); + SacsErrorState ErrorState { get; } + String ErrorStateMessage { get; } + bool SetErrorState (String message); + void ClearErrorState (); + } + + //======================================================================= + /// + /// State Item - allows business component to record and report error message. + /// + public class SacsStateItem : ISacsStateItem + { + //------------------------------------------------------------------- + // Data items. + + //------------------------------------------------------------------- + // Protected construction - use derived classes. + protected SacsStateItem () { } + + //------------------------------------------------------------------- + // Poperties. + public SacsErrorState ErrorState { get; private set; } + public bool HasErrorState () + { + return ErrorState != null; + } + public String ErrorStateMessage + { + get { return (ErrorState != null ? ErrorState.Message : String.Empty); } + } + // Update properties. + public virtual bool SetErrorState (String message) + { + if (StringUtil.IsEmpty(StringUtil.EmptyOnNull(message).Trim())) + throw new NotSupportedException(GetType().Name + ".SetErrorState Blank error message"); + this.ErrorState = new SacsErrorState(message); + return false; + } + public void ClearErrorState () + { + ErrorState = null; + } + + //------------------------------------------------------------------- + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Core/StackLimited.cs b/SacsStandardBase/Base/Core/StackLimited.cs new file mode 100644 index 0000000..6336bc8 --- /dev/null +++ b/SacsStandardBase/Base/Core/StackLimited.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Core +{ + //======================================================================= + /// + /// Stack (LIFO) with limit on length. + /// + /// + public class StackLimited + { + //------------------------------------------------------------------- + // Data members. + private int limit = 0; + private readonly LinkedList items = new LinkedList(); + //------------------------------------------------------------------- + // Construction. + public StackLimited (int limit = 0) + { + this.limit = limit; + } + //------------------------------------------------------------------- + // Utilities. + public int Count + { + get { return items.Count; } + } + public void Clear () + { + items.Clear(); + } + //------------------------------------------------------------------- + /// + /// Add item to the stack and ensure limit isn't exceeded. + /// + /// + public void Push (ITEM item) + { + items.AddFirst(item); + while (limit > 0 && items.Count > limit) + items.RemoveLast(); + } + //------------------------------------------------------------------- + /// + /// Retrieve and remove last item added to the stack. + /// + /// + public ITEM Pop () + { + ITEM item = items.First.Value; + items.RemoveFirst(); + return item; + } + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Core/StringPair.cs b/SacsStandardBase/Base/Core/StringPair.cs new file mode 100644 index 0000000..93f4c1d --- /dev/null +++ b/SacsStandardBase/Base/Core/StringPair.cs @@ -0,0 +1,108 @@ +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Core +{ + //======================================================================= + public class StringPair : Tuple + { + //------------------------------------------------------------------- + // Definitions. + public enum Value + { + Value1, + Value2, + } + + //------------------------------------------------------------------- + // Construction. + public StringPair (String value1, String value2) : + base(StringUtil.EmptyOnNull(value1), StringUtil.EmptyOnNull(value2)) + { + } + + //------------------------------------------------------------------- + // Concatenation. + public String Concat (String delimiter, bool delimBlanks = true) + { + String value = StringUtil.EmptyOnNull(Item1); + if (!String.IsNullOrEmpty(Item2) || delimBlanks) + value += StringUtil.EmptyOnNull(delimiter); + value += StringUtil.EmptyOnNull(Item2); + return value; + } + + //------------------------------------------------------------------- + // Operators. + public static implicit operator StringPair (String value1) + { + return new StringPair(value1, String.Empty); + } + + //------------------------------------------------------------------- + } + //======================================================================= + public static class StringPairList + { + //------------------------------------------------------------------- + public static List CreateFrom (List valuesToAdd, bool duplicateAsS2 = false) + { + List values = new List(); + foreach (String valueToAdd in valuesToAdd) + values.Add(new StringPair(valueToAdd, (duplicateAsS2 ? valueToAdd : ""))); + return values; + } + public static List CreateFrom (List s1Values, List s2Values) + { + List values = new List(); + for (int valueIdx = 0; valueIdx < (Math.Min(s1Values.Count,s2Values.Count)); valueIdx++) + { + values.Add(new StringPair(s1Values[valueIdx], s2Values[valueIdx])); + } + return values; + } + + //------------------------------------------------------------------- + public static void Add (this List values, String s1, String s2 = "") + { + values.Add(new StringPair(s1, s2)); + } + public static void AddRange (this List values, List valuesToAdd, bool duplicateAsS2 = false) + { + values.AddRange(CreateFrom(valuesToAdd, duplicateAsS2)); + } + + //------------------------------------------------------------------- + public static List Concat (this List values, String delimiter, bool delimitBlanks = true) + { + List data = new List(); + foreach (var pair in values) + { + String value = pair.Item1; + if (!String.IsNullOrEmpty(pair.Item2) || delimitBlanks) + value += delimiter; + value += pair.Item2; + data.Add(value); + } + return data; + } + + //------------------------------------------------------------------- + public static List GetValues (this List values, StringPair.Value valueType = StringPair.Value.Value1) + { + List data = new List(); + foreach (var pair in values) + { + String value = pair.Item1; + if (valueType == StringPair.Value.Value2) + value = pair.Item2; + data.Add(value); + } + return data; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Core/TypeCodeDesc.cs b/SacsStandardBase/Base/Core/TypeCodeDesc.cs new file mode 100644 index 0000000..970c2d1 --- /dev/null +++ b/SacsStandardBase/Base/Core/TypeCodeDesc.cs @@ -0,0 +1,213 @@ +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Core +{ + //======================================================================= + public class TypeCodeDesc : CodeDesc + { + //------------------------------------------------------------------- + // Construction. + public TypeCodeDesc (int type, String code, String desc, String comment = "") : + base(code, desc, comment) + { + Type = type; + } + //------------------------------------------------------------------- + // Properties. + public int Type { get; } + //------------------------------------------------------------------- + } + + //======================================================================= + public static class TypeCodeDescList + { + //------------------------------------------------------------------- + // P o p u l a t i o n + //------------------------------------------------------------------- + public static void Add (this List values, int type, String code, string desc, string comment = "") + { + values.Add(new TypeCodeDesc(type, code, desc, comment)); + } + // Generic version - to supply type as enum. + public static void Add (this List values, T type, String code, string desc, string comment = "") where T : struct, IConvertible + { + if (!(type is Enum)) + throw new ArgumentException("TypeCodeDescList.Add Type parameter must be an enumerated type. Type supplied: " + type.GetType().Name); + values.Add(new TypeCodeDesc((int)(type as IConvertible), code, desc, comment)); + } + public static void Insert (this List values, int index, int type, String code, string desc, string comment = "") + { + values.Insert(index, new TypeCodeDesc(type, code, desc, comment)); + } + // Generic version - to supply type as enum. + public static void Insert (this List values, int index, T type, String code, string desc, string comment = "") where T : struct, IConvertible + { + if (!(type is Enum)) + throw new ArgumentException("TypeCodeDescList.Insert Type parameter must be an enumerated type. Type supplied: " + type.GetType().Name); + values.Insert(index, new TypeCodeDesc((int)(type as IConvertible), code, desc, comment)); + } + + //------------------------------------------------------------------- + // C o n t a i n s + //------------------------------------------------------------------- + [Obsolete("Deprecated - use ContainsType() instead.")] + public static bool FindType (this List values, int type) + { + foreach (var value in values) + { + if (value.Type == type) return true; + } + return false; + } + /// Does the line include an entry for this type. + public static bool ContainsType (this List values, int type) + { + foreach (var value in values) + { + if (value.Type == type) return true; + } + return false; + } + /// Does the line include an entry for this (enum) type. + public static bool ContainsType (this List values, T type) where T : struct, IConvertible + { + if (!(type is Enum)) + throw new ArgumentException("TypeCodeDescList.FindType parameter must be an enumerated type. Type supplied: " + type.GetType().Name); + return values.ContainsType((int)(type as IConvertible)); + } + + //------------------------------------------------------------------- + [Obsolete("Deprecated - use ContainsCode() instead.")] + public static bool FindCode (this List values, String code) + { + foreach (var value in values) + { + if (value.Code.EqualsCI(code)) return true; + } + return false; + } + /// Does the line include an entry for this case-insensitive code. + public static bool ContainsCode (this List values, String code) + { + foreach (var value in values) + { + if (value.Code.EqualsCI(code)) return true; + } + return false; + } + + //------------------------------------------------------------------- + // L o o k u p + //------------------------------------------------------------------- + /// Retrieve the entry associated with this type. + public static TypeCodeDesc GetEntryForType (this List values, int type) + { + return values.Find(item => item.Type == type); + } + /// Retrieve the entry associated with this (enum) type. + public static TypeCodeDesc GetEntryForType (this List values, T type) where T : struct, IConvertible + { + if (!(type is Enum)) + throw new ArgumentException("TypeCodeDescList.GetEntryForType parameter must be an enumerated type. Type supplied: " + type.GetType().Name); + return values.GetEntryForType((int)(type as IConvertible)); + } + + //------------------------------------------------------------------- + // T y p e L o o k u p + //------------------------------------------------------------------- + /// Retrieve the type associated with this case-insensitive code. + public static int GetTypeForCode (this List values, String code, int defaultValue = 0, bool caseSensitive = false) + { + foreach (var value in values) + { + if (caseSensitive && value.Code.Equals(code)) return value.Type; + if (!caseSensitive && value.Code.EqualsCI(code)) return value.Type; + } + return defaultValue; + } + /// Retrieve the type associated with this case-insensitive description. + public static int GetTypeForDesc (this List values, String desc, int defaultValue = 0, bool caseSensitive = false) + { + foreach (var value in values) + { + if (caseSensitive && value.Desc.Equals(desc)) return value.Type; + if (!caseSensitive && value.Desc.EqualsCI(desc)) return value.Type; + } + return defaultValue; + } + + //------------------------------------------------------------------- + // C o d e L o o k u p + //------------------------------------------------------------------- + /// Retrieve the code associated with this type. + public static String GetCodeForType (this List values, int type, String defaultValue = "") + { + return values.Find(item => item.Type == type)?.Code ?? defaultValue; + } + /// Retrieve the code associated with this (enum) type. + public static String GetCodeForType (this List values, T type, String defaultValue = "") where T : struct, IConvertible + { + if (!(type is Enum)) + throw new ArgumentException("TypeCodeDescList.GetCodeForType parameter must be an enumerated type. Type supplied: " + type.GetType().Name); + return values.GetCodeForType((int)(type as IConvertible), defaultValue); + } + + //------------------------------------------------------------------- + // D e s c r i p t i o n L o o k u p + //------------------------------------------------------------------- + /// Retrieve the description associated with this type. + public static String GetDescForType (this List values, int type, String defaultValue = "") + { + return values.Find(item => item.Type == type)?.Desc ?? defaultValue; + } + /// Retrieve the description associated with this (enum) type. + public static String GetDescForType (this List values, T type, String defaultValue = "") where T : struct, IConvertible + { + if (!(type is Enum)) + throw new ArgumentException("TypeCodeDescList.GetDescForType parameter must be an enumerated type. Type supplied: " + type.GetType().Name); + return values.GetDescForType((int)(type as IConvertible), defaultValue); + } + /// Retrieve the description associated with this case-insensitive code. + 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 defaultValue; + } + + //------------------------------------------------------------------- + // C o d e D e s c r i p t i o n L o o k u p + //------------------------------------------------------------------- + /// Retrieve the code and description associated with this type. + public static CodeDesc GetCodeDescForType (this List values, int type) + { + foreach (var value in values) + if (value.Type == type) return new CodeDesc(value.Code, value.Desc); + return null; + } + /// Retrieve the code and description associated with this (enum) type. + public static CodeDesc GetCodeDescForType (this List values, T type) where T : struct, IConvertible + { + if (!(type is Enum)) + throw new ArgumentException("TypeCodeDescList.GetCodeDescForType parameter must be an enumerated type. Type supplied: " + type.GetType().Name); + return values.GetCodeDescForType((int)(type as IConvertible)); + } + + //------------------------------------------------------------------- + // C o n v e r s i o n + //------------------------------------------------------------------- + /// Convert to a code & description list. + public static List ToCodeDescList (this List values) + { + List codeDescs = new List(); + foreach (var value in values) + codeDescs.Add(value.Code, value.Desc); + return codeDescs; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Data/SacsAesIvCrypter.cs b/SacsStandardBase/Base/Data/SacsAesIvCrypter.cs new file mode 100644 index 0000000..eee4a7b --- /dev/null +++ b/SacsStandardBase/Base/Data/SacsAesIvCrypter.cs @@ -0,0 +1,117 @@ +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace Sarand.Net.Standard.Base.Data +{ + //======================================================================= + public static class SacsAesIvCrypter + { + //------------------------------------------------------------------- + // Properties. + public static String AesIvDelimiter { get; } = ";"; + public static String AesIvPrefix { get; } = "AESIV:"; + + //------------------------------------------------------------------- + // Ref. https://xamarinhelp.com/cryptography-in-xamarin-forms/ + // For reversible encryption, AES is generally the recommended standard. + // AES 256 requires a 32 byte key to encrypt. + // Either generate a 32 byte key or use any string and convert it into a key with CreateKey. + // NB The salt will remain the same here for each key generated - it does not need to be randomized as with hashing. + //------------------------------------------------------------------- + private static byte[] CreateKey (string password, int keyBytes = 32) + { + byte[] salt = new byte[] { 80, 70, 60, 50, 40, 30, 20, 10 }; + int iterations = 300; + DeriveBytes keyGenerator = new Rfc2898DeriveBytes(password, salt, iterations); + return keyGenerator.GetBytes(keyBytes); + } + + //------------------------------------------------------------------- + // Encrypt String. + // Return the encrypted text with the IV appended to the end. + // In AES, the IV is a randomizer, making sure that no two identical messages ever give the same encrypted output. + // The IV can be public and does not need to be hidden. + //------------------------------------------------------------------- + public static string Encrypt (this string clearValue, string encryptionKey) + { + using (Aes aes = Aes.Create()) + { + aes.Key = CreateKey(encryptionKey); + byte[] aesIV = aes.IV; + byte[] encrypted = Encrypt(clearValue, aes.Key, aesIV); + return Convert.ToBase64String(encrypted) + AesIvDelimiter + Convert.ToBase64String(aesIV); + } + } + //------------------------------------------------------------------- + public static byte[] Encrypt (string plainText, byte[] key, byte[] iv) + { + if (plainText == null || plainText.Length <= 0) throw new ArgumentNullException($"{nameof(plainText)}"); + if (key == null || key.Length <= 0) throw new ArgumentNullException($"{nameof(key)}"); + if (iv == null || iv.Length <= 0) throw new ArgumentNullException($"{nameof(iv)}"); + byte[] encrypted; + using (Aes aes = Aes.Create()) + { + aes.Key = key; + aes.IV = iv; + using (MemoryStream memoryStream = new MemoryStream()) + { + using (ICryptoTransform encryptor = aes.CreateEncryptor()) + using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) + using (StreamWriter streamWriter = new StreamWriter(cryptoStream)) + { + streamWriter.Write(plainText); + } + encrypted = memoryStream.ToArray(); + } + } + return encrypted; + } + + //------------------------------------------------------------------- + // Decrypt String. + // Decryption requires the encrypted value and the secret key. + // This function is directly related to the above encryption process and will + // assume that the IV is at the end of the string passed in. + // To decrypt a message encrypted by another function, the IV will have to be passed explicitly. + //------------------------------------------------------------------- + public static string Decrypt (this string encryptedValue, string encryptionKey) + { + String[] valueParts = encryptedValue.Split(new String[] { AesIvDelimiter }, StringSplitOptions.None); + if (valueParts.Length != 2) + { + SaLogger.Warn("SacsAesIvCrypter.Decrypt Missing IV - assuming not encrypted"); + return encryptedValue; + //throw new CryptographicException("Decrypt failure: Missing IV"); + } + return Decrypt(Convert.FromBase64String(valueParts[0]), CreateKey(encryptionKey), Convert.FromBase64String(valueParts[1])); + } + //------------------------------------------------------------------- + public static string Decrypt (byte[] cipherText, byte[] key, byte[] iv) + { + if (cipherText == null || cipherText.Length <= 0) throw new ArgumentNullException($"{nameof(cipherText)}"); + if (key == null || key.Length <= 0) throw new ArgumentNullException($"{nameof(key)}"); + if (iv == null || iv.Length <= 0) throw new ArgumentNullException($"{nameof(iv)}"); + + string plaintext = ""; + using (Aes aes = Aes.Create()) + { + aes.Key = key; + aes.IV = iv; + using (MemoryStream memoryStream = new MemoryStream(cipherText)) + using (ICryptoTransform decryptor = aes.CreateDecryptor()) + using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) + using (StreamReader streamReader = new StreamReader(cryptoStream)) + plaintext = streamReader.ReadToEnd(); + } + return plaintext; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Data/SacsCsvHandler.cs b/SacsStandardBase/Base/Data/SacsCsvHandler.cs new file mode 100644 index 0000000..17f8c50 --- /dev/null +++ b/SacsStandardBase/Base/Data/SacsCsvHandler.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.Data +{ + //======================================================================= + /// + /// Very basic CSV data handling. + /// For more advanced CSV features, see SaCsvHelper in SacsWinOffice. + /// + public static class SacsCsvHandler + { + //------------------------------------------------------------------- + // Properties. + + //------------------------------------------------------------------- + public static String ToCsv (this IEnumerable values, char fieldDelimiter = ',', char textDelimiter = '"') + { + String data = ""; + int valueCount = 0; + foreach (String rawValue in values) + { + valueCount++; + String value = rawValue; + if (value.Contains(fieldDelimiter.ToString()) || value.Contains(textDelimiter.ToString())) + value = textDelimiter + value.Replace(textDelimiter.ToString(), textDelimiter.ToString() + textDelimiter.ToString()) + textDelimiter; + data += (valueCount > 1 ? fieldDelimiter.ToString() : "") + value; + } + return data; + } + + //------------------------------------------------------------------- + /// + /// Extract comma delimited fields from data record. + /// Fields starting with double quotes are terminated with the next single double quotes character. + /// Embedded double quotes pairs are treated as a single double quotes character. + /// + /// + /// + public static List ParseCsvRecord (String data, char fieldDelimiter = ',', char textDelimiter = '"') + { + List fields = new List(); + if (data == null) return fields; + + // Parse control properties. + String currentField = ""; + bool inTextField = false; + bool previousCharTextDelim = false; + + // Parse the data. + foreach (char c in data) + { + if (inTextField) + { + if (c == textDelimiter) + { + // Text delimiter found. + if (!previousCharTextDelim) + { + // Make a note that a text delimiter has been found. + previousCharTextDelim = true; + continue; + } + else + { + // 2nd text delimiter found - add single text delimiter to field. + currentField += c; + previousCharTextDelim = false; + continue; + } + } + if (previousCharTextDelim) + { + // Non-text delimiter foudn after text delimiter - End of text field. + inTextField = false; + previousCharTextDelim = false; + if (c == fieldDelimiter) + { + // End of field. + fields.Add(currentField); + currentField = ""; + continue; + } + continue; + } + currentField += c; + } + else + { + // Non-text field. + if (c == fieldDelimiter) + { + // End of field. + fields.Add(currentField); + currentField = ""; + continue; + } + if (currentField.Length == 0 && c == textDelimiter) + { + // First character of field is text delimiter. + inTextField = true; + continue; + } + // Append character to field. + currentField += c; + } + } + fields.Add(currentField); + + return fields; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Data/SacsData.cs b/SacsStandardBase/Base/Data/SacsData.cs new file mode 100644 index 0000000..a9559e1 --- /dev/null +++ b/SacsStandardBase/Base/Data/SacsData.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Sarand.Net.Standard.Base.Data +{ + //======================================================================= + public class SacsData + { + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Construction. + /// Construction - Empty data. + public SacsData () { } + /// Construction - Data from byte array. + public SacsData (byte[] data) + { + DataBuffer = new byte[data?.Length ?? 0]; + DataLength = data?.Length ?? 0; + if (DataLength > 0) + Array.Copy(data, 0, DataBuffer, 0, DataLength); + CheckForNull(); + } + /// + /// Construction - Data from string. + /// Encoding: ASCII + /// + public SacsData (string data) : + this(data, Encoding.ASCII) + { } + /// Construction - Data from string with specific encoding. + public SacsData (string data, Encoding encoding) : + this(encoding.GetBytes(data)) + { + StringEncoding = encoding; + } + /// Copy construction. + public SacsData (SacsData data) : + this(data.DataBuffer) + { + StringEncoding = data.StringEncoding; + } + + //------------------------------------------------------------------- + // Properties. + /// Data buffer - cannot be replaced but individual entries can be amended. + public byte[] DataBuffer { get; protected set; } = new byte[0]; + /// Length of data may be less than the buffer length. + public int DataLength { get; protected set; } + /// Encoding used to convert data to String. + public Encoding StringEncoding { get; set; } = Encoding.ASCII; + /// Does the data contain null (0x00) values?In which case conversion to String will be invalid. + public bool ContainsNull { get; protected set; } + + //------------------------------------------------------------------- + // Utilities. + private void CheckForNull() + { + ContainsNull = Array.Exists(DataBuffer, item => item == 0); + } + + //------------------------------------------------------------------- + // Conversion to String. + public override String ToString () + { + return String.Format(GetType().Name + " [Length: {0}]", DataLength); + } + public static implicit operator String (SacsData data) + { + return data.StringEncoding.GetString(data.DataBuffer); + } + public String NullTerminatedString + { + get + { + int nullIdx = Array.IndexOf(DataBuffer,(byte)0); + if (nullIdx >= 0) + return StringEncoding.GetString(DataBuffer.Take(nullIdx).ToArray()); + return StringEncoding.GetString(DataBuffer); + } + } + + //------------------------------------------------------------------- + // Append data. + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Data/SacsDataFn.cs b/SacsStandardBase/Base/Data/SacsDataFn.cs new file mode 100644 index 0000000..d8e8117 --- /dev/null +++ b/SacsStandardBase/Base/Data/SacsDataFn.cs @@ -0,0 +1,325 @@ +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Sarand.Net.Standard.Base.Data +{ + //======================================================================= + /// + /// Legacy basic data encryption. + /// Ref. ItemOptions, Session registration + /// + public static class SacsDataFn + { + //------------------------------------------------------------------- + // Data members. + private static String acknowledge = "Sarand Software Solutions: www.sarand.com"; + + //------------------------------------------------------------------- + // S t r i n g E n c r y p t i o n + //------------------------------------------------------------------- + /// Encrypt string data. + public static String _saecs (String data, SacsData keyData = null) + { + // Raw data must be a multiple of 3 + 2 in length to allow + // for 4 char header and blocks of 3 encoded chars: + data = StringUtil.PadRight(data, ((data.Length / 3) * 3) + 2, ' '); + // Encrypt the data. + SacsData encryptedData = _saecp (new SacsData(data), keyData); + // Encode the encrypted data. + SacsData encodedData = _saecd (encryptedData.DataBuffer); + // Convert the result to a string. + String result = (encodedData != null ? (String)encodedData : ""); + // Return the results. + return result; + } + + //------------------------------------------------------------------- + /// Decrypt string data. + public static String _sadcs (String data, SacsData keyData = null) + { + // Decode the data. + SacsData decodedData = _sadcd (new SacsData(data).DataBuffer); + // Decrypt the data. + SacsData decryptedData = _sadcp (decodedData.DataBuffer, keyData); + // Convert to string. + String rawData = (decryptedData != null ? (String)decryptedData : ""); + // Return the results. + return rawData.TrimEnd(new char[] { ' ' }); + } + + //------------------------------------------------------------------- + // E n c r y p t i o n + //------------------------------------------------------------------- + // Encrypted data includes a header with the following structure: + // Bytes 1 & 2 0x5aec "SArand EnCrypted" + // Byte 3 bits 0-2 Encryption method (currently only '0' supported). + // Byte 4 Reserved. + //------------------------------------------------------------------- + public static SacsData _saecp (SacsData rawData, SacsData keyData) + { + // Select encryption parameters. + // Dictates which sequence of encryption techniques is used (0 - 7). + int encryptMethod = 0; +#if DEBUG + if (encryptMethod != 0) + SaLogger.Warn("_saecp: Unknown enryption method - " + encryptMethod); +#endif + byte[] encryptedData = null; + int encryptedDataLength = 0; + + //------------------------------------------------------------------- + // Version 1.0.0 - just use the RC4 method with default key. + if (encryptMethod == 0) + { + SacsData dataRC4 = new SacsData(RC4 (rawData, keyData)); + encryptedData = dataRC4.DataBuffer; + encryptedDataLength = dataRC4.DataLength; + } + + //------------------------------------------------------------------- + // Header length - May differ by encryption method. + ushort resultHeaderLength = 4; + // Create a buffer for the result. + int resultLength = (rawData.DataLength + resultHeaderLength); + byte[] resultBuffer = new byte [resultLength]; + if (encryptMethod == 0) + { + // First two bytes always the same. + // 'SA' = "Sarand". + resultBuffer[0] = 0x5a; + // 'EC' = "Encrypted". + resultBuffer[1] = 0xec; + // Next byte holds the encrypt method in the 3 high bits: + resultBuffer[2] = (byte)(encryptMethod << 5); + resultBuffer[3] = 0; + } + // Copy the encrypted data to the remaining portion of the buffer. + Array.Copy(encryptedData, 0, resultBuffer, resultHeaderLength, encryptedData.Length); + // Return data. + return new SacsData(resultBuffer); + } + + //------------------------------------------------------------------- + // D e c r y p t i o n + //------------------------------------------------------------------- + /// Decryot data. + public static SacsData _sadcp (byte[] rawData, SacsData keyData) + { + // Decode the header. + if (rawData == null || rawData.Length == 0) + { + // No data. + return new SacsData(); + } + if (rawData.Length < 4) + { + SaLogger.Warn("_sadcp Invalid data length: " + rawData.Length + ". Expected multiple of 4."); + return new SacsData(); + } + byte headerChar1 = rawData[0]; + byte headerChar2 = rawData[1]; + // High 3 bits of header byte #3. + int encryptMethod = rawData[2]>>5; + // May differ by encryption method. + ushort resultHeaderLength = 4; + // Verify the encrypted header: + if (headerChar1 != 0x5a || headerChar2 != 0xec) + { + SaLogger.Warn("_sadcp Invalid header."); + return new SacsData(); + } + if (encryptMethod != 0) + { + SaLogger.Warn("_sadcp Invalid encryption method."); + return new SacsData(); + } + + // Decrypt data. + SacsData resultData = new SacsData(); + // Method '0' - simple RC4 decryption. + if (encryptMethod == 0) + { + resultData = RC4(rawData.Skip(resultHeaderLength).ToArray(), keyData); + } + // Convert the data to the return type: + return resultData; + } + + //------------------------------------------------------------------- + // E n c o d i n g + //------------------------------------------------------------------- + /// Encode data. + public static SacsData _saecd (byte[] pData) + { + // Prepare a buffer for the results. + int dataLength = pData.Length; + byte[] encodeBuffer = new byte[((dataLength + 2) / 3) * 4]; + int iBufPos = 0; + // Data is encoded in lots of 3 bytes at a time. + // Each lot of 3 bytes becoms 4 bytes in the encoded data set: + for (uint i = 0; i < dataLength; i += 3) + { + byte byte0 = pData[i]; + byte byte1 = (byte)(i < dataLength - 1 ? pData[i+1] : 0); + byte byte2 = (byte)(i < dataLength - 2 ? pData[i+2] : 0); + // The 3 data bytes are first converted to 4 6-bit values: + // Raw: abcdefgh ijklmnop qrstuvwx + // Conv: 00abcdef 00ghijkl 00mnopqr 00stuvwx + // Each of the resulting 4 bytes is then incremented by 0x20 (ascii code for a space): + encodeBuffer[iBufPos++] = ENC(byte0 >> 2); // Byte 1 bits 7-2 + encodeBuffer[iBufPos++] = ENC((byte0 << 4) & 0x30 | (byte1 >> 4) & 0x0F); // Byte 1 bits 1-0 & Byte 2 bits 7-4 + encodeBuffer[iBufPos++] = ENC((byte1 << 2) & 0x3C | (byte2 >> 6) & 0x03); // Byte 2 bits 3-0 & Byte 3 bits 7-6 + encodeBuffer[iBufPos++] = ENC(byte2 & 0x3F); // Byte 3 bits 5-0 + } + SacsData pdEncode = new SacsData(encodeBuffer); + return pdEncode; + } + private static byte ENC (int data) + { + return (byte)((data & 0x3F) + 0x20/*Space*/); + } + + //------------------------------------------------------------------- + /// Decode data. + public static SacsData _sadcd (byte[] rawData) + { + // The encoded data must be a multiple of 4 bytes. + if (rawData == null || rawData.Length % 4 != 0) + { + SaLogger.Warn("_sadcd Data is not a multiple of 4 bytes. Length: " + (rawData?.Length ?? 0)); + return new SacsData(); + } + // Prepare a buffer for the results. + byte[] resultsBuffer = new byte[rawData.Length / 4 * 3]; + int bufferIdx = 0; + // Iterate through the encoded data in blocks of four bytes. + for (uint idx = 0; idx < rawData.Length; idx += 4) + { + byte p = rawData[idx]; + int c1 = (DEC(rawData[idx+0]) << 2) | (DEC(rawData[idx+1]) >> 4); +#if DEBUG + int z = rawData[idx+1]; + z = DEC(z); + z = z << 4; + z = rawData[idx + 2]; + z = DEC(z); + z = z >> 2; + z = (DEC(rawData[idx + 1]) << 4) | (DEC(rawData[idx + 2]) >> 2); +#endif + int c2 = (DEC(rawData[idx+1]) << 4) | (DEC(rawData[idx+2]) >> 2); + int c3 = (DEC(rawData[idx+2]) << 6) | DEC(rawData[idx+3]); + resultsBuffer[bufferIdx++] = (byte)c1; + resultsBuffer[bufferIdx++] = (byte)c2; + resultsBuffer[bufferIdx++] = (byte)c3; + } + return new SacsData(resultsBuffer); + } + private static byte DEC (int data) + { +#if DEBUG + int z = data - 0x20; + z = z & 0x3F; +#endif + return (byte)((data - 0x20/*Space*/) & 0x3F); + } + + //------------------------------------------------------------------- + // R C 4 E n c r y p t i o n + //------------------------------------------------------------------- + // www.systweak.com & www.codeproject.com/cpp/crypt_routine.asp + // This Crypt routine uses an algorithm created by Rodney Thayer. + // This algorithm can use keys of various sizes. + // With a 40-bit key (5 characters) it can be freely exported from the U.S. + // The cipher is considered robust with 128 bits of key material but can use up to 2048 bits. + // It is compatible with RSA’s RC4 algorithm. + //--------------------------------------------------------------------------- + // The only moral here is that we are generating a 256 bit key given from the user. To encrypt we always pick a random byte from the key and encrypt the text with that key. If we have a large pattern of repeating text with this algorithm the key repeat interval (distance between n repetitions) will be approximately of 21700 (2 to the 1700th power). + // For very good security I recommend using 128 bits (16 characters) of key material. If you want optimal security you could use 2048 bits (256 characters) of key material (but this is usually considered an overkill). + // This routine is constructed from a draft taken from Internet Engineering Task Force (IETF) at http://www.ietf.cnri.reston.va.us/home.htm + // I have tested the routine on a 27 MB file to test that it produce valid decrypted output from input and get the valid results. + // We do not enter the key so we will be using default key provide by the crypt routine: + // TCHAR HelloCrypt[] = "Hello Crypt"; + // Encrypt: Crypt(HelloCrypt, _tcslen(HelloCrypt)); + // Decrypt: Crypt(HelloCrypt, _tcslen(HelloCrypt)); + // If we have to supply a key we have to use crypt as follows: + // TCHAR Data[] = "abcdefghijklmnopqrstuvwxyz" + // TCHAR Key[] = "Strong key"; + // Encrypt: Crypt(Data, _tcslen(Data), Key, _tcslen(Data)); + // Decrypt: Crypt(Data, _tcslen(Data), Key, _tcslen(Data)); + //--------------------------------------------------------------------------- + internal static SacsData RC4 (SacsData dataRaw, SacsData keyData) + { + return RC4(dataRaw.DataBuffer, keyData != null ? keyData.DataBuffer : null); + } + internal static SacsData RC4 (byte[] rawData, SacsData keyData) + { + return RC4(rawData, keyData != null ? keyData.DataBuffer : null); + } + public static SacsData RC4 (byte[] rawData, byte[] userKey = null) + { + // Key buffers (256 bytes = 1024 bits): + byte[] keyBuffer = new byte[257]; + Array.Clear(keyBuffer, 0, keyBuffer.Length); + + // Initialize the key buffer. + for (int keyIdx = 0; keyIdx <= 255U; keyIdx++) + keyBuffer[keyIdx] = (byte)keyIdx; + + // Either use the specific key supplied, or the default (unsecured) key: + byte[] keyData = Encoding.ASCII.GetBytes(acknowledge); + int keyLength = StringUtil.GetLength(acknowledge); + if (userKey != null && userKey.Length > 0) + { + keyData = userKey; + keyLength = userKey.Length; + } + // Scramble this raw key into the key buffer. + uint rootKeyIdx = 0; + for (int dataIdx = 0; dataIdx < 256; dataIdx++) + { + // Position in the root key. + rootKeyIdx = (rootKeyIdx + (uint)keyBuffer[dataIdx] + keyData[dataIdx % keyLength]) % 256U; + byte temp = keyBuffer[dataIdx]; + keyBuffer[dataIdx] = keyBuffer[rootKeyIdx]; + keyBuffer[rootKeyIdx] = temp; + } +#if DEBUG_X + // Retrieve the contents of the key buffer in a readable form. + String key = ""; + for (int keyBufferIdx = 0; keyBufferIdx <= 12; keyBufferIdx++) key += ((char)keyBuffer[keyBufferIdx]).ToString() + "."; +#endif + int i = 0; + int j = 0; + // Create a buffer for the encrypted data: + byte[]resultData = new byte [rawData.Length]; + for (uint x = 0; x < rawData.Length; x++) + { + // Increment i. + i = (int)((i + 1U) % 256U); + // Increment j. + j = (int)((j + (uint)keyBuffer[i]) % 256U); + // Scramble pKeyBuf #1 further so encryption routine will only repeat itself at great interval. + byte temp = keyBuffer[i]; + keyBuffer[i] = keyBuffer[j]; + keyBuffer[j] = temp; + // Get ready to create pseudo random byte for encryption key. + int t = (int)(((int) keyBuffer[i] + (int)keyBuffer[j]) % 256U); + // Get the target byte: + byte k = keyBuffer[t]; + // Xor with the data and done: + byte raw = rawData[x]; + resultData[x] = (byte)(raw ^ k); + } + // Create the return value. + return new SacsData(resultData); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Data/SacsShaHasher.cs b/SacsStandardBase/Base/Data/SacsShaHasher.cs new file mode 100644 index 0000000..816f16d --- /dev/null +++ b/SacsStandardBase/Base/Data/SacsShaHasher.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace Sarand.Net.Standard.Base.Data +{ + //======================================================================= + public static class SacsShaHasher + { + //------------------------------------------------------------------- + // Properties. + public static String ShaSaltDelim { get; } = ":"; + public static String Sha256Prefix { get; } = "Sha256:"; + public static String Sha384Prefix { get; } = "Sha384:"; + public static String Sha512Prefix { get; } = "Sha512:"; + + //------------------------------------------------------------------- + // Ref. https://xamarinhelp.com/cryptography-in-xamarin-forms/ + // Hash Salt. + // To add a salt to the hash, append the Salt (random string) to the value. + // The Salt can be stored with the hashed value. + // Salts do not need to be kept secret and should be different for each hash. + // This is to reduce the impact of pre-built hash tables, eg. where common passwords (‘password’) generate the same Hash. + // It also stops the same password generating the identical Hash for 2 different accounts. + //------------------------------------------------------------------- + /// + /// Non-reversible encryption. + /// Result is converted to Base64 string to ensure that all bytes are properly recorded, as UTF8 doesn’t have an exact byte match for each possible byte sequence. + /// Algorythm must be dispoed by calling routine. + /// + public static string HashString (this string value, HashAlgorithm algorythm) + { + return Convert.ToBase64String(algorythm.ComputeHash(System.Text.Encoding.UTF8.GetBytes(value))); + } + + //------------------------------------------------------------------- + /// + /// Non-reversible encryption using Sha256. + /// Result is converted to Base64 string to ensure that all bytes are properly recorded, as UTF8 doesn’t have an exact byte match for each possible byte sequence. + /// + public static string HashSha256 (this string value) + { + using (HashAlgorithm algorythm = SHA256.Create()) + { + return HashString(value, algorythm); + } + } + + //------------------------------------------------------------------- + /// + /// Non-reversible encryption using Sha384. + /// Result is converted to Base64 string to ensure that all bytes are properly recorded, as UTF8 doesn’t have an exact byte match for each possible byte sequence. + /// + public static string HashSha384 (this string value) + { + using (HashAlgorithm algorythm = SHA384.Create()) + { + return HashString(value, algorythm); + } + } + + //------------------------------------------------------------------- + /// + /// Non-reversible encryption using Sha512. + /// Result is converted to Base64 string to ensure that all bytes are properly recorded, as UTF8 doesn’t have an exact byte match for each possible byte sequence. + /// + public static string HashSha512 (this string value) + { + using (HashAlgorithm algorythm = SHA512.Create()) + { + return HashString(value, algorythm); + } + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Dbas/DbDfnColumn.cs b/SacsStandardBase/Base/Dbas/DbDfnColumn.cs new file mode 100644 index 0000000..8a69a11 --- /dev/null +++ b/SacsStandardBase/Base/Dbas/DbDfnColumn.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.Dbas +{ + //========================================================================== + //[Serializable()] + public enum DbColumnDataType + { + // Ref. https://msdn.microsoft.com/en-us/library/yy6y35y8%28v=vs.110%29.aspx + Unknown = -1, + VarChar, //SQL_C_CHAR, + Bit, // SQL_C_BIT, + TinyInt, // SQL_C_TINYINT, + SmallInt, // SQL_C_SHORT, + Int, //SQL_C_SLONG, + BigInt, // SQL_C_SBIGINT, + Single, // SQL_C_REAL, + Double, // SQL_C_DOUBLE, + Decimal, // SQL_DECIMAL?, + DateTime, // SQL_C_TYPE_TIMESTAMP + Binary, + }; + //[Serializable()] + public enum DbColumnNullable + { + NoNulls = 0, // SQL_NO_NULLS, + Nullable = 1, // SQL_NULLABLE, + Unknown = 2, // SQL_NULLABLE_UNKNOWN, // Not known whether or not a data type supports null values. + }; + //========================================================================== + //[Serializable()] + public class DbDfnColumn + { + //------------------------------------------------------------------- + // Data members. + //------------------------------------------------------------------- + // Construction. + public DbDfnColumn () { } + public DbDfnColumn (String schema, String table, short pos, String name) + { + Schema = schema; + Table = table; + Pos = pos; + Name = name; + } + //------------------------------------------------------------------- + // Attributes. + public String Schema { get; set; } = String.Empty; + public String Table { get; set; } = String.Empty; + public short Pos { get; set; } = 0; + public String Name { get; set; } = String.Empty; + public DbColumnDataType DataType { get; set; } = DbColumnDataType.Unknown; + public String DataTypeName { get; set; } = String.Empty; + public short DataTypeNative { get; set; } = 0; + public String DataTypeNameNative { get; set; } = String.Empty; + public bool IsIdentity { get; set; } = false; + public long Size { get; set; } = 0; + public long BufferLen { get; set; } = 0; + public short DecimalDigits { get; set; } = 0; + public DbColumnNullable Nullable { get; set; } = DbColumnNullable.Unknown; + public bool IsNullable { get; set; } = false; + public String Remarks { get; set; } = String.Empty; + //------------------------------------------------------------------- + // Variable type and default - used in variable declaration. + public String GetVariableTypeName () + { + switch (DataType) + { + case DbColumnDataType.Binary: + return "ByteArray"; + } + return GetVariableType(); + } + public String GetVariableType () + { + switch (DataType) + { + case DbColumnDataType.VarChar: + return "String"; + case DbColumnDataType.Bit: + return "Boolean"; + case DbColumnDataType.TinyInt: + return "Byte"; + case DbColumnDataType.SmallInt: + return "Int16"; + case DbColumnDataType.Int: + return "Int32"; + case DbColumnDataType.BigInt: + return "Int64"; + case DbColumnDataType.Single: + return "Single"; + case DbColumnDataType.Double: + return "Double"; + case DbColumnDataType.Decimal: + return "Decimal"; + case DbColumnDataType.DateTime: + return "DateTime"; + case DbColumnDataType.Binary: + return "Byte[]"; + } + return String.Empty; + } + public String GetVariableDefault () + { + switch (DataType) + { + case DbColumnDataType.VarChar: + return "String.Empty"; + case DbColumnDataType.Bit: + return "false"; + case DbColumnDataType.TinyInt: + return "0"; + case DbColumnDataType.SmallInt: + return "0"; + case DbColumnDataType.Int: + return "0"; + case DbColumnDataType.BigInt: + return "0"; + case DbColumnDataType.Single: + return "0.0f"; + case DbColumnDataType.Double: + return "0.0"; + case DbColumnDataType.Decimal: + return "0.0m"; + case DbColumnDataType.DateTime: + return "DateTime.MinValue"; + case DbColumnDataType.Binary: + return "null"; + } + return String.Empty; + } + public String GetGetFieldSuffix () + { + switch (DataType) + { + case DbColumnDataType.VarChar: + case DbColumnDataType.Bit: + case DbColumnDataType.TinyInt: + case DbColumnDataType.SmallInt: + case DbColumnDataType.Int: + case DbColumnDataType.BigInt: + case DbColumnDataType.Single: + case DbColumnDataType.Double: + case DbColumnDataType.Decimal: + case DbColumnDataType.DateTime: + return GetVariableType(); + case DbColumnDataType.Binary: + return "ByteArray"; + } + return String.Empty; + } + //------------------------------------------------------------------- + } + //========================================================================== + // Column list extensions. + public static class DbDfnColumnList + { + // List of column names. + public static List GetNames (this List columns) + { + List names = new List(); + foreach (var column in columns) + { + names.Add(column.Name); + } + return names; + } + // Identity column. + public static String GetIdentityColumnName (this List columns) + { + List names = new List(); + foreach (var column in columns) + { + if (column.IsIdentity) + return column.Name; + } + return String.Empty; + } + // Locate the column in the list: + public static DbDfnColumn GetColumnByPos (this List columns, short pos) + { + foreach (var column in columns) + { + if (column.Pos == pos) + return column; + } + return null; + } + public static DbDfnColumn GetColumnByName (this List columns, String name) + { + foreach (var column in columns) + { + if (column.Name.EqualsCI(name)) + return column; + } + return null; + } + } + //========================================================================== +} diff --git a/SacsStandardBase/Base/Dbas/DbDfnDatabase.cs b/SacsStandardBase/Base/Dbas/DbDfnDatabase.cs new file mode 100644 index 0000000..33683d5 --- /dev/null +++ b/SacsStandardBase/Base/Dbas/DbDfnDatabase.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Dbas +{ + //======================================================================= + //[Serializable()] + public class DbDfnDatabase + { + //------------------------------------------------------------------- + // Data members. + private readonly List tables = new List(); + //------------------------------------------------------------------- + // Construction. + public DbDfnDatabase () { } + //------------------------------------------------------------------- + // Tables. + public List Tables + { + get { return tables; } + set { tables.Clear(); tables.AddRange(value); } + } + public bool HasTables () + { + return (tables != null && tables.Count > 0); + } + public void CearTables () + { + tables.Clear(); + } + public void AddTable (DbDfnTable table) + { + tables.Add(table); + } + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Dbas/DbDfnIndex.cs b/SacsStandardBase/Base/Dbas/DbDfnIndex.cs new file mode 100644 index 0000000..40cd1d7 --- /dev/null +++ b/SacsStandardBase/Base/Dbas/DbDfnIndex.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.Dbas +{ + //======================================================================= + //[Serializable()] + public class DbDfnIndex + { + //------------------------------------------------------------------- + // Definitions. + public static readonly int SQL_TABLE_STAT = 0; + public static readonly int SQL_INDEX_CLUSTERED = 1; + public static readonly int SQL_INDEX_HASHED = 2; + public static readonly int SQL_INDEX_OTHER = 3; + // SQL_INDEX_BTREE = ???; + // SQL_INDEX_CONTENT = ???; + + //------------------------------------------------------------------- + // Data members. + private readonly List columns = new List(); + //------------------------------------------------------------------- + // Construction. + public DbDfnIndex () { } + public DbDfnIndex (String schema, String table, String name, bool unique) + { + Schema = schema; + Table = table; + Name = name; + Unique = unique; + } + //------------------------------------------------------------------- + // Attributes. + public String Schema { get; set; } = String.Empty; + public String Table { get; set; } = String.Empty; + public String Name { get; set; } = String.Empty; + public int IndexType { get; set; } = -1; + public bool Unique { get; set; } = false; + public bool Primary { get; set; } = false; + public bool Clustered { get; set; } = false; + public bool Nullable { get; set; } = false; + public int Pages { get; set; } = 0; + public int UniqueValues { get; set; } = 0; + public bool HasColumns { get; set; } = false; + //------------------------------------------------------------------- + // Columns. + public List Columns + { + get { return columns; } + set { columns.Clear(); columns.AddRange(value); } + } + public void CearColumns () + { + columns.Clear(); + } + public void AddColumn (DbDfnIndexColumn column) + { + columns.Add(column); + } + //------------------------------------------------------------------- + } + //======================================================================= + // Index list extensions. + public static class DbDfnIndexList + { + // List of index names. + public static List GetNames (this List indices) + { + List names = new List(); + foreach (var index in indices) + names.Add(index.Name); + return names; + } + // Locate the index in the list. + public static DbDfnIndex GetIndexByName (this List indices, String name) + { + return indices.Find(idx => idx.Name.EqualsCI(name)); + } + // Retrieve primary index. + public static DbDfnIndex GetPrimaryIndex (this List indices) + { + return indices.Find(idx => idx.Primary); + } + // Retrieve clustered index. + public static DbDfnIndex GetClusteredIndex (this List indices) + { + DbDfnIndex index = indices.Find(idx => idx.Clustered); + if (index == null) + index = indices.Find(idx => idx.IndexType == DbDfnIndex.SQL_INDEX_CLUSTERED); + return index; + } + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Dbas/DbDfnIndexColumn.cs b/SacsStandardBase/Base/Dbas/DbDfnIndexColumn.cs new file mode 100644 index 0000000..5fb71d4 --- /dev/null +++ b/SacsStandardBase/Base/Dbas/DbDfnIndexColumn.cs @@ -0,0 +1,61 @@ +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Dbas +{ + //========================================================================== + //[Serializable()] + public class DbDfnIndexColumn + { + //------------------------------------------------------------------- + // Construction. + public DbDfnIndexColumn () { } + public DbDfnIndexColumn (short seq, String name, bool ascend) + { + this.Seq = seq; + this.Name = name; + this.Ascend = ascend; + } + + //------------------------------------------------------------------- + // Attributes. + public short Seq { get; set; } = 0; + public String Name { get; set; } = String.Empty; + public bool Ascend { get; set; } = true; + + //------------------------------------------------------------------- + } + //========================================================================== + // Index Column list extensions. + public static class DbDfnIndexColumnList + { + //------------------------------------------------------------------- + /// Compile list of column names. + public static List GetNames (this List columns) + { + List names = new List(); + foreach (var column in columns) + { + names.Add(column.Name); + } + return names; + } + + //------------------------------------------------------------------- + /// Get position (from 1) for column by name. + public static int GetPosForName (this List columns, String columnName) + { + for (int idx = 0; idx < columns.Count; idx++) + foreach (var column in columns) + { + if (columns[idx].Name.EqualsCI(columnName)) return idx + 1; + } + return 0; + } + + //------------------------------------------------------------------- + } + //========================================================================== +} diff --git a/SacsStandardBase/Base/Dbas/DbDfnTable.cs b/SacsStandardBase/Base/Dbas/DbDfnTable.cs new file mode 100644 index 0000000..ade6286 --- /dev/null +++ b/SacsStandardBase/Base/Dbas/DbDfnTable.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.Dbas +{ + //======================================================================= + //[Serializable()] + public class DbDfnTable + { + //------------------------------------------------------------------- + // Data members. + private readonly List columns = new List(); + private readonly List indices = new List(); + + //------------------------------------------------------------------- + // Construction. + public DbDfnTable () { } + public DbDfnTable (String schema, String name, String remarks) + { + this.Schema = schema; + this.Name = name; + this.Remarks = remarks; + } + + //------------------------------------------------------------------- + // Properties. + public String Schema { get; set; } = String.Empty; + public String Name { get; set; } = String.Empty; + public String QualifiedName { get { return (!StringUtil.IsEmpty(Schema) ? Schema + "." : "") + Name; } } + public String Remarks { get; set; } = String.Empty; + + //------------------------------------------------------------------- + // Comparison. + public bool MatchesSchemaName (String schema, String name) + { + return (this.Schema.EqualsCI(schema) && this.Name.EqualsCI(name)); + } + public bool MatchesName (String name) + { + return (this.Name.EqualsCI(name)); + } + + //------------------------------------------------------------------- + // Columns. + public bool HasColumnByName (String columnName) + { + return (columns != null && columns.Exists(col => col.Name.EqualsCI(columnName))); + } + public bool HasColumns () + { + return (columns != null && columns.Count > 0); + } + public List Columns + { + get { return new List(columns); } + set { columns.Clear(); columns.AddRange(value); } + } + public void CearColumns () + { + columns.Clear(); + } + public void AddColumn (DbDfnColumn column) + { + columns.Add(column); + } + public List GetPrimaryColumnNames () + { + ListprimaryColumnNames = new List(); + // Identify primary index. + var index = Indices.GetPrimaryIndex(); + if (index == null) index = indices.GetClusteredIndex(); + if (index != null) + { + foreach (var column in index.Columns) + primaryColumnNames.Add(column.Name); + } + if (primaryColumnNames.IsEmpty()) throw new NotSupportedException("Table " + Name + " has no primary columns defined."); + return primaryColumnNames; + } + + //------------------------------------------------------------------- + // Indices. + public bool HasIndexByName (String indexName) + { + return (indices != null && indices.Exists(idx => idx.Name.EqualsCI(indexName))); + } + public List Indices + { + get { return new List(indices); } + set { indices.Clear(); indices.AddRange(value); } + } + public DbDfnIndex GetIndexByName (String name) + { + return indices.GetIndexByName(name); + } + public void CearIndices () + { + indices.Clear(); + } + public void AddIndex (DbDfnIndex Index) + { + indices.Add(Index); + } + + //------------------------------------------------------------------- + } + //======================================================================= + // Table list extensions. + public static class DbDfnTableList + { + //------------------------------------------------------------------- + /// List of table names. + public static List GetNames (this List tables) + { + List names = new List(); + foreach (DbDfnTable table in tables) + { + names.Add(table.Name); + } + return names; + } + + //------------------------------------------------------------------- + /// Extract the table definition by schema and table name. + public static DbDfnTable GetTableBySchemaName (this List tables, String schema, String name) + { + foreach (DbDfnTable table in tables) + { + if (table.MatchesSchemaName(schema, name)) + return table; + } + return null; + } + /// Extract the table definition by table name. + public static DbDfnTable GetTableByName (this List tables, String name) + { + foreach (DbDfnTable table in tables) + { + if (table.MatchesName(name)) + return table; + } + return null; + } + + //------------------------------------------------------------------- + /// Get total number of columns for all tables. + public static int GetColumnCount (this List tables) + { + int columnCount = 0; + foreach (DbDfnTable table in tables) + columnCount += table.Columns.Count; + return columnCount; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Dbas/DbQuery.cs b/SacsStandardBase/Base/Dbas/DbQuery.cs new file mode 100644 index 0000000..1b617a7 --- /dev/null +++ b/SacsStandardBase/Base/Dbas/DbQuery.cs @@ -0,0 +1,751 @@ +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.Utils; + +namespace Sarand.Net.Standard.Base.Dbas +{ + //======================================================================= + // Definitions. + public enum DbJoinType + { + Inner, + LeftOuter, + RightOuter, + FullOuter, + Cross, + } + public enum DbOrderByNullMode + { + Default, + DemoteNulls, + PromoteNulls, + DateDemoteNulls, + DatePromoteNulls + } + public enum DbDateDiffInterval + { + Year, + Quarter, + Month, + DayOfYear, + Day, + Weekday, + Week, + Hour, + Minute, + Second, + } + + //======================================================================= + // Global DBMS. + // When dealing with databases using different DBMSs, the methods below that + // depend on the DBMS to correctly format query clauses need to ensure that + // they are referencing the correct DBMS type. + // This global value is designed to be populated whenever a Query is created + // with a specific DBMS specified so that it can be made available to other + // methods that depend on the value (especially static ones). + public class DbGlobalDbms + { + private DbGlobalDbms () { } + private static DbmsType dbms = DbmsType.General; + public static DbmsType Type + { + get { return dbms; } + set { if (value != DbmsType.General) dbms = value; } + } + } + + //======================================================================= + public class DbQueryJoin + { + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Construction. + public DbQueryJoin (DbJoinType type = DbJoinType.Inner) + { + this.Type = type; + } + + //------------------------------------------------------------------- + // Properties. + public DbJoinType Type { get; private set; } = DbJoinType.Inner; + public String JoinType { get { return GetJoinType(Type); } } + public StringPair Table { get; set; } = null; /* Second value is the optional alias. */ + public List Filters { get; } = new List(); + public List SubJoins { get; } = new List(); + + //------------------------------------------------------------------- + public static String GetJoinType (DbJoinType type) + { + switch (type) + { + case DbJoinType.LeftOuter: return (DbGlobalDbms.Type == DbmsType.Sage ? "LEFT JOIN" : "LEFT OUTER JOIN"); + case DbJoinType.RightOuter: return (DbGlobalDbms.Type == DbmsType.Sage ? "RIGHT JOIN" : "RIGHT OUTER JOIN"); + case DbJoinType.FullOuter: return (DbGlobalDbms.Type == DbmsType.Sage ? "FULL JOIN" : "FULL OUTER JOIN"); + case DbJoinType.Cross: return "CROSS JOIN"; + case DbJoinType.Inner: + default: return "INNER JOIN"; + } + } + + //------------------------------------------------------------------- + public String BuildJoin (String enclosedJoin) + { + //--------------------------------------------------------------- + // Select syntax. + // SELECT [ DISTINCT | ALL ] + // column_expression1, column_expression2, .... + // [ FROM from_clause ] + // [ WHERE where_expression ] + // [ GROUP BY expression1, expression2, .... ] + // [ HAVING having_expression ] + // [ ORDER BY order_column_expr1, order_column_expr2, .... ] + // column_expression ::= expression [ AS ] [ column_alias ] + // from_clause ::= select_table1, select_table2, ... + // from_clause ::= select_table1 LEFT [OUTER] JOIN select_table2 ON expr ... + // from_clause ::= select_table1 RIGHT [OUTER] JOIN select_table2 ON expr ... + // from_clause ::= select_table1 [INNER] JOIN select_table2 ... + // select_table ::= table_name [ AS ] [ table_alias ] + // select_table ::= ( sub_select_statement ) [ AS ] [ table_alias ] + // order_column_expr ::= expression [ ASC | DESC ] + // Issues. + // MS Access - complex join expressions must be bracketed: ON (expr ...) + //--------------------------------------------------------------- + String joinData = String.Empty; + //--------------------------------------------------------------- + // Integrate multiple joins (nested). + // + // ( + // + // ( ON ) + // ON ) + // ON + //--------------------------------------------------------------- + // Sage doesn't use 'AS' to alias tables. + String tableAliasOperator = (DbGlobalDbms.Type == DbmsType.Sage ? " " : " AS "); + String joinType = GetJoinType(Type); + String joinTable = Table.Concat (tableAliasOperator, false); + String subJoinData = String.Empty; + foreach (var subJoin in SubJoins) + { + // Add another bracket in front of the table: + joinTable = "(" + joinTable; + // Add the sub join. + subJoinData += subJoin.BuildJoin("") + ")"; + } + // Build this join's list of filter conditions. + // Take care not to add too many embedded brackets - confuses Access etc. + // [Original version: lFilter.Concat (" AND ", "(", ")", true, true)]. + String filter = String.Empty; + foreach (var rawFilterClause in Filters) + { + // Retrieve the next filter clause. + String filterClause = rawFilterClause.Trim(); + if (filterClause.IsEmpty()) continue; + // Embed the clause in brackets if it contains at least one 'OR' operator. + if (filterClause.IndexOf(" OR ", StringComparison.OrdinalIgnoreCase) >= 0) + filterClause = "(" + filterClause + ")"; + filter += (!filter.IsEmpty() ? " AND " : "") + filterClause; + } + // Surround the entire filter in brackets (if not already done). + if (filter.FindMatchingBrace(0) != filter.Length) + filter = "(" + filter + ")"; + // Build the join. + if (enclosedJoin.IsEmpty() && subJoinData.IsEmpty()) + { + //----------------------------------------------------------- + // Simple join syntax. + // ON + joinData = joinType + " " + joinTable + " ON " + filter; + //----------------------------------------------------------- + } + else + { + //----------------------------------------------------------- + // More complex join syntax - nested joins. + // ( ) ON + //----------------------------------------------------------- + // Bracket the table, enclosing any enclosed or subordinate joins. + joinData = joinType + " (" + joinTable; + if (!subJoinData.IsEmpty()) + joinData += " " + subJoinData; + if (!enclosedJoin.IsEmpty()) + joinData += " " + enclosedJoin; + joinData += ") ON " + filter; + //----------------------------------------------------------- + } + return joinData; + } + + //------------------------------------------------------------------- + // Debugging. + public void Dump () + { + SaLogger.Debug(GetType().Name + ".Dump"); + SaLogger.Debug("\tType = " + GetJoinType(Type)); + SaLogger.Debug("\tTable = " + Table.Concat(" AS ")); + SaLogger.Debug("\tFilters = " + Filters.Concat("", "\t\t", "\n")); + if (SubJoins.Count > 0) + { + } + SaLogger.Debug("\tJoin = " + BuildJoin("")); + } + + //------------------------------------------------------------------- + } + + //======================================================================= + public static class DbQueryJoinList + { + //------------------------------------------------------------------- + // List extensions. + public static String BuildJoins (this List joins, String primaryTable) + { + String joinData = primaryTable; + if (joins.Count > 0) + { + //----------------------------------------------------------- + // Integrate multiple joins (nested). + // + // ( + // + // ( ON ) + // ON ) + // ON + //----------------------------------------------------------- + bool bBracketFromFirstToLast = true; + if (bBracketFromFirstToLast) + { + // Bracketing from the first through to the last causes the + // Main table name to be included in the brackets. + // This appears to work for complex joins (multiple tables) + // but may not be 100% compliant with the SQL specification. + int joinCount = 0; + foreach (var join in joins) + { + // Add the next join in the list. + if (++joinCount > 1 && !joinData.IsEmpty()) joinData = "(" + joinData + ")"; + joinData = joinData + " " + join.BuildJoin(""); + joinData.Trim(); + } + } + else + { + // Jul-04 Attempt to organise joins with innermost bracketed. + // Start from the end of the list and work backwards. + // NB Fails with multiple joins - check SQL specification! + // Start with the last join and roll up towards the first. + joinData = String.Empty; + foreach (var join in joins) + { + // Add the next join in the list. + joinData = join.BuildJoin(joinData); + joinData.Trim(); + } + joinData = primaryTable + " " + joinData; + } + } + return joinData; + } + //------------------------------------------------------------------- + } + + //======================================================================= + public class DbQuery + { + //------------------------------------------------------------------- + // Results paging. + public class Paging + { + public uint PageNo { get; set; } = 0; + public ushort PageLength { get; set; } = 0; + public virtual bool Paged { get { return PageNo > 0 && PageLength > 0; } } + } + + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Construction. + public DbQuery (DbmsType dbms) + { + DbGlobalDbms.Type = dbms; + } + + //------------------------------------------------------------------- + // Properties. + public bool DistinctRows { get; set; } = false; + public uint RowLimit { get; set; } = 0; + public List Tables { get; } = new List(); + public List Columns { get; } = new List(); /* s1 = qualified column name, s2 = optional alias. */ + public List Filters { get; } = new List(); + public List GroupBy { get; } = new List(); + public List OrderBy { get; } = new List(); + public List Joins { get; } = new List(); + /// Date value used as alternative to Null on database (ref. C++ ODBC defaults). + public static DateTime NullDate { get; } = new DateTime(1970, 1, 1); + + //------------------------------------------------------------------- + // S t r i n g V a l u e F o r m a t t i n g + //------------------------------------------------------------------- + /// + /// Format the value so that it can be used in a conditional clause. + /// - single quotes are duplicated. + /// + public static String FormatForCondition (String value) + { + return StringUtil.EmptyOnNull(value).Replace("'", "''"); + } + /// + /// Format the specified string value so that it can be used in a 'LIKE' clause. + /// - single quotes are duplicated + /// - open square bracket ('[') is enclosed in square brackets ('[[]') + /// - percent ('%') is enclosed in square brackets ('[%]') unless wildcards allowed. + /// - asterisk ('*') converted to percent ('%') if wildcards allowed. + /// + public static String FormatForLike (String value, bool allowWildcards = false) + { + value = StringUtil.EmptyOnNull(value).Replace("'", "''").Replace("[", "[[]"); + if (allowWildcards) + { + value = value.Replace("*", "%"); + } + else + { + value = value.Replace("%", "[%]"); + } + return value; + } + /// + /// Format the list of values so that they it can be used in an 'In' clause. + /// - Ignore blanks (if instructed), remove duplicates & pass string values through 'FormatForCondition'. + /// - Format as: ('Value1','Value2','Value3') if string data, else (Value1,Value2,Value3) etc. + /// - Returns empty string if list is empty. + /// + public static String FormatForIn (List rawValues, bool stringData = true, bool ignoreBlanks = true) + { + String result = String.Empty; + List values = new List(); + foreach (var rawValue in rawValues) + { + String value = rawValue; + if (!rawValue.IsEmpty() || !ignoreBlanks) + { + if (stringData) + value = "'" + FormatForCondition(value) + "'"; + else value.Trim(); // Non string data can be cleansed. + values.Add(value); + } + } + values.RemoveDuplicatesCI(); + if (values.Count > 0) + result = "(" + values.Concat(",") + ")"; + return result; + } + + //------------------------------------------------------------------- + // D a t e V a l u e F o r m a t t i n g + //------------------------------------------------------------------- + public static String GetDateDelim () + { + String delim = "#"; // MS Access only? + if (DbGlobalDbms.Type == DbmsType.MsSql) + delim = "'"; // MS SQL Server. + return delim; + } + public static String FormatDateForCondition (DateTime date, bool delimit = true, bool includeMidnight = false) + { + String value = DateUtil.Format(date, "dd-MMM-yyyy"); + if (includeMidnight) + value += " 00:00:00"; + if (delimit) + { + String delim = GetDateDelim(); + value = delim + value + delim; + } + return value; + } + public static String FormatDateTimeForCondition (DateTime dateTime, bool delimit = true, bool precise = false) + { + String value = DateUtil.Format(dateTime, "yyyy-MM-dd HH:mm:ss"+(precise?".fff":"")); + if (delimit) + { + String delim = GetDateDelim(); + value = delim + value + delim; + } + return value; + } + + public static String FormatTimeForCondition (DateTime time, bool delimit = true, bool zeroSeconds = false) + { + String value = String.Empty; + if (zeroSeconds) + value = DateUtil.Format(time, "HH:mm:00"); + else + value = DateUtil.Format(time, "HH:mm:ss"); + if (delimit) + { + String delim = GetDateDelim(); + value = delim + value + delim; + } + return value; + } + + //------------------------------------------------------------------- + // F o r m a t F o r F i l t e r + //------------------------------------------------------------------- + public static String FormatNullValueFilter (String expression, bool excludeNulls) + { + String clause = String.Empty; + expression = StringUtil.EmptyOnNull(expression).Trim(); + if (!expression.IsEmpty()) + { + // Surround the expression in brackets if not present. + if (expression.Left(1) != "(") + expression = "(" + expression + ")"; + // Logic to include or exclude null values. + if (excludeNulls) + { + // Explicitly exclude Null values. + //if (DbGlobalDbms.Type == DbmsType.MsSql) + // sClause = "(CASE WHEN "+sExpr+" IS NULL THEN 0 ELSE 1 END) = 1"; + //else + clause = expression + " IS NOT NULL"; + } + else + { + // Explicitly include only Null values. + //if (DbGlobalDbms.Type == DbmsType.MsSql) + // sClause = "(CASE WHEN "+sExpr+" IS NULL THEN 0 ELSE 1 END) = 0"; + //else + clause = expression + " IS NULL"; + } + } + return clause; + } + // Construct 'Order By' clauses, allowing direction (ASC/DESC) and whether + // to demote NULL values (push to end of sequence) into account. + public static List FormatForOrderBy (String expression, bool ascending, DbOrderByNullMode nullMode = DbOrderByNullMode.Default, bool treatZeroAsNull = false) + { + List orderBy = new List(); + expression = StringUtil.EmptyOnNull(expression).Trim(); + if (!expression.IsEmpty()) + { + // Surround the expression in brackets if not present. + if (expression.Left(1) != "(") + expression = "(" + expression + ")"; + // Logic to promote null values to start of sequence or demote null values to the end of the sequence. + String nullsClause = String.Empty; + if (nullMode == DbOrderByNullMode.PromoteNulls || nullMode == DbOrderByNullMode.DatePromoteNulls) + { + if (DbGlobalDbms.Type == DbmsType.MsSql) + { + // Treat NULLs before other values. + List nullsFilters = new List(); + nullsFilters.Add(expression + " IS NULL"); + if (treatZeroAsNull) + nullsFilters.Add(expression + "=0"); + if (nullMode == DbOrderByNullMode.DatePromoteNulls) + nullsFilters.Add(expression + "<=" + FormatDateForCondition(NullDate)); + nullsClause = "(CASE WHEN " + nullsFilters.Concat(" OR ", "(", ")") + " THEN 0 ELSE 1 END)"; + } + else + nullsClause = expression + " IS NULL"; + } + else if (nullMode == DbOrderByNullMode.DemoteNulls || nullMode == DbOrderByNullMode.DateDemoteNulls) + { + if (DbGlobalDbms.Type == DbmsType.MsSql) + { + // Treat NULLs after other values. + List nullsFilters = new List(); + nullsFilters.Add(expression + " IS NULL"); + if (treatZeroAsNull) + nullsFilters.Add(expression + "=0"); + if (nullMode == DbOrderByNullMode.DateDemoteNulls) + nullsFilters.Add(expression + "<=" + FormatDateForCondition(NullDate)); + nullsClause = "(CASE WHEN " + nullsFilters.Concat(" OR ", "(", ")") + " THEN 1 ELSE 0 END)"; + } + else + nullsClause = expression + " IS NOT NULL"; + } + if (!nullsClause.IsEmpty()) orderBy.Add(nullsClause); + // Main clause for this expression. + orderBy.Add(expression + " " + (ascending ? "ASC" : "DESC")); + } + return orderBy; + } + + //------------------------------------------------------------------- + // S Q L F u n c t i o n F o r m a t t i n g + //------------------------------------------------------------------- + // NB Many functions depend on the DBMS in use. + // Default versions are for Access/SQL. + public static String FormatFn_Len (String column) + { + if (String.IsNullOrEmpty(column)) return "''"; + // MS Access. + return "Len(" + column + ")"; + } + public static String FormatFn_Left (String column, int length) + { + if (String.IsNullOrEmpty(column) || length < 1) return "''"; + // MS Access. + return "Left(" + column + "," + length.ToString() + ")"; + } + public static String FormatFn_Right (String column, int length) + { + if (String.IsNullOrEmpty(column) || length < 1) return "''"; + // MS Access. + return "Right(" + column + "," + length.ToString() + ")"; + } + public static String FormatFn_TimeValue (String expression) + { + if (DbGlobalDbms.Type == DbmsType.MsSql) + { + // Convert with Date/Time values. + // Param 3 - Date/Time format. + // 1 = mm/dd/yy + // 2 = yy.mm.dd + // 3 = dd/mm/yy + // 4 = dd.mm.yy + // 5 = dd-mm-yy + // 6 = dd mmm yy + // 7 = mmm dd, yy + // 8 = hh:mm:ss + // 9 = mmm dd yyyy hh:mm:ss:000AP + return "Convert(VarChar(10)," + expression + ",8)"; // CONVERT(varchar(10), DateClosed, 8) + } + return "TimeValue(" + expression + ")"; + } + + //------------------------------------------------------------------- + // N u m e r i c F u n c t i o n s + //------------------------------------------------------------------- + public static String FormatFn_Abs (String sExpr) + { + if (sExpr.IsEmpty()) return "''"; + return "Abs(" + sExpr + ")"; + } + + public static String FormatFn_Round (String expression, int decimals = 0) + { + if (String.IsNullOrEmpty(expression) || decimals < 0) return "''"; + if (DbGlobalDbms.Type == DbmsType.Access) + { + // MS Access. + // NB Uses the 'round-to-even' logic - 0.5 values go to the even whole value rather than always up. + // Solution to get it behave consistently - add a fraction to make it 0.5001. + return "Round(" + expression + ((long)(10 ^ -(decimals + 2))).ToString() + "," + decimals.ToString() + ")"; + } + return "Round(" + expression + "," + decimals.ToString() + ")"; + } + + public static String FormatFn_Truncate (String expression, int decimals = 0) + { + if (String.IsNullOrEmpty(expression) || decimals < 0) return "''"; + if (DbGlobalDbms.Type == DbmsType.MsSql) + return "Round(" + expression + "," + decimals.ToString() + ",1)"; + if (DbGlobalDbms.Type == DbmsType.Access) + // MS Access. + if (decimals > 0) + { + String factor = ((long)(10^decimals)).ToString(); + return "Int(" + expression + "*" + factor + "," + decimals.ToString() + ")/" + factor; + } + return "Int(" + expression + "," + decimals.ToString() + ")"; + } + + public static String FormatFn_FormatNumber (String expression, int decimals) + { + if (String.IsNullOrEmpty(expression) || decimals < 0) return "''"; + // MS Access. + return "FormatNumber(" + expression + "," + decimals.ToString() + ")"; + } + + //------------------------------------------------------------------- + // D a t e D i f f e r e n c e + //------------------------------------------------------------------- + // Date difference: eg. DateDiff('n',Col1,Col2). + public static String FormatFn_DateDiff (DbDateDiffInterval dateInterval, String column1, String column2) + { + if (column1.IsEmpty() || column2.IsEmpty()) return "''"; + String interval = String.Empty; + if (DbGlobalDbms.Type == DbmsType.MsSql) + { + // SQL Server. + switch (dateInterval) + { + // NB No delimiter. + case DbDateDiffInterval.Year: interval = "yyyy"; break; + case DbDateDiffInterval.Quarter: interval = "q"; break; + case DbDateDiffInterval.Month: interval = "m"; break; + case DbDateDiffInterval.DayOfYear: interval = "y"; break; + case DbDateDiffInterval.Day: interval = "d"; break; + //case Weekday: interval = "w"; break; // Not supported by SQL Server. + case DbDateDiffInterval.Week: interval = "ww"; break; + case DbDateDiffInterval.Hour: interval = "h"; break; + case DbDateDiffInterval.Minute: interval = "n"; break; + case DbDateDiffInterval.Second: interval = "s"; break; + //case MilliSecond: interval = "ms"; break; // Not yet defined. + } + } + else + { + // MS Access. + switch (dateInterval) + { + case DbDateDiffInterval.Year: interval = "'yyyy'"; break; + case DbDateDiffInterval.Quarter: interval = "'q'"; break; + case DbDateDiffInterval.Month: interval = "'m'"; break; + case DbDateDiffInterval.DayOfYear: interval = "'y'"; break; + case DbDateDiffInterval.Day: interval = "'d'"; break; + case DbDateDiffInterval.Weekday: interval = "'w'"; break; + case DbDateDiffInterval.Week: interval = "'ww'"; break; + case DbDateDiffInterval.Hour: interval = "'h'"; break; + case DbDateDiffInterval.Minute: interval = "'n'"; break; + case DbDateDiffInterval.Second: interval = "'s'"; break; + } + if (interval.IsEmpty()) return "''"; + } + return "DateDiff(" + interval + "," + column1 + "," + column2 + ")"; + } + + //------------------------------------------------------------------- + // B i t M a p p e d V a l u e s + //------------------------------------------------------------------- + public static String BuildBitMappedValue (String column, ushort bit, bool match) + { + // Filter clause to test for specific bit set in field. + String clause = String.Empty; + if (!String.IsNullOrEmpty(column)) + { + if (bit > 0 && bit <= 32) + { + // The operation depands on whether the value should be matched or not. + String condition = (match? "<>" : "="); + // Test is achieved by shifting the value so as to drop the target bit, shift back one bit + // and identify those value that then do not match the original value shifted by one place less. + // Example match: abcdefgh, Test bit 3: abcde0 <> abcdef + clause = "(" + FormatFn_Truncate(column + "/" + Math.Pow(2, bit).ToString()) + "*2)" + condition + FormatFn_Truncate(column + "/" + Math.Pow(2, bit - 1).ToString()); + if (DbGlobalDbms.Type == DbmsType.MsSql) + clause = "(Case When " + clause + " Then 1 Else 0 End)"; + } +#if DEBUG + else + { + SaLogger.Debug("BuildBitMappedFilter - Invalid bit (" + bit + ")."); + } +#endif + } +#if DEBUG + else + { + SaLogger.Debug("BuildBitMappedFilter - NULL column name."); + } +#endif + return clause; + } + + public static String BuildBitMappedFilter (String column, ushort bit, bool match) + { + String clause = BuildBitMappedValue(column, bit, match); + if (!clause.IsEmpty()) + { + if (DbGlobalDbms.Type == DbmsType.MsSql) + clause = "(" + clause + " <> 0)"; + } + return clause; + } + + //------------------------------------------------------------------- + // A t t r i b u t e s + //------------------------------------------------------------------- + public void Clear () + { + Tables.Clear(); + Columns.Clear(); + Filters.Clear(); + GroupBy.Clear(); + OrderBy.Clear(); + Joins.Clear(); + } + public bool IsValid () + { + return (Tables.Count > 0); + } + + //------------------------------------------------------------------- + // B u i l d Q u e r y + //------------------------------------------------------------------- + public String BuildQuery (Paging paging = null) + { + if (Tables.Count < 1) return ""; + + //--------------------------------------------------------------------- + // Start with the column names. + String query = "SELECT "; + if (DistinctRows) query += "DISTINCT "; + if (RowLimit > 0) query += "TOP " + RowLimit.ToString() + " "; + if (Columns.Count < 1) query += "* "; + else query += Columns.Concat(" AS ", false).Concat(","); + + //--------------------------------------------------------------------- + // Add the tables and joins. + String tableAliasOperator = DbGlobalDbms.Type == DbmsType.Sage? " " : " AS "; + if (Tables.Count > 1 || Joins.Count < 1) + { + query += " FROM " + Tables.Concat(tableAliasOperator, false).Concat(",") + " " + Joins.BuildJoins(""); + } + else if (Tables.Count == 1) + { + // Table name is bracketed with multiple joins... + query += " FROM " + Joins.BuildJoins(Tables.Concat(tableAliasOperator, false)[0]); + } + query.TrimEnd(' '); + + //--------------------------------------------------------------------- + // Filters. + String filter = Filters.Concat (" AND ", "(", ")", true, true); + // Jan2019 Not required: if (Filters.Count > 1) filter = "(" + filter + ")"; + if (!filter.IsEmpty()) query += " WHERE " + filter; + + // Groupings. + String groupByData = GroupBy.Concat(",", "", "", true, true); + if (!groupByData.IsEmpty()) query += " GROUP BY " + groupByData; + + // Sort sequence. + String orderByData = OrderBy.Concat(",", "", "", true, true); + if (!orderByData.IsEmpty()) query += " ORDER BY " + orderByData; + + // Paging. + if (paging != null && paging.Paged) + { + // Must have an 'Order By' clause to use 'Offset ... Fetch Next ...' + if (orderByData.IsEmpty()) + throw new NotSupportedException("Query paging requires sort ofrder to be specified"); + if (DbGlobalDbms.Type != DbmsType.MsSql) + throw new NotSupportedException("Query paging only supported for MS SQL"); + query += " OFFSET " + ((paging.PageNo - 1) * paging.PageLength) + " ROWS FETCH NEXT " + paging.PageLength + " ROWS ONLY"; + } + + //--------------------------------------------------------------------- +#if DEBUG + bool bDisplayDebugMsg = false; + if (bDisplayDebugMsg) + SaLogger.Debug("Query: " + query); +#endif + + //--------------------------------------------------------------------- + return query; + } + + //------------------------------------------------------------------- + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Dbas/DbResultSet.cs b/SacsStandardBase/Base/Dbas/DbResultSet.cs new file mode 100644 index 0000000..f76b32c --- /dev/null +++ b/SacsStandardBase/Base/Dbas/DbResultSet.cs @@ -0,0 +1,176 @@ +using System; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.App; + +namespace Sarand.Net.Standard.Base.Dbas +{ + //======================================================================= + public abstract class DbResultSet : SaErrorStateItem, IDbResultSet + { + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Construction. + protected DbResultSet () + { + } + + //------------------------------------------------------------------- + // Navigation. + public abstract bool NextRecord (); + public abstract void Close (); + + //------------------------------------------------------------------- + // Properties. + public abstract int FieldCount { get; } + public bool IsValidColumn (int columnIdx) + { + return (columnIdx >= 0 && columnIdx < FieldCount); + } + public abstract bool HasRows { get; } + + //------------------------------------------------------------------- + // Field Values. + public abstract int GetOrdinal (String columnName); + public abstract String GetDataTypeName (int columnIdx); + public abstract String GetName (int columnIdx); + public abstract bool IsDbNull (int columnIdx); + public abstract Object GetValue (int columnIdx); + public abstract bool GetBoolean (int columnIdx); + public abstract byte GetByte (int columnIdx); + public abstract short GetInt16 (int columnIdx); + public abstract int GetInt32 (int columnIdx); + public abstract long GetInt64 (int columnIdx); + public abstract float GetFloat (int columnIdx); + public abstract double GetDouble (int columnIdx); + public abstract decimal GetDecimal (int columnIdx); + public abstract String GetString (int columnIdx); + public abstract DateTime GetDateTime (int columnIdx); + + //------------------------------------------------------------------- + // Get Field Value. + public DataValue GetFieldValue (String columnName) + { + String fn = GetType().Name+".GetFieldValue('" + columnName + "')"; + DataValue dataValue = null; + try + { + dataValue = GetFieldValue(GetOrdinal(columnName)); + } + catch (Exception exc) + { + SaLogger.Warn(fn + " Exception: " + exc.Message); + } + return dataValue; + } +#if DEBUG + private static bool handlingGetFieldValueException = false; +#endif + public DataValue GetFieldValue (int columnIdx) + { + String fn = GetType().Name+".GetFieldValue(" + columnIdx + ")"; + if (IsDbNull(columnIdx)) return null; + String columnDataType = ""; + DataValue dataValue = null; + try + { + columnDataType = GetDataTypeName(columnIdx); + switch (columnDataType) + { + // Catering for c# (DataTableReader) and sql (DbDataReader) type names + case "bit": + case "Bool": + dataValue = new DataValue(GetBoolean(columnIdx)); + break; + case "tinyint": + dataValue = new DataValue((long)GetByte(columnIdx)); + break; + case "smallint": + case "Int16": + dataValue = new DataValue((long)GetInt16(columnIdx)); + break; + case "int": + case "int identity": + case "Int32": + dataValue = new DataValue((long)GetInt32(columnIdx)); + break; + case "bigint": + case "Int64": + dataValue = new DataValue((long)GetInt64(columnIdx)); + break; + case "real": + case "float": + case "Float": + case "Single": + case "Double": + dataValue = new DataValue(GetDouble(columnIdx)); + break; + case "decimal": + case "Decimal": + case "numeric": + case "Numeric": + dataValue = new DataValue(GetDecimal(columnIdx)); + break; + case "char": + case "varchar": + case "nvarchar": + case "String": + dataValue = new DataValue(GetString(columnIdx)); + break; + case "datetime": + case "DateTime": + case "smalldatetime": + case "SmallDateTime": + dataValue = new DataValue(GetDateTime(columnIdx)); + break; + default: + SaLogger.Warn(fn + " Unrecognised data type '" + columnDataType + "' for column '" + GetName(columnIdx) + "'"); + break; + } + } + catch (Exception exc) + { + SaLogger.Warn(fn + " Data type: "+ columnDataType + ". Exception: " + exc.Message /*+ "\n" + exc.ToString()*/); +#if DEBUG + if (!handlingGetFieldValueException) + { + // Reinvoke for debugging. + handlingGetFieldValueException = true; + DataValue value = GetFieldValue(columnIdx); + handlingGetFieldValueException = false; + } +#endif + } + return dataValue; + } + // Get Record Values. + public NamedValue.Map GetRecordValues () + { + NamedValue.Map values = null; + values = new NamedValue.Map(); + for (int columnIdx = 0; columnIdx < FieldCount; columnIdx++) + { + String columnName = GetName(columnIdx); + DataValue dataValue = GetFieldValue(columnIdx); + if (dataValue != null) + values.AddValue(columnName, dataValue); + } + return values; + } + + //------------------------------------------------------------------- + // Logging. + public static SaMessageLevel LoggingLevel { get; set; } = SaMessageLevel.None; + public static String LoggingPrefix { get; set; } = " ~~~ "; + protected static void Log (String fn, String message, SaMessageLevel level = SaMessageLevel.Debug) + { + if ((int)level > (int)LoggingLevel) return; + SaLogger.Log((fn + "\n\t" + message).Trim(),level); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Dbas/DbUpdateInfo.cs b/SacsStandardBase/Base/Dbas/DbUpdateInfo.cs new file mode 100644 index 0000000..f517078 --- /dev/null +++ b/SacsStandardBase/Base/Dbas/DbUpdateInfo.cs @@ -0,0 +1,29 @@ +using System; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.Dbas +{ + //======================================================================= + public class DbUpdateInfo + { + //------------------------------------------------------------------- + // Construction. + public DbUpdateInfo (SysId userId, String userName, String host) + { + UserId = SysId.InvalidOnNull(userId); + UserName = StringUtil.EmptyOnNull(userName); + Host = StringUtil.EmptyOnNull(host); + } + + //------------------------------------------------------------------- + // Properties. + public SysId UserId { get; private set; } + public String UserName { get; private set; } + public String Host { get; private set; } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Dbas/DbUtil.cs b/SacsStandardBase/Base/Dbas/DbUtil.cs new file mode 100644 index 0000000..7b2b4eb --- /dev/null +++ b/SacsStandardBase/Base/Dbas/DbUtil.cs @@ -0,0 +1,10 @@ +namespace Sarand.Net.Standard.Base.Dbas +{ + //======================================================================= + public static class DbUtil + { + //------------------------------------------------------------------- + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Dbas/IDbDataItem.cs b/SacsStandardBase/Base/Dbas/IDbDataItem.cs new file mode 100644 index 0000000..3553970 --- /dev/null +++ b/SacsStandardBase/Base/Dbas/IDbDataItem.cs @@ -0,0 +1,54 @@ +using Sarand.Net.Standard.Base.Dbas; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Text; + +namespace Sarand.Net.Standard.Base.Dbas +{ + //======================================================================= + /// + /// Database information - object supplied by main application. + /// NB Application may deploy to databases with schema differences. + /// Each instance of a DbDataItem must be database specific, ie. must be able to distinguish the schema in use. + /// + public interface IDatabaseInfo + { + String Context { get; } + bool TableExists(String schemaName, String tableName); + bool ColumnExists(String schemaName, String tableName, String columnName); + List GetColumnsForTable(String schemaName, String tableName); + } + + //======================================================================= + public interface IDbDataItem + { + //------------------------------------------------------------------- + // Population. + void PopulateFrom (DbCommand dbCommand, String tableAlias, String filter); + void PopulateFrom (DbDataReader dbdataReader); + + //------------------------------------------------------------------- + // Retrieve field values. + String GetFieldString (DbDataReader dbDataReader, String name, String defaultValue); + Boolean GetFieldBoolean (DbDataReader dbDataReader, String name, Boolean defaultValue); + Int16 GetFieldInt16 (DbDataReader dbDataReader, String name, Int16 defaultValue); + Byte GetFieldByte (DbDataReader dbDataReader, String name, Byte defaultValue); + Int32 GetFieldInt32 (DbDataReader dbDataReader, String name, Int32 defaultValue); + Int64 GetFieldInt64 (DbDataReader dbDataReader, String name, Int64 defaultValue); + Single GetFieldSingle (DbDataReader dbDataReader, String name, Single defaultValue); + Double GetFieldDouble (DbDataReader dbDataReader, String name, Double defaultValue); + Decimal GetFieldDecimal (DbDataReader dbDataReader, String name, Decimal defaultValue); + DateTime GetFieldDateTime (DbDataReader dbDataReader, String name, DateTime defaultValue); + + //------------------------------------------------------------------- + // Database updates via ODBC Command. + bool ExecuteInsertCommand (DbCommand dbCommand); + bool ExecuteUpdateCommand (DbCommand dbCommand); + //bool ExecuteDeleteCommand (DbCommand dbCommand); + + //------------------------------------------------------------------- + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Dbas/IDbDataTable.cs b/SacsStandardBase/Base/Dbas/IDbDataTable.cs new file mode 100644 index 0000000..eb86c35 --- /dev/null +++ b/SacsStandardBase/Base/Dbas/IDbDataTable.cs @@ -0,0 +1,9 @@ +namespace Sarand.Net.Standard.Base.Dbas +{ + //======================================================================= + public interface IDbDataTable + { + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Dbas/IDbResultSet.cs b/SacsStandardBase/Base/Dbas/IDbResultSet.cs new file mode 100644 index 0000000..c5d793f --- /dev/null +++ b/SacsStandardBase/Base/Dbas/IDbResultSet.cs @@ -0,0 +1,37 @@ +using System; +using Sarand.Net.Standard.Base.Core; + +namespace Sarand.Net.Standard.Base.Dbas +{ + //======================================================================= + interface IDbResultSet : IDisposable + { + //------------------------------------------------------------------- + // Navigation. + bool NextRecord (); + void Close (); + //------------------------------------------------------------------- + // Attributes. + int FieldCount { get; } + bool HasRows { get; } + int GetOrdinal (String columnName); + String GetDataTypeName (int columnIdx); + String GetName (int columnIdx); + bool GetBoolean (int columnIdx); + short GetInt16 (int columnIdx); + int GetInt32 (int columnIdx); + long GetInt64 (int columnIdx); + float GetFloat (int columnIdx); + double GetDouble (int columnIdx); + decimal GetDecimal (int columnIdx); + String GetString (int columnIdx); + DateTime GetDateTime (int columnIdx); + //------------------------------------------------------------------- + // Field values. + DataValue GetFieldValue (String columnName); + DataValue GetFieldValue (int columnIdx); + NamedValue.Map GetRecordValues (); + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Dbas/ISacsDatabase.cs b/SacsStandardBase/Base/Dbas/ISacsDatabase.cs new file mode 100644 index 0000000..a87cd38 --- /dev/null +++ b/SacsStandardBase/Base/Dbas/ISacsDatabase.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Core; + +namespace Sarand.Net.Standard.Base.Dbas +{ + //======================================================================= + // Definitions. + public enum DbmsType + { + General, + Access, + MsSql, + MySql, + Oracle, + Pervasive, + Dbase, + FoxPro, + Paradox, + Interbase, + /// No 'AS' in table alias, no 'OUTER' in join type. + Sage, + QBooks, + SybaseAdvantage, + } + public enum SelectLockMode + { + /// No specific lock mode requested - use DBMS default. + Default, + /// Shared locks (MSSQL default). [MSSQL] + ReadCommitted, + /// Dirty reads - no locking. [MSSQL] + ReadUncommitted, + /// Locks all data in query. [MSSQL] + RepeatableRead, + /// Range lock on data preventing modifications. [MSSQL] + Serializable, + }; + /// Reserved word delimiters - used by database definitions. + public enum DbReservedWordDelim + { + None, + SquareBrackets, + DoubleQuotes + }; + public enum DbGetValueType { eDbValueSum, eDbValueMin, eDbValueMax }; + + //======================================================================= + public interface ISacsDatabase : ISacsStateItem + { + //------------------------------------------------------------------- + // Database properties. + String ConnectionString { get; } + DbmsType DbmsType { get; } + String DatabaseName { get; } + SelectLockMode SelectLockMode { get; set; } + bool DisplayErrors { get; set; } + DbConnection CreateConnection(bool forceOdbc = false); + + //------------------------------------------------------------------- + // Identity generation. + SysId GetNextId(String objectName, DbUpdateInfo dbUpdateInfo, bool databaseTable = true, String idColumn = "Id"); + int GetNextIdFromTable(String objectName, String idColumn = "Id"); + bool SetMinNextId(String objectName, SysId id, DbUpdateInfo dbUpdateInfo, bool databaseTable = true, String idColumn = "Id", bool force = false); + + //------------------------------------------------------------------- + // Database schema. + List GetTableNames (); + List GetTableDfns (bool refresh = false); + DbDfnTable GetTableDfn (String name, bool refresh = false); + bool TableExists (String tableName); + bool ColumnExists (String tableName, String columnName); + bool IndexExists (String tableName, String indexName); + + //------------------------------------------------------------------- + // Query records. + String GetSelectQueryPrefix (); + IDbDataTable ExecuteQuery (String query); + int ExecuteNonQuery (String query); + int ExecuteInsert (String tableName, List columnNameValues); + int ExecuteUpdate (String tableName, List columnNameValues, String filter); + int ExecuteDelete (String tableName, String filter); + // Specific queries. + long GetRowCount (String tableName, String filter); + DataValue GetRowValue (String tableName, String columnName, String filter); + DataValue GetRowValue (String tableName, String columnName, String filter, DbGetValueType getValueType); + + //------------------------------------------------------------------- + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Dbas/OdbcUtil.cs b/SacsStandardBase/Base/Dbas/OdbcUtil.cs new file mode 100644 index 0000000..b8242ad --- /dev/null +++ b/SacsStandardBase/Base/Dbas/OdbcUtil.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; + +namespace Sarand.Net.Standard.Base.Dbas +{ + //======================================================================= + public static class OdbcUtil + { + //------------------------------------------------------------------- + // O d b c D r i v e r s + //------------------------------------------------------------------- + [DllImport("odbccp32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool SQLGetInstalledDriversW (char[] lpszBuf, ushort cbufMax, out ushort pcbBufOut); + /// + /// Gets the ODBC driver names from the SQLGetInstalledDrivers function. + /// + /// a string array containing the ODBC driver names, if the call to SQLGetInstalledDrivers was successfull; null, otherwise. + public static string[] GetOdbcDriverNames () + { + string[] odbcDriverNames = null; + char[] driverNamesBuffer = new char[ushort.MaxValue]; + ushort size; + bool succeeded = SQLGetInstalledDriversW(driverNamesBuffer, ushort.MaxValue, out size); + if (succeeded == true) + { + char[] driverNames = new char[size - 1]; + Array.Copy(driverNamesBuffer, driverNames, size - 1); + odbcDriverNames = (new string(driverNames)).Split('\0'); + } + return odbcDriverNames; + } + /// + /// Gets the name of an ODBC driver for Microsoft SQL Server giving preference to the most recent one. + /// + /// the name of an ODBC driver for Microsoft SQL Server, if one is present; null, otherwise. + public static string GetOdbcSqlDriverName () + { + List driverPrecedence = new List() { "SQL Server Native Client 14.0", "SQL Server Native Client 13.0", "SQL Server Native Client 12.0", "SQL Server Native Client 11.0", "SQL Server Native Client 10.0", "SQL Server Native Client 9.0", "SQL Server" }; + string[] availableOdbcDrivers = GetOdbcDriverNames(); + string driverName = null; + if (availableOdbcDrivers != null) + { + driverName = driverPrecedence.Intersect(availableOdbcDrivers).FirstOrDefault(); + } + return driverName; + } + //------------------------------------------------------------------- + // SQL Data Type to Native Data Type + //------------------------------------------------------------------- + public static short GetSqlNativeDataType (String dataTypeName) + { + switch (dataTypeName) + { + case "varchar": return 12; // SQL_VARCHAR + case "nvarchar": return -9; // SQL_WVARCHAR + case "bit": return -7; // SQL_BIT + case "tinyint": return -6; // SQL_TINYINT + case "smallint": return 5; // SQL_SMALLINT + case "int": return 4; // SQL_INTEGER + case "bigint": return -5; // SQL_BIGINT + case "real": return 7; // SQL_REAL + case "float": return 6; // SQL_FLOAT + case "decimal": return 3; // SQL_DECIMAL + case "numeric": return 2; // SQL_NUMERIC + case "datetime": return 93; // SQL_TYPE_TIMESTAMP + case "smalldatetime": return 93; // SQL_TYPE_TIMESTAMP + case "varbinary": return 93; // SQL_TYPE_TIMESTAMP + default: + SaLogger.Warn("OdbcUtil.GetSqlNativeDataType Unrecognised SQL data type: '" + dataTypeName + "'"); + break; + } + return 0; + } + //------------------------------------------------------------------- + // O d b c T o D a t a T y p e + //------------------------------------------------------------------- + // ANSI SQL data type C++ data type + // CHAR CString + // DECIMAL CString 1 + // SMALLINT int + // REAL float + // INTEGER long + // FLOAT double + // DOUBLE double + // NUMERIC CString 1 + // VARCHAR CString + // LONGVARCHAR CLongBinary, CString 2 + // BIT BOOL + // TINYINT BYTE + // BIGINT CString 1 + // BINARY CByteArray + // VARBINARY CByteArray + // LONGVARBINARY CLongBinary, CByteArray 3 + // DATE CTime, CString + // TIME CTime, CString + // TIMESTAMP CTime, CString + // Notes: + // 1. ANSI DECIMAL and NUMERIC map to CString because SQL_C_CHAR is the default ODBC transfer type. + // 2. Character data beyond 255 characters is truncated by default when mapped to CString. + // You can extend the truncation length by explicitly setting the nMaxLength argument of RFX_Text. + // 3. Binary data beyond 255 characters is truncated by default when mapped to CByteArray. + // You can extend the truncation length by explicitly setting the nMaxLength argument of RFX_Binary. + //------------------------------------------------------------------- + public static DbColumnDataType OdbcToDataType (short dataType) + { + // Data types: + // 2 DECIMAL, CURRENCY + // 4 INTEGER, COUNTER + // 5 SMALLINT + // 7 REAL + // 8 DOUBLE + // 11 DATETIME (Date & Time) + // 12 VARCHAR + // -6 BYTE + // -7 BIT (YesNo) + // -11 GUID (NumberReplId) + DbColumnDataType type = DbColumnDataType.Unknown; + switch (dataType) + { + //--------------------------------------------------------------- + case 1: // SQL_CHAR: // 1 CHAR(n) Character string of fixed string length n. + case 12: // SQL_VARCHAR: // 12 VARCHAR(n) Variable-length character string with a maximum string length n. + case -1: // SQL_LONGVARCHAR: // -1 LONG VARCHAR Variable length character data. Maximum length is data source–dependent.[9] + case -8: // SQL_WCHAR: // -8 WCHAR(n) Unicode character string of fixed string length n + case -9: // SQL_WVARCHAR: // -9 VARWCHAR(n) Unicode variable-length character string with a maximum string length n + case -10: // SQL_WLONGVARCHAR: // -10 LONGWVARCHAR Unicode variable-length character data. Maximum length is data source–dependent + type = DbColumnDataType.VarChar; + break; + //--------------------------------------------------------------- + case 3: // SQL_DECIMAL: // 3 DECIMAL(p,s) Signed, exact, numeric value with a precision of at least p and scale s. (The maximum precision is driver-defined.) (1 <= p <= 15; s <= p).[4] + case 2: // SQL_NUMERIC: // 2 NUMERIC(p,s) Signed, exact, numeric value with a precision p and scale s (1 <= p <= 15; s <= p).[4] + type = DbColumnDataType.Decimal; + break; + //--------------------------------------------------------------- + case -6: // SQL_TINYINT: // -6 TINYINT Exact numeric value with precision 3 and scale 0 (signed: –128 <= n <= 127, unsigned:0 <= n <= 255)[3]. + type = DbColumnDataType.TinyInt; //eByte + break; + //--------------------------------------------------------------- + case 5: // SQL_SMALLINT: // 5 SMALLINT Exact numeric value with precision 5 and scale 0 (signed: –32,768 <= n <= 32,767, unsigned: 0 <= n <= 65,535)[3]. + type = DbColumnDataType.SmallInt; + break; + //--------------------------------------------------------------- + case 4: // SQL_INTEGER: // 4 INTEGER Exact numeric value with precision 10 and scale 0 (signed: –2[31] <= n <= 2[31] – 1, unsigned:0 <= n <= 2[32] – 1)[3]. + type = DbColumnDataType.Int; + break; + //--------------------------------------------------------------- + case 7: // SQL_REAL: // 7 REAL Signed, approximate, numeric value with a binary precision 24 (zero or absolute value 10[–38] to 10[38]). + type = DbColumnDataType.Single; + break; + //--------------------------------------------------------------- + case 6: // SQL_FLOAT: // 6 FLOAT(p) Signed, approximate, numeric value with a binary precision of at least p. (The maximum precision is driver-defined.)[5] + case 8: // SQL_DOUBLE: // 8 DOUBLE PRECISION Signed, approximate, numeric value with a binary precision 53 (zero or absolute value 10[–308] to 10[308]). + type = DbColumnDataType.Double; + break; + //--------------------------------------------------------------- + case 9: // SQL_DATETIME: // 9 DateTime ODBC + case 11: // 11 DateTime Access DateTime format (SQL_TIMESTAMP?) + case 91: // SQL_TYPE_DATE: // 91 DATE Year, month, and day fields, conforming to the rules of the Gregorian calendar. + case 92: // SQL_TYPE_TIME: // 92 TIME(p) Hour, minute, and second fields, with valid values for hours of 00 to 23, valid values for minutes of 00 to 59, and valid values for seconds of 00 to 61. Precision p indicates the seconds precision. + case 93: // SQL_TYPE_TIMESTAMP: // 93 Time Stamp TIMESTAMP(p) - Year, month, day, hour, minute, and second fields, with valid values as defined for the DATE and TIME data types. + type = DbColumnDataType.DateTime; + break; + //--------------------------------------------------------------- + case -7: // SQL_BIT: // -7 BIT Single bit binary data.[8] + type = DbColumnDataType.Bit; + break; + //--------------------------------------------------------------- + case -2: // SQL_BINARY: // -2 BINARY(n) Binary data of fixed length n.[9] + case -3: // SQL_VARBINARY: // -3 VARBINARY(n) Variable length binary data of maximum length n. The maximum is set by the user.[9] + case -4: // SQL_LONGVARBINARY: // -4 LONG VARBINARY Variable length binary data. Maximum length is data source–dependent.[9] + type = DbColumnDataType.Binary; + break; + //--------------------------------------------------------------- + case -5: // SQL_BIGINT: // -5 BIGINT Exact numeric value with precision 19 (if signed) or 20 (if unsigned) and scale 0 (signed: –2[63] <= n <= 2[63] – 1, unsigned:0 <= n <= 2[64] – 1)[3],[9]. + case 102: // SQL_INTERVAL_MONTH: // 102 INTERVAL MONTH(p) Number of months between two dates; p is the interval leading precision. + case 101: // SQL_INTERVAL_YEAR: // 101 INTERVAL YEAR(p) Number of years between two dates; p is the interval leading precision. + case 107: // SQL_INTERVAL_YEAR_TO_MONTH: // 107 INTERVAL YEAR(p) TO MONTH Number of years and months between two dates; p is the interval leading precision. + case 103: // SQL_INTERVAL_DAY: // 103 INTERVAL DAY(p) Number of days between two dates; p is the interval leading precision. + case 104: // SQL_INTERVAL_HOUR: // 104 INTERVAL HOUR(p) Number of hours between two date/times; p is the interval leading precision. + case 105: // SQL_INTERVAL_MINUTE: // 105 INTERVAL MINUTE(p) Number of minutes between two date/times; p is the interval leading precision. + case 106: // SQL_INTERVAL_SECOND: // 106 INTERVAL SECOND(p,q) Number of seconds between two date/times; p is the interval leading precision and q is the interval seconds precision. + case 108: // SQL_INTERVAL_DAY_TO_HOUR: // 108 INTERVAL DAY(p) TO HOUR Number of days/hours between two date/times; p is the interval leading precision. + case 109: // SQL_INTERVAL_DAY_TO_MINUTE: // 109 INTERVAL DAY(p) TO MINUTE Number of days/hours/minutes between two date/times; p is the interval leading precision. + case 110: // SQL_INTERVAL_DAY_TO_SECOND: // 110 INTERVAL DAY(p) TO SECOND(q) Number of days/hours/minutes/seconds between two date/times; p is the interval leading precision and q is the interval seconds precision. + case 111: // SQL_INTERVAL_HOUR_TO_MINUTE: // 111 INTERVAL HOUR(p) TO MINUTE Number of hours/minutes between two date/times; p is the interval leading precision. + case 112: // SQL_INTERVAL_HOUR_TO_SECOND: // 112 INTERVAL HOUR(p) TO SECOND(q) Number of hours/minutes/seconds between two date/times; p is the interval leading precision and q is the interval seconds precision. + case 113: // SQL_INTERVAL_MINUTE_TO_SECOND: // 113 INTERVAL MINUTE(p) TO SECOND(q) Number of minutes/seconds between two date/times; p is the interval leading precision and q is the interval seconds precision. + case -11: // SQL_GUID: // -11 GUID Fixed length Globally Unique Identifier. + break; + //--------------------------------------------------------------- + default: + SaLogger.Debug("OdbcUtil.OdbcToDataType Unrecognised native data type: " + dataType); + break; + //--------------------------------------------------------------- + } + return type; + } + //------------------------------------------------------------------- + public static String GetOdbcDataTypeCaption (short dataType) + { + switch (dataType) + { + //--------------------------------------------------------------- + case 1: return "Char"; // SQL_CHAR + case 12: return "VarChar"; // SQL_VARCHAR + case -1: return "LongVarChar"; // SQL_LONGVARCHAR + case -8: return "WChar"; // SQL_WCHAR + case -9: return "VarWChar"; // SQL_WVARCHAR + case -10: return "LongWVarChar"; // SQL_WLONGVARCHAR + case 3: return "Decimal"; // SQL_DECIMAL + case 2: return "Numeric"; // SQL_NUMERIC + case -6: return "TinyInt"; // SQL_TINYINT + case 5: return "SmallInt"; // SQL_SMALLINT + case 4: return "Integer"; // SQL_INTEGER + case 7: return "Real"; // SQL_REAL + case 6: return "Float"; // SQL_FLOAT + case 8: return "Double"; // SQL_DOUBLE + case 9: return "DateTime"; // SQL_DATETIME + case 11: return "TimeStamp_11"; + case 91: return "Date"; // SQL_TYPE_DATE + case 92: return "Time"; // SQL_TYPE_TIME + case 93: return "TimeStamp"; // SQL_TYPE_TIMESTAMP + case -7: return "Bit"; // SQL_BIT + case -5: return "BigInt"; // SQL_BIGINT + case -2: return "Binary"; // SQL_BINARY + case -3: return "VarBinary"; // SQL_VARBINARY + /* + case SQL_LONGVARBINARY: // -4 LONG VARBINARY Variable length binary data. Maximum length is data source–dependent.[9] + case SQL_INTERVAL_MONTH: // 102 INTERVAL MONTH(p) Number of months between two dates; p is the interval leading precision. + case SQL_INTERVAL_YEAR: // 101 INTERVAL YEAR(p) Number of years between two dates; p is the interval leading precision. + case SQL_INTERVAL_YEAR_TO_MONTH: // 107 INTERVAL YEAR(p) TO MONTH Number of years and months between two dates; p is the interval leading precision. + case SQL_INTERVAL_DAY: // 103 INTERVAL DAY(p) Number of days between two dates; p is the interval leading precision. + case SQL_INTERVAL_HOUR: // 104 INTERVAL HOUR(p) Number of hours between two date/times; p is the interval leading precision. + case SQL_INTERVAL_MINUTE: // 105 INTERVAL MINUTE(p) Number of minutes between two date/times; p is the interval leading precision. + case SQL_INTERVAL_SECOND: // 106 INTERVAL SECOND(p,q) Number of seconds between two date/times; p is the interval leading precision and q is the interval seconds precision. + case SQL_INTERVAL_DAY_TO_HOUR: // 108 INTERVAL DAY(p) TO HOUR Number of days/hours between two date/times; p is the interval leading precision. + case SQL_INTERVAL_DAY_TO_MINUTE: // 109 INTERVAL DAY(p) TO MINUTE Number of days/hours/minutes between two date/times; p is the interval leading precision. + case SQL_INTERVAL_DAY_TO_SECOND: // 110 INTERVAL DAY(p) TO SECOND(q) Number of days/hours/minutes/seconds between two date/times; p is the interval leading precision and q is the interval seconds precision. + case SQL_INTERVAL_HOUR_TO_MINUTE: // 111 INTERVAL HOUR(p) TO MINUTE Number of hours/minutes between two date/times; p is the interval leading precision. + case SQL_INTERVAL_HOUR_TO_SECOND: // 112 INTERVAL HOUR(p) TO SECOND(q) Number of hours/minutes/seconds between two date/times; p is the interval leading precision and q is the interval seconds precision. + case SQL_INTERVAL_MINUTE_TO_SECOND: // 113 INTERVAL MINUTE(p) TO SECOND(q) Number of minutes/seconds between two date/times; p is the interval leading precision and q is the interval seconds precision. + case SQL_GUID: // -11 GUID Fixed length Globally Unique Identifier. + */ + default: + break; + //--------------------------------------------------------------- + } + return "Type " + dataType; + } + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Diary/SaDiaryConsole.cs b/SacsStandardBase/Base/Diary/SaDiaryConsole.cs new file mode 100644 index 0000000..2ebc6bd --- /dev/null +++ b/SacsStandardBase/Base/Diary/SaDiaryConsole.cs @@ -0,0 +1,42 @@ +using Sarand.Net.Standard.Base.Bus; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.Diary +{ + //======================================================================= + public abstract class SaDiaryConsole : SaErrorStateItem + { + //------------------------------------------------------------------- + // Definitions. + + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Static initialisation. + static SaDiaryConsole () + { + } + + //------------------------------------------------------------------- + // Construction. + protected SaDiaryConsole () + { + } + + //------------------------------------------------------------------- + // Properties. + // Database. + public abstract bool DatabaseIsValid { get; } + public abstract bool CanUpdateDatabase { get; } + + //------------------------------------------------------------------- + // D i a r y E n t r i e s + //------------------------------------------------------------------- + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Email/SaEmailAttachmentData.cs b/SacsStandardBase/Base/Email/SaEmailAttachmentData.cs new file mode 100644 index 0000000..df4972e --- /dev/null +++ b/SacsStandardBase/Base/Email/SaEmailAttachmentData.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.Email +{ + //======================================================================= + public class SaEmailAttachmentData + { + //------------------------------------------------------------------- + // Construction. + public SaEmailAttachmentData() : this("", "") { } + public SaEmailAttachmentData(String mediaType, String mediaSubtype) + { + ContentType.MediaType = mediaType; + ContentType.MediaSubtype = mediaSubtype; + } + + //------------------------------------------------------------------- + // Properties. + public bool Inline { get; set; } + /// Filename - identity for non-inline attachments. + public String Filename { get; set; } + /// ContentId - identity for inline attachments. + public String ContentId { get; set; } + /// Attachment data - as byte array. + public byte[] Data { get; set; } + public class ContentTypeData + { + public String MediaType { get; set; } = "application"; + public String MediaSubtype { get; set; } = "octet-stream"; + } + /// Content type identified as Media and sub media types, eg. 'Image','png'. + public ContentTypeData ContentType { get; } = new ContentTypeData(); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Email/SaEmailConfig.cs b/SacsStandardBase/Base/Email/SaEmailConfig.cs new file mode 100644 index 0000000..742c80b --- /dev/null +++ b/SacsStandardBase/Base/Email/SaEmailConfig.cs @@ -0,0 +1,46 @@ +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.Email +{ + //======================================================================= + public class SaEmailConfig + { + //------------------------------------------------------------------- + // Construction. + public SaEmailConfig(String context) + { + Context = StringUtil.EmptyOnNull(context); + } + + //------------------------------------------------------------------- + // Properties. + public String Context { get; } + public class MessageData + { + public String AddressFrom { get; set; } + public List AddressesBcc { get; } = new List(); + } + public MessageData Message { get; } = new MessageData(); + public class SMTPData + { + public String Server { get; set; } + public int ServerPort { get; set; } + public bool UseSSL { get; set; } + } + public SMTPData SMTP { get; } = new SMTPData(); + public class SendData + { + public String Using { get; set; } + public String UserName { get; set; } + public String Password { get; set; } + } + public SendData Send { get; } = new SendData(); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Email/SaEmailConsole.cs b/SacsStandardBase/Base/Email/SaEmailConsole.cs new file mode 100644 index 0000000..8783966 --- /dev/null +++ b/SacsStandardBase/Base/Email/SaEmailConsole.cs @@ -0,0 +1,194 @@ +using MailKit.Net.Smtp; +using MailKit.Security; +using MimeKit; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +namespace Sarand.Net.Standard.Base.Email +{ + //======================================================================= + public abstract class SaEmailConsole + { + //------------------------------------------------------------------- + // Protected construction. + protected SaEmailConsole() + { + } + + //------------------------------------------------------------------- + // Send email. + //------------------------------------------------------------------- + public bool SendEmail(SaEmailData emailData) + { + String fn = GetType().Name + ".SendEmail"; + + // Validate email data. + if (emailData.AddressesTo.IsEmpty()) + { + SaLogger.Error(fn + " Unable to send email - To address not specified."); + return false; + } + + try + { + // Mail message. + MimeMessage mailMessage = CreateMailMessage(emailData); + + // Send the email. + using (var smtpClient = new SmtpClient()) + { + smtpClient.ServerCertificateValidationCallback = (s, c, h, e) => true; + + smtpClient.Connect(emailData.Config.SMTP.Server, emailData.Config.SMTP.ServerPort, emailData.Config.SMTP.UseSSL ? SecureSocketOptions.StartTlsWhenAvailable : SecureSocketOptions.Auto); + if (!StringUtil.IsEmpty(emailData.Config.Send.UserName)) + smtpClient.Authenticate(emailData.Config.Send.UserName, emailData.Config.Send.Password); + smtpClient.Send(mailMessage); + smtpClient.Disconnect(true); + } + } + catch (Exception exc) + { + SaLogger.Error(fn + " " + ExceptionUtil.GetLogMessage(exc)); + return false; + } + SaLogger.Info(fn + " Email sent to " + emailData.AddressesTo[0] + ". Subject: " + emailData.Subject); + return true; + } + + //------------------------------------------------------------------- + private MimeMessage CreateMailMessage(SaEmailData emailData) + { + // Mail message. + MimeMessage mailMessage = new MimeMessage(); + + // Addresses. + mailMessage.From.Add(new MailboxAddress("", emailData.Config.Message.AddressFrom)); + foreach (String addressTo in emailData.AddressesTo) + mailMessage.To.Add(new MailboxAddress("", addressTo)); + foreach (String addressCc in emailData.AddressesCc) + mailMessage.Cc.Add(new MailboxAddress("", addressCc)); + foreach (String addressBcc in emailData.AddressesBcc) + mailMessage.Bcc.Add(new MailboxAddress("", addressBcc)); + foreach (String addressBcc in emailData.Config.Message.AddressesBcc) + mailMessage.Bcc.Add(new MailboxAddress("", addressBcc)); + + // Body builder. + var builder = new BodyBuilder(); + + // First add the message itself. + mailMessage.Subject = emailData.Subject; + if (emailData.HtmlBody) + builder.HtmlBody = emailData.MessageBody; + else + builder.TextBody = emailData.MessageBody; + + // Attachments. + int attachmentCount = 0; + foreach (SaEmailAttachmentData attachmentData in emailData.Attachments) + { + attachmentCount++; + + // Construct attachment. + MimePart attachment = new MimePart(attachmentData.ContentType.MediaType, attachmentData.ContentType.MediaSubtype) + { + Content = new MimeContent(new MemoryStream(attachmentData.Data)), + ContentDisposition = new ContentDisposition(attachmentData.Inline ? ContentDisposition.Inline : ContentDisposition.Attachment), + ContentTransferEncoding = ContentEncoding.Base64 + }; + + // All attachments should have a Filename. + attachment.FileName = attachmentData.Filename; + if (StringUtil.IsEmpty(attachment.FileName)) + attachment.FileName = "Attachment_" + StringUtil.PadLeft(attachmentCount.ToString(), 3, '0') + ".dat"; + + // Inline attachments also have a ContentId. + if (attachmentData.Inline) + { + attachment.ContentId = attachmentData.ContentId; + if (StringUtil.IsEmpty(attachmentData.ContentId)) + attachmentData.ContentId = "Attachment_" + StringUtil.PadLeft(attachmentCount.ToString(), 3, '0') + "@saxonite.com"; + + // Add the inline attachment as a linked resource. + builder.LinkedResources.Add(attachment); + } + else + { + // Add the attachment. + builder.Attachments.Add(attachment); + } + } + + // Populate the message body. + mailMessage.Body = builder.ToMessageBody(); + + return mailMessage; + } + + //------------------------------------------------------------------- + // Replace dynamic Email Body tags. + //------------------------------------------------------------------- + // Tags. + /// Check whether the body includes a specific tag. + public static bool EmailContainsTag(SaEmailData emailData, String tag) + { + return (Regex.Matches(emailData.MessageBody, Regex.Escape(tag), RegexOptions.IgnoreCase).Count > 0); + } + /// Replace a dynamic tag in the email message. + public static void ReplaceDynamicEmailTag(SaEmailData emailData, String tag, String content) + { + if (EmailContainsTag(emailData, tag)) + emailData.MessageBody = Regex.Replace(emailData.MessageBody, Regex.Escape(tag), content ?? "", RegexOptions.IgnoreCase); + } + /// + /// Replace dynamic tags in email message that are known to the base project eg.: + /// - stylesheet (including style tags). + /// + public static void ReplaceDynamicEmailTags(SaEmailData emailData) + { + // Nothing to do. + } + + //------------------------------------------------------------------- + protected void AddInlineImageToEmail(SaEmailData emailData, String imageResourceName, byte[] imageData, String imageType = "png") + { + string tag = "[#image." + imageResourceName + "]"; + String imageFilename = Regex.Replace(imageResourceName, "_" + imageType, "." + imageType); + if (Regex.Matches(emailData.MessageBody, Regex.Escape(tag), RegexOptions.IgnoreCase).Count > 0) + { + String imageContentId = FormatAttachmentContentId(Regex.Replace(imageFilename, "\\.", "_")); + if (!emailData.Attachments.Exists(item => StringUtil.EmptyOnNull(item.ContentId).EqualsCI(imageContentId))) + { + // Image not yet in attachments. + SaEmailAttachmentData attachment = new SaEmailAttachmentData("image", imageResourceName) + { + Filename = imageFilename, + ContentId = imageContentId, + Data = imageData, + Inline = true + }; + emailData.Attachments.Add(attachment); + } + String content = "cid:" + imageContentId; + emailData.MessageBody = Regex.Replace(emailData.MessageBody, Regex.Escape(tag), content, RegexOptions.IgnoreCase); + } + } + + //------------------------------------------------------------------- + public static String FormatAttachmentContentId(String attachmentName) + { + // Inline content Ids should be formatted similar to an email address (user@domain). + // Replace '@' and whitespace with underscores and add domain. + String contentId = Regex.Replace(Regex.Replace(Regex.Replace(attachmentName, "@", "_"), "\\s+", "_"), "_+", "_") + "@saxonite.com"; + return contentId; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Email/SaEmailData.cs b/SacsStandardBase/Base/Email/SaEmailData.cs new file mode 100644 index 0000000..8d91fb4 --- /dev/null +++ b/SacsStandardBase/Base/Email/SaEmailData.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.Email +{ + //======================================================================= + public class SaEmailData + { + //------------------------------------------------------------------- + // Properties. + public SaEmailConfig Config { get; set; } + public List AddressesTo { get; } = new List(); + public List AddressesCc { get; } = new List(); + public List AddressesBcc { get; } = new List(); + public String Subject { get; set; } + public String MessageBody { get; set; } + public bool HtmlBody { get; set; } + public List Attachments { get; } = new List(); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Helpers/SacsContentTypeHelper.cs b/SacsStandardBase/Base/Helpers/SacsContentTypeHelper.cs new file mode 100644 index 0000000..4379489 --- /dev/null +++ b/SacsStandardBase/Base/Helpers/SacsContentTypeHelper.cs @@ -0,0 +1,239 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.StaticFiles; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +namespace Sarand.Net.Standard.Base.Helpers +{ + //========================================================================== + public static class SacsContentTypeHelper + { + //------------------------------------------------------------------- + // Definition - Content Type Data. + public class ContentTypeData + { + protected ContentTypeData(List extensions, String caption, String contentType, Sacs.MimeType mime) + { + ContentType = contentType; + Caption = caption; + Extensions.AddRange(extensions); + Mime = mime; + } + public String PreferredContentType { get; protected set; } + public String ContentType { get; } + public String Caption { get; } + public List Extensions { get; } = new List(); + public Sacs.MimeType Mime { get; } + } + private class ContentTypeData_Internal : ContentTypeData + { + public ContentTypeData_Internal(String extension, String caption, String contentType, Sacs.MimeType mime = Sacs.MimeType.Unknown) : + base(StringListUtil.Split(extension, ",", true, true, true), caption, contentType, mime) + { } + public new String PreferredContentType { set { base.PreferredContentType = value; } } + } + + //------------------------------------------------------------------- + // Data members. + private static readonly List contentTypes; + private static FileExtensionContentTypeProvider provider = null; + private static readonly HashSet allowedMimeTypes_CandidateCV = new HashSet() + { + Sacs.MimeType.MsDoc, + Sacs.MimeType.MsDocx, + Sacs.MimeType.OpenDocText, + Sacs.MimeType.Pdf, + Sacs.MimeType.Rtf + }; + + //------------------------------------------------------------------- + // Static initialisation. + static SacsContentTypeHelper() + { + // A few useful content types for reference. + contentTypes = new List() + { + // Data. + new ContentTypeData_Internal("txt", "Text, (generally ASCII or ISO 8859-n)", "text/plain", Sacs.MimeType.Text), + new ContentTypeData_Internal("csv", "Comma separated variable", "text/csv", Sacs.MimeType.Csv), + new ContentTypeData_Internal("xml", "XML (not readable from casual users)", "application/xml", Sacs.MimeType.Xml) { PreferredContentType = "text/xml" }, + new ContentTypeData_Internal("xml", "XML (readable from casual users)", "text/xml", Sacs.MimeType.Xml), + new ContentTypeData_Internal("zip", "ZIP archive", "application/x-zip-compressed", Sacs.MimeType.Zip), + new ContentTypeData_Internal("zip", "ZIP archive", "application/zip", Sacs.MimeType.Zip) { PreferredContentType = "application/x-zip-compressed" }, + new ContentTypeData_Internal("bin", "Generic binary data", "application/octet-stream", Sacs.MimeType.Bin), + // Web. + new ContentTypeData_Internal("html,htm", "HyperText Markup Language (HTML)", "text/html", Sacs.MimeType.Html), + new ContentTypeData_Internal("css", "Cascading Style Sheets (CSS)", "text/css", Sacs.MimeType.Css), + new ContentTypeData_Internal("js", "JavaScript", "application/javascript", Sacs.MimeType.Javascript), + new ContentTypeData_Internal("js", "JavaScript", "text/javascript", Sacs.MimeType.Javascript) { PreferredContentType = "application/javascript" }, + new ContentTypeData_Internal("json", "JSON format", "application/json", Sacs.MimeType.Json), + // Images. + new ContentTypeData_Internal("png", "Portable Network Graphics", "image/png", Sacs.MimeType.Png), + new ContentTypeData_Internal("jpg,jpeg", "JPEG images", "image/jpeg", Sacs.MimeType.Jpeg), + new ContentTypeData_Internal("bmp", "Windows OS/2 Bitmap Graphics", "image/bmp", Sacs.MimeType.Bmp), + new ContentTypeData_Internal("gif", "Graphics Interchange Format (GIF)", "image/gif", Sacs.MimeType.Gif), + new ContentTypeData_Internal("tif,tiff", "Tagged Image File Format (TIFF)", "image/tiff", Sacs.MimeType.Tif), + // Documents. + new ContentTypeData_Internal("pdf", "Adobe Portable Document Format (PDF)", "application/pdf", Sacs.MimeType.Pdf), + // Text documents. + new ContentTypeData_Internal("rtf", "Rich text format file", "application/rtf", Sacs.MimeType.Rtf), + new ContentTypeData_Internal("me", "ReadMe text file", "application/x-troff-me"), + new ContentTypeData_Internal("doc", "Microsoft Word", "application/msword", Sacs.MimeType.MsDoc), + new ContentTypeData_Internal("docx", "Microsoft Word (OpenXML)", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", Sacs.MimeType.MsDocx), + new ContentTypeData_Internal("odt", "OpenDocument text document", "application/vnd.oasis.opendocument.text", Sacs.MimeType.OpenDocText), + // Spreadsheets. + new ContentTypeData_Internal("xls", "Microsoft Excel", "application/vnd.ms-excel", Sacs.MimeType.MsXls), + new ContentTypeData_Internal("xlsx", "Microsoft Excel (OpenXML)", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", Sacs.MimeType.MsXlsx), + new ContentTypeData_Internal("ods", "OpenDocument spreadsheet document", "application/vnd.oasis.opendocument.spreadsheet", Sacs.MimeType.OpenDocSpreadsheet), + // Miscellaneous. + new ContentTypeData_Internal("aac", "AAC audio", "audio/aac"), + new ContentTypeData_Internal("abw", "AbiWord document", "application/x-abiword"), + new ContentTypeData_Internal("arc", "Archive document (multiple files embedded)", "application/x-freearc"), + new ContentTypeData_Internal("avi", "AVI: Audio Video Interleave", "video/x-msvideo"), + new ContentTypeData_Internal("azw", "Amazon Kindle eBook format", "application/vnd.amazon.ebook"), + new ContentTypeData_Internal("bz", "BZip archive", "application/x-bzip"), + new ContentTypeData_Internal("bz2", "BZip2 archive", "application/x-bzip2"), + new ContentTypeData_Internal("csh", "C-Shell script", "application/x-csh"), + new ContentTypeData_Internal("csv", "Comma-separated values (CSV)", "text/csv"), + new ContentTypeData_Internal("eot", "MS Embedded OpenType fonts", "application/vnd.ms-fontobject"), + new ContentTypeData_Internal("epub", "Electronic publication (EPUB)", "application/epub+zip"), + new ContentTypeData_Internal("gz", "GZip Compressed Archive", "application/x-gzip"), + new ContentTypeData_Internal("gz", "GZip Compressed Archive", "application/gzip") { PreferredContentType = "application/x-gzip" }, + new ContentTypeData_Internal("ico", "Icon format", "image/x-icon"), + new ContentTypeData_Internal("ico", "Icon format", "image/vnd.microsoft.icon") { PreferredContentType = "image/x-icon" }, + new ContentTypeData_Internal("ics", "iCalendar format", "text/calendar"), + new ContentTypeData_Internal("jar", "Java Archive (JAR)", "application/java-archive"), + new ContentTypeData_Internal("jsonld", "JSON-LD format", "application/ld+json"), + new ContentTypeData_Internal("midi,mid", "Musical Instrument Digital Interface (MIDI)", "audio/mid"), + new ContentTypeData_Internal("midi,mid", "Musical Instrument Digital Interface (MIDI)", "audio/midi") { PreferredContentType = "audio/mid" }, + new ContentTypeData_Internal("midi,mid", "Musical Instrument Digital Interface (MIDI)", "audio/x-midi") { PreferredContentType = "audio/mid" }, + new ContentTypeData_Internal("mjs", "JavaScript module", "text/javascript"), + new ContentTypeData_Internal("mp3", "MP3 audio", "audio/mpeg"), + new ContentTypeData_Internal("mpeg", "MPEG Video", "video/mpeg"), + new ContentTypeData_Internal("mpkg", "Apple Installer Package", "application/vnd.apple.installer+xml"), + new ContentTypeData_Internal("odp", "OpenDocument presentation document", "application/vnd.oasis.opendocument.presentation"), + new ContentTypeData_Internal("oga", "OGG audio", "audio/ogg"), + new ContentTypeData_Internal("ogv", "OGG video", "video/ogg"), + new ContentTypeData_Internal("ogx", "OGG", "application/ogg"), + new ContentTypeData_Internal("opus", "Opus audio", "audio/opus"), + new ContentTypeData_Internal("otf", "OpenType font", "font/otf"), + new ContentTypeData_Internal("php", "Hypertext Preprocessor (Personal Home Page)", "application/x-httpd-php"), + new ContentTypeData_Internal("ppt", "Microsoft PowerPoint", "application/vnd.ms-powerpoint"), + new ContentTypeData_Internal("pptx", "Microsoft PowerPoint (OpenXML)", "application/vnd.openxmlformats-officedocument.presentationml.presentation"), + new ContentTypeData_Internal("rar", "RAR archive", "application/vnd.rar"), + new ContentTypeData_Internal("rtf", "Rich Text Format (RTF)", "application/rtf"), + new ContentTypeData_Internal("sh", "Bourne shell script", "application/x-sh"), + new ContentTypeData_Internal("svg", "Scalable Vector Graphics (SVG)", "image/svg+xml"), + new ContentTypeData_Internal("swf", "Small web format (SWF) or Adobe Flash document", "application/x-shockwave-flash"), + new ContentTypeData_Internal("tar", "Tape Archive (TAR)", "application/x-tar"), + new ContentTypeData_Internal("ts", "MPEG transport stream", "video/vnd.dlna.mpeg-tts"), + new ContentTypeData_Internal("ts", "MPEG transport stream", "video/mp2t") { PreferredContentType = "video/vnd.dlna.mpeg-tts" }, + new ContentTypeData_Internal("ttf", "TrueType Font", "application/x-font-ttf"), + new ContentTypeData_Internal("ttf", "TrueType Font", "font/ttf") { PreferredContentType = "application/x-font-ttf" }, + new ContentTypeData_Internal("vsd", "Microsoft Visio", "application/vnd.visio"), + new ContentTypeData_Internal("wav", "Waveform Audio Format", "audio/wav"), + new ContentTypeData_Internal("webm", "WEBM video", "video/webm"), + new ContentTypeData_Internal("weba", "WEBM audio", "audio/webm"), + new ContentTypeData_Internal("webp", "WEBP image", "image/webp"), + new ContentTypeData_Internal("woff", "Web Open Font Format (WOFF)", "application/font-woff"), + new ContentTypeData_Internal("woff", "Web Open Font Format (WOFF)", "font/woff") { PreferredContentType = "application/font-woff" }, + new ContentTypeData_Internal("woff2", "Web Open Font Format (WOFF)", "font/woff2"), + new ContentTypeData_Internal("xhtml", "XHTML", "application/xhtml+xml"), + new ContentTypeData_Internal("xul", "XUL", "application/vnd.mozilla.xul+xml"), + new ContentTypeData_Internal("3gp", "3GPP video container", "video/3gpp"), + new ContentTypeData_Internal("3gp", "3GPP audio container", "audio/3gpp") { PreferredContentType = "video/3gpp" }, + new ContentTypeData_Internal("3g2", "3GPP2 video container", "video/3gpp2"), + new ContentTypeData_Internal("3g2", "3GPP2 audio container", "audio/3gpp2") { PreferredContentType = "video/3gpp2" }, + new ContentTypeData_Internal("7z", "7-zip archive", "application/x-7z-compressed") + }; + } + + //------------------------------------------------------------------- + // Properties. + /// Generic content type used for 'unknown' document types. + public static String ContentTypeDefault { get; } = "application/octet-stream"; + /// Selection of comon content types. + public static List ContentTypes { get { return new List(contentTypes); } } + public static FileExtensionContentTypeProvider Provider + { + get + { + if (provider == null) + provider = new FileExtensionContentTypeProvider(); + return provider; + } + } + + //------------------------------------------------------------------- + /// Generic content types that might have a better preferred content type when converting, eg. from extension. + private static readonly List genericContentTypes = new List() { "application/octet-stream", "application/oleobject" }; + /// + /// Given a file path, determine the MIME type. + /// Ref. NuGet package Microsoft.AspNetCore.StaticFiles + /// + public static String GetContentTypeFromFilename(this String fileName) + { + string contentType = ""; + if (!Provider.TryGetContentType(fileName, out contentType) || genericContentTypes.ContainsCI(contentType)) + { + // Locate suitable 'common' content type definition. + String extension = Regex.Replace(Path.GetExtension(fileName), "^\\.", ""); + // Use the 'primary' definition for the extension, with no 'preferred' content type. + ContentTypeData contentTypeData = contentTypes.Find(item => item.Extensions.ContainsCI(extension) && StringUtil.IsEmpty(item.PreferredContentType)); + if (contentTypeData != null) + contentType = contentTypeData.ContentType; + } + if (StringUtil.IsEmpty(contentType)) + contentType = ContentTypeDefault; + return contentType; + } + + //---------------------------------------------------------------------- + /// + /// Interpret the content type for the specified file. + /// - The file's content type if not generic (application/octet-stream, application/oleobject). + /// - The content type derived from the file's extension if available + /// - Generic content type: application/octet-stream. + /// + public static String GetContentTypeFromFormFile(String contentType, String fileName) + { + // Return file's content type if specific. + if (!StringUtil.IsEmpty(contentType) && !genericContentTypes.ContainsCI(contentType)) + return contentType; + // Otherwise, interpret filename's content type. + if (!StringUtil.IsEmpty(fileName)) + return SacsContentTypeHelper.GetContentTypeFromFilename(fileName); + return ContentTypeDefault; + } + + /// + /// Interpret the content type for the specified file. + /// - The file's content type if not generic (application/octet-stream, application/oleobject). + /// - The content type derived from the file's extension if available + /// - Generic content type: application/octet-stream. + /// + public static String GetContentTypeFromFormFile(this IFormFile formFile) + { + return GetContentTypeFromFormFile(formFile.ContentType, formFile.FileName); + } + + //---------------------------------------------------------------------- + /// Convert the content type to enumerated Mime. + public static Sacs.MimeType GetMimeTypeFromContentType(this String contentType) + { + if (StringUtil.IsEmpty(contentType)) return Sacs.MimeType.Unknown; + ContentTypeData contentTypeData = contentTypes.Find(item => item.ContentType.EqualsCI(contentType)); + if (contentTypeData != null) + return contentTypeData.Mime; + return Sacs.MimeType.Unknown; + } + + //---------------------------------------------------------------------- + } + //========================================================================== +} diff --git a/SacsStandardBase/Base/Helpers/SacsCsvHelper.cs b/SacsStandardBase/Base/Helpers/SacsCsvHelper.cs new file mode 100644 index 0000000..7e1b6e3 --- /dev/null +++ b/SacsStandardBase/Base/Helpers/SacsCsvHelper.cs @@ -0,0 +1,266 @@ +using CsvHelper; +using CsvHelper.Configuration; +using Sarand.Net.Standard.Base.Core; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + +namespace Sarand.Net.Standard.Base.Helpers +{ + //======================================================================= + public static class SacsCsvHelper + { + //------------------------------------------------------------------- + // Definitions. + /// + /// Encapsulate CSV Reader and the text object that it is to parse. + /// + private class ReaderClass : SacsDisposable + { + public ReaderClass() : this(DefaultCulture) { } + public ReaderClass(CultureInfo culture) + { + Reader = new StringReader(""); + CsvReader = new CsvReader(Reader, culture ?? DefaultCulture); + } + protected override void Dispose(bool disposing) + { + if (disposing) + { + CsvReader.Dispose(); + Reader.Dispose(); + } + } + public StringReader Reader { get; } + public CsvReader CsvReader { get; } + } + /// + /// Encapsulate CSV Writer and the text stream that it is to generate. + /// + private class WriterClass : SacsDisposable + { + // Construction. + public WriterClass() : this(DefaultCulture) { } + public WriterClass(CultureInfo culture) + { + Writer = new StringWriter() { NewLine = "" }; + CsvWriter = new CsvWriter(Writer, culture ?? DefaultCulture); + } + protected override void Dispose(bool disposing) + { + if (disposing) + { + CsvWriter.Dispose(); + Writer.Dispose(); + } + } + // Properties. + public StringWriter Writer { get; } + public CsvWriter CsvWriter { get; } + // To CSV. + public String ToCsv(IEnumerable values) + { + foreach (String field in values) + CsvWriter.WriteField(field); + CsvWriter.NextRecord(); + // NB Shouldn't need to strip line delimiters as the writer's NewLine is blank but CsvHelper v16 still adds the delimiter. + String record = Writer.GetStringBuilder().ToString().Replace("\r", "").Replace("\n", ""); + // Clear the String Writer data. [Ref. https://stackoverflow.com/questions/12839982/clear-all-content-in-xmltextwriter-and-stringwriter] + Writer.GetStringBuilder().Clear(); + return record; + } + } + + //------------------------------------------------------------------- + // Properties. + public static CultureInfo DefaultCulture { get; set; } = CultureInfo.InvariantCulture; + + //------------------------------------------------------------------- + // C o n f i g u r a t i o n D e f a u l t s + //------------------------------------------------------------------- + // AllowComments false bool + // BadDataFound {Method = {Void BadDataFound(CsvHelper.ReadingContext)}} System.Action + // BufferSize 2048 int + // Comment 35 '#' char + // CountBytes false bool + // CultureInfo {en-GB} System.Globalization.CultureInfo + // Delimiter "," string + // DetectColumnCountChanges false bool + // DoubleQuoteString "\"\"" string + // DynamicPropertySort null System.Collections.Generic.IComparer + // Encoding {System.Text.UTF8Encoding} System.Text.Encoding {System.Text.UTF8Encoding} + // Escape 34 '"' char + // GetConstructor {Method = {System.Reflection.ConstructorInfo GetConstructor(System.Type)}} System.Func + // HasHeaderRecord true bool + // HeaderValidated {Method = {Void HeaderValidated(Boolean, System.String[], Int32, CsvHelper.ReadingContext)}} System.Action + // IgnoreBlankLines true bool + // IgnoreQuotes false bool + // IgnoreReferences false bool + // IncludePrivateMembers false bool + // InjectionCharacters {char[4]} char[] + // InjectionEscapeCharacter 9 '\t' char + // LineBreakInQuotedFieldIsBadData false bool + // Maps {CsvHelper.Configuration.ClassMapCollection} CsvHelper.Configuration.ClassMapCollection + // MemberTypes Properties CsvHelper.Configuration.MemberTypes + // MissingFieldFound {Method = {Void MissingFieldFound(System.String[], Int32, CsvHelper.ReadingContext)}} System.Action + // PrepareHeaderForMatch {Method = {System.String PrepareHeaderForMatch(System.String, Int32)}} System.Func + // Quote 34 '"' char + // QuoteString "\"" string + // ReadingExceptionOccurred {Method = {Boolean ReadingExceptionOccurred(CsvHelper.CsvHelperException)}} System.Func + // ReferenceHeaderPrefix null System.Func + // SanitizeForInjection false bool + // ShouldQuote {Method = {Boolean ShouldQuote(System.String, CsvHelper.WritingContext)}} System.Func + // ShouldSkipRecord {Method = {Boolean ShouldSkipRecord(System.String[])}} System.Func + // ShouldUseConstructorParameters {Method = {Boolean ShouldUseConstructorParameters(System.Type)}} System.Func + // TrimOptions None CsvHelper.Configuration.TrimOptions + // TypeConverterCache {CsvHelper.TypeConversion.TypeConverterCache} CsvHelper.TypeConversion.TypeConverterCache + // TypeConverterOptionsCache {CsvHelper.TypeConversion.TypeConverterOptionsCache} CsvHelper.TypeConversion.TypeConverterOptionsCache + // UseNewObjectForNullReferenceMembers true bool + //------------------------------------------------------------------- + public class ParserConfig /* IParserConfiguration */ + { + /// Culture + public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; + /// Gets or sets the size of the buffer used for reading CSV files. Default: 2048. + public int BufferSize { get; set; } = 2048; + /// + /// Gets or sets a value indicating whether the number of bytes should be counted while parsing. + /// This will slow down parsing because it needs to get the byte count of every char for the given encoding. + /// The CsvHelper.Configuration.IParserConfiguration.Encoding needs to be set correctly for this to be accurate. + /// Default: false. + /// + public bool CountBytes { get; set; } = false; + /// Gets or sets the encoding used when counting bytes. + public Encoding Encoding { get; set; } = Encoding.UTF8; + /// + /// Gets or sets the function that is called when bad field data is found. + /// A field has bad data if it contains a quote and the field is not quoted (escaped). + /// You can supply your own function to do other things like logging the issue instead of throwing an exception. + /// Arguments: context + /// + public Action BadDataFound { get; set; } + /// + /// Gets or sets a value indicating if a line break found in a quote field should be considered bad data. + /// True to consider a line break bad data, otherwise false. + /// Defaults to false. + /// + public bool LineBreakInQuotedFieldIsBadData { get; set; } = false; + /// + /// Gets or sets the character used to denote a line that is commented out. + /// Default: '#'. + /// + public char Comment { get; set; } = '#'; + /// + /// Gets or sets a value indicating if comments are allowed. + /// True to allow commented out lines, otherwise false (default). + public bool AllowComments { get; set; } = false; + /// + /// Gets or sets a value indicating if blank lines should be ignored when reading. + /// True to ignore, otherwise false. + /// Default: true. + /// + public bool IgnoreBlankLines { get; set; } = true; + /// Gets or sets a value indicating if quotes should be ingored when parsing and treated like any other character. + public bool IgnoreQuotes { get; set; } = false; + /// + /// Gets or sets the character used to quote fields. + /// Default: '"'. + /// + public char Quote { get; set; } = '"'; + /// + /// Gets or sets the delimiter used to separate fields. + /// Default: CultureInfo.CurrentCulture.TextInfo.ListSeparator (','). + /// + public string Delimiter { get; set; } + /// + /// Gets or sets the escape character used to escape a quote inside a field. + /// Default: '"'. + /// + public char Escape { get; set; } = '"'; + /// + /// Gets or sets the field trimming options. + /// Default: None + /// + public TrimOptions TrimOptions { get; set; } = TrimOptions.None; + } + + //------------------------------------------------------------------- + // C o n v e r t T o C S V + //------------------------------------------------------------------- + public static List ParseCsv(String data, ParserConfig parserConfig = null) + { + List records = new List(); + + using (TextReader reader = new StringReader(data)) + using (CsvParser csvParser = new CsvParser(reader, parserConfig?.Culture ?? CultureInfo.InvariantCulture)) + { + if (parserConfig != null) + { + if (parserConfig.BufferSize > 0) + csvParser.Configuration.BufferSize = parserConfig.BufferSize; + csvParser.Configuration.CountBytes = parserConfig.CountBytes; + csvParser.Configuration.Encoding = parserConfig.Encoding; + if (parserConfig.BadDataFound != null) + csvParser.Configuration.BadDataFound = parserConfig.BadDataFound; + csvParser.Configuration.LineBreakInQuotedFieldIsBadData = parserConfig.LineBreakInQuotedFieldIsBadData; + csvParser.Configuration.Comment = parserConfig.Comment; + csvParser.Configuration.AllowComments = parserConfig.AllowComments; + csvParser.Configuration.IgnoreBlankLines = parserConfig.IgnoreBlankLines; + csvParser.Configuration.IgnoreQuotes = parserConfig.IgnoreQuotes; + csvParser.Configuration.Quote = parserConfig.Quote; + if (parserConfig.Delimiter != null) + csvParser.Configuration.Delimiter = parserConfig.Delimiter; + csvParser.Configuration.Escape = parserConfig.Escape; + csvParser.Configuration.TrimOptions = parserConfig.TrimOptions; + } + while (true) + { + String[] record = csvParser.Read(); + if (record == null) break; + records.Add(record); + } + } + + return records; + } + + //------------------------------------------------------------------- + // C o n v e r t T o C S V + //------------------------------------------------------------------- + // Single record. + public static String ToCsv(IEnumerable values) + { + return ToCsv(values, DefaultCulture); + } + public static String ToCsv(IEnumerable values, CultureInfo culture) + { + String record = ""; + using (WriterClass writer = new WriterClass(culture ?? DefaultCulture)) + { + record = writer.ToCsv(values); + } + return record; + } + //------------------------------------------------------------------- + // Multiple records. + public static List ToCsv(IEnumerable> data) + { + return ToCsv(data, DefaultCulture); + } + public static List ToCsv(IEnumerable> data, CultureInfo culture) + { + List records = new List(); + using (WriterClass writer = new WriterClass(culture ?? DefaultCulture)) + { + foreach (String[] values in data) + records.Add(writer.ToCsv(values)); + } + return records; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Helpers/SacsDataValidationHelper.cs b/SacsStandardBase/Base/Helpers/SacsDataValidationHelper.cs new file mode 100644 index 0000000..8e83458 --- /dev/null +++ b/SacsStandardBase/Base/Helpers/SacsDataValidationHelper.cs @@ -0,0 +1,87 @@ +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text; +using System.Text.RegularExpressions; + +namespace Sarand.Net.Standard.Base.Helpers +{ + //======================================================================= + public static class SacsDataValidationHelper + { + //------------------------------------------------------------------- + /// + /// Validate the email address. + /// Ref. https://stackoverflow.com/questions/1365407/c-sharp-code-to-validate-email-address + /// + public static bool IsValidEmailAddress(String value) + { + if (StringUtil.IsEmpty(value)) return false; + if (!new EmailAddressAttribute().IsValid(value)) return false; + + // Exclude spaces. + if (Regex.Matches(value, " ").Count > 0) return false; + + // Exclude consecutive dots. + if (Regex.Matches(value, "\\.\\.").Count > 0) return false; + + // Check for at least one dot after @ [to ensure john@smith is invalid]. + if (Regex.Matches(value, "@[^@.]+\\.").Count < 1) return false; + + return true; + } + + //------------------------------------------------------------------- + /// + /// Validate the phone number. + /// Ref. https://www.twilio.com/blog/validating-phone-numbers-effectively-with-c-and-the-net-frameworks + /// + public static bool IsValidPhone(String value) + { + if (StringUtil.IsEmpty(value)) return false; + if (!new PhoneAttribute().IsValid(value)) return false; + + // Apply minimum length. + if (StringUtil.GetLength(value) < 8) return false; + + return true; + } + + //------------------------------------------------------------------- + // Tax identification number. + // European country Tax Identification Numbers: + // https://ec.europa.eu/taxation_customs/vies/faqvies.do#item_11 + //------------------------------------------------------------------- + /// + /// Validate the tax identification number. + /// + public static bool IsValidTaxIdentificationNumber(String countryCode, String value) + { + if (StringUtil.IsEmpty(value)) return false; + + //--------------------------------------------------------------- + // GB, United Kingdom. + // Standard: + // GB999 9999 99 block of 3 digits, block of 4 digits and block of 2 digits [00-96] + // Branch: + // GB999 9999 99 9995 as above followed by block of 3 digits + // Government departments: + // GBGD9996 'GD' followed by block of 3 or 4 digits + // Health Authoritites: + // GBHA9997 'HA' followed by block of 3 or 4 digits + //--------------------------------------------------------------- + // Apply minimum length. + if (StringUtil.GetLength(value) < 8) return false; + + // Country prefix. + if (!value.StartsWith("GB")) return false; + + return true; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Helpers/SacsEncryptionHelper.cs b/SacsStandardBase/Base/Helpers/SacsEncryptionHelper.cs new file mode 100644 index 0000000..d0f124c --- /dev/null +++ b/SacsStandardBase/Base/Helpers/SacsEncryptionHelper.cs @@ -0,0 +1,128 @@ +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace Sarand.Net.Standard.Base.Helpers +{ + //======================================================================= + public static class SacsEncryptionHelper + { + //------------------------------------------------------------------- + /// Number of byes in key. [32 bytes = 256 bits] + public static int KeyLength { get; } = 32; + /// Default encoding: UTF8. + public static Encoding DefaultEncoding { get; } = Encoding.UTF8; + + //------------------------------------------------------------------- + // Symmetric Encryption in .NET Core + // System.Security.Cryptography implements and provides the APIs for encryption in .NET Core. + // Ref. https://damienbod.com/2020/08/19/symmetric-and-asymmetric-encryption-in-net-core/ + //------------------------------------------------------------------- + public static bool IsBase64(String value, int expectedLength = 0) + { + try + { + Byte[] decoded = Convert.FromBase64String(value); + if (expectedLength > 0 && decoded.Length != expectedLength) + return false; + } + catch (Exception) { return false; } + return true; + } + public static String EncodePlainKey(String plainKey, int keyLength = 32, Encoding encoding = null) + { + if (StringUtil.IsEmpty(plainKey)) + throw new NotSupportedException("Plain key must not be null/blank"); + // Ensure key if 32 bytes long. + while (StringUtil.GetLength(plainKey) < keyLength) + plainKey += plainKey; + plainKey = StringUtil.Left(plainKey, keyLength); + + // Encode to base-64. + encoding = encoding ?? DefaultEncoding; + return Convert.ToBase64String(encoding.GetBytes(plainKey)); + } + + //------------------------------------------------------------------- + // Generate random key and IV. + // NB Can use own key if suitable - 32 character value in base-64 format. + public static String InitSymmetricEncryptionKey() + { + var key = GetEncodedRandomString(KeyLength); // 256 bits. + return key; + } + public static string InitSymmetricEncryptionIV(String key) + { + Aes cipher = CreateCipher(key); + var IVBase64 = Convert.ToBase64String(cipher.IV); + return IVBase64; + } + public static (string Key, string IVBase64) InitSymmetricEncryptionKeyIV() + { + var key = InitSymmetricEncryptionKey(); + var IVBase64 = InitSymmetricEncryptionIV(key); + return (key, IVBase64); + } + + //------------------------------------------------------------------- + public static string GetEncodedRandomString(int length) + { + var base64 = Convert.ToBase64String(GenerateRandomBytes(length)); + return base64; + } + + private static Aes CreateCipher(string keyBase64) + { + // Default values: Keysize 256 bits, Padding PKC27. + Aes cipher = Aes.Create(); + // Ensure the integrity of the ciphertext if using CBC + cipher.Mode = CipherMode.CBC; + + cipher.Padding = PaddingMode.ISO10126; + byte[] rawKey = Convert.FromBase64String(keyBase64); + String rawKeyValue = System.Text.Encoding.Default.GetString(rawKey); + cipher.Key = rawKey; + + return cipher; + } + + private static byte[] GenerateRandomBytes(int length) + { + var byteArray = new byte[length]; + SacsRandomHelper.Fill(byteArray); + return byteArray; + } + + //------------------------------------------------------------------- + public static string Encrypt(string text, string IV, string key) + { + Aes cipher = CreateCipher(key); + cipher.IV = Convert.FromBase64String(IV); + + ICryptoTransform cryptTransform = cipher.CreateEncryptor(); + byte[] plaintext = Encoding.UTF8.GetBytes(text); + byte[] cipherText = cryptTransform.TransformFinalBlock(plaintext, 0, plaintext.Length); + + return Convert.ToBase64String(cipherText); + } + + //------------------------------------------------------------------- + public static string Decrypt(string encryptedText, string IV, string key) + { + Aes cipher = CreateCipher(key); + cipher.IV = Convert.FromBase64String(IV); + + ICryptoTransform cryptTransform = cipher.CreateDecryptor(); + byte[] encryptedBytes = Convert.FromBase64String(encryptedText); + byte[] plainBytes = cryptTransform.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length); + + return Encoding.UTF8.GetString(plainBytes); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Helpers/SacsHttpClientHelper.cs b/SacsStandardBase/Base/Helpers/SacsHttpClientHelper.cs new file mode 100644 index 0000000..9c43106 --- /dev/null +++ b/SacsStandardBase/Base/Helpers/SacsHttpClientHelper.cs @@ -0,0 +1,253 @@ +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Tasks; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Sarand.Net.Standard.Base.Helpers +{ + //======================================================================= + public static class SacsHttpClientHelper + { + //------------------------------------------------------------------- + /// Request configuration + public class RequestData : SacsTaskData + { + // Construction. + public RequestData(CancellationToken? cancellationToken) : base(cancellationToken) { } + /// URL to send request to. + public UriBuilder UriBuilder { get; } = new UriBuilder(); + /// + /// Request timeout. Default: 5 secs. + /// NB Not currently supported as can only be changed when static HTTP Client not in use. + /// + public int TimeoutSecs { get; set; } = RequestTimeoutSecs_Default; + /// + /// Headers to send with request. + /// Accept, Accept-Charset, Accept-Encoding, Accept-Language, User-Agent, Transfer-Encoding, Pragma, Expect etc. + /// + public Dictionary> RequestHeaders { get; } = new Dictionary>(); + /// + /// Content Headers to send with request. + /// Content-Length (bytes), Content-Type (media type), Content-Encoding (compression algorithm), Content-Language etc. + /// Content-Type header is defaulted for POST request if not specified. + /// + public Dictionary> ContentHeaders { get; } = new Dictionary>(); + /// + /// Form values to post as 'URL Encoded'. + /// Content-Type defaults to 'application/x-www-form-urlencoded; charset=utf-8'. + /// + public List> FormData { get; } = new List>(); + /// + /// Data to POST. Ignored if FormData populated. + /// Content-Type defaults to 'text/plain; charset=utf-8'. + /// + public String PostData { get; set; } + /// Use POST request rather than GET even if no data to post. + public bool ForcePost { get; set; } + } + + //------------------------------------------------------------------- + /// Response data + public class ResponseData + { + public DateTime RequestStarted { get; set; } + public DateTime RequestCompleted { get; set; } + /// Response headers. + public Dictionary> ResponseHeaders { get; } = new Dictionary>(); + /// Response content headers. + public Dictionary> ContentHeaders { get; } = new Dictionary>(); + /// Indicates whether Status Code represents success. + public bool Success { get; set; } + /// HTTP Status Code. + public HttpStatusCode StatusCode { get; set; } + /// HTTP message version. Default: 1.1 + public Version Version { get; set; } + /// HTTP Reason Code. + public String ReasonPhrase { get; set; } = ""; + /// Response data. + public String Data { get; set; } = ""; + /// Elapsed time for request. + public TimeSpan Elapsed { get; set; } + /// Exception thrown during handling of request. + public Exception Exception { get; set; } + /// Error state message - generally exception type and message. + public String ErrorStateMessage { get; set; } + } + + //------------------------------------------------------------------- + // Data members. + private static readonly HttpClient httpClient; + + //------------------------------------------------------------------- + // Static initialisation. + static SacsHttpClientHelper () + { + // Configure client. + httpClient = new HttpClient(); + httpClient.Timeout = new TimeSpan(0, 0, RequestTimeoutSecs_Default); + } + + //------------------------------------------------------------------- + // Properties. + public static HttpClient HttpClient { get { return httpClient; } } + public static int RequestTimeoutSecs_Default { get; } = 5; + public static String HeaderName_ContentType { get; } = "Content-Type"; + + //------------------------------------------------------------------- + public static async Task DoRequest (RequestData requestData) + { + String fn = "SacsHttpClientHelper.DoRequest"; + requestData.ThrowIfCancellationRequested(); + ResponseData responseData = new ResponseData(); + + // Construct content to post. + HttpContent postContent = null; + if (requestData.FormData.Count > 0) + { + postContent = new FormUrlEncodedContent(requestData.FormData); + if (!requestData.ContentHeaders.ContainsKey(HeaderName_ContentType)) + requestData.ContentHeaders.Add(HeaderName_ContentType, "application/x-www-form-urlencoded; charset=utf-8"); + } + else if (!StringUtil.IsEmpty(requestData.PostData) || requestData.ForcePost) + { + postContent = new StringContent(StringUtil.EmptyOnNull(requestData.PostData), Encoding.UTF8); + if (!requestData.ContentHeaders.ContainsKey(HeaderName_ContentType)) + requestData.ContentHeaders.Add(HeaderName_ContentType, "text/plain; charset=utf-8"); + } + requestData.ThrowIfCancellationRequested(); + + // Make request. + DateTime startedAt = DateTime.Now; + try + { + // Request. + using (HttpRequestMessage request = new HttpRequestMessage()) + { + request.RequestUri = requestData.UriBuilder.Uri; + + // Request headers. + foreach (String headerName in requestData.RequestHeaders.Keys) + { + if (request.Headers.Contains(headerName)) + request.Headers.Remove(headerName); + request.Headers.Add(headerName, requestData.RequestHeaders[headerName]); + } + + // Default request headers. + if (HttpClient.DefaultRequestHeaders.Accept.Count == 0 && request.Headers.Accept.Count == 0) + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/*")); + + // Configure request. + HttpResponseMessage response = null; + // Send request. + if (postContent != null) + { + // POST. + request.Method = HttpMethod.Post; + request.Content = postContent; + // Content headers. + foreach (String headerName in requestData.ContentHeaders.Keys) + { + if (request.Content.Headers.Contains(headerName)) + request.Content.Headers.Remove(headerName); + request.Content.Headers.Add(headerName, requestData.ContentHeaders[headerName]); + } + // Send request. + responseData.RequestStarted = DateTime.Now; + response = await HttpClient.SendAsync(request, requestData.CancellationToken); + responseData.RequestCompleted = DateTime.Now; + } + else + { + // GET. + request.Method = HttpMethod.Get; + responseData.RequestStarted = DateTime.Now; + response = await HttpClient.SendAsync(request, requestData.CancellationToken); + responseData.RequestCompleted = DateTime.Now; + } + + // Extract response data. + responseData.Version = response.Version; + responseData.Success = response.IsSuccessStatusCode; + responseData.StatusCode = response.StatusCode; + responseData.ReasonPhrase = response.ReasonPhrase; + if (response.Headers != null) + { + foreach (var header in response.Headers) + responseData.ResponseHeaders[header.Key] = new List(header.Value); + } + if (response.Content != null && response.Content.Headers != null) + { + foreach (var header in response.Content.Headers) + responseData.ContentHeaders[header.Key] = new List(header.Value); + } + responseData.Data = await response.Content.ReadAsStringAsync(); + if (response != null) response.Dispose(); + } + } + catch (Exception exc) + { + // Ignore if task has been cancelled. + if (requestData.IsCancellationRequested) throw exc; + // Record exception. + responseData.Exception = exc; + if (exc.InnerException != null) + { + exc = exc.InnerException; + } + String msg = exc.GetType().Name + ": " + exc.Message; + SaLogger.Warn(fn + " " + msg + ExceptionUtil.GetStackEntries(exc, 6).Concat("", "\n\t")); + responseData.ErrorStateMessage = msg; + } + requestData.ThrowIfCancellationRequested(); + + // Complete response data. + responseData.Elapsed = DateTime.Now - startedAt; + return responseData; + } + + //------------------------------------------------------------------- + // R e s p o n s e S t a t u s D e s c r i p t i o n + //------------------------------------------------------------------- + public static String GetResponseStatusDescription (HttpStatusCode httpStatus) + { + // Ref. https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + switch (httpStatus) + { + // 1xx: Informational - Request received, continuing process. + case HttpStatusCode.Continue: return "Continue"; + // 2xx: Success - The action was successfully received, understood, and accepted. + case HttpStatusCode.OK: return "OK"; + case HttpStatusCode.Created: return "Created"; + case HttpStatusCode.Accepted: return "Accepted"; + // 3xx: Redirection - Further action must be taken in order to complete the request. + case HttpStatusCode.MultipleChoices: return "Multiple choices"; + case HttpStatusCode.MovedPermanently: return "Moved permanently"; + case HttpStatusCode.Found: return "Found"; + // 4xx: Client Error -The request contains bad syntax or cannot be fulfilled. + case HttpStatusCode.BadRequest: return "Bad request"; + case HttpStatusCode.Unauthorized: return "Unauthorized"; + case HttpStatusCode.PaymentRequired: return "Payment required"; + case HttpStatusCode.Forbidden: return "Forbidden"; + case HttpStatusCode.NotFound: return "Not found"; + case HttpStatusCode.MethodNotAllowed: return "Method not allowed"; + case HttpStatusCode.NotAcceptable: return "Not acceptable"; + // 5xx: Server Error -The server failed to fulfill an apparently valid request. + case HttpStatusCode.InternalServerError: return "Internal server error"; + } + return httpStatus.ToString(); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Helpers/SacsJsonHelper.cs b/SacsStandardBase/Base/Helpers/SacsJsonHelper.cs new file mode 100644 index 0000000..0b5ebec --- /dev/null +++ b/SacsStandardBase/Base/Helpers/SacsJsonHelper.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.Helpers +{ + //======================================================================= + public static class SacsJsonHelper + { + //------------------------------------------------------------------- + public static dynamic DeserializeJsonObject(String data) + { + return JsonConvert.DeserializeObject(data); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Helpers/SacsRandomHelper.cs b/SacsStandardBase/Base/Helpers/SacsRandomHelper.cs new file mode 100644 index 0000000..97b5456 --- /dev/null +++ b/SacsStandardBase/Base/Helpers/SacsRandomHelper.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace Sarand.Net.Standard.Base.Helpers +{ + //======================================================================= + public static class SacsRandomHelper + { + //------------------------------------------------------------------- + // Properties. + public static RandomNumberGenerator RandomNumberGenerator { get; } = RandomNumberGenerator.Create(); + private static Random random; + public static Random Random { get { if (random == null) random = new Random(GetInt32(Int32.MaxValue)); return random; } } + + public static void Fill(byte[] byteArray) + { + // ToDo RandomNumberGenerator.Fill(byteArray); + RandomNumberGenerator.GetBytes(byteArray); + } + + //------------------------------------------------------------------- + /// + /// Generates a random integer between 0 (inclusive) and a specified exclusive upper + /// bound using a cryptographically strong random number generator. + /// + /// The exclusive upper bound of the random range. + /// A random integer between 0 (inclusive) and toExclusive (exclusive). + /// The toExclusive parameter is less than or equal to 0. + public static int GetInt32(int toExclusive) + { + return RandomNumberGenerator.GetInt32(toExclusive); + //return Random.Next(toExclusive); + } + + //------------------------------------------------------------------- + /// + /// Generates a random integer between a specified inclusive lower bound and a specified + /// exclusive upper bound using a cryptographically strong random number generator. + /// + /// The inclusive lower bound of the random range. + /// The exclusive upper bound of the random range. + /// A random integer between fromInclusive (inclusive) and toExclusive (exclusive). + /// The toExclusive parameter is less than or equal to the fromInclusive parameter. + public static int GetInt32(int fromInclusive, int toExclusive) + { + return RandomNumberGenerator.GetInt32(fromInclusive, toExclusive); + //return Random.Next(fromInclusive, toExclusive); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Helpers/SacsResourceHelper.cs b/SacsStandardBase/Base/Helpers/SacsResourceHelper.cs new file mode 100644 index 0000000..79fcaca --- /dev/null +++ b/SacsStandardBase/Base/Helpers/SacsResourceHelper.cs @@ -0,0 +1,243 @@ +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text; + +namespace Sarand.Net.Standard.Base.Helpers +{ + //======================================================================= + public static class SacsResourceHelper + { + //------------------------------------------------------------------- + // Definitions. + public enum TextResource + { + SaBaseCss, + SaBaseJs, + JQueryJs, + JQueryUiCss, + JQueryUiJs, + } + public enum ImageResource + { + // Burgers. + Icon_Burger_Black_48_png, + Icon_Burger_Black_64_png, + // Arrows. + Arrow_Left_Black_16_png, + Arrow_Left_Double_Black_16_png, + Arrow_Right_Black_16_png, + Arrow_Right_Double_Black_16_png, + Arrow_Left_Black_24_png, + Arrow_Left_Double_Black_24_png, + Arrow_Right_Black_24_png, + Arrow_Right_Double_Black_24_png, + // IconShock. + IconShock_Arrow_Down_Purple_72_png, + IconShock_Arrow_Right_Purple_72_png, + IconShock_Add_Green_72_png, + IconShock_Pencil_Orange_72_png, + IconShock_Close_Red_72_png, + IconShock_TrashCan_Grey_72_png, + } + + //------------------------------------------------------------------- + // NB 'Properties' not available in Core .Net project. + // Resources must be loaded via csproj so that they are available in the Manifest Resources: + // + // + // ResXFileCodeGenerator + // Resources.Designer.cs + // + // + // + // + // REf. https://stackoverflow.com/questions/38762368/embedded-resource-in-net-core-libraries + //------------------------------------------------------------------- + public static String GetTextResource(TextResource textResource) + { + String partialPath = ""; + String resourceFilename = ""; + switch (textResource) + { + case TextResource.SaBaseCss: + //return Properties.Resources.sa_base_css; + resourceFilename = "sa-base.css"; + break; + case TextResource.SaBaseJs: + //return Properties.Resources.sa_base_js; + resourceFilename = "sa-base.js"; + break; + case TextResource.JQueryJs: + //return Properties.Resources.jquery_1_12_1_min_js; + resourceFilename = "jquery-1.12.1.min.js"; + break; + case TextResource.JQueryUiCss: + //return Properties.Resources.jquery_ui_1_12_1_min_css; + resourceFilename = "jquery-ui-1.12.1.min.css"; + break; + case TextResource.JQueryUiJs: + //return Properties.Resources.jquery_ui_1_12_1_min_js; + resourceFilename = "jquery-ui-1.12.1.min.js"; + break; + } + if (!StringUtil.IsEmpty(resourceFilename)) + { + var assembly = typeof(SacsResourceHelper).GetTypeInfo().Assembly; + String resourceText = GetManifestResourceAsText(assembly, partialPath, resourceFilename); + if (!StringUtil.IsEmpty(resourceText)) + return resourceText; + } + return ""; + } + + //------------------------------------------------------------------- + public static byte[] GetImageResource(ImageResource imageResource) + { + String partialPath = ""; + String resourceFilename = ""; + switch (imageResource) + { + //----------------------------------------------------------- + // Burgers. + case ImageResource.Icon_Burger_Black_48_png: + resourceFilename = "burger_black_48.png"; + break; + case ImageResource.Icon_Burger_Black_64_png: + resourceFilename = "burger_black_64.png"; + break; + //----------------------------------------------------------- + // Arrows. + case ImageResource.Arrow_Left_Black_16_png: + resourceFilename = "arrow_left_black_16.png"; + break; + case ImageResource.Arrow_Left_Double_Black_16_png: + resourceFilename = "arrow_left_double_black_16.png"; + break; + case ImageResource.Arrow_Right_Black_16_png: + resourceFilename = "arrow_right_black_16.png"; + break; + case ImageResource.Arrow_Right_Double_Black_16_png: + resourceFilename = "arrow_right_double_black_16.png"; + break; + case ImageResource.Arrow_Left_Black_24_png: + resourceFilename = "arrow_left_black_24.png"; + break; + case ImageResource.Arrow_Left_Double_Black_24_png: + resourceFilename = "arrow_left_double_black_24.png"; + break; + case ImageResource.Arrow_Right_Black_24_png: + resourceFilename = "arrow_right_black_24.png"; + break; + case ImageResource.Arrow_Right_Double_Black_24_png: + resourceFilename = "arrow_right_double_black_24.png"; + break; + //----------------------------------------------------------- + // IconShock. + case ImageResource.IconShock_Arrow_Down_Purple_72_png: + partialPath = "IconShock"; + resourceFilename = "arrow_down_purple_72.png"; + break; + case ImageResource.IconShock_Arrow_Right_Purple_72_png: + partialPath = "IconShock"; + resourceFilename = "arrow_right_purple_72.png"; + break; + case ImageResource.IconShock_Add_Green_72_png: + partialPath = "IconShock"; + resourceFilename = "add_green_72.png"; + break; + case ImageResource.IconShock_Pencil_Orange_72_png: + partialPath = "IconShock"; + resourceFilename = "pencil_orange_72.png"; + break; + case ImageResource.IconShock_Close_Red_72_png: + partialPath = "IconShock"; + resourceFilename = "close_red_72.png"; + break; + case ImageResource.IconShock_TrashCan_Grey_72_png: + partialPath = "IconShock"; + resourceFilename = "trashcan_grey_72.png"; + break; + //----------------------------------------------------------- + } + if (!StringUtil.IsEmpty(resourceFilename)) + { + var assembly = typeof(SacsResourceHelper).GetTypeInfo().Assembly; + byte[] resourceData = GetManifestResourceAsBinary(assembly, partialPath, resourceFilename); + if (resourceData != null) + return resourceData; + } + return new byte[0]; + } + + //------------------------------------------------------------------- + /// Retrieve resource as String based on assembly and partial resource name. + public static String GetManifestResourceAsText(Assembly assembly, String partialPath, String resourceNameEndsWith) + { + String fn = typeof(SacsResourceHelper).Name + ".GetManifestResourceAsText"; + if (StringUtil.IsEmpty(resourceNameEndsWith)) return ""; + + using (Stream resourceStream = GetManifestResourceStream(assembly, partialPath, resourceNameEndsWith)) + { + if (resourceStream != null) + { + using (StreamReader resourceReader = new StreamReader(resourceStream, Encoding.UTF8)) + { + return resourceReader.ReadToEnd(); + } + } + } + return ""; + } + + //------------------------------------------------------------------- + /// Retrieve resource as byte array based on assembly and partial resource name. + public static byte[] GetManifestResourceAsBinary(Assembly assembly, String partialPath, String resourceNameEndsWith) + { + String fn = typeof(SacsResourceHelper).Name + ".GetManifestResourceAsBinary"; + if ( + StringUtil.IsEmpty(resourceNameEndsWith)) return null; + using (Stream resourceStream = GetManifestResourceStream(assembly, partialPath, resourceNameEndsWith)) + { + if (resourceStream != null) + { + using (BinaryReader resourceReader = new BinaryReader(resourceStream, Encoding.UTF8)) + { + return resourceReader.ReadBytes((int)resourceStream.Length); + } + } + } + SaLogger.Warn(fn + " Failed to locate text resource: " + resourceNameEndsWith); + return null; + } + + //------------------------------------------------------------------- + /// Retrieve resource as stream based on assembly and partial resource name. + public static Stream GetManifestResourceStream(Assembly assembly, String partialPath, String resourceNameEndsWith) + { + String fn = typeof(SacsResourceHelper).Name + ".GetManifestResourceStream"; + //Logger.Debug(fn + " Manifest resource names: " + new List(assembly.GetManifestResourceNames()).Concat("","\n\t")); + foreach (String resourceName in assembly.GetManifestResourceNames()) + { + //SaLogger.Debug(fn + " Resource: " + resourceName); + + // Match the partial path. + if (!StringUtil.IsEmpty(partialPath) && !resourceName.ContainsCI(partialPath)) continue; + // Match the resource filename to the end of the resource name - resource names start with default namespace (eg. 'Sarand'). + if (!resourceName.EndsWithCI(resourceNameEndsWith)) continue; + + //SaLogger.Debug(fn + " Resource filename: " + resourceNameEndsWith + ". Matched to resource: " + resourceName); + return assembly.GetManifestResourceStream(resourceName); + } + SaLogger.Warn(fn + " Resource filename not found: " + resourceNameEndsWith); + return null; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/IO/DirectoryUtil.cs b/SacsStandardBase/Base/IO/DirectoryUtil.cs new file mode 100644 index 0000000..1499543 --- /dev/null +++ b/SacsStandardBase/Base/IO/DirectoryUtil.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; + +namespace Sarand.Net.Standard.Base.IO +{ + //======================================================================= + public static class DirectoryUtil + { + //------------------------------------------------------------------- + // D i r e c t o r y E x i s t s + //------------------------------------------------------------------- + public static bool DirectoryExists (String directory) + { + try + { + return Directory.Exists(directory); + } + catch (Exception) { } + return false; + } + + //------------------------------------------------------------------- + // G e t D i r e c t o r y N a m e + //------------------------------------------------------------------- + /// + /// Returns the directory information for the specified path string. + /// + /// The path of a file or directory. + /// Directory information for path, or null if path denotes a root directory or is + /// null. Returns System.String.Empty if path does not contain directory information. + public static String GetDirectoryName (String path) + { + try + { + // Exceptions: + // T:System.ArgumentException: + // The path parameter contains invalid characters, is empty, or contains only white spaces. + // T:System.IO.PathTooLongException: + // The path parameter is longer than the system-defined maximum length. + return Path.GetDirectoryName(path); + } + catch (Exception) { } + return String.Empty; + } + + //------------------------------------------------------------------- + // E n u m e r a t e D i r e c t o r i e s + //------------------------------------------------------------------- + /// + /// Retrieve list of sub directories in directory. + /// Avoiding Exception thrown when network path isn't valid. + /// + public static IEnumerable EnumerateDirectories (String path) + { + try + { + return Directory.EnumerateDirectories(path); + } + catch (Exception exc) + { + SaLogger.Error("DirectoryUtil.EnumerateDirectories " + exc.GetType().Name + ": " + exc.Message + ". Path: '" + path + "'"); + } + return new List(); + } + + //------------------------------------------------------------------- + // E n u m e r a t e F i l e s + //------------------------------------------------------------------- + /// + /// Retrieve list of files in directory. + /// Avoiding Exception thrown when network path isn't valid. + /// + public static IEnumerable EnumerateFiles (String path) + { + try + { + return Directory.EnumerateFiles(path); + } + catch (Exception exc) + { + SaLogger.Error("DirectoryUtil.EnumerateFiles " + exc.GetType().Name + ": " + exc.Message + ". Path: '" + path + "'"); + } + return new List(); + } + //------------------------------------------------------------------- + /// + /// Retrieve list of files in directory. + /// Avoiding Exception thrown when network path isn't valid. + /// + public static IEnumerable EnumerateFiles (String path, String searchPattern) + { + try + { + return Directory.EnumerateFiles(path, searchPattern); + } + catch (Exception exc) + { + SaLogger.Error("DirectoryUtil.EnumerateFiles " + exc.GetType().Name + ": " + exc.Message + ". Path: '" + path + "'. Pattern: " + searchPattern); + } + return new List(); + } + //------------------------------------------------------------------- + /// + /// Retrieve list of files in directory. + /// Avoiding Exception thrown when network path isn't valid. + /// + public static IEnumerable EnumerateFiles (String path, String searchPattern, SearchOption searchOption) + { + try + { + return Directory.EnumerateFiles(path, searchPattern, searchOption); + } + catch (Exception exc) + { + SaLogger.Error("DirectoryUtil.EnumerateFiles " + exc.GetType().Name + ": " + exc.Message + ". Path: '" + path + "'. Pattern: " + searchPattern + ". Option: " + searchOption.ToString()); + } + return new List(); + } + + //------------------------------------------------------------------- + // C r e a t e D i r e c t o r y + //------------------------------------------------------------------- + /// + /// Create Directory in the specified path. + /// Avoiding Exception thrown when network path isn't valid. + /// + public static DirectoryInfo CreateDirectory (String path) + { + try + { + return Directory.CreateDirectory(path); + } + catch (Exception exc) + { + SaLogger.Error("DirectoryUtil.CreateDirectory " + exc.GetType().Name + ": " + exc.Message + ". Path: '" + path + "'"); + } + return null; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/IO/FileUtil.cs b/SacsStandardBase/Base/IO/FileUtil.cs new file mode 100644 index 0000000..655b269 --- /dev/null +++ b/SacsStandardBase/Base/IO/FileUtil.cs @@ -0,0 +1,361 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.IO +{ + //======================================================================= + public static class FileUtil + { + //------------------------------------------------------------------- + // U t i l i t i e s + //------------------------------------------------------------------- + public static String GetFileLengthFmt(long length) + { + // Longs run out around EB + string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; + if (length == 0) return "0" + suf[0]; + long bytes = Math.Abs(length); + int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); + double num = Math.Round(bytes / Math.Pow(1024, place), place > 0 ? 1 : 0); + if (num >= 1000.0) + { + // Avoid values with 4 digits (and thousands separator). + place++; + num = Math.Round(bytes / Math.Pow(1024, place), place > 0 ? 1 : 0, MidpointRounding.AwayFromZero); + } + return (Math.Sign(length) * num).ToString(place > 0 ? "N1" : "N0") + " " + suf[place]; + } + + //------------------------------------------------------------------- + // P a t h + //------------------------------------------------------------------- + public static String GetPathDelim(String path) + { + if (!StringUtil.IsEmpty(path) && path.IndexOf(Path.AltDirectorySeparatorChar) >= 0) + return Path.AltDirectorySeparatorChar.ToString(); + return Path.PathSeparator.ToString(); + } + + //------------------------------------------------------------------- + // D i r e c t o r y + //------------------------------------------------------------------- + public delegate bool DirectoryExistsFn(String directoryPath); + public static DirectoryExistsFn fnDirectoryExists { get; set; } = null; + public static bool IsDirectory(String directoryPath) + { + try + { + // Alternative method, if specified. + if (fnDirectoryExists != null) + return fnDirectoryExists(directoryPath); + // Default method. + return Directory.Exists(directoryPath); + } + catch (Exception exc) { SaLogger.Debug("FileUtil.IsDirectory " + exc.GetType().Name + ": " + exc.Message); } + return false; + } + public delegate String DirectoryGetParentFn(String filepath); + public static DirectoryGetParentFn fnDirectoryGetParent { get; set; } + public static String GetParentDirectory(String filepath) + { + try + { + // Alternative method, if specified. + if (fnDirectoryGetParent != null) + return fnDirectoryGetParent(filepath); + // Default method. + DirectoryInfo parentDirectoryInfo = Directory.GetParent(filepath); + if (parentDirectoryInfo != null) + return parentDirectoryInfo.FullName; + } + catch (Exception exc) { SaLogger.Debug("FileUtil.GetParentDirectory " + exc.GetType().Name + ": " + exc.Message); } + return String.Empty; + } + public delegate bool DirectoryCreateDirectoryFn(String filepath); + public static DirectoryCreateDirectoryFn fnDirectoryCreateDirectory { get; set; } = null; + public static bool CreateDirectory(String directoryPath) + { + try + { + // Alternative method, if specified. + if (fnDirectoryCreateDirectory != null) + return fnDirectoryCreateDirectory(directoryPath); + // Default method. + DirectoryInfo info = Directory.CreateDirectory(directoryPath); + return info.Exists; + } + catch (Exception exc) { SaLogger.Debug("FileUtil.CreateDirectory " + exc.GetType().Name + ": " + exc.Message); } + return false; + } + public static bool ValidateDirectory(String directoryPath, bool allowCreation = false) + { + // Validate parent folder first. + String parentDirectoryPath = GetParentDirectory(directoryPath); + if (!StringUtil.IsEmpty(parentDirectoryPath) && !ValidateDirectory(parentDirectoryPath, allowCreation)) + return false; + // Validate folder itself. + if (!IsDirectory(directoryPath) && allowCreation) + CreateDirectory(directoryPath); + return IsDirectory(directoryPath); + } + + //------------------------------------------------------------------- + // F i l e + //------------------------------------------------------------------- + public static DateTime GetFileLastWriteTime(String filePath) + { + try + { + return File.GetLastWriteTime(filePath); + } + catch (Exception exc) + { + SaLogger.Warn(typeof(FileUtil).Name + ".GetFileLastWriteTime " + exc.GetType().Name + ": " + exc.Message); + } + return DateTime.MinValue; + } + //------------------------------------------------------------------- + // File utilities. + public delegate bool FileExistsFn(String filePath); + public static FileExistsFn fnFileExists { get; set; } = null; + /// + /// Test whether file exists. + /// + /// + /// + public static bool IsFile(String filePath) + { + try + { + // Alternative method, if specified. + if (fnFileExists != null) + return fnFileExists(filePath); + // Default method. + return File.Exists(filePath); + } + catch (Exception exc) { SaLogger.Debug("FileUtil.IsFile " + exc.GetType().Name + ": " + exc.Message); } + return false; + } + + //------------------------------------------------------------------- + public delegate String[] FileReadAllLinesFn(String filePath); + public static FileReadAllLinesFn fnFileReadAllLines { get; set; } = null; + /// + /// Read all lines from a file. + /// + /// + /// + public static String[] ReadAllFileLines(String filePath) + { + try + { + // Alternative method, if specified. + if (fnFileReadAllLines != null) + return fnFileReadAllLines(filePath); + // Default method. + return File.ReadAllLines(filePath); + } + catch (Exception exc) { SaLogger.Debug("FileUtil.ReadAllFileLines " + exc.GetType().Name + ": " + exc.Message); } + return new String[0]; + } + + //------------------------------------------------------------------- + /// + /// Locate the specified file in a folder path relative to the specified folder. + /// Search is in the fspecified folder and then each parent folder in turn. + /// Any subDirectorites at each level found in the suppleid list are also searched. + /// + public static String LocateFileRelative(String baseFolder, String filename, List subDirectories) + { + if (String.IsNullOrEmpty(filename)) return String.Empty; + String rootfolder = SacsSystem.ApplicationFolder; +#if DEBUG + SaLogger.Trace("FileUtil.LocateFileRelative Root folder = " + rootfolder); +#endif + + String directory = rootfolder; + while (!directory.IsEmpty()) + { + // Does this directory contain the specified file: + String filepath = directory + Path.AltDirectorySeparatorChar + filename; + if (IsFile(filepath)) return filepath; + + // Check sub-directories: + if (subDirectories != null) + { + foreach (var subDirectory in subDirectories) + { + filepath = directory + Path.AltDirectorySeparatorChar + subDirectory + Path.AltDirectorySeparatorChar + filename; + if (IsFile(filepath)) return filepath; + } + } + directory = GetParentDirectory(directory); + } + return String.Empty; + } + /// Locate file in path relative to application folder. + public static String LocateFileRelative(String filename, List subDirectories) + { + return LocateFileRelative(SacsSystem.ApplicationFolder, filename, subDirectories); + } + + //------------------------------------------------------------------- + // L o c a t e F i l e s + //------------------------------------------------------------------- + // Locate files in the folder that match the file and one of the specified extensions. + // Returned list is the full path for each file. + public static List LocateFiles(String folder, String file, List extensions, int maxSubFolders) + { + List files = new List(); + + if (!DirectoryUtil.DirectoryExists(folder) || StringUtil.IsEmpty(file)) return files; + + // Locate matching files in the specified folder. + String fileSpec = file + "." + (extensions.Count == 1 ? extensions[0] : "*"); + foreach (String filename in DirectoryUtil.EnumerateFiles(folder, fileSpec)) + { + String filePath = folder + GetPathDelim(folder) + filename; + bool validFile = true; + // Verify the extension: + if (extensions.Count > 0) + { + String extension = Path.GetExtension(filePath); + validFile = extensions.Any(item => item.EqualsCI(extension)); + } + if (validFile) + files.Add(filePath); + } + if (maxSubFolders > 0) + { + // Locate sub folders: + foreach (String subFolder in DirectoryUtil.EnumerateDirectories(folder)) + { + if (subFolder == "." || subFolder == "..") continue; + files.AddRange(LocateFiles(subFolder, file, extensions, maxSubFolders - 1)); + } + } + + return files; + } + + //------------------------------------------------------------------- + /// + /// Replace invalid characters with '_'. + /// Invalid characters: \/:*?\"<>| + /// + /// + /// + public static String FormatFilename(String filename) + { + if (StringUtil.IsEmpty(filename)) return String.Empty; + // Replace invalid characters with '_'. + filename = filename.ReplaceAny("\\/:*?\"<>|", '_'); + filename = StringUtil.ReplaceAll(filename, "__", "_"); + filename = StringUtil.ReplaceAll(filename, "..", "."); + filename = filename.ReplaceAll(new string[] { "_ ", " _", " " }, " "); + return filename; + } + + //------------------------------------------------------------------- + // Shell32 alternatives to file utilities to avoid 'path too long' exceptions etc. + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode/*Auto*/)] + public struct SHFILEINFO + { + public IntPtr hIcon; + public int iIcon; + public uint dwAttributes; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string szDisplayName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] + public string szTypeName; + }; + [DllImport("shell32.dll", CharSet = CharSet.Unicode/*Auto*/)] + public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags); + + //------------------------------------------------------------------- + // Create/Open file handle. + // Ref. http://www.pinvoke.net/default.aspx/kernel32.getfiletime + private const uint GENERIC_READ = 0x80000000; + private const uint FILE_SHARE_READ = 0x1; + private const uint FILE_ATTRIBUTE_NORMAL = 0x80; + private const int INVALID_HANDLE_VALUE = -1; + private const uint OPEN_EXISTING = 3; + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CloseHandle(IntPtr hObject); + [DllImport("kernel32.dll", CharSet = CharSet.Unicode/*Auto*/, CallingConvention = CallingConvention.StdCall, SetLastError = true)] + private static extern IntPtr CreateFile( + string lpFileName, + uint dwDesiredAccess, + uint dwShareMode, + IntPtr SecurityAttributes, + uint dwCreationDisposition, + uint dwFlagsAndAttributes, + IntPtr hTemplateFile); + + //------------------------------------------------------------------- + // File date/times. + // Ref. http://www.pinvoke.net/default.aspx/kernel32.getfiletime + // REf. https://msdn.microsoft.com/en-us/library/windows/desktop/ms724320(v=vs.85).aspx + public class FileTimes + { + public DateTime CreationTime { get; internal set; } = DateTime.MinValue; + public DateTime LastAccessTime { get; internal set; } = DateTime.MinValue; + public DateTime LastWriteTime { get; internal set; } = DateTime.MinValue; + } + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool GetFileTime(IntPtr hFile, ref FILETIME lpCreationTime, ref FILETIME lpLastAccessTime, ref FILETIME lpLastWriteTime); + [StructLayout(LayoutKind.Sequential)] + private struct FILETIME + { + public uint dwLowDateTime; + public uint dwHighDateTime; + } + public static FileTimes GetFileTimes(String filePath) + { + FileTimes fileTimes = new FileTimes(); + try + { + // .Net versions first. + // These fail on long file paths. + fileTimes.CreationTime = File.GetCreationTime(filePath).Date; + fileTimes.LastAccessTime = File.GetLastAccessTime(filePath).Date; + fileTimes.LastWriteTime = File.GetLastWriteTime(filePath).Date; + return fileTimes; + } + catch (Exception) { } + // If .Net methods fail, try P/Invoke via kernel32. + IntPtr ptr = IntPtr.Zero; + FILETIME ftCreationTime = new FILETIME(); + FILETIME ftLastAccessTime = new FILETIME(); + FILETIME ftLastWriteTime = new FILETIME(); + try + { + // NB Opening file will fail if it's open elsewhere. + ptr = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero); + if (ptr.ToInt32() == INVALID_HANDLE_VALUE) + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + if (GetFileTime(ptr, ref ftCreationTime, ref ftLastAccessTime, ref ftLastWriteTime) != true) + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + fileTimes.CreationTime = DateTime.FromFileTimeUtc((((long)ftCreationTime.dwHighDateTime) << 32) | ((uint)ftCreationTime.dwLowDateTime)); + fileTimes.LastAccessTime = DateTime.FromFileTimeUtc((((long)ftLastAccessTime.dwHighDateTime) << 32) | ((uint)ftLastAccessTime.dwLowDateTime)); + fileTimes.LastWriteTime = DateTime.FromFileTimeUtc((((long)ftLastWriteTime.dwHighDateTime) << 32) | ((uint)ftLastWriteTime.dwLowDateTime)); + } + catch (Exception) { } + finally + { + if (ptr != IntPtr.Zero && ptr.ToInt32() != INVALID_HANDLE_VALUE) CloseHandle(ptr); + } + return fileTimes; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Interfaces/SaInterface.cs b/SacsStandardBase/Base/Interfaces/SaInterface.cs new file mode 100644 index 0000000..f8fda81 --- /dev/null +++ b/SacsStandardBase/Base/Interfaces/SaInterface.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.Interfaces +{ + //======================================================================= + public class SaInterface : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Pos = 0, + // Interface. + Id, + Context, + Category, + Category_Desc, + Type, + Type_Desc, + Code, + Caption, + Active_Y, + Active_TickCross, + Active_Ind, + // Derived properties. + Process_Count, + DataItem_Count, + // Common properties. + Notes, + Status, + Status_Desc, + Status_Ind, + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public String Context { get; set; } = String.Empty; + public SaInterfaceCategory Category { get; set; } = SaInterfaceCategory.Unknown; + public short Type { get; set; } = 0; + public String Code { get; set; } = String.Empty; + public String Caption { 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 Context + { + get { return data.Context; } + set { CheckChanged(data.Context, value); data.Context = value; } + } + public SaInterfaceCategory Category + { + get { return data.Category; } + set { CheckChanged((int)data.Category, (int)value); data.Category = value; } + } + public short Type + { + get { return data.Type; } + set { CheckChanged(data.Type, value); data.Type = value; } + } + public String Code + { + get { return data.Code; } + set { CheckChanged(data.Code, value); data.Code = value; } + } + public String Caption + { + get { return data.Caption; } + set { CheckChanged(data.Caption, value); data.Caption = value; } + } + // Flags. + public bool Active + { + get { return GetFlag(Flag_Active); } + set { SetFlag(Flag_Active, value); } + } + // Population. + internal void PopulateFrom (Data data) + { + Context = data.Context; + Category = data.Category; + Type = data.Type; + Code = data.Code; + Caption = data.Caption; + 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 Context { get; set; } = ValidValue.Valid; + public ValidValue Category { get; set; } = ValidValue.Valid; + public ValidValue Type { get; set; } = ValidValue.Valid; + public ValidValue Code { get; set; } = ValidValue.Valid; + public ValidValue Caption { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + // Flags. + public static readonly Byte Flag_Active = 0x01; + + //------------------------------------------------------------------- + // Construction. + public SaInterface (SysId id, String context, SaInterfaceCategory category, short type) : + base(id) + { + base.SetData(data); + Context = context; + Category = category; + Type = type; + Status = DefaultStatus; + } + + //------------------------------------------------------------------- + // Statuses. + private static readonly List validStatuses = new List() + { + SaStatus.Active, + }; + public static SaStatus DefaultStatus { get { return ActiveStatus; } } + public static SaStatus ActiveStatus { get { return SaStatus.Active; } } + public static List ValidStatuses { get { return new List(validStatuses); } } + public static DataValue.TriStateType GetStatusTriState (SaStatus status) + { + switch (status) + { + case SaStatus.Active: + 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(Context) && !Context.Equals(data.Context)) + throw new NotSupportedException(fn + " Context cannot be changed"); + if ((int)Category >= 0 && (int)Category != (int)data.Category) + throw new NotSupportedException(fn + " Category cannot be changed"); + if (Type >= 0 && Type != data.Type) + throw new NotSupportedException(fn + " Type cannot be changed"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + public String Context + { + get { return data.Context; } + protected set { data.Context = value; } + } + public SaInterfaceCategory Category + { + get { return data.Category; } + protected set { data.Category = value; } + } + public short Type + { + get { return data.Type; } + protected set { data.Type = value; } + } + public String Code + { + get { return data.Code; } + set { data.Code = value; } + } + public String Caption + { + get { return data.Caption; } + set { data.Caption = value; } + } + + //------------------------------------------------------------------- + // Flags. + public bool Active + { + get { return GetFlag(Flag_Active); } + set { SetFlag(Flag_Active, value); } + } + + //------------------------------------------------------------------- + // List parameters. + public enum StatusCat + { + Any, + /// NB Interface activity controlled by flag rather than status. + ActiveOnly, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + public List IncludedIds { get; } = new List(); + public List ExcludedIds { get; } = new List(); + public String Context { get; set; } + public SaInterfaceCategory InterfaceCategory { get; set; } = SaInterfaceCategory.Unknown; + public List IncludedInterfaceTypes { get; } = new List(); + /// Exact code match + public String Code { get; set; } + public String CaptionPartial { get; set; } + /// Text to parially match code or caption + public String InterfaceText { get; set; } + /// Active flag + public bool ActiveOnly { get; set; } = true; + public StatusCat StatusCat { get; set; } = StatusCat.Any; + } + + //------------------------------------------------------------------- + } + //======================================================================= + // L i s t + //======================================================================= + public static class SaInterfaceList + { + //------------------------------------------------------------------- + // E x i s t s + //------------------------------------------------------------------- + public static bool ExistsByContextCategoryType (this List interfaces, String context, SaInterfaceCategory interfaceCategory, short interfaceType) + { + return interfaces.Exists(item => item.Context.EqualsCI(context) && item.Category == interfaceCategory && item.Type == interfaceType); + } + public static bool ExistsByContextCategoryType (this List interfaces, String context, SaInterfaceCategory interfaceCategory, short interfaceType) + { + return interfaces.Exists(item => item.Context.EqualsCI(context) && item.Category == interfaceCategory && item.Type == interfaceType); + } + + //------------------------------------------------------------------- + // F i n d A l l + //------------------------------------------------------------------- + public static List FindAllByContextCategory (this List interfaces, String context, SaInterfaceCategory interfaceCategory) + { + return interfaces.FindAll(item => item.Context.EqualsCI(context) && item.Category == interfaceCategory); + } + public static List FindAllByContextCategory (this List interfaces, String context, SaInterfaceCategory interfaceCategory) + { + return interfaces.FindAll(item => item.Context.EqualsCI(context) && item.Category == interfaceCategory); + } + public static List FindAllByContextCategoryType (this List interfaces, String context, SaInterfaceCategory interfaceCategory, short interfaceType) + { + return interfaces.FindAll(item => item.Context.EqualsCI(context) && item.Category == interfaceCategory && item.Type == interfaceType); + } + public static List FindAllByContextCategoryType (this List interfaces, String context, SaInterfaceCategory interfaceCategory, short interfaceType) + { + return interfaces.FindAll(item => item.Context.EqualsCI(context) && item.Category == interfaceCategory && item.Type == interfaceType); + } + + //------------------------------------------------------------------- + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Interfaces/SaInterfaceConsole.cs b/SacsStandardBase/Base/Interfaces/SaInterfaceConsole.cs new file mode 100644 index 0000000..3f941e9 --- /dev/null +++ b/SacsStandardBase/Base/Interfaces/SaInterfaceConsole.cs @@ -0,0 +1,211 @@ +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Dbas; +using Sarand.Net.Standard.Base.ItemOptions; +using Sarand.Net.Standard.Base.UpdateLog; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Interfaces +{ + //======================================================================= + public abstract class SaInterfaceConsole : SaErrorStateItem + { + //------------------------------------------------------------------- + // Interface definition. + public class SaInterfaceDfn + { + public SaInterfaceDfn (String context, SaInterfaceCategory category, TypeCodeDesc typeDfn, bool duplicatesAllowed) + { + Context = context; + Category = category; + TypeDfn = typeDfn; + DuplicatesAllowed = duplicatesAllowed; + } + public String Context { get; } = ""; + public SaInterfaceCategory Category { get; } = SaInterfaceCategory.Unknown; + public short InterfaceType { get { return (short)TypeDfn.Type; } } + public TypeCodeDesc TypeDfn { get; } = null; + public bool DuplicatesAllowed { get; } = false; + } + + //------------------------------------------------------------------- + // Data members. + private readonly List interfaceDfns = new List(); + + //------------------------------------------------------------------- + // Static initialisation. + static SaInterfaceConsole () + { + } + + //------------------------------------------------------------------- + // Construction. + protected SaInterfaceConsole () + { + } + + //------------------------------------------------------------------- + // Properties. + public abstract short InterfaceDataType { get; } + public abstract SaItemOptionConsole ItemOptionConsole { get; } + public abstract SaUpdateLogConsole UpdateLogConsole { get; } + // Database. + public abstract bool DatabaseIsValid { get; } + public abstract bool CanUpdateDatabase { get; } + + //--------------------------------------------------------------- + // Manage interfaces - displays dialog. + public abstract bool ManageInterfaces (String interfaceContext, bool userCanManageInterfaces, bool userCanProcessInterfaces, DbUpdateInfo dbUpdateInfo); + + //--------------------------------------------------------------- + // Utilities. + public abstract String GetItemTypeDesc (short itemType); + public abstract String GetItemCaption (ItemId itemId); + + //--------------------------------------------------------------- + // Interface item option definitions. + public abstract List GetInterfaceItemOptionDfns (SaInterfaceCategory interfaceCategory, short interfaceType); + public abstract List GetInterfaceItemOptionDfns (SysId interfaceId); + + //------------------------------------------------------------------- + // I n t e r f a c e R e g i s t r a t i o n + //------------------------------------------------------------------- + public bool HasRegisteredInterfaces () + { + return (interfaceDfns.Count > 0); + } + protected void RegisterInterfaces (String context, SaInterfaceCategory category, List types, bool allowDuplicates) + { + foreach (TypeCodeDesc typeDfn in types) + { + // Remove any matches from the list. + interfaceDfns.RemoveAll(item => item.Context.EqualsCI(context) && item.Category == category && item.InterfaceType == typeDfn.Type); + // Add interface definition to list. + SaInterfaceDfn interfaceDfn = new SaInterfaceDfn(context, category, typeDfn, allowDuplicates); + interfaceDfns.Add(interfaceDfn); + } + } + public void ClearRegisteredInterfaces () + { + interfaceDfns.Clear(); + } + public List GetInterfaceDfns (String context, bool createOnly) + { + List interfaceDfns = new List(); + foreach (SaInterfaceDfn dfn in this.interfaceDfns.FindAll(item => item.Context.EqualsCI(context))) + { + if (!createOnly || CanCreateInterface(dfn.Context, dfn.Category, dfn.InterfaceType)) + interfaceDfns.Add(dfn); + } + return interfaceDfns; + } + public bool HasInterfaceDfns (String context, SaInterfaceCategory interfaceCategory, short interfaceType, bool createOnly) + { + List interfaceDfns = new List(); + foreach (SaInterfaceDfn dfn in this.interfaceDfns.FindAll(item => item.Context.EqualsCI(context) && ((int)interfaceCategory < 1 || item.Category == interfaceCategory) && ((int)interfaceType < 1 || item.InterfaceType == interfaceType))) + { + if (!createOnly || CanCreateInterface(dfn.Context, dfn.Category, dfn.InterfaceType)) + return true; + } + return false; + } + public List GetInterfaceDfns (String context, SaInterfaceCategory interfaceCategory, short interfaceType, bool createOnly) + { + List interfaceDfns = new List(); + foreach (SaInterfaceDfn dfn in this.interfaceDfns.FindAll(item => item.Context.EqualsCI(context) && ((int)interfaceCategory < 1 || item.Category == interfaceCategory) && ((int)interfaceType < 1 || item.InterfaceType == interfaceType))) + { + if (!createOnly || CanCreateInterface(dfn.Context, dfn.Category, dfn.InterfaceType)) + interfaceDfns.Add(dfn); + } + return interfaceDfns; + } + public virtual String GetInterfaceTypeDesc (String context, SaInterfaceCategory category, short type) + { + SaInterfaceDfn dfn = interfaceDfns.Find(item => item.Context.EqualsCI(context) && item.Category == category && item.InterfaceType == type); + if (dfn != null) + return dfn.TypeDfn.Desc; + return ""; + } + + //------------------------------------------------------------------- + // I n t e r f a c e A c c e s s C o n t r o l + //------------------------------------------------------------------- + public abstract bool CanCreateInterfaces (); + public abstract bool CanCreateInterfaces (String context, SaInterfaceCategory interfaceCategory); + public abstract bool CanCreateInterface (String context, SaInterfaceCategory interfaceCategory, short interfaceType); + public abstract bool AreMultipleInterfaceInstancesAllowed (String context, SaInterfaceCategory interfaceCategory, short interfaceType); + public abstract bool AreMultipleActiveInterfacesAllowed (String context, SaInterfaceCategory interfaceCategory); + public abstract bool CanUpdateInterface (SaInterface interfaceObj); + public abstract bool CanCopyInterfaces (); + public abstract bool CanCopyInterfaces (String context, SaInterfaceCategory interfaceCategory); + public abstract bool CanCopyInterface (String context, SaInterfaceCategory interfaceCategory, short interfaceType); + public abstract bool CanCopyInterface (SaInterface interfaceObj); + public abstract bool IsCompatibleForInterfaceCopy (String context, SaInterfaceCategory interfaceCategory, int typeFrom, int typeTo); + public abstract bool CanRemoveInterface (SaInterface interfaceObj); + // Interface management. + public abstract SysId CreateInterface (String context, SaInterfaceCategory interfaceCategory, short interfaceType, String code, String caption, DbUpdateInfo dbUpdateInfo); + // Associated events. + public abstract void OnInterfaceCreated (SysId interfaceId); + public abstract void OnInterfaceUpdated (SysId interfaceId); + public abstract void OnInterfaceRemoved (SysId interfaceId); + // Clear configuration options, data items, processes etc. + public abstract bool ClearDependentData (SaInterface interfaceObj, DbUpdateInfo dbUpdateInfo); + //--------------------------------------------------------------- + // Process interface. + public abstract bool CanProcessInterface (SaInterface interfaceObj); + public abstract bool ProcessInterface (SaInterface interfaceObj, DbUpdateInfo dbUpdateInfo); + //--------------------------------------------------------------- + // Retrieve interfaces. + public abstract SaInterface GetInterfaceById (SysId interfaceId); + public abstract SaInterface GetInterface (SaInterface.Params prmInterface); + public abstract List GetInterfaceList (SaInterface.Params prmInterface); + //--------------------------------------------------------------- + // Conversion between Interface Category & Code and the equivalent database Id. + public abstract SysId GetInterfaceIdForContextCategoryType (String context, SaInterfaceCategory interfaceCategory, short interfaceType); + public abstract SysId GetInterfaceIdForContextCategoryCode (String context, SaInterfaceCategory interfaceCategory, String code); + public abstract SaInterfaceCategory GetCategoryForInterfaceId (SysId interfaceId); + public abstract String GetCodeForInterfaceId (SysId interfaceId); + + //--------------------------------------------------------------- + // I n t e r f a c e P r o c e s s e s + //--------------------------------------------------------------- + // Identify the highest sequence currently in use for an interface. + public abstract int GetMaxProcessSequence (SysId interfaceId, SaInterfaceProcessDirection processDirection, bool completedOnly = true); + // Create new process and imediately store details on database (status set to 'In Progress'). + public abstract SaInterfaceProcess StartInterfaceProcess (SysId interfaceId, SaInterfaceProcessDirection eDirection, String batchRef, DateTime batchAt, int sequence, DbUpdateInfo dbUpdateInfo); + // Retrieve a process using its identity (restricted by interface). + public abstract SaInterfaceProcess GetInterfaceProcess (SysId interfaceId, SysId interfaceProcessId); + // Update the details for a process. + public abstract bool StoreInterfaceProcess (SaInterfaceProcess interfaceProcess, DbUpdateInfo dbUpdateInfo); + + //--------------------------------------------------------------- + // I n t e r f a c e P r o c e s s M e s s a g e s + //--------------------------------------------------------------- + public abstract bool LogInterfaceProcessMessage (SysId interfaceProcessId, DbUpdateInfo dbUpdateInfo, String message, SaMessageLevel messageLevel = SaMessageLevel.Info, String Filename = "", int LineNumber = 0, ItemId ItemId = null); + + //--------------------------------------------------------------- + // I n t e r f a c e D a t a I t e m s + //--------------------------------------------------------------- + public abstract bool ItemHasDataItems (ItemId itemId); + public abstract bool InterfaceDataItemExistsByRef (SysId interfaceId, SaInterfaceProcessDirection processDirection, short itemType, String externalReference); + public abstract SaInterfaceDataItem CreateInterfaceDataItem (SysId interfaceId, SaInterfaceProcessDirection processDirection, ItemId itemId, String externalReference); + public abstract SaInterfaceDataItem GetInterfaceDataItem (SysId interfaceDataItemId); + /// Fails if more than 1 matching data item. + public abstract SaInterfaceDataItem GetInterfaceDataItemByItemRef (SysId interfaceId, SaInterfaceProcessDirection processDirection, ItemId itemId, String externalReference); + /// Fails if more than 1 matching data item. + public abstract SaInterfaceDataItem GetInterfaceDataItemByRef (SysId interfaceId, SaInterfaceProcessDirection processDirection, short itemType, String externalReference); + /// Fails if more than 1 matching data item. + public abstract SaInterfaceDataItem GetInterfaceDataItemByItem (SysId interfaceId, SaInterfaceProcessDirection processDirection, ItemId itemId); + public abstract List GetInterfaceDataItemsByItem (SysId interfaceId, SaInterfaceProcessDirection processDirection, ItemId itemId); + public abstract List GetInterfaceDataItemsByRef (SysId interfaceId, SaInterfaceProcessDirection processDirection, short itemType, String externalReference); + public abstract bool StoreInterfaceDataItem (SaInterfaceDataItem interfaceDataItem, DbUpdateInfo dbUpdateInfo); + public abstract bool StoreInterfaceDataItems (List interfaceDataItems, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearInterfaceDataItem (SaInterfaceDataItem interfaceDataItem, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearInterfaceDataItems (ItemId itemId, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Interfaces/SaInterfaceDataItem.cs b/SacsStandardBase/Base/Interfaces/SaInterfaceDataItem.cs new file mode 100644 index 0000000..cefa605 --- /dev/null +++ b/SacsStandardBase/Base/Interfaces/SaInterfaceDataItem.cs @@ -0,0 +1,383 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.Interfaces +{ + //======================================================================= + public class SaInterfaceDataItem : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Pos = 0, + Id, + // Interface. + InterfaceId, + // Data item. + Direction, + Direction_Desc, + ExternalReference, + ItemType, + ItemType_Desc, + ItemId, + Item_Caption, + ProcessStatus, + ProcessStatus_Desc, + ProcessStatus_Ind, + ProcessStatusBy, + ProcessStatusBy_Name, + ProcessStatusAt, + ProcessStatusAt_Date, + ProcessStatusAt_Time, + ItemStatus, + ItemStatus_Desc, + ItemStatus_Ind, + ItemStatusBy, + ItemStatusBy_Name, + ItemStatusAt, + ItemStatusAt_Date, + ItemStatusAt_Time, + // Common properties. + Notes, + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public SysId InterfaceId { get; set; } = null; + public SaInterfaceProcessDirection Direction { get; set; } = SaInterfaceProcessDirection.Unknown; + public ItemId ItemId { get; set; } = null; + public String ExternalReference { get; set; } = String.Empty; + public SaStatus ProcessStatus { get; set; } = SaStatus.Unknown; + public SysId ProcessStatusBy { get; set; } = null; + public DateTime ProcessStatusAt { get; set; } = DateTime.MinValue; + public SaStatus ItemStatus { get; set; } = SaStatus.Unknown; + public SysId ItemStatusBy { get; set; } = null; + public DateTime ItemStatusAt { get; set; } = DateTime.MinValue; + } + + //------------------------------------------------------------------- + // Data. + public new class Data : SacsHierarchyItem.Data + { + // Data members. + private readonly DataProperties data = new DataProperties(); + // Construction. + public Data () + { + ProcessStatus = DefaultProcessStatus; + ItemStatus = DefaultItemStatus; + } + // Properties. + public SysId InterfaceId + { + get { return data.InterfaceId; } + set { CheckChanged(data.InterfaceId, value); data.InterfaceId = value; } + } + public SaInterfaceProcessDirection Direction + { + get { return data.Direction; } + set { CheckChanged((int)data.Direction, (int)value); data.Direction = value; } + } + public ItemId ItemId + { + get { return data.ItemId; } + set { CheckChanged(data.ItemId, value); data.ItemId = value; } + } + public String ExternalReference + { + get { return data.ExternalReference; } + set { CheckChanged(data.ExternalReference, value); data.ExternalReference = value; } + } + public SaStatus ProcessStatus + { + get { return data.ProcessStatus; } + set { CheckChanged((int)data.ProcessStatus, (int)value); data.ProcessStatus = value; } + } + public SysId ProcessStatusBy + { + get { return data.ProcessStatusBy; } + set { CheckChanged(data.ProcessStatusBy, value); data.ProcessStatusBy = value; } + } + public DateTime ProcessStatusAt + { + get { return data.ProcessStatusAt; } + set { CheckChanged(data.ProcessStatusAt, value); data.ProcessStatusAt = value; } + } + public SaStatus ItemStatus + { + get { return data.ItemStatus; } + set { CheckChanged((int)data.ItemStatus, (int)value); data.ItemStatus = value; } + } + public SysId ItemStatusBy + { + get { return data.ItemStatusBy; } + set { CheckChanged(data.ItemStatusBy, value); data.ItemStatusBy = value; } + } + public DateTime ItemStatusAt + { + get { return data.ItemStatusAt; } + set { CheckChanged(data.ItemStatusAt, value); data.ItemStatusAt = value; } + } + // Population. + internal void PopulateFrom (Data data) + { + InterfaceId = data.InterfaceId; + Direction = data.Direction; + ItemId = data.ItemId; + ExternalReference = data.ExternalReference; + ProcessStatus = data.ProcessStatus; + ProcessStatusBy = data.ProcessStatusBy; + ProcessStatusAt = data.ProcessStatusAt; + ItemStatus = data.ItemStatus; + ItemStatusBy = data.ItemStatusBy; + ItemStatusAt = data.ItemStatusAt; + 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 InterfaceId { get; set; } = ValidValue.Valid; + public ValidValue Direction { get; set; } = ValidValue.Valid; + public ValidValue ItemType { get; set; } = ValidValue.Valid; + public ValidValue ItemId { get; set; } = ValidValue.Valid; + public ValidValue ExternalReference { get; set; } = ValidValue.Valid; + public ValidValue ProcessStatus { get; set; } = ValidValue.Valid; + public ValidValue ProcessStatusBy { get; set; } = ValidValue.Valid; + public ValidValue ProcessStatusAt { get; set; } = ValidValue.Valid; + public ValidValue ItemStatus { get; set; } = ValidValue.Valid; + public ValidValue ItemStatusBy { get; set; } = ValidValue.Valid; + public ValidValue ItemStatusAt { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + // Flags. + public static readonly Byte Flag_OnHold = 0x01; + + //------------------------------------------------------------------- + // Construction. + public SaInterfaceDataItem (SysId id, SysId interfaceId, SaInterfaceProcessDirection direction, ItemId itemId, String externalReference) : + base(id) + { + base.SetData(data); + InterfaceId = interfaceId; + Direction = direction; + ItemId = itemId; + ExternalReference = externalReference; + ProcessStatus = DefaultProcessStatus; + ItemStatus = DefaultItemStatus; + } + + //------------------------------------------------------------------- + // Process statuses. + private static readonly List validProcessStatuses = new List() + { + SaStatus.Awaiting, + SaStatus.Completed, + SaStatus.Cancelled, + SaStatus.Failed + }; + public static SaStatus DefaultProcessStatus { get { return AwaitingProcessStatus; } } + public static SaStatus AwaitingProcessStatus { get { return SaStatus.Awaiting; } } + public static SaStatus CompletedProcessStatus { get { return SaStatus.Completed; } } + public static SaStatus CancelledProcessStatus { get { return SaStatus.Cancelled; } } + public static SaStatus FailedProcessStatus { get { return SaStatus.Failed; } } + public static List ValidProcessStatuses { get { return new List(validProcessStatuses); } } + public static DataValue.TriStateType GetProcessStatusTriState (SaStatus status) + { + switch (status) + { + case SaStatus.Completed: + return Core.DataValue.TriStateType.Green; + case SaStatus.Awaiting: + return Core.DataValue.TriStateType.Amber; + case SaStatus.Cancelled: + return Core.DataValue.TriStateType.Red; + case SaStatus.Failed: + return Core.DataValue.TriStateType.Yellow; + case SaStatus.Deleted: + return Core.DataValue.TriStateType.Purple; + } + return Core.DataValue.TriStateType.Unknown; + } + + //------------------------------------------------------------------- + // Item Statuses. + private static readonly List validItemStatuses = new List() + { + SaStatus.InProgress, + SaStatus.Imported, + SaStatus.Exported, + SaStatus.Cancelled, + }; + public static SaStatus DefaultItemStatus { get { return SaStatus.Unknown; } } + public static List ValidItemStatuses { get { return new List(validItemStatuses); } } + public static DataValue.TriStateType GetItemStatusTriState (SaStatus status) + { + switch (status) + { + case SaStatus.Imported: + case SaStatus.Exported: + return Core.DataValue.TriStateType.Green; + case SaStatus.InProgress: + return Core.DataValue.TriStateType.Amber; + case SaStatus.Cancelled: + 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 (SysId.IsValid(InterfaceId) && SysId.ToInt(InterfaceId) != SysId.ToInt(data.InterfaceId)) + throw new NotSupportedException(fn + " Interface cannot be changed"); + if ((int)Direction > 0 && (int)Direction != (int)data.Direction) + throw new NotSupportedException(fn + " Direction cannot be changed"); + if (ItemId.Type >= 0 && ItemId.Type != data.ItemId.Type) + throw new NotSupportedException(fn + " Item type cannot be changed"); + if (SysId.IsValid(ItemId.Id) && SysId.ToInt(ItemId.Id) != SysId.ToInt(data.ItemId.Id)) + throw new NotSupportedException(fn + " Item Id cannot be changed"); + if (!StringUtil.IsEmpty(ExternalReference) && !ExternalReference.Equals(data.ExternalReference)) + throw new NotSupportedException(fn + " External reference cannot be changed"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + public SysId InterfaceId + { + get { return data.InterfaceId; } + protected set { data.InterfaceId = value; } + } + public SaInterfaceProcessDirection Direction + { + get { return data.Direction; } + protected set { data.Direction = value; } + } + public ItemId ItemId + { + get { return data.ItemId; } + protected set { data.ItemId = value; } + } + public String ExternalReference + { + get { return data.ExternalReference; } + set { data.ExternalReference = value; } + } + public SaStatus ProcessStatus + { + get { return data.ProcessStatus; } + set { data.ProcessStatus = value; } + } + public SysId ProcessStatusBy + { + get { return data.ProcessStatusBy; } + set { data.ProcessStatusBy = value; } + } + public DateTime ProcessStatusAt + { + get { return data.ProcessStatusAt; } + set { data.ProcessStatusAt = value; } + } + public SaStatus ItemStatus + { + get { return data.ItemStatus; } + set { data.ItemStatus = value; } + } + public SysId ItemStatusBy + { + get { return data.ItemStatusBy; } + set { data.ItemStatusBy = value; } + } + public DateTime ItemStatusAt + { + get { return data.ItemStatusAt; } + set { data.ItemStatusAt = value; } + } + + //------------------------------------------------------------------- + // Flags. + public bool OnHold + { + get { return GetFlag(Flag_OnHold); } + set { SetFlag(Flag_OnHold, value); } + } + + //------------------------------------------------------------------- + // List parameters. + public enum ProcessStatusCat + { + Any, + AwaitingOnly, + CompletedOnly, + } + public enum ItemStatusCat + { + Any, + InProgressOnly, + ImportedOnly, + ExportedOnly, + CancelledOnly, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + public List IncludedIds { get; } = new List(); + public List ExcludedIds { get; } = new List(); + public SysId InterfaceId { get; set; } + public SaInterfaceProcessDirection Direction { get; set; } = SaInterfaceProcessDirection.Unknown; + public short ItemType { get; set; } + public List IncludedItemIds { get; } = new List(); + public String ExternalReference { get; set; } + public ProcessStatusCat ProcessStatusCat { get; set; } = ProcessStatusCat.Any; + public ItemStatusCat ItemStatusCat { get; set; } = ItemStatusCat.Any; + } + + //------------------------------------------------------------------- + /// Mode for filtering associated data items. + public enum FilterMode + { + None, + Exists, + NotExists, + } + + //------------------------------------------------------------------- + // L i s t + //------------------------------------------------------------------- + public class List : List + { + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Interfaces/SaInterfaceDbDfn.cs b/SacsStandardBase/Base/Interfaces/SaInterfaceDbDfn.cs new file mode 100644 index 0000000..1967ec6 --- /dev/null +++ b/SacsStandardBase/Base/Interfaces/SaInterfaceDbDfn.cs @@ -0,0 +1,455 @@ +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:58 +//--------------------------------------------------------------------------- +namespace Sarand.Net.Standard.Base.Interfaces +{ + public class SaInterfaceDbDfn + { + //------------------------------------------------------------------- + // Table identifiers. + public enum TableIdx + { + Unknown = 0, + SaInterface, + SaInterfaceDataItem, + SaInterfaceProcess, + SaInterfaceProcessDataItem, + SaInterfaceProcessMessage, + } + + //------------------------------------------------------------------- + // Column identifiers. + public enum ColumnIdx + { + Unknown = 0, + SaInterface_Id, + SaInterface_Context, + SaInterface_Code, + SaInterface_Category, + SaInterface_Type, + SaInterface_Caption, + SaInterface_Notes, + SaInterface_Flags, + SaInterface_Status, + SaInterface_CreateOn, + SaInterface_CreateBy, + SaInterface_CreateHost, + SaInterface_UpdateOn, + SaInterface_UpdateBy, + SaInterface_UpdateHost, + SaInterfaceDataItem_Id, + SaInterfaceDataItem_InterfaceId, + SaInterfaceDataItem_Direction, + SaInterfaceDataItem_ExternalRef, + SaInterfaceDataItem_ItemType, + SaInterfaceDataItem_ItemId, + SaInterfaceDataItem_ProcessStatus, + SaInterfaceDataItem_ProcessStatusBy, + SaInterfaceDataItem_ProcessStatusAt, + SaInterfaceDataItem_ItemStatus, + SaInterfaceDataItem_ItemStatusBy, + SaInterfaceDataItem_ItemStatusAt, + SaInterfaceDataItem_Notes, + SaInterfaceDataItem_Flags, + SaInterfaceDataItem_CreateOn, + SaInterfaceDataItem_CreateBy, + SaInterfaceDataItem_CreateHost, + SaInterfaceDataItem_UpdateOn, + SaInterfaceDataItem_UpdateBy, + SaInterfaceDataItem_UpdateHost, + SaInterfaceProcess_Id, + SaInterfaceProcess_InterfaceId, + SaInterfaceProcess_Direction, + SaInterfaceProcess_ProcRef, + SaInterfaceProcess_ProcSeq, + SaInterfaceProcess_ProcessAt, + SaInterfaceProcess_BatchAt, + SaInterfaceProcess_ElapseSecs, + SaInterfaceProcess_ItemCount, + SaInterfaceProcess_Flags, + SaInterfaceProcess_Status, + SaInterfaceProcess_CreateOn, + SaInterfaceProcess_CreateBy, + SaInterfaceProcess_CreateHost, + SaInterfaceProcess_UpdateOn, + SaInterfaceProcess_UpdateBy, + SaInterfaceProcess_UpdateHost, + SaInterfaceProcessDataItem_ProcessId, + SaInterfaceProcessDataItem_InterfaceDataItemId, + SaInterfaceProcessDataItem_ItemData, + SaInterfaceProcessDataItem_Flags, + SaInterfaceProcessDataItem_Status, + SaInterfaceProcessDataItem_CreateOn, + SaInterfaceProcessDataItem_CreateBy, + SaInterfaceProcessDataItem_CreateHost, + SaInterfaceProcessDataItem_UpdateOn, + SaInterfaceProcessDataItem_UpdateBy, + SaInterfaceProcessDataItem_UpdateHost, + SaInterfaceProcessMessage_Id, + SaInterfaceProcessMessage_ProcessId, + SaInterfaceProcessMessage_MsgType, + SaInterfaceProcessMessage_Msg, + SaInterfaceProcessMessage_FileName, + SaInterfaceProcessMessage_LineNumber, + SaInterfaceProcessMessage_ItemType, + SaInterfaceProcessMessage_ItemId, + SaInterfaceProcessMessage_Flags, + SaInterfaceProcessMessage_Status, + SaInterfaceProcessMessage_CreateOn, + SaInterfaceProcessMessage_CreateBy, + SaInterfaceProcessMessage_CreateHost, + SaInterfaceProcessMessage_UpdateOn, + SaInterfaceProcessMessage_UpdateBy, + SaInterfaceProcessMessage_UpdateHost, + } + + //------------------------------------------------------------------- + // Data members. + private static readonly int tableCount = 5; + private static readonly int columnCount = 79; + 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) + { + SaInterfaceDbDfn.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) + { + SaInterfaceDbDfn.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 = + { + "SaInterface", + "SaInterfaceDataItem", + "SaInterfaceProcess", + "SaInterfaceProcessDataItem", + "SaInterfaceProcessMessage", + }; + + //------------------------------------------------------------------- + // 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,"Context",0,-9,false,32), + new ColumnDfn(1,"Code",0,-9,false,32), + new ColumnDfn(1,"Category",3,5,false,5), + new ColumnDfn(1,"Type",3,5,false,5), + new ColumnDfn(1,"Caption",0,-9,false,50), + 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,"InterfaceId",4,4,false,10), + new ColumnDfn(2,"Direction",3,5,false,5), + new ColumnDfn(2,"ExternalRef",0,-9,false,64), + new ColumnDfn(2,"ItemType",3,5,false,5), + new ColumnDfn(2,"ItemId",4,4,false,10), + new ColumnDfn(2,"ProcessStatus",0,-9,false,3), + new ColumnDfn(2,"ProcessStatusBy",4,4,false,10), + new ColumnDfn(2,"ProcessStatusAt",9,93,false,23), + new ColumnDfn(2,"ItemStatus",0,-9,false,3), + new ColumnDfn(2,"ItemStatusBy",4,4,false,10), + new ColumnDfn(2,"ItemStatusAt",9,93,false,23), + new ColumnDfn(2,"Notes",0,-9,false,255), + new ColumnDfn(2,"Flags",3,5,false,5), + 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), + new ColumnDfn(3,"Id",4,4,false,10), + new ColumnDfn(3,"InterfaceId",4,4,false,10), + new ColumnDfn(3,"Direction",3,5,false,5), + new ColumnDfn(3,"ProcRef",0,-9,false,32), + new ColumnDfn(3,"ProcSeq",4,4,false,10), + new ColumnDfn(3,"ProcessAt",9,93,false,23), + new ColumnDfn(3,"BatchAt",9,93,false,23), + new ColumnDfn(3,"ElapseSecs",4,4,false,10), + new ColumnDfn(3,"ItemCount",4,4,false,10), + new ColumnDfn(3,"Flags",3,5,false,5), + new ColumnDfn(3,"Status",0,-9,false,3), + new ColumnDfn(3,"CreateOn",9,93,false,23), + new ColumnDfn(3,"CreateBy",4,4,false,10), + new ColumnDfn(3,"CreateHost",0,-9,false,64), + new ColumnDfn(3,"UpdateOn",9,93,false,23), + new ColumnDfn(3,"UpdateBy",4,4,false,10), + new ColumnDfn(3,"UpdateHost",0,-9,false,64), + new ColumnDfn(4,"ProcessId",4,4,false,10), + new ColumnDfn(4,"InterfaceDataItemId",4,4,false,10), + new ColumnDfn(4,"ItemData",0,-9,false,64), + new ColumnDfn(4,"Flags",3,5,false,5), + new ColumnDfn(4,"Status",0,-9,false,3), + new ColumnDfn(4,"CreateOn",9,93,false,23), + new ColumnDfn(4,"CreateBy",4,4,false,10), + new ColumnDfn(4,"CreateHost",0,-9,false,64), + new ColumnDfn(4,"UpdateOn",9,93,false,23), + new ColumnDfn(4,"UpdateBy",4,4,false,10), + new ColumnDfn(4,"UpdateHost",0,-9,false,64), + new ColumnDfn(5,"Id",4,4,false,10), + new ColumnDfn(5,"ProcessId",4,4,false,10), + new ColumnDfn(5,"MsgType",0,12,false,3), + new ColumnDfn(5,"Msg",0,-9,false,1024), + new ColumnDfn(5,"FileName",0,-9,false,64), + new ColumnDfn(5,"LineNumber",4,4,false,10), + new ColumnDfn(5,"ItemType",3,5,false,5), + new ColumnDfn(5,"ItemId",4,4,false,10), + new ColumnDfn(5,"Flags",3,5,false,5), + new ColumnDfn(5,"Status",0,12,false,3), + new ColumnDfn(5,"CreateOn",9,93,false,23), + new ColumnDfn(5,"CreateBy",4,4,false,10), + new ColumnDfn(5,"CreateHost",0,-9,false,64), + new ColumnDfn(5,"UpdateOn",9,93,false,23), + new ColumnDfn(5,"UpdateBy",4,4,false,10), + new ColumnDfn(5,"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/Interfaces/SaInterfaceMgr-InterfaceDataItems.cs b/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceDataItems.cs new file mode 100644 index 0000000..8300e9e --- /dev/null +++ b/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceDataItems.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +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 Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.Interfaces +{ + //======================================================================= + public partial class SaInterfaceMgr + { + //******************************************************************* + // I n t e r f a c e D a t a I t e m s + //******************************************************************* + // Create Interface Data Item. + public abstract SaInterfaceDataItem CreateInterfaceDataItem (SysId interfaceId, SaInterfaceProcessDirection direction, ItemId itemId, String externalReference); + + // Retrieve individual interface data items. + public abstract bool InterfaceDataItemExistsById (SysId id); + public abstract SaInterfaceDataItem GetInterfaceDataItemById (SysId id); + public abstract SaInterfaceDataItem GetInterfaceDataItem (SaInterfaceDataItem.Params prm); + + //------------------------------------------------------------------- + // Validate Interface Data Item. + public bool IsValidInterfaceDataItem (SaInterfaceDataItem interfaceDataItem) + { + ClearErrorState(); + if (interfaceDataItem == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidInterfaceDataItemData(interfaceDataItem.GetData(), new SaInterfaceDataItem.ErrField()); + } + public virtual bool IsValidInterfaceDataItemData (SaInterfaceDataItem.Data data, SaInterfaceDataItem.ErrField errField) + { + ClearErrorState(); + // Interface. + if (errField.InterfaceId != SacsHierarchyItem.ValidValue.Ignore && !SysId.IsValid(data.InterfaceId)) + { + errField.InterfaceId = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Interface must be supplied"); + } + if (errField.Direction != SacsHierarchyItem.ValidValue.Ignore && (int)data.Direction < 1) + { + errField.Direction = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Direction must be supplied"); + } + // Data item. + if (errField.ItemType != SacsHierarchyItem.ValidValue.Ignore && (data.ItemId == null || data.ItemId.Type < 1)) + { + errField.ItemType = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Invalid item type"); + } + if (errField.ItemId != SacsHierarchyItem.ValidValue.Ignore && (data.ItemId == null || !SysId.IsValid(data.ItemId.Id))) + { + errField.ItemId = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Invalid item id"); + } + if (errField.ExternalReference != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.ExternalReference, "reference", (long)SaInterfaceDbDfn.GetColTextLen(SaInterfaceDbDfn.ColumnIdx.SaInterfaceDataItem_ExternalRef))) + { + errField.ExternalReference = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + // Process status. + if (errField.ProcessStatus != SacsHierarchyItem.ValidValue.Ignore) + { + if (!IsValidValue(SacsSystem.GetStatusCode(data.ProcessStatus), "process status", (long)SaInterfaceDbDfn.GetColTextLen(SaInterfaceDbDfn.ColumnIdx.SaInterfaceDataItem_ProcessStatus), true)) + { + errField.ProcessStatus = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!SaInterfaceDataItem.ValidProcessStatuses.Contains(data.ProcessStatus)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Invalid process status"); + } + } + // Item status. + if (errField.ItemStatus != SacsHierarchyItem.ValidValue.Ignore) + { + if (!IsValidValue(SacsSystem.GetStatusCode(data.ItemStatus), "item status", (long)SaInterfaceDbDfn.GetColTextLen(SaInterfaceDbDfn.ColumnIdx.SaInterfaceDataItem_ItemStatus), true)) + { + errField.ItemStatus = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!SaInterfaceDataItem.ValidItemStatuses.Contains(data.ItemStatus)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Invalid item status"); + } + } + // Common properties. + if (errField.Notes != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Notes, "notes", (long)SaInterfaceDbDfn.GetColTextLen(SaInterfaceDbDfn.ColumnIdx.SaInterfaceDataItem_Notes))) + { + errField.Notes = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + // NB No Status on Interface Data Item. + return true; + } + + //------------------------------------------------------------------- + // Interface Data Item database interface. + public abstract bool InsertInterfaceDataItem (SaInterfaceDataItem interfaceDataItem, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateInterfaceDataItem (SaInterfaceDataItem interfaceDataItem, DbUpdateInfo dbUpdateInfo); + public virtual bool InterfaceDataItemCanBeDeleted (SaInterfaceDataItem interfaceDataItem) + { + ClearErrorState(); + return true; + } + public abstract bool DeleteInterfaceDataItem (SaInterfaceDataItem interfaceDataItem, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // Interface Data Item list. + public abstract ColumnDfn GetInterfaceDataItemColumnDfn (SaInterfaceDataItem.Column columnId, String caption = "", int width = 0); + public abstract List GetInterfaceDataItemQueryFilter (SaInterfaceDataItem.Params prm); + public abstract long GetInterfaceDataItemCount (SaInterfaceDataItem.Params prm); + public abstract List GetInterfaceDataItemList (SaInterfaceDataItem.Params prm); + public abstract DbResultSet ExecuteInterfaceDataItemQuery (List columns, SaInterfaceDataItem.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 GetInterfaceDataItemDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + switch ((SaInterfaceDataItem.Column)column.Id) + { + //----------------------------------------------------------- + // Category. + case SaInterfaceDataItem.Column.Direction_Desc: + { + SaInterfaceProcessDirection direction = (SaInterfaceProcessDirection)StringUtil.ToInt(values.GetValue(column.DataName).ToString()); + value = new DataValue(ProcessDirections.GetCodeForType(direction, direction.ToString())); + } + break; + + //----------------------------------------------------------- + // Item Type. + case SaInterfaceDataItem.Column.ItemType_Desc: + { + short itemType = StringUtil.ToShort(values.GetValue(column.DataName).ToString()); + String itemTypeDesc = InterfaceConsole.GetItemTypeDesc(itemType); + value = new DataValue(itemTypeDesc); + } + break; + + //----------------------------------------------------------- + // Item Type. + case SaInterfaceDataItem.Column.Item_Caption: + { + short itemType = StringUtil.ToShort(values.GetValue(GetInterfaceProcessMessageColumnDfn(SaInterfaceProcessMessage.Column.ItemType).DataName).ToString()); + SysId itemId = new SysId(StringUtil.ToInt(values.GetValue(column.DataName).ToString())); + String itemTypeDesc = InterfaceConsole.GetItemCaption(new ItemId(itemType,itemId)); + value = new DataValue(itemTypeDesc); + } + break; + + //----------------------------------------------------------- + // Process Status. + case SaInterfaceDataItem.Column.ProcessStatus_Desc: + case SaInterfaceDataItem.Column.ProcessStatus_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaInterfaceDataItem.Column.ProcessStatus_Desc) + value = new DataValue(SacsSystem.GetStatusDescForCode(statusCode)); + else if (column.Id == (int)SaInterfaceDataItem.Column.ProcessStatus_Ind) + value = new DataValue(SaInterfaceDataItem.GetProcessStatusTriState(status)); + } + break; + + //----------------------------------------------------------- + // Item Status. + case SaInterfaceDataItem.Column.ItemStatus_Desc: + case SaInterfaceDataItem.Column.ItemStatus_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaInterfaceDataItem.Column.ItemStatus_Desc) + value = new DataValue(SacsSystem.GetStatusDescForCode(statusCode)); + else if (column.Id == (int)SaInterfaceDataItem.Column.ItemStatus_Ind) + value = new DataValue(SaInterfaceDataItem.GetItemStatusTriState(status)); + } + break; + + //----------------------------------------------------------- + // Notes. + case SaInterfaceDataItem.Column.Notes: + { + value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200)); + } + break; + + //----------------------------------------------------------- + } + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceProcessDataItems.cs b/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceProcessDataItems.cs new file mode 100644 index 0000000..08244b2 --- /dev/null +++ b/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceProcessDataItems.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Interfaces +{ + //======================================================================= + public partial class SaInterfaceMgr + { + //******************************************************************* + // I n t e r f a c e P r o c e s s D a t a I t e m s + //******************************************************************* + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceProcessMessages.cs b/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceProcessMessages.cs new file mode 100644 index 0000000..4233251 --- /dev/null +++ b/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceProcessMessages.cs @@ -0,0 +1,166 @@ +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 Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Interfaces +{ + //======================================================================= + public partial class SaInterfaceMgr + { + //******************************************************************* + // I n t e r f a c e P r o c e s s M e s s a g e s + //******************************************************************* + // Retrieve individual interface process messages. + public abstract bool InterfaceProcessMessageExistsById (SysId id); + public abstract SaInterfaceProcessMessage GetInterfaceProcessMessageById (SysId id); + public abstract SaInterfaceProcessMessage GetInterfaceProcessMessage (SaInterfaceProcessMessage.Params prm); + + // Create Interface Process Message. + public abstract SaInterfaceProcessMessage CreateInterfaceProcessMessage (SysId interfaceProcessId); + + //------------------------------------------------------------------- + // Validate Interface Process Message. + public bool IsValidInterfaceProcessMessage (SaInterfaceProcessMessage interfaceProcessMessage) + { + ClearErrorState(); + if (interfaceProcessMessage == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidInterfaceProcessMessageData(interfaceProcessMessage.GetData(), new SaInterfaceProcessMessage.ErrField()); + } + public virtual bool IsValidInterfaceProcessMessageData (SaInterfaceProcessMessage.Data data, SaInterfaceProcessMessage.ErrField errField) + { + ClearErrorState(); + if (errField.InterfaceProcessId != SacsHierarchyItem.ValidValue.Ignore && !SysId.IsValid(data.InterfaceProcessId)) + { + errField.InterfaceProcessId = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Process must be supplied"); + } + if (errField.Level != SacsHierarchyItem.ValidValue.Ignore && (int)data.Level < 1) + { + errField.Level = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Level must be supplied"); + } + if (errField.Level != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(messageLevels.GetCodeForType(data.Level), "level", (long)SaInterfaceDbDfn.GetColTextLen(SaInterfaceDbDfn.ColumnIdx.SaInterfaceProcessMessage_MsgType), true, true)) + { + errField.Level = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Message != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Message, "message", (long)SaInterfaceDbDfn.GetColTextLen(SaInterfaceDbDfn.ColumnIdx.SaInterfaceProcessMessage_Msg), true)) + { + errField.Message = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Filename != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Filename, "filename", (long)SaInterfaceDbDfn.GetColTextLen(SaInterfaceDbDfn.ColumnIdx.SaInterfaceProcessMessage_FileName))) + { + errField.Filename = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.LineNumber != SacsHierarchyItem.ValidValue.Ignore && data.LineNumber < 0) + { + errField.LineNumber = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Invalid line number"); + } + // Common properties. + if (errField.Status != SacsHierarchyItem.ValidValue.Ignore && !SaInterfaceProcessMessage.ValidStatuses.Contains(data.Status)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState(ErrorMessage.InvalidStatus); + } + return true; + } + + //------------------------------------------------------------------- + // Interface Process Message database interface. + public abstract bool InsertInterfaceProcessMessage (SaInterfaceProcessMessage interfaceProcessMessage, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateInterfaceProcessMessage (SaInterfaceProcessMessage interfaceProcessMessage, DbUpdateInfo dbUpdateInfo); + public virtual bool InterfaceProcessMessageCanBeDeleted (SaInterfaceProcessMessage interfaceProcessMessage) + { + ClearErrorState(); + return true; + } + public abstract bool DeleteInterfaceProcessMessage (SaInterfaceProcessMessage interfaceProcessMessage, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // Interface Process Message list. + public abstract ColumnDfn GetInterfaceProcessMessageColumnDfn (SaInterfaceProcessMessage.Column columnId, String caption = "", int width = 0); + public abstract List GetInterfaceProcessMessageQueryFilter (SaInterfaceProcessMessage.Params prm); + public abstract long GetInterfaceProcessMessageCount (SaInterfaceProcessMessage.Params prm); + public abstract List GetInterfaceProcessMessageList (SaInterfaceProcessMessage.Params prm); + public abstract DbResultSet ExecuteInterfaceProcessMessageQuery (List columns, SaInterfaceProcessMessage.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 GetInterfaceProcessMessageDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + switch ((SaInterfaceProcessMessage.Column)column.Id) + { + //----------------------------------------------------------- + // Category. + case SaInterfaceProcessMessage.Column.Level_Desc: + { + String level = values.GetValue(column.DataName).ToString(); + // Legacy 'Warning' code: 'WAR'. + if (level.EndsWithCI(MessageTypeWarningAlt)) level = messageLevels.GetCodeForType(SaMessageLevel.Warning); + value = new DataValue(messageLevels.GetDescForCode(level, level)); + } + break; + + //----------------------------------------------------------- + // Item Type. + case SaInterfaceProcessMessage.Column.ItemType_Desc: + { + short itemType = StringUtil.ToShort(values.GetValue(column.DataName).ToString()); + String itemTypeDesc = InterfaceConsole.GetItemTypeDesc(itemType); + value = new DataValue(itemTypeDesc); + } + break; + + //----------------------------------------------------------- + // Item Type. + case SaInterfaceProcessMessage.Column.Item_Caption: + { + short itemType = StringUtil.ToShort(values.GetValue(GetInterfaceProcessMessageColumnDfn(SaInterfaceProcessMessage.Column.ItemType).DataName).ToString()); + SysId itemId = new SysId(StringUtil.ToInt(values.GetValue(column.DataName).ToString())); + String itemTypeDesc = InterfaceConsole.GetItemCaption(new ItemId(itemType,itemId)); + value = new DataValue(itemTypeDesc); + } + break; + + //----------------------------------------------------------- + // Notes. + case SaInterfaceProcessMessage.Column.Notes: + { + value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200)); + } + break; + + //----------------------------------------------------------- + // Status. + case SaInterfaceProcessMessage.Column.Status_Desc: + case SaInterfaceProcessMessage.Column.Status_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaInterfaceProcessMessage.Column.Status_Desc) + value = new DataValue(SacsSystem.GetStatusDescForCode(statusCode)); + else if (column.Id == (int)SaInterfaceProcessMessage.Column.Status_Ind) + value = new DataValue(SaInterfaceProcessMessage.GetStatusTriState(status)); + } + break; + + //----------------------------------------------------------- + } + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceProcesses.cs b/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceProcesses.cs new file mode 100644 index 0000000..30de11c --- /dev/null +++ b/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-InterfaceProcesses.cs @@ -0,0 +1,238 @@ +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 Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Interfaces +{ + //======================================================================= + public partial class SaInterfaceMgr + { + //******************************************************************* + // I n t e r f a c e P r o c e s s e s + //******************************************************************* + // Data members. + protected static readonly String colInterfaceProcess_MessageStatusErr = "IntPrcMessageStatusErr"; + protected static readonly String colInterfaceProcess_MessageStatusWrn = "IntPrcMessageStatusWrn"; + protected static readonly String colInterfaceProcess_MessageStatusInf = "IntPrcMessageStatusInf"; + + //------------------------------------------------------------------- + // Associated data. + public abstract int GetInterfaceProcessMessageCount (SysId id); + public abstract bool InterfaceProcessHasMessages (SysId id); + public abstract int GetInterfaceProcessDataItemCount (SysId id); + public abstract bool InterfaceProcessHasDataItems (SysId id); + + //------------------------------------------------------------------- + // Retrieve individual interface processes. + public abstract bool InterfaceProcessExistsById (SysId id); + public abstract SaInterfaceProcess GetInterfaceProcessById (SysId id); + public abstract SaInterfaceProcess GetInterfaceProcess (SaInterfaceProcess.Params prm); + + // Create Interface Process. + public abstract SaInterfaceProcess CreateInterfaceProcess (SysId interfaceId, SaInterfaceProcessDirection direction, String reference, int sequence); + + //------------------------------------------------------------------- + // Validate Interface Process. + public bool IsValidInterfaceProcess (SaInterfaceProcess interfaceProcess) + { + ClearErrorState(); + if (interfaceProcess == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidInterfaceProcessData(interfaceProcess.GetData(), new SaInterfaceProcess.ErrField()); + } + public virtual bool IsValidInterfaceProcessData (SaInterfaceProcess.Data data, SaInterfaceProcess.ErrField errField) + { + ClearErrorState(); + if (errField.InterfaceId != SacsHierarchyItem.ValidValue.Ignore && !SysId.IsValid(data.InterfaceId)) + { + errField.InterfaceId = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Interface must be supplied"); + } + if (errField.Direction != SacsHierarchyItem.ValidValue.Ignore && (int)data.Direction < 1) + { + errField.Direction = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Direction must be supplied"); + } + if (errField.Reference != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Reference, "reference", (long)SaInterfaceDbDfn.GetColTextLen(SaInterfaceDbDfn.ColumnIdx.SaInterfaceProcess_ProcRef))) + { + errField.Reference = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Sequence != SacsHierarchyItem.ValidValue.Ignore && data.Sequence < 1) + { + errField.Sequence = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Sequence must be supplied"); + } + // Common properties. + if (errField.Status != SacsHierarchyItem.ValidValue.Ignore && !SaInterfaceProcess.ValidStatuses.Contains(data.Status)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState(ErrorMessage.InvalidStatus); + } + return true; + } + + //------------------------------------------------------------------- + // Interface Process database interface. + public abstract bool InsertInterfaceProcess (SaInterfaceProcess interfaceProcess, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateInterfaceProcess (SaInterfaceProcess interfaceProcess, DbUpdateInfo dbUpdateInfo); + public virtual bool InterfaceProcessCanBeDeleted (SaInterfaceProcess interfaceProcess) + { + ClearErrorState(); + if (InterfaceProcessHasMessages(interfaceProcess.Id)) return SetErrorState("Process has messages"); + if (InterfaceProcessHasDataItems(interfaceProcess.Id)) return SetErrorState("Process has data items"); + return true; + } + public abstract bool ClearInterfaceProcessMessages (List interfaceProcessIds, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearInterfaceProcessDataItems (List interfaceProcessIds, DbUpdateInfo dbUpdateInfo); + public abstract bool DeleteInterfaceProcess (SaInterfaceProcess interfaceProcess, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // Interface Process list. + public abstract ColumnDfn GetInterfaceProcessColumnDfn (SaInterfaceProcess.Column columnId, String caption = "", int width = 0); + public abstract List GetInterfaceProcessQueryFilter (SaInterfaceProcess.Params prm); + public abstract long GetInterfaceProcessCount (SaInterfaceProcess.Params prm); + public abstract List GetInterfaceProcessList (SaInterfaceProcess.Params prm); + public abstract DbResultSet ExecuteInterfaceProcessQuery (List columns, SaInterfaceProcess.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 GetInterfaceProcessDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + switch ((SaInterfaceProcess.Column)column.Id) + { + //----------------------------------------------------------- + // Category. + case SaInterfaceProcess.Column.Interface_Category_Desc: + { + SaInterfaceCategory category = (SaInterfaceCategory)StringUtil.ToInt(values.GetValue(column.DataName).ToString()); + value = new DataValue(interfaceCategories.GetCodeForType(category, category.ToString())); + break; + } + + //----------------------------------------------------------- + // Type. + case SaInterfaceProcess.Column.Interface_Type_Desc: + { + String context = values.GetValue(GetInterfaceProcessColumnDfn(SaInterfaceProcess.Column.Interface_Context).DataName).ToString(); + SaInterfaceCategory category = (SaInterfaceCategory)StringUtil.ToInt(values.GetValue(GetInterfaceProcessColumnDfn(SaInterfaceProcess.Column.Interface_Category).DataName).ToString()); + short type = StringUtil.ToShort(values.GetValue(column.DataName).ToString()); + String typeDesc = InterfaceConsole.GetInterfaceTypeDesc(context,category,type); + if (StringUtil.IsEmpty(typeDesc)) typeDesc = type.ToString(); + value = new DataValue(typeDesc); + break; + } + + //----------------------------------------------------------- + // Process Direction. + case SaInterfaceProcess.Column.ProcessDirection_Desc: + { + SaInterfaceProcessDirection direction = (SaInterfaceProcessDirection)StringUtil.ToInt(values.GetValue(column.DataName).ToString()); + value = new DataValue(processDirections.GetCodeForType(direction, direction.ToString())); + break; + } + + //----------------------------------------------------------- + // Processed & Batch DateTimes. + case SaInterfaceProcess.Column.ProcessAt: + case SaInterfaceProcess.Column.ProcessAt_Date: + case SaInterfaceProcess.Column.ProcessAt_Time: + { + // Skip if 'ProcessAt' db column exists. + if (ColumnExists(SaInterfaceDbDfn.ColumnIdx.SaInterfaceProcess_ProcessAt)) break; + int processDate = (int)values.GetValue(GetInterfaceProcessColumnDfn(SaInterfaceProcess.Column.ProcessAt_Date).DataName).AsLong(); + int processTime = (int)values.GetValue(GetInterfaceProcessColumnDfn(SaInterfaceProcess.Column.ProcessAt_Time).DataName).AsLong(); + DateTime processAt = DateUtil.Int32ToDate(processDate); + processAt = processAt.SetTime(DateUtil.Int32ToTime(processTime)); + value = new DataValue(processAt); + break; + } + + case SaInterfaceProcess.Column.BatchAt: + case SaInterfaceProcess.Column.BatchAt_Date: + case SaInterfaceProcess.Column.BatchAt_Time: + { + // Skip if 'BatchAt' db column exists. + if (ColumnExists(SaInterfaceDbDfn.ColumnIdx.SaInterfaceProcess_BatchAt)) break; + int batchDate = (int)values.GetValue(GetInterfaceProcessColumnDfn(SaInterfaceProcess.Column.BatchAt_Date).DataName).AsLong(); + int batchTime = (int)values.GetValue(GetInterfaceProcessColumnDfn(SaInterfaceProcess.Column.BatchAt_Time).DataName).AsLong(); + DateTime batchAt = DateUtil.Int32ToDate(batchDate); + batchAt = batchAt.SetTime(DateUtil.Int32ToTime(batchTime)); + value = new DataValue(batchAt); + break; + } + + //----------------------------------------------------------- + // Elapsed time. + case SaInterfaceProcess.Column.Elapsed_Fmt: + { + int elapsedSecs = StringUtil.ToInt(values.GetValue(column.DataName).ToString()); + value = new DataValue(DateUtil.FormatNumberOfSeconds(elapsedSecs)); + break; + } + + //----------------------------------------------------------- + // Status. + case SaInterfaceProcess.Column.Message_Status_Desc: + case SaInterfaceProcess.Column.Message_Status_Ind: + { + long messageCountErr = values.GetValue(colInterfaceProcess_MessageStatusErr).AsLong(); + long messageCountWrn = values.GetValue(colInterfaceProcess_MessageStatusWrn).AsLong(); + long messageCountInf = values.GetValue(colInterfaceProcess_MessageStatusInf).AsLong(); + if (column.Id == (int)SaInterfaceProcess.Column.Message_Status_Desc) + { + String statusDesc = ""; + if (messageCountErr > 0) statusDesc = messageLevels.GetCodeForType(SaMessageLevel.Error, "Error"); + else if (messageCountWrn > 0) statusDesc = messageLevels.GetCodeForType(SaMessageLevel.Warning, "Warning"); + else if (messageCountInf > 0) statusDesc = messageLevels.GetCodeForType(SaMessageLevel.Info, "Info"); + value = new DataValue(statusDesc); + } + else if (column.Id == (int)SaInterfaceProcess.Column.Message_Status_Ind) + { + DataValue.TriStateType triState = DataValue.TriStateType.Unknown; + if (messageCountErr > 0) triState = DataValue.TriStateType.Red; + else if (messageCountWrn > 0) triState = DataValue.TriStateType.Amber; + else if (messageCountInf > 0) triState = DataValue.TriStateType.Green; + value = new DataValue(triState); + } + break; + } + + //----------------------------------------------------------- + // Notes. + case SaInterfaceProcess.Column.Notes: + { + value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200)); + break; + } + + //----------------------------------------------------------- + // Status. + case SaInterfaceProcess.Column.Status_Desc: + case SaInterfaceProcess.Column.Status_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaInterfaceProcess.Column.Status_Desc) + value = new DataValue(SacsSystem.GetStatusDescForCode(statusCode)); + else if (column.Id == (int)SaInterfaceProcess.Column.Status_Ind) + value = new DataValue(SaInterfaceProcess.GetStatusTriState(status)); + break; + } + + //----------------------------------------------------------- + } + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-Interfaces.cs b/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-Interfaces.cs new file mode 100644 index 0000000..099dffc --- /dev/null +++ b/SacsStandardBase/Base/Interfaces/SaInterfaceMgr-Interfaces.cs @@ -0,0 +1,210 @@ +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 Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Interfaces +{ + //======================================================================= + public partial class SaInterfaceMgr + { + + //******************************************************************* + // I n t e r f a c e s + //******************************************************************* + // Associated data. + public abstract bool InterfaceHasProcesses (SysId id); + public abstract bool InterfaceHasDataItems (SysId id); + // Process sequence. + public abstract int GetMaxProcessSequenceForInterface (SysId interfaceId, SaInterfaceProcessDirection processDirection); + + //------------------------------------------------------------------- + // Retrieve individual interfaces. + public abstract bool InterfaceExistsById (SysId id); + public abstract bool InterfaceExistsByCode (String code); + public abstract SaInterface GetInterfaceById (SysId id); + public abstract SaInterface GetInterfaceByContextCode (String context, String code); + public abstract SaInterface GetInterface (SaInterface.Params prm); + + // Create Interface. + public abstract SaInterface CreateInterface (String context, SaInterfaceCategory category, short type); + + //------------------------------------------------------------------- + // Validate Interface. + public bool IsValidInterface (SaInterface interfaceObj) + { + ClearErrorState(); + if (interfaceObj == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidInterfaceData(interfaceObj.GetData(), new SaInterface.ErrField()); + } + public virtual bool IsValidInterfaceData (SaInterface.Data data, SaInterface.ErrField errField) + { + ClearErrorState(); + if (errField.Context != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Context, "context", (long)SaInterfaceDbDfn.GetColTextLen(SaInterfaceDbDfn.ColumnIdx.SaInterface_Context))) + { + errField.Context = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Category != SacsHierarchyItem.ValidValue.Ignore && (int)data.Category < 1) + { + errField.Category = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Category must be supplied"); + } + if (errField.Type != SacsHierarchyItem.ValidValue.Ignore && data.Type < 1) + { + errField.Type = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Type must be supplied"); + } + if (errField.Code != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Code, "code", (long)SaInterfaceDbDfn.GetColTextLen(SaInterfaceDbDfn.ColumnIdx.SaInterface_Code), true)) + { + errField.Code = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + // Code must be only alphanumeric with hyphens and underscores. + String codeTest = data.Code.Replace("-","").Replace("_",""); + if (StringUtil.IsEmpty(codeTest) || !StringUtil.IsAlphaNumeric(codeTest)) + { + SetErrorState("Code must only consts if letters, numbers, hyphens and underscores, and have at least one letter or number"); + errField.Code = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + // Code must be unique. + SaInterface interfaceByCode = GetInterfaceByContextCode(data.Context, data.Code); + if (interfaceByCode != null && !data.Id.Equals(interfaceByCode.Id)) + { + SetErrorState("Code is already in use by the '" + interfaceByCode.Caption + "' interface."); + errField.Code = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + // Caption. + if (errField.Caption != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Caption, "caption", (long)SaInterfaceDbDfn.GetColTextLen(SaInterfaceDbDfn.ColumnIdx.SaInterface_Caption), true)) + { + errField.Caption = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + // Common properties. + if (errField.Notes != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Notes, "notes", (long)SaInterfaceDbDfn.GetColTextLen(SaInterfaceDbDfn.ColumnIdx.SaInterface_Notes))) + { + errField.Notes = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Status != SacsHierarchyItem.ValidValue.Ignore && !SaInterface.ValidStatuses.Contains(data.Status)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState(ErrorMessage.InvalidStatus); + } + return true; + } + + //------------------------------------------------------------------- + // Interface database interface. + public abstract bool InsertInterface (SaInterface interfaceObj, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateInterface (SaInterface interfaceObj, DbUpdateInfo dbUpdateInfo); + public virtual bool InterfaceCanBeDeleted (SaInterface interfaceObj) + { + ClearErrorState(); + if (InterfaceHasProcesses(interfaceObj.Id)) return SetErrorState("Interface has processes"); + if (InterfaceHasDataItems(interfaceObj.Id)) return SetErrorState("Interface has data items"); + return true; + } + public abstract bool DeleteInterface (SaInterface interfaceObj, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // Interface list. + public abstract ColumnDfn GetInterfaceColumnDfn (SaInterface.Column columnId, String caption = "", int width = 0); + public abstract List GetInterfaceQueryFilter (SaInterface.Params prm); + public abstract long GetInterfaceCount (SaInterface.Params prm); + public abstract List GetInterfaceList (SaInterface.Params prm); + public abstract DbResultSet ExecuteInterfaceQuery (List columns, SaInterface.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 GetInterfaceDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + switch ((SaInterface.Column)column.Id) + { + //----------------------------------------------------------- + // Category. + case SaInterface.Column.Category_Desc: + { + SaInterfaceCategory category = (SaInterfaceCategory)StringUtil.ToInt(values.GetValue(column.DataName).ToString()); + value = new DataValue(interfaceCategories.GetCodeForType(category, category.ToString())); + } + break; + + //----------------------------------------------------------- + // Type. + case SaInterface.Column.Type_Desc: + { + String context = values.GetValue(GetInterfaceColumnDfn(SaInterface.Column.Context).DataName).ToString(); + SaInterfaceCategory category = (SaInterfaceCategory)StringUtil.ToInt(values.GetValue(GetInterfaceColumnDfn(SaInterface.Column.Category).DataName).ToString()); + short type = StringUtil.ToShort(values.GetValue(column.DataName).ToString()); + String typeDesc = InterfaceConsole.GetInterfaceTypeDesc(context, category, type); + if (StringUtil.IsEmpty(typeDesc)) typeDesc = type.ToString(); + value = new DataValue(typeDesc); + } + break; + + //----------------------------------------------------------- + // Item type. + case SaInterface.Column.Active_Y: + case SaInterface.Column.Active_TickCross: + case SaInterface.Column.Active_Ind: + { + ushort flags = (ushort)values.GetValue(column.DataName).AsLong(); + bool activeInterface = SacsHierarchyItem.GetFlag(flags, SaInterface.Flag_Active); + if (column.Id == (int)SaInterface.Column.Active_Y) + value = new DataValue(activeInterface ? "Y" : ""); + else if (column.Id == (int)SaInterface.Column.Active_Ind) + value = new DataValue(activeInterface ? DataValue.TriStateType.Green : DataValue.TriStateType.Red); + else + value = new DataValue(activeInterface); + } + break; + + //----------------------------------------------------------- + // Notes. + case SaInterface.Column.Notes: + { + value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200)); + } + break; + + //----------------------------------------------------------- + // Status. + case SaInterface.Column.Status_Desc: + case SaInterface.Column.Status_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaInterface.Column.Status_Desc) + { + String statusDesc = SacsSystem.GetStatusDescForCode(statusCode); + value = new DataValue(statusDesc); + } + else if (column.Id == (int)SaInterface.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; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Interfaces/SaInterfaceMgr.cs b/SacsStandardBase/Base/Interfaces/SaInterfaceMgr.cs new file mode 100644 index 0000000..9478d96 --- /dev/null +++ b/SacsStandardBase/Base/Interfaces/SaInterfaceMgr.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Core; + +namespace Sarand.Net.Standard.Base.Interfaces +{ + //======================================================================= + public abstract partial class SaInterfaceMgr : SacsItemMgr + { + //------------------------------------------------------------------- + // Data members. + private static readonly List interfaceCategories = SacsSystem.GetCodesOfType(SaSysCodeType.InterfaceCategory); + private static readonly List processDirections = SacsSystem.GetCodesOfType(SaSysCodeType.InterfaceProcessDirection); + private static readonly List messageLevels = SacsSystem.GetCodesOfType(SaSysCodeType.MessageLevel); + + //------------------------------------------------------------------- + // Static initialisation. + static SaInterfaceMgr () + { + } + + //------------------------------------------------------------------- + // Construction. + protected SaInterfaceMgr (SaInterfaceConsole interfaceConsole) + { + InterfaceConsole = interfaceConsole; + } + + //------------------------------------------------------------------- + // Properties. + protected SaInterfaceConsole InterfaceConsole { get; } + protected static List InterfaceCategories { get { return new List(interfaceCategories); } } + protected static List ProcessDirections { get { return new List(processDirections); } } + protected static List MessageLevels { get { return new List(messageLevels); } } + // Database. + public abstract bool DatabaseIsValid { get; } + public abstract bool CanUpdateDatabase { get; } + // Alternate 'Warning' message level code (pre 2014 Sapphire). + protected static String MessageTypeWarningAlt { get; } = "WAR"; + + //------------------------------------------------------------------- + // Utilities. + public abstract bool ColumnExists (SaInterfaceDbDfn.ColumnIdx columnIdx); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Interfaces/SaInterfaceProcess.cs b/SacsStandardBase/Base/Interfaces/SaInterfaceProcess.cs new file mode 100644 index 0000000..f7ec594 --- /dev/null +++ b/SacsStandardBase/Base/Interfaces/SaInterfaceProcess.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.Interfaces +{ + //======================================================================= + public class SaInterfaceProcess : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Pos = 0, + Id, + // Interface. + InterfaceId, + Interface_Context, + Interface_Category, + Interface_Category_Desc, + Interface_Type, + Interface_Type_Desc, + Interface_Code, + Interface_Caption, + // Interface process. + ProcessDirection, + ProcessDirection_Desc, + ProcessReference, + ProcessSequence, + ProcessAt, + ProcessAt_Date, + ProcessAt_Time, + BatchAt, + BatchAt_Date, + BatchAt_Time, + Elapsed_Secs, + Elapsed_Fmt, + // Derived properties. + Message_Count, + Message_Status_Desc, + Message_Status_Ind, + DataItem_Count, + // Common properties. + Notes, + Status, + Status_Desc, + Status_Ind, + CreateOn_Date, + UpdateOn_Date, + UpdateHost, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public SysId InterfaceId { get; set; } = null; + public SaInterfaceProcessDirection Direction { get; set; } = SaInterfaceProcessDirection.Unknown; + public String Reference { get; set; } = String.Empty; + public int Sequence { get; set; } = 0; + public DateTime ProcessAt { get; set; } = DateTime.MinValue; + public DateTime BatchAt { get; set; } = DateTime.MinValue; + public int ElapseSecs { get; set; } = 0; + } + + //------------------------------------------------------------------- + // Data. + public new class Data : SacsHierarchyItem.Data + { + // Data members. + private readonly DataProperties data = new DataProperties(); + // Construction. + public Data () + { + Status = DefaultStatus; + } + // Properties. + public SysId InterfaceId + { + get { return data.InterfaceId; } + set { CheckChanged(data.InterfaceId, value); data.InterfaceId = value; } + } + public SaInterfaceProcessDirection Direction + { + get { return data.Direction; } + set { CheckChanged((int)data.Direction, (int)value); data.Direction = value; } + } + public String Reference + { + get { return data.Reference; } + set { CheckChanged(data.Reference, value); data.Reference = value; } + } + public int Sequence + { + get { return data.Sequence; } + set { CheckChanged(data.Sequence, value); data.Sequence = value; } + } + public DateTime ProcessAt + { + get { return data.ProcessAt; } + set { CheckChanged(data.ProcessAt, value); data.ProcessAt = value; } + } + public DateTime BatchAt + { + get { return data.BatchAt; } + set { CheckChanged(data.BatchAt, value); data.BatchAt = value; } + } + public int ElapseSecs + { + get { return data.ElapseSecs; } + set { CheckChanged(data.ElapseSecs, value); data.ElapseSecs = value; } + } + // Population. + internal void PopulateFrom (Data data) + { + InterfaceId = data.InterfaceId; + Direction = data.Direction; + Reference = data.Reference; + Sequence = data.Sequence; + ProcessAt = data.ProcessAt; + BatchAt = data.BatchAt; + ElapseSecs = data.ElapseSecs; + 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 InterfaceId { get; set; } = ValidValue.Valid; + public ValidValue Direction { get; set; } = ValidValue.Valid; + public ValidValue Reference { get; set; } = ValidValue.Valid; + public ValidValue Sequence { get; set; } = ValidValue.Valid; + public ValidValue ProcessedAt { get; set; } = ValidValue.Valid; + public ValidValue BatchAt { get; set; } = ValidValue.Valid; + public ValidValue ElapseSecs { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + // Flags. + public static readonly Byte Flag_ = 0x01; + + //------------------------------------------------------------------- + // Construction. + public SaInterfaceProcess (SysId id, SysId interfaceId, SaInterfaceProcessDirection direction, String reference, int sequence) : + base(id) + { + base.SetData(data); + InterfaceId = interfaceId; + Direction = direction; + Reference = reference; + Sequence = sequence; + Status = DefaultStatus; + } + + //------------------------------------------------------------------- + // Statuses. + private static readonly List validStatuses = new List() + { + SaStatus.Awaiting, + SaStatus.InProgress, + SaStatus.Completed, + SaStatus.Cancelled + }; + public static SaStatus DefaultStatus { get { return AwaitingStatus; } } + public static SaStatus AwaitingStatus { get { return SaStatus.Awaiting; } } + public static SaStatus InProgressStatus { get { return SaStatus.InProgress; } } + public static SaStatus CompletedStatus { get { return SaStatus.Completed; } } + public static SaStatus CancelledStatus { get { return SaStatus.Cancelled; } } + public static List ValidStatuses { get { return new List(validStatuses); } } + public static DataValue.TriStateType GetStatusTriState (SaStatus status) + { + switch (status) + { + case SaStatus.Awaiting: + return Core.DataValue.TriStateType.Amber; + case SaStatus.InProgress: + return Core.DataValue.TriStateType.Blue; + case SaStatus.Completed: + return Core.DataValue.TriStateType.Green; + case SaStatus.Cancelled: + 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 (SysId.IsValid(InterfaceId) && SysId.ToInt(InterfaceId) != SysId.ToInt(data.InterfaceId)) + throw new NotSupportedException(fn + " Interface cannot be changed"); + if ((int)Direction > 0 && (int)Direction != (int)data.Direction) + throw new NotSupportedException(fn + " Direction cannot be changed"); + if (!StringUtil.IsEmpty(Reference) && !Reference.Equals(data.Reference)) + throw new NotSupportedException(fn + " Reference cannot be changed"); + if (Sequence > 0 && Sequence != data.Sequence) + throw new NotSupportedException(fn + " Sequence cannot be changed"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + public SysId InterfaceId + { + get { return data.InterfaceId; } + protected set { data.InterfaceId = value; } + } + public SaInterfaceProcessDirection Direction + { + get { return data.Direction; } + protected set { data.Direction = value; } + } + public String Reference + { + get { return data.Reference; } + protected set { data.Reference = value; } + } + public int Sequence + { + get { return data.Sequence; } + protected set { data.Sequence = value; } + } + public DateTime ProcessAt + { + get { return data.ProcessAt; } + set { data.ProcessAt = value; } + } + public DateTime BatchAt + { + get { return data.BatchAt; } + set { data.BatchAt = value; } + } + public int ElapseSecs + { + get { return data.ElapseSecs; } + set { data.ElapseSecs = value; } + } + + //------------------------------------------------------------------- + // Flags. + + //------------------------------------------------------------------- + // List parameters. + public enum StatusCat + { + Any, + AwaitingOnly, + InProgressOnly, + CompletedOnly, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + public List IncludedIds { get; } = new List(); + public List ExcludedIds { get; } = new List(); + public SysId InterfaceId { get; set; } + public SaInterfaceProcessDirection Direction { get; set; } = SaInterfaceProcessDirection.Unknown; + public String Reference { get; set; } + public String ReferencePartial { get; set; } + public DateTime ProcessedOn { get; set; } = DateTime.MinValue; + public DateTime ProcessedFrom { get; set; } = DateTime.MinValue; + public DateTime ProcessedTo { get; set; } = DateTime.MinValue; + public String MessageText { get; set; } + public String MessageTextPartial { get; set; } + public StatusCat StatusCat { get; set; } = StatusCat.Any; + } + + //------------------------------------------------------------------- + // L i s t + //------------------------------------------------------------------- + public class List : List + { + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Interfaces/SaInterfaceProcessDataItem.cs b/SacsStandardBase/Base/Interfaces/SaInterfaceProcessDataItem.cs new file mode 100644 index 0000000..0ba4b08 --- /dev/null +++ b/SacsStandardBase/Base/Interfaces/SaInterfaceProcessDataItem.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Core; + +namespace Sarand.Net.Standard.Base.Interfaces +{ + //======================================================================= + public class SaInterfaceProcessDataItem : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Pos = 0, + Id, + // Process. + ProcessId, + // Data item. + ItemType, + ItemType_Desc, + ItemId, + Item_Caption, + // Common properties. + Notes, + Status, + Status_Desc, + Status_Ind, + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public SysId InterfaceProcessId { get; set; } = null; + public ItemId ItemId { get; set; } = null; + public String ItemData { 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 InterfaceProcessId + { + get { return data.InterfaceProcessId; } + set { CheckChanged(data.InterfaceProcessId, value); data.InterfaceProcessId = value; } + } + public ItemId ItemId + { + get { return data.ItemId; } + set { CheckChanged(data.ItemId, value); data.ItemId = value; } + } + public String ItemData + { + get { return data.ItemData; } + set { CheckChanged(data.ItemData, value); data.ItemData = value; } + } + //------------------------------------------------------------------- + // Population. + internal void PopulateFrom (Data data) + { + InterfaceProcessId = data.InterfaceProcessId; + ItemId = data.ItemId; + ItemData = data.ItemData; + 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 InterfaceProcessId { get; set; } = ValidValue.Valid; + public ValidValue ItemType { get; set; } = ValidValue.Valid; + public ValidValue ItemId { get; set; } = ValidValue.Valid; + public ValidValue ItemData { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + // Flags. + public static readonly Byte Flag_ = 0x01; + + //------------------------------------------------------------------- + // Construction. + public SaInterfaceProcessDataItem (SysId id, SysId interfaceProcessId, ItemId itemId) : + base(id) + { + base.SetData(data); + InterfaceProcessId = interfaceProcessId; + ItemId = itemId; + Status = DefaultStatus; + } + + //------------------------------------------------------------------- + // Statuses. + private static readonly List validStatuses = new List() + { + SaStatus.Completed + }; + public static SaStatus DefaultStatus { get { return CompletedStatus; } } + public static SaStatus CompletedStatus { get { return SaStatus.Completed; } } + public static List ValidStatuses { get { return new List(validStatuses); } } + public static DataValue.TriStateType GetStatusTriState (SaStatus status) + { + switch (status) + { + case SaStatus.Completed: + return DataValue.TriStateType.Green; + case SaStatus.Deleted: + return DataValue.TriStateType.Purple; + } + return 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 (SysId.IsValid(InterfaceProcessId) && SysId.ToInt(InterfaceProcessId) != SysId.ToInt(data.InterfaceProcessId)) + throw new NotSupportedException(fn + " Interface Process cannot be changed"); + if (ItemId.Type >= 0 && ItemId.Type != data.ItemId.Type) + throw new NotSupportedException(fn + " Item type cannot be changed"); + if (SysId.IsValid(ItemId.Id) && SysId.ToInt(ItemId.Id) != SysId.ToInt(data.ItemId.Id)) + throw new NotSupportedException(fn + " Item Id cannot be changed"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + public SysId InterfaceProcessId + { + get { return data.InterfaceProcessId; } + protected set { data.InterfaceProcessId = value; } + } + public ItemId ItemId + { + get { return data.ItemId; } + protected set { data.ItemId = value; } + } + public String ItemData + { + get { return data.ItemData; } + set { data.ItemData = value; } + } + + //------------------------------------------------------------------- + // Flags. + + //------------------------------------------------------------------- + // List parameters. + public enum StatusCat + { + Any, + CompletedOnly, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + public List IncludedIds { get; } = new List(); + public List ExcludedIds { get; } = new List(); + public SysId InterfaceProcessId { get; } = null; + public List IncludedItemIds { get; } = new List(); + public StatusCat StatusCat { get; set; } = StatusCat.Any; + } + + //------------------------------------------------------------------- + // L i s t + //------------------------------------------------------------------- + public class List : List + { + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Interfaces/SaInterfaceProcessMessage.cs b/SacsStandardBase/Base/Interfaces/SaInterfaceProcessMessage.cs new file mode 100644 index 0000000..ab4b5ab --- /dev/null +++ b/SacsStandardBase/Base/Interfaces/SaInterfaceProcessMessage.cs @@ -0,0 +1,238 @@ +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Core; +using System; +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Interfaces +{ + //======================================================================= + public class SaInterfaceProcessMessage : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Pos = 0, + Id, + // Process. + ProcessId, + // Message. + Level, + Level_Desc, + Message, + Filename, + LineNumber, + ItemType, + ItemType_Desc, + ItemId, + Item_Caption, + // Common properties. + Notes, + Status, + Status_Desc, + Status_Ind, + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public SysId InterfaceProcessId { get; set; } = null; + public SaMessageLevel Level { get; set; } = SaMessageLevel.Unknown; + public String Message { get; set; } = String.Empty; + public String Filename { get; set; } = String.Empty; + public int LineNumber { get; set; } = 0; + public ItemId ItemId { get; set; } = null; + } + + //------------------------------------------------------------------- + // Data. + public new class Data : SacsHierarchyItem.Data + { + // Data members. + private readonly DataProperties data = new DataProperties(); + // Construction. + public Data () + { + Status = DefaultStatus; + } + // Properties. + public SysId InterfaceProcessId + { + get { return data.InterfaceProcessId; } + set { CheckChanged(data.InterfaceProcessId, value); data.InterfaceProcessId = value; } + } + public SaMessageLevel Level + { + get { return data.Level; } + set { CheckChanged((int)data.Level, (int)value); data.Level = value; } + } + public String Message + { + get { return data.Message; } + set { CheckChanged(data.Message, value); data.Message = value; } + } + public String Filename + { + get { return data.Filename; } + set { CheckChanged(data.Filename, value); data.Filename = value; } + } + public int LineNumber + { + get { return data.LineNumber; } + set { CheckChanged(data.LineNumber, value); data.LineNumber = value; } + } + public ItemId ItemId + { + get { return data.ItemId; } + set { if (value == null) value = new ItemId(); CheckChanged(data.ItemId, value); data.ItemId = value; } + } + // Population. + internal void PopulateFrom (Data data) + { + InterfaceProcessId = data.InterfaceProcessId; + Level = data.Level; + Message = data.Message; + Filename = data.Filename; + LineNumber = data.LineNumber; + ItemId = data.ItemId; + 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 InterfaceProcessId { get; set; } = ValidValue.Valid; + public ValidValue Level { get; set; } = ValidValue.Valid; + public ValidValue Message { get; set; } = ValidValue.Valid; + public ValidValue Filename { get; set; } = ValidValue.Valid; + public ValidValue LineNumber { get; set; } = ValidValue.Valid; + public ValidValue ItemId { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + // Flags. + public static readonly UInt16 Flag_ = 0x01; + + //------------------------------------------------------------------- + // Construction. + public SaInterfaceProcessMessage (SysId id, SysId interfaceProcessId) : + base(id) + { + base.SetData(data); + InterfaceProcessId = interfaceProcessId; + Status = DefaultStatus; + } + + //------------------------------------------------------------------- + // Statuses. + private static readonly List validStatuses = new List() + { + SaStatus.Completed + }; + public static SaStatus DefaultStatus { get { return CompletedStatus; } } + public static SaStatus CompletedStatus { get { return SaStatus.Completed; } } + public static List ValidStatuses { get { return new List(validStatuses); } } + public static DataValue.TriStateType GetStatusTriState (SaStatus status) + { + switch (status) + { + case SaStatus.Completed: + 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 (SysId.IsValid(InterfaceProcessId) && SysId.ToInt(InterfaceProcessId) != SysId.ToInt(data.InterfaceProcessId)) + throw new NotSupportedException(fn + " Id cannot be changed"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + public SysId InterfaceProcessId + { + get { return data.InterfaceProcessId; } + protected set { data.InterfaceProcessId = value; } + } + public SaMessageLevel Level + { + get { return data.Level; } + set { data.Level = value; } + } + public String Message + { + get { return data.Message; } + set { data.Message = value; } + } + public String Filename + { + get { return data.Filename; } + set { data.Filename = value; } + } + public int LineNumber + { + get { return data.LineNumber; } + set { data.LineNumber = value; } + } + public ItemId ItemId + { + get { return data.ItemId; } + set { data.ItemId = value; } + } + + //------------------------------------------------------------------- + // Flags. + + //------------------------------------------------------------------- + // List parameters. + public enum StatusCat + { + Any, + CompletedOnly, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + public List IncludedIds { get; } = new List(); + public List ExcludedIds { get; } = new List(); + public SysId InterfaceProcessId { get; set; } = new SysId(); + public List IncludedItemIds { get; } = new List(); + public StatusCat StatusCat { get; set; } = StatusCat.Any; + } + + //------------------------------------------------------------------- + // L i s t + //------------------------------------------------------------------- + public class List : List + { + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ItemData/SaItemData.cs b/SacsStandardBase/Base/ItemData/SaItemData.cs new file mode 100644 index 0000000..24bcec3 --- /dev/null +++ b/SacsStandardBase/Base/ItemData/SaItemData.cs @@ -0,0 +1,222 @@ +using Sarand.Net.Standard.Base.Bus; +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.ItemData +{ + //======================================================================= + public class SaItemData : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Pos = 0, + Id, + // Item Option properties. + ItemType, + ItemType_Desc, + ItemId, + DataType, + ItemData, + // Common properties. + Notes, + Status, + Status_Desc, + Status_Ind, + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public ItemId ItemId { get; set; } = new ItemId(); + public String DataType { get; set; } = String.Empty; + public String ItemData { 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 ItemId ItemId + { + get { return data.ItemId; } + set { CheckChanged(data.ItemId, value); data.ItemId = value; } + } + public String DataType + { + get { return data.DataType; } + set { CheckChanged(data.DataType, value); data.DataType = value; } + } + public String ItemData + { + get { return data.ItemData; } + set { CheckChanged(data.ItemData, value); data.ItemData = value; } + } + // Population. + internal void PopulateFrom (Data data) + { + ItemId = data.ItemId; + DataType = data.DataType; + ItemData = data.ItemData; + 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 ItemType { get; set; } = ValidValue.Valid; + public ValidValue ItemId { get; set; } = ValidValue.Valid; + public ValidValue DataType { get; set; } = ValidValue.Valid; + public ValidValue ItemData { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + // Flags. + public static readonly Byte Flag_Encrypted = 0x01; + + //------------------------------------------------------------------- + // Construction. + public SaItemData (SysId id, ItemId itemId) : + base(id) + { + base.SetData(data); + ItemId = itemId; + Status = DefaultStatus; + } + + //------------------------------------------------------------------- + // Status. + private List validStatuses = new List() + { + SaStatus.Current, + SaStatus.Deleted, + }; + public static SaStatus DefaultStatus { get { return StatusCurrent; } } + public static SaStatus StatusCurrent { get { return SaStatus.Current; } } + public static List ValidStatuses { get { return new List(ValidStatuses); } } + public static DataValue.TriStateType GetStatusTriState (SaStatus status) + { + if (status == SaStatus.Current) return DataValue.TriStateType.Green; + if (status == SaStatus.Deleted) return DataValue.TriStateType.Purple; + return 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 (ItemId.Type >= 0 && ItemId.Type != data.ItemId.Type) + throw new NotSupportedException(fn + " Item type cannot be changed"); + if (SysId.IsValid(ItemId.Id) && SysId.ToInt(ItemId.Id) != SysId.ToInt(data.ItemId.Id)) + throw new NotSupportedException(fn + " Item Id cannot be changed"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + public ItemId ItemId + { + get { return data.ItemId; } + protected set { data.ItemId = value; } + } + public String DataType + { + get { return data.DataType; } + set { data.DataType = value; } + } + public String ItemData + { + get { return data.ItemData; } + set { data.ItemData = value; } + } + + //------------------------------------------------------------------- + // Flags. + public bool Encrypted + { + get { return GetFlag(Flag_Encrypted); } + set { SetFlag(Flag_Encrypted, value); } + } + + //------------------------------------------------------------------- + // Comparison. + public bool Matches (ItemId itemId) + { + if (ItemId.Type != itemId.Type) return false; + if (SysId.ToInt(ItemId.Id) != SysId.ToInt(itemId.Id)) return false; + return true; + } + public bool Matches (ItemId itemId, String dataType) + { + if (!Matches(itemId)) return false; + if (!DataType.EqualsCI(dataType)) return false; + return true; + } + + //------------------------------------------------------------------- + // List parameters. + public enum StatusCat + { + Any, + NotDeleted, + CurrentOnly, + DeletedOnly, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + public List IncludedIds { get; } = new List(); + public List ExcludedIds { get; } = new List(); + public List IncludedItemIds { get; } = new List(); + public List IncludedDataTypes { get; } = new List(); + public StatusCat StatusCat { get; set; } = StatusCat.NotDeleted; + } + + //------------------------------------------------------------------- + } + + //======================================================================= + /// List extension methods. + public static class SaItemDataList + { + public static List FindByItem (this List values, ItemId itemId) + { + return values.FindAll(item => item.Matches(itemId)); + } + public static List FindByItemDataType (this List values, ItemId itemId, String dataType) + { + return values.FindAll(item => item.Matches(itemId, dataType)); + } + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/ItemData/SaItemDataConsole.cs b/SacsStandardBase/Base/ItemData/SaItemDataConsole.cs new file mode 100644 index 0000000..8a2df9f --- /dev/null +++ b/SacsStandardBase/Base/ItemData/SaItemDataConsole.cs @@ -0,0 +1,69 @@ +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.Text; + +namespace Sarand.Net.Standard.Base.ItemData +{ + //======================================================================= + public abstract class SaItemDataConsole : SaErrorStateItem + { + //------------------------------------------------------------------- + // Definitions. + + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Static initialisation. + static SaItemDataConsole () + { + } + + //------------------------------------------------------------------- + // Construction. + protected SaItemDataConsole () + { + } + + //------------------------------------------------------------------- + // Properties. + // Database. + public abstract bool DatabaseIsValid { get; } + public abstract bool CanUpdateDatabase { get; } + + //------------------------------------------------------------------- + // I t e m D a t a + //------------------------------------------------------------------- + /// Type codes - specific for each type of item. + public abstract List GetDataTypeCodes (int itemType); + + //------------------------------------------------------------------- + // Item data interface. + public abstract bool CanUpdateItemData (SaItemData itemData); + public abstract bool DoUpdateItemData (SaItemData itemData); + public abstract bool CanRemoveItemData (SaItemData itemData); + + //------------------------------------------------------------------- + // Retrieve item data entries that match the specified params. + public abstract bool ItemDataExists (SaItemData.Params prmItemData); + public abstract long GetItemDataCount (SaItemData.Params prmItemData); + public abstract List GetItemDataList (SaItemData.Params prmItemData); + + //------------------------------------------------------------------- + // Create a new item data. + public abstract SaItemData CreateItemData (ItemId itemId); + + //------------------------------------------------------------------- + // Store item data. + public abstract bool Store (SaItemData itemData, DbUpdateInfo dbUpdateInfo); + public abstract bool Remove (SaItemData itemData, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearItemData (ItemId itemId, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearItemData (ItemId itemId, String dataType, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ItemData/SaItemDataDbDfn.cs b/SacsStandardBase/Base/ItemData/SaItemDataDbDfn.cs new file mode 100644 index 0000000..c50c054 --- /dev/null +++ b/SacsStandardBase/Base/ItemData/SaItemDataDbDfn.cs @@ -0,0 +1,317 @@ +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:50 +//--------------------------------------------------------------------------- +namespace Sarand.Net.Standard.Base.ItemData +{ + public class SaItemDataDbDfn + { + //------------------------------------------------------------------- + // Table identifiers. + public enum TableIdx + { + Unknown = 0, + SaItemData, + } + + //------------------------------------------------------------------- + // Column identifiers. + public enum ColumnIdx + { + Unknown = 0, + SaItemData_Id, + SaItemData_ItemType, + SaItemData_ItemId, + SaItemData_DataType, + SaItemData_ItemData, + SaItemData_Notes, + SaItemData_Flags, + SaItemData_Status, + SaItemData_CreateOn, + SaItemData_CreateBy, + SaItemData_CreateHost, + SaItemData_UpdateOn, + SaItemData_UpdateBy, + SaItemData_UpdateHost, + } + + //------------------------------------------------------------------- + // Data members. + private static readonly int tableCount = 1; + private static readonly int columnCount = 14; + 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) + { + SaItemDataDbDfn.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) + { + SaItemDataDbDfn.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 = + { + "SaItemData", + }; + + //------------------------------------------------------------------- + // 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,"ItemType",3,5,false,5), + new ColumnDfn(1,"ItemId",4,4,false,10), + new ColumnDfn(1,"DataType",0,-9,false,50), + new ColumnDfn(1,"ItemData",0,-9,false,0), + 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), + }; + + //------------------------------------------------------------------- + // 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/ItemData/SaItemDataMgr.cs b/SacsStandardBase/Base/ItemData/SaItemDataMgr.cs new file mode 100644 index 0000000..9201f29 --- /dev/null +++ b/SacsStandardBase/Base/ItemData/SaItemDataMgr.cs @@ -0,0 +1,171 @@ +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.Text; + +namespace Sarand.Net.Standard.Base.ItemData +{ + //======================================================================= + public abstract class SaItemDataMgr : SacsItemMgr + { + //------------------------------------------------------------------- + // Definitions. + + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Static initialisation. + static SaItemDataMgr () + { + } + + //------------------------------------------------------------------- + // Construction. + protected SaItemDataMgr () + { + } + + //------------------------------------------------------------------- + // Properties. + public abstract bool DatabaseIsValid { get; } + public abstract bool CanUpdateDatabase { get; } + + //******************************************************************* + // I t e m D a t a + //******************************************************************* + // Utilities. + public abstract bool ItemHasData (ItemId itemId, bool ignoreDeleted = true); + public abstract bool ItemHasData (ItemId itemId, String dataType, bool ignoreDeleted = true); + + //------------------------------------------------------------------- + // Retrieve individual item data entries. + public abstract bool ItemDataExists (SysId id); + public abstract bool ItemDataExists (ItemId itemId, String dataType, bool ignoreDeleted = true); + public abstract SaItemData GetItemData (SysId id); + public abstract SaItemData GetItemData (ItemId itemId, String dataType); + public abstract SaItemData GetItemData (SaItemData.Params prmItemData); + + // Create Item Data. + public abstract SaItemData CreateItemData (ItemId itemId); + + //------------------------------------------------------------------- + // Validate Item Data. + public bool IsValidItemData (SaItemData itemData) + { + ClearErrorState(); + if (itemData == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidItemDataData(itemData.GetData(), new SaItemData.ErrField()); + } + public virtual bool IsValidItemDataData (SaItemData.Data itemDataData, SaItemData.ErrField errField) + { + ClearErrorState(); + if ((int)itemDataData.ItemId.Type < 1) + { + errField.ItemType = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Item type must be supplied"); + } + if (!SysId.IsValid(itemDataData.ItemId.Id)) + { + errField.ItemId = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Item Id must be supplied"); + } + if (!IsValidValue(itemDataData.DataType, "Data type", (long)SaItemDataDbDfn.GetColTextLen(SaItemDataDbDfn.ColumnIdx.SaItemData_DataType), true)) + { + errField.DataType = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!IsValidValue(itemDataData.ItemData, "Item data", (long)SaItemDataDbDfn.GetColTextLen(SaItemDataDbDfn.ColumnIdx.SaItemData_ItemData))) + { + errField.ItemData = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + // Common properties. + if (!IsValidValue(itemDataData.Notes, "notes", (long)SaItemDataDbDfn.GetColTextLen(SaItemDataDbDfn.ColumnIdx.SaItemData_Notes))) + { + errField.Notes = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!SaItemData.ValidStatuses.Contains(itemDataData.Status)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState(ErrorMessage.InvalidStatus); + } + return true; + } + + //------------------------------------------------------------------- + // Item Data database interface. + public abstract bool InsertItemData (SaItemData itemData, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateItemData (SaItemData itemData, DbUpdateInfo dbUpdateInfo); + public virtual bool ItemDataCanBeDeleted (SaItemData itemData) + { + ClearErrorState(); + if (!SysId.IsValid(itemData.Id)) return SetErrorState(ErrorMessage.InvalidItemId); + return true; + } + public abstract bool DeleteItemData (SaItemData itemData, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearItemData (ItemId itemId, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearItemData (ItemId itemId, String dataType, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // Item Data list. + public abstract ColumnDfn GetItemDataColumnDfn (SaItemData.Column columnId, String caption = "", int width = 0); + public abstract List GetItemDataQueryFilter (SaItemData.Params prm); + public abstract long GetItemDataCount (SaItemData.Params prm); + public abstract List GetItemDataList (SaItemData.Params prm); + public abstract DbResultSet ExecuteItemDataQuery (List columns, SaItemData.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 GetItemDataDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + switch ((SaItemData.Column)column.Id) + { + //----------------------------------------------------------- + // Item type. + case SaItemData.Column.ItemType_Desc: + { + short itemType = (short)values.GetValue(column.DataName).AsLong(); + value = new DataValue(itemType.ToString()); + } + break; + + //----------------------------------------------------------- + // Notes. + case SaItemData.Column.Notes: + { + value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200)); + } + break; + + //----------------------------------------------------------- + // Status. + case SaItemData.Column.Status_Desc: + case SaItemData.Column.Status_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaItemData.Column.Status_Desc) + value = new DataValue(SacsSystem.GetStatusDescForCode(statusCode)); + else if (column.Id == (int)SaItemData.Column.Status_Ind) + value = new DataValue(SaItemData.GetStatusTriState(status)); + } + break; + + //----------------------------------------------------------- + } + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ItemDocs/SaItemDoc.cs b/SacsStandardBase/Base/ItemDocs/SaItemDoc.cs new file mode 100644 index 0000000..a621a88 --- /dev/null +++ b/SacsStandardBase/Base/ItemDocs/SaItemDoc.cs @@ -0,0 +1,313 @@ +using Sarand.Net.Standard.Base.Bus; +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.ItemDocs +{ + //======================================================================= + public class SaItemDoc : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Pos = 0, + Id, + // Item Id. + ItemType, + ItemType_Desc, + ItemId, + // Item Doc properties. + Category, + Category_Desc, + DocType, + DocType_Desc, + DescBrief, + Folder, + Filename, + MimeType, + // Common properties. + Notes, + Status, + Status_Desc, + Status_Ind, + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public ItemId ItemId { get; set; } = new ItemId(); + public SaItemDocConsole.Category Category { get; set; } + public String DocType { get; set; } = String.Empty; + public String DescBrief { get; set; } = String.Empty; + public String Folder { get; set; } = String.Empty; + public String Filename { get; set; } = String.Empty; + public String MimeType { get; set; } = String.Empty; + public short ItemTypeAutoAssign { get; set; } + } + + //------------------------------------------------------------------- + // Data. + public new class Data : SacsHierarchyItem.Data + { + // Data members. + private readonly DataProperties data = new DataProperties(); + // Construction. + public Data () + { + Status = DefaultStatus; + } + // Properties. + public ItemId ItemId + { + get { return data.ItemId; } + set { CheckChanged(data.ItemId, value); data.ItemId = value; } + } + public SaItemDocConsole.Category Category + { + get { return data.Category; } + set { CheckChanged((int)data.Category, (int)value); data.Category = value; } + } + public String DocType + { + get { return data.DocType; } + set { CheckChanged(data.DocType, value); data.DocType = value; } + } + public String DescBrief + { + get { return data.DescBrief; } + set { CheckChanged(data.DescBrief, value); data.DescBrief = value; } + } + public String Folder + { + get { return data.Folder; } + set { CheckChanged(data.Folder, value); data.Folder = value; } + } + public String Filename + { + get { return data.Filename; } + set { CheckChanged(data.Filename, value); data.Filename = value; } + } + public String MimeType + { + get { return data.MimeType; } + set { CheckChanged(data.MimeType, value); data.MimeType = value; } + } + public short ItemTypeAutoAssign + { + get { return data.ItemTypeAutoAssign; } + set { CheckChanged(data.ItemTypeAutoAssign, value); data.ItemTypeAutoAssign = value; } + } + // Population. + internal void PopulateFrom (Data data) + { + ItemId = data.ItemId; + Category = data.Category; + DocType = data.DocType; + DescBrief = data.DescBrief; + Folder = data.Folder; + Filename = data.Filename; + MimeType = data.MimeType; + ItemTypeAutoAssign = data.ItemTypeAutoAssign; + 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 ItemType { get; set; } = ValidValue.Valid; + public ValidValue ItemId { get; set; } = ValidValue.Valid; + public ValidValue Category { get; set; } = ValidValue.Valid; + public ValidValue DocType { get; set; } = ValidValue.Valid; + public ValidValue DescBrief { get; set; } = ValidValue.Valid; + public ValidValue Folder { get; set; } = ValidValue.Valid; + public ValidValue Filename { get; set; } = ValidValue.Valid; + public ValidValue MimeType { get; set; } = ValidValue.Valid; + public ValidValue ItemTypeAutoAssign { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + // Flags. + public static readonly Byte Flag_AutoAssigned = 0x01; + + //------------------------------------------------------------------- + // Construction. + public SaItemDoc (SysId id, ItemId itemId, SaItemDocConsole.Category category) : + base(id) + { + base.SetData(data); + ItemId = itemId; + Category = category; + Status = DefaultStatus; + } + + //------------------------------------------------------------------- + // Status. + private List validStatuses = new List() + { + SaStatus.Current, + SaStatus.Deleted, + }; + public static SaStatus DefaultStatus { get { return StatusCurrent; } } + public static SaStatus StatusCurrent { get { return SaStatus.Current; } } + public static List ValidStatuses { get { return new List(ValidStatuses); } } + public static DataValue.TriStateType GetStatusTriState (SaStatus status) + { + if (status == SaStatus.Current) return DataValue.TriStateType.Green; + if (status == SaStatus.Deleted) return DataValue.TriStateType.Purple; + return 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 (ItemId.Type >= 0 && ItemId.Type != data.ItemId.Type) + throw new NotSupportedException(fn + " Item type cannot be changed"); + if (SysId.IsValid(ItemId.Id) && SysId.ToInt(ItemId.Id) != SysId.ToInt(data.ItemId.Id)) + throw new NotSupportedException(fn + " Item Id cannot be changed"); + // Category. + if ((int)data.Category < 1) + data.Category = Category; + if ((int)Category != 0 && Category != data.Category) + throw new NotSupportedException(fn + " Category cannot be changed"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + public ItemId ItemId + { + get { return data.ItemId; } + protected set { data.ItemId = value; } + } + public SaItemDocConsole.Category Category + { + get { return data.Category; } + protected set { data.Category = value; } + } + public String DocType + { + get { return data.DocType; } + set { data.DocType = value; } + } + public String DescBrief + { + get { return data.DescBrief; } + set { data.DescBrief = value; } + } + public String Folder + { + get { return data.Folder; } + set { data.Folder = value; } + } + public String Filename + { + get { return data.Filename; } + set { data.Filename = value; } + } + public String MimeType + { + get { return data.MimeType; } + set { data.MimeType = value; } + } + public short ItemTypeAutoAssign + { + get { return data.ItemTypeAutoAssign; } + set { data.ItemTypeAutoAssign = value; } + } + + //------------------------------------------------------------------- + // Flags. + public bool AutoAssigned + { + get { return GetFlag(Flag_AutoAssigned); } + set { SetFlag(Flag_AutoAssigned, value); } + } + + //------------------------------------------------------------------- + // Comparison. + public bool Matches (ItemId itemId) + { + if (ItemId.Type != itemId.Type) return false; + if (SysId.ToInt(ItemId.Id) != SysId.ToInt(itemId.Id)) return false; + return true; + } + public bool Matches (ItemId itemId, SaItemDocConsole.Category category) + { + if (!Matches(itemId)) return false; + if (Category != category) return false; + return true; + } + public bool Matches (ItemId itemId, SaItemDocConsole.Category category, String docType) + { + if (!Matches(itemId, category)) return false; + if (!DocType.EqualsCI(docType)) return false; + return true; + } + + //------------------------------------------------------------------- + // List parameters. + public enum StatusCat + { + Any, + NotDeleted, + CurrentOnly, + DeletedOnly, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + public List IncludedIds { get; } = new List(); + public List ExcludedIds { get; } = new List(); + public List IncludedItemIds { get; } = new List(); + public SaItemDocConsole.Category Category { get; set; } + public String DocType { get; set; } + public String DescBriefPartial { get; set; } + public String MimeType { get; set; } + public String FolderFull { get; set; } + public String FolderPartial { get; set; } + public String FilenameFull { get; set; } + public short ItemTypeAutoAssign { get; set; } + public StatusCat StatusCat { get; set; } = StatusCat.NotDeleted; + } + + //------------------------------------------------------------------- + } + + //======================================================================= + /// List extension methods. + public static class SaItemDocList + { + public static List FindByItem (this List values, ItemId itemId) + { + return values.FindAll(item => item.Matches(itemId)); + } + public static List FindByItemDocCategory (this List values, ItemId itemId, SaItemDocConsole.Category category) + { + return values.FindAll(item => item.Matches(itemId, category)); + } + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/ItemDocs/SaItemDocConsole.cs b/SacsStandardBase/Base/ItemDocs/SaItemDocConsole.cs new file mode 100644 index 0000000..3ff102f --- /dev/null +++ b/SacsStandardBase/Base/ItemDocs/SaItemDocConsole.cs @@ -0,0 +1,85 @@ +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.Text; + +namespace Sarand.Net.Standard.Base.ItemDocs +{ + //======================================================================= + public abstract class SaItemDocConsole : SaErrorStateItem + { + //------------------------------------------------------------------- + // Definitions. + /// Item document category. + public enum Category + { + // Do not change values - tied to database. + Unknown = 0, + Folder = 10, + Document = 20, + Pdf = 21, + Image = 30, + FileSpec = 90, /* Generic filename with wildcards */ + } + + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Static initialisation. + static SaItemDocConsole () + { + } + + //------------------------------------------------------------------- + // Construction. + protected SaItemDocConsole () + { + } + + //------------------------------------------------------------------- + // Properties. + // Database. + public abstract bool DatabaseIsValid { get; } + public abstract bool CanUpdateDatabase { get; } + + //------------------------------------------------------------------- + // I t e m D o c s + //------------------------------------------------------------------- + // Category codes. + public abstract List GetCategoryCodes (); + /// eg. for 'Image' documents. + public abstract List GetCategoryTypeCodes (Category category); + + //------------------------------------------------------------------- + // 'Recent' and 'Favourite' folders - to populate drop-down list when selecting folder: + /// How many recent foldes to retain? [default:5] + public abstract int GetMaxRecentFolderCount (); + /// List of favourites to always include in dropdown. + public abstract void SetFavouriteFolders (List folders); + /// Mix of recent and favourites. + public abstract List GetSuggestedFolders (); + + //------------------------------------------------------------------- + // Retrieve list of docs that match the specified params: + public abstract bool ItemDocsExist (SaItemDoc.Params prmItemDoc); + public abstract List GetItemDocs (SaItemDoc.Params prmItemDoc); + // Create a new document: + public abstract SaItemDoc CreateItemDoc (ItemId itemId, Category category); + // Store documents: + public abstract bool StoreItemDoc (SaItemDoc itemDoc, DbUpdateInfo dbUpdateInfo); + public abstract bool StoreItemDocs (List itemDocs, DbUpdateInfo dbUpdateInfo); + public abstract bool RemoveItemDoc (SaItemDoc itemDoc, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearItemDocs (ItemId itemId, Category category, DbUpdateInfo dbUpdateInfo); + // Auto-assign documents. + // The master items define 'auto-assign' docs for the item's type. + // The 'File part' defines the filename excluding the extension. + // If the extension list is empty, any extension is matched. + public abstract bool AutoAssignDocs (ItemId itemId, List masterItemIds, String filePart, List extension, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ItemDocs/SaItemDocMgr.cs b/SacsStandardBase/Base/ItemDocs/SaItemDocMgr.cs new file mode 100644 index 0000000..fe9fbb0 --- /dev/null +++ b/SacsStandardBase/Base/ItemDocs/SaItemDocMgr.cs @@ -0,0 +1,207 @@ +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.Text; + +namespace Sarand.Net.Standard.Base.ItemDocs +{ + //======================================================================= + public abstract class SaItemDocMgr : SacsItemMgr + { + //------------------------------------------------------------------- + // Definitions. + + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Static initialisation. + static SaItemDocMgr () + { + } + + //------------------------------------------------------------------- + // Construction. + protected SaItemDocMgr () + { + } + + //------------------------------------------------------------------- + // Properties. + public abstract bool DatabaseIsValid { get; } + public abstract bool CanUpdateDatabase { get; } + + //******************************************************************* + // I t e m D o c s + //******************************************************************* + // Utilities. + public abstract bool ItemHasDocs (ItemId itemId, bool ignoreDeleted = true); + public abstract bool ItemHasDocs (ItemId itemId, SaItemDocConsole.Category category, bool ignoreDeleted = true); + + //------------------------------------------------------------------- + // Retrieve individual item data entries. + public abstract bool ItemDocExists (SysId id); + public abstract SaItemDoc GetItemDoc (SysId id); + public abstract SaItemDoc GetItemDoc (SaItemDoc.Params prmItemDoc); + + // Create Item Data. + public abstract SaItemDoc CreateItemDoc (ItemId itemId, SaItemDocConsole.Category category); + + //------------------------------------------------------------------- + // Validate Item Data. + public bool IsValidItemDoc (SaItemDoc itemDoc) + { + ClearErrorState(); + if (itemDoc == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidItemDocData(itemDoc.GetData(), new SaItemDoc.ErrField()); + } + public virtual bool IsValidItemDocData (SaItemDoc.Data itemDocData, SaItemDoc.ErrField errField) + { + ClearErrorState(); + if ((int)itemDocData.ItemId.Type < 1) + { + errField.ItemType = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Item type must be supplied"); + } + if (!SysId.IsValid(itemDocData.ItemId.Id)) + { + errField.ItemId = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Item Id must be supplied"); + } + if ((int)itemDocData.Category < 1) + { + errField.Category = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Category must be supplied"); + } + if (!IsValidValue(itemDocData.DocType, "Doc type", (long)SaItemDocsDbDfn.GetColTextLen(SaItemDocsDbDfn.ColumnIdx.SaItemDoc_Type), true)) + { + errField.DocType = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!IsValidValue(itemDocData.DescBrief, "Desciption", (long)SaItemDocsDbDfn.GetColTextLen(SaItemDocsDbDfn.ColumnIdx.SaItemDoc_DescBrief))) + { + errField.DescBrief = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!IsValidValue(itemDocData.Folder, "Folder", (long)SaItemDocsDbDfn.GetColTextLen(SaItemDocsDbDfn.ColumnIdx.SaItemDoc_Folder))) + { + errField.Folder = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!IsValidValue(itemDocData.Filename, "Filename", (long)SaItemDocsDbDfn.GetColTextLen(SaItemDocsDbDfn.ColumnIdx.SaItemDoc_Filename), true)) + { + errField.Filename = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!IsValidValue(itemDocData.MimeType, "Mime", (long)SaItemDocsDbDfn.GetColTextLen(SaItemDocsDbDfn.ColumnIdx.SaItemDoc_MimeType))) + { + errField.MimeType = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + // Common properties. + if (!IsValidValue(itemDocData.Notes, "notes", (long)SaItemDocsDbDfn.GetColTextLen(SaItemDocsDbDfn.ColumnIdx.SaItemDoc_Notes))) + { + errField.Notes = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!SaItemDoc.ValidStatuses.Contains(itemDocData.Status)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState(ErrorMessage.InvalidStatus); + } + return true; + } + + //------------------------------------------------------------------- + // Item Data database interface. + public abstract bool InsertItemDoc (SaItemDoc itemDoc, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateItemDoc (SaItemDoc itemDoc, DbUpdateInfo dbUpdateInfo); + public virtual bool ItemDocCanBeDeleted (SaItemDoc itemDoc) + { + ClearErrorState(); + if (!SysId.IsValid(itemDoc.Id)) return SetErrorState(ErrorMessage.InvalidItemId); + return true; + } + public abstract bool DeleteItemDoc (SaItemDoc itemDoc, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearItemDocs (ItemId itemId, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearItemDocs (ItemId itemId, SaItemDocConsole.Category category, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // Item Data list. + public abstract ColumnDfn GetItemDocColumnDfn (SaItemDoc.Column columnId, String caption = "", int width = 0); + public abstract List GetItemDocQueryFilter (SaItemDoc.Params prm); + public abstract long GetItemDocCount (SaItemDoc.Params prm); + public abstract List GetItemDocList (SaItemDoc.Params prm); + public abstract DbResultSet ExecuteItemDocQuery (List columns, SaItemDoc.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 GetItemDocDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + switch ((SaItemDoc.Column)column.Id) + { + //----------------------------------------------------------- + // Item type. + case SaItemDoc.Column.ItemType_Desc: + { + short itemType = (short)values.GetValue(column.DataName).AsLong(); + value = new DataValue(itemType.ToString()); + } + break; + + //----------------------------------------------------------- + // Category. + case SaItemDoc.Column.Category_Desc: + { + short category = (short)values.GetValue(column.DataName).AsLong(); + value = new DataValue(category.ToString()); + } + break; + + //----------------------------------------------------------- + // Document type. + case SaItemDoc.Column.DocType_Desc: + { + short docType = (short)values.GetValue(column.DataName).AsLong(); + value = new DataValue(docType.ToString()); + } + break; + + //----------------------------------------------------------- + // Notes. + case SaItemDoc.Column.Notes: + { + value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200)); + } + break; + + //----------------------------------------------------------- + // Status. + case SaItemDoc.Column.Status_Desc: + case SaItemDoc.Column.Status_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaItemDoc.Column.Status_Desc) + value = new DataValue(SacsSystem.GetStatusDescForCode(statusCode)); + else if (column.Id == (int)SaItemDoc.Column.Status_Ind) + value = new DataValue(SaItemDoc.GetStatusTriState(status)); + } + break; + + //----------------------------------------------------------- + } + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ItemDocs/SaItemDocsDbDfn.cs b/SacsStandardBase/Base/ItemDocs/SaItemDocsDbDfn.cs new file mode 100644 index 0000000..816a4c6 --- /dev/null +++ b/SacsStandardBase/Base/ItemDocs/SaItemDocsDbDfn.cs @@ -0,0 +1,327 @@ +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:54 +//--------------------------------------------------------------------------- +namespace Sarand.Net.Standard.Base.ItemDocs +{ + public class SaItemDocsDbDfn + { + //------------------------------------------------------------------- + // Table identifiers. + public enum TableIdx + { + Unknown = 0, + SaItemDoc, + } + + //------------------------------------------------------------------- + // Column identifiers. + public enum ColumnIdx + { + Unknown = 0, + SaItemDoc_Id, + SaItemDoc_ItemType, + SaItemDoc_ItemId, + SaItemDoc_Category, + SaItemDoc_Type, + SaItemDoc_DescBrief, + SaItemDoc_Folder, + SaItemDoc_Filename, + SaItemDoc_MimeType, + SaItemDoc_ItemTypeAutoAssign, + SaItemDoc_Notes, + SaItemDoc_Flags, + SaItemDoc_Status, + SaItemDoc_CreateOn, + SaItemDoc_CreateBy, + SaItemDoc_CreateHost, + SaItemDoc_UpdateOn, + SaItemDoc_UpdateBy, + SaItemDoc_UpdateHost, + } + + //------------------------------------------------------------------- + // Data members. + private static readonly int tableCount = 1; + private static readonly int columnCount = 19; + 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) + { + SaItemDocsDbDfn.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) + { + SaItemDocsDbDfn.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 = + { + "SaItemDoc", + }; + + //------------------------------------------------------------------- + // 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,"ItemType",3,5,false,5), + new ColumnDfn(1,"ItemId",4,4,false,10), + new ColumnDfn(1,"Category",3,5,false,5), + new ColumnDfn(1,"Type",0,-9,false,16), + new ColumnDfn(1,"DescBrief",0,-9,false,64), + new ColumnDfn(1,"Folder",0,-9,false,255), + new ColumnDfn(1,"Filename",0,-9,false,255), + new ColumnDfn(1,"MimeType",0,-9,false,32), + new ColumnDfn(1,"ItemTypeAutoAssign",3,5,false,5), + 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), + }; + + //------------------------------------------------------------------- + // 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/ItemOptions/SaItemOption.cs b/SacsStandardBase/Base/ItemOptions/SaItemOption.cs new file mode 100644 index 0000000..0647008 --- /dev/null +++ b/SacsStandardBase/Base/ItemOptions/SaItemOption.cs @@ -0,0 +1,279 @@ +using Sarand.Net.Standard.Base.ItemOptions; +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.ItemOptions +{ + //======================================================================= + public class SaItemOption : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Pos = 0, + // Item Option properties. + ItemType, + ItemType_Desc, + ItemId, + Group, + Name, + Name_Display, + Context, + Context_Display, + Value, + Value_Display, + Password, + // Common properties. + Notes, + Status, + Status_Desc, + Status_Ind, + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public ItemId ItemId { get; set; } = new ItemId(); + public String Name { get; set; } = String.Empty; + public String Context { get; set; } = String.Empty; + public String Value { get; set; } = String.Empty; + } + + //------------------------------------------------------------------- + // Data. + public new class Data : SacsHierarchyItem.Data + { + // Data members. + private readonly DataProperties data = new DataProperties(); + // Construction. + public Data () + { + Status = SaItemOptionMgr.ActiveItemOptionStatus; + } + // Properties. + public ItemId ItemId + { + get { return data.ItemId; } + set { CheckChanged(data.ItemId, value); data.ItemId = value; } + } + public String Name + { + get { return data.Name; } + set { CheckChanged(data.Name, value); data.Name = value; } + } + public String Context + { + get { return data.Context; } + set { CheckChanged(data.Context, value); data.Context = value; } + } + public String Value + { + get { return data.Value; } + set { CheckChanged(data.Value, value); data.Value = value; } + } + // Population. + internal void PopulateFrom (Data data) + { + ItemId = data.ItemId; + Name = data.Name; + Context = data.Context; + Value = data.Value; + 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 ItemType { get; set; } = ValidValue.Valid; + public ValidValue ItemId { get; set; } = ValidValue.Valid; + public ValidValue Name { get; set; } = ValidValue.Valid; + public ValidValue Context { get; set; } = ValidValue.Valid; + public ValidValue Value { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + // Flags. + public static readonly Byte Flag_Encrypted = 0x01; + + //------------------------------------------------------------------- + // Construction. + public SaItemOption (ItemId itemId, String name, String context) : + base() + { + base.SetData(data); + ItemId = itemId; + Name = name; + // Trim Context as legacy systems (Sapphire, StockMgr, PatientMgr) store a space instead of a blank value. + Context = context.TrimAll(); + Status = SaItemOptionMgr.ActiveItemOptionStatus; + } + + //------------------------------------------------------------------- + // Statuses. + private static readonly List validStatuses = new List() + { + SaStatus.Current, + SaStatus.Deleted + }; + public static SaStatus DefaultStatus { get { return ActiveStatus; } } + public static SaStatus ActiveStatus { 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 DataValue.TriStateType.Green; + case SaStatus.Deleted: + return DataValue.TriStateType.Purple; + } + return 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 (ItemId.Type >= 0 && ItemId.Type != data.ItemId.Type) + throw new NotSupportedException(fn + " Item type cannot be changed"); + if (SysId.IsValid(ItemId.Id) && SysId.ToInt(ItemId.Id) != SysId.ToInt(data.ItemId.Id)) + throw new NotSupportedException(fn + " Item Id cannot be changed"); + if (!StringUtil.IsEmpty(Name) && !Name.Equals(data.Name)) + throw new NotSupportedException(fn + " Name cannot be changed"); + if (!StringUtil.IsEmpty(Context) && !Context.Equals(data.Context)) + throw new NotSupportedException(fn + " Context cannot be changed"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + public ItemId ItemId + { + get { return data.ItemId; } + protected set { data.ItemId = value; } + } + public String Name + { + get { return data.Name; } + protected set { data.Name = value; } + } + public String Context + { + get { return data.Context; } + protected set { data.Context = value; } + } + public String Value + { + get { return data.Value; } + set { data.Value = value; } + } + + //------------------------------------------------------------------- + // Flags. + public bool Encrypted + { + get { return GetFlag(Flag_Encrypted); } + set { SetFlag(Flag_Encrypted, value); } + } + + //------------------------------------------------------------------- + // List parameters. + public enum StatusCat + { + Any, + NotDeleted, + CurrentOnly, + DeletedOnly, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + public List IncludedItemIds { get; } = new List(); + public List IncludedNameContexts { get; } = new List(); + public StatusCat StatusCat { get; set; } = StatusCat.CurrentOnly; + } + + //------------------------------------------------------------------- + /// + /// Item option cache - to allow op. + /// + public class Cache + { + public Cache () + { + } + public ItemId ItemId { get; private set; } = new ItemId(); + public ItemId ItemIdRef { get; private set; } = new ItemId(); + public List ItemOptions { get; } = new List(); + public void Reset (ItemId itemId, ItemId itemIdRef) + { + ItemId = itemId != null ? itemId : new ItemId(); + ItemIdRef = itemIdRef != null ? itemIdRef : new ItemId(); + ItemOptions.Clear(); + } + public void Clear () + { + ItemId = new ItemId(); + ItemIdRef = new ItemId(); + ItemOptions.Clear(); + } + } + + //------------------------------------------------------------------- + } + + //======================================================================= + /// + /// Item Option list utilities. + /// + public static class SaItemOptionList + { + /// Does the list include an option for the specified name and context. + public static bool IsOptionDefined (this List options, String name, String context) + { + return options.Exists(item => item.Name.EqualsCI(name) && item.Context.EqualsCI(context) && !item.IsStatusDeleted); + } + /// Retrieve the list of contexts populated for the specified option name. + public static List GetContextsForName (this List options, String name) + { + List optionContexts = new List(); + foreach (SaItemOption itemOption in options.FindAll(item => item.Name.EqualsCI(name) && !item.IsStatusDeleted)) + { + if (!StringUtil.IsEmpty(itemOption.Context) && !optionContexts.ContainsCI(itemOption.Context)) + optionContexts.Add(itemOption.Context); + } + return optionContexts; + } + /// Retrieve the option value for the name and context. + public static String GetOptionValue (this List options, String name, String context, String defaultValue = "") + { + SaItemOption itemOption = options.Find(item => item.Name.EqualsCI(name) && item.Context.EqualsCI(context) && !item.IsStatusDeleted); + if (itemOption != null) + return itemOption.Value; + return defaultValue; + } + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/ItemOptions/SaItemOptionConsole.cs b/SacsStandardBase/Base/ItemOptions/SaItemOptionConsole.cs new file mode 100644 index 0000000..4515904 --- /dev/null +++ b/SacsStandardBase/Base/ItemOptions/SaItemOptionConsole.cs @@ -0,0 +1,86 @@ +using Sarand.Net.Standard.Base.ItemOptions; +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 Sarand.Net.Standard.Base.UpdateLog; + +namespace Sarand.Net.Standard.Base.ItemOptions +{ + //======================================================================= + public abstract class SaItemOptionConsole : SaErrorStateItem + { + //------------------------------------------------------------------- + // Definitions. + + //------------------------------------------------------------------- + // Data members. + private static readonly List yesNoValues = new List(); + + //------------------------------------------------------------------- + // Static initialisation. + static SaItemOptionConsole () + { + yesNoValues.Add("Yes", "Yes"); + yesNoValues.Add("No", "No"); + } + + //------------------------------------------------------------------- + // Construction. + protected SaItemOptionConsole () + { + } + + //------------------------------------------------------------------- + // Properties. + public abstract short ItemOptionDataType { get; } + public abstract SaUpdateLogConsole UpdateLogConsole { get; } + // Database. + public abstract bool DatabaseIsValid { get; } + public abstract bool CanUpdateDatabase { get; } + + //------------------------------------------------------------------- + // Manage Item Options - returns true if changes made. + public abstract bool ShowItemOptionsDialog (ItemId itemId, DbUpdateInfo dbUpdateInfo); + public abstract bool MangageItemOptions (List itemOptions); + + //------------------------------------------------------------------- + // Utilities. + public static List YesNoValues { get { return new List(yesNoValues); } } + + //------------------------------------------------------------------- + // I t e m O p t i o n s + //------------------------------------------------------------------- + // Item Option definitions. + public abstract List GetItemOptionDfnsForItem (ItemId itemId); + // Retrieve specific option. + public abstract SaItemOption GetItemOption (ItemId itemId, String name, String context); + // Retrieve options. + public abstract List GetItemOptions (SaItemOption.Params prmItemOption); + public abstract List GetItemOptionsForItem (ItemId itemId); + // Retrieve specific option for range of items (all matching items if item id list is blank). + public abstract List GetItemOptions (List itemIds, String name, String context); + + //------------------------------------------------------------------- + // Create an item option. + public abstract SaItemOption CreateItemOption (ItemId itemId, String name, String context); + // Item independent options, eg. for processes. + public abstract SaItemOption CreateItemOptionByNameContext (String name, String context); + + //------------------------------------------------------------------- + // Store item option - create if not exsts, otherwise update. + public abstract bool StoreItemOption (SaItemOption itemOption, DbUpdateInfo dbUpdateInfo); + // Store a specific option against the specified item. + public abstract bool StoreItemOption (ItemId itemId, String name, String context, String sValue, DbUpdateInfo dbUpdateInfo); + // Store the list of options against the specified item. Options no longer in the list are deleted. + public abstract bool StoreItemOptions (ItemId itemId, List itemOptions, DbUpdateInfo dbUpdateInfo); + // Remove option(s) for the specified item. + public abstract bool ClearItemOption (SaItemOption itemOption, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearItemOption (ItemId itemId, String name, String context, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearItemOptions (ItemId itemId, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ItemOptions/SaItemOptionDbDfn.cs b/SacsStandardBase/Base/ItemOptions/SaItemOptionDbDfn.cs new file mode 100644 index 0000000..074a1e0 --- /dev/null +++ b/SacsStandardBase/Base/ItemOptions/SaItemOptionDbDfn.cs @@ -0,0 +1,317 @@ +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:46 +//--------------------------------------------------------------------------- +namespace Sarand.Net.Standard.Base.ItemOptions +{ + public class SaItemOptionDbDfn + { + //------------------------------------------------------------------- + // Table identifiers. + public enum TableIdx + { + Unknown = 0, + SaItemOption, + } + + //------------------------------------------------------------------- + // Column identifiers. + public enum ColumnIdx + { + Unknown = 0, + SaItemOption_ItemType, + SaItemOption_ItemId, + SaItemOption_Name, + SaItemOption_Context, + SaItemOption_Value, + SaItemOption_Notes, + SaItemOption_Flags, + SaItemOption_Status, + SaItemOption_CreateOn, + SaItemOption_CreateBy, + SaItemOption_CreateHost, + SaItemOption_UpdateOn, + SaItemOption_UpdateBy, + SaItemOption_UpdateHost, + } + + //------------------------------------------------------------------- + // Data members. + private static readonly int tableCount = 1; + private static readonly int columnCount = 14; + 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) + { + SaItemOptionDbDfn.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) + { + SaItemOptionDbDfn.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 = + { + "SaItemOption", + }; + + //------------------------------------------------------------------- + // 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,"ItemType",3,5,false,5), + new ColumnDfn(1,"ItemId",4,4,false,10), + new ColumnDfn(1,"Name",0,-9,false,64), + new ColumnDfn(1,"Context",0,-9,false,64), + new ColumnDfn(1,"Value",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), + }; + + //------------------------------------------------------------------- + // 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/ItemOptions/SaItemOptionDfn.cs b/SacsStandardBase/Base/ItemOptions/SaItemOptionDfn.cs new file mode 100644 index 0000000..befb3e1 --- /dev/null +++ b/SacsStandardBase/Base/ItemOptions/SaItemOptionDfn.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.ItemOptions +{ + //======================================================================= + public class SaItemOptionDfn + { + //------------------------------------------------------------------- + // Item Option Action. + public delegate bool ItemOptionActionFunction (SaItemOption itemOption, List itemOptions); + public class Action + { + public Action (String caption, ItemOptionActionFunction itemOptionActionFn) + { + Caption = caption; + ItemOptionActionFn = itemOptionActionFn; + } + public String Caption { get; set; } = ""; + public ItemOptionActionFunction ItemOptionActionFn { get; set; } = null; + } + + //------------------------------------------------------------------- + // Item Option display options. + public class Display + { + public String Group { get; set; } = ""; + public String Caption { get; set; } = ""; + /// Sort sequence for display. + public int Sequence { get; set; } = 0; + /// Allowed contexts ('Value' aganst each context defines default value). + public List Contexts { get; } = new List(); + /// Allowed values. + public List Values { get; } = new List(); + // Maximum value is defined by database. + public int MaxLength { get; set; } = 0; + // User must pick from the supplied list. + public bool ExclusiveValues { get; set; } = true; + public bool RightText { 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; + public bool Encrypted { get; set; } = false; + public bool ReadOnly { get; set; } = false; + public decimal MinValue { get; set; } = 0.0m; + public decimal MaxValue { get; set; } = 0.0m; + public bool RequiresContext { get; set; } = false; + public bool PopulateWithContext { get; set; } = false; + /// Defines available contexts and other rules, but should not be shown in list. + public bool NonDisplayTemplate { get; set; } = false; + /// User can add additional options for other contexts. + public bool CanAddOption { get; set; } = false; + /// Use can remove the option from the list. + public bool CanRemoveOption { get; set; } = false; + /// Browse for filename/folder etc. + public Action BrowseAction { get; set; } = null; + } + + //------------------------------------------------------------------- + // Properties. + /// Option Name - identifying code. + public String Name { get; set; } = ""; + public String DisplayName { get { return StringUtil.OnEmpty(DisplayOptions.Caption,Name); } } + public String DefaultValue { get; set; } = ""; + public String DefaultNotes { get; set; } = ""; + public Display DisplayOptions { get; } = new Display(); + + //------------------------------------------------------------------- + // Utilities. + public String GetContextDesc (String context) + { + return DisplayOptions.Contexts.GetDescForCode(context, context); + } + + //------------------------------------------------------------------- + } + //======================================================================= + public static class SaItemOptionDfnList + { + //------------------------------------------------------------------- + // List value properties. + public static List GetNames (this List values) + { + List names = new List(); + foreach (var itemOptionDfn in values) + { + if (!StringUtil.IsEmpty(itemOptionDfn.Name) && !names.ContainsCI(itemOptionDfn.Name)) + names.Add(itemOptionDfn.Name); + } + return names; + } + + //------------------------------------------------------------------- + // Retrieve available Item Option Definitions, ie. those that aren't fully defined in the list of item options. + public static List GetAvailableItemOptionDfns (this List values, List itemOptions) + { + List availableItemOptionDfns = new List(); + foreach (SaItemOptionDfn itemOptionDfn in values) + { + if (itemOptionDfn.DisplayOptions.Contexts.Count == 0) + { + // Option has no context. + if (!itemOptions.IsOptionDefined(itemOptionDfn.Name, "")) + availableItemOptionDfns.Add(itemOptionDfn); + } + else + { + List definedOptionContexts = itemOptions.GetContextsForName(itemOptionDfn.Name); + if (itemOptionDfn.DisplayOptions.Contexts.Exists(item => !definedOptionContexts.ContainsCI(item.Code))) + availableItemOptionDfns.Add(itemOptionDfn); + } + } + return availableItemOptionDfns; + } + + //------------------------------------------------------------------- + // Retrieve Item Option Definition + public static SaItemOptionDfn GetDfnForItemOption (this List values, SaItemOption itemOption) + { + return values.GetItemOptionDfnByName(itemOption.Name); + } + public static SaItemOptionDfn GetItemOptionDfnByName (this List values, String name) + { + if (StringUtil.IsEmpty(name)) return null; + foreach (var itemOptionDfn in values) + { + if (itemOptionDfn.Name.EqualsCI(name)) + return itemOptionDfn; + } + return null; + } + + //------------------------------------------------------------------- + // Sorting + public enum SortBy + { + Name, + DisplayName, + } + public static void Sort (this List values) + { + } + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ItemOptions/SaItemOptionMgr.cs b/SacsStandardBase/Base/ItemOptions/SaItemOptionMgr.cs new file mode 100644 index 0000000..8b7de50 --- /dev/null +++ b/SacsStandardBase/Base/ItemOptions/SaItemOptionMgr.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; + +namespace Sarand.Net.Standard.Base.ItemOptions +{ + //======================================================================= + public abstract class SaItemOptionMgr : SacsItemMgr + { + //------------------------------------------------------------------- + // Data members. + private static readonly List itemOptionStatuses = new List(); + + //------------------------------------------------------------------- + // Static initialisation. + static SaItemOptionMgr () + { + // Item option statuses. + itemOptionStatuses.Add(SaStatus.Current); + itemOptionStatuses.Add(SaStatus.Deleted); + } + + //------------------------------------------------------------------- + // Construction. + protected SaItemOptionMgr () + { + } + + //------------------------------------------------------------------- + // Properties. + public abstract bool DatabaseIsValid { get; } + public abstract bool CanUpdateDatabase { get; } + + //******************************************************************* + // I t e m O p t i o n s + //******************************************************************* + // Statuses. + public static SaStatus ActiveItemOptionStatus { get { return SaStatus.Current; } } + public static List ItemOptionStatuses { get { return itemOptionStatuses; } } + + //------------------------------------------------------------------- + // Associated data. + + //------------------------------------------------------------------- + // Retrieve individual item options. + public abstract bool ItemOptionExists (SaItemOption itemOption); + public abstract bool ItemOptionExists (ItemId itemId, String name, String context); + public abstract SaItemOption GetItemOption (SaItemOption itemOption); + public abstract SaItemOption GetItemOption (ItemId itemId, String name, String context); + + // Create Item Option. + public abstract SaItemOption CreateItemOption (ItemId itemId, String name, String context); + + //------------------------------------------------------------------- + // Validate Item Option. + public bool IsValidItemOption (SaItemOption itemOption) + { + ClearErrorState(); + if (itemOption == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidItemOptionData(itemOption.GetData(), new SaItemOption.ErrField()); + } + public virtual bool IsValidItemOptionData (SaItemOption.Data itemOptionData, SaItemOption.ErrField errField) + { + ClearErrorState(); + if ((int)itemOptionData.ItemId.Type < 1) + { + errField.ItemType = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Item type must be supplied"); + } + if (!SysId.IsValid(itemOptionData.ItemId.Id)) + { + errField.ItemId = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Item Id must be supplied"); + } + if (!IsValidValue(itemOptionData.Name, "name", (long)SaItemOptionDbDfn.GetColTextLen(SaItemOptionDbDfn.ColumnIdx.SaItemOption_Name), true)) + { + errField.Name = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!IsValidValue(itemOptionData.Context, "context", (long)SaItemOptionDbDfn.GetColTextLen(SaItemOptionDbDfn.ColumnIdx.SaItemOption_Context))) + { + errField.Context = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!IsValidValue(itemOptionData.Value, "value", (long)SaItemOptionDbDfn.GetColTextLen(SaItemOptionDbDfn.ColumnIdx.SaItemOption_Value))) + { + errField.Value = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + // Common properties. + if (!IsValidValue(itemOptionData.Notes, "notes", (long)SaItemOptionDbDfn.GetColTextLen(SaItemOptionDbDfn.ColumnIdx.SaItemOption_Notes))) + { + errField.Notes = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!ItemOptionStatuses.Contains(itemOptionData.Status)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState(ErrorMessage.InvalidStatus); + } + return true; + } + + //------------------------------------------------------------------- + // Database interface. + public abstract bool InsertItemOption (SaItemOption itemOption, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateItemOption (SaItemOption itemOption, DbUpdateInfo dbUpdateInfo); + public virtual bool ItemOptionCanBeDeleted (SaItemOption itemOption) + { + ClearErrorState(); + return true; + } + public abstract bool DeleteItemOption (SaItemOption itemOption, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearItemOptionsForItem (ItemId itemId, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // Item list. + public abstract ColumnDfn GetItemOptionColumnDfn (SaItemOption.Column columnId, String caption = "", int width = 0); + public abstract List GetItemOptionQueryFilter (SaItemOption.Params prm); + public abstract long GetItemOptionCount (SaItemOption.Params prm); + public abstract List GetItemOptionList (SaItemOption.Params prm); + public abstract DbResultSet ExecuteItemOptionQuery (List columns, SaItemOption.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 DataValue GetItemOptionDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + switch ((SaItemOption.Column)column.Id) + { + //----------------------------------------------------------- + // Item type. + case SaItemOption.Column.ItemType_Desc: + { + short itemType = (short)values.GetValue(column.DataName).AsLong(); + value = new DataValue(itemType.ToString()); + } + break; + + //----------------------------------------------------------- + // Notes. + case SaItemOption.Column.Notes: + { + value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200)); + } + break; + + //----------------------------------------------------------- + // Status. + case SaItemOption.Column.Status_Desc: + case SaItemOption.Column.Status_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaItemOption.Column.Status_Desc) + value = new DataValue(SacsSystem.GetStatusDescForCode(statusCode)); + else if (column.Id == (int)SaItemOption.Column.Status_Ind) + value = new DataValue(SaItemOption.GetStatusTriState(status)); + } + break; + + //----------------------------------------------------------- + } + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Procs/SacsProcess.cs b/SacsStandardBase/Base/Procs/SacsProcess.cs new file mode 100644 index 0000000..f379016 --- /dev/null +++ b/SacsStandardBase/Base/Procs/SacsProcess.cs @@ -0,0 +1,244 @@ +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.Utils; + +namespace Sarand.Net.Standard.Base.Procs +{ + //======================================================================= + public interface ISacsProcessNotifier + { + void OnStartProcess (); + void SetProgressInfo (List progressInfo); + void OnEndProcess (); + } + + //======================================================================= + public abstract class SacsProcess : SacsDisposable + { + //------------------------------------------------------------------- + // Process options and groups - for configuration of process to run. + // See Sarand.Win.Forms.Utility.SaForm_SelectOptions. + public class OptionData + { + public OptionData (TypeCodeDesc option) + { + Option = option; + } + public TypeCodeDesc Option { get; set; } + public String Notes { get; set; } = ""; + public bool Selected { get; set; } = false; + } + public class GroupData + { + public GroupData (TypeCodeDesc optionGroup) + { + OptionGroup = optionGroup; + } + public TypeCodeDesc OptionGroup { get; set; } + public List Options { get; } = new List(); + public bool ExclusiveOptions { get; set; } = false; + public bool Selected { get; set; } = false; + } + + //------------------------------------------------------------------- + // Configuration. + public enum ProcessType + { + Unknown = 0, + ImportData, + ExportData, + ImportExportData, + Other, + } + public class Config + { + protected Config () + { + } + /// Identifies the type of process. + public ProcessType ProcessType { get; set; } = ProcessType.Unknown; + /// Is the process to be run in 'Test' mode. + public bool TestProcess{ get; set; } = SacsSystem.DebugMode; + /// Process imports data. + public bool Import { get { return ProcessType == ProcessType.ImportData || ProcessType == ProcessType.ImportExportData; } } + /// Process exports data. + public bool Export { get { return ProcessType == ProcessType.ExportData || ProcessType == ProcessType.ImportExportData; } } + /// Can the process be interrupted? + public bool AllowInterruption { get; set; } = true; + /// Confirm when interruption is requested. + public bool ConfirmInterruption { get; set; } = true; + /// Automated processes must not display desktop coponents - popup messages etc. + public bool Automated { get; set; } = false; + } + + //------------------------------------------------------------------- + // Data members. + private Config config; + private String processError = ""; + + //------------------------------------------------------------------- + // Construction. + protected SacsProcess (Config config) + { + this.config = config; + } + protected override void Dispose (bool disposing) + { + if (disposing) + { + } + } + + //------------------------------------------------------------------- + // Properties. + public bool DebugMode { get { return SacsSystem.DebugMode; } } + public ISacsProcessNotifier ProcessNotifier { get; set; } = null; + public bool Processing { get; protected set; } = false; + public bool ProcessEnded { get; protected set; } = false; + public DateTime StartedAt { get; private set; } = DateTime.MinValue; + public DateTime FinishedAt { get; private set; } = DateTime.MinValue; + public int ElapsedSecs + { + get + { + if (DateUtil.IsValid(StartedAt)) + return (int)((DateUtil.IsValid(FinishedAt) ? FinishedAt : DateTime.Now) - StartedAt).TotalSeconds; + return 0; + } + } + + //------------------------------------------------------------------- + // Properties. + /// Process caption. + public abstract String ProcessDescription { get; } + /// Process comments - to be displayed with process selection. + public virtual List ProcessComments { get { return new List(); } } + /// Processing statistics. + public virtual List ProcessingStatistics { get { return new List(); } } + + //------------------------------------------------------------------- + // Run process inline. + public bool RunProcess () + { + String fn = GetType().Name+".RunProcess"; + if (ProcessNotifier == null) + throw new NotSupportedException("Process Notifier must be specified"); + // Prepare process. + bool runProcess = false; + Reset(); + Processing = true; + StartedAt = DateTime.Now; + try + { + // Notify start of process and log/ignore exceptions. + ProcessNotifier.OnStartProcess(); + } + catch (Exception exc) + { + SaLogger.Error(fn + " OnStartProcess: " + GetProcessError() + exc.GetStackEntries(6).Concat("", "\n\tat "), true); + } + try + { + // Run process. + OnProcessStarting(); + runProcess = Run(); + OnProcessEnding(); + } + catch (Exception exc) + { + SetProcessError(exc.GetType().Name + ": " + exc.Message); + SaLogger.Error(fn + " " + GetProcessError() + exc.GetStackEntries(6).Concat("", "\n\tat "), true); + } + // Processing completed. + FinishedAt = DateTime.Now; + Processing = false; + ProcessEnded = true; + try + { + // Notify end of process and log/ignore exceptions. + ProcessNotifier.OnEndProcess(); + } + catch (Exception exc) + { + SaLogger.Error(fn + " OnEndProcess: " + GetProcessError() + exc.GetStackEntries(6).Concat("", "\n\tat "), true); + } + return runProcess; + } + protected abstract bool Run (); + protected virtual void OnProcessStarting () { } + protected virtual void OnProcessEnding () { } + + //------------------------------------------------------------------- + // Process control. + public bool OK + { + get + { + if (HasProcessError()) return false; + if (isInterrupted) return false; + return true; + } + } + private void Reset () + { + ClearProcessError(); + isInterrupted = false; + ProcessEnded = false; + StartedAt = DateTime.MinValue; + FinishedAt = DateTime.MinValue; + } + + //------------------------------------------------------------------- + // Progress information. + protected void SetProgressInfo (String progressInfo) + { + SetProgressInfo(StringListUtil.CreateList(progressInfo)); + } + protected void SetProgressInfo (List progressInfo) + { + if (ProcessNotifier != null) + ProcessNotifier.SetProgressInfo(progressInfo); + } + + //------------------------------------------------------------------- + // Error handling. + public bool HasProcessError () { return !StringUtil.IsEmpty(processError); } + public String GetProcessError () { return StringUtil.EmptyOnNull(processError); } + public void ClearProcessError () { processError = ""; } + /// Record the processing error - always returns false. + public bool SetProcessError (String error) + { + processError = StringUtil.EmptyOnNull(error); + return false; + } + + //------------------------------------------------------------------- + // Interruption. + private bool isInterrupted = false; + public bool IsInterrupted + { + get { return isInterrupted; } + } + public bool Interrupt () + { + if (!config.AllowInterruption) + { + SaLogger.Warn("SacsProcess.Interrupt() Interruptions not allowed in " + this.GetType().Name); + return false; + } + if (config.ConfirmInterruption && !IsInterruptionConfirmed()) return false; + isInterrupted = true; + return true; + } + protected virtual bool IsInterruptionConfirmed () + { + return true; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Procs/SacsReport.cs b/SacsStandardBase/Base/Procs/SacsReport.cs new file mode 100644 index 0000000..192e551 --- /dev/null +++ b/SacsStandardBase/Base/Procs/SacsReport.cs @@ -0,0 +1,66 @@ +using System; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Bus; + +namespace Sarand.Net.Standard.Base.Procs +{ + //======================================================================= + public abstract class SacsReport : SaErrorStateItem + { + //------------------------------------------------------------------- + // Definitions. + + //------------------------------------------------------------------- + // Configuration. + public class Config + { + } + + //------------------------------------------------------------------- + // Data members. + private Config config = null; + + //------------------------------------------------------------------- + // Construction. + protected SacsReport (Config config) + { + this.config = config; + } + protected override void Dispose (bool disposing) + { + if (disposing) + { + } + } + + //------------------------------------------------------------------- + // Properties. + public abstract String Caption { get; } + public abstract bool ValidConfig { get; } + public bool DebugMode { get { return SacsSystem.DebugMode; } } + + //------------------------------------------------------------------- + // P a g i n g + //------------------------------------------------------------------- + /// First Page number currently being printed. + public int PageNumberFrom { get; protected set; } = 1; + /// Last Page number currently being printed. + public int PageNumberTo { get; protected set; } = 1; + /// Page number currently being printed. + public int PageNumber { get; protected set; } = 1; + /// Does the report require the page count, eg. to be printed in footer. + public virtual bool RequirePageCount { get; } + /// + /// Highest page number known to be in report. + /// Used to indicate minimum page range prior to page count being known. + /// + public int PageNumberMaxKnown { get; protected set; } = 1; + /// Total number of pages - only populated once confirmed. + public int PageCount { get; protected set; } + /// Report progress (0-100) where available. + public virtual decimal ProgressPercent { get; } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaCurrency.cs b/SacsStandardBase/Base/ReferenceData/SaCurrency.cs new file mode 100644 index 0000000..cd5e7ec --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaCurrency.cs @@ -0,0 +1,302 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + public abstract class SaCurrency : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Hidden, + Pos, + Id, + // Currency. + Code, + Name, + PrimaryCurrencyId, + PrimaryCurrency_Code, + PrimaryCurrency_Name, + Division, + RegionId, + Region_Name, + Symbol, + SuffixSymbol_Y, + SuffixSymbol_Tick, + IncludeSpace_Y, + IncludeSpace_Tick, + Decimals, + // Common properties. + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public String Code { get; set; } = ""; + public String Name { get; set; } = ""; + public SysId CurrencyIdPrimary { get; set; } = new SysId(); + public String Division { get; set; } = ""; + public SysId RegionId { get; set; } = new SysId(); + public String Symbol { get; set; } = ""; + public bool SuffixSymbol { get; set; } = false; + public bool IncludeSpace { get; set; } = false; + public short Decimals { get; set; } = 0; + } + + //------------------------------------------------------------------- + // Data. + public new class Data : SacsHierarchyItem.Data + { + // Data members. + private readonly DataProperties data = new DataProperties(); + // Construction. + public Data () + { + // Currency has no status. + } + // Properties. + public String Code + { + get { return data.Code; } + set { CheckChanged(data.Code, value); data.Code = value; } + } + public String Name + { + get { return data.Name; } + set { CheckChanged(data.Name, value); data.Name = value; } + } + public SysId CurrencyIdPrimary + { + get { return data.CurrencyIdPrimary; } + set { CheckChanged(data.CurrencyIdPrimary, value); data.CurrencyIdPrimary = value; } + } + public String Division + { + get { return data.Division; } + set { CheckChanged(data.Division, value); data.Division = value; } + } + public SysId RegionId + { + get { return data.RegionId; } + set { CheckChanged(data.RegionId, value); data.RegionId = value; } + } + public String Symbol + { + get { return data.Symbol; } + set { CheckChanged(data.Symbol, value); data.Symbol = value; } + } + public bool SuffixSymbol + { + get { return data.SuffixSymbol; } + set { CheckChanged(data.SuffixSymbol, value); data.SuffixSymbol = value; } + } + public bool IncludeSpace + { + get { return data.IncludeSpace; } + set { CheckChanged(data.IncludeSpace, value); data.IncludeSpace = value; } + } + public short Decimals + { + get { return data.Decimals; } + set { CheckChanged(data.Decimals, value); data.Decimals = value; } + } + + internal void PopulateFrom (Data data) + { + Code = data.Code; + Name = data.Name; + CurrencyIdPrimary = data.CurrencyIdPrimary; + Division = data.Division; + RegionId = data.RegionId; + Symbol = data.Symbol; + SuffixSymbol = data.SuffixSymbol; + IncludeSpace = data.IncludeSpace; + Decimals = data.Decimals; + base.PopulateFrom(data); + } + public Data Clone () + { + Data data = new Data(); + data.PopulateFrom(this); + return data; + } + } + + //------------------------------------------------------------------- + public new class ErrField : SacsHierarchyItem.ErrField + { + // Identifies the field that has invalid data. + public ValidValue Code { get; set; } = ValidValue.Valid; + public ValidValue Name { get; set; } = ValidValue.Valid; + public ValidValue CurrencyIdPrimary { get; set; } = ValidValue.Valid; + public ValidValue Division { get; set; } = ValidValue.Valid; + public ValidValue RegionId { get; set; } = ValidValue.Valid; + public ValidValue Symbol { get; set; } = ValidValue.Valid; + public ValidValue SuffixSymbol { get; set; } = ValidValue.Valid; + public ValidValue IncludeSpace { get; set; } = ValidValue.Valid; + public ValidValue Decimals { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + + //------------------------------------------------------------------- + // Static construction. + static SaCurrency () + { + } + + //------------------------------------------------------------------- + // Construction. + protected SaCurrency (SysId id) : base(id) + { + base.SetData(data); + // Currency has no status. + } + + //------------------------------------------------------------------- + // Statuses - not applicable for currencies. + + //------------------------------------------------------------------- + // Utilities. + public String FormatAmount (decimal amount) + { + return FormatAmount(Symbol, SuffixSymbol, IncludeSpace, amount, Decimals); + } + public static String FormatAmount (String symbol, bool suffix, bool includeSpace, decimal amount, short decimals) + { + String format = "N" + Math.Max(0, Math.Min(10, (int)decimals)); + String amountFmt = amount.ToString(format); + if (suffix) amountFmt += (includeSpace ? " " : "") + symbol; + else amountFmt = symbol + (includeSpace ? " " : "") + amountFmt; + return amountFmt; + } + + //------------------------------------------------------------------- + // Data. + 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"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + + //------------------------------------------------------------------- + // Properties. + public String Code + { + get { return data.Code; } + set { data.Code = value; } + } + public String Name + { + get { return data.Name; } + set { data.Name = value; } + } + public SysId CurrencyIdPrimary + { + get { return data.CurrencyIdPrimary; } + set { data.CurrencyIdPrimary = value; } + } + public String Division + { + get { return data.Division; } + set { data.Division = value; } + } + public SysId RegionId + { + get { return data.RegionId; } + set { data.RegionId = value; } + } + public String Symbol + { + get { return data.Symbol; } + set { data.Symbol = value; } + } + public bool SuffixSymbol + { + get { return data.SuffixSymbol; } + set { data.SuffixSymbol = value; } + } + public bool IncludeSpace + { + get { return data.IncludeSpace; } + set { data.IncludeSpace = value; } + } + public short Decimals + { + get { return data.Decimals; } + set { data.Decimals = value; } + } + + //------------------------------------------------------------------- + // A s s o c i a t e d D a t a E l e m e n t s + //------------------------------------------------------------------- + public abstract bool HasDependentCurrencies (); + + //------------------------------------------------------------------- + // P a r a m s + //------------------------------------------------------------------- + /// + /// Parameters - used to define the filters that should be applied to a List of Currencies + /// + // List parameters. + public enum StatusCat + { + Any, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + // List filtering. + public List IncludedIds { get; } = new List(); + public List ExcludedIds { get; } = new List(); + // Currency. + public String Code { get; set; } + public String CodePartial { get; set; } + public String Name { get; set; } + public String NamePartial { get; set; } + public SysId RegionId { get; set; } + public StatusCat StatusCat { get; set; } = StatusCat.Any; + } + + //------------------------------------------------------------------- + } + //======================================================================= + // C u r r e n c y D a t a L i s t + //======================================================================= + public static class SaCurrencyList + { + public static SaCurrency FindById (this List values, SysId currencyId) + { + if (!SysId.IsValid(currencyId)) return null; + return values.Find(item => item.Id.Equals(currencyId)); + } + public static SaCurrency FindByCode (this List values, String currencyCode) + { + if (StringUtil.IsEmpty(currencyCode)) return null; + return values.Find(item => item.Code.EqualsCI(currencyCode)); + } + public static SaCurrency FindBySymbol (this List values, String currencySymbol) + { + if (StringUtil.IsEmpty(currencySymbol)) return null; + return values.Find(item => item.Symbol.EqualsCI(currencySymbol)); + } + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaLanguage.cs b/SacsStandardBase/Base/ReferenceData/SaLanguage.cs new file mode 100644 index 0000000..568ca83 --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaLanguage.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Bus; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + public abstract class SaLanguage : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Hidden, + Pos, + Id, + // Language. + Code, + Name, + LanguageIdPrimary, + Unicode, + Translation, + // Common properties. + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public String Code { get; set; } = ""; + public String Name { get; set; } = ""; + public SysId LanguageIdPrimary { get; set; } = new SysId(); + public bool Unicode { get; set; } = false; + public bool Translation { get; set; } = false; + } + + //------------------------------------------------------------------- + // Data. + public new class Data : SacsHierarchyItem.Data + { + // Data members. + private readonly DataProperties data = new DataProperties(); + // Construction. + public Data () + { + // Language has no status. + } + // Properties. + public String Code + { + get { return data.Code; } + set { CheckChanged(data.Code, value); data.Code = value; } + } + public String Name + { + get { return data.Name; } + set { CheckChanged(data.Name, value); data.Name = value; } + } + public SysId LanguageIdPrimary + { + get { return data.LanguageIdPrimary; } + set { CheckChanged(data.LanguageIdPrimary, value); data.LanguageIdPrimary = value; } + } + public bool Unicode + { + get { return data.Unicode; } + set { CheckChanged(data.Unicode, value); data.Unicode = value; } + } + public bool Translation + { + get { return data.Translation; } + set { CheckChanged(data.Translation, value); data.Translation = value; } + } + internal void PopulateFrom (Data data) + { + Code = data.Code; + Name = data.Name; + LanguageIdPrimary = data.LanguageIdPrimary; + Unicode = data.Unicode; + Translation = data.Translation; + base.PopulateFrom(data); + } + public Data Clone () + { + Data data = new Data(); + data.PopulateFrom(this); + return data; + } + } + + //------------------------------------------------------------------- + public new class ErrField : SacsHierarchyItem.ErrField + { + // Identifies the field that has invalid data. + public ValidValue Code { get; set; } = ValidValue.Valid; + public ValidValue Name { get; set; } = ValidValue.Valid; + public ValidValue LanguageIdPrimary { get; set; } = ValidValue.Valid; + public ValidValue Unicode { get; set; } = ValidValue.Valid; + public ValidValue Translation { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + + //------------------------------------------------------------------- + // Static construction. + static SaLanguage () + { + } + + //------------------------------------------------------------------- + // Construction. + protected SaLanguage (SysId id) : base(id) + { + base.SetData(data); + // Language has no status. + } + + //------------------------------------------------------------------- + // Statuses - not applicable for languages. + + //------------------------------------------------------------------- + // Utilities. + + //------------------------------------------------------------------- + // Data. + 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"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + + //------------------------------------------------------------------- + // Properties. + public String Code + { + get { return data.Code; } + set { data.Code = value; } + } + public String Name + { + get { return data.Name; } + set { data.Name = value; } + } + public SysId LanguageIdPrimary + { + get { return data.LanguageIdPrimary; } + set { data.LanguageIdPrimary = value; } + } + public bool Unicode + { + get { return data.Unicode; } + set { data.Unicode = value; } + } + public bool Translation + { + get { return data.Translation; } + set { data.Translation = value; } + } + + //------------------------------------------------------------------- + // A s s o c i a t e d D a t a E l e m e n t s + //------------------------------------------------------------------- + public abstract bool HasDependentLanguages (); + + //------------------------------------------------------------------- + // P a r a m s + //------------------------------------------------------------------- + // List parameters. + public enum StatusCat + { + Any, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + // List filtering. + public List IncludedIds { get; } = new List(); + public List ExcludedIds { get; } = new List(); + // Language. + public String Code { get; set; } + public String CodePartial { get; set; } + public String Name { get; set; } + public String NamePartial { get; set; } + public SysId LanguageIdPrimary { get; set; } + public StatusCat StatusCat { get; set; } = StatusCat.Any; + } + + //------------------------------------------------------------------- + } + //======================================================================= + // L a n g u a g e D a t a L i s t + //======================================================================= + public static class SaLanguageList + { + public static SaLanguage FindById (this List values, SysId languageId) + { + if (!SysId.IsValid(languageId)) return null; + return values.Find(item => item.Id.Equals(languageId)); + } + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-Currency.cs b/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-Currency.cs new file mode 100644 index 0000000..43bfda6 --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-Currency.cs @@ -0,0 +1,35 @@ +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + partial class SaReferenceDataConsole + { + //******************************************************************* + // C u r r e n c i e s + //******************************************************************* + // Properties. + public abstract short CurrencyItemType { get; } + public abstract String TableName_Currency { get; } + public abstract bool DatabaseIsValid_Currency { get; } + + // Retrieve Currency. + public abstract SaCurrency GetCurrency (SaCurrency.Params prmCurrency); + + // Currency list. + public abstract ColumnDfn GetCurrencyColumnDfn (SaCurrency.Column columnId, String caption = "", int width = 0); + public abstract List GetCurrencyQueryFilter (SaCurrency.Params prm); + public abstract long GetCurrencyCount (SaCurrency.Params prmCurrency); + public abstract List GetCurrencyList (SaCurrency.Params prmCurrency); + public abstract DbResultSet ExecuteCurrencyQuery (List columns, SaCurrency.Params prm); + public abstract DataValue GetCurrencyDataValue (ColumnDfn column, NamedValue.Map values); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-Language.cs b/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-Language.cs new file mode 100644 index 0000000..14c83f3 --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-Language.cs @@ -0,0 +1,35 @@ +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + partial class SaReferenceDataConsole + { + //******************************************************************* + // L a n g u a g e s + //******************************************************************* + // Properties. + public abstract short LanguageItemType { get; } + public abstract String TableName_Language { get; } + public abstract bool DatabaseIsValid_Language { get; } + + // Retrieve Language. + public abstract SaLanguage GetLanguage (SaLanguage.Params prmLanguage); + + // Language list. + public abstract ColumnDfn GetLanguageColumnDfn (SaLanguage.Column columnId, String caption = "", int width = 0); + public abstract List GetLanguageQueryFilter (SaLanguage.Params prm); + public abstract long GetLanguageCount (SaLanguage.Params prmLanguage); + public abstract List GetLanguageList (SaLanguage.Params prmLanguage); + public abstract DbResultSet ExecuteLanguageQuery (List columns, SaLanguage.Params prm); + public abstract DataValue GetLanguageDataValue (ColumnDfn column, NamedValue.Map values); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-Region.cs b/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-Region.cs new file mode 100644 index 0000000..f44a032 --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-Region.cs @@ -0,0 +1,47 @@ +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + partial class SaReferenceDataConsole + { + //******************************************************************* + // R e g i o n s + //******************************************************************* + // Utilities. + /// Return the valid parent type for the specified Region type. + public static SaRegionType ParentTypeFor (SaRegionType regionType) + { + return SaReferenceDataMgr.ParentTypeFor(regionType); + } + /// Return the valid subordinate Region type for the specified Region type. + public static SaRegionType SubordinateTypeFor (SaRegionType regionType) + { + return SaReferenceDataMgr.SubordinateTypeFor(regionType); + } + + // Properties. + public abstract short RegionItemType { get; } + public abstract String TableName_Region { get; } + public abstract bool DatabaseIsValid_Region { get; } + + // Retrieve Region. + public abstract SaRegion GetRegion (SaRegion.Params prmRegion); + + // Region list. + public abstract ColumnDfn GetRegionColumnDfn (SaRegion.Column columnId, String caption = "", int width = 0); + public abstract List GetRegionQueryFilter (SaRegion.Params prm); + public abstract long GetRegionCount (SaRegion.Params prmRegion); + public abstract List GetRegionList (SaRegion.Params prmRegion); + public abstract DbResultSet ExecuteRegionQuery (List columns, SaRegion.Params prm); + public abstract DataValue GetRegionDataValue (ColumnDfn column, NamedValue.Map values); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-TaxCode.cs b/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-TaxCode.cs new file mode 100644 index 0000000..b4f6400 --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-TaxCode.cs @@ -0,0 +1,47 @@ +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + partial class SaReferenceDataConsole + { + //******************************************************************* + // T a x C o d e s + //******************************************************************* + // Utilities. + public static String GetTaxCode (SaTaxCodeType taxCode) + { + switch (taxCode) + { + case SaTaxCodeType.Zero: return "T0"; + case SaTaxCodeType.Std: return "T1"; + case SaTaxCodeType.Exempt: return "T3"; + } + return ""; + } + + // Properties. + public abstract short TaxCodeItemType { get; } + public abstract String TableName_TaxCode { get; } + public abstract bool DatabaseIsValid_TaxCode { get; } + + // Retrieve Tax Code. + public abstract SaTaxCode GetTaxCode (SaTaxCode.Params prmTaxCode); + + // Tax Code list. + public abstract ColumnDfn GetTaxCodeColumnDfn (SaTaxCode.Column columnId, String caption = "", int width = 0); + public abstract List GetTaxCodeQueryFilter (SaTaxCode.Params prm); + public abstract long GetTaxCodeCount (SaTaxCode.Params prmTaxCode); + public abstract List GetTaxCodeList (SaTaxCode.Params prmTaxCode); + public abstract DbResultSet ExecuteTaxCodeQuery (List columns, SaTaxCode.Params prm); + public abstract DataValue GetTaxCodeDataValue (ColumnDfn column, NamedValue.Map values); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-UnitMeasure.cs b/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-UnitMeasure.cs new file mode 100644 index 0000000..0cc2cec --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole-UnitMeasure.cs @@ -0,0 +1,35 @@ +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + partial class SaReferenceDataConsole + { + //******************************************************************* + // U n i t s o f M e a s u r e + //******************************************************************* + // Properties. + public abstract short UnitMeasureItemType { get; } + public abstract String TableName_UnitMeasure { get; } + public abstract bool DatabaseIsValid_UnitMeasure { get; } + + // Retrieve Unit of Measure. + public abstract SaUnitMeasure GetUnitMeasure (SaUnitMeasure.Params prmUnitMeasure); + + // Unit of Measure list. + public abstract ColumnDfn GetUnitMeasureColumnDfn (SaUnitMeasure.Column columnId, String caption = "", int width = 0); + public abstract List GetUnitMeasureQueryFilter (SaUnitMeasure.Params prm); + public abstract long GetUnitMeasureCount (SaUnitMeasure.Params prmUnitMeasure); + public abstract List GetUnitMeasureList (SaUnitMeasure.Params prmUnitMeasure); + public abstract DbResultSet ExecuteUnitMeasureQuery (List columns, SaUnitMeasure.Params prm); + public abstract DataValue GetUnitMeasureDataValue (ColumnDfn column, NamedValue.Map values); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole.cs b/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole.cs new file mode 100644 index 0000000..e33ce84 --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaReferenceDataConsole.cs @@ -0,0 +1,265 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; +using Sarand.Net.Standard.Base.UpdateLog; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //===================================================================== + // R e f e r e n c e D a t a + //===================================================================== + // Reference data item types. + public enum ReferenceDataItemType + { + Unknown = 0, + Language, + Region, + Currency, + TaxCode, + UnitMeasure, + } + //------------------------------------------------------------------- + // Reference data code types. + public enum ReferenceDataSystemCode + { + RegionType, + CurrencyCode, + TaxCodeType, + TaxCodeColumn, + UnitMeasureCategory, + UnitMeasureType, + } + //------------------------------------------------------------------- + // Region types. + public enum SaRegionType + { + Unknown = 0, + Continent, // 'CON' + Country, // 'COU' + Province, // 'PRO' + Town, // 'TWN' + }; + //------------------------------------------------------------------- + // Currency code. + public enum SaCurrencyCode + { + Unknown = 0, + Sterling, + Euro, + }; + //------------------------------------------------------------------- + // Tax code - with Sage equivalent. + public enum SaTaxCodeType + { + Zero, // 'T0' + Std, // 'T1' + Exempt, // 'T2' or 'T3' + }; + //------------------------------------------------------------------- + // Tax code column. + public enum SaTaxCodeColumn + { + Unknown = 0, + Code = 10, + CodeAlt1, + CodeAlt2, + }; + //------------------------------------------------------------------- + // NB Do not change these values - they are tied to the database. + public enum SaUomCategory + { + /// Allows all UOM categories, eg. in combobox. + Any = -1, + /// UOM category is not defined. Allows any in filters, but none in combobox. + Unknown = 0, + Time = 1, + Pack = 2, + Weight = 3, + Length = 4, + Area = 5, + Volume = 6, + Density = 7, + /// 'BOX' - types of container, eg. box, pallet. + Box = 8, + }; + //------------------------------------------------------------------- + // NB Do not change these values - they are tied to the database. + public enum SaUomType + { + Any = -1, + Unknown = 0, + // Time units. + Time_Second = 100, + Time_Minute = 101, + Time_Hour = 102, + Time_Day = 110, + Time_WorkingDay = 111, + Time_Week = 120, + Time_WorkingWeek = 121, + Time_Fortnight = 122, + Time_Month = 140, + Time_Quarter = 141, + Time_Year = 142, + // Pack units. + Pack_Each = 200, + Pack_Set = 201, + // Weight units. + Weight_Gram = 300, + Weight_Kilogram = 301, + Weight_Tonne = 302, + // Length units. + Length_Metre = 400, + Length_Kilometre = 401, + // Area units. + Length_SquareMetre = 500, + Length_Hectare = 501, + Length_Acre = 590, + // Volume units. + Volume_Litre = 600, + Volume_CubicMetre = 601, + // Density units. + Density_KilogramsPerCubicMetre = 700, + Density_GramsPerCubicCentimeter = 701, + // Box units. + Box_Box = 800, + Box_Carton = 801, + Box_Pallet = 802, + }; + + //======================================================================= + public abstract partial class SaReferenceDataConsole : SaErrorStateItem + { + //------------------------------------------------------------------- + // Definitions. + + //------------------------------------------------------------------- + // Data members. + private static List regionTypes; + private static List currencyCodes; + private static List taxCodeTypes; + private static List taxCodeColumns; + private static List unitMeasureCategories; + private static List unitMeasureTypes; + + //------------------------------------------------------------------- + // Static initialisation. + static SaReferenceDataConsole () + { + } + + //------------------------------------------------------------------- + // Construction. + protected SaReferenceDataConsole () + { + } + + //------------------------------------------------------------------- + // Properties. + public abstract SaUpdateLogConsole UpdateLogConsole { get; } + // Database. + public abstract bool CanUpdateDatabase { get; } + + //--------------------------------------------------------------- + // Manage reference data - displays dialog. + public abstract bool ManageReferenceData (bool userCanManageReferenceData, DbUpdateInfo dbUpdateInfo); + + //--------------------------------------------------------------- + // Reference data codes. + public static List GetReferenceDataCodes (ReferenceDataSystemCode systemCode) + { + switch (systemCode) + { + case ReferenceDataSystemCode.RegionType: + if (regionTypes == null) + { + regionTypes = new List(); + regionTypes.Add(SaRegionType.Continent, "CON", "Continent"); + regionTypes.Add(SaRegionType.Country, "COU", "Country"); + regionTypes.Add(SaRegionType.Province, "PRO", "Province"); + regionTypes.Add(SaRegionType.Town, "TWN", "Town"); + } + return new List(regionTypes); + case ReferenceDataSystemCode.CurrencyCode: + if (currencyCodes == null) + { + currencyCodes = new List(); + currencyCodes.Add(SaCurrencyCode.Sterling, "GBP", "GBP"); + currencyCodes.Add(SaCurrencyCode.Euro, "EUR", "EUR"); + } + return new List(currencyCodes); + case ReferenceDataSystemCode.TaxCodeType: + if (taxCodeTypes == null) + { + taxCodeTypes = new List(); + taxCodeTypes.Add(SaTaxCodeType.Zero, "ZERO", "Zero"); + taxCodeTypes.Add(SaTaxCodeType.Std, "STD", "Standard"); + taxCodeTypes.Add(SaTaxCodeType.Exempt, "EXEMPT", "Exempt"); + } + return new List(taxCodeTypes); + case ReferenceDataSystemCode.TaxCodeColumn: + if (taxCodeColumns == null) + { + taxCodeColumns = new List(); + taxCodeColumns.Add(SaTaxCodeColumn.Code, SaReferenceDataDbDfn.GetColumnName(SaReferenceDataDbDfn.ColumnIdx.TaxCode_Code, false), "Tax code"); + taxCodeColumns.Add(SaTaxCodeColumn.CodeAlt1, SaReferenceDataDbDfn.GetColumnName(SaReferenceDataDbDfn.ColumnIdx.TaxCode_CodeAlt1, false), "Alt. tax code 1"); + taxCodeColumns.Add(SaTaxCodeColumn.CodeAlt2, SaReferenceDataDbDfn.GetColumnName(SaReferenceDataDbDfn.ColumnIdx.TaxCode_CodeAlt2, false), "Alt. tax code 2"); + } + return new List(taxCodeColumns); + case ReferenceDataSystemCode.UnitMeasureCategory: + if (unitMeasureCategories == null) + { + unitMeasureCategories = new List(); + unitMeasureCategories.Add(SaUomCategory.Time, "TIM", "Time"); + unitMeasureCategories.Add(SaUomCategory.Pack, "PCK", "Pack"); + unitMeasureCategories.Add(SaUomCategory.Weight, "WEI", "Weight"); + unitMeasureCategories.Add(SaUomCategory.Length, "LEN", "Length"); + unitMeasureCategories.Add(SaUomCategory.Area, "ARE", "Area"); + unitMeasureCategories.Add(SaUomCategory.Volume, "VOL", "Volume"); + unitMeasureCategories.Add(SaUomCategory.Density, "DEN", "Density"); + unitMeasureCategories.Add(SaUomCategory.Box, "BOX", "Box (container)"); + } + return new List(unitMeasureCategories); + case ReferenceDataSystemCode.UnitMeasureType: + if (unitMeasureTypes == null) + { + unitMeasureTypes = new List(); + unitMeasureTypes.Add(SaUomType.Time_Second, "SEC", "Second"); + unitMeasureTypes.Add(SaUomType.Time_Minute, "MIN", "Minute"); + unitMeasureTypes.Add(SaUomType.Time_Hour, "HR", "Hour"); + unitMeasureTypes.Add(SaUomType.Time_Day, "DAY", "Day"); + unitMeasureTypes.Add(SaUomType.Time_WorkingDay, "WDAY", "Working day"); + unitMeasureTypes.Add(SaUomType.Time_Week, "WK", "Week"); + unitMeasureTypes.Add(SaUomType.Time_WorkingWeek, "WWK", "Working week"); + unitMeasureTypes.Add(SaUomType.Time_Fortnight, "FNT", "Fortnight"); + unitMeasureTypes.Add(SaUomType.Time_Month, "MTH", "Month"); + unitMeasureTypes.Add(SaUomType.Time_Quarter, "QTR", "Quarter"); + unitMeasureTypes.Add(SaUomType.Time_Year, "YR", "Year"); + unitMeasureTypes.Add(SaUomType.Pack_Each, "EACH", "Each"); + unitMeasureTypes.Add(SaUomType.Pack_Set, "SET", "Set"); + unitMeasureTypes.Add(SaUomType.Weight_Gram, "GRM", "Gram"); + unitMeasureTypes.Add(SaUomType.Weight_Kilogram, "KG", "Kilogram"); + unitMeasureTypes.Add(SaUomType.Weight_Tonne, "TON", "Tonne"); + unitMeasureTypes.Add(SaUomType.Length_Metre, "METRE", "Metre"); + unitMeasureTypes.Add(SaUomType.Length_Kilometre, "KMET", "Kilometre"); + unitMeasureTypes.Add(SaUomType.Length_SquareMetre, "SQMET", "Square metre"); + unitMeasureTypes.Add(SaUomType.Length_Hectare, "HECT", "Hectare"); + unitMeasureTypes.Add(SaUomType.Length_Acre, "ACRE", "Acre"); + unitMeasureTypes.Add(SaUomType.Volume_Litre, "LITRE", "Litre"); + unitMeasureTypes.Add(SaUomType.Volume_CubicMetre, "CUBMET", "Cubic metre"); + unitMeasureTypes.Add(SaUomType.Density_KilogramsPerCubicMetre, "KPCMET", "Kg/m3"); + unitMeasureTypes.Add(SaUomType.Density_GramsPerCubicCentimeter, "GPCCMET", "g/cm3"); + unitMeasureTypes.Add(SaUomType.Box_Box, "BOX", "Box"); + unitMeasureTypes.Add(SaUomType.Box_Carton, "CARTON", "Carton"); + unitMeasureTypes.Add(SaUomType.Box_Pallet, "PALLET", "Pallet"); + } + return new List(unitMeasureTypes); + } + throw new NotSupportedException(typeof(SaReferenceDataConsole).Name + ".GetReferenceDataCodes Invalid code type specified: " + systemCode.ToString()); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaReferenceDataDbDfn.cs b/SacsStandardBase/Base/ReferenceData/SaReferenceDataDbDfn.cs new file mode 100644 index 0000000..721b589 --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaReferenceDataDbDfn.cs @@ -0,0 +1,466 @@ +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.Linq; + +//--------------------------------------------------------------------------- +// Code generated by Sarand Database Utilities Thu 3-Oct-2019 15:11:01 +//--------------------------------------------------------------------------- +namespace Sarand.Net.Standard.Base.ReferenceData +{ + public class SaReferenceDataDbDfn + { + //------------------------------------------------------------------- + // Table identifiers. + public enum TableIdx + { + Unknown = 0, + Currency, + Language, + Region, + TaxCode, + UnitMeasure, + } + + //------------------------------------------------------------------- + // Column identifiers. + public enum ColumnIdx + { + Unknown = 0, + Currency_Id, + Currency_Code, + Currency_Name, + Currency_Primary, + Currency_Division, + Currency_Region, + Currency_Symbol, + Currency_Suffix, + Currency_IncSpace, + Currency_Decimals, + Currency_CreateOn, + Currency_CreateBy, + Currency_CreateHost, + Currency_UpdateOn, + Currency_UpdateBy, + Currency_UpdateHost, + Language_Id, + Language_Code, + Language_Name, + Language_Primary, + Language_Unicode, + Language_Translation, + Language_CreateOn, + Language_CreateBy, + Language_CreateHost, + Language_UpdateOn, + Language_UpdateBy, + Language_UpdateHost, + Region_Id, + Region_Type, + Region_Name, + Region_Parent, + Region_Notes, + Region_Flags, + Region_Status, + Region_CreateOn, + Region_CreateBy, + Region_CreateHost, + Region_UpdateOn, + Region_UpdateBy, + Region_UpdateHost, + TaxCode_Id, + TaxCode_Code, + TaxCode_CodeAlt1, + TaxCode_CodeAlt2, + TaxCode_Description, + TaxCode_RegionId, + TaxCode_EffectFrom, + TaxCode_EffectTo, + TaxCode_TaxRate, + TaxCode_TaxRatePrev, + TaxCode_TaxCodeIdLink, + TaxCode_Notes, + TaxCode_Flags, + TaxCode_Status, + TaxCode_CreateOn, + TaxCode_CreateBy, + TaxCode_CreateHost, + TaxCode_UpdateOn, + TaxCode_UpdateBy, + TaxCode_UpdateHost, + UnitMeasure_Id, + UnitMeasure_Category, + UnitMeasure_Type, + UnitMeasure_UnitType, + UnitMeasure_Code, + UnitMeasure_Abbr, + UnitMeasure_Single, + UnitMeasure_Multiple, + UnitMeasure_EquValue, + UnitMeasure_UomIdEqu, + UnitMeasure_EquAlgorythm, + UnitMeasure_UomIdRef1, + UnitMeasure_UomIdRef2, + UnitMeasure_UomIdRef3, + UnitMeasure_Notes, + UnitMeasure_Flags, + UnitMeasure_Status, + UnitMeasure_CreateOn, + UnitMeasure_CreateBy, + UnitMeasure_CreateHost, + UnitMeasure_UpdateOn, + UnitMeasure_UpdateBy, + UnitMeasure_UpdateHost, + } + + //------------------------------------------------------------------- + // Data members. + private static readonly int tableCount = 5; + private static readonly int columnCount = 84; + 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) + { + SaReferenceDataDbDfn.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) + { + SaReferenceDataDbDfn.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 = + { + "Currency", + "Language", + "Region", + "TaxCode", + "UnitMeasure", + }; + + //------------------------------------------------------------------- + // 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,"Code",0,-9,false,50), + new ColumnDfn(1,"Name",0,-9,false,50), + new ColumnDfn(1,"Primary",4,4,false,10), + new ColumnDfn(1,"Division",0,-9,false,50), + new ColumnDfn(1,"Region",4,4,false,10), + new ColumnDfn(1,"Symbol",0,-9,false,12), + new ColumnDfn(1,"Suffix",1,-7,false,1), + new ColumnDfn(1,"IncSpace",1,-7,false,1), + new ColumnDfn(1,"Decimals",3,5,false,5), + 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,4), + new ColumnDfn(2,"Name",0,-9,false,50), + new ColumnDfn(2,"Primary",4,4,false,10), + new ColumnDfn(2,"Unicode",1,-7,false,1), + new ColumnDfn(2,"Translation",1,-7,false,1), + 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), + new ColumnDfn(3,"Id",4,4,false,10), + new ColumnDfn(3,"Type",0,-9,false,3), + new ColumnDfn(3,"Name",0,-9,false,50), + new ColumnDfn(3,"Parent",4,4,false,10), + new ColumnDfn(3,"Notes",0,-9,false,255), + new ColumnDfn(3,"Flags",3,5,false,5), + new ColumnDfn(3,"Status",0,-9,false,3), + new ColumnDfn(3,"CreateOn",9,93,false,23), + new ColumnDfn(3,"CreateBy",4,4,false,10), + new ColumnDfn(3,"CreateHost",0,-9,false,64), + new ColumnDfn(3,"UpdateOn",9,93,false,23), + new ColumnDfn(3,"UpdateBy",4,4,false,10), + new ColumnDfn(3,"UpdateHost",0,-9,false,64), + new ColumnDfn(4,"Id",4,4,false,10), + new ColumnDfn(4,"Code",0,-9,false,12), + new ColumnDfn(4,"CodeAlt1",0,-9,false,12), + new ColumnDfn(4,"CodeAlt2",0,-9,false,12), + new ColumnDfn(4,"Description",0,-9,false,50), + new ColumnDfn(4,"RegionId",4,4,false,10), + new ColumnDfn(4,"EffectFrom",9,93,false,23), + new ColumnDfn(4,"EffectTo",9,93,false,23), + new ColumnDfn(4,"TaxRate",7,6,false,53), + new ColumnDfn(4,"TaxRatePrev",7,6,false,53), + new ColumnDfn(4,"TaxCodeIdLink",4,4,false,10), + new ColumnDfn(4,"Notes",0,-9,false,255), + new ColumnDfn(4,"Flags",3,5,false,5), + new ColumnDfn(4,"Status",0,-9,false,3), + new ColumnDfn(4,"CreateOn",9,93,false,23), + new ColumnDfn(4,"CreateBy",4,4,false,10), + new ColumnDfn(4,"CreateHost",0,-9,false,64), + new ColumnDfn(4,"UpdateOn",9,93,false,23), + new ColumnDfn(4,"UpdateBy",4,4,false,10), + new ColumnDfn(4,"UpdateHost",0,-9,false,64), + new ColumnDfn(5,"Id",4,4,false,10), + new ColumnDfn(5,"Category",3,5,false,5), + new ColumnDfn(5,"Type",3,5,false,5), + new ColumnDfn(5,"UnitType",3,5,false,5), + new ColumnDfn(5,"Code",0,-9,false,12), + new ColumnDfn(5,"Abbr",0,-9,false,12), + new ColumnDfn(5,"Single",0,-9,false,50), + new ColumnDfn(5,"Multiple",0,-9,false,50), + new ColumnDfn(5,"EquValue",7,6,false,53), + new ColumnDfn(5,"UomIdEqu",4,4,false,10), + new ColumnDfn(5,"EquAlgorythm",4,4,false,10), + new ColumnDfn(5,"UomIdRef1",4,4,false,10), + new ColumnDfn(5,"UomIdRef2",4,4,false,10), + new ColumnDfn(5,"UomIdRef3",4,4,false,10), + new ColumnDfn(5,"Notes",0,-9,false,255), + new ColumnDfn(5,"Flags",3,5,false,5), + new ColumnDfn(5,"Status",0,-9,false,3), + new ColumnDfn(5,"CreateOn",9,93,false,23), + new ColumnDfn(5,"CreateBy",4,4,false,10), + new ColumnDfn(5,"CreateHost",0,-9,false,64), + new ColumnDfn(5,"UpdateOn",9,93,false,23), + new ColumnDfn(5,"UpdateBy",4,4,false,10), + new ColumnDfn(5,"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/ReferenceData/SaReferenceDataMgr.cs b/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr.cs new file mode 100644 index 0000000..e1e13c7 --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr.cs @@ -0,0 +1,36 @@ +using Sarand.Net.Standard.Base.Bus; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + public abstract partial class SaReferenceDataMgr : SacsItemMgr + { + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Static initialisation. + static SaReferenceDataMgr() + { + } + + //------------------------------------------------------------------- + // Construction. + protected SaReferenceDataMgr () + { + } + + //------------------------------------------------------------------- + // Properties. + // Database. + public abstract bool DatabaseIsValid_Language { get; } + public abstract bool DatabaseIsValid_Region { get; } + public abstract bool DatabaseIsValid_Currency { get; } + public abstract bool DatabaseIsValid_TaxCode { get; } + public abstract bool DatabaseIsValid_UnitMeasure { get; } + public abstract bool CanUpdateDatabase { get; } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_Currency.cs b/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_Currency.cs new file mode 100644 index 0000000..7a7ede9 --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_Currency.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + public abstract partial class SaReferenceDataMgr : SacsItemMgr + { + //******************************************************************* + // C u r r e n c i e s + //******************************************************************* + // Create currency. + public abstract SaCurrency CreateCurrency (); + + // Retrieve individual currencies. + public abstract bool CurrencyExistsById (SysId id); + public abstract bool CurrencyExistsByCode (String code); + public abstract SaCurrency GetCurrencyById (SysId id); + public abstract SaCurrency GetCurrencyByCode (String code); + public abstract SaCurrency GetCurrency (SaCurrency.Params prm); + + //------------------------------------------------------------------- + // Validate currency data. + public bool IsValidCurrency (SaCurrency currency) + { + ClearErrorState(); + if (currency == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidCurrencyData(currency.GetData(), new SaCurrency.ErrField()); + } + public virtual bool IsValidCurrencyData (SaCurrency.Data data, SaCurrency.ErrField errField) + { + ClearErrorState(); + // Currency properties. + if (errField.Code != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Code, "code", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.Currency_Code), true)) + { + errField.Code = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Name != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Name, "name", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.Currency_Name), true)) + { + errField.Name = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Division != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Division, "division", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.Currency_Division))) + { + errField.Division = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Symbol != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Symbol, "symbol", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.Currency_Symbol), true)) + { + errField.Symbol = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Decimals != SacsHierarchyItem.ValidValue.Ignore && (data.Decimals < 0 || data.Decimals > 4)) + { + errField.Decimals = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Currency decimals must be between 0 and 4"); + } + // Validate associated data. + if (errField.RegionId != SacsHierarchyItem.ValidValue.Ignore && SysId.IsValid(data.RegionId)) + { + if (!RegionExistsById(data.RegionId)) + { + errField.RegionId = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Currency's region not found"); + } + } + if (errField.CurrencyIdPrimary != SacsHierarchyItem.ValidValue.Ignore && SysId.IsValid(data.CurrencyIdPrimary)) + { + if (!CurrencyExistsById(data.CurrencyIdPrimary)) + { + errField.CurrencyIdPrimary = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Currency's primary not found"); + } + } + return true; + } + + //------------------------------------------------------------------- + // Currency database interface. + public abstract bool InsertCurrency (SaCurrency currency, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateCurrency (SaCurrency currency, DbUpdateInfo dbUpdateInfo); + public virtual bool CurrencyCanBeDeleted (SaCurrency currency) + { + ClearErrorState(); + if (currency == null) return SetErrorState("Item not specified"); + if (currency.HasDependentCurrencies()) return SetErrorState("Has dependent currencies"); + return true; + } + public abstract bool DeleteCurrency (SaCurrency currency, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // Currency list. + public abstract ColumnDfn GetCurrencyColumnDfn (SaCurrency.Column columnId, String caption = "", int width = 0); + public abstract List GetCurrencyQueryFilter (SaCurrency.Params prm); + public abstract long GetCurrencyCount (SaCurrency.Params prm); + public abstract List GetCurrencyList (SaCurrency.Params prm); + public abstract DbResultSet ExecuteCurrencyQuery (List columns, SaCurrency.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 GetCurrencyDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + /* + switch ((SaCurrency.Column)column.Id) + { + //----------------------------------------------------------- + // Notes. + case SaCurrency.Column.Notes: + value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200)); + break; + + //----------------------------------------------------------- + // Status. + case SaCurrency.Column.Status_Desc: + case SaCurrency.Column.Status_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaCurrency.Column.Status_Desc) + { + String statusDesc = SacsSystem.GetStatusDescForCode(statusCode); + value = new DataValue(statusDesc); + } + else if (column.Id == (int)SaCurrency.Column.Status_Ind) + { + DataValue.TriStateType triState = DataValue.TriStateType.Unknown; + if (status == SaStatus.Active) + triState = DataValue.TriStateType.Green; + else if (status == SaStatus.Inactive) + triState = DataValue.TriStateType.Red; + value = new DataValue(triState); + } + } + break; + + //----------------------------------------------------------- + } + */ + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_Language.cs b/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_Language.cs new file mode 100644 index 0000000..72a8f9f --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_Language.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + public abstract partial class SaReferenceDataMgr : SacsItemMgr + { + //******************************************************************* + // L a n g u a g e s + //******************************************************************* + // Retrieve individual languages. + public abstract bool LanguageExistsById (SysId id); + public abstract bool LanguageExistsByCode (String code); + public abstract SaLanguage GetLanguageById (SysId id); + public abstract SaLanguage GetLanguageByCode (String code); + public abstract SaLanguage GetLanguage (SaLanguage.Params prm); + + // Create language. + public abstract SaLanguage CreateLanguage (); + + //------------------------------------------------------------------- + // Validate language data. + public bool IsValidLanguage (SaLanguage language) + { + ClearErrorState(); + if (language == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidLanguageData(language.GetData(), new SaLanguage.ErrField()); + } + public virtual bool IsValidLanguageData (SaLanguage.Data data, SaLanguage.ErrField errField) + { + ClearErrorState(); + // Language properties. + if (errField.Code != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Code, "code", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.Language_Code), true)) + { + errField.Code = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Name != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Name, "name", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.Language_Name), true)) + { + errField.Name = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Unicode != SacsHierarchyItem.ValidValue.Ignore && data.Unicode) + { + errField.Unicode = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Translation != SacsHierarchyItem.ValidValue.Ignore && data.Translation) + { + errField.Translation = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + // Validate associated data. + if (errField.LanguageIdPrimary != SacsHierarchyItem.ValidValue.Ignore && SysId.IsValid(data.LanguageIdPrimary)) + { + if (!LanguageExistsById(data.LanguageIdPrimary)) + { + errField.LanguageIdPrimary = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Language's parent not found"); + } + } + return true; + + } + + //------------------------------------------------------------------- + // Language database interface. + public abstract bool InsertLanguage (SaLanguage language, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateLanguage (SaLanguage language, DbUpdateInfo dbUpdateInfo); + public virtual bool LanguageCanBeDeleted (SaLanguage language) + { + ClearErrorState(); + if (language == null) return SetErrorState("Item not specified"); + if (language.HasDependentLanguages()) return SetErrorState("Has dependent languages"); + return true; + } + public abstract bool DeleteLanguage (SaLanguage language, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // Language list. + public abstract ColumnDfn GetLanguageColumnDfn (SaLanguage.Column columnId, String caption = "", int width = 0); + public abstract List GetLanguageQueryFilter (SaLanguage.Params prm); + public abstract long GetLanguageCount (SaLanguage.Params prm); + public abstract List GetLanguageList (SaLanguage.Params prm); + public abstract DbResultSet ExecuteLanguageQuery (List columns, SaLanguage.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 GetLanguageDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + switch ((SaLanguage.Column)column.Id) + { + //----------------------------------------------------------- + // Default. + default: + break; + + //----------------------------------------------------------- + } + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_Region.cs b/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_Region.cs new file mode 100644 index 0000000..ed47684 --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_Region.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + public abstract partial class SaReferenceDataMgr : SacsItemMgr + { + //******************************************************************* + // R e g i o n s + //******************************************************************* + // Utilities. + /// Return the valid parent type for the specified Region type. + public static SaRegionType ParentTypeFor (SaRegionType regionType) + { + if (regionType == SaRegionType.Country) return SaRegionType.Continent; + if (regionType == SaRegionType.Province) return SaRegionType.Country; + if (regionType == SaRegionType.Town) return SaRegionType.Province; + return SaRegionType.Unknown; + } + /// Return the valid subordinate Region type for the specified Region type. + public static SaRegionType SubordinateTypeFor (SaRegionType regionType) + { + if (regionType == SaRegionType.Continent) return SaRegionType.Country; + if (regionType == SaRegionType.Country) return SaRegionType.Province; + if (regionType == SaRegionType.Province) return SaRegionType.Town; + return SaRegionType.Unknown; + } + + //------------------------------------------------------------------- + // Retrieve individual regions. + public abstract bool RegionExistsById (SysId id); + public abstract bool RegionExistsByType (String type); + public abstract SaRegion GetRegionById (SysId id); + public abstract SaRegion GetRegionByType (String type); + public abstract SaRegion GetRegion (SaRegion.Params prm); + + // Create region. + public abstract SaRegion CreateRegion (SaRegionType regionType); + + //------------------------------------------------------------------- + // Validate region data. + public bool IsValidRegion (SaRegion region) + { + ClearErrorState(); + if (region == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidRegionData(region.GetData(), new SaRegion.ErrField()); + } + public virtual bool IsValidRegionData (SaRegion.Data data, SaRegion.ErrField errField) + { + ClearErrorState(); + // Region properties. + if (errField.RegionType != SacsHierarchyItem.ValidValue.Ignore) + { + if ((int)data.RegionType < 1) + { + errField.RegionType = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Region type must be supplied"); + } + } + if (errField.Name != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Name, "name", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.Region_Name), true)) + { + errField.Name = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + // Common properties. + if (errField.Notes != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Notes, "notes", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.Region_Notes))) + { + errField.Notes = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Status != SacsHierarchyItem.ValidValue.Ignore && !SaRegion.ValidStatuses.Contains(data.Status)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState(ErrorMessage.InvalidStatus); + } + // Validate associated data. + if (errField.RegionIdParent != SacsHierarchyItem.ValidValue.Ignore && SysId.IsValid(data.RegionIdParent)) + { + if (!RegionExistsById(data.RegionIdParent)) + { + errField.RegionIdParent = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Region's parent not found"); + } + } + return true; + } + + //------------------------------------------------------------------- + // Region database interface. + public abstract bool InsertRegion (SaRegion region, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateRegion (SaRegion region, DbUpdateInfo dbUpdateInfo); + public virtual bool RegionCanBeDeleted (SaRegion region) + { + ClearErrorState(); + if (region == null) return SetErrorState("Item not specified"); + if (region.HasDependentRegions()) return SetErrorState("Has dependent regions"); + if (region.HasDependentCurrencies()) return SetErrorState("Has dependent currencies"); + if (region.HasDependentTaxCodes()) return SetErrorState("Has dependent tax codes"); + return true; + } + public abstract bool DeleteRegion (SaRegion region, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // Region list. + public abstract ColumnDfn GetRegionColumnDfn (SaRegion.Column columnId, String caption = "", int width = 0); + public abstract List GetRegionQueryFilter (SaRegion.Params prm); + public abstract long GetRegionCount (SaRegion.Params prm); + public abstract List GetRegionList (SaRegion.Params prm); + public abstract DbResultSet ExecuteRegionQuery (List columns, SaRegion.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 GetRegionDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + switch ((SaRegion.Column)column.Id) + { + + //----------------------------------------------------------- + // Notes. + case SaRegion.Column.Notes: + value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200)); + break; + + //----------------------------------------------------------- + // Status. + case SaRegion.Column.Status_Desc: + case SaRegion.Column.Status_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaRegion.Column.Status_Desc) + value = new DataValue(SacsSystem.GetStatusDescForCode(statusCode)); + else if (column.Id == (int)SaRegion.Column.Status_Ind) + value = new DataValue(SaRegion.GetStatusTriState(status)); + } + break; + + //----------------------------------------------------------- + } + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_TaxCode.cs b/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_TaxCode.cs new file mode 100644 index 0000000..3334b65 --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_TaxCode.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + public abstract partial class SaReferenceDataMgr : SacsItemMgr + { + //******************************************************************* + // T a x C o d e s + //******************************************************************* + // Utilities. + public String GetTaxCode (SaTaxCodeType taxCode) + { + switch (taxCode) + { + case SaTaxCodeType.Zero: return "T0"; + case SaTaxCodeType.Std: return "T1"; + case SaTaxCodeType.Exempt: return "T3"; + } + return ""; + } + //------------------------------------------------------------------- + // Retrieve individual taxCodes. + public abstract bool TaxCodeExistsById (SysId id); + public abstract bool TaxCodeExistsByCode (String code); + public abstract SaTaxCode GetTaxCodeById (SysId id); + public abstract SaTaxCode GetTaxCodeByCode (String code); + public abstract SaTaxCode GetTaxCode (SaTaxCode.Params prm); + + // Create taxCode. + public abstract SaTaxCode CreateTaxCode (); + + //------------------------------------------------------------------- + // Validate taxCode data. + public bool IsValidTaxCode (SaTaxCode taxCode) + { + ClearErrorState(); + if (taxCode == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidTaxCodeData(taxCode.GetData(), new SaTaxCode.ErrField()); + } + public virtual bool IsValidTaxCodeData (SaTaxCode.Data data, SaTaxCode.ErrField errField) + { + ClearErrorState(); + // TaxCode properties. + if (errField.Code != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Code, "code", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.TaxCode_Code), true)) + { + errField.Code = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.CodeAlt1 != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.CodeAlt1, "codeAlt1", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.TaxCode_CodeAlt1))) + { + errField.CodeAlt1 = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.CodeAlt2 != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.CodeAlt2, "codeAlt2", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.TaxCode_CodeAlt2))) + { + errField.CodeAlt2 = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Description != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Description, "description", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.TaxCode_Description), true)) + { + errField.Description = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.EffectFrom != SacsHierarchyItem.ValidValue.Ignore && errField.EffectTo != SacsHierarchyItem.ValidValue.Ignore && DateUtil.IsValid(data.EffectFrom) && DateUtil.IsValid(data.EffectTo)) + { + if (data.EffectFrom.CompareTo(data.EffectTo) > 0) + { + errField.EffectFrom = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Invalid effective date range"); + } + } + if (errField.TaxRate != SacsHierarchyItem.ValidValue.Ignore && (short)data.TaxRate < 1) + { + errField.TaxRate = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Tax Rate must be supplied"); + } + if (errField.TaxRatePrev != SacsHierarchyItem.ValidValue.Ignore && (short)data.TaxRatePrev < 1) + { + errField.TaxRatePrev = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("previous tax rate must be supplied"); + } + // Common properties. + if (errField.Notes != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Notes, "notes", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.TaxCode_Notes))) + { + errField.Notes = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Status != SacsHierarchyItem.ValidValue.Ignore && !SaTaxCode.ValidStatuses.Contains(data.Status)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState(ErrorMessage.InvalidStatus); + } + // Validate associated data. + if (errField.RegionId != SacsHierarchyItem.ValidValue.Ignore && SysId.IsValid(data.RegionId)) + { + if (!RegionExistsById(data.RegionId)) + { + errField.RegionId = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Tax code's region not found"); + } + } + if (errField.TaxCodeIdLink != SacsHierarchyItem.ValidValue.Ignore && SysId.IsValid(data.TaxCodeIdLink)) + { + if (!TaxCodeExistsById(data.TaxCodeIdLink)) + { + errField.TaxCodeIdLink = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Tax code's parent not found"); + } + } + return true; + } + + //------------------------------------------------------------------- + // TaxCode database interface. + public abstract bool InsertTaxCode (SaTaxCode taxCode, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateTaxCode (SaTaxCode taxCode, DbUpdateInfo dbUpdateInfo); + public virtual bool TaxCodeCanBeDeleted (SaTaxCode taxCode) + { + ClearErrorState(); + if (taxCode.HasDependentTaxCodes()) return SetErrorState("Has dependent tax codes"); + return true; + } + public abstract bool DeleteTaxCode (SaTaxCode taxCode, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // TaxCode list. + public abstract ColumnDfn GetTaxCodeColumnDfn (SaTaxCode.Column columnId, String caption = "", int width = 0); + public abstract List GetTaxCodeQueryFilter (SaTaxCode.Params prm); + public abstract long GetTaxCodeCount (SaTaxCode.Params prm); + public abstract List GetTaxCodeList (SaTaxCode.Params prm); + public abstract DbResultSet ExecuteTaxCodeQuery (List columns, SaTaxCode.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 GetTaxCodeDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + switch ((SaTaxCode.Column)column.Id) + { + + //----------------------------------------------------------- + // Notes. + case SaTaxCode.Column.Notes: + value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200)); + break; + + //----------------------------------------------------------- + // Status. + case SaTaxCode.Column.Status_Desc: + case SaTaxCode.Column.Status_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaTaxCode.Column.Status_Desc) + value = new DataValue(SacsSystem.GetStatusDescForCode(statusCode)); + else if (column.Id == (int)SaTaxCode.Column.Status_Ind) + value = new DataValue(SaTaxCode.GetStatusTriState(status)); + } + break; + + //----------------------------------------------------------- + } + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_UnitMeasure.cs b/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_UnitMeasure.cs new file mode 100644 index 0000000..fec8ba8 --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaReferenceDataMgr_UnitMeasure.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + public abstract partial class SaReferenceDataMgr : SacsItemMgr + { + //******************************************************************* + // U n i t s o f M e a s u r e + //******************************************************************* + // Utilities. + + //------------------------------------------------------------------- + // Retrieve individual unitMeasures. + public abstract bool UnitMeasureExistsById (SysId id); + public abstract bool UnitMeasureExistsByCode (String code); + public abstract SaUnitMeasure GetUnitMeasureById (SysId id); + public abstract SaUnitMeasure GetUnitMeasureByCode (String code); + public abstract SaUnitMeasure GetUnitMeasure (SaUnitMeasure.Params prm); + + // Create unitMeasure. + public abstract SaUnitMeasure CreateUnitMeasure (); + + //------------------------------------------------------------------- + // Validate unitMeasure data. + public bool IsValidUnitMeasure (SaUnitMeasure unitMeasure) + { + ClearErrorState(); + if (unitMeasure == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidUnitMeasureData(unitMeasure.GetData(), new SaUnitMeasure.ErrField()); + } + public virtual bool IsValidUnitMeasureData (SaUnitMeasure.Data data, SaUnitMeasure.ErrField errField) + { + ClearErrorState(); + // UnitMeasure properties. + if (errField.Category != SacsHierarchyItem.ValidValue.Ignore && (short)data.Category < 1) + { + errField.Category = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.UnitType != SacsHierarchyItem.ValidValue.Ignore && (short)data.UnitType < 1) + { + if ((int)data.UnitType < 1) + { + errField.UnitType = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("UnitMeasure type must be supplied"); + } + } + if (errField.Code != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Code, "code", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.UnitMeasure_Code), true)) + { + errField.Code = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Abbr != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Abbr, "abbr", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.UnitMeasure_Abbr), true)) + { + errField.Abbr = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Single != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Single, "single", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.UnitMeasure_Single), true)) + { + errField.Single = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Multiple != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Multiple, "multiple", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.UnitMeasure_Multiple), true)) + { + errField.Multiple = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.EquivalencyValue != SacsHierarchyItem.ValidValue.Ignore && data.EquivalencyValue < 1) + { + errField.EquivalencyValue = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("UnitMeasure equal value must be supplied"); + } + if (errField.UomIdEquivalency != SacsHierarchyItem.ValidValue.Ignore && data.UomIdEquivalency < 1) + { + errField.UomIdEquivalency = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("UnitMeasure Id equal must be supplied"); + } + if (errField.EquivalencyAlgorythm != SacsHierarchyItem.ValidValue.Ignore && data.EquivalencyAlgorythm < 1) + { + errField.EquivalencyAlgorythm = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("UnitMeasure equal algorythm must be supplied"); + } + if (errField.UomIdRef1 != SacsHierarchyItem.ValidValue.Ignore && data.UomIdRef1 < 1) + { + errField.UomIdRef1 = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("UnitMeasure reference1 must be supplied"); + } + if (errField.UomIdRef2 != SacsHierarchyItem.ValidValue.Ignore && data.UomIdRef2 < 1) + { + errField.UomIdRef2 = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("UnitMeasure reference2 must be supplied"); + } + if (errField.UomIdRef3 != SacsHierarchyItem.ValidValue.Ignore && data.UomIdRef3 < 1) + { + errField.UomIdRef3 = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("UnitMeasure reference1 must be supplied"); + } + // Common properties. + if (errField.Notes != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Notes, "notes", (long)SaReferenceDataDbDfn.GetColTextLen(SaReferenceDataDbDfn.ColumnIdx.UnitMeasure_Notes))) + { + errField.Notes = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Status != SacsHierarchyItem.ValidValue.Ignore && !SaUnitMeasure.ValidStatuses.Contains(data.Status)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState(ErrorMessage.InvalidStatus); + } + // Validate associated data. + /* + if (errField. != SacsHierarchyItem.ValidValue.Ignore && SysId.IsValid(data.UnitMeasureIdParent)) + { + if (!UnitMeasureExistsById(data.UnitMeasureIdParent)) + { + errField.UnitMeasureIdParent = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("UnitMeasure's parent not found"); + } + } + */ + return true; + } + + //------------------------------------------------------------------- + // UnitMeasure database interface. + public abstract bool InsertUnitMeasure (SaUnitMeasure unitMeasure, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateUnitMeasure (SaUnitMeasure unitMeasure, DbUpdateInfo dbUpdateInfo); + public virtual bool UnitMeasureCanBeDeleted (SaUnitMeasure unitMeasure) + { + ClearErrorState(); + if (unitMeasure.HasDependentUnitsOfMeasure()) return SetErrorState("Has dependent UOMs"); + return true; + } + public abstract bool DeleteUnitMeasure (SaUnitMeasure unitMeasure, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // UnitMeasure list. + public abstract ColumnDfn GetUnitMeasureColumnDfn (SaUnitMeasure.Column columnId, String caption = "", int width = 0); + public abstract List GetUnitMeasureQueryFilter (SaUnitMeasure.Params prm); + public abstract long GetUnitMeasureCount (SaUnitMeasure.Params prm); + public abstract List GetUnitMeasureList (SaUnitMeasure.Params prm); + public abstract DbResultSet ExecuteUnitMeasureQuery (List columns, SaUnitMeasure.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 GetUnitMeasureDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + switch ((SaUnitMeasure.Column)column.Id) + { + + //----------------------------------------------------------- + // Notes. + case SaUnitMeasure.Column.Notes: + value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200)); + break; + + //----------------------------------------------------------- + // Status. + case SaUnitMeasure.Column.Status_Desc: + case SaUnitMeasure.Column.Status_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaUnitMeasure.Column.Status_Desc) + value = new DataValue(SacsSystem.GetStatusDescForCode(statusCode)); + else if (column.Id == (int)SaUnitMeasure.Column.Status_Ind) + value = new DataValue(SaUnitMeasure.GetStatusTriState(status)); + } + break; + + //----------------------------------------------------------- + } + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaRegion.cs b/SacsStandardBase/Base/ReferenceData/SaRegion.cs new file mode 100644 index 0000000..e93b81a --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaRegion.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + public abstract class SaRegion : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Hidden, + Pos, + Id, + // Region. + Type, + Name, + Parent, + // Common properties. + Notes, + Status, + Status_Desc, + Status_Ind, + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public SaRegionType RegionType { get; set; } = SaRegionType.Unknown; + public String Name { get; set; } = ""; + public SysId RegionIdParent { get; set; } = new SysId(); + } + + //------------------------------------------------------------------- + // Data. + public new class Data : SacsHierarchyItem.Data + { + // Data members. + private readonly DataProperties data = new DataProperties(); + // Construction. + public Data () + { + Status = DefaultStatus; + } + // Properties. + public SaRegionType RegionType + { + get { return data.RegionType; } + set { CheckChanged((int)data.RegionType, (int)value); data.RegionType = value; } + } + public String Name + { + get { return data.Name; } + set { CheckChanged(data.Name, value); data.Name = value; } + } + public SysId RegionIdParent + { + get { return data.RegionIdParent; } + set { CheckChanged(data.RegionIdParent, value); data.RegionIdParent = value; } + } + internal void PopulateFrom(Data data) + { + RegionType = data.RegionType; + Name = data.Name; + RegionIdParent = data.RegionIdParent; + base.PopulateFrom(data); + } + public Data Clone() + { + Data data = new Data(); + data.PopulateFrom(this); + return data; + } + } + + //------------------------------------------------------------------- + public new class ErrField : SacsHierarchyItem.ErrField + { + // Identifies the field that has invalid data. + public ValidValue RegionType { get; set; } = ValidValue.Valid; + public ValidValue Name { get; set; } = ValidValue.Valid; + public ValidValue RegionIdParent { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + + //------------------------------------------------------------------- + // Construction. + protected SaRegion (SysId id, SaRegionType regionType) : base(id) + { + RegionType = regionType; + Status = DefaultStatus; + base.SetData(data); + } + + //------------------------------------------------------------------- + // Statuses. + private static readonly List validStatuses = new List() + { + SaStatus.Current + }; + public static SaStatus DefaultStatus { 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 DataValue.TriStateType.Green; + case SaStatus.Redundant: + return DataValue.TriStateType.Red; + case SaStatus.Deleted: + return DataValue.TriStateType.Purple; + } + return DataValue.TriStateType.Unknown; + } + + //------------------------------------------------------------------- + // Utilities. + + //------------------------------------------------------------------- + // Data. + 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"); + this.data.PopulateFrom(data); + } + + //------------------------------------------------------------------- + // Properties. + public SaRegionType RegionType + { + get { return data.RegionType; } + protected set { data.RegionType = value; } + } + public String Name + { + get { return data.Name; } + set { data.Name = value; } + } + public SysId RegionIdParent + { + get { return data.RegionIdParent; } + set { data.RegionIdParent = value; } + } + + //------------------------------------------------------------------- + // A s s o c i a t e d D a t a E l e m e n t s + //------------------------------------------------------------------- + public abstract bool HasDependentRegions (); + public abstract bool HasDependentCurrencies (); + public abstract bool HasDependentTaxCodes (); + + //------------------------------------------------------------------- + // P a r a m s + //------------------------------------------------------------------- + // List parameters. + public enum StatusCat + { + Any, + ActiveOnly, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + // List filtering. + public List IncludedIds { get; } = new List(); + public List ExcludedIds { get; } = new List(); + // Region. + public SaRegionType RegionType { get; set; } = SaRegionType.Unknown; + public String Name { get; set; } + public String NamePartial { get; set; } + public SysId RegionIdParent { get; set; } + public StatusCat StatusCat { get; set; } = StatusCat.Any; + } + + //------------------------------------------------------------------- + } + + //======================================================================= + // R e g i o n D a t a L i s t + //======================================================================= + public static class SaRegionList + { + /// Retrieve region with specified Id. + public static SaRegion FindById (this List values, SysId measureId) + { + return values.Find(item => item.Id.Equals(measureId)); + } + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaTaxCode.cs b/SacsStandardBase/Base/ReferenceData/SaTaxCode.cs new file mode 100644 index 0000000..6ce4aac --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaTaxCode.cs @@ -0,0 +1,330 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.ReferenceData +{ + //======================================================================= + public abstract class SaTaxCode : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Hidden, + Pos, + Id, + // TaxCode. + Code, + CodeAlt1, + CodeAlt2, + Description, + RegionId, + EffectFrom, + EffectTo, + TaxRate, + TaxRatePrev, + TaxCodeIdLink, + // Common properties. + Notes, + Status, + Status_Desc, + Status_Ind, + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public String Code { get; set; } = ""; + public String CodeAlt1 { get; set; } = ""; + public String CodeAlt2 { get; set; } = ""; + public String Description { get; set; } = ""; + public SysId RegionId { get; set; } = new SysId(); + public DateTime EffectFrom { get; set; } = DateTime.MinValue; + public DateTime EffectTo { get; set; } = DateTime.MinValue; + public decimal TaxRate { get; set; } = 0.0m; + public decimal TaxRatePrev { get; set; } = 0.0m; + public SysId TaxCodeIdLink { get; set; } = new SysId(); + } + + //------------------------------------------------------------------- + // 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 CodeAlt1 + { + get { return data.CodeAlt1; } + set { CheckChanged(data.CodeAlt1, value); data.CodeAlt1 = value; } + } + public String CodeAlt2 + { + get { return data.CodeAlt2; } + set { CheckChanged(data.CodeAlt2, value); data.CodeAlt2 = value; } + } + public String Description + { + get { return data.Description; } + set { CheckChanged(data.Description, value); data.Description = value; } + } + public SysId RegionId + { + get { return data.RegionId; } + set { CheckChanged(data.RegionId, value); data.RegionId = value; } + } + public DateTime EffectFrom + { + get { return data.EffectFrom; } + set { CheckChanged(data.EffectFrom, value); data.EffectFrom = value; } + } + public DateTime EffectTo + { + get { return data.EffectTo; } + set { CheckChanged(data.EffectTo, value); data.EffectTo = value; } + } + public decimal TaxRate + { + get { return data.TaxRate; } + set { CheckChanged(data.TaxRate, value); data.TaxRate = value; } + } + public decimal TaxRatePrev + { + get { return data.TaxRatePrev; } + set { CheckChanged(data.TaxRatePrev, value); data.TaxRatePrev = value; } + } + public SysId TaxCodeIdLink + { + get { return data.TaxCodeIdLink; } + set { CheckChanged(data.TaxCodeIdLink, value); data.TaxCodeIdLink = value; } + } + internal void PopulateFrom (Data data) + { + Code = data.Code; + CodeAlt1 = data.CodeAlt1; + CodeAlt2 = data.CodeAlt2; + Description = data.Description; + RegionId = data.RegionId; + EffectFrom = data.EffectFrom; + EffectTo = data.EffectTo; + TaxRate = data.TaxRate; + TaxRatePrev = data.TaxRatePrev; + TaxCodeIdLink = data.TaxCodeIdLink; + base.PopulateFrom(data); + } + public Data Clone () + { + Data data = new Data(); + data.PopulateFrom(this); + return data; + } + } + + //------------------------------------------------------------------- + public new class ErrField : SacsHierarchyItem.ErrField + { + // Identifies the field that has invalid data. + public ValidValue Code { get; set; } = ValidValue.Valid; + public ValidValue CodeAlt1 { get; set; } = ValidValue.Valid; + public ValidValue CodeAlt2 { get; set; } = ValidValue.Valid; + public ValidValue Description { get; set; } = ValidValue.Valid; + public ValidValue RegionId { get; set; } = ValidValue.Valid; + public ValidValue EffectFrom { get; set; } = ValidValue.Valid; + public ValidValue EffectTo { get; set; } = ValidValue.Valid; + public ValidValue TaxRate { get; set; } = ValidValue.Valid; + public ValidValue TaxRatePrev { get; set; } = ValidValue.Valid; + public ValidValue TaxCodeIdLink { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + + //------------------------------------------------------------------- + // Construction. + protected SaTaxCode (SysId id) : base(id) + { + base.SetData(data); + } + + //------------------------------------------------------------------- + // Statuses. + private static readonly List validStatuses = new List() + { + SaStatus.Current + }; + public static SaStatus DefaultStatus { 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 DataValue.TriStateType.Green; + case SaStatus.Deleted: + return DataValue.TriStateType.Purple; + } + return DataValue.TriStateType.Unknown; + } + + //------------------------------------------------------------------- + // Utilities. - this is for the EffectiveOn Date 'From' & 'To' + public bool IsEffectiveOn (DateTime effectiveDate) + { + if (DateUtil.IsValid(EffectFrom) && EffectFrom.CompareTo(effectiveDate) > 0) return false; + if (DateUtil.IsValid(EffectTo) && EffectTo.CompareTo(effectiveDate) < 0) return false; + return true; + } + + //------------------------------------------------------------------- + // Data. + 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.EqualsCI(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); + } + + //------------------------------------------------------------------- + // Properties. + public String Code + { + get { return data.Code; } + set { data.Code = value; } + } + public String CodeAlt1 + { + get { return data.CodeAlt1; } + set { data.CodeAlt1 = value; } + } + public String CodeAlt2 + { + get { return data.CodeAlt2; } + set { data.CodeAlt2 = value; } + } + public String Description + { + get { return data.Description; } + set { data.Description = value; } + } + public SysId RegionId + { + get { return data.RegionId; } + set { data.RegionId = value; } + } + public DateTime EffectFrom + { + get { return data.EffectFrom; } + set { data.EffectFrom = value; } + } + public DateTime EffectTo + { + get { return data.EffectTo; } + set { data.EffectTo = value; } + } + public decimal TaxRate + { + get { return data.TaxRate; } + set { data.TaxRate = value; } + } + public decimal TaxRatePrev + { + get { return data.TaxRatePrev; } + set { data.TaxRatePrev = value; } + } + public SysId TaxCodeIdLink + { + get { return data.TaxCodeIdLink; } + set { data.TaxCodeIdLink = value; } + } + + //------------------------------------------------------------------- + // A s s o c i a t e d D a t a E l e m e n t s + //------------------------------------------------------------------- + public abstract bool HasDependentTaxCodes (); + + //------------------------------------------------------------------- + // P a r a m s + //------------------------------------------------------------------- + // List parameters. + public enum StatusCat + { + Any, + ActiveOnly, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + // List filtering. + public List IncludedIds { get; } = new List(); + public List ExcludedIds { get; } = new List(); + // Tax Code properties. + public String Code { get; set; } + public String CodePartial { get; set; } + public String CodeAlt1 { get; set; } + public String CodeAlt2 { get; set; } + public SysId RegionId { get; set; } + public DateTime EffectOn { get; set; } = DateTime.MinValue; + public String NotesPartial { get; set; } + public StatusCat StatusCat { get; set; } = StatusCat.Any; + } + + //------------------------------------------------------------------- + } + + //======================================================================= + // T a x C o d e D a t a L i s t + //======================================================================= + public static class SaTaxCodeList + { + /// Check whether taxcode exists/locate in list with specified Id. + public static bool ExistsById (this List values, SysId taxId) + { + if (!SysId.IsValid(taxId)) return false; + return values.Exists(item => item.Id.Equals(taxId)); + } + /// Retrieve tax code with specified Id. + public static SaTaxCode FindById (this List values, SysId taxId) + { + if (!SysId.IsValid(taxId)) return null; + return values.Find(item => item.Id.Equals(taxId)); + } + public static SaTaxCode FindByCode (this List values, String taxCode) + { + if (StringUtil.IsEmpty(taxCode)) return null; + return values.Find(item => item.Code.Equals(taxCode)); + } + public static SaTaxCode FindByCodeEffectiveOn (this List values, String taxCode, DateTime effectiveOn) + { + if (StringUtil.IsEmpty(taxCode)) return null; + if (!DateUtil.IsValid(effectiveOn)) return null; + return values.Find(item => item.Code.Equals(taxCode) && item.IsEffectiveOn(effectiveOn)); + } + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/ReferenceData/SaUnitMeasure.cs b/SacsStandardBase/Base/ReferenceData/SaUnitMeasure.cs new file mode 100644 index 0000000..b41e731 --- /dev/null +++ b/SacsStandardBase/Base/ReferenceData/SaUnitMeasure.cs @@ -0,0 +1,352 @@ +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.ReferenceData +{ + //======================================================================= + public abstract class SaUnitMeasure : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Hidden, + Pos, + Id, + // Unit of Measure. + Category, + UnitType, + Code, + Abbr, + Single, + Multiple, + EquivalencyValue, + UomIdEquivalency, + EquivalencyAlgorythm, + UomIdRef1, + UomIdRef2, + UomIdRef3, + // Common properties. + Notes, + Status, + Status_Desc, + Status_Ind, + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public SaUomCategory Category { get; set; } = SaUomCategory.Unknown; + public SaUomType UnitType { get; set; } = SaUomType.Unknown; + public String Code { get; set; } = ""; + public String Abbr { get; set; } = ""; + public String Single { get; set; } = ""; + public String Multiple { get; set; } = ""; + public decimal EquivalencyValue { get; set; } = 0.0m; + public int UomIdEquivalency { get; set; } = 0; + public int EquivalencyAlgorythm { get; set; } = 0; + public int UomIdRef1 { get; set; } = 0; + public int UomIdRef2 { get; set; } = 0; + public int UomIdRef3 { get; set; } = 0; + } + + //------------------------------------------------------------------- + // Data. + public new class Data : SacsHierarchyItem.Data + { + // Data members. + private readonly DataProperties data = new DataProperties(); + // Construction. + public Data () + { + Status = DefaultStatus; + } + // Properties. + public SaUomCategory Category + { + get { return data.Category; } + set { CheckChanged((short)data.Category, (short)value); data.Category = value; } + } + public SaUomType UnitType + { + get { return data.UnitType; } + set { CheckChanged((short)data.UnitType, (short)value); data.UnitType = value; } + } + public String Code + { + get { return data.Code; } + set { CheckChanged(data.Code, value); data.Code = value; } + } + public String Abbr + { + get { return data.Abbr; } + set { CheckChanged(data.Abbr, value); data.Abbr = value; } + } + public String Single + { + get { return data.Single; } + set { CheckChanged(data.Single, value); data.Single = value; } + } + public String Multiple + { + get { return data.Multiple; } + set { CheckChanged(data.Multiple, value); data.Multiple = value; } + } + public decimal EquivalencyValue + { + get { return data.EquivalencyValue; } + set { CheckChanged(data.EquivalencyValue, value); data.EquivalencyValue = value; } + } + public int UomIdEquivalency + { + get { return data.UomIdEquivalency; } + set { CheckChanged(data.UomIdEquivalency, value); data.UomIdEquivalency = value; } + } + public int EquivalencyAlgorythm + { + get { return data.EquivalencyAlgorythm; } + set { CheckChanged(data.EquivalencyAlgorythm, value); data.EquivalencyAlgorythm = value; } + } + public int UomIdRef1 + { + get { return data.UomIdRef1; } + set { CheckChanged(data.UomIdRef1, value); data.UomIdRef1 = value; } + } + public int UomIdRef2 + { + get { return data.UomIdRef2; } + set { CheckChanged(data.UomIdRef2, value); data.UomIdRef2 = value; } + } + public int UomIdRef3 + { + get { return data.UomIdRef3; } + set { CheckChanged(data.UomIdRef3, value); data.UomIdRef3 = value; } + } + // Population. + internal void PopulateFrom (Data data) + { + Category = data.Category; + UnitType = data.UnitType; + Code = data.Code; + Abbr = data.Abbr; + Single = data.Single; + Multiple = data.Multiple; + EquivalencyValue = data.EquivalencyValue; + UomIdEquivalency = data.UomIdEquivalency; + EquivalencyAlgorythm = data.EquivalencyAlgorythm; + UomIdRef1 = data.UomIdRef1; + UomIdRef2 = data.UomIdRef2; + UomIdRef3 = data.UomIdRef3; + 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 Category { get; set; } = ValidValue.Valid; + public ValidValue UnitType { get; set; } = ValidValue.Valid; + public ValidValue Code { get; set; } = ValidValue.Valid; + public ValidValue Abbr { get; set; } = ValidValue.Valid; + public ValidValue Single { get; set; } = ValidValue.Valid; + public ValidValue Multiple { get; set; } = ValidValue.Valid; + public ValidValue EquivalencyValue { get; set; } = ValidValue.Valid; + public ValidValue UomIdEquivalency { get; set; } = ValidValue.Valid; + public ValidValue EquivalencyAlgorythm { get; set; } = ValidValue.Valid; + public ValidValue UomIdRef1 { get; set; } = ValidValue.Valid; + public ValidValue UomIdRef2 { get; set; } = ValidValue.Valid; + public ValidValue UomIdRef3 { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + public static readonly Byte Flag_EquivalencyAccurate = 0x01; + public static readonly Byte Flag_CategoryDefault = 0x02; + + //------------------------------------------------------------------- + // Construction. + protected SaUnitMeasure (SysId id) : base(id) + { + base.SetData(data); + Status = DefaultStatus; + } + + //------------------------------------------------------------------- + // Statuses. + private static readonly List validStatuses = new List() + { + SaStatus.Current + }; + public static SaStatus DefaultStatus { 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 DataValue.TriStateType.Green; + case SaStatus.Deleted: + return DataValue.TriStateType.Purple; + } + return DataValue.TriStateType.Unknown; + } + + //------------------------------------------------------------------- + // Utilities. + + //------------------------------------------------------------------- + // Data. + 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.EqualsCI(data.Code)) + throw new NotSupportedException(fn + " Codecannot be changed"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + + //------------------------------------------------------------------- + // Properties. + public SaUomCategory Category + { + get { return data.Category; } + set { data.Category = value; } + } + public SaUomType UnitType + { + get { return data.UnitType; } + set { data.UnitType = value; } + } + public String Code + { + get { return data.Code; } + set { data.Code = value; } + } + public String Abbr + { + get { return data.Abbr; } + set { data.Abbr = value; } + } + public String Single + { + get { return data.Single; } + set { data.Single = value; } + } + public String Multiple + { + get { return data.Multiple; } + set { data.Multiple = value; } + } + public decimal EquivalencyValue + { + get { return data.EquivalencyValue; } + set { data.EquivalencyValue = value; } + } + public int UomIdEquivalency + { + get { return data.UomIdEquivalency; } + set { data.UomIdEquivalency = value; } + } + public int EquivalencyAlgorythm + { + get { return data.EquivalencyAlgorythm; } + set { data.EquivalencyAlgorythm = value; } + } + public int UomIdRef1 + { + get { return data.UomIdRef1; } + set { data.UomIdRef1 = value; } + } + public int UomIdRef2 + { + get { return data.UomIdRef2; } + set { data.UomIdRef2 = value; } + } + public int UomIdRef3 + { + get { return data.UomIdRef3; } + set { data.UomIdRef3 = value; } + } + + //------------------------------------------------------------------- + // Flags. + public bool EquivalencyAccurate + { + get { return GetFlag(Flag_EquivalencyAccurate); } + set { SetFlag(Flag_EquivalencyAccurate, value); } + } + public bool CategoryDefault + { + get { return GetFlag(Flag_CategoryDefault); } + set { SetFlag(Flag_CategoryDefault, value); } + } + + //------------------------------------------------------------------- + // A s s o c i a t e d D a t a E l e m e n t s + //------------------------------------------------------------------- + public abstract bool HasDependentUnitsOfMeasure (); + + //------------------------------------------------------------------- + // P a r a m s + //------------------------------------------------------------------- + // List parameters. + public enum StatusCat + { + Any, + ActiveOnly, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + // List filtering. + public List IncludedIds { get; } = new List(); + public List ExcludedIds { get; } = new List(); + // UnitMeasure. + public SaUomCategory Category { get; set; } + public SaUomType UnitType { get; set; } + public String Code { get; set; } + public String CodePartial { get; set; } + public String Desc { get; set; } + public String DescPartial { get; set; } + public String NotesPartial { get; set; } + public StatusCat StatusCat { get; set; } = StatusCat.Any; + } + + //------------------------------------------------------------------- + } + + //======================================================================= + // U n i t M e a s u r e D a t a L i s t + //======================================================================= + public static class SaUnitMeasureList + { + /// Retrieve unit measure with specified Id. + public static SaUnitMeasure FindById (this List values, SysId measureId) + { + return values.Find(item => item.Id.Equals(measureId)); + } + } + + //======================================================================= +} \ No newline at end of file diff --git a/SacsStandardBase/Base/Security/SaSecurityConsole.cs b/SacsStandardBase/Base/Security/SaSecurityConsole.cs new file mode 100644 index 0000000..94cf0e5 --- /dev/null +++ b/SacsStandardBase/Base/Security/SaSecurityConsole.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; +using Sarand.Net.Standard.Base.UpdateLog; + +namespace Sarand.Net.Standard.Base.Security +{ + //======================================================================= + // Definitions. + public class SaSecurity + { + public enum ItemType + { + Any = -1, + Unknown = 0, + // Values are tied to the database - do not change. + User = 1, + Computer = 2, + Group = 3, + Right = 4, + } + public enum FilterAttrType + { + Code, // Partial code. + Name, // Partial name. + Group_Include, // Item belongs to group. + Group_Exclude, // Item doesn't belong to group. + }; + } + // Password configuration. + public class PasswordConfig + { + /// Minimum password length. < 1 = no limit. + public int MinimumLength { get; protected set; } = 6; + /// Maximum password length. < 1 = no limit. + public int MaximumLength { get; protected set; } = 6; + } + + //======================================================================= + public abstract class SaSecurityConsole : SaErrorStateItem + { + //------------------------------------------------------------------- + // Definitions. + protected class DataItemDfn + { + public DataItemDfn (short dataItemType, String caption) + { + DataItemType = dataItemType; + Caption = caption; + } + public short DataItemType { get; set; } = -1; + public String Caption { get; set; } = String.Empty; + public List ItemsAssign { get; } = new List(); + public List ItemsFull { get; } = new List(); + } + + //------------------------------------------------------------------- + // Data members. + protected static String GroupSuperCode = "SUPER"; + protected static String AdminUserCode = "admin"; + private static List securityItemTypes = new List(); + + //------------------------------------------------------------------- + // Static initialisation + static SaSecurityConsole () + { + // Security item types. + securityItemTypes.Add(SaSecurity.ItemType.User, "USER", "User"); + securityItemTypes.Add(SaSecurity.ItemType.Computer, "COMP", "Computer"); + securityItemTypes.Add(SaSecurity.ItemType.Group, "SGRP", "Group"); + securityItemTypes.Add(SaSecurity.ItemType.Right, "SRGT", "Right"); + } + + //------------------------------------------------------------------- + // Protected construction - use derived classes. + protected SaSecurityConsole () + { + } + + //------------------------------------------------------------------- + // Properties. + public bool LogSecurityProfile { get; set; } = false; + public abstract short SecurityItemDataType { get; } + public abstract short SecurityLinkDataType { get; } + public abstract SaUpdateLogConsole UpdateLogConsole { get; } + public static List SecurityItemTypes { get { return new List(securityItemTypes); } } +#if DEBUG + // Login dialog has a 'Require debug login' option. + public bool RequireDebugLogin { get; protected set; } = true; +#endif + protected List SecurityRights { get; } = new List(); + protected Dictionary DataItemDfns { get; } = new Dictionary(); + + //------------------------------------------------------------------- + // Database properties. + public abstract bool DatabaseIsValid { get; } + public abstract bool CanUpdateDatabase { get; } + public abstract bool ColumnExists (SaSecurityDbDfn.ColumnIdx columnIdx); + + //------------------------------------------------------------------- + // U t i l i t i e s + //------------------------------------------------------------------- + // Default user Id. + private SysId userIdDefault = new SysId(); + public SysId GetUserIdDefault () + { + return userIdDefault; + } + + //------------------------------------------------------------------- + // L o g i n + //------------------------------------------------------------------- + /// User login. + public abstract bool LoginUser (SysId userId, bool recordLastLogin = true); + /// Using current windows user account. + public abstract bool LoginAuthenticatedUser (); + protected abstract bool OnLogin (SysId userId); + /// Logout - Clear user and security details. + public abstract bool LogoutUser (); + protected abstract void OnLogout (); + + //------------------------------------------------------------------- + // A p p l i c a t i o n I n t e r f a c e + //------------------------------------------------------------------- + /// Show Login form. + public abstract bool ManageSecurity (bool userCanManageSecurity, DbUpdateInfo dbUpdateInfo); + /// Show Security maintenance form. + public abstract bool ShowUserLoginForm (DbUpdateInfo dbUpdateInfo); + /// User modified event. + public abstract bool OnUserModified (SysId userId); + /// Security Group modified event. + public abstract bool OnSecurityGroupModified (SysId userId); + + //------------------------------------------------------------------- + // Password configuration. + public PasswordConfig PasswordConfig { get; protected set; } = new PasswordConfig(); + /// Show form to change password for currently logged in user. + /// Flag to indicate password changed + public abstract bool ShowChangePasswordForm (DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // A p p l i c a t i o n S e c u r i t y R i g h t s + //------------------------------------------------------------------- + // Register Security Rights. + public abstract void RegisterSecurityRights (); + //------------------------------------------------------------------- + // Security rights are optionally associated with a specified data item type (>0), + // in which case they are always associated with a specific item Id of that type. + public bool RegisterSecurityRight (String code, String desc, short dataItemType = 0) + { + SecurityRights.Add(code, desc, dataItemType); + return true; + } + // Replace previous rights. + public bool RegisterSecurityRights (List securityRights) + { + SecurityRights.Clear(); + SecurityRights.AddRange(securityRights); + return true; + } + // Determine whether any security items are present and allow them to be populated. + public abstract bool RequiresPopulation (); + public abstract bool PopulateSecurityRights (DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // A p p l i c a t i o n D a t a I t e m s + //------------------------------------------------------------------- + // Application data items - as referenced by assigned security rights (where relevant). + // Items assign: Register the list of application data items of a particular type that can be associated with security rights. + // Items full: If specified, only data items in the 'assign' list can be associated with security items and existing security + // items that reference data items in the 'full' list but not in the 'assign' list cannot be modified. + public bool RegisterDataItemType (short dataItemType, String caption, List itemsAssign = null, List itemsFull = null) + { + DataItemDfn dataItemDfn = new DataItemDfn(dataItemType,caption); + if (itemsAssign != null) + dataItemDfn.ItemsAssign.AddRange(itemsAssign); + if (itemsFull != null) + dataItemDfn.ItemsFull.AddRange(itemsFull); + DataItemDfns.Add(dataItemType,dataItemDfn); + return true; + } + // Modify list of data item associated with specified type. + public bool RegisterDataItems (short dataItemType, List itemsAssign, List itemsFull = null) + { + if (!DataItemDfns.ContainsKey(dataItemType)) return false; + DataItemDfn dataItemDfn = DataItemDfns[dataItemType]; + // Clear and repopulate assignable items. + dataItemDfn.ItemsAssign.Clear(); + if (itemsAssign != null) + dataItemDfn.ItemsAssign.AddRange(itemsAssign); + // Clear and populate full list of items. + dataItemDfn.ItemsFull.Clear(); + if (itemsFull != null) + dataItemDfn.ItemsFull.AddRange(itemsFull); + return true; + } + + //------------------------------------------------------------------- + // U s e r S e c u r i t y P r o f i l e + //------------------------------------------------------------------- + /// Retrieve security profile for host computer + public abstract SaSecurityProfile GetHostSecurityProfile (); + /// Retrieve security profile for specified user + public abstract SaSecurityProfile GetUserSecurityProfile (SysId userId); + /// Internal only - creat securitu profile. + protected SaSecurityProfile CreateSecurityProfile (SaSecurityItem secItem, List profileGroups, List profileRights, bool systemAdmin) + { + return new SaSecurityProfile(secItem, profileGroups, profileRights, systemAdmin); + } + /* + // The security profile applies right by user and then host computer (if not defined by user). + public bool IsSecurityRightGranted (String rightCode, SysId dataItemId = null) + { +#if DEBUG + if (!RequireDebugLogin) return true; +#endif + if (UserSecurityProfile.IsSecurityRightGranted(rightCode, dataItemId)) return true; + if (HostSecurityProfile.IsSecurityRightGranted(rightCode, dataItemId)) return true; + return false; + } + public bool IsSecurityRightDenied (String rightCode, SysId dataItemId = null) + { +#if DEBUG + if (!RequireDebugLogin) return false; +#endif + if (UserSecurityProfile.IsSecurityRightDenied(rightCode, dataItemId)) return true; + if (HostSecurityProfile.IsSecurityRightDenied(rightCode, dataItemId)) return true; + return false; + } + // Retrieve the list of data items for which the user is explicitly granted the + // specified security right, or where the right is explicitly denied if the flag is false. + // NB If both lists are empty, assume that user is granted the right to all data items. + public List GetDataItemsForRight (String rightCode, bool granted = true) + { + List dataItemIds = UserSecurityProfile.GetDataItemsForRight (rightCode, granted); + dataItemIds.AddIds(HostSecurityProfile.GetDataItemsForRight(rightCode, granted)); + return dataItemIds; + } + */ + + //------------------------------------------------------------------- + // S e c u r i t y I t e m s + //------------------------------------------------------------------- + // Security items (user, computer, group or right). + public bool SecurityItemCodeExists (SaSecurity.ItemType securityItemType, String userCode) + { + return SysId.IsValid(GetIdForItemCode(securityItemType, userCode)); + } + public SaSecurityItem GetUserByCode (String code) + { + return GetSecurityItemByCode(SaSecurity.ItemType.User, code); + } + public abstract SaSecurityItem GetSecurityItemByCode (SaSecurity.ItemType securityItemType, String code); + public abstract SaSecurityItem GetSecurityItem (SaSecurityItem.Params prm); + // Security item properties. + public abstract SysId GetIdForItemCode (SaSecurity.ItemType securityItemType, string code); + public abstract String GetCodeForItemId (SysId securityItemId); + public abstract String GetNameForItemId (SysId securityItemId); + public abstract Dictionary GetSecurityItemDescriptions (List securityItemIds); + // Security Item list. + public abstract ColumnDfn GetSecurityItemColumnDfn (SaSecurityItem.Column columnId, String caption = "", int width = 0); + public abstract long GetSecurityItemCount (SaSecurityItem.Params prm); + public abstract List GetSecurityItemList (SaSecurityItem.Params prm); + public abstract DbResultSet ExecuteSecurityItemQuery (List columns, SaSecurityItem.Params prm); + public abstract DataValue GetSecurityItemDataValue (ColumnDfn column, NamedValue.Map values); + + //------------------------------------------------------------------- + // Security item updates. + public abstract bool CreateUser (String code, String name, DbUpdateInfo dbUpdateInfo); + public abstract bool AssignUserToGroup (String userCode, String groupCode, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // S e c u r i t y I t e m s L i n k s + //------------------------------------------------------------------- + // Security item links. + public abstract bool ItemsAreLinked (SysId securityItemId1, SysId securityItemId2); + + //------------------------------------------------------------------- + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Security/SaSecurityDbDfn.cs b/SacsStandardBase/Base/Security/SaSecurityDbDfn.cs new file mode 100644 index 0000000..fb3af42 --- /dev/null +++ b/SacsStandardBase/Base/Security/SaSecurityDbDfn.cs @@ -0,0 +1,360 @@ +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.Linq; + +//--------------------------------------------------------------------------- +// Code generated by Sarand Database Utilities Thu 3-Oct-2019 15:10:38 +//--------------------------------------------------------------------------- +namespace Sarand.Net.Standard.Base.Security +{ + public class SaSecurityDbDfn + { + //------------------------------------------------------------------- + // Table identifiers. + public enum TableIdx + { + Unknown = 0, + SaSecItem, + SaSecLink, + } + + //------------------------------------------------------------------- + // Column identifiers. + public enum ColumnIdx + { + Unknown = 0, + SaSecItem_Id, + SaSecItem_Code, + SaSecItem_Description, + SaSecItem_Type, + SaSecItem_EmailAddress, + SaSecItem_Location, + SaSecItem_EncPword, + SaSecItem_PwordSalt, + SaSecItem_PwordHash, + SaSecItem_LastLogin, + SaSecItem_DataItemType, + SaSecItem_Notes, + SaSecItem_Flags, + SaSecItem_Status, + SaSecItem_CreateOn, + SaSecItem_CreateBy, + SaSecItem_CreateHost, + SaSecItem_UpdateOn, + SaSecItem_UpdateBy, + SaSecItem_UpdateHost, + SaSecLink_ItemOwner, + SaSecLink_ItemTarget, + SaSecLink_ItemContext, + SaSecLink_DayApplic, + SaSecLink_DateApplicFrom, + SaSecLink_DateApplicTo, + SaSecLink_Flags, + SaSecLink_Status, + SaSecLink_CreateOn, + SaSecLink_CreateBy, + SaSecLink_CreateHost, + SaSecLink_UpdateOn, + SaSecLink_UpdateBy, + SaSecLink_UpdateHost, + } + + //------------------------------------------------------------------- + // Data members. + private static readonly int tableCount = 2; + private static readonly int columnCount = 34; + 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) + { + SaSecurityDbDfn.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) + { + SaSecurityDbDfn.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 = + { + "SaSecItem", + "SaSecLink", + }; + + //------------------------------------------------------------------- + // 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,"Code",0,-9,false,20), + new ColumnDfn(1,"Description",0,-9,false,50), + new ColumnDfn(1,"Type",2,-6,false,3), + new ColumnDfn(1,"EmailAddress",0,12,false,128), + new ColumnDfn(1,"Location",0,-9,false,50), + new ColumnDfn(1,"EncPword",0,-9,false,128), + new ColumnDfn(1,"PwordSalt",0,-9,false,16), + new ColumnDfn(1,"PwordHash",0,-9,false,64), + new ColumnDfn(1,"LastLogin",9,93,false,23), + new ColumnDfn(1,"DataItemType",3,5,false,5), + new ColumnDfn(1,"Notes",0,-9,false,255), + new ColumnDfn(1,"Flags",3,5,false,5), + new ColumnDfn(1,"Status",0,12,false,3), + new ColumnDfn(1,"CreateOn",9,93,false,23), + new ColumnDfn(1,"CreateBy",4,4,false,10), + new ColumnDfn(1,"CreateHost",0,12,false,64), + new ColumnDfn(1,"UpdateOn",9,93,false,23), + new ColumnDfn(1,"UpdateBy",4,4,false,10), + new ColumnDfn(1,"UpdateHost",0,12,false,64), + new ColumnDfn(2,"ItemOwner",4,4,false,10), + new ColumnDfn(2,"ItemTarget",4,4,false,10), + new ColumnDfn(2,"ItemContext",4,4,false,10), + new ColumnDfn(2,"DayApplic",0,-9,false,50), + new ColumnDfn(2,"DateApplicFrom",4,4,false,10), + new ColumnDfn(2,"DateApplicTo",4,4,false,10), + 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/Security/SaSecurityItem.cs b/SacsStandardBase/Base/Security/SaSecurityItem.cs new file mode 100644 index 0000000..9e350c2 --- /dev/null +++ b/SacsStandardBase/Base/Security/SaSecurityItem.cs @@ -0,0 +1,389 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.App; + +namespace Sarand.Net.Standard.Base.Security +{ + //======================================================================= + public abstract class SaSecurityItem : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Pos = 0, + Id, + ItemType, + ItemType_Desc, + Code, + EmailAddress, + Description, + Location, + LastLogin_Date, + OwnerLinkCount, + TargetLinkCount, + TargetLinkCount_Groups, + OwnerLinkCount_Rights, + Notes, + Status, + Status_Desc, + Status_Ind, + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public SaSecurity.ItemType ItemType { get; set; } = SaSecurity.ItemType.Unknown; + public String Code { get; set; } = String.Empty; + public String EmailAddress { get; set; } = String.Empty; + public String Description { get; set; } = String.Empty; + public String Location { get; set; } = String.Empty; + public String PasswordEncrypted { get; set; } = String.Empty; + public String PasswordSalt { get; set; } = String.Empty; + public String PasswordHash { get; set; } = String.Empty; + public DateTime LastLogin { get; set; } = DateTime.MinValue; + public int DataItemType { get; set; } = -1; + } + + //------------------------------------------------------------------- + // Data. + public new class Data : SacsHierarchyItem.Data + { + // Data members. + private readonly DataProperties data = new DataProperties(); + // Construction. + public Data () + { + Status = DefaultStatus; + } + // Properties. + public SaSecurity.ItemType ItemType + { + get { return data.ItemType; } + set { CheckChanged((int)data.ItemType, (int)value); data.ItemType = value; } + } + public String Code + { + get { return data.Code; } + set { CheckChanged(data.Code, value); data.Code = value; } + } + public String EmailAddress + { + get { return data.EmailAddress; } + set { CheckChanged(data.EmailAddress, value); data.EmailAddress = value; } + } + public String Description + { + get { return data.Description; } + set { CheckChanged(data.Description, value); data.Description = value; } + } + public String Location + { + get { return data.Location; } + set { CheckChanged(data.Location, value); data.Location = value; } + } + public String PasswordEncrypted + { + get { return data.PasswordEncrypted; } + set { CheckChanged(data.PasswordEncrypted, value); data.PasswordEncrypted = value; } + } + public String PasswordSalt + { + get { return data.PasswordSalt; } + set { CheckChanged(data.PasswordSalt, value); data.PasswordSalt = value; } + } + public String PasswordHash + { + get { return data.PasswordHash; } + set { CheckChanged(data.PasswordHash, value); data.PasswordHash = value; } + } + public DateTime LastLogin + { + get { return data.LastLogin; } + set { CheckChanged(data.LastLogin, value); data.LastLogin = value; } + } + public int DataItemType + { + get { return data.DataItemType; } + set { CheckChanged(data.DataItemType, value); data.DataItemType = value; } + } + // Flags. + public bool AuthenticatedUser + { + get { return GetFlag(Flag_AuthenticatedUser); } + set { SetFlag(Flag_AuthenticatedUser, value); } + } + // Population. + internal void PopulateFrom (Data data) + { + ItemType = data.ItemType; + Code = data.Code; + EmailAddress = data.EmailAddress; + Description = data.Description; + Location = data.Location; + PasswordEncrypted = data.PasswordEncrypted; + PasswordSalt = data.PasswordSalt; + PasswordHash = data.PasswordHash; + LastLogin = data.LastLogin; + DataItemType = data.DataItemType; + 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 ItemType { get; set; } = ValidValue.Valid; + public ValidValue Code { get; set; } = ValidValue.Valid; + public ValidValue EmailAddress { get; set; } = ValidValue.Valid; + public ValidValue Description { get; set; } = ValidValue.Valid; + public ValidValue Location { get; set; } = ValidValue.Valid; + public ValidValue Password { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + // Flags. + public static readonly Byte Flag_AuthenticatedUser = 0x01; + + //------------------------------------------------------------------- + // Construction. + public SaSecurityItem (SysId id, SaSecurity.ItemType securityItemType) : + base(id) + { + base.SetData(data); + ItemType = securityItemType; + Status = DefaultStatus; + } + + //------------------------------------------------------------------- + // Status. + private static readonly List securityItemStatuses = new List() + { + SaStatus.Enabled, + SaStatus.Disabled, + }; + public static SaStatus DefaultStatus { get { return EnabledStatus; } } + public static SaStatus EnabledStatus { get { return SaStatus.Enabled; } } + public static SaStatus DisabledStatus { get { return SaStatus.Disabled; } } + public static List ValidStatuses { get { return new List(securityItemStatuses); } } + public static DataValue.TriStateType GetStatusTriState (SaStatus status) + { + switch (status) + { + case SaStatus.Enabled: + return DataValue.TriStateType.Green; + case SaStatus.Disabled: + return DataValue.TriStateType.Red; + case SaStatus.Deleted: + return DataValue.TriStateType.Purple; + } + return DataValue.TriStateType.Unknown; + } + + //------------------------------------------------------------------- + // Data 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 ((int)data.ItemType > 0 && data.ItemType != ItemType) + throw new NotSupportedException(fn + " Item Type cannot be changed"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + + //------------------------------------------------------------------- + // Properties. + public SaSecurity.ItemType ItemType + { + get { return data.ItemType; } + protected set { data.ItemType = value; } + } + public String Code + { + get { return data.Code; } + set { data.Code = value; } + } + public String EmailAddress + { + get { return data.EmailAddress; } + set { data.EmailAddress = value; } + } + public String Description + { + get { return data.Description; } + set { data.Description = value; } + } + public String Location + { + get { return data.Location; } + set { data.Location = value; } + } + public String PasswordEncrypted + { + get { return data.PasswordEncrypted; } + set { data.PasswordEncrypted = value; } + } + public String PasswordSalt + { + get { return data.PasswordSalt; } + set { data.PasswordSalt = value; } + } + public String PasswordHash + { + get { return data.PasswordHash; } + set { data.PasswordHash = value; } + } + public DateTime LastLogin + { + get { return data.LastLogin; } + set { data.LastLogin = value; } + } + public int DataItemType + { + get { return data.DataItemType; } + set { data.DataItemType = value; } + } + + //------------------------------------------------------------------- + // Flags. + public bool AuthenticatedUser + { + get { return GetFlag(Flag_AuthenticatedUser); } + set { SetFlag(Flag_AuthenticatedUser, value); } + } + + //------------------------------------------------------------------- + // Password utilities. + private static readonly Encoding Encoding_Ansi1252 = Encoding.GetEncoding(1252); + public bool IsPasswordBlank () + { + return EncryptPassword("").Equals(PasswordEncrypted); + } + public bool PasswordMatches (String password) + { + return PasswordEncrypted.Equals(EncryptPassword(password)); + } + public static String EncryptPassword (String password) + { + //String fn = "SaSecurityItem.EncryptPassword"; + + // We know this is not very water-tight, but it at least + // hides the contents of the password from prying eyes. + String toBeEncrypted = password; + // Password length goes in as first character: + toBeEncrypted = ByteToString((byte)(128 - password.Length)) + toBeEncrypted; + toBeEncrypted = string.Concat(toBeEncrypted.Reverse()); + // Pad out to fixed length (minimum 16, multiples of 8): + int fixedLength = 16; + if (password.Length / 8 > 2) fixedLength = 8 * (int)(password.Length / 8); + while (toBeEncrypted.Length < fixedLength) + { + char charToAdd = (char)(password.Length > 0 ? password[(toBeEncrypted.Length % password.Length)] : 'A' + (toBeEncrypted.Length * 13) % 23); + if (toBeEncrypted.Length % 2 != 0) + toBeEncrypted += charToAdd; + else toBeEncrypted = charToAdd + toBeEncrypted; + } + + // Now perform a simple algorythm on each character to hide its true value: + byte[] encryptedChars = new byte[toBeEncrypted.Length]; + for (int i = 0; i < toBeEncrypted.Length; i++) + { + int iData = toBeEncrypted[i]; + iData += 17 + i; + if (iData > 255) iData -= (255 + 32); + encryptedChars[i] = (byte)iData; + } + String result = Encoding_Ansi1252.GetString(encryptedChars); +#if DEBUG + // Password: mgbgt + // To encrypt: tbmggtgbgm{btggm + // Encrypted: 133, 116, 128, 123, 124, 138, 126, 122, 128, 135, 150, 126, 145, 133, 134, 141 + //Logger.Info(fn + "\n\tPassword: " + password + "\n\tTo encrypt: " + toBeEncrypted + "\n\tEncrypted: " + encryptedChars.Select(i => i.ToString()).ToList().Concat(", ")); + //Logger.Debug(password + ": " + result); +#endif + return result; + } + private byte[] PasswordStringToBytes (String value) + { + byte[] values = new byte[value.Length]; + for (int idx = 0; idx < value.Length; idx++) + { + char c = value[idx]; + UInt32 ui = Convert.ToUInt32(c); + SaLogger.Info(idx + ". " + ui.ToString("x8")); + values[idx] = (byte)(c); + } + return values; + } + private static String ByteToString (byte value) + { + return Encoding_Ansi1252.GetString(new[] { value }); + } + + //------------------------------------------------------------------- + // List parameters. + public enum StatusCat + { + Any, + EnabledOnly, + DisabledOnly, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + public List IncludedIds { get; } = new List(); + public List ExcludedIds { get; } = new List(); + public SaSecurity.ItemType SecurityItemType { get; set; } = SaSecurity.ItemType.Any; + public String Code { get; set; } + public String CodePartial { get; set; } + public String EmailAddress { get; set; } + public String EmailAddressPartial { get; set; } + public String Description { get; set; } + public String DescriptionPartial { get; set; } + public List IncludedGroupIds { get; } = new List(); + public StatusCat StatusCat { get; set; } = StatusCat.EnabledOnly; + } + + //------------------------------------------------------------------- + } + + //======================================================================= + public static class SaSecurityItemList + { + public static SaSecurityItem GetSecItemById (this List secItems, SysId secItemId) + { + if (!SysId.IsValid(secItemId)) return null; + foreach (SaSecurityItem secItem in secItems) + { + if (secItem.Id.Equals(secItemId)) + return secItem; + } + return null; + } + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Security/SaSecurityLink.cs b/SacsStandardBase/Base/Security/SaSecurityLink.cs new file mode 100644 index 0000000..e27fb2a --- /dev/null +++ b/SacsStandardBase/Base/Security/SaSecurityLink.cs @@ -0,0 +1,286 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; + +namespace Sarand.Net.Standard.Base.Security +{ + //======================================================================= + public abstract class SaSecurityLink : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Pos = 0, + ItemIdOwner, + ItemOwner_Code, + ItemOwner_Desc, + ItemIdTarget, + ItemTarget_Code, + ItemTarget_Desc, + ItemIdContext, + ItemContext_Desc, + DaysApplicable, + DateApplicableFrom, + DateApplicableTo, + Granted_Y, + Denied_X, + Status, + Status_Ind, + Status_Desc, + CreateOn_Date, + UpdateOn_Date, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public SysId ItemIdOwner { get; set; } = new SysId(); + public SysId ItemIdTarget { get; set; } = new SysId(); + public SysId ItemIdContext { get; set; } = new SysId(); + public String DayApplicability { get; set; } = String.Empty; + public DateTime ApplicableFrom { get; set; } = DateTime.MinValue; + public DateTime ApplicableTo { get; set; } = DateTime.MinValue; + } + + //------------------------------------------------------------------- + // Data. + public new class Data : SacsHierarchyItem.Data + { + // Data members. + private readonly DataProperties data = new DataProperties(); + // Construction. + public Data () + { + Status = DefaultStatus; + } + // Properties. + public SysId ItemIdOwner + { + get { return data.ItemIdOwner; } + set { CheckChanged(data.ItemIdOwner, value); data.ItemIdOwner = value; } + } + public SysId ItemIdTarget + { + get { return data.ItemIdTarget; } + set { CheckChanged(data.ItemIdTarget, value); data.ItemIdTarget = value; } + } + public SysId ItemIdContext + { + get { return data.ItemIdContext; } + set { CheckChanged(data.ItemIdContext, value); data.ItemIdContext = value; } + } + public String DayApplicability + { + get { return data.DayApplicability; } + set { CheckChanged(data.DayApplicability, value); data.DayApplicability = value; } + } + public DateTime ApplicableFrom + { + get { return data.ApplicableFrom; } + set { CheckChanged(data.ApplicableFrom, value); data.ApplicableFrom = value; } + } + public DateTime ApplicableTo + { + get { return data.ApplicableTo; } + set { CheckChanged(data.ApplicableTo, value); data.ApplicableTo = value; } + } + // Population. + internal void PopulateFrom (Data data) + { + ItemIdOwner = data.ItemIdOwner; + ItemIdTarget = data.ItemIdTarget; + ItemIdContext = data.ItemIdContext; + DayApplicability = data.DayApplicability; + ApplicableFrom = data.ApplicableFrom; + ApplicableTo = data.ApplicableTo; + 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 ItemIdOwner { get; set; } = ValidValue.Valid; + public ValidValue ItemIdTarget { get; set; } = ValidValue.Valid; + public ValidValue ItemIdContext { get; set; } = ValidValue.Valid; + public ValidValue DayApplicability { get; set; } = ValidValue.Valid; + public ValidValue ApplicableFrom { get; set; } = ValidValue.Valid; + public ValidValue ApplicableTo { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + + //------------------------------------------------------------------- + // Construction. + public SaSecurityLink (SysId itemIdOwner, SysId itemIdTarget, SysId itemIdContext) : + base() + { + base.SetData(data); + ItemIdOwner = itemIdOwner; + ItemIdTarget = itemIdTarget; + ItemIdContext = itemIdContext; + Status = DefaultStatus; + } + + //------------------------------------------------------------------- + // Status. + private static readonly List securityLinkStatuses = new List() + { + SaStatus.Granted, + SaStatus.Denied, + }; + public static SaStatus DefaultStatus { get { return GrantedStatus; } } + public static SaStatus GrantedStatus { get { return SaStatus.Granted; } } + public static SaStatus DeniedStatus { get { return SaStatus.Denied; } } + public static List ValidStatuses { get { return new List(securityLinkStatuses); } } + public static DataValue.TriStateType GetStatusTriState (SaStatus status) + { + switch (status) + { + case SaStatus.Granted: + return DataValue.TriStateType.Green; + case SaStatus.Denied: + return DataValue.TriStateType.Red; + case SaStatus.Deleted: + return DataValue.TriStateType.Purple; + } + return 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(data.Id)) + throw new NotSupportedException(fn + " Id is not applicable for security links"); + if (SysId.IsValid(data.ItemIdOwner) && SysId.ToInt(data.ItemIdOwner) != SysId.ToInt(ItemIdOwner)) + throw new NotSupportedException(fn + " Owner Item cannot be changed"); + if (SysId.IsValid(data.ItemIdTarget) && SysId.ToInt(data.ItemIdTarget) != SysId.ToInt(ItemIdTarget)) + throw new NotSupportedException(fn + " Target Item cannot be changed"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + + //------------------------------------------------------------------- + // Properties. + public override bool ItemHasId { get { return false; } } + public SysId ItemIdOwner + { + get { return data.ItemIdOwner; } + protected set { data.ItemIdOwner = value; } + } + public SysId ItemIdTarget + { + get { return data.ItemIdTarget; } + protected set { data.ItemIdTarget = value; } + } + public SysId ItemIdContext + { + get { return data.ItemIdContext; } + protected set { data.ItemIdContext = value; } + } + public String DayApplicability + { + get { return data.DayApplicability; } + set { data.DayApplicability = value; } + } + public DateTime ApplicableFrom + { + get { return data.ApplicableFrom; } + set { data.ApplicableFrom = value; } + } + public DateTime ApplicableTo + { + get { return data.ApplicableTo; } + set { data.ApplicableTo = value; } + } + + //------------------------------------------------------------------- + // List parameters. + public enum StatusCat + { + Any, + GrantedOnly, + DeniedOnly, + } + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + public readonly List includedItemIdsOwner = new List(); + public SaSecurity.ItemType OwnerItemType { get; set; } = SaSecurity.ItemType.Unknown; + public readonly List includedItemIdsTarget = new List(); + public SaSecurity.ItemType TargetItemType { get; set; } = SaSecurity.ItemType.Unknown; + public readonly List includedItemIdsContext = new List(); + public StatusCat StatusCat { get; set; } = StatusCat.Any; + } + + //------------------------------------------------------------------- + } + //======================================================================= + public static class SaSecurityLinkList + { + //------------------------------------------------------------------- + // Link owner. + public static bool ContaineOwnerItemId (this List links, SysId itemIdOwner) + { + if (!SysId.IsValid(itemIdOwner)) return false; + SaSecurityLink secLink = links.Find(link => SysId.IsValid(link.ItemIdOwner) && link.ItemIdOwner.Equals(itemIdOwner)); + return (secLink != null); + } + public static List GetOwnerItemIds (this List links) + { + List ownerItemIds = new List(); + foreach (SaSecurityLink secLink in links) + { + if (SysId.IsValid(secLink.ItemIdOwner) && !ownerItemIds.Contains(secLink.ItemIdOwner)) + ownerItemIds.Add(secLink.ItemIdOwner); + } + return ownerItemIds; + } + //------------------------------------------------------------------- + // Link target. + public static bool ContainsTargetItemId (this List links, SysId itemIdTarget) + { + if (!SysId.IsValid(itemIdTarget)) return false; + SaSecurityLink secLink = links.Find(link => SysId.IsValid(link.ItemIdTarget) && link.ItemIdTarget.Equals(itemIdTarget)); + return (secLink != null); + } + public static List GetTargetItemIds (this List links) + { + List targetItemIds = new List(); + foreach (SaSecurityLink secLink in links) + { + if (SysId.IsValid(secLink.ItemIdTarget) && !targetItemIds.Contains(secLink.ItemIdTarget)) + targetItemIds.Add(secLink.ItemIdTarget); + } + return targetItemIds; + } + public static SaStatus GetStatusByTargetItemId (this List links, SysId itemIdTarget) + { + if (!SysId.IsValid(itemIdTarget)) return SaStatus.Unknown; + SaSecurityLink secLink = links.Find(link => SysId.IsValid(link.ItemIdTarget) && link.ItemIdTarget.Equals(itemIdTarget)); + return (secLink != null ? secLink.Status : SaStatus.Unknown); + } + //------------------------------------------------------------------- + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Security/SaSecurityMgr-SecurityItem.cs b/SacsStandardBase/Base/Security/SaSecurityMgr-SecurityItem.cs new file mode 100644 index 0000000..4b147e2 --- /dev/null +++ b/SacsStandardBase/Base/Security/SaSecurityMgr-SecurityItem.cs @@ -0,0 +1,166 @@ +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.Security +{ + //======================================================================= + partial class SaSecurityMgr + { + //******************************************************************* + // S e c u r i t y I t e m s + //******************************************************************* + // Associated data. + public bool SecurityItemHasLinks (SysId securityItemId) + { + return (SecurityItemIsSourceOfLinks(securityItemId) || SecurityItemIsTargetOfLinks(securityItemId)); + } + public abstract bool SecurityItemIsSourceOfLinks (SysId securityItemId); + public abstract bool SecurityItemIsTargetOfLinks (SysId securityItemId); + public abstract bool SecurityItemHasLinksWith (SysId securityItemIdOwner, SysId securityItemIdTarget); + + //------------------------------------------------------------------- + // Retrieve individual security items. + public abstract bool SecurityItemExistsById (SysId id); + public abstract bool SecurityItemExistsByTypeCode (SaSecurity.ItemType securityItemType, String code); + public abstract SaSecurityItem GetSecurityItemById (SysId id); + public abstract SaSecurityItem GetSecurityItemByTypeCode (SaSecurity.ItemType securityItemType, String code); + public abstract SaSecurityItem GetSecurityItem (SaSecurityItem.Params prmSecItem); + + // Create Security Item. + public abstract SaSecurityItem CreateSecurityItem (SaSecurity.ItemType securityItemType); + + //------------------------------------------------------------------- + // Validate Security Item. + public bool IsValidSecurityItem (SaSecurityItem securityItem) + { + ClearErrorState(); + if (securityItem == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidSecurityItemData(securityItem.GetData(), new SaSecurityItem.ErrField()); + } + public virtual bool IsValidSecurityItemData (SaSecurityItem.Data data, SaSecurityItem.ErrField errField) + { + ClearErrorState(); + if (errField.ItemType != SacsHierarchyItem.ValidValue.Ignore && (int)data.ItemType < 1) + { + SetErrorState("security item type must be supplied"); + errField.ItemType = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Code != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Code, "code", (long)SaSecurityDbDfn.GetColTextLen(SaSecurityDbDfn.ColumnIdx.SaSecItem_Code), true)) + { + errField.Code = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.EmailAddress != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.EmailAddress, "email address", (long)SaSecurityDbDfn.GetColTextLen(SaSecurityDbDfn.ColumnIdx.SaSecItem_EmailAddress))) + { + errField.EmailAddress = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Description != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Description, "description", (long)SaSecurityDbDfn.GetColTextLen(SaSecurityDbDfn.ColumnIdx.SaSecItem_Description), true)) + { + errField.Description = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Location != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Location, "location", (long)SaSecurityDbDfn.GetColTextLen(SaSecurityDbDfn.ColumnIdx.SaSecItem_Location))) + { + errField.Location = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Password != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.PasswordEncrypted, "password", (long)SaSecurityDbDfn.GetColTextLen(SaSecurityDbDfn.ColumnIdx.SaSecItem_EncPword))) + { + errField.Password = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + // Common properties. + if (errField.Notes != SacsHierarchyItem.ValidValue.Ignore && !IsValidValue(data.Notes, "notes", (long)SaSecurityDbDfn.GetColTextLen(SaSecurityDbDfn.ColumnIdx.SaSecItem_Notes))) + { + errField.Notes = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (errField.Status != SacsHierarchyItem.ValidValue.Ignore) + { + if (!IsValidValue(data.StatusCode, "status", (long)SaSecurityDbDfn.GetColTextLen(SaSecurityDbDfn.ColumnIdx.SaSecItem_Status), true, true)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!SaSecurityItem.ValidStatuses.Contains(data.Status)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState(ErrorMessage.InvalidStatus); + } + } + return true; + } + + //------------------------------------------------------------------- + // SecurityItem database interface. + public abstract bool InsertSecurityItem (SaSecurityItem securityItem, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateSecurityItem (SaSecurityItem securityItem, DbUpdateInfo dbUpdateInfo); + public virtual bool SecurityItemCanBeDeleted (SaSecurityItem securityItem) + { + ClearErrorState(); + if (SecurityItemHasLinks(securityItem.Id)) return SetErrorState("security item has links"); + return true; + } + public abstract bool DeleteSecurityItem (SaSecurityItem securityItem, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // Item list. + public abstract ColumnDfn GetSecurityItemColumnDfn (SaSecurityItem.Column columnId, String caption = "", int width = 0); + public abstract List GetSecurityItemQueryFilter (SaSecurityItem.Params prm); + public abstract long GetSecurityItemCount (SaSecurityItem.Params prm); + public abstract List GetSecurityItemList (SaSecurityItem.Params prm); + public abstract DbResultSet ExecuteSecurityItemQuery (List columns, SaSecurityItem.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 DataValue GetSecurityItemDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + switch ((SaSecurityItem.Column)column.Id) + { + //----------------------------------------------------------- + // Item type. + case SaSecurityItem.Column.ItemType_Desc: + SaSecurity.ItemType securityItemType = (SaSecurity.ItemType)values.GetValue(column.DataName).AsLong(); + value = new DataValue(securityItemTypes.GetCodeForType(securityItemType, securityItemType.ToString())); + break; + + //----------------------------------------------------------- + // Notes. + case SaSecurityItem.Column.Notes: + value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200)); + break; + + //----------------------------------------------------------- + // Status. + case SaSecurityItem.Column.Status_Desc: + case SaSecurityItem.Column.Status_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaSecurityItem.Column.Status_Desc) + value = new DataValue(SacsSystem.GetStatusDescForCode(statusCode)); + else if (column.Id == (int)SaSecurityItem.Column.Status_Ind) + value = new DataValue(SaSecurityItem.GetStatusTriState(status)); + } + break; + + //----------------------------------------------------------- + } + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Security/SaSecurityMgr-SecurityLink.cs b/SacsStandardBase/Base/Security/SaSecurityMgr-SecurityLink.cs new file mode 100644 index 0000000..997c8f9 --- /dev/null +++ b/SacsStandardBase/Base/Security/SaSecurityMgr-SecurityLink.cs @@ -0,0 +1,126 @@ +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.Security +{ + //======================================================================= + partial class SaSecurityMgr + { + //******************************************************************* + // S e c u r i t y L i n k s + //******************************************************************* + // Retrieve individual securityLinks. + public abstract bool SecurityLinkExistsByItemsIds (SysId itemIdOwner, SysId itemIdTarget, SysId itemIdContext); + public abstract SaSecurityLink GetSecurityLinkByItemsIds (SysId itemIdOwner, SysId itemIdTarget, SysId itemIdContext); + + // Create securityLink. + public abstract SaSecurityLink CreateSecurityLink (SysId itemIdOwner, SysId itemIdTarget, SysId itemIdContext); + + //------------------------------------------------------------------- + // Validate securityLink data. + public bool IsValidSecurityLink (SaSecurityLink securityLink) + { + ClearErrorState(); + if (securityLink == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidSecurityLinkData(securityLink.GetData(), new SaSecurityLink.ErrField()); + } + public virtual bool IsValidSecurityLinkData (SaSecurityLink.Data securityLinkData, SaSecurityLink.ErrField errField) + { + ClearErrorState(); + if (!SysId.IsValid(securityLinkData.ItemIdOwner)) + { + errField.ItemIdOwner = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("owner must be specified"); + } + if (!SysId.IsValid(securityLinkData.ItemIdOwner)) + { + errField.ItemIdOwner = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("owner must be specified"); + } + if (!SysId.IsValid(securityLinkData.ItemIdOwner)) + { + errField.ItemIdOwner = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("owner must be specified"); + } + // Common properties. + if (!SaSecurityLink.ValidStatuses.Contains(securityLinkData.Status)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState(ErrorMessage.InvalidStatus); + } + return true; + } + + //------------------------------------------------------------------- + // SecurityLink database interface. + public abstract bool InsertSecurityLink (SaSecurityLink securityLink, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateSecurityLink (SaSecurityLink securityLink, DbUpdateInfo dbUpdateInfo); + public virtual bool SecurityLinkCanBeDeleted (SaSecurityLink securityLink) + { + ClearErrorState(); + return true; + } + public abstract bool DeleteSecurityLink (SaSecurityLink securityLink, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // Security Link list: + public abstract ColumnDfn GetSecurityLinkColumnDfn (SaSecurityLink.Column columnId, String caption = "", int width = 0); + public abstract List GetSecurityLinkQueryFilter (SaSecurityLink.Params prm); + public abstract long GetSecurityLinkCount (SaSecurityLink.Params prm); + public abstract List GetSecurityLinkList (SaSecurityLink.Params prm); + public abstract DbResultSet ExecuteSecurityLinkQuery (List columns, SaSecurityLink.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 DataValue GetSecurityLinkDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + SaSecurityLink.Column colId = (SaSecurityLink.Column)column.Id; + switch (colId) + { + //----------------------------------------------------------- + // Context Item description. + case SaSecurityLink.Column.ItemContext_Desc: + { + SysId contextItemId = new SysId((int)values.GetValue(GetSecurityLinkColumnDfn(SaSecurityLink.Column.ItemIdContext).DataName).AsLong()); + value = new DataValue("Item #" + SysId.ToString(contextItemId)); + } + break; + + //----------------------------------------------------------- + // Status. + case SaSecurityLink.Column.Granted_Y: + case SaSecurityLink.Column.Denied_X: + case SaSecurityLink.Column.Status_Desc: + case SaSecurityLink.Column.Status_Ind: + { + String statusValue = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusValue); + if (column.Id == (int)SaSecurityLink.Column.Granted_Y) + value = new DataValue(status == SaStatus.Granted ? "Y" : ""); + else if (column.Id == (int)SaSecurityLink.Column.Denied_X) + value = new DataValue(status == SaStatus.Denied ? "X" : ""); + else if (column.Id == (int)SaSecurityLink.Column.Status_Desc) + value = new DataValue(SacsSystem.GetStatusDescForCode(statusValue)); + else if (column.Id == (int)SaSecurityLink.Column.Status_Ind) + value = new DataValue(SaSecurityLink.GetStatusTriState(status)); + } + break; + + //----------------------------------------------------------- + } + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Security/SaSecurityMgr.cs b/SacsStandardBase/Base/Security/SaSecurityMgr.cs new file mode 100644 index 0000000..6eac2f6 --- /dev/null +++ b/SacsStandardBase/Base/Security/SaSecurityMgr.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; + +namespace Sarand.Net.Standard.Base.Security +{ + //======================================================================= + public abstract partial class SaSecurityMgr : SacsItemMgr + { + //------------------------------------------------------------------- + // Data members. + private List securityItemTypes; + + //------------------------------------------------------------------- + // Static initialisation. + static SaSecurityMgr () + { + } + + //------------------------------------------------------------------- + // Construction. + protected SaSecurityMgr () + { + securityItemTypes = SaSecurityConsole.SecurityItemTypes; + } + + //------------------------------------------------------------------- + // Database properties. + public abstract bool DatabaseIsValid { get; } + public abstract bool CanUpdateDatabase { get; } + + //------------------------------------------------------------------- + // Utilities. + public abstract bool ColumnExists (SaSecurityDbDfn.ColumnIdx columnIdx); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Security/SaSecurityProfile.cs b/SacsStandardBase/Base/Security/SaSecurityProfile.cs new file mode 100644 index 0000000..9d964eb --- /dev/null +++ b/SacsStandardBase/Base/Security/SaSecurityProfile.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.Security +{ + //======================================================================= + public class SaSecurityProfile + { + //------------------------------------------------------------------- + public class GroupData + { + public readonly List rights = new List(); + public GroupData (SaSecurityItem secGroup, List groupRights) + { + if (secGroup.ItemType != SaSecurity.ItemType.Group) throw new NotSupportedException("SaSecurityProfile.GroupData - Invalid security item type: "+ secGroup.ItemType.ToString()); + GroupId = secGroup.Id; + GroupCode = secGroup.Code; + GroupDesc = secGroup.Description; + rights.AddRange(groupRights); + } + protected GroupData () { } + public SysId GroupId { get; protected set; } + public String GroupCode { get; protected set; } + public String GroupDesc { get; protected set; } + public List Rights { get { return new List(rights); } } + } + public class RightData + { + public RightData (SaSecurityItem secRight, SaSecurityLink secRightLink) + { + if (secRight.ItemType != SaSecurity.ItemType.Right) throw new NotSupportedException("SaSecurityProfile.RightData - Invalid security item type: " + secRight.ItemType.ToString()); + RightId = secRight.Id; + RightCode = secRight.Code; + RightDesc = secRight.Description; + DataItemId = secRightLink.ItemIdContext; + Granted = (secRightLink.Status == SaStatus.Granted); + } + protected RightData () { } + public SysId RightId { get; protected set; } + public String RightCode { get; protected set; } + public String RightDesc { get; protected set; } + public SysId DataItemId { get; protected set; } + public bool Granted { get; protected set; } + } + + //------------------------------------------------------------------- + // Data members. + protected readonly List securityGroups = new List(); + protected readonly List securityRights = new List(); + + //------------------------------------------------------------------- + // Construction. + internal SaSecurityProfile (SaSecurityItem secItem, List profileGroups, List profileRights, bool systemAdmin) + { + Clear(); + if (secItem != null) + { + // Security item properties. + Id = secItem.Id; + SecurityItemType = secItem.ItemType; + Code = secItem.Code; + EmailAddress = secItem.EmailAddress; + Description = secItem.Description; + Location = secItem.Location; + LastLogin = secItem.LastLogin; + } + if (profileGroups != null) + securityGroups.AddRange(profileGroups); + if (profileRights != null) + securityRights.AddRange(profileRights); + SystemAdmin = systemAdmin; + } + /// + /// Protected construction - for derived classes. + /// Ref. Mobile Security Profile. + /// + protected SaSecurityProfile() + { + Clear(); + } + + //------------------------------------------------------------------- + // Properties. + public SysId Id { get; protected set; } + public SaSecurity.ItemType SecurityItemType { get; protected set; } = SaSecurity.ItemType.Unknown; + public String Code { get; protected set; } + public String EmailAddress { get; protected set; } + public String Description { get; protected set; } + public String Location { get; protected set; } + public DateTime LastLogin { get; protected set; } = DateTime.MinValue; + public List Groups { get { return new List(securityGroups); } } + public List Rights { get { return new List(securityRights); } } + public bool SystemAdmin { get; protected set; } = false; + + //------------------------------------------------------------------- + // Population. + + //------------------------------------------------------------------- + // Clear profile. + public void Clear() + { + Id = new SysId(); + SecurityItemType = SaSecurity.ItemType.Unknown; + Code = String.Empty; + Description = String.Empty; + Location = String.Empty; + LastLogin = DateTime.MinValue; + securityGroups.Clear(); + securityRights.Clear(); + SystemAdmin = false; + } + + //------------------------------------------------------------------- + // Utilities. + public bool IsMemberOfSecurityGroup (SysId secGroupId) + { + return securityGroups.Exists(item => item.GroupId.Equals(secGroupId)); + } + + //------------------------------------------------------------------- + /// + /// Retrieve the list of data items for which the user is explicitly granted the + /// specified security right, or where the right is explicitly denied if the flag is false. + /// + public List GetDataItemsForRight (String rightCode, bool granted = true) + { + return securityGroups.GetDataItemsForRight(rightCode, granted); + } + + //------------------------------------------------------------------- + /// Is the Security Right code granted access for the Data item (if specified). + public bool IsSecurityRightGranted (String rightCode, SysId dataItemId = null) + { + if (SystemAdmin) return true; + return (securityGroups.IsSecurityRightGranted(rightCode, dataItemId) || securityRights.IsSecurityRightGranted(rightCode, dataItemId)); + } + + //------------------------------------------------------------------- + /// Is the Security Right code denined access for the Data item (if specified). + public bool IsSecurityRightDenied (String rightCode, SysId dataItemId = null) + { + if (SystemAdmin) return false; + return (securityGroups.IsSecurityRightDenied(rightCode, dataItemId) || securityRights.IsSecurityRightDenied(rightCode, dataItemId)); + } + + //------------------------------------------------------------------- + } + + //======================================================================= + public static class SaSecurityProfileGroupDataList + { + /// + /// Retrieve the list of data items for which the user is explicitly granted the + /// specified security right, or where the right is explicitly denied if the flag is false. + /// + public static List GetDataItemsForRight (this List groups, String rightCode, bool granted = true) + { + List dataItemIds = new List(); + foreach (var securityGroup in groups) + dataItemIds.AddIds(securityGroup.rights.GetDataItemsForRight(rightCode,granted)); + return dataItemIds; + } + /// Is the Security Right code granted access for the Data item (if specified). + public static bool IsSecurityRightGranted (this List groups, String rightCode, SysId dataItemId = null) + { + return groups.Exists(item => item.rights.IsSecurityRightGranted(rightCode, dataItemId)); + } + /// Is the Security Right code denined access for the Data item (if specified). + public static bool IsSecurityRightDenied (this List groups, String rightCode, SysId dataItemId = null) + { + return groups.Exists(item => item.rights.IsSecurityRightDenied(rightCode, dataItemId)); + } + } + + //======================================================================= + public static class SaSecurityProfileRightDataList + { + /// + /// Retrieve the list of data items for which the user is explicitly granted the + /// specified security right, or where the right is explicitly denied if the flag is false. + /// + public static List GetDataItemsForRight (this List rights, String rightCode, bool granted = true) + { + List dataItemIds = new List(); + foreach (var securityRight in rights.FindAll(x => x.RightCode.EqualsCI(rightCode) && x.Granted == granted)) + dataItemIds.AddId(securityRight.DataItemId); + return dataItemIds; + } + /// Is the Security Right code granted access for the Data item (if specified). + public static bool IsSecurityRightGranted (this List rights, String rightCode, SysId dataItemId = null) + { + foreach (var userSecurityRight in rights.FindAll(item => item.RightCode.EqualsCI(rightCode))) + { + if (SysId.ToInt(dataItemId) != SysId.ToInt(userSecurityRight.DataItemId)) continue; + return userSecurityRight.Granted; + } + return false; + } + /// Is the Security Right code denined access for the Data item (if specified). + public static bool IsSecurityRightDenied (this List rights, String rightCode, SysId dataItemId = null) + { + foreach (var userSecurityRight in rights.FindAll(item => item.RightCode.EqualsCI(rightCode))) + { + if (SysId.ToInt(dataItemId) != SysId.ToInt(userSecurityRight.DataItemId)) continue; + return !userSecurityRight.Granted; + } + return false; + } + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Security/SaSecurityRight.cs b/SacsStandardBase/Base/Security/SaSecurityRight.cs new file mode 100644 index 0000000..5a2a4c9 --- /dev/null +++ b/SacsStandardBase/Base/Security/SaSecurityRight.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Security +{ + //======================================================================= + /// Definition of security right as required by application + public class SaSecurityRight + { + //------------------------------------------------------------------- + // Construction. + public SaSecurityRight (String code, String desc, int dataItemType = 0) + { + Code = code; + Desc = desc; + DataItemType = dataItemType; + } + //------------------------------------------------------------------- + // Properties. + public String Code { get; set; } + public String Desc { get; set; } + public int DataItemType { get; set; } + + //------------------------------------------------------------------- + } + + //======================================================================= + public static class SaSecurityRightList + { + public static void Add (this List securityRights, String code, String desc, int dataItemType = 0) + { + securityRights.Add(new SaSecurityRight(code, desc, dataItemType)); + } + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Tasks/SacsTaskData.cs b/SacsStandardBase/Base/Tasks/SacsTaskData.cs new file mode 100644 index 0000000..c82afe3 --- /dev/null +++ b/SacsStandardBase/Base/Tasks/SacsTaskData.cs @@ -0,0 +1,76 @@ +using Sarand.Net.Standard.Base.Core; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace Sarand.Net.Standard.Base.Tasks +{ + //======================================================================= + public class SacsTaskData : SacsDisposable + { + //------------------------------------------------------------------- + // Data members. + // Cancellation token source - allows task cancellation to be triggered + // if the cancellation tokden wasn't supplied on construction. + private CancellationTokenSource cancellationTokenSource = null; + + //------------------------------------------------------------------- + // Construction. + public SacsTaskData (CancellationToken? cancellationToken) + { + if (cancellationToken != null) + { + // Cancell token supplied on construction + // - cancellation is controlled by a higher level task. + CancellationToken = (CancellationToken)cancellationToken; + } + else + { + // Cancellation token not supplied - cancellation can be triggered here. + cancellationTokenSource = new CancellationTokenSource(); + CancellationToken = cancellationTokenSource.Token; + } + } + protected override void Dispose (bool disposing) + { + if (disposing) + { + if (cancellationTokenSource != null) + { + cancellationTokenSource.Cancel(); + cancellationTokenSource.Dispose(); + } + cancellationTokenSource = null; + } + } + + //------------------------------------------------------------------- + // Properties. + public bool Debug { get; set; } + + //------------------------------------------------------------------- + // C a n c e l l a t i o n + //------------------------------------------------------------------- + /// Token to allow the async request to be cancelled. + public CancellationToken CancellationToken { get; } + /// Gets whether cancellation has been requested. + public bool IsCancellationRequested { get { return CancellationToken.IsCancellationRequested; } } + /// Throws a System.OperationCanceledException if this token has had cancellation requested. + public void ThrowIfCancellationRequested() + { + CancellationToken.ThrowIfCancellationRequested(); + } + /// Can cancellation be triggered here? + public bool CanBeCancelled { get { return cancellationTokenSource != null; } } + public bool TriggerCancellation () + { + if (!CanBeCancelled) return false; + cancellationTokenSource.Cancel(); + return true; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/UpdateLog/SaUpdateLogConsole.cs b/SacsStandardBase/Base/UpdateLog/SaUpdateLogConsole.cs new file mode 100644 index 0000000..479a6e1 --- /dev/null +++ b/SacsStandardBase/Base/UpdateLog/SaUpdateLogConsole.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; + +namespace Sarand.Net.Standard.Base.UpdateLog +{ + //======================================================================= + // Definitions. + + //======================================================================= + public abstract class SaUpdateLogConsole : SaErrorStateItem + { + //------------------------------------------------------------------- + // Definitions. + + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Static initialisation + static SaUpdateLogConsole () + { + } + + //------------------------------------------------------------------- + // Protected construction - use derived classes. + protected SaUpdateLogConsole () + { + } + + //------------------------------------------------------------------- + // Properties. + public abstract bool DatabaseIsValid { get; } + public abstract bool CanUpdateDatabase { get; } + + //------------------------------------------------------------------- + // Utilities - retrieve application data. + public abstract Dictionary GetNameForUserIds (List userIds); + + //------------------------------------------------------------------- + // Update Log Form. + public class ShowUpdateLogConfig + { + public ShowUpdateLogConfig(ItemId itemId, String itemCaption, DbUpdateInfo dbUpdateInfo) + { + ItemId = itemId; + ItemCaption = itemCaption; + DbUpdateInfo = dbUpdateInfo; + } + public ItemId ItemId { get; } + public String ItemCaption { get; } + public DbUpdateInfo DbUpdateInfo { get; } + } + public abstract bool ShowUpdateLogDialog (ShowUpdateLogConfig config); + + //******************************************************************* + // U p d a t e L o g E n t r i e s + //******************************************************************* + // Log updates. + public abstract bool LogUpdates (SaUpdateLogItemMonitor updateLogItemMonitor); + + //------------------------------------------------------------------- + // Retrieve list of updates that match the specified params: + public abstract bool ItemHasLogEntries (ItemId itemId); + public abstract bool LogEntriesExist (SaUpdateLogEntry.Params prmUpdateLog); + public abstract SaUpdateLogEntry GetLogEntry (ItemId itemId, String attrName, DateTime loggedAt); + public abstract long GetLogEntryCount (SaUpdateLogEntry.Params prmUpdateLog); + public abstract List GetLogEntries (SaUpdateLogEntry.Params prmUpdateLog); + + //------------------------------------------------------------------- + // Create a new item data. + public abstract SaUpdateLogEntry CreateLogEntry (ItemId itemId, String attrName); + + //------------------------------------------------------------------- + // Store item data. + public abstract bool StoreLogEntry (SaUpdateLogEntry logEntry, DbUpdateInfo dbUpdateInfo); + public abstract bool RemoveLogEntry (SaUpdateLogEntry logEntry, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearLogEntriesForItem (ItemId itemId, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/UpdateLog/SaUpdateLogDbDfn.cs b/SacsStandardBase/Base/UpdateLog/SaUpdateLogDbDfn.cs new file mode 100644 index 0000000..879c798 --- /dev/null +++ b/SacsStandardBase/Base/UpdateLog/SaUpdateLogDbDfn.cs @@ -0,0 +1,320 @@ +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.Linq; + +//--------------------------------------------------------------------------- +// Code generated by Sarand Database Utilities Thu 3-Oct-2019 15:11:05 +//--------------------------------------------------------------------------- +namespace Sarand.Net.Standard.Base.UpdateLog +{ + public class SaUpdateLogDbDfn + { + //------------------------------------------------------------------- + // Table identifiers. + public enum TableIdx + { + Unknown = 0, + SaAttrUpdate, + } + + //------------------------------------------------------------------- + // Column identifiers. + public enum ColumnIdx + { + Unknown = 0, + SaAttrUpdate_ItemType, + SaAttrUpdate_ItemId, + SaAttrUpdate_Logged, + SaAttrUpdate_Attribute, + SaAttrUpdate_DataBefore, + SaAttrUpdate_DataAfter, + SaAttrUpdate_Notes, + SaAttrUpdate_Flags, + SaAttrUpdate_Status, + SaAttrUpdate_CreateOn, + SaAttrUpdate_CreateBy, + SaAttrUpdate_CreateHost, + SaAttrUpdate_UpdateOn, + SaAttrUpdate_UpdateBy, + SaAttrUpdate_UpdateHost, + } + + //------------------------------------------------------------------- + // Data members. + private static readonly int tableCount = 1; + private static readonly int columnCount = 15; + 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) + { + SaUpdateLogDbDfn.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) + { + SaUpdateLogDbDfn.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 = + { + "SaAttrUpdate", + }; + + //------------------------------------------------------------------- + // 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,"ItemType",3,5,false,5), + new ColumnDfn(1,"ItemId",4,4,false,10), + new ColumnDfn(1,"Logged",9,93,false,23), + new ColumnDfn(1,"Attribute",0,12,false,32), + new ColumnDfn(1,"DataBefore",0,-9,false,1024), + new ColumnDfn(1,"DataAfter",0,-9,false,1024), + new ColumnDfn(1,"Notes",0,-9,false,255), + new ColumnDfn(1,"Flags",3,5,false,5), + new ColumnDfn(1,"Status",0,12,false,3), + new ColumnDfn(1,"CreateOn",9,93,false,23), + new ColumnDfn(1,"CreateBy",4,4,false,10), + new ColumnDfn(1,"CreateHost",0,12,false,64), + new ColumnDfn(1,"UpdateOn",9,93,false,23), + new ColumnDfn(1,"UpdateBy",4,4,false,10), + new ColumnDfn(1,"UpdateHost",0,12,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/UpdateLog/SaUpdateLogEntry.cs b/SacsStandardBase/Base/UpdateLog/SaUpdateLogEntry.cs new file mode 100644 index 0000000..520af1b --- /dev/null +++ b/SacsStandardBase/Base/UpdateLog/SaUpdateLogEntry.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.UpdateLog +{ + //======================================================================= + public abstract class SaUpdateLogEntry : SacsHierarchyItem + { + //------------------------------------------------------------------- + // Columns. + public enum Column + { + Pos = 0, + // Log Entry properties. + ItemType, + ItemType_Desc, + ItemId, + Attribute, + Location, + Logged, + Logged_Date, + Logged_Time, + DataBefore, + DataAfter, + // Common properties. + Notes, + Status, + Status_Desc, + Status_Ind, + CreateOn_Date, + CreateBy, + CreateBy_Name, + UpdateOn_Date, + Hidden, + } + + //------------------------------------------------------------------- + /// Properties other than common. + public class DataProperties + { + public ItemId ItemId { get; set; } = null; + public String Attribute { get; set; } = String.Empty; + public DateTime Logged { get; set; } = DateTime.MinValue; + public String DataBefore { get; set; } = String.Empty; + public String DataAfter { 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 ItemId ItemId + { + get { return data.ItemId; } + set { CheckChanged(data.ItemId, value); data.ItemId = value; } + } + public String Attribute + { + get { return data.Attribute; } + set { CheckChanged(data.Attribute, value); data.Attribute = value; } + } + public DateTime Logged + { + get { return data.Logged; } + set { CheckChanged(data.Logged, value); data.Logged = value; } + } + public String DataBefore + { + get { return data.DataBefore; } + set { CheckChanged(data.DataBefore, value); data.DataBefore = value; } + } + public String DataAfter + { + get { return data.DataAfter; } + set { CheckChanged(data.DataAfter, value); data.DataAfter = value; } + } + // Population. + internal void PopulateFrom (Data data) + { + ItemId = data.ItemId; + Attribute = data.Attribute; + Logged = data.Logged; + DataBefore = data.DataBefore; + DataAfter = data.DataAfter; + 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 ItemType { get; set; } = ValidValue.Valid; + public ValidValue ItemId { get; set; } = ValidValue.Valid; + public ValidValue Attribute { get; set; } = ValidValue.Valid; + public ValidValue Logged { get; set; } = ValidValue.Valid; + public ValidValue DataBefore { get; set; } = ValidValue.Valid; + public ValidValue DataAfter { get; set; } = ValidValue.Valid; + } + + //------------------------------------------------------------------- + // Data members. + private readonly Data data = new Data(); + + //------------------------------------------------------------------- + // Construction. + public SaUpdateLogEntry (ItemId itemId, String attribute, DateTime logged) : + base() + { + base.SetData(data); + ItemId = itemId; + Attribute = attribute; + Logged = logged; + Status = DefaultStatus; + } + + //------------------------------------------------------------------- + // Statuses. + private static readonly List validStatuses = new List() + { + SaStatus.Logged, + SaStatus.Deleted + }; + public static SaStatus DefaultStatus { get { return LoggedStatus; } } + public static SaStatus LoggedStatus { get { return SaStatus.Logged; } } + public static List ValidStatuses { get { return new List(validStatuses); } } + public static DataValue.TriStateType GetStatusTriState (SaStatus status) + { + switch (status) + { + case SaStatus.Logged: + return DataValue.TriStateType.Green; + case SaStatus.Deleted: + return DataValue.TriStateType.Purple; + } + return 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 (ItemId.Type >= 0 && ItemId.Type != data.ItemId.Type) + throw new NotSupportedException(fn + " Item type cannot be changed"); + if (SysId.IsValid(ItemId.Id) && SysId.ToInt(ItemId.Id) != SysId.ToInt(data.ItemId.Id)) + throw new NotSupportedException(fn + " Item Id cannot be changed"); + if (!StringUtil.IsEmpty(Attribute) && !Attribute.Equals(data.Attribute)) + throw new NotSupportedException(fn + " Attribute cannot be changed"); + if (Logged != DateTime.MinValue && !Logged.Equals(data.Logged)) + throw new NotSupportedException(fn + " Logged date/time cannot be changed"); + // Populate individual properties to ensure that 'Changed' indicator is kept up to date. + this.data.PopulateFrom(data); + } + public ItemId ItemId + { + get { return data.ItemId; } + protected set { data.ItemId = value; } + } + public String Attribute + { + get { return data.Attribute; } + protected set { data.Attribute = value; } + } + public DateTime Logged + { + get { return data.Logged; } + protected set { data.Logged = value; } + } + public String DataBefore + { + get { return data.DataBefore; } + set { data.DataBefore = value; } + } + public String DataAfter + { + get { return data.DataAfter; } + set { data.DataAfter = value; } + } + + //------------------------------------------------------------------- + // List parameters. + /// + /// Item list parameters. + /// + public new class Params : SacsHierarchyItem.Params + { + public List IncludedItemIds { get; } = new List(); + public List IncludedAttributes { get; } = new List(); + public DateTime Logged { get; set; } = DateTime.MinValue; + public DateTime LoggedFrom { get; set; } = DateTime.MinValue; + public DateTime LoggedTo { get; set; } = DateTime.MinValue; + } + + //------------------------------------------------------------------- + // L i s t + //------------------------------------------------------------------- + public class List : List + { + } + + //------------------------------------------------------------------- + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/UpdateLog/SaUpdateLogItemMonitor.cs b/SacsStandardBase/Base/UpdateLog/SaUpdateLogItemMonitor.cs new file mode 100644 index 0000000..6be3c00 --- /dev/null +++ b/SacsStandardBase/Base/UpdateLog/SaUpdateLogItemMonitor.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.UpdateLog +{ + //======================================================================= + public class SaUpdateLogItemMonitor : SacsStateItem + { + //------------------------------------------------------------------- + // Configuration. + public class Config + { + public Config (SaUpdateLogConsole updateLogConsole, DbUpdateInfo dbUpdateInfo) + { + UpdateLogConsole = updateLogConsole; + DbUpdateInfo = dbUpdateInfo; + } + public SaUpdateLogConsole UpdateLogConsole { get; } + public short ItemType { get; set; } + public SysId ItemId { get; set; } + public DbUpdateInfo DbUpdateInfo { get; } + } + + //------------------------------------------------------------------- + // Definitions. + public class AttrData + { + public AttrData (String name) + { + Name = name; + } + public String Name { get; private set; } + public DataValue ValueBefore { get; internal set; } = new DataValue(); + public DataValue ValueAfter { get; internal set; } = new DataValue(); + } + + //------------------------------------------------------------------- + // Data members. + private Config config = null; + private readonly Dictionary attrUpdateMap = new Dictionary(); + + //------------------------------------------------------------------- + // Static initialisation + static SaUpdateLogItemMonitor () + { + } + + //------------------------------------------------------------------- + // Protected construction - use derived classes. + protected SaUpdateLogItemMonitor (Config config) + { + this.config = config; + } + + //------------------------------------------------------------------- + // Properties. + public short ItemType { get { return config.ItemType; } } + public SysId ItemId { get { return config.ItemId; } } + public List AttrUpdates + { + get + { + // Compile list of attributes whose value has changed. + List attrUpdates = new List(); + foreach (AttrData attrData in attrUpdateMap.Values) + { + if (!FormatDataValue(attrData.ValueBefore).Equals(FormatDataValue(attrData.ValueAfter))) + { + // Data values are ppre-formatted as Strings for database storage. + attrUpdates.Add(new AttrData(attrData.Name) { ValueBefore=new DataValue(FormatDataValue(attrData.ValueBefore)), ValueAfter= new DataValue(FormatDataValue(attrData.ValueAfter)) }); + } + } + return attrUpdates; + } + } + public DbUpdateInfo DbUpdateInfo { get { return config.DbUpdateInfo; } } + + //------------------------------------------------------------------- + // Log updates. + public bool LogUpdates () + { + ClearErrorState(); + if (!config.UpdateLogConsole.LogUpdates(this)) + SetErrorState(config.UpdateLogConsole.ErrorStateMessage); + return !HasErrorState(); + } + protected virtual bool IsColumnChangeLogged (String attrName) + { + if (!config.UpdateLogConsole.CanUpdateDatabase) return false; + return true; + } + + //------------------------------------------------------------------- + // Populate attribute values. + protected bool RecordAttrValue (String attrName, String value, bool newValue) + { + return RecordAttrValue(attrName, new DataValue(value), newValue); + } + protected bool RecordAttrValue (String attrName, int value, bool newValue, bool blankZeroValue = true) + { + return RecordAttrValue(attrName, new DataValue(value == 0 && blankZeroValue ? "" : value.ToString()), newValue); + } + protected bool RecordAttrValue (String attrName, decimal value, bool newValue, bool blankZeroValue = true, ushort decimals = 2) + { + return RecordAttrValue(attrName, new DataValue(value == 0.0m && blankZeroValue ? "" : value.ToString("N" + decimals.ToString())), newValue); + } + protected bool RecordAttrValue (String attrName, DateTime value, bool newValue, bool includeTime = true) + { + return RecordAttrValue(attrName, new DataValue(DateUtil.Format(value, (includeTime ? "d-MMM-yyyy HH:mm" : "d-MMM-yyyy"))), newValue); + } + protected bool RecordAttrValue (String attrName, DataValue value, bool newValue) + { + ClearErrorState(); + if (attrName.IsEmpty()) return SetErrorState("Blank attribute name ["+GetType().Name+"]"); + if (!IsColumnChangeLogged(attrName)) return true; + if (newValue) + SetAttrValueAfter(attrName, value); + else + SetAttrValueBefore(attrName, value); + return !HasErrorState(); + } + public void SetAttrValueBefore (String attrName, DataValue value) + { + AttrData attrData = (attrUpdateMap.ContainsKey(attrName) ? attrUpdateMap[attrName] : null); + if (attrData == null) + { + attrData = new AttrData(attrName); + attrUpdateMap[attrName] = attrData; + } + attrData.ValueBefore = new DataValue(value); + } + public void SetAttrValueAfter (String attrName, DataValue value) + { + AttrData attrData = (attrUpdateMap.ContainsKey(attrName) ? attrUpdateMap[attrName] : null); + if (attrData == null) + { + attrData = new AttrData(attrName); + attrUpdateMap[attrName] = attrData; + } + attrData.ValueAfter = new DataValue(value); + } + + //------------------------------------------------------------------- + // Format data value for database storage. + private static String FormatDataValue (DataValue dataValue) + { + String value = (dataValue != null ? dataValue.ToString() : ""); + StringUtil.ReplaceAll(value, "\t", " "); + StringUtil.ReplaceAll(value, "\n\r", "\n"); + StringUtil.ReplaceAll(value, "\r\n", "\n"); + StringUtil.ReplaceAll(value, "\r", " "); + StringUtil.ReplaceAll(value, "\n", " "); + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/UpdateLog/SaUpdateLogMgr.cs b/SacsStandardBase/Base/UpdateLog/SaUpdateLogMgr.cs new file mode 100644 index 0000000..9379cda --- /dev/null +++ b/SacsStandardBase/Base/UpdateLog/SaUpdateLogMgr.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Dbas; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.Net.Standard.Base.UpdateLog +{ + //======================================================================= + public abstract class SaUpdateLogMgr : SacsItemMgr + { + //------------------------------------------------------------------- + // Data members. + private readonly Dictionary userNames = new Dictionary(); + + //------------------------------------------------------------------- + // Static initialisation. + static SaUpdateLogMgr () + { + } + + //------------------------------------------------------------------- + // Construction. + protected SaUpdateLogMgr (SaUpdateLogConsole updateLogConsole) + { + UpdateLogConsole = updateLogConsole; + } + + //------------------------------------------------------------------- + // Properties. + protected SaUpdateLogConsole UpdateLogConsole { get; } + public abstract bool DatabaseIsValid { get; } + public abstract bool CanUpdateDatabase { get; } + + //******************************************************************* + // U p d a t e L o g E n t r i e s + //******************************************************************* + // Retrieve individual update log entries. + public abstract bool LogEntryExists (SaUpdateLogEntry logEntry); + public abstract bool LogEntryExists (ItemId itemId, String attribute, DateTime logged); + public abstract SaUpdateLogEntry GetLogEntry (ItemId itemId, String attribute, DateTime logged); + + // Create Update Log Entry. + public abstract SaUpdateLogEntry CreateLogEntry (ItemId itemId, String attribute); + + //------------------------------------------------------------------- + // Validate Update Log Entry. + public bool IsValidLogEntry (SaUpdateLogEntry logEntry) + { + ClearErrorState(); + if (logEntry == null) return SetErrorState(ErrorMessage.ItemNotSpecified); + return IsValidLogEntryData(logEntry.GetData(), new SaUpdateLogEntry.ErrField()); + } + public virtual bool IsValidLogEntryData (SaUpdateLogEntry.Data logEntryData, SaUpdateLogEntry.ErrField errField) + { + ClearErrorState(); + if ((int)logEntryData.ItemId.Type < 1) + { + errField.ItemType = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Item type must be supplied"); + } + if (!SysId.IsValid(logEntryData.ItemId.Id)) + { + errField.ItemId = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState("Item Id must be supplied"); + } + if (!IsValidValue(logEntryData.Attribute, "attribute", (long)SaUpdateLogDbDfn.GetColTextLen(SaUpdateLogDbDfn.ColumnIdx.SaAttrUpdate_Attribute), true)) + { + errField.Attribute = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!IsValidValue(logEntryData.DataBefore, "data before", (long)SaUpdateLogDbDfn.GetColTextLen(SaUpdateLogDbDfn.ColumnIdx.SaAttrUpdate_DataBefore))) + { + errField.DataBefore = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!IsValidValue(logEntryData.DataAfter, "data after", (long)SaUpdateLogDbDfn.GetColTextLen(SaUpdateLogDbDfn.ColumnIdx.SaAttrUpdate_DataAfter))) + { + errField.DataAfter = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + // Common properties. + if (!IsValidValue(logEntryData.Notes, "notes", (long)SaUpdateLogDbDfn.GetColTextLen(SaUpdateLogDbDfn.ColumnIdx.SaAttrUpdate_Notes))) + { + errField.Notes = SacsHierarchyItem.ValidValue.Invalid; + return false; + } + if (!SaUpdateLogEntry.ValidStatuses.Contains(logEntryData.Status)) + { + errField.Status = SacsHierarchyItem.ValidValue.Invalid; + return SetErrorState(ErrorMessage.InvalidStatus); + } + return true; + } + + //------------------------------------------------------------------- + // Update Log Entry database interface. + public abstract bool LogUpdates (SaUpdateLogItemMonitor updateLogItemMonitor, DbUpdateInfo dbUpdateInfo); + public abstract bool InsertLogEntry (SaUpdateLogEntry logEntry, DbUpdateInfo dbUpdateInfo); + public abstract bool UpdateLogEntry (SaUpdateLogEntry logEntry, DbUpdateInfo dbUpdateInfo); + public virtual bool LogEntryCanBeDeleted (SaUpdateLogEntry logEntry) + { + ClearErrorState(); + return true; + } + public abstract bool DeleteLogEntry (SaUpdateLogEntry logEntry, DbUpdateInfo dbUpdateInfo); + public abstract bool ClearLogEntriesForItem (ItemId itemId, DbUpdateInfo dbUpdateInfo); + + //------------------------------------------------------------------- + // Update Log Entry list. + public abstract ColumnDfn GetLogEntryColumnDfn (SaUpdateLogEntry.Column columnId, String caption = "", int width = 0); + public abstract List GetLogEntryQueryFilter (SaUpdateLogEntry.Params prm); + public abstract long GetLogEntryCount (SaUpdateLogEntry.Params prm); + public abstract List GetLogEntryList (SaUpdateLogEntry.Params prm); + public abstract DbResultSet ExecuteLogEntryQuery (List columns, SaUpdateLogEntry.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 GetLogEntryDataValue (ColumnDfn column, NamedValue.Map values) + { + DataValue value = new DataValue(); + switch ((SaUpdateLogEntry.Column)column.Id) + { + //----------------------------------------------------------- + // Item type. + case SaUpdateLogEntry.Column.ItemType_Desc: + { + short itemType = (short)values.GetValue(column.DataName).AsLong(); + value = new DataValue(itemType.ToString()); + } + break; + + //----------------------------------------------------------- + // Data values. + case SaUpdateLogEntry.Column.DataBefore: + case SaUpdateLogEntry.Column.DataAfter: + { + value = new DataValue(values.GetValue(column.DataName).ToString()); + String attribute = values.GetValue(GetLogEntryColumnDfn(SaUpdateLogEntry.Column.Attribute).DataName).ToString(); + if (attribute.EqualsCI("Status")) + value = new DataValue(SacsSystem.GetStatusDescForCode(value.ToString())); + } + break; + + //----------------------------------------------------------- + // Notes. + case SaUpdateLogEntry.Column.Notes: + { + value = new DataValue(SacsHierarchyItem.FormatForDisplay(values.GetValue(column.DataName).ToString(), 200)); + } + break; + + //----------------------------------------------------------- + // Status. + case SaUpdateLogEntry.Column.Status_Desc: + case SaUpdateLogEntry.Column.Status_Ind: + { + String statusCode = values.GetValue(column.DataName).ToString(); + SaStatus status = SacsSystem.GetStatusForCode(statusCode); + if (column.Id == (int)SaUpdateLogEntry.Column.Status_Desc) + { + String statusDesc = SacsSystem.GetStatusDescForCode(statusCode); + value = new DataValue(statusDesc); + } + else if (column.Id == (int)SaUpdateLogEntry.Column.Status_Ind) + { + DataValue.TriStateType triState = DataValue.TriStateType.Unknown; + if (status == SaStatus.Logged) + triState = DataValue.TriStateType.Green; + value = new DataValue(triState); + } + } + break; + + //----------------------------------------------------------- + // Notes. + case SaUpdateLogEntry.Column.CreateBy_Name: + { + SysId userId = new SysId(StringUtil.ToInt(values.GetValue(GetLogEntryColumnDfn(SaUpdateLogEntry.Column.CreateBy).DataName).ToString())); + String userName = SysId.ToString(userId); + if (SysId.IsValid(userId) && !userNames.ContainsKey(userId)) + { + foreach (var userNameKV in UpdateLogConsole.GetNameForUserIds(new List { userId })) + userNames.Add(userNameKV.Key, userNameKV.Value); + } + if (userNames.ContainsKey(userId)) + userName = userNames[userId]; + value = new DataValue(userName); + } + break; + + //----------------------------------------------------------- + } + return value; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/ArrayUtil.cs b/SacsStandardBase/Base/Utils/ArrayUtil.cs new file mode 100644 index 0000000..3a95917 --- /dev/null +++ b/SacsStandardBase/Base/Utils/ArrayUtil.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class ArrayUtil + { + //------------------------------------------------------------------- + /// Concatenate 2 arrays. + public static T[] ConcatArrays (T[] array1, T[] array2) + { + T[] newArray = new T[array1.Length + array2.Length]; + Array.Copy(array1, 0, newArray, 0, array1.Length); + Array.Copy(array2, 0, newArray, array1.Length, array2.Length); + return newArray; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/AssemblyUtil.cs b/SacsStandardBase/Base/Utils/AssemblyUtil.cs new file mode 100644 index 0000000..64b60e2 --- /dev/null +++ b/SacsStandardBase/Base/Utils/AssemblyUtil.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class AssemblyUtil + { + //------------------------------------------------------------------- + #region Gets the build date and time (by reading the COFF header) + // http://msdn.microsoft.com/en-us/library/ms680313 + + /* + struct _IMAGE_FILE_HEADER + { + public ushort Machine; + public ushort NumberOfSections; + public uint TimeDateStamp; + public uint PointerToSymbolTable; + public uint NumberOfSymbols; + public ushort SizeOfOptionalHeader; + public ushort Characteristics; + }; + */ + + static DateTime GetBuildDateTime (Assembly assembly) + { + /* + // Not available until .Net Standard 1.5. + var path = assembly.GetName().CodeBase; + if (File.Exists(path)) + { + var buffer = new byte[Math.Max(Marshal.SizeOf(typeof(_IMAGE_FILE_HEADER)), 4)]; + using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + fileStream.Position = 0x3C; + fileStream.Read(buffer, 0, 4); + fileStream.Position = BitConverter.ToUInt32(buffer, 0); // COFF header offset + fileStream.Read(buffer, 0, 4); // "PE\0\0" + fileStream.Read(buffer, 0, buffer.Length); + } + var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned); + try + { + var coffHeader = (_IMAGE_FILE_HEADER)Marshal.PtrToStructure(pinnedBuffer.AddrOfPinnedObject(), typeof(_IMAGE_FILE_HEADER)); + + return TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1) + new TimeSpan(coffHeader.TimeDateStamp * TimeSpan.TicksPerSecond)); + } + finally + { + pinnedBuffer.Free(); + } + } + */ + return new DateTime(); + } + #endregion + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/DateUtil.cs b/SacsStandardBase/Base/Utils/DateUtil.cs new file mode 100644 index 0000000..75b8629 --- /dev/null +++ b/SacsStandardBase/Base/Utils/DateUtil.cs @@ -0,0 +1,651 @@ +using Sarand.Net.Standard.Base.App; +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class DateUtil + { + //------------------------------------------------------------------------- + // Definitions. + public static String Format_XmlDate { get; } = "yyyy-MM-dd"; + public static String Format_XmlTime { get; } = "HH:mm:ss"; + public static String Delim_XmlTime { get; } = "T"; + public static String Format_XmlDateTime { get; } = Format_XmlDate + Delim_XmlTime + Format_XmlTime; + + //------------------------------------------------------------------------- + // P o p u l a t i o n + //------------------------------------------------------------------------- + /// Combine date and time. + public static DateTime SetTime (this DateTime value, DateTime time) + { + if (!IsValid(value)) return value; + return value.Date.Add(time.TimeOfDay); + } + /// Combine date and time. + public static DateTime SetTime (this DateTime value, TimeSpan time) + { + if (!IsValid(value)) return value; + return value.Date + time; + } + + //------------------------------------------------------------------------- + public static DateTime ClearSeconds (this DateTime value) + { + return new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, 0, value.Kind); + } + + //------------------------------------------------------------------------- + public enum RoundingDirection + { + /// Set to next minutes interval if past previous. + Up, + /// Set to previous minutes interval if not reached next. + Down, + /// Set to nearest minutes interval. + Nearest, + } + /// + /// Roud the time portion of the value to the appropriate multiple of the interval minutes. + /// Seconds can be included in the algorythm but are cleared in the return value. + /// Interval minutes are capped at 60. + /// + public static DateTime RoundTime (this DateTime value, ushort intervalMinutes, RoundingDirection direction, bool ignoreSeconds = true) + { + intervalMinutes = (ushort)Math.Min(60, (int)intervalMinutes); + // Number of minutes over the interval. + double minsDifference = (value.Minute % intervalMinutes) + (ignoreSeconds ? 0.0 : (value.Second / 60.0)); + // Value rounded down to the interval minutes. + DateTime newValue = value.Date + new TimeSpan(value.Hour, value.Minute - (value.Minute % intervalMinutes), 0); + switch (direction) + { + case RoundingDirection.Up: + if (minsDifference > 0.0) + newValue += new TimeSpan(0, intervalMinutes, 0); + break; + case RoundingDirection.Down: + // Nothng to do. + break; + case RoundingDirection.Nearest: + if (minsDifference > (intervalMinutes / 2.0)) + newValue += new TimeSpan(0, intervalMinutes, 0); + break; + } + return newValue; + } + + //------------------------------------------------------------------------- + // C o n v e r t F r o m S t r i n g + //------------------------------------------------------------------------- + /// + /// Parse date value. + /// Format: eg. 'yyyyMMdd', 'dd/MM/yyyy' + /// + public static DateTime ParseExact (String value, String format) + { + try + { + return DateTime.ParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.None); + } + catch (Exception) { } + return DateTime.MinValue; + } + //------------------------------------------------------------------------- + /// Parse XML date: 'yyyy-MM-dd' + public static DateTime ParseXmlDate (String value) + { + return ParseExact(value, Format_XmlDate); + } + /// Parse XML time: 'HH:mm:ss' + public static DateTime ParseXmlTime (String value) + { + return ParseExact(value, Format_XmlTime); + } + /// Parse XML datetime: 'yyyy-MM-ddTHH:mm:ss' + public static DateTime ParseXmlDateTime (String value) + { + return ParseExact(value, Format_XmlDateTime); + } + + //------------------------------------------------------------------------- + // V a l i d a t i o n + //------------------------------------------------------------------------- + /// Vaidate that date is between MinDate & MaxDate. + public static bool IsValid (this DateTime value) + { + if (value <= DateTime.MinValue) return false; + if (value >= DateTime.MaxValue) return false; + return true; + } + /// Vaidate that date is between MinDate & MaxDate and after null date. + public static bool IsValid (this DateTime value, DateTime nullDate) + { + if (!IsValid(value)) return false; + if (value <= nullDate) return false; + return true; + } + /// Vaidate that date is not null and between MinDate & MaxDate. + public static bool IsValid (this DateTime? value) + { + return (value != null && IsValid((DateTime)value)); + } + + //------------------------------------------------------------------------- + // I n v a l i d + //------------------------------------------------------------------------- + public static DateTime OnInvalid (this DateTime value, DateTime defaultValue) + { + if (!IsValid(value)) return defaultValue; + return value; + } + + //------------------------------------------------------------------------- + // C o m p a r i s o n + //------------------------------------------------------------------------- + public static DateTime Min (DateTime time1, DateTime time2) + { + return new DateTime(Math.Min(time1.Ticks, time2.Ticks), time1.Kind); + } + public static DateTime Max (DateTime time1, DateTime time2) + { + return new DateTime(Math.Max(time1.Ticks, time2.Ticks), time1.Kind); + } + + //------------------------------------------------------------------------- + // P e r i o d s + //------------------------------------------------------------------------- + public enum Period + { + Period, + Day, + Today, + Yesterday, + Week, + WeekCurrent, + WeekPrevious, + WorkingWeek, + WorkingWeekCurrent, + WorkingWeekPrevious, + Month, + MonthCurrent, + MonthPrevious, + Quarter, + QuarterCurrent, + QuarterPrevious, + Year, + YearCurrent, + YearPrevious, + } + public static String GetPeriodCaption (Period period) + { + switch (period) + { + case Period.Period: + return "Period"; + case Period.Day: + return "Day"; + case Period.Today: + return "Today"; + case Period.Yesterday: + return "Yesterday"; + case Period.Week: + return "Week"; + case Period.WeekCurrent: + return "This week"; + case Period.WeekPrevious: + return "Last week"; + case Period.WorkingWeek: + return "Working week"; + case Period.WorkingWeekCurrent: + return "This working week"; + case Period.WorkingWeekPrevious: + return "Last working week"; + case Period.Month: + return "Month"; + case Period.MonthCurrent: + return "This month"; + case Period.MonthPrevious: + return "Last month"; + case Period.Quarter: + return "Quarter"; + case Period.QuarterCurrent: + return "This quarter"; + case Period.QuarterPrevious: + return "Last quarter"; + case Period.Year: + return "Year"; + case Period.YearCurrent: + return "This year"; + case Period.YearPrevious: + return "Last year"; + } + return period.ToString(); + } + public static bool IsPeriodFromFixed (Period period) + { + switch (period) + { + case Period.Period: + case Period.Day: + case Period.Week: + case Period.WorkingWeek: + case Period.Month: + case Period.Quarter: + case Period.Year: + return false; + } + return true; + } + public static bool IsPeriodToFixed (Period period) + { + switch (period) + { + case Period.Period: + return false; + } + return true; + } + public class ComboBoxItemPeriod + { + public DateUtil.Period Period { get; } + public ComboBoxItemPeriod (DateUtil.Period period) + { + Period = period; + } + public override String ToString () + { + return GetPeriodCaption(Period); + } + // Comparison. + public override int GetHashCode () + { + return 23 + Period.GetHashCode(); + } + public override bool Equals (object obj) + { + if (obj is ComboBoxItemPeriod) return Equals((ComboBoxItemPeriod)obj); + if (obj is Period) return Equals((Period)obj); + return base.Equals(obj); + } + public bool Equals (ComboBoxItemPeriod item) + { + return (Period == item.Period); + } + public bool Equals (Period period) + { + return (Period == period); + } + } + public static DateTime StartOfPeriod (Period period, DateTime from, DateTime to) + { + switch (period) + { + case Period.Period: + return from; + case Period.Day: + return from; + case Period.Today: + return DateTime.Today; + case Period.Yesterday: + return DateTime.Today.AddDays(-1); + case Period.Week: + return from.StartOfWeek(); + case Period.WeekCurrent: + return DateTime.Today.StartOfWeek(); + case Period.WeekPrevious: + return DateTime.Today.StartOfWeek().AddDays(-7); + case Period.WorkingWeek: + return from.StartOfWorkingWeek(); + case Period.WorkingWeekCurrent: + return DateTime.Today.StartOfWorkingWeek(); + case Period.WorkingWeekPrevious: + return DateTime.Today.StartOfWorkingWeek().AddDays(-7); + case Period.Month: + return from.StartOfMonth(); + case Period.MonthCurrent: + return DateTime.Today.StartOfMonth(); + case Period.MonthPrevious: + return DateTime.Today.StartOfMonth().AddMonths(-1); + case Period.Quarter: + return from.StartOfQuarter(); + case Period.QuarterCurrent: + return DateTime.Today.StartOfQuarter(); + case Period.QuarterPrevious: + return DateTime.Today.StartOfQuarter().AddMonths(-3); + case Period.Year: + return from.StartOfYear(); + case Period.YearCurrent: + return DateTime.Today.StartOfYear(); + case Period.YearPrevious: + return DateTime.Today.StartOfYear().AddYears(-1); + } + return DateTime.Today; + } + public static DateTime EndOfPeriod (Period period, DateTime from, DateTime to) + { + DateTime startOfPeriod = StartOfPeriod(period,from,to); + switch (period) + { + case Period.Period: + if (to.CompareTo(from) < 0) return from; + return to; + case Period.Day: + case Period.Today: + case Period.Yesterday: + return startOfPeriod; + case Period.Week: + case Period.WeekCurrent: + case Period.WeekPrevious: + case Period.WorkingWeek: + case Period.WorkingWeekCurrent: + case Period.WorkingWeekPrevious: + return startOfPeriod.AddDays(6); + case Period.Month: + case Period.MonthCurrent: + case Period.MonthPrevious: + return startOfPeriod.AddMonths(1).AddDays(-1); + case Period.Quarter: + case Period.QuarterCurrent: + case Period.QuarterPrevious: + return startOfPeriod.AddMonths(3).AddDays(-1); + case Period.Year: + case Period.YearCurrent: + case Period.YearPrevious: + return startOfPeriod.AddYears(1).AddDays(-1); + } + return DateTime.Today; + } + + //------------------------------------------------------------------- + // R e l a t i v e D a t e s + //------------------------------------------------------------------- + // Relative dates - day. + public static DateTime PreviousDay (this DateTime value) + { + return value.AddDays(-1); + } + public static DateTime NextDay (this DateTime value) + { + return value.AddDays(1); + } + public static DateTime IncrementDate (this DateTime value, int days) + { + return value.AddDays(days); + } + public static DateTime IncrementMonth (this DateTime value, int months) + { + return value.AddMonths(months); + } + + //------------------------------------------------------------------- + // Relative dates - week. + /// + /// Sunday at start of week including specified date. + /// + /// Date in week for which starting Sunday is required + /// + public static DateTime StartOfWeek (this DateTime value) + { + // value.DayOfWeek is number of days to go back to Sunday (0 for Sunday, 1 for Monday etc). + return value.AddDays(-(int)value.DayOfWeek); + } + public static DateTime StartOfWorkingWeek (this DateTime value) + { + // Go back number to nearest Monday. + return value.AddDays(-((((int)value.DayOfWeek) + 6) % 7)); + } + //------------------------------------------------------------------- + // Relative dates - month. + // Ref. http://stackoverflow.com/questions/24245523/getting-the-first-and-last-day-of-a-month-using-a-given-datetime-object for performance analysis. + public static DateTime StartOfMonth (this DateTime value) + { + return new DateTime(value.Year, value.Month, 1, 0, 0, 0, value.Kind); + } + public static int DaysInMonth (this DateTime value) + { + return DateTime.DaysInMonth(value.Year, value.Month); + } + public static DateTime EndOfMonth (this DateTime value) + { + return new DateTime(value.Year, value.Month, value.DaysInMonth(), 0, 0, 0, value.Kind); + } + //------------------------------------------------------------------- + // Relative dates - quarter. + public static DateTime StartOfQuarter (this DateTime value) + { + return new DateTime(value.Year, (((value.Month - 1) / 3) * 3) + 1, 1, 0, 0, 0, value.Kind); + } + public static int DaysInQuarter (this DateTime value) + { + DateTime startOfQuarter = value.StartOfQuarter(); + return DateTime.DaysInMonth(startOfQuarter.Year, startOfQuarter.Month) + DateTime.DaysInMonth(startOfQuarter.Year, startOfQuarter.Month + 1) + DateTime.DaysInMonth(startOfQuarter.Year, startOfQuarter.Month + 2); + } + public static DateTime EndOfQuarter (this DateTime value) + { + DateTime startOfQuarter = value.StartOfQuarter(); + return startOfQuarter.EndOfMonth().IncrementDate(1).EndOfMonth().IncrementDate(1).EndOfMonth(); + } + + //------------------------------------------------------------------- + // Relative dates - year. + public static DateTime StartOfYear (this DateTime value) + { + return new DateTime(value.Year, 1, 1, 0, 0, 0, value.Kind); + } + public static int DaysInYear (this DateTime value) + { + // To take leap year into account. + return 365 - 28 + DateTime.DaysInMonth(value.Year, 2); + } + public static DateTime EndOfYear (this DateTime value) + { + return new DateTime(value.Year, 12, 31, 0, 0, 0, value.Kind); + } + + //------------------------------------------------------------------- + // Age calculation. + //------------------------------------------------------------------- + // Age calculation (returns 0 as a result of any exceptions): + public static ushort CalculateAge (DateTime dob, DateTime ageOn) + { + int ageInYears = 0; + if (IsValid(dob) && IsValid(ageOn) && dob < ageOn) + { + ageInYears = ageOn.Year - dob.Year; + if (ageOn.Month < dob.Month) + ageInYears--; + if (ageOn.Month == dob.Month && ageOn.Day < dob.Day) + ageInYears--; + } + return (ushort)ageInYears; + } + + public static ushort CalculateAgeMonths (DateTime dob, DateTime ageOn) + { + int ageInMonths = 0; + if (IsValid(dob) && IsValid(ageOn) && dob < ageOn) + { + ageInMonths = (ageOn.Year - dob.Year) * 12; + // Will decrement the value if birth month > age month. + ageInMonths += ageOn.Month - dob.Month; + if (ageOn.Day < dob.Day) + ageInMonths--; + } + return (ushort)ageInMonths; + } + + //------------------------------------------------------------------- + // C o n v e r s i o n + //------------------------------------------------------------------- + // Convert date to 32bit integer value. + /// + /// Convert Date (yyyyMMdd) to Int32 (C++ long). + /// Used by legacy database columns to store date as 4 byte integer. + /// + public static Int32 DateToInt32 (this DateTime value) + { + if (value.Equals(DateTime.MinValue)) return 0; + Int32 dateValue = (value.Year << 16) + (value.Month << 8) + value.Day; + return dateValue; + } + /// + /// Convert Int32 (C++ long) to Date (yyyyMMdd). + /// Used by legacy database columns to store date as 4 byte integer. + /// + public static DateTime Int32ToDate (Int32 dateValue) + { + if (dateValue == 0) return DateTime.MinValue; + int year = ((dateValue >> 16) & 0xffff); + int month = ((dateValue >> 8) & 0xff); + int day = (dateValue & 0xff); + return new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc); + } + + //------------------------------------------------------------------- + /// + /// Convert Time (HHmmssnnn) to Int32 (C++ long). + /// Used by legacy database columns to store time as 4 byte integer. + /// + public static Int32 TimeToInt32 (this DateTime value) + { + if (value.Equals(DateTime.MinValue)) return 0; + Int32 timeValue = (value.Hour << 24) + (value.Minute << 16) + (value.Second << 8) + (value.Millisecond / 10); + return timeValue; + } + /// + /// Convert Int32 (C++ long) to Time (HHmmssnnn). + /// Used by legacy database columns to store time as 4 byte integer. + /// + public static DateTime Int32ToTime (Int32 timeValue) + { + if (timeValue == 0) return DateTime.MinValue; + int year = DateTime.Today.Year; + int month = DateTime.Today.Month; + int day = DateTime.Today.Day; + int hour = (timeValue >> 24) & 0xff; + int minute = (timeValue >> 16) & 0xff; + int second = (timeValue >> 8) & 0xff; + int millis = timeValue & 0xff; + return new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc).AddMilliseconds(millis); + } + + //------------------------------------------------------------------- + // F o r m a t t i n g + //------------------------------------------------------------------- + /// + /// Format Date/time using format string. + /// d, dd: Day of month + /// ddd, dddd: Day of week + /// M, MM: Month (1 to 12) + /// MMM, MMMM: Month name + /// y - yyyy: Year + /// H, HH: hour (24 hour clock) + /// h, hh: hour (12 hour clock) + /// t, tt: AM/PM designator + /// m, mm: minute + /// s, ss: second + /// f: 10ths second + /// fff: 1,000ths second + /// ffffff: 1,000,000ths second + /// F: 10ths second (non zero) etc + /// g, gg: Period or era + /// + /// + /// + /// + public static String Format (this DateTime value, String format) + { + if (!IsValid(value)) return String.Empty; + try + { + if (!String.IsNullOrEmpty(format)) + return value.ToString(format); + return value.ToString(); + } + catch (Exception exc) + { + SaLogger.Debug("DateUtil.Format Exception: " + exc.Message); + } + return String.Empty; + } + + //------------------------------------------------------------------- + /// Format as XML date: 'yyyy-MM-dd' + public static String FormatXmlDate (this DateTime value) + { + return Format(value, Format_XmlDate); + } + /// Format as XML time: 'HH:mm:ss' + public static String FormatXmlTime (this DateTime value) + { + return Format(value, Format_XmlTime); + } + /// Format as XML datetime: 'yyyy-MM-ddTHH:mm:ss' + public static String FormatXmlDateTime (this DateTime value) + { + return Format(value, Format_XmlDateTime); + } + + //------------------------------------------------------------------- + /// Format number of seconds. + public static String FormatNumberOfSeconds (int numberOfSeconds) + { + int numberOfMinutes = numberOfSeconds / 60; + int numberOfHours = numberOfMinutes / 60; + int numberOfDays = numberOfHours / 24; + + String elapsed = ""; + if (numberOfDays > 0) + elapsed += numberOfDays + "d "; + if (numberOfHours > 0) + elapsed += (numberOfHours % 24) + "h "; + if (numberOfMinutes > 0) + elapsed += (numberOfMinutes % 60) + "m "; + if (StringUtil.IsEmpty(elapsed) || numberOfSeconds % 60 > 0) + elapsed += (numberOfSeconds % 60) + "s "; + return elapsed.Trim(); + } + + /// Format number of seconds, including millis where value <= 10s. + public static String FormatNumberOfSeconds(double numberOfSeconds) + { + int numberOfMinutes = (int)(numberOfSeconds / 60); + int numberOfHours = numberOfMinutes / 60; + int numberOfDays = numberOfHours / 24; + + String elapsed = ""; + if (numberOfDays > 0) + elapsed += numberOfDays + "d "; + if (numberOfHours > 0) + elapsed += (numberOfHours % 24) + "h "; + if (numberOfMinutes > 0) + elapsed += (numberOfMinutes % 60) + "m "; + if (StringUtil.IsEmpty(elapsed) || numberOfSeconds % 60 > 0) + { + int millis = (int)((numberOfSeconds - (int)numberOfSeconds) * 1000); + if (numberOfSeconds > 10.0) + elapsed += (int)(numberOfSeconds % 60) + "s "; + else elapsed += (int)(numberOfSeconds % 60) + "." + millis.ToString("D3") + "s "; + } + return elapsed.Trim(); + } + + //------------------------------------------------------------------- + // L i s t S o r t i n g + //------------------------------------------------------------------- + public static void SortAscending (this List list) + { + list.Sort((a, b) => a.CompareTo(b)); + } + + public static void SortDescending (this List list) + { + list.Sort((a, b) => b.CompareTo(a)); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/DictionaryUtil.cs b/SacsStandardBase/Base/Utils/DictionaryUtil.cs new file mode 100644 index 0000000..7066602 --- /dev/null +++ b/SacsStandardBase/Base/Utils/DictionaryUtil.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class DictionaryUtil + { + //------------------------------------------------------------------- + // C o n t a i n s K e y s + //------------------------------------------------------------------- + public static bool ContainsKeys(this Dictionary dict, IEnumerable keys) + { + bool result = false; + keys.ForEachOrBreak((x) => { result = dict.ContainsKey(x); return result; }); + return result; + } + + //------------------------------------------------------------------- + // G e n e r i c G e t W i t h D e f a u l t + //------------------------------------------------------------------- + /// Safely get value from Dictionary with default if not defined. + public static TValue GetValue(this IDictionary dictionary, TKey key, TValue defaultValue) + { + TValue value; + return dictionary.TryGetValue(key, out value) ? value : defaultValue; + } + + //------------------------------------------------------------------- + // G e t V a l u e s + //------------------------------------------------------------------- + public static List GetValues(this Dictionary dict) + { + return dict.Select(item => item.Value).ToList(); + } + + //------------------------------------------------------------------- + // S t r i n g - I n t U t i l i t i e s + //------------------------------------------------------------------- + /// Increment the count against the specified key by the supplied value, creating the key's entry if needed. + public static void Increment (this IDictionary dictionary, String key, int increment = 1) + { + if (!dictionary.ContainsKey(key)) + dictionary[key] = increment; + else + dictionary[key] += increment; + } + + //------------------------------------------------------------------- + // A d d T o L i s t V a l u e + //------------------------------------------------------------------- + /// Add key:object to dictionary where each value is a list of objects. + public static void Add (this Dictionary> dict, TKey key, TValue value) + { + if (!dict.ContainsKey(key)) + dict[key] = new List(); + dict[key].Add(value); + } + /// Add key:objects to dictionary where each value is a list of objects. + public static void Add (this Dictionary> dict, TKey key, IEnumerable values) + { + if (!dict.ContainsKey(key)) + dict[key] = new List(); + dict[key].AddRange(values); + } + + //------------------------------------------------------------------- + // A d d R a n g e + //------------------------------------------------------------------- + /// Add entries from another dictionary. + public static void AddRange (this Dictionary dict, Dictionary dictToAdd) + { + dictToAdd.ForEach(x => dict.Add(x.Key, x.Value)); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/EnumUtil.cs b/SacsStandardBase/Base/Utils/EnumUtil.cs new file mode 100644 index 0000000..744a4ec --- /dev/null +++ b/SacsStandardBase/Base/Utils/EnumUtil.cs @@ -0,0 +1,95 @@ +using Sarand.Net.Standard.Base.App; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class EnumUtil + { + //------------------------------------------------------------------- + public static bool IsValid (int value) where TEnum : struct, IConvertible + { + try + { + TEnum t = (TEnum)Enum.Parse(typeof(TEnum), value.ToString(), true); + return true; + } + catch (Exception) { } + return false; + } + public static bool IsValid (String value) where TEnum : struct, IConvertible + { + try + { + TEnum t = (TEnum)Enum.Parse(typeof(TEnum), value, true); + return true; + } + catch (Exception) { } + return false; + } + //------------------------------------------------------------------- + /// + /// Get Name for enum value. + /// + /// + /// + /// + public static String GetName (Object value) where TEnum : struct, IConvertible + { + return Enum.GetName(typeof(TEnum), value); + } + //------------------------------------------------------------------- + /// + /// Get enum values. + /// Usage: var values = EnumUtil.GetValues(); + /// + /// Enum type + /// + public static IEnumerable GetValues () where TEnum : struct, IConvertible + { + return Enum.GetValues(typeof(TEnum)).Cast(); + } + //------------------------------------------------------------------- + /// + /// Convert String to enum. + /// + /// + /// + /// + public static TEnum ParseEnum (string value) where TEnum : struct, IConvertible + { + return (TEnum)Enum.Parse(typeof(TEnum), value, true); + } + public static TEnum ParseEnum (string value, TEnum defaultValue) where TEnum : struct, IConvertible + { + if (StringUtil.IsEmpty(value)) return defaultValue; + try + { + return (TEnum)Enum.Parse(typeof(TEnum), value, true); + } + catch (Exception exc) { SaLogger.Debug("EnumUtil.ParseEnum " + ExceptionUtil.GetErrorStateMessage(exc) + " [Enum: " + typeof(TEnum).Name + ". Value: " + value + "]"); } + return defaultValue; + } + //------------------------------------------------------------------- + /// + /// Retrieve the first enum value whose name starts with the specified value. + /// + /// + /// + /// + /// + public static TEnum StartsWith (string value, TEnum defaultValue) where TEnum : struct, IConvertible + { + foreach (TEnum t in GetValues()) + { + if (value.EqualsCI(GetName(t).Left(value.Length))) + return t; + } + return defaultValue; + } + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/EnumerableUtil.cs b/SacsStandardBase/Base/Utils/EnumerableUtil.cs new file mode 100644 index 0000000..410d35c --- /dev/null +++ b/SacsStandardBase/Base/Utils/EnumerableUtil.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class EnumerableUtil + { + //------------------------------------------------------------------- + // F o r E a c h + //------------------------------------------------------------------- + public static void ForEach (this IEnumerable source, Action action) + { + foreach (var item in source) + action(item); + } + public static void ForEachOrBreak (this IEnumerable source, Func func) + { + foreach (var item in source) + { + bool result = func(item); + if (result) break; + } + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/ExceptionUtil.cs b/SacsStandardBase/Base/Utils/ExceptionUtil.cs new file mode 100644 index 0000000..d1fb788 --- /dev/null +++ b/SacsStandardBase/Base/Utils/ExceptionUtil.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class ExceptionUtil + { + //------------------------------------------------------------------- + /// + /// Extract and cleanse exception message. + /// - \r\n replaced with \n + /// - Duplicate lines removed. + /// + public static String GetMessage (this Exception exc) + { + List messageLines = new List(); + foreach (String messageLine in (StringUtil.EmptyOnNull(exc?.Message) ?? "").Trim().ReplaceAll("\r\n", "\n").Split('\n')) + { + if (!messageLines.ContainsCI(messageLine)) + messageLines.Add(messageLine); + } + return messageLines.Concat("\n"); + } + + //------------------------------------------------------------------- + /// + /// Generate standard exception log message. + /// [ExceptionType]: [ExceptionMessage] + /// followed by stack entries on separate lines. + /// + public static String GetErrorStateMessage (this Exception exc, bool includeExceptionType = true) + { + return (includeExceptionType ? exc.GetType().Name + ": " : "") + GetMessage(exc); + } + + //------------------------------------------------------------------- + /// + /// Generate standard exception log message. + /// [ExceptionType]: [ExceptionMessage] + /// followed by stack entries on separate lines. + /// + public static String GetLogMessage (this Exception exc, int entries = 8) + { + return exc.GetType().Name + ": " + GetMessage(exc) + exc.GetStackEntries(entries).Concat("", "\n\t"); + } + + //------------------------------------------------------------------- + /// Retrieve stack entries, up to maximum number. + public static List GetStackEntries (this Exception exc, int entries = 0) + { + List stackEntries = new List(); + stackEntries.AddRange(exc.StackTrace.Split(new String[] { "\r\n" }, (entries > 0 ? entries : 999), StringSplitOptions.None)); + for (int idx = 0; idx < stackEntries.Count; idx++) + stackEntries[idx] = stackEntries[idx].Replace(" at ", ""); + return stackEntries; + } + + //------------------------------------------------------------------- + } + + //======================================================================= + // File handling exceptions. + public class FileException : Exception + { + public FileException (string message) : + base(message) + { } + } + public class ArchiveFileException : FileException + { + public ArchiveFileException (string message) : + base(message) + { } + } + public class FileExistsException : FileException + { + public FileExistsException (string message) : + base(message) + { } + } + + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/FtpUtil.cs b/SacsStandardBase/Base/Utils/FtpUtil.cs new file mode 100644 index 0000000..2baa151 --- /dev/null +++ b/SacsStandardBase/Base/Utils/FtpUtil.cs @@ -0,0 +1,155 @@ +using Sarand.Net.Standard.Base.App; +using System; +using System.Text.RegularExpressions; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class FtpUtil + { + //------------------------------------------------------------------- + /// Cleanse the folder name - delimit with '/', remove duplicate, leading and trailing delimiters. + public static String CleansePath (String path) + { + return path.ReplaceAll("\\", "/").ReplaceAll("//", "/").TrimStart('/').TrimEnd('/'); + } + + //------------------------------------------------------------------- + /// Retrieve parent folder for folder. Parent for top level folder is blank. + public static String GetParentFolder (String folder) + { + String parentFolder = ""; + String[] folders = CleansePath(folder).Split('/'); + if (folders.Length > 0) + parentFolder = String.Join("/", folders, 0, folders.Length - 1); + return parentFolder; + } + + //------------------------------------------------------------------- + /// Retrieve bottom level item name from path. Parent for top level folder is blank. + public static String GetItemName (String folder) + { + String[] folders = CleansePath(folder).Split('/'); + if (folders.Length >= 0) + return folders[folders.Length - 1]; + return ""; + } + + //------------------------------------------------------------------- + public class FtpDirectoryItem + { + public String Attr { get; set; } = ""; + public bool IsDirectory { get; set; } + public String Owner { get; set; } = ""; + public String Group { get; set; } = ""; + public int Links { get; set; } + public long Size { get; set; } + public int Day { get; set; } + public int Month { get; set; } + public String MonthName { get; set; } = ""; + public int Year { get; set; } + public String Time { get; set; } = ""; + public DateTime Modified { get; set; } + public String Name { get; set; } = ""; + } + // YangMing (Unix?) format: + // 08-03-18 10:10AM SolentInboundTest + // 08-03-18 10:10AM SolentOutboundTest + // 08-03-18 11:23AM 8507075 testDataWithRatherLongFilename.zip + // 08-03-18 11:17AM 22 test.txt + public static readonly Regex DirectoryListPattern_Unix = new Regex(@"^"+ + // 1. Month. + @"(\d{2})-"+ + // 2. Day of month. + @"(\d{2})-"+ + // 3. Year. + @"(\d{2})\s+"+ + // 4. Time. + @"(\d{2}:\d{2})"+ + // 5. Time period. + @"(\w{2})\s+"+ + // 6. Directory indicator or Filesize. + @"(|\d+)\s+"+ + // Name. + @"([^\s]+)$", + RegexOptions.IgnoreCase); + // Windows/DOS format: + // drwx------ 3 user group 0 Apr 10 07:47 test + // -rw - r--r-- 1 ftp ftp 5592560 Feb 26 18:53 DamonJenkinsA_84_C_CTS.dds + // Update date format: + // MMM dd HH:mm if the file was modified in the last six months + // MMM dd yyyy otherwise. + public static readonly Regex DirectoryListPattern_Windows = new Regex(@"^"+ + // 1. Attributes. + // 1st Character: '-' file. 'b' Block special file. 'c' Character special file. 'd' directory. 'l' link. 'n' Network file. 'p' FIFO. 's' Socket. + // Attributes: r Permission to read file. w Permission to write to file. x Permission to execute file. a Archive bit is on (file has not been backed up). + // c Compressed file. s System file. h Hidden file. t Temporary file. + @"([abcdhlnprstwx-]{10})\s+" + + // 2. Number of links to ths file. + @"(\d+)\s+"+ + // 3. Owner. + @"([^\s]+)\s+"+ + // 4. Group. + @"([^\s]+)\s+"+ + // 5. Size. + @"(\d+)\s+"+ + // 6. Month. + @"(\w{3})\s+"+ + // 7. Day of month. + @"(\d{1,2})\s+"+ + // 8. Time or Year. + @"(\d{4}|\d\d:\d\d)\s+"+ + // Name. + @"(.+)$", + RegexOptions.IgnoreCase); + /// Extract details from Directory detail list item. + public static FtpDirectoryItem GetDirectoryItem (String directoryItemData) + { + if (StringUtil.IsEmpty(StringUtil.EmptyOnNull(directoryItemData).Trim())) return null; + + // Check for Unix directory listing pattern. + Match match = DirectoryListPattern_Unix.Match(directoryItemData); + if (match.Success && match.Groups.Count == 8) + { + FtpDirectoryItem directoryItem = new FtpDirectoryItem(); + directoryItem.Month = StringUtil.ToInt(match.Groups[1].Value); + directoryItem.Day = StringUtil.ToInt(match.Groups[2].Value); + directoryItem.Year = StringUtil.ToInt(match.Groups[3].Value); + String directoryOrFileSize = match.Groups[6].Value; + if (directoryOrFileSize.EqualsCI("")) + directoryItem.IsDirectory = true; + else + directoryItem.Size = StringUtil.ToLong(directoryOrFileSize); + directoryItem.Name = match.Groups[7].Value; + return directoryItem; + } + + // Check for Windows directory listing pattern. + match = DirectoryListPattern_Windows.Match(directoryItemData); + if (match.Success && match.Groups.Count == 10) + { + FtpDirectoryItem directoryItem = new FtpDirectoryItem(); + directoryItem.Attr = match.Groups[1].Value; + directoryItem.IsDirectory = StringUtil.Left(directoryItem.Attr, 1).EqualsCI("d"); + directoryItem.Links = StringUtil.ToInt(match.Groups[2].Value); + directoryItem.Owner = match.Groups[3].Value; + directoryItem.Group = match.Groups[4].Value; + directoryItem.Size = StringUtil.ToInt(match.Groups[5].Value); + directoryItem.MonthName = match.Groups[6].Value; + directoryItem.Day = StringUtil.ToInt(match.Groups[7].Value); + String timeYear = match.Groups[8].Value; + if (StringUtil.IsNumeric(timeYear)) + directoryItem.Year = StringUtil.ToInt(timeYear); + directoryItem.Name = match.Groups[9].Value; + return directoryItem; + } + + // Log failure. + SaLogger.Warn("FtpUtils.GetDirectoryItem Parse Failed: '" + directoryItemData + "'", true); + return null; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/ListUtil.cs b/SacsStandardBase/Base/Utils/ListUtil.cs new file mode 100644 index 0000000..ebf3538 --- /dev/null +++ b/SacsStandardBase/Base/Utils/ListUtil.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class ListUtil + { + //------------------------------------------------------------------- + public static void ClearAndDispose (this IList values) where T : IDisposable + { + while (values.Count > 0) + { + T value = values[0]; + values.RemoveAt(0); + value.Dispose(); + } + } + + //------------------------------------------------------------------- + /// Is the list empty + public static Boolean IsEmpty(this IEnumerable items) + { + return items.Count() == 0; + } + + //------------------------------------------------------------------- + /// + /// Is the value in the list of items. + /// NB String values are compared case-sensitive. + /// Use InCI() for case-insensitive string comparison. + /// + public static Boolean In(this T item, IEnumerable items) + { + return items.Contains(item); + } + /// Is the String in the list of items [case-insensitive] + public static Boolean InCI(this String item, IEnumerable items) + { + return items.ContainsCI(item); + } + + //------------------------------------------------------------------- + public enum Equality + { + /// Exact - exact match between items in each list. + Exact = 0x01, + /// Unique values only - ignores number of instances of each value. + UniqueValuesOnly = 0x02, + } + public static bool Equals (this IList sourceList, IList targetList, Equality equality) + { + bool sameLength = (sourceList.Count == targetList.Count); + if (equality == Equality.Exact) + { + // List must contain exactly the same members in each position. + if (!sameLength) return false; + for (int idx = 0; idx < sourceList.Count; idx++) + { + if (!sourceList[idx].Equals(targetList[idx])) return false; + } + return true; + } + // Compare just the unique values. + if (sourceList.Except(targetList).Any()) return false; + if (targetList.Except(sourceList).Any()) return false; + return true; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/LoggingUtils.cs b/SacsStandardBase/Base/Utils/LoggingUtils.cs new file mode 100644 index 0000000..38518c9 --- /dev/null +++ b/SacsStandardBase/Base/Utils/LoggingUtils.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class LoggingUtils + { + //------------------------------------------------------------------- + // M e t h o d N a m e + //------------------------------------------------------------------- + /* + [MethodImpl(MethodImplOptions.NoInlining)] + public static string GetMyMethodName () + { + var st = new StackTrace(new StackFrame(1)); + return st.GetFrame(0).GetMethod().Name; + } + */ + + //------------------------------------------------------------------- + // L o g g i n g P r e f i x + //------------------------------------------------------------------- + /// + /// Format logging prefix from class and function names. + /// '<class>.<function>' + /// + public static String GetFn (this Object obj, String function) + { + return GetFn(obj.GetType(), function); + } + /// + /// Format logging prefix from class and function names. + /// '<class>.<function>' + /// + public static String GetFn (this Type type, String function) + { + return type.Name + "." + function; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/MathUtil.cs b/SacsStandardBase/Base/Utils/MathUtil.cs new file mode 100644 index 0000000..496d21d --- /dev/null +++ b/SacsStandardBase/Base/Utils/MathUtil.cs @@ -0,0 +1,148 @@ +using System; + +namespace Sarand.Net.Standard.Base.Utils +{ + public static class MathUtil + { + //------------------------------------------------------------------- + // C o m p a r i s o n + //------------------------------------------------------------------- + /// Compare double values within tollerance. + public static bool Equals (this double value, double compareValue, double tollerance) + { + return (Math.Abs(value - compareValue) <= tollerance); + } + /// Compare float values within tollerance. + public static bool Equals (this float value, float compareValue, float tollerance) + { + return (Math.Abs(value - compareValue) <= tollerance); + } + /// Compare decimal values within tollerance. + public static bool Equals (this decimal value, decimal compareValue, decimal tollerance) + { + return (Math.Abs(value - compareValue) <= tollerance); + } + + //------------------------------------------------------------------- + // C o n s t r a i n + //------------------------------------------------------------------- + /// + /// Constrain integer value by maximum and minimum values. + /// + /// + /// + /// + /// + public static int Constrain (this int value, int min, int max) + { + // Ref. Math.Clamp() in .Net Core 2.0. + return Math.Min(max, Math.Max(min, value)); + } + /// + /// Constrain decimal value by maximum and minimum values. + /// + /// + /// + /// + /// + public static decimal Constrain (this decimal value, decimal min, decimal max) + { + return Math.Min(max, Math.Max(min, value)); + } + + //------------------------------------------------------------------- + // S o l v e Q u a d r a t i c + //------------------------------------------------------------------- + // Ref. https://en.wikipedia.org/wiki/Quadratic_formula + // ax² + bx + c = 0 + // x = (-b ± Sqrt(b²-4ac)) / 2a + // NB c must be <= b² / 4a + public static double SolveQuadratic (double valueA, double valueB, double valueC, bool maxValue = true) + { + double result = 0; + double result1 = (-valueB + Math.Sqrt(Math.Pow(valueB, 2) - (4 * valueA * valueC))) / (2 * valueA); + double result2 = (-valueB - Math.Sqrt(Math.Pow(valueB, 2) - (4 * valueA * valueC))) / (2 * valueA); + if (maxValue) + result = Math.Max(result1, result2); + else result = Math.Min(result1, result2); + return result; + } + + //------------------------------------------------------------------- + /// + /// Test for near zero values (<1e-10). + /// + /// + /// + public static bool IsZero (this double d, double zeroEpsilon = 1e-10) + { + return Math.Abs(d) < zeroEpsilon; + } + + //------------------------------------------------------------------- + // Convert degress to radians. + public static double DegreesToRadians (double degrees) + { + return (Math.PI / 180) * AdjustDegrees(degrees); + } + // Convert radians to degrees. + public static double RadiansToDegrees (double radians) + { + return AdjustDegrees((180 / Math.PI) * radians); + } + public static double AdjustDegrees (double degrees) + { + while (degrees < 0.0) degrees += 360.0; + while (degrees >= 360.0) degrees -= 360.0; + return degrees; + } + + //------------------------------------------------------------------- + // Calculate hypotenuse lenfth from the other two sides. + public static double GetHypotenuseLength (double side1, double side2) + { + return Math.Sqrt(Math.Pow(side1, 2.0) + Math.Pow(side2, 2.0)); + } + + //------------------------------------------------------------------- + /* + /// + /// Offset size by specified distance and angle (anti-clockwise from vertical). + /// angle = 0: straight up; angle = 90: to left; angle = -90: to right, etc. + /// + /// + /// + /// + public static SizeF GetSizeOffsetByDegrees (float distance, double angleDegrees) + { + double angleRadians = MathUtil.DegreesToRadians(angleDegrees); + return GetSizeOffsetByRadians(distance, angleRadians); + } + public static SizeF GetSizeOffsetByRadians (float distance, double angleRadians) + { + return new SizeF(-Convert.ToSingle(Math.Sin(angleRadians) * distance), Convert.ToSingle(Math.Cos(angleRadians) * distance)); + } + */ + + //------------------------------------------------------------------- + /* + /// + /// Rotate a point about the specified center point by the number of degrees. + /// + public static PointF RotatePointDegrees (PointF point, PointF center, double angleDegrees) + { + return RotatePointRadians(point, center, MathUtil.DegreesToRadians(angleDegrees)); + } + public static PointF RotatePointRadians (PointF point, PointF center, double angleRadians) + { + double cosTheta = Math.Cos(angleRadians); + double sinTheta = Math.Sin(angleRadians); + float x = Convert.ToSingle(cosTheta * (point.X - center.X) - sinTheta * (point.Y - center.Y) + center.X); + float y = Convert.ToSingle(sinTheta * (point.X - center.X) + cosTheta * (point.Y - center.Y) + center.Y); + return new PointF(x, y); + } + */ + + //------------------------------------------------------------------- + } +} diff --git a/SacsStandardBase/Base/Utils/SerializableUtil.cs b/SacsStandardBase/Base/Utils/SerializableUtil.cs new file mode 100644 index 0000000..4f6a305 --- /dev/null +++ b/SacsStandardBase/Base/Utils/SerializableUtil.cs @@ -0,0 +1,82 @@ +using System; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Xml.Serialization; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class SerializableUtil + { + //------------------------------------------------------------------- + /// + /// Perform a deep Copy of the object. + /// + /// The type of object being copied. + /// The object instance to copy. + /// The copied object. + public static T DeepCopy(T source) + { + if (!typeof(T).IsSerializable) + throw new ArgumentException("The type must be serializable.", "source"); + + // Don't serialize a null object, simply return the default for that object + if (Object.ReferenceEquals(source, null)) + return default(T); + + IFormatter formatter = new BinaryFormatter(); + Stream stream = new MemoryStream(); + using (stream) + { + formatter.Serialize(stream, source); + stream.Seek(0, SeekOrigin.Begin); + return (T)formatter.Deserialize(stream); + } + } + + //------------------------------------------------------------------- + // Serialize. + public static String Serialize(T source) + { + if (!typeof(T).IsSerializable) throw new ArgumentException("The type must be serializable.", "source"); + // Don't serialize a null object, simply return the default for that object + if (Object.ReferenceEquals(source, null)) return ""; + // Serialise to String. + IFormatter formatter = new BinaryFormatter(); + using (Stream stream = new MemoryStream()) + using (StreamReader streamReader = new StreamReader(stream)) + { + formatter.Serialize(stream, source); + stream.Seek(0, SeekOrigin.Begin); + return streamReader.ReadToEnd(); + } + } + + //------------------------------------------------------------------- + // Serialize using XML. + public static String ToXml(T source) + { + XmlSerializer serializer = new XmlSerializer(typeof(T)); + using (Stream stream = new MemoryStream()) + { + serializer.Serialize(stream, source); + stream.Seek(0, SeekOrigin.Begin); + var streamReader = new StreamReader(stream); + return streamReader.ReadToEnd(); + } + } + public static T FromXml(String data) + { + XmlSerializer serializer = new XmlSerializer(typeof(T)); + using (Stream stream = new MemoryStream(Encoding.Unicode.GetBytes(data ?? ""))) + { + return (T)serializer.Deserialize(stream); + } + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/StringListUtil.cs b/SacsStandardBase/Base/Utils/StringListUtil.cs new file mode 100644 index 0000000..917a307 --- /dev/null +++ b/SacsStandardBase/Base/Utils/StringListUtil.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class StringListUtil + { + //------------------------------------------------------------------- + // C r e a t i o n + //------------------------------------------------------------------- + /// Create list of strings with value repeated n times. + public static List CreateList (String value, int repeat = 1) + { + List values = new List(); + for (int i = 0; i < repeat; i++) + values.Add(value); + return values; + } + + //------------------------------------------------------------------- + // Split string using delimiter and return list. + public static List Split (String data, String delimiter = ",", bool trimSpaces = true, bool ignoreEmpty = false, bool ignoreDuplicates = false, bool caseSensitive = false, long maxItems = 0) + { + List values = new List(); + if (data == null) return values; + foreach (String rawValue in data.Split(new string[] { delimiter }, StringSplitOptions.None)) + { + String value = rawValue; + if (trimSpaces) value = value.Trim(); + if (ignoreEmpty && value.IsEmpty()) continue; + if (ignoreDuplicates && values.Contains(value, caseSensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal)) continue; + values.Add(value); + if (maxItems > 0 && values.Count >= maxItems) break; + } + return values; + } + + //------------------------------------------------------------------- + // C o m p a r i s o n + //------------------------------------------------------------------- + /// Compare lists to verify that they contain the same value in the corresponding position. + public static bool ContentEquals (this List values, List target) + { + if (values.Count != target.Count) return false; + var combinedValues = values.Zip(target, Tuple.Create); + foreach (var combinedValue in combinedValues) + if (!combinedValue.Item1.Equals(combinedValue.Item2)) return false; + return true; + } + /// Compare lists to verify that they contain the same case-insensitive value in the corresponding position. + public static bool ContentEqualsCI (this List values, List target) + { + if (values.Count != target.Count) return false; + var combinedValues = values.Zip(target, Tuple.Create); + foreach (var combinedValue in combinedValues) + if (!combinedValue.Item1.EqualsCI(combinedValue.Item2)) return false; + return true; + } + + //------------------------------------------------------------------- + // C o n t e n t s + //------------------------------------------------------------------- + /// Does the list have any entries. + public static bool IsEmpty (this ICollection values) + { + return (values.Count == 0); + } + /// Case-insensitive contains. + public static bool ContainsCI (this IEnumerable values, String value) + { + return values.Contains(value, StringComparer.OrdinalIgnoreCase); + } + + //------------------------------------------------------------------- + // E x t r a c t i o n + //------------------------------------------------------------------- + /// + /// Remove the specified number of entries from the top of the list and return them. + /// Original list retains the remaining items. + /// + public static List ExtractValues (this List values, int numberToRemove) + { + List extracted = new List(); + while (values.Count > 0 && extracted.Count < numberToRemove) + { + String id = values.ElementAt(0); + values.RemoveAt(0); + extracted.Add(id); + } + return extracted; + } + + //------------------------------------------------------------------- + // C l e a n s i n g + //------------------------------------------------------------------- + /// Remove all values that match, case-insensitive. + public static void RemoveAllCI (this List values, String value) + { + values.RemoveAll(item => item.EqualsCI(value)); + } + /// Remove all blank values with option to trim values before checking for blank. + public static void RemoveBlanks (this List values, bool trimValues = true) + { + for (int idx = 0; idx < values.Count; idx++) + { + String value = values[idx]; + if (trimValues) value = value.Trim(); + if (StringUtil.IsEmpty(value)) + { + values.RemoveAt(idx); + idx--; + } + } + } + /// Remove all duplicate values from the list, case-insensitive. + public static void RemoveDuplicatesCI (this List values) + { + for (int idx = 0; idx < values.Count; idx++) + { + String value = values[idx]; + for (int idx2 = idx + 1; idx2 < values.Count; idx2++) + { + String value2 = values[idx2]; + if (value.EqualsCI(value2)) + { + values.RemoveAt(idx2); + idx2--; + } + } + } + } + + //------------------------------------------------------------------- + // M o d i f i c a t i o n + //------------------------------------------------------------------- + /// + /// Bracket each entry in the list with the specified prefix and suffix (eg. '[' & ']'). + /// Options to trim values and ignore blank values. + /// + public static List Bracket (this IList values, String prefix = "", String suffix = "", bool trimSpaces = true, bool ignoreEmpty = false) + { + List results = new List(); + prefix = StringUtil.EmptyOnNull(prefix); + suffix = StringUtil.EmptyOnNull(suffix); + foreach (String rawValue in values) + { + String value = rawValue; + if (trimSpaces) value = value.Trim(); + if (ignoreEmpty && value.IsEmpty()) continue; + results.Add(prefix + value + suffix); + } + return results; + } + + //------------------------------------------------------------------- + // C o n c a t e n a t i o n + //------------------------------------------------------------------- + public static String Concat (this IEnumerable values, String delimiter, String prefix = "", String suffix = "", bool trimSpaces = true, bool ignoreEmpty = false) + { + delimiter = StringUtil.EmptyOnNull(delimiter); + prefix = StringUtil.EmptyOnNull(prefix); + suffix = StringUtil.EmptyOnNull(suffix); + StringBuilder output = new StringBuilder(); + int valuesAdded = 0; + foreach (String rawValue in values) + { + String value = rawValue; + if (trimSpaces) value = value.Trim(); + if (ignoreEmpty && value.IsEmpty()) continue; + if (valuesAdded++ > 0) + output.Append(delimiter); + output.Append(prefix).Append(value).Append(suffix); + } + return output.ToString(); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/StringUtil.cs b/SacsStandardBase/Base/Utils/StringUtil.cs new file mode 100644 index 0000000..ebc26d9 --- /dev/null +++ b/SacsStandardBase/Base/Utils/StringUtil.cs @@ -0,0 +1,469 @@ +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using System; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class StringUtil + { + //------------------------------------------------------------------- + // U t i l i t i e s + //------------------------------------------------------------------- + public static String EmptyOnNull (String value) + { + return (value == null ? String.Empty : value); + } + public static String OnEmpty (String value, String defaultValue) + { + if (IsEmpty(value)) return defaultValue; + return value; + } + public static int GetLength (String value) + { + if (String.IsNullOrEmpty(value)) return 0; + return value.Length; + } + + //------------------------------------------------------------------- + // C o m p a r i s o n + //------------------------------------------------------------------- + public static bool IsEmpty (this string value) + { + return String.IsNullOrEmpty(value); + } + public static bool EqualsCI (this String value1, String value2) + { + return String.Equals(value1, value2, StringComparison.OrdinalIgnoreCase); + } + public static bool StartsWithCI (this String value1, String value2) + { + if (IsEmpty(value1) || IsEmpty(value2)) return false; + return (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)); + } + public static bool EndsWithCI (this String value1, String value2) + { + if (IsEmpty(value1) || IsEmpty(value2)) return false; + return (value1.EndsWith(value2, StringComparison.OrdinalIgnoreCase)); + } + public static bool ContainsCI (this String value1, String value2) + { + if (IsEmpty(value1) || IsEmpty(value2)) return false; + return (value1.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0); + } + + //------------------------------------------------------------------- + // C o n t e n t + //------------------------------------------------------------------- + public static bool IsAlpha (this string value) + { + return EmptyOnNull(value).All(Char.IsLetter); + } + public static bool IsAlphaNumeric (this string value) + { + return EmptyOnNull(value).All(Char.IsLetterOrDigit); + } + public static bool IsNumeric (this string value, bool allowNegative = false) + { + Int64 number = 0; + if (!Int64.TryParse(value, out number)) return false; + return (allowNegative || number >= 0); + } + public static bool IsDecimal (this string value, bool allowNegative = false) + { + Decimal number = 0; + if (!Decimal.TryParse(value, out number)) return false; + return (allowNegative || number >= 0); + } + //------------------------------------------------------------------- + /// Test for Hex character. + public static bool IsHex (this char c) + { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + } + /// Test for Hex string. + public static bool IsHex (this String value) + { + if (IsEmpty(value)) return false; + foreach (char c in value) + if (!IsHex(c)) return false; + return true; + } + + //------------------------------------------------------------------- + // P a d d i n g + //------------------------------------------------------------------- + public static String PadLeft (this String value, int length, char pad = ' ') + { + value = StringUtil.EmptyOnNull(value); + while (value.Length < length) + value = pad + value; + return value; + } + public static String PadRight (this String value, int length, char pad = ' ') + { + value = StringUtil.EmptyOnNull(value); + while (value.Length < length) + value = value + pad; + return value; + } + + //------------------------------------------------------------------- + // C o n v e r t + //------------------------------------------------------------------- + public static String CapitaliseFirst (this String value, TextInfo textInfo = null) + { + textInfo = (textInfo ?? CultureInfo.CurrentCulture.TextInfo); + if (StringUtil.IsEmpty(value)) return value; + return textInfo.ToUpper(value.First()) + value.Substring(1); + } + public static string ToTitleCase (this string value, TextInfo textInfo = null) + { + textInfo = (textInfo ?? CultureInfo.CurrentCulture.TextInfo); + String[] tokens = value.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < tokens.Length; i++) + { + String token = tokens[i]; + tokens[i] = textInfo.ToUpper(token.First()) + token.Substring(1); + } + + return string.Join(" ", tokens); + } + + //------------------------------------------------------------------- + // C l e a n s e + //------------------------------------------------------------------- + /// Trim leading, trailling and multiple internal spaces. + public static String TrimAll (this String value) + { + if (IsEmpty(value)) return value; + value = value.Trim().TrimInternalSpaces(); + return value; + } + /// + /// Trim any combination of the specified characters from the start, + /// end and where more than one found together internally, + /// replacing with the specified string. + /// + /// + /// + /// + public static String TrimAll (this String value, char[] trimChars, String replacement = " ") + { + if (IsEmpty(value) || trimChars.Length < 1) return value; + // Trim leading and trailing. + value = value.Trim(trimChars); + // Use regex to replace multiple internal. + Regex regexMultipleInternal = new Regex("["+Regex.Escape(new String(trimChars))+"]{2,}", RegexOptions.None); + return regexMultipleInternal.Replace(value, replacement); + } + private static Regex regexMultipleSpaces = new Regex("[ ]{2,}", RegexOptions.Compiled); + public static String TrimInternalSpaces (this String value) + { + return regexMultipleSpaces.Replace(value, " "); + } + + //------------------------------------------------------------------- + // R e p l a c e + //------------------------------------------------------------------- + /// Replace any of the characters in the search string with the specified replacement character. + public static string ReplaceAny (this string value, String searchChars, char replaceChar) + { + for (int i = 0; i < searchChars.Length; i++) + value = value.Replace(searchChars[i], replaceChar); + return value; + } + /// + /// Replace all occurences of any of the search strings with the replace string, even where search values remain after the each replacement. + /// NB Use String.Replace if the replacement contains the search string + /// + public static string ReplaceAll (this string value, String search, String replace) + { + return value.ReplaceAll(new String[] { search }, replace); + } + public static string ReplaceAll (this string value, String[] searches, String replace) + { + foreach (String search in searches) + { + while (value.Contains(search)) + { + value = value.Replace(search, replace); + if (value.Contains(search) && replace.Contains(search)) + throw new NotSupportedException("StringUtil.ReplaceAll Replace value cannot contain search value"); + } + } + return value; + } + /// Duplicate every instance of the specified character. + public static String Duplicate (this String value, char search) + { + return value.Replace(search.ToString(), search.ToString() + search.ToString()); + } + + //------------------------------------------------------------------- + // E x t r a c t i o n + //------------------------------------------------------------------- + public static string Left (this string value, int length) + { + if (String.IsNullOrEmpty(value)) return value; + if (length < 0) return String.Empty; + return (length >= value.Length ? value : value.Substring(0, length)); + } + public static string Right (this string value, int length) + { + if (String.IsNullOrEmpty(value)) return value; + if (length < 0) return String.Empty; + return + ( + length >= value.Length + ? value + : value.Substring(value.Length - length) + ); + } + /// + /// Safe SubString - avoinding out of bounds exceptions etc. + /// + /// + /// + /// + /// + public static string SubstringSafe (this string value, int start, int length = -1) + { + if (String.IsNullOrEmpty(value)) return String.Empty; + start = Math.Max(0, start); + if (start >= value.Length) return String.Empty; + value = value.Substring(start); + if (length < 0 || length > value.Length) return value; + return value.Substring(0, length); + } + + //------------------------------------------------------------------- + // C o n v e r s i o n + //------------------------------------------------------------------- + // Integer values. + public static short ToShort (this string value, short defaultValue = 0) + { + return (short)ToLong(value, defaultValue); + } + public static int ToInt (this string value, int defaultValue = 0) + { + return (int)ToLong(value, defaultValue); + } + public static long ToLong (this string value, long defaultValue = 0) + { + if (String.IsNullOrEmpty(value)) return defaultValue; + try + { + Double returnValue = defaultValue; + if (Double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out returnValue)) + return (Int64)returnValue; + } + catch (Exception) { } + return defaultValue; + } + public static short ToShort (Object value, short defaultValue = 0) + { + return (short)ToLong(value, defaultValue); + } + public static int ToInt (Object value, int defaultValue = 0) + { + return (int)ToLong(value, defaultValue); + } + public static long ToLong (Object value, long defaultValue = 0) + { + if (value == null) return defaultValue; + try + { + return ToLong(value.ToString()); + } + catch (Exception) { } + return defaultValue; + } + //--------------------------------------------------------------------------- + // Hex values to integer. + /// Convert hex character to integer value. + public static byte ToHex (this char value) + { + if (value >= '0' && value <= '9') return (byte)(value - '0'); + if (value >= 'A' && value <= 'F') return (byte)(value - 'A' + 10); + if (value >= 'a' && value <= 'f') return (byte)(value - 'a' + 10); + return 0; + } + /// Convert pair of hex character to integer value. + public static byte ToHex (char cHex1, char cHex2) + { + return (byte)((ToHex(cHex1) * 16) + ToHex(cHex2)); + } + /// Convert hex strings to integer value. + public static ulong ToHex (this String value) + { + if (IsEmpty(value)) return 0; + ulong intValue = 0; + foreach (char c in value) + { + // Shift previous value half a byte (nibble) - equivalent to single hex character. + intValue <<= 4; + // Get value of next hex character. + byte charValue = ToHex (c); + intValue += charValue; + } + return intValue; + } + //------------------------------------------------------------------- + // Floating point values. + public static float ToSingle (this string value, float defaultValue = 0) + { + return Convert.ToSingle(ToDouble(value, defaultValue)); + } + public static double ToDouble (this string value, double defaultValue = 0) + { + if (String.IsNullOrEmpty(value)) return defaultValue; + try + { + Double returnValue = defaultValue; + if (Double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out returnValue)) + return returnValue; + } + catch (Exception) { } + return defaultValue; + } + public static float ToSingle (Object value, float defaultValue = 0) + { + return Convert.ToSingle(ToDouble(value, defaultValue)); + } + public static double ToDouble (Object value, double defaultValue = 0) + { + if (value == null) return defaultValue; + try + { + return ToDouble(value.ToString()); + } + catch (Exception) { } + return defaultValue; + } + //------------------------------------------------------------------- + // Decimal values. + public static decimal ToDecimal (this string value, decimal defaultValue = 0) + { + if (String.IsNullOrEmpty(value)) return defaultValue; + try + { + Decimal returnValue = defaultValue; + if (Decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out returnValue)) + return returnValue; + } + catch (Exception) { } + return defaultValue; + } + public static decimal ToDecimal (Object value, decimal defaultValue = 0) + { + if (value == null) return defaultValue; + try + { + return ToDecimal(value.ToString()); + } + catch (Exception) { } + return defaultValue; + } + //------------------------------------------------------------------- + // Boolean values. + public static bool ToBool (this string value) + { + return value.IsTrue(); + } + public static bool IsTrue (this string value) + { + if (String.IsNullOrEmpty(value)) return false; + if (value.ToLong() != 0) return true; + if (value.ToDouble() != 0.0) return true; + if (value.Left(1).EqualsCI("Y")) return true; + if (value.EqualsCI("True")) return true; + return false; + } + + //------------------------------------------------------------------- + // F o r m a t t i n g + //------------------------------------------------------------------- + public static String FormatNumeric (double numericValue, ushort decimalPlaces = 0, ushort sigFigs = 0, char thousandsDelim = '\0', char decimalDelim = '.') + { + String value = String.Empty; + try + { + String format = "F"+decimalPlaces.ToString(); + if (thousandsDelim != '\0') + format = "N" + decimalPlaces.ToString(); + value = numericValue.ToString(format); + } + catch (Exception exc) + { + SaLogger.Debug("StringUtil.FormatNumeric(double) " + exc.GetType().Name + ": " + exc.Message); + } + return value; + } + public static String FormatNumeric (decimal numericValue, ushort decimalPlaces = 0, ushort sigFigs = 0, char thousandsDelim = '\0', char decimalDelim = '.') + { + String value = String.Empty; + try + { + String format = "F"+decimalPlaces.ToString(); + if (thousandsDelim != '\0') + format = "N" + decimalPlaces.ToString(); + value = numericValue.ToString(format); + } + catch (Exception exc) + { + SaLogger.Debug("StringUtil.FormatNumeric(decimal) " + exc.GetType().Name + ": " + exc.Message); + } + return value; + } + //------------------------------------------------------------------- + // Specialist finding. + public static int FindMatchingBrace (this String value, int braceOpenPos) + { + if (String.IsNullOrEmpty(value) || braceOpenPos < 0 || braceOpenPos >= value.Length) return -1; + char sourceBrace = value[braceOpenPos]; + char targetBrace = '\0'; // Target character to be found. + bool openBraceFound = false; // Identifies forward or backwards search (close or open bracket to be found). + + // Identify the target bracket character: + switch (sourceBrace) + { + case '(': targetBrace = ')'; openBraceFound = true; break; + case ')': targetBrace = '('; break; + case '[': targetBrace = ']'; openBraceFound = true; break; + case ']': targetBrace = '['; break; + case '{': targetBrace = '}'; openBraceFound = true; break; + case '}': targetBrace = '{'; break; + case '<': targetBrace = '>'; openBraceFound = true; break; + case '>': targetBrace = '<'; break; + } + // Verify that a valid bracket has been found: + if (targetBrace == 0) return -1; + // Start search from the 'source' bracket position: + int braceCount = 1; // Search continues until this count gets back down to '0'. + int searchPos = braceOpenPos; + while (true) + { + // Navigate to next character to be checked (direction depends on whether target is open or close bracket): + if (openBraceFound) searchPos++; else searchPos--; + if (searchPos < 0 || searchPos >= value.Length) return -1; + // If character at this position is the 'source' bracket, increment count (another level of embedded brackets): + char searchChar = value[searchPos]; + if (searchChar == sourceBrace) braceCount++; + else if (searchChar == targetBrace) + { + braceCount--; + if (braceCount == 0) return searchPos; // Matching bracket found. + else if (braceCount < 0) return -1; // Invalid combination of brackets. + } + } + //return -1; Unreachable code! + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/SysUtil.cs b/SacsStandardBase/Base/Utils/SysUtil.cs new file mode 100644 index 0000000..7d00cd5 --- /dev/null +++ b/SacsStandardBase/Base/Utils/SysUtil.cs @@ -0,0 +1,14 @@ +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public class SysUtil + { + //------------------------------------------------------------------- + // Data members. + //------------------------------------------------------------------- + // Construction. + private SysUtil () { } + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Utils/WebUtil.cs b/SacsStandardBase/Base/Utils/WebUtil.cs new file mode 100644 index 0000000..889c969 --- /dev/null +++ b/SacsStandardBase/Base/Utils/WebUtil.cs @@ -0,0 +1,45 @@ +using Sarand.Net.Standard.Base.Core; +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; + +namespace Sarand.Net.Standard.Base.Utils +{ + public static class WebUtil + { + //------------------------------------------------------------------- + // H T M L E n c o d i n g + //------------------------------------------------------------------- + /// Converts a string to an HTML-encoded string. + public static String HtmlEncode (this String value) + { + if (StringUtil.IsEmpty(value)) return value; + return WebUtility.HtmlEncode(value); + } + /// Converts a string that has been HTML-encoded for HTTP transmission into a decoded string. + public static String HtmlDecode (this String value) + { + if (StringUtil.IsEmpty(value)) return value; + return WebUtility.HtmlDecode(value); + } + + //------------------------------------------------------------------- + // U R L E n c o d i n g + //------------------------------------------------------------------- + /// Converts a string to an HTML-encoded string. + public static String UrlEncode (this String value) + { + if (StringUtil.IsEmpty(value)) return value; + return WebUtility.UrlEncode(value); + } + /// Converts a string that has been HTML-encoded for HTTP transmission into a decoded string. + public static String UrlDecode (this String value) + { + if (StringUtil.IsEmpty(value)) return value; + return WebUtility.UrlDecode(value); + } + + //------------------------------------------------------------------- + } +} diff --git a/SacsStandardBase/Base/Utils/XmlUtil.cs b/SacsStandardBase/Base/Utils/XmlUtil.cs new file mode 100644 index 0000000..da9225e --- /dev/null +++ b/SacsStandardBase/Base/Utils/XmlUtil.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using System.Xml; +using System.Xml.Linq; + +namespace Sarand.Net.Standard.Base.Utils +{ + //======================================================================= + public static class XmlUtil + { + //------------------------------------------------------------------- + // Convert XML document to string. + public static String GetXml (this XDocument xmlDoc) + { + using (var stringWriter = new StringWriter()) + using (var xmlTextWriter = XmlWriter.Create(stringWriter)) + { + xmlDoc.WriteTo(xmlTextWriter); + xmlTextWriter.Flush(); + return stringWriter.GetStringBuilder().ToString(); + } + } + + //------------------------------------------------------------------- + // Append element to node. + public static XmlElement AppendElement (this XmlNode node, String name, String text = "") + { + XmlDocument doc = (node is XmlDocument ? node as XmlDocument : node.OwnerDocument); + XmlElement child = (XmlElement)node.AppendChild(doc.CreateElement(name)); + if (!StringUtil.IsEmpty(text)) + child.AppendChild(doc.CreateTextNode(text)); + return child; + } + + //------------------------------------------------------------------- + // Get child text. + public static String GetChildText (this XmlNode node, String name) + { + foreach (XmlNode child in node.ChildNodes) + if (child.Name.EqualsCI(name)) + return child.InnerText; + return String.Empty; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Xml/SacsSoapUtil.cs b/SacsStandardBase/Base/Xml/SacsSoapUtil.cs new file mode 100644 index 0000000..ff7ed81 --- /dev/null +++ b/SacsStandardBase/Base/Xml/SacsSoapUtil.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sarand.Net.Standard.Base.Xml +{ + //======================================================================= + public enum SoapElementType + { + Envelope, + Header, + Body + } + public enum SoapNamespaceType + { + Soap_1_1, + Soap_1_2, + Encoding_1_2, + Security, + } + + //======================================================================= + public static class SacsSoapUtils + { + //------------------------------------------------------------------- + public static String GetElementName (SoapElementType element) + { + switch (element) + { + case SoapElementType.Envelope: return "Envelope"; + case SoapElementType.Header: return "Header"; + case SoapElementType.Body: return "Body"; + } + return ""; + } + + //------------------------------------------------------------------- + public static String GetSoapNamespace(SoapNamespaceType namespaceType) + { + switch (namespaceType) + { + case SoapNamespaceType.Soap_1_1: + return "http://schemas.xmlsoap.org/soap/envelope/"; + case SoapNamespaceType.Soap_1_2: + return "http://www.w3.org/2003/05/soap-envelope"; + case SoapNamespaceType.Encoding_1_2: + return "http://www.w3.org/2003/05/soap-encoding"; + case SoapNamespaceType.Security: + return "http://schemas.xmlsoap.org/soap/security/2000-12"; + } + return ""; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Base/Xml/SacsXmlUtil.cs b/SacsStandardBase/Base/Xml/SacsXmlUtil.cs new file mode 100644 index 0000000..20e9817 --- /dev/null +++ b/SacsStandardBase/Base/Xml/SacsXmlUtil.cs @@ -0,0 +1,180 @@ +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Linq; + +namespace Sarand.Net.Standard.Base.Xml +{ + //======================================================================= + public static class SacsXmlUtil + { + //------------------------------------------------------------------- + // Data members. + public static Encoding DefaultEncoding { get; } = Encoding.UTF8; + + //------------------------------------------------------------------- + /// Retrieve the Xml Declaration from the XML document. + public static XDeclaration GetXmlDeclaration (this XDocument xmlDoc) + { + return xmlDoc.Declaration; + } + + //------------------------------------------------------------------- + // Namespace. + public static String GetNamespacePrefix (this XElement element) + { + if (element.Name.Namespace != null) + return element.GetPrefixOfNamespace(element.Name.Namespace); + return ""; + } + + //------------------------------------------------------------------- + // Convert XML document to string. + public static String GetXml (this XContainer container, Encoding encoding = null) + { + //return container.ToString(SaveOptions.DisableFormatting); + + var builder = new StringBuilder(); + var settings = new XmlWriterSettings() + { + Indent = false + }; + using (var writer = XmlWriter.Create(builder, settings)) + { + container.WriteTo(writer); + } + return builder.ToString(); + } + public static String FormatXml (this XContainer container, Encoding encoding = null) + { + //return container.ToString(); + + var builder = new StringBuilder(); + var settings = new XmlWriterSettings() + { + Indent = true + }; + using (var writer = XmlWriter.Create(builder, settings)) + { + container.WriteTo(writer); + } + return builder.ToString(); + } + + //------------------------------------------------------------------- + // N o d e N a m e + //------------------------------------------------------------------- + public static String GetLocalName (this XElement element) + { + return element?.Name?.LocalName ?? ""; + } + + //------------------------------------------------------------------- + // A t t r i b u t e s + //------------------------------------------------------------------- + public static XAttribute GetAttribute (this XElement element, String name) + { + foreach (XAttribute attribute in element.Attributes()) + if (attribute.Name.LocalName.EqualsCI(name)) + return attribute; + return null; + } + public static bool HasAttribute (this XElement element, String name) + { + return element.GetAttribute(name) != null; + } + public static String GetAttributeValue (this XElement element, String name) + { + XAttribute attribute = element.GetAttribute(name); + if (attribute != null) return attribute.Value; + return ""; + } + public static void SetAttribute (this XElement element, String name, String value) + { + element.SetAttributeValue(name, value); + } + + //------------------------------------------------------------------- + // C h i l d N o d e s + //------------------------------------------------------------------- + public static List GetChildElements (this XContainer container, String name = "") + { + List childElements = new List(); + if (container == null) return childElements; + foreach (XElement element in container.Elements()) + { + if (StringUtil.IsEmpty(name) || element.Name.LocalName.EqualsCI(name)) + childElements.Add(new XElement(element)); + } + return childElements; + } + public static bool HasChildNode (this XContainer container, String name) + { + if (container == null) return false; + foreach (XElement element in container.Elements()) + { + if (StringUtil.IsEmpty(name) || element.Name.LocalName.EqualsCI(name)) + return true; + } + return false; + } + public static XElement GetFirstChildElement (this XContainer container, String name = "") + { + if (container == null) return null; + foreach (XElement element in container.Elements()) + { + if (StringUtil.IsEmpty(name) || element.Name.LocalName.EqualsCI(name)) + return element; + } + return null; + } + public static XElement GetNextSiblingElement (this XElement child, String name = "") + { + if (child == null) return null; + XNode sibling = null; + while ((sibling = child.NextNode) != null) + { + if (sibling is XElement && (StringUtil.IsEmpty(name) || (sibling as XElement).Name.LocalName.EqualsCI(name))) + return sibling as XElement; + } + return null; + } + + //------------------------------------------------------------------- + /// Get text from first child that matches the node name. + public static String GetChildText (this XElement element, String name) + { + foreach (XElement child in element.Elements()) + if (child.Name.LocalName.EqualsCI(name)) + return child.Value; + return String.Empty; + } + + //------------------------------------------------------------------- + // Append element to node. + public static XElement AppendElement (this XContainer container, XElement element) + { + container.Add(element); + return element; + } + public static XElement AppendElement (this XContainer container, String name, String text = "") + { + XElement child = new XElement(name, text); + container.Add(child); + return child; + } + public static XElement AppendElement (this XContainer container, XNamespace xmlNamespace, String name, String text = "") + { + XElement child = new XElement(xmlNamespace + name, text); + container.Add(child); + return child; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardBase/Resources/html/sa-base.css b/SacsStandardBase/Resources/html/sa-base.css new file mode 100644 index 0000000..a7b5e51 --- /dev/null +++ b/SacsStandardBase/Resources/html/sa-base.css @@ -0,0 +1,141 @@ +/* + Sarand Core Projects + Base css styling + From a www.sarand.com original. +*/ +body{margin:0;padding:0;font-family:Verdana,Arial;font-size:9pt;background-color:#F8F8F8;overflow:auto;} +div, button, input, select, textarea, table, tr, td {font-family:inherit;font-size:1em;} + +/* --- Common styles --- */ +h1,h2,h3{color:#0000B0;} +h1{font-size:14px;font-weight:bold;margin:6px 0 6px 0;padding:0;} +h2{font-size:12px;font-weight:bold;margin:3px 0 6px 0;padding:0;} +h3{font-size:11px;font-weight:bold;margin:3px 0 6px 0;padding:0;} +div,form{margin:0;padding:0;} +p{margin:3px 0 6px 0;} +hr{height:4px;width:90%;text-align:center;color:#000;margin:4px auto;} +a{font-size:1em;} +img{border-style:none;} +ul{margin:3px 0 6px 0;} +table{margin:0;border-collapse:separate;border-spacing:2px;border-width:0;empty-cells:inherit;} +th,td{vertical-align:top;padding:0;} +label{margin:0;} +input{margin:0;padding:0 2px 1px 2px;} +textarea{resize:none;} +.bold,b{font-weight:bold !important;} +.ul,u{text-decoration:underline !important;} +.italic,i{font-style:italic !important;} +.upper{text-transform:uppercase !important;} +.lower{text-transform:lowercase !important;} +.center{text-align:center !important;} +.right{text-align:right !important;} +.middle{vertical-align:middle !important;} +.bottom{vertical-align:bottom !important;} +.pointer{cursor:pointer;} +.action{cursor:pointer;} +.action:hover {text-decoration:underline} +td.action{text-align:center;} +.fullwidth{width:100%;} +.floatl,.floatleft{float:left;} +.floatr,.floatright{float:right;} +.clearl,.clearleft{clear:left;} +.clearr,.clearright{clear:right;} +.clearb,.clearboth{clear:both;margin:0;padding:0;width:0;height:0;line-height:0.0em;font-size:0;} +.clear-after:after +{ + visibility:hidden; + display:block; + font-size:0; + content:" "; + clear:both; + height:0; +} +.nowrap{white-space:nowrap !important;} +.spacer{height:4px;font-size:2px;} +.spacer2{height:8px;font-size:2px;} +.spacer4{height:16px;font-size:2px;} +.pt2{padding-top:2px !important;}.pt4{padding-top:4px !important;}.pt8{padding-top:8px !important;} +.pb2{padding-bottom:2px !important;}.pb4{padding-bottom:4px !important;}.pb8{padding-bottom:8px !important;} +.pl2{padding-left:2px !important;}.pl4{padding-left:4px !important;}.pl8{padding-left:8px !important;} +.pr2{padding-right:2px !important;}.pr4{padding-right:4px !important;}.pr8{padding-right:8px !important;} +.lastTabSeq{z-index:99;} +.hidden{visibility:hidden;} +.shadow +{ + -moz-box-shadow:2px 2px 3px #ccc; + -webkit-box-shadow:2px 2px 3px #ccc; + box-shadow:2px 2px 3px #ccc; /* For IE 8 */ + -ms-filter:"progid:DXImageTransform.Microsoft.Shadow(Strength=3,Direction=135,Color='#cccccc')"; /* For IE 5.5 - 7 */ + filter:progid:DXImageTransform.Microsoft.Shadow(Strength=3,Direction=135,Color='#cccccc'); +} +.slm-data-template{display:none;} + +/* --- Message panel --- */ +div.msg{margin-bottom:4px;padding:4px;border:1px solid #C0C0C0;border-radius:4px;} +div.msg.inf{color:darkgreen;} +div.msg.wrn{color:darkorange;} +div.msg.err{color:darkred;} + +/* --- Data tables --- */ +.label { + color: #606060; + font-weight: bold; +} +td.label{padding-left:4px;padding-right:8px;text-align:right;} +.value{} +td.value{padding-right:8px;} + +/* --- JQUI Dialog --- */ +.dialog { display:none; } +.ui-widget-overlay {opacity:0.25;} +.ui-widget, .ui-widget input, .ui-widget textarea { font-family:inherit; } +div.ui-dialog .ui-dialog-title { margin:0; } +.dialog input[type="text"] { width:80%; } +.dialog input[type="file"] { width:96%; } +div.ui-dialog.no-close .ui-dialog-titlebar-close { display: none; } +div.ui-dialog.ui-widget.ui-widget-content +{ + border-width:1px; + border-color:#808080; + -moz-box-shadow:4px 4px 6px #888; + -webkit-box-shadow:4px 4px 6px #888; + box-shadow:4px 4px 6px #888; + -ms-filter:"progid:DXImageTransform.Microsoft.Shadow(Strength=6,Direction=165,Color='#888888')"; + filter:progid:DXImageTransform.Microsoft.Shadow(Strength=6,Direction=165,Color='#888888'); +} +div.ui-dialog .ui-widget-header +{ + color:#404040; + background-color:rgba(247, 217, 0, 0.7); + background-image:none; + border:1px solid #C0C0C0; +} +div.ui-dialog.center-buttons .ui-dialog-buttonpane .ui-dialog-buttonset +{ + float:none; + margin:0 auto; + display:table; +} +.ui-dialog .ui-dialog-buttonpane { padding:4px; } +.ui-dialog .ui-dialog-buttonpane button +{ + min-width:80px; + margin:2px; + padding-top:2px; + padding-bottom:4px; +} +.ui-widget-content a { color:#337ab7;} +.ui-dialog .ui-dialog-content input[type="text"], +.ui-dialog .ui-dialog-content input[type="password"], +.ui-dialog .ui-dialog-content textarea, +.ui-dialog .ui-dialog-content select +{ + width:96%; + border:1px solid #808080; +} +.ui-dialog .ui-dialog-content .ui-selectmenu-button +{ + border:1px solid #808080; +} + +/* --- The End --- */ diff --git a/SacsStandardBase/Resources/html/sa-base.js b/SacsStandardBase/Resources/html/sa-base.js new file mode 100644 index 0000000..5465360 --- /dev/null +++ b/SacsStandardBase/Resources/html/sa-base.js @@ -0,0 +1,33 @@ +/* + Sarand LogisticsMgr + Base Javascript + From a www.sarand.com original. +*/ +if (typeof sacs == 'undefined') sacs = {}; + +//------------------------------------------------------------- +sacs.checkTextAreaMaxLength = function (ctrl, len) +{ + if (ctrl == null || ctrl.value == null) return; + if (ctrl.value.length > len) ctrl.value = ctrl.value.substring(0, len); +} +sacs.isDef = function (obj) +{ + return (obj === null || typeof (obj) == "undefined") ? false : true; +} +sacs.isFn = function (fn) +{ + return (fn != null && typeof (fn) == typeof (Function)); +} +sacs.isNumeric = function (n) +{ + return (!isNaN(parseFloat(n)) && isFinite(n)); +} +sacs.zeroPad = function (num, count) +{ + var val = num + ''; + while (val.length < count) val = "0" + val; + return val; +} + +//------------------------------------------------------------- diff --git a/SacsStandardBase/Resources/images/IconShock/add_green_72.png b/SacsStandardBase/Resources/images/IconShock/add_green_72.png new file mode 100644 index 0000000000000000000000000000000000000000..50c21d0d24274085ab4239b7da41f3c35a2f6103 GIT binary patch literal 2232 zcmV;p2uJscP)6+ZXg@$AEowVj9Uw4o`FqWs~d0#a#gZwaVzP*c%XKns#l4SvKi>i~+xLj)-x zA|P=#jvY4yRa9z0ZBc`$Lra>C?eZguKq7xiBQ>OHQ^$?t=RRj=?nQUqRnwiBojcx{ ztxe}jmVfNI=bZb^Ip>~x&zV8sjiLx351--)cmP>ghzF1dP-#KLv!In`K?h^)n`Efk z0f06mgx6sNYXPA(z@Q!>T(_hzon;he0I?gC!8Af}ol@-;1>z_A!;fDs%_i*4E{g>X zj(_(vSo41eW7LKa`H(#o-%HJaUZNU(k&^VW-sZ>tRSGYWxvl}U_xZNQ8mXZp;H&v2 z#^mE7%Gjl$RC_gz?RKP6#L&AZ|52wDCB>6<-xqNTXR$ z)HGzZ3@F;tf1^=Bd>|7)3!#G0t4lB(W zqmatRA)c89jjC4UhQCX=0xEsA5R|I{1YDR0@r?R*OJwU)Q_B)4B*ldDZ(e%%wkm1e zxYf4Jfk`S0IbbNG%hmHe^DrV%l>@2<5W+=yQc7P>r{-Gww=FM!T9ySxM<1z?E7zY# z2$@5PY2|;KE3;{r&D&HkZph%12i%bnLvQQ zU?xn}6qrd&g2tBTN_H1PumttNCXk41I+Zbap|knGHiUrL;cpwDq1diLLMnEe2}x%Y zFqfPHzU`KC@U)^X*Z{tYN>j0#mOauFKHO_ZFIxZ|KHc?zECru26OdHqVLml&N512< zp}@LaY%&{)mj3##@X((`1q%ao@MQa@KxN$p1c+}Ty1@hSeCoQWTvzF81M4A>+v)U! zF}9e@PJL?k)+aBCC?O2c@R`1+F_s=QVw%Y&U?%CxQAlJBhBWoTMpJjARDGtqHTsYU zb0L5Z#d_}b6aPsghCCoo$KNa|=KSm{&l&3a^S|CNvIavM9+TEpZ!}FVRitj&xh49t zl>i}thQ}gjF~XlKWX8904vtDoJ0H3M5Z9F_mTRkSHB130qvyI?20mXhK!am_Z8DOc zH)6DynSsUhtd$JQ-`xU;3$FIpLA9^mPzQyn+k0E0qs0`ow4mWLeP=P2bk@U<3vVyJ zZn=070yhET^4C}220T|P9F(f(x?7_~t*9tK2V#$Yw31X`1i(7Fh17H|Q+8sw4G_v&k?PX)OEG zo|eP=mIcu8SmYc*__KwR6AtG*ZUe;i=Luw`K?12UdcM15Ko>&tYc{o@=;$_|uWB>* zAzvXt*B9Ral`2knSB^(H0Ftnr+x87cCKG=p)Yo>>@KVYYprJFp+X$9*5uP#)xO+J{ z$^j79m%ACiDk&TqRoZ%52MzN)Q-FqJeftQJeq6{bl}*5`!&!5=0ICZ%LcriKt7+;l zy2H_*7(H}zfJ}}SPVQcLg~|nx*=|0PWypf(3+JaQES-cZPmCx=#d%4QbfD5?g`tj=ycS>#D$;h;d@>TVvm#g>bW zjP*|=UNbKk*A^}V57foRN(G2t(KgoHu2Y>ccB8Z9piw4iUK$vg*qs3ex;xCt`PVEB zVWk4Z6>kcCM6W*rrE|*xT{^=FOMtDDL$&Xn z2L1vh94Q=fo=3;}&mx2jOV8KiZ&=3C4^T zx>}nT6fGIv6+U{#>fu?sSe}8`{$c^;z^RS~zg%?@0XA&kS~U$+Ko`l-9QGHv4I-Da~Z zwuw_c(i4ul*Tl&I;%&ggV}0WUOS;@BkNQe%1Ex&-2hVw2Zv*Ba>_2f|btrWEc~s;* zuzVwVA~R=sMJsCVg>=G=$4GiW^a&Mv9g3G`FeX5dlxv&gf4p_Y?(0w*&Hi~&O0@5} z`}elp$}tPNnbF|s&K(l*A4P~XiUQ>Z9WNtmYO1IAo{GDUL3aDK2JVpVV0AVhxw0M9ba|lR}=1dyZ z=xUttJt78JYw*OG0;p8#dB_5!i2}Zi2wXLI04Wni0BW%b2glnp>KCnf^duru9sp{w zT;kP|xts?lBlXt;;35F9sKrOy&kJZc2Ou%f2b*`Y_rzu=8X;+8!16tID}os4u)c&<>}WeA^DZgQ+dM51So zx*YWOlZVo6LpQb};A?tBgpbwlpgY-YHr$t!mA$ZZ{nTq(^=SsQw4ggTXcc+@V20M> zW;y6Tc_}$N6r^)M?3nY17M+>^-FD6i1e~d*XuL5-I%v2KCBZZO1LCX=UGfcHy_x{I zB=2rSSioD1CEg4`O5GqK-Syjwo@3Dvv7Ye_aY^1PL}=n0#U5=IAVmQI(p1-8RKu<& zbe_e&xn<8QKw%dk3hbkzM^@!}E#NKr)l$LFL?b$^=Q$1)FQKyKNy4ggoNBxQ+793TKeB+b=rMHMVMnE>6s zgg6I)X)H-uViE}mkq+7?X6KcyedN5FRyIJAcPk>irIw6meTS+q z&45BjE^@UG5pAp!XN^k=KtTN&0_MKIS-uoYcU%W8k-e`A06SSH%@da#fB>in?%FoF zMH3*G?CCV}NsjvI8sF4ehTk>oK z#CLcm%@dnUfZ|8w8BvU2^REJ zw@UN5)*!tniX#b04;%#w1=?QQ?p(|T$cX1(BLPV-90f`s5M28~$DCR&KyImL9w7BH z9|~dwnhD7WIEw5WzQBtg?JoX?3y^EpDJ$4|dyP03G6RxPZ~%zUe|>CDL)Y{nE86H#gM>hej{f;bK^vWELE?#d%mK6QJ@!?*l}jzZm`aSrHKr zl38#7VaSq+Ggobt&Zu<;6QGcbByTe!xYZJB*&hv(X>e37Eh6@42SI*!>v!wHRk(=1Y`sV{K`f}83(<}71{!|kA3i7C3Io?Um_Yrp{X!0LAS834Yo zfw&!{AiC>bB&YPJfGzFuhL-#R9`|T;(5(_>4$pm}h{ploe%_+-;*D-G5+E0E`p~g< zuT%AX#Av!SIcPZ1d(!cY0leG_fD4U+WF#Qb3i~FE%}LD1Ea5<0vS&3=Y)q!lp+p;Q zSlL`ynb6On!XYgw^gfSBcOv3FdfcSfLBT-4=O2Qy#$CmodUOnr)zxB=0v0>Jon#@w zdPFEq#3z}QK-ZOvLzU~jPyJ~G99@faJON8)cRwtMLL&uOz}t4b4!ZSGFMc;V+g81F z-K-yt0%MAUBE`9#-T6R-MnF)$m1o*`03pJ$F`3rdPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D29ilcK~#8N?V5RP z6jd0&-+Oy-)S_UujZx4TL=yiKrI!SZNDqiaKu{ryLeZo#f`lVjs89|C#iRz|r)_Y~>8hs#<2`Ot?Dmq|2w}O9yMu6=BXgc3euq)oK`8H_jI~QK z`5$qno%H!Q=@zS>oe!!Gzm@3HkN?T56_oX#lrgLGc8GvbfpdX3pVvKv&WQkkkPd&Ik?~I#_OWF; z#XZGYmPr!l?(vfCOG*#jjwwxa34*ap<*2*?e%-pudlgnna=G1EH|BabVyKBOK|5D| z_n;J&r(tLdR!V}q!x>wa9lsw#RWt=b^|(9Qdsjmd8J^IS#VSdnFBiKa>oHVCQ&3&i z$%kVRc_N0kW0fShUwfqXK3iqE&H^r?@)RJQJ*i4OJzE znv^fS6i+?6gk&U9LgQa~_lI%?k-qQA(}~0-am8X#7DUM~Y^D{7NK)&o;wrYF zz~2LZse74!amPq(rJiH^&XKl#ofzqW^Il&|*TtdJA$6O*ejR76wIwwAJQLDDA}+Kwfa1Hk^K(HAJ!d;ncjkRV3b^7s#% zu}TgQlO^ry5$F?x}!Y>O*LJPY>Zj=-a5Fr|s%r#2Q8_*cRP@-+<83h?u4U9fB| zhPp5;NKjGe|BwutH~4~OvoX+(?jd$H5$_=J81TuyC@uJ7Fu!R&whfVxpj}^0 zxa5k;B~Y>_Fy&apIZ6^Q+}u*wiz!1SBuKF3Sa}F4sQ@OvMoc*lac<%qx3`x4i7CS* zGzVQzMZx}Mu-je>lXt?x&4<743zotL7w*@Sz!X7(1%>|Ua3)(|@=ob@`fv`HKuM*q zrL-PXNnzR*hZSOXj1IY5Tse4M#Yfc1 zW%={fAm<|{(`DO^l>Uk-)38L)73Sq19POg9&Cr1flu|FIO#!yV5s-ffroF}6f|E{R z+H@=xbcN@NnzLQ}LOD$2Q(=B6g~@yfzGVm)Lfuf6O`OoZ110XSZ7s9>-q037*E4Ud zKc6v@4_Q+k1VTbkEhl&+=gYd|W1{hcWXO;q fLxv0)R3YR)D@l?WMB+YF00000NkvXXu0mjfJ6t{D literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/images/IconShock/checkmark_green_72.png b/SacsStandardBase/Resources/images/IconShock/checkmark_green_72.png new file mode 100644 index 0000000000000000000000000000000000000000..3bfe9448c217c0ad65dd3f9057b364bf59e320f7 GIT binary patch literal 953 zcmV;q14jIbP)LaQOhn;*fenlu~@ zmeSydFh+qu;s?;!+MP8^)1)rT?lUvH)9ErjHuKDEfBEk-Gew&EcnTop<1L_od=jF7 z6cAq%Q3+BBQVHS{PzmDmQ$0u}NF|6*KqZLJ@7VO9g)a*k<8OTS)xGjd8nR;n^nByR z^#S9=I>z{Pv(;EC=T=Hkq{ssmwwA6Ytys;*>7dXcMH=V;M~hW4!e^a{fg(i~$P0%i zNs$DC;6RZg2SkGdL5dU*3yvRBWPl2rg^2`Cf7UTR1Kq+8HEy%4?w)^`?0P!TJ zgF%S7gHqPia%OoyQb1wi*ie0)ps;cJ8b}0A-vWukVFsH0_-I78tam78K3U7J?Xm6} zEOpP;3*&>ybj|Jh0G;oshU`{P-Gd&ye>{v3sUxIavMg;|*U8<=T;;#c4}DPDaW>A$ zM5<s5VHCBo}`_VwEybRst9SrkOkbvB=p3O@u9J_j&Qxgaf3lbEl zmpI%#h^Hq(fO?G+I1o*e{D5S@2?Yp>iR%)^$6^$L9=MSlD|B9 zYQlIuQ~F9%*Nd?|0qVsMWnEJN`N%0C1;qMtwd+)ZRDx83_ykmf`2184QVCKC;uBB_ b;`93lw#$EKnXom?00000NkvXXu0mjfpXaA( literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/images/IconShock/close_red_72.png b/SacsStandardBase/Resources/images/IconShock/close_red_72.png new file mode 100644 index 0000000000000000000000000000000000000000..662bb6542e1a4aec5d0896649e7c85742edde0dd GIT binary patch literal 2656 zcmV-m3ZM0fP)w0~5P zAOm&?B!EeYlQ=0AYSe_bQL0FZiq!D3F->_jyb3m$m+gC>?{;^pJ^PIDzGnB<*r;d8 zf9*HleDm9HzM1*v+e47~(DtO#8fx+Jf zgz^Z(3`YRN2tqjs;~d#uA`w3jv4cpY9TRc~2)PBKbcPK_K6KT+b0IySFxUFNcu=Hj z{!{@w7a^zPM+h?|=1ja-nuy{R5|4l85+b`D+v|^|ql>2PfB_0kd%Td08M_Gb3+5t+ zqm;%Nvoa(ezW}#xzKbqhtn+?xtj%nD8hP6Rk!fRGf&2*zanZ;%i09Mt7McMhr(`hL zDz#kQ?7Mt(4{B^o(6*zIvlUS1oS)>PVTBD$cJ_|}+p5}lETRNrAn-TR-d3LT!CyO! z8_-GEHj2RV`7@A<--}pg1YOqyBSi%9HcANP-fcTSwWEi$0r}_88ph=2)q^YRSz3cg zED9tP0ul-W35SyZK*+t#5d!4)0OEN-9v+a_3&@p4+XX;Z?4a0wvm*QV2ioZyDJ9ba z6fU1xB$bRhggDkP2ooX!xqS=7Yb^k=m^GWT+`tqS0~(PB2w_7VauCPYTEFLM`0%=+ zu!ewTK(Tpq#w7~!|A83&Wt)g1NH^O+y50(quql3W4!}jlfD4KMVSP|UNVIV6H>SDj z{%C3cWC;+NH7h$39n*zI{yORdz=4&wQ90Fq>FuvS}P49xh40O#atYLFt~y}p{=3(a(4 z3Q(|e{%qvS`GZym;*IMdUA}0hf7@l5v890X3$^tI{d2wB-)f-km>N)Q-pq0Fg3F>VDFl7@oryGfPeau(yoK9m56`=6a7k^4}eFxPP5;-9MyZQdd9HY@;4D2Hl zH8+Uo`{7qDK7;&U05*Hzbw45)l$7_|30B zW@MNlpzw+%&k>LNBh{(ZauvjuD@J{EKW7+l&0PSo_umFc#Od1fm*O1Yno|L>_xAw^ z0+kMCSnN2$t6(bx>%);CngmTF)KD75CKMsPS4q)As72qznrXDBsO7zVr>Yk-|R z6@=#F5L&;|JS5Hm-c8$poj3)AdOr8Ks07sU3zYwXDK2C_i8fh2;^ z%)h!_u>cvyAW|{S6)u_V2Lz7(y(hjp0T7`$_pvN^f42qLDNmY*L;=TCEONVndqjl; zfw-r&F&ou?p_+ryoMM5hMbojz`)9@U5)vRZ9yNSpy#ln5)>YjNx^~0qF-Be_5X&iS zm%^bLzS=#itC=>SRZF(uEcYtK^ty8g#IMiN1Ck|aAfn0;?#lbV)x`0m079C%Ws{I>fzuNU zxhYR5ri644UTLNLcmRZO3ku_cjGq2 zb|%?GYaF2sE8>8V63~$^; z=1C%cn%}(RVS6@;S1xzolA8UafFn<(P>3%Hk8r%2C6%!>SoLsk{3J;jn5;y2A>bl-t-1$?kNA zY-ecQ@)N)?))QuPD!^5tlQ2t7E3K*z3P{umvoYh6MGlHTopYG(Mp1qkSh=JcXSo$= z0J)@vD&?xPTNaGqPrRvyMIr#Ru6y7uV<$ut}lNM_*3=P#{mA4y0Y@oj!xJ0Glf7E9R` zShaLI&T=VIukv%r%*7$gf~$Of_obiJN;A#@SuZSrX#F1JY1K0Ty^BK>j!gH|?D|-5 z&tm97$zZ*{stFL*xOzTez2?x|Zpys0eD(b3MAdpm+peCIfR-(I8u;wLt6BJ<7B7Im z<7w}XeV-Xor}7|KNv8b80A+Y#c@Y_rcMdS-)kKF>0afXEz$5b2Mbo*ABr>=pj zG??0hdZt=A@Y(Nau^bX5czST;B|jW^-d($QztPl6JvRj?In`GEvJSbj9>^k25{vHT zx4t=WMVvAqxePeCdg+(g=~8CDd$1=1_Yf-shIss_r+UYe16~H4g!IfazR<*PH6cd( z6NF@iyr_bEv;~{5Q){-2uPZY1LOg zEf4CeD6(wc&qO!B3o&e7ItHcNR=!4NB7vpu?fd?et}eN~ZGdEDgZfFPL_qZWhwdkt zecx7K_OlhJpl}o7eZ_XXMI#*~7`R4mw5|8;KUi-hYxcRV9@M|{;Nlk+L3Ykc#Bt-z z8b%{e67lok@2KH-H|(O3w<0s3fKmq8{KEpRBp(0tf&XCvt+%E2(-Z(?7$Dr|rzxaP zL`bJ3N&g=|O<~2ghf16KGR20lBodjUjvh O0000^HSWAUy!vg8u5;tP^ZDU^i=aa{ih-RUe&u$A>pPRFF&RizDy)0Zm zzX&TVV07l_Xejb+VY{dxHAUH>x#-W0<_rPeQ-TW|E$iMmnsl%|<+!jw#{QdvluL6F zOGZH7{TXs0S!=t4P6;;Ljrmm{y|?VUxp6eZ2ajt83WAj^1yY_yQzOdj%{B+TO=iet z$nj0Re9+2l)h^jcW3~%fZuV>6>s(v4>$=nh=7!B4lQ@sst;|~SZG+?8wCuIv41Qjk z*21^`%wM|Ww|Yak@$SBB)?5K5C+9ro>}XCp7Y}p;&;<_M%~@;BjsJ*M8*aX>akhY8 zXrd*5SO2OixkeMV3qlSVG5sP}EXtOy`aMx5(P8Vg{va<6&m{|{ZG5-SWOK7eAJCmU znF|EL?%ZzoQ2BftHuQ?hC(tbuYgBybl;FHJ6uGG}^@yiA_-?vOo^b`p?Xqv#esPi}Uf77sp+Y zX02%S97`lJB1j~G{>CXxmInzDCtG8&9+pUK^CFQ0!nw}IrMml%e%8dhizms01OOA( z+S)}nKAz=4A_asqry3v^na+k9+LxM!KjlKTZBj^xW0r0AvixZvAdfo=H~_4}nNl`X zpH@L)h_+?h*8>4mj8mAD3KAlw4%&7zd7#~JG$4I$O(yq~26DquM7q(MOx`66#ET<| z^w>%!`^aw6qBw#`W4t)dxW4@)WjOQyLRh&{t2=JieXv=Iw!wG(1Rf&7kB(IrL!r(!@$A^m2U+!v=1VEnf6xWH%@p008Wcs2v=>HakM8WXgFGdf{3FH zD5yAEfP##p3>0)6C7^1+kp`+394Vk`!Vw0lHXI=!PjLXI;d@=T%C8SxCvk})u2#cy z9NfOZ&Q5JfT)*cZaRtibgleAwX1hGNnrZMc4)+qng+YYWI{@VR-y^T1?{WBnUf)L8 z{sEABuM3F;ju?Rfv<(E)>2Z-89xr~xuSYKz_^$Rx-lNKL>b gNX^geK`ws305nj9XIWUp2LJ#707*qoM6N<$g64{g-2eap literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/images/IconShock/trashcan_grey_72.png b/SacsStandardBase/Resources/images/IconShock/trashcan_grey_72.png new file mode 100644 index 0000000000000000000000000000000000000000..fda3285d9ad6148da7e08e06ce1e5e47d1c96490 GIT binary patch literal 598 zcmV-c0;&CpP)i*# z+7gZKOrnS1qd4Qvx8H0wn~=e0CjkPV-2?!r!VmxgAlV`UgJ2L0k`aJGGJcJ&L17r4 z8Dn0F=*DH(%=~SPxl7aZ-6h^7rZGTLIGf^{hSUfkEjY)3Bm&g!cG*SSremhlsdJsq zfq9VtR58^61V9EYR_8_$Eq1`=CQZ|mx}t?)DC&+)=_*O`WB^oQ6956w0SivaPhk)Y zs>h(&Y*v~$2!hh@wSF(T|N1?!+OJ|-1s@6&fQo6}HUR(xK!2Agv{;1^00cm?mVrSq z2nLm&2VtXFh_UTv6@UO}znfSXl_Ysx z(?IF7$u0soIv1(YXe zW^Xha-M8EAh=?w<2D&Q`Gk>mDtEAuWzYvkt{7-d2nu9xqTmaP6dC)1$b{%7YK`=<> k6%2wwFi1uK2FdvS0301RXYkp_yZ`_I07*qoM6N<$g11uwfB*mh literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/images/icons/arrows/arrow_left_black_16.png b/SacsStandardBase/Resources/images/icons/arrows/arrow_left_black_16.png new file mode 100644 index 0000000000000000000000000000000000000000..da6d966554a447333f014f87e863711c2fd0be1a GIT binary patch literal 415 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`+9j?LCC){ui6xo&c?uz!xv30p zMVShw3SOl-3PuKomI{UzRt9EP1||x+20&o;R@MWkjlIOv*OmP-J14t1J8w()GN2G= zfk$L90|VCw5N32*V=@CM$dc~p>&U>cv7h@-A}f&p-P6S}q~ca`g2a~x|NsC0>hNmM zoH=*?FIcdkZjxznVd3BKfPf$R+S?w7ZhXkcW2wx{CbBZdw>Ufxu-TioyO?Pb2nRo2+haG-vIxW`BP8_Q~ce=}{~ zAtfdC|LBn;|EjC1?9^|nzQ4bpeK*S#HWd|>f9|fX6W>iLetxbo=imA@Yk0~!+uM() zEaG+K7oOnVu+h23%cYD{;6?MKk4HL%-+GoYDl#yrt*d0*zVj_VFbEhtUHx3vIVCg! E0Fuq4VgLXD literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/images/icons/arrows/arrow_left_black_24.png b/SacsStandardBase/Resources/images/icons/arrows/arrow_left_black_24.png new file mode 100644 index 0000000000000000000000000000000000000000..c1508ec202e7e4db3f6e01ac5932329e8f9cd54d GIT binary patch literal 610 zcmV-o0-gPdP)WdKHUX&^NqN_A`?GB7bYATc;PFg7|cG$1W7FfcGS?Hxz}000McNliru z;|B#13Nddl&RGBe010qNS#tmY3labT3lag+-G2N4000DMK}|sb0I`n?{9y$E00Fv5 zL_t(|+U-=iiULs(tQkiXK}5y-7R>wyLw~_gM9|34+)%$m{0t4v3gT@C!>w_veYC>D!RwOXY|zf;T-uh(lzr_=EHd^j8q&@>IR z+3YTlYKDBVSWqgJg4gTC^?DU@j*nnC!Duw1OeO=j+l|ZR0wRK{s>tW_KbHB1>2yji zmy4p&C`BR>3I>A|2n48HF4N28T)ETf6yii8A%l&_<5a8F43R$_vd7~go6RPAb3C0+ zC=?24Hk+mwELVj}3QXsEXZgM}EH_x_f2t zR;v~J{a$9$>2w})7`jFJMk$lY1e?u9W^p_oMY4Xsk5Z}hzH`l;=%$lv%HT`NPnYpM w=bAg=?b3}Xmz1UA_gfHmS1Og)Psg9e2W$q(DIW_3YXATM07*qoM6N<$g0!&*@&Et; literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/images/icons/arrows/arrow_left_double_black_16.png b/SacsStandardBase/Resources/images/icons/arrows/arrow_left_double_black_16.png new file mode 100644 index 0000000000000000000000000000000000000000..adfd17d13eb60543788101436c13fc43b7fea93e GIT binary patch literal 570 zcmV-A0>%A_P)WdKHUX&^NqN_A`?GB7bYATc;PFg7|cG$1W7FfcGS?Hxz}000McNliru z;|B=`7cNgW&&mJ*010qNS#tmY3h)2`3h)6!tTdPa000DMK}|sb0I`n?{9y$E00ELo zL_t(|+RakEio!q;j!B^)QPh*5#$N;r(O3i#?X0Zrg~Q59^a=Lhn!_eVP#?qI%F1`J zJ}{kFYcaRqqKE6{!j(78vNQYr%*-mvzX~GKaBjC-mP{t8-|v&pGnUKcsM&1t=T!eu zJsyt$QK3+vVzDR@a8ibxX_}Nyr|En?Gf8>B-+Q>0N+pWN<1`wLK5zB9>$;@tx&YT| zH5v>COj14^4ni3UsZ@%l)9Ir+%jfgL70!0MErU0k%{#!AN`(@M1kLAj@1|@yjziIC zR1h~B4H^!IGWc{l31u+HVli4Q7T?N|NQA*Mz!=%GVc zU&7(Aa=Ba--aiKX;k&-&aw&!bm25Ukv)SxXiOS`&$oG0ZQhtloYK7tz8>m*R5(!uL zkk4c?SKGER6C=WdKHUX&^NqN_A`?GB7bYATc;PFg7|cG$1W7FfcGS?Hxz}000McNliru z;|B#12?LDP%o+dy010qNS#tmY3labT3lag+-G2N4000DMK}|sb0I`n?{9y$E00QMn zL_t(|+U-@lN^4ONogI%F@fi|d_>73>gwuvU|@jP*Vow# z*Vx!7xOhBn<9uEYkB^Vs+}sQ&`uzN){r!C}ICpn<6EoB8?Jb?0oJbPO4Sjca$Ki08 zLZJ|Se}9u@S@NxAU0q$=)YK%wf%*FSLf7scTwY%C=;$Z~0s#TT(dYBg+}xZ4S1y+s z!5bPH1Y@v~NQ9=Rr&Y}ax;=0x!C`QEe}AXh*;z`b)5_qA#Ui)0wUOWNw<|}NBO@cA z$Hu9jm5+~)6C4dCN-3J3pQr8ZZ3h+u)zwR9NPySb*-2+-XAYdBgUQKBYH4W^r~3MO z`7SOlI)XnuJb-T*s0se|_D0cYv?{or;Ns$f+uPfr7;(aTeSM|X)m7Tu+_atY`8@aa z^~vBxHvC(Si9~`94i2hVqTytTDerTNy@R{7iD;2;kT4IvpX zEG&5LhNat*fr4{X1bM|`G1}PJsH#YJDKmj#J{=t$w7a{jyb}15$)qZkN(8API8<|f zeoo8F%T<+uYcqV{K%<_UX9vpAq$$F}NQH2QrBW%{+1dG7ArB7^i*#X-DSjUv9VyoY z#xRTC-d;&!1_;P4!a;Y(56odjg5`5X97KJ1t5~312}?$!^1;bT3T{AYyMR>o0SA+lhMi4 z)D*iWFiCE1Zs_Uh$vzXH?DX^$d-1bEWjZkG(z`-4Gcz{MRbsd`SgK$!D8Er5o~E(N zN1q7RJ7OO^sB3R;4+>MR{}0d!c-9KDPX}HtD-;Uq`uf_wNszqTX?HW(;I*|i@2#~g j+$f%ynBYGR{O{W@io=S@Q7|iy00000NkvXXu0mjf9#fvt literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/images/icons/arrows/arrow_right_black_16.png b/SacsStandardBase/Resources/images/icons/arrows/arrow_right_black_16.png new file mode 100644 index 0000000000000000000000000000000000000000..ee81bfdf20eb60e0c631b743a5552cc3e8385bbb GIT binary patch literal 420 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`+9j?LCC){ui6xo&c?uz!xv30p zMVShw3SOl-3PuKomI{UzRt9EP1||x+20&o;R@MWkjlIOv*OmP-J14ui=s#PvcR(S| z0*}aI1_rJVAk65r#$*OikR{#G*O7r?V?XzwL{=dGkEe@cNX4zQLE7t@0|ZppDhhCP z3H{@2Te8S{ffx@{lcVAd_D8x46ie4E(q0hD80!|8;HkB&Jg@8JY3@6#%Fpyz?mt;@ zXTS3#74_TO8rPIFteC-C61!gKG}HA`M*)@uvCLU%^2ZH37X2{s6>d^U5O-XX$u;9h zq09u8&e#@#GS|Qcozwn@4L+z{Uq5L|s^{bc*?G_FRd4=rJ+mpNd-MC>(tbq&d9&1F z^jF{A*tAH)gmIfx8>5)#=3)_7{?O2a-)p_{Zhw1TJNxY-k9p4*WJ>*IebV!I#~T}E zp2Nmg)$`m>#$LA#vsON_b@k24(C7YFN^6gltG#YK`7?SC!;ue_jCG%rQi0*X;OXk; Jvd$@?2>=95o&*2@ literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/images/icons/arrows/arrow_right_black_24.png b/SacsStandardBase/Resources/images/icons/arrows/arrow_right_black_24.png new file mode 100644 index 0000000000000000000000000000000000000000..7d689e108c9415b15f44d60a9a653f4b6b750544 GIT binary patch literal 613 zcmV-r0-F7aP)WdKHUX&^NqN_A`?GB7bYATc;PFg7|cG$1W7FfcGS?Hxz}000McNliru z;|B#13jt8=&$Ivl010qNS#tmY3labT3lag+-G2N4000DMK}|sb0I`n?{9y$E00F&8 zL_t(|+U-=is=`nZoh+gjB4VM&4GU|(!ODLS3lTx9lv>;RC4PlqWoIb}BJR7E;yz~P z@vLG@qRoSM;Bu3jB8?Ns{nWE=dsyr@|h3>f#=C&0@LaA z?YD0gq|<2}4u?N4YviPWc78k_O{q6jKs)dE`*68jxIJlJE|*-uETmTtve_)#;&!|F zp8iX#)rt#f;hN4)S3#jrz;?UMmYmOL_I$lw!)P?JME){^P$-1djx-3n-4156nPqZ< zthY!#{@d@{?UuX64*5%zN+mwE+U56t1%DO~U{Bj0359}T00000NkvXXu0mjfITZ&G literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/images/icons/arrows/arrow_right_double_black_16.png b/SacsStandardBase/Resources/images/icons/arrows/arrow_right_double_black_16.png new file mode 100644 index 0000000000000000000000000000000000000000..06760b632f95c9591662a20645140174c6e2bf71 GIT binary patch literal 573 zcmV-D0>b@?P)WdKHUX&^NqN_A`?GB7bYATc;PFg7|cG$1W7FfcGS?Hxz}000McNliru z;|B=`7dg!3N(}%2010qNS#tmY3h)2`3h)6!tTdPa000DMK}|sb0I`n?{9y$E00EUr zL_t(|+Qn15io#G3y(Wc%sHiJJjZXxNMPnf%+F4oK3yYPN=oj3DZ5EppLH!JSE35oN z>V?>ewHC8?hP_K-Zn?1280T@$nKKDv{~iGF+U<6sTrPw7%b)9$$pmt_9Gp%k{?o6A z8g926FBA%3nkLk0HEa%nV6IxNA~^WI4@wo&>6EwIZ30Q9Qs6iapfaq@=ku>jCIj_) zoj_dIy(pCo1_NHHREQ0KgX^qTE6C^bq!4F4&r>Uz&1SsQ=}==bnFQOmQHh1M&1Q4S zX0y<2HVNc-Jboxa7K;T(LlFd5N+c4$O19fAq|<4s5U$rNXwLw`s88VoyWj6D9*;9I z@FHtM`kU~Ao;6Kl=kxhJqF_88L#b4PMx#M2GQVE0Ip$FvD%WdKHUX&^NqN_A`?GB7bYATc;PFg7|cG$1W7FfcGS?Hxz}000McNliru z;|B#12rkeOp)LRb010qNS#tmY3labT3lag+-G2N4000DMK}|sb0I`n?{9y$E00Qqx zL_t(|+U-?4ODj>Ez$Ub>L@fYm3j%&xx5S7!2Z7Rp)FrOJ84Kq)Eu<^YQKNE&r83XG7oL z-&8CXqFc6WCv8jY%o z3JmP+?Ww-LJ_IzK(Rh!?!#6iKW`-*(D{6LjR{nRp-Lf_|Hqy}0kiA5uSYKaPV`F1- z4-%lgy`4HbI_UE9(j-}0T2c!O3-tQ>Dg?OnWRc(R=flH8ww0(9Gcz;1y}d1c0TJYS zdU~Sg=Vy!G^71n4ibPW2^YfD)A0IJYwFv+%7bZ!ZL4xbz;-bQDYHErP4i2R8utiVS z*4FGLGNAQjlyB@2RD#}=V{UGa_xJb34tokCj!GvcCREi5;_*12o}SX%+nYSY3D4!S z&d<;D#>R%+BRDF=)aiks*49>gg5lv|J~=s&sd#yLp{AxL39e_~Vn04U&OfB4gxA;C zNUHjn_V)HNngcnYqdxqgc|~w!etdi^4TT+6WDSB+ILk9fd8G}-A*h`#!IQ}(Z*Fd4 zZ^$$HqYphH(7W1VYgRBiI?8&+&2o(j$~D(>&pSIiVq+W^Nb2nDtRlGStQ~lIdYZgm zuQUqIde%uF85ItpP>7991?2O2Y(CrIfXgTmQ)0-1#_848!8jIRcYl8`{izn*f`Nen t)@KfDX=;8zQmGV&!(rI`>WlX8;}0Q@s|$GF&!zwX002ovPDHLkV1oRHpCSMN literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/images/icons/burgers/burger_black_128.png b/SacsStandardBase/Resources/images/icons/burgers/burger_black_128.png new file mode 100644 index 0000000000000000000000000000000000000000..67f2270a19605b63ad5b20c3e51242ab9f007130 GIT binary patch literal 857 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRS+9j?LCC){ui6xo&c?uz!xv31k zK!%BeTWXSmk%6J5f}xp}sj-!jnS!nX5X|K2`3cm^d(!Iw!PVb%0@m~OFAq+?eL{8&bke8}{sxF*7rZI~HGCwV``15cK!;-8&|4EV<)~^zpA> zzrJx)t4~jVewu+N=+^V=ckaX#3%+^({`tL!|6jd$u|kU9o-LuE==|M;)Kt}bYd0^q zjbyld`SM@62_+M@Zrxh`_Sv?#mv29O`0(JzkA>GJPd#E^T3Y&b+v~|ay}h*@%o}E@ z)~l(h-P%_e{fNOz$}g*4hq}ba#KryVInNj${nz(2Ye8{waR2=8zis)fzFVBT zUwM3eUd?6`JG+0DJO|3u=Bid*%jyH_Z*^L@Z^w=wdmoFs#GgHL#^l+D=y3H;?F?<6 z176eq+`Mxq=eIzG{_fqoYvp^M_&sJUSo`{9zj~!e@SOSc^XF`_V_=>v))`&*xuT$8 z!_}`(?nlPP=GHCWvt`Sb?~Goj@A&!prf!W|TXcV??vq%at65+FzIpTJ^$*F&P)6XWZvy}T zI}jdll*)x0_0twrHwfZgSJ^?rvC{;%c-%QcRBVhfI_e@0fK(mqjgRB7#NvTRX=TqWiZlt|MJ5dp|BvX)AxT6ZWbkW9$ zvNLvFr!M4P;{9~+fI$Q+h+$6?dbg)a<5v~3BnJbq%PyC+wE~4nW|^Ju5Vcf9l&|J0 zI&Yn^U1x~pg}pO($73Q$#->Hy{AB)`6l&f%G&FQ~=yMopkLid9KZP$p09zCnbb`U) z30*Sx0BI9`S}Nrvr=?jL0f8N%im5Tt>r;rRGxt8+U-6TDaGU*mG+Nj0%xqk3O|7(6 zf$rJiSjkJ6I1=Vth+BB`-puk4i#8c~k1e2-Sl1=xg}1J1(nllfYZa%>%WNTalW(mH zWfdlLzL5vMHGqyG*WI4L_H-sq`&D^KHfrXG=d8J-vj=P{)R$S}_rf?_Xyjb*$VSP{UUWd#?UtdlM`6{^@q2YtL)N+(PW_A3& zFN~*qE8qRu^raO)A2>>j6+CJ;`8JplppT3JQC)!6F1yOES+EN|E3VCE$8wg@AF}6X z;;%%q$3}s-y3Mp5j9A3>e#Ez>CBi2HbQGB0?V!anav*u~Qk`+%dV71>Tdu59)4W`rN6ePXPff8aG+Uae={-i3$IEbza+-uf^biEm|-XWApWEm-O*NW=9b zSbS|(b@~^HjI%zGVMbM+Yx$B8N+WwkeNUiSj_8vB8GUv#(N; zlRfrovUD7Ptjd`w3@;ZnW7zny?4?HS;7#A~WjEuYZLp;w6&Iy#!r`FfweQ}4 zgR*SV?N#K!`*S^G^Cg&6(@5vK3-=Pi4)hQleX%dx?`z_b;Br3%bNEW*$ieikb1*hk g{rkB7lPW3e01P2CS*_;AKN|zYu@mry-_G9n7uh)ofdBvi literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/images/icons/burgers/burger_black_32.png b/SacsStandardBase/Resources/images/icons/burgers/burger_black_32.png new file mode 100644 index 0000000000000000000000000000000000000000..d2c7850e98a775d815b43a92e42fbd6abb8539da GIT binary patch literal 285 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz?Go3B66d1S#FEVXJcW?V+*Af% zAj3q#Ej3BO$iUE2!O+ag)Y!_%OhMNG2xfBi`~+%aFY)wsWq-`XB4EL6E#}z;6yhxK zh%9Dc;Q9cB2 zt9O6(vpL~ylUK#<|8by#q4UJcz4NC@B`*m&b@q(6J)d18`vb-bh6BtW7;G30l*uho XwS1(r>*@)h0~kDA{an^LB{Ts5L)u=R literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/images/icons/burgers/burger_black_48.png b/SacsStandardBase/Resources/images/icons/burgers/burger_black_48.png new file mode 100644 index 0000000000000000000000000000000000000000..40491f2a46f2c98aa102ae43cd3d34b6906c2cf1 GIT binary patch literal 359 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC?Go3B66d1S#FEVXJcW?V+*Af% zAj3q#Ej3BO$iUE2!O+ag)Y!_%OhMNG2xfBi`~+%aFY)wsWq-`XB4DofGmsNxEN6j7 zWHAE+*9Q=0bXsFF11QLn?&#~tz_78O`%fY(kiXy4#WAGf*4t|bIhzecj(yz1d9>$p z@dMUXNAqlk6)ijP5pPuKjU`TuhV&_cCFLfePEML z`OAl9%g#tB+uk|ObNItHhi`k@1@vOC&wXSRFL&y1MD|sSzPKtr2J6M#oT>}CJzMlt wBzFlk?7Gm}GU-CAij(*xo>&fshFLa@x*z=e3RgwE0eXeO)78&qol`;+02acBVgLXD literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/images/icons/burgers/burger_black_64.png b/SacsStandardBase/Resources/images/icons/burgers/burger_black_64.png new file mode 100644 index 0000000000000000000000000000000000000000..fdc5fa2db79d4590747757c708c53fdf93c0b549 GIT binary patch literal 438 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=+9j?LCC){ui6xo&c?uz!xv31k zK!%BeTWXSmk%6J5f}xp}sj-!jnS!nX5X|K2`3cmbnWCl=>CEd~2k%3`jKlh(RRt5$}K2I0NkcwMxZ`kuS8OXFfoR+}E zEptfnMnv!C=SgYl$qyNh7_4IdDD-BQmftCPR-v3V?Jj9>k{b>Z{dKirS? zcP#SQa=4x+YT8q^(?*H6x4rwBUK|@P;(C$A!b8({?`B4Z8O?SJ_-h+>zGs?y<_qIn z)?W<28rXx+14&QEm&^!uke8d@ zBeIx*f$IYZGdis?nE@1JNq6*hWMJ6X&;2Knm4Sirw5N+>NX4zUcMP)+IY=D)80@^b zBx}Eu`*Izp>wbJzTusgQomY5^?{A*myxIGE!2SkTtFR*rcYDab{<^8uKxO*8J?TIG zKRBq`c768|1)CKjKr0|XZ`Gvd3TwX}F53BLj^FaE%{Ny>tyQz#zWvqLU0GYBvh3!& z@4ox)?|t2-JeAixoHM5+^F8}OS@Qg1HmU`^Jzx{s8 z#S9T|{v>R^gY;9$edS&vc2E44?l_em}Vbi4yy?_}9%5I(n{d z|NSb1lE_b!x1Ec$KjLTE=h*i6cT|I%dRm3XOny3&`XKOM4Fj(N`d9uj*Me%@Fp{>0=+ZhPJ* Okcg+NpUXO@geCwTs|zdu literal 0 HcmV?d00001 diff --git a/SacsStandardBase/Resources/jquery/jquery-1.12.1.min.js b/SacsStandardBase/Resources/jquery/jquery-1.12.1.min.js new file mode 100644 index 0000000..a5195a3 --- /dev/null +++ b/SacsStandardBase/Resources/jquery/jquery-1.12.1.min.js @@ -0,0 +1,5 @@ +/*! jQuery v1.12.1 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="1.12.1",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}if(f=d.getElementById(e[2]),f&&f.parentNode){if(f.id!==e[2])return A.find(a);this.length=1,this[0]=f}return this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||(e=n.uniqueSort(e)),D.test(a)&&(e=e.reverse())),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=!0,c||j.disable(),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.addEventListener?(d.removeEventListener("DOMContentLoaded",K),a.removeEventListener("load",K)):(d.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(d.addEventListener||"load"===a.event.type||"complete"===d.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll)a.setTimeout(n.ready);else if(d.addEventListener)d.addEventListener("DOMContentLoaded",K),a.addEventListener("load",K);else{d.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&d.documentElement}catch(e){}c&&c.doScroll&&!function f(){if(!n.isReady){try{c.doScroll("left")}catch(b){return a.setTimeout(f,50)}J(),n.ready()}}()}return I.promise(b)},n.ready.promise();var L;for(L in n(l))break;l.ownFirst="0"===L,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c,e;c=d.getElementsByTagName("body")[0],c&&c.style&&(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",l.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(e))}),function(){var a=d.createElement("div");l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}a=null}();var M=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b},N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0; +}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},Z=/^(?:checkbox|radio)$/i,$=/<([\w:-]+)/,_=/^$|\/(?:java|ecma)script/i,aa=/^\s+/,ba="abbr|article|aside|audio|bdi|canvas|data|datalist|details|dialog|figcaption|figure|footer|header|hgroup|main|mark|meter|nav|output|picture|progress|section|summary|template|time|video";function ca(a){var b=ba.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}!function(){var a=d.createElement("div"),b=d.createDocumentFragment(),c=d.createElement("input");a.innerHTML="
a",l.leadingWhitespace=3===a.firstChild.nodeType,l.tbody=!a.getElementsByTagName("tbody").length,l.htmlSerialize=!!a.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==d.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,b.appendChild(c),l.appendChecked=c.checked,a.innerHTML="",l.noCloneChecked=!!a.cloneNode(!0).lastChild.defaultValue,b.appendChild(a),c=d.createElement("input"),c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),a.appendChild(c),l.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!!a.addEventListener,a[n.expando]=1,l.attributes=!a.getAttribute(n.expando)}();var da={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:l.htmlSerialize?[0,"",""]:[1,"X
","
"]};da.optgroup=da.option,da.tbody=da.tfoot=da.colgroup=da.caption=da.thead,da.th=da.td;function ea(a,b){var c,d,e=0,f="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,ea(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function fa(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}var ga=/<|&#?\w+;/,ha=/r;r++)if(g=a[r],g||0===g)if("object"===n.type(g))n.merge(q,g.nodeType?[g]:g);else if(ga.test(g)){i=i||p.appendChild(b.createElement("div")),j=($.exec(g)||["",""])[1].toLowerCase(),m=da[j]||da._default,i.innerHTML=m[1]+n.htmlPrefilter(g)+m[2],f=m[0];while(f--)i=i.lastChild;if(!l.leadingWhitespace&&aa.test(g)&&q.push(b.createTextNode(aa.exec(g)[0])),!l.tbody){g="table"!==j||ha.test(g)?""!==m[1]||ha.test(g)?0:i:i.firstChild,f=g&&g.childNodes.length;while(f--)n.nodeName(k=g.childNodes[f],"tbody")&&!k.childNodes.length&&g.removeChild(k)}n.merge(q,i.childNodes),i.textContent="";while(i.firstChild)i.removeChild(i.firstChild);i=p.lastChild}else q.push(b.createTextNode(g));i&&p.removeChild(i),l.appendChecked||n.grep(ea(q,"input"),ia),r=0;while(g=q[r++])if(d&&n.inArray(g,d)>-1)e&&e.push(g);else if(h=n.contains(g.ownerDocument,g),i=ea(p.appendChild(g),"script"),h&&fa(i),c){f=0;while(g=i[f++])_.test(g.type||"")&&c.push(g)}return i=null,p}!function(){var b,c,e=d.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b]=c in a)||(e.setAttribute(c,"t"),l[b]=e.attributes[c].expando===!1);e=null}();var ka=/^(?:input|select|textarea)$/i,la=/^key/,ma=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,na=/^(?:focusinfocus|focusoutblur)$/,oa=/^([^.]*)(?:\.(.+)|)/;function pa(){return!0}function qa(){return!1}function ra(){try{return d.activeElement}catch(a){}}function sa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)sa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=qa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return"undefined"==typeof n||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(G)||[""],h=b.length;while(h--)f=oa.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=oa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(i=m=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!na.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),h=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),l=n.event.special[q]||{},f||!l.trigger||l.trigger.apply(e,c)!==!1)){if(!f&&!l.noBubble&&!n.isWindow(e)){for(j=l.delegateType||q,na.test(j+q)||(i=i.parentNode);i;i=i.parentNode)p.push(i),m=i;m===(e.ownerDocument||d)&&p.push(m.defaultView||m.parentWindow||a)}o=0;while((i=p[o++])&&!b.isPropagationStopped())b.type=o>1?j:l.bindType||q,g=(n._data(i,"events")||{})[b.type]&&n._data(i,"handle"),g&&g.apply(i,c),g=h&&i[h],g&&g.apply&&M(i)&&(b.result=g.apply(i,c),b.result===!1&&b.preventDefault());if(b.type=q,!f&&!b.isDefaultPrevented()&&(!l._default||l._default.apply(p.pop(),c)===!1)&&M(e)&&h&&e[q]&&!n.isWindow(e)){m=e[h],m&&(e[h]=null),n.event.triggered=q;try{e[q]()}catch(s){}n.event.triggered=void 0,m&&(e[h]=m)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.rnamespace||a.rnamespace.test(g.namespace))&&(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]","i"),va=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,wa=/\s*$/g,Aa=ca(d),Ba=Aa.appendChild(d.createElement("div"));function Ca(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function Da(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function Ea(a){var b=ya.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Ga(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(Da(b).text=a.text,Ea(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&Z.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}function Ha(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&xa.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(o&&(k=ja(b,a[0].ownerDocument,!1,a,d),e=k.firstChild,1===k.childNodes.length&&(k=e),e||d)){for(i=n.map(ea(k,"script"),Da),h=i.length;o>m;m++)g=k,m!==p&&(g=n.clone(g,!0,!0),h&&n.merge(i,ea(g,"script"))),c.call(a[m],g,m);if(h)for(j=i[i.length-1].ownerDocument,n.map(i,Ea),m=0;h>m;m++)g=i[m],_.test(g.type||"")&&!n._data(g,"globalEval")&&n.contains(j,g)&&(g.src?n._evalUrl&&n._evalUrl(g.src):n.globalEval((g.text||g.textContent||g.innerHTML||"").replace(za,"")));k=e=null}return a}function Ia(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(ea(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&fa(ea(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(va,"<$1>")},clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!ua.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(Ba.innerHTML=a.outerHTML,Ba.removeChild(f=Ba.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=ea(f),h=ea(a),g=0;null!=(e=h[g]);++g)d[g]&&Ga(e,d[g]);if(b)if(c)for(h=h||ea(a),d=d||ea(f),g=0;null!=(e=h[g]);g++)Fa(e,d[g]);else Fa(a,f);return d=ea(f,"script"),d.length>0&&fa(d,!i&&ea(a,"script")),d=h=e=null,f},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.attributes,m=n.event.special;null!=(d=a[h]);h++)if((b||M(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k||"undefined"==typeof d.removeAttribute?d[i]=void 0:d.removeAttribute(i),c.push(f))}}}),n.fn.extend({domManip:Ha,detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return Y(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||d).createTextNode(a))},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(ea(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return Y(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(ta,""):void 0;if("string"==typeof a&&!wa.test(a)&&(l.htmlSerialize||!ua.test(a))&&(l.leadingWhitespace||!aa.test(a))&&!da[($.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ea(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ha(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(ea(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],f=n(a),h=f.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(f[d])[b](c),g.apply(e,c.get());return this.pushStack(e)}});var Ja,Ka={HTML:"block",BODY:"block"};function La(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function Ma(a){var b=d,c=Ka[a];return c||(c=La(a,b),"none"!==c&&c||(Ja=(Ja||n("