From effd96143fbffda9493899dffaee78ac9bc65234 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Fri, 16 Dec 2022 06:01:23 +0000 Subject: [PATCH] --- server/ConfigureSwaggerOptions.cs | 54 + .../ControllerHelpers/ApiCreatedResponse.cs | 19 + .../ApiCustomExceptionFilter.cs | 106 ++ server/ControllerHelpers/ApiDetailError.cs | 30 + server/ControllerHelpers/ApiError.cs | 37 + server/ControllerHelpers/ApiErrorResponse.cs | 122 ++ .../ApiNotAuthorizedResponse.cs | 34 + server/ControllerHelpers/ApiOkResponse.cs | 10 + server/ControllerHelpers/ApiServerState.cs | 220 +++ .../ControllerHelpers/ApiUploadProcessor.cs | 180 ++ server/ControllerHelpers/Authorized.cs | 228 +++ .../DisableFormValueModelBindingAttribute.cs | 50 + .../MultipartRequestHelper.cs | 77 + server/ControllerHelpers/UserIdFromContext.cs | 16 + .../ControllerHelpers/UserNameFromContext.cs | 20 + .../ControllerHelpers/UserRolesFromContext.cs | 17 + .../UserTranslationIdFromContext.cs | 15 + .../ControllerHelpers/UserTypeFromContext.cs | 17 + server/Controllers/ApiRootController.cs | 520 ++++++ server/Controllers/AttachmentController.cs | 666 ++++++++ server/Controllers/AuthController.cs | 717 ++++++++ .../AuthorizationRolesController.cs | 69 + server/Controllers/BackupController.cs | 160 ++ server/Controllers/CustomerController.cs | 233 +++ server/Controllers/CustomerNoteController.cs | 137 ++ .../CustomerNotifySubscriptionController.cs | 206 +++ server/Controllers/DashboardViewController.cs | 124 ++ .../DataListColumnViewController.cs | 136 ++ server/Controllers/DataListController.cs | 187 +++ .../DataListSavedFilterController.cs | 189 +++ server/Controllers/EnumListController.cs | 585 +++++++ server/Controllers/EventLogController.cs | 196 +++ server/Controllers/ExportController.cs | 160 ++ server/Controllers/FormCustomController.cs | 221 +++ .../FormFieldsDefinitionsController.cs | 67 + .../Controllers/FormUserOptionsController.cs | 135 ++ .../GlobalBizSettingsController.cs | 128 ++ .../GlobalOpsBackupSettingsController.cs | 114 ++ ...GlobalOpsNotificationSettingsController.cs | 137 ++ server/Controllers/HeadOfficeController.cs | 159 ++ server/Controllers/HealthController.cs | 43 + server/Controllers/ImportController.cs | 193 +++ server/Controllers/IntegrationController.cs | 246 +++ server/Controllers/JobOperationsController.cs | 281 ++++ server/Controllers/KPIController.cs | 85 + server/Controllers/LogFilesController.cs | 163 ++ server/Controllers/LogoController.cs | 222 +++ server/Controllers/MemoController.cs | 228 +++ server/Controllers/NameController.cs | 68 + server/Controllers/NotifyController.cs | 353 ++++ .../NotifySubscriptionController.cs | 187 +++ server/Controllers/PickListController.cs | 284 ++++ server/Controllers/ReminderController.cs | 164 ++ server/Controllers/ReportController.cs | 425 +++++ server/Controllers/ReviewController.cs | 168 ++ server/Controllers/ScheduleController.cs | 404 +++++ server/Controllers/SearchController.cs | 145 ++ server/Controllers/ServerMetricsController.cs | 361 ++++ server/Controllers/ServerStateController.cs | 363 ++++ server/Controllers/TagController.cs | 370 ++++ server/Controllers/TranslationController.cs | 569 +++++++ server/Controllers/UserController.cs | 385 +++++ server/Controllers/UserOptionsController.cs | 206 +++ server/DataList/AttachmentDataList.cs | 81 + server/DataList/CustomerDataList.cs | 326 ++++ server/DataList/CustomerNoteDataList.cs | 87 + ...CustomerNotificationDeliveryLogDataList.cs | 75 + server/DataList/DataListFactory.cs | 56 + server/DataList/DataListFetcher.cs | 310 ++++ server/DataList/DataListField.cs | 17 + server/DataList/DataListFieldDefinition.cs | 94 ++ .../DataListFilterComparisonOperator.cs | 43 + server/DataList/DataListProcessingBase.cs | 210 +++ server/DataList/DataListReturnData.cs | 27 + .../DataListSqlFilterCriteriaBuilder.cs | 1179 +++++++++++++ .../DataListSqlFilterOrderByBuilder.cs | 51 + server/DataList/DataListSqlSelectBuilder.cs | 126 ++ server/DataList/EventDataList.cs | 75 + server/DataList/HeadOfficeDataList.cs | 240 +++ server/DataList/IDataListInternalCriteria.cs | 11 + server/DataList/IDataListProcessing.cs | 23 + server/DataList/InsideUserDataList.cs | 138 ++ server/DataList/IntegrationDataList.cs | 41 + server/DataList/MemoDataList.cs | 123 ++ .../NotificationDeliveryLogDataList.cs | 84 + server/DataList/OutsideUserDataList.cs | 151 ++ server/DataList/ReminderDataList.cs | 103 ++ server/DataList/ReportDataList.cs | 65 + server/DataList/ReviewDataList.cs | 208 +++ server/DataList/TranslationDataList.cs | 45 + server/PickList/AyaPickList.cs | 30 + server/PickList/AyaPickListFieldDefinition.cs | 42 + server/PickList/CustomerPickList.cs | 86 + server/PickList/HeadOfficePickList.cs | 56 + server/PickList/IAyaPickList.cs | 20 + server/PickList/IAyaPickListVariant.cs | 7 + server/PickList/PickListFactory.cs | 71 + server/PickList/PickListFetcher.cs | 101 ++ server/PickList/PickListOptions.cs | 73 + server/PickList/PickListSqlBuilder.cs | 318 ++++ server/PickList/ReportPickList.cs | 56 + server/PickList/UserPickList.cs | 80 + server/Program.cs | 339 ++++ server/Startup.cs | 684 ++++++++ server/appsettings.Development.json | 10 + server/appsettings.json | 17 + server/biz/ApiErrorCode.cs | 60 + server/biz/ApiErrorCodeStockMessage.cs | 95 ++ server/biz/AttachmentBiz.cs | 248 +++ server/biz/AuthorizationRoles.cs | 621 +++++++ server/biz/BizObject.cs | 101 ++ server/biz/BizObjectExistsInDatabase.cs | 75 + server/biz/BizObjectFactory.cs | 70 + server/biz/BizObjectNameFetcherDirect.cs | 54 + server/biz/BizRoleSet.cs | 15 + server/biz/BizRoles.cs | 581 +++++++ server/biz/CoreBizObjectAttribute.cs | 22 + server/biz/CustomFieldType.cs | 40 + server/biz/CustomFieldsValidator.cs | 121 ++ server/biz/CustomerBiz.cs | 668 ++++++++ server/biz/CustomerNoteBiz.cs | 303 ++++ server/biz/CustomerNotifySubscriptionBiz.cs | 187 +++ server/biz/DashboardViewBiz.cs | 122 ++ server/biz/DataListColumnViewBiz.cs | 196 +++ server/biz/DataListSavedFilterBiz.cs | 261 +++ server/biz/EventLogProcessor.cs | 157 ++ server/biz/FormCustomBiz.cs | 334 ++++ server/biz/FormFieldReference.cs | 383 +++++ server/biz/FormUserOptionsBiz.cs | 158 ++ server/biz/GlobalBizSettingsBiz.cs | 121 ++ server/biz/GlobalOpsBackupSettingsBiz.cs | 128 ++ .../biz/GlobalOpsNotificationSettingsBiz.cs | 110 ++ server/biz/HeadOfficeBiz.cs | 597 +++++++ server/biz/IBizObject.cs | 51 + server/biz/IExportAbleObject.cs | 21 + server/biz/IImportAbleObject.cs | 15 + server/biz/IJobObject.cs | 22 + server/biz/INotifiableObject.cs | 12 + server/biz/IReportAbleObject.cs | 21 + server/biz/ISearchAbleObject.cs | 16 + server/biz/ImportableBizObjectAttribute.cs | 15 + server/biz/IntegrationBiz.cs | 253 +++ server/biz/JobOperationsBiz.cs | 145 ++ server/biz/JobStatus.cs | 19 + server/biz/JobType.cs | 37 + server/biz/JobsBiz.cs | 412 +++++ server/biz/MemoBiz.cs | 511 ++++++ server/biz/NotifyDeliveryMethod.cs | 20 + server/biz/NotifyEventHelper.cs | 406 +++++ server/biz/NotifyEventType.cs | 40 + server/biz/NotifyMailSecurity.cs | 21 + server/biz/NotifySubscriptionBiz.cs | 256 +++ server/biz/PickListBiz.cs | 303 ++++ server/biz/PrimeData.cs | 147 ++ server/biz/ReminderBiz.cs | 544 ++++++ server/biz/ReportBiz.cs | 900 ++++++++++ server/biz/ReportPaperFormat.cs | 21 + server/biz/ReportRenderType.cs | 13 + server/biz/ReportableBizObjectAttribute.cs | 15 + server/biz/RequiredFieldsValidator.cs | 141 ++ server/biz/ReviewBiz.cs | 691 ++++++++ server/biz/Search.cs | 998 +++++++++++ .../SearchTranslationWordBreakDataCache.cs | 77 + server/biz/SockDaysOfWeek.cs | 25 + server/biz/SockEvent.cs | 37 + server/biz/SockType.cs | 85 + server/biz/SockTypeId.cs | 113 ++ server/biz/TagBiz.cs | 347 ++++ server/biz/TranslationBiz.cs | 690 ++++++++ server/biz/UiFieldDataType.cs | 28 + server/biz/UserBiz.cs | 1017 +++++++++++ server/biz/UserOptionsBiz.cs | 155 ++ server/biz/UserType.cs | 14 + server/biz/ValidationError.cs | 19 + server/generator/BackgroundService.cs | 69 + server/generator/CoreIntegrationLogSweeper.cs | 48 + server/generator/CoreJobBackup.cs | 156 ++ server/generator/CoreJobCustomerNotify.cs | 285 ++++ server/generator/CoreJobMetricsSnapshot.cs | 190 +++ server/generator/CoreJobNotify.cs | 411 +++++ ...CoreJobReportRenderEngineProcessCleanup.cs | 41 + server/generator/CoreJobSweeper.cs | 149 ++ server/generator/CoreJobTempFolderCleanup.cs | 56 + server/generator/CoreNotificationSweeper.cs | 64 + server/generator/Generate.cs | 81 + server/kpi/IAyaKPI.cs | 17 + server/kpi/KPIFactory.cs | 33 + server/kpi/KPIFetcher.cs | 158 ++ server/kpi/KPIRequestOptions.cs | 30 + server/models/AyContext.cs | 132 ++ server/models/Customer.cs | 119 ++ server/models/CustomerNote.cs | 51 + server/models/CustomerNotifyDeliveryLog.cs | 41 + server/models/CustomerNotifyEvent.cs | 59 + server/models/CustomerNotifySubscription.cs | 63 + server/models/DashboardView.cs | 23 + server/models/DataListColumnFilter.cs | 8 + server/models/DataListColumnView.cs | 27 + server/models/DataListFilterOption.cs | 29 + server/models/DataListProcessingBase.cs | 20 + .../models/DataListReportProcessingOptions.cs | 15 + server/models/DataListReportRequest.cs | 9 + server/models/DataListRequestBase.cs | 18 + server/models/DataListSavedFilter.cs | 26 + .../DataListSelectedProcessingOptions.cs | 74 + server/models/DataListSelectedRequest.cs | 14 + server/models/DataListSortRequest.cs | 4 + .../models/DataListTableProcessingOptions.cs | 99 ++ server/models/DataListTableRequest.cs | 8 + server/models/Event.cs | 67 + server/models/FileAttachment.cs | 45 + server/models/FormCustom.cs | 31 + server/models/FormUserOptions.cs | 24 + server/models/GlobalBizSettings.cs | 119 ++ server/models/GlobalOpsBackupSettings.cs | 23 + .../models/GlobalOpsNotificationSettings.cs | 46 + server/models/HeadOffice.cs | 101 ++ server/models/ICoreBizObjectModel.cs | 26 + server/models/Integration.cs | 44 + server/models/IntegrationItem.cs | 51 + server/models/IntegrationLog.cs | 34 + server/models/Logo.cs | 17 + server/models/Memo.cs | 61 + server/models/MetricDD.cs | 33 + server/models/MetricMM.cs | 32 + server/models/Notification.cs | 47 + server/models/NotifyDeliveryLog.cs | 48 + server/models/NotifyEvent.cs | 64 + server/models/NotifySubscription.cs | 52 + server/models/OpsJob.cs | 69 + server/models/OpsJobLog.cs | 33 + server/models/PickListTemplate.cs | 15 + server/models/Reminder.cs | 75 + server/models/Report.cs | 66 + server/models/Review.cs | 86 + server/models/SchemaVersion.cs | 22 + server/models/SearchDictionary.cs | 15 + server/models/SearchKey.cs | 24 + server/models/Tag.cs | 17 + server/models/Translation.cs | 59 + server/models/TranslationItem.cs | 38 + server/models/User.cs | 149 ++ server/models/UserOptions.cs | 135 ++ server/models/dto/AddressRecord.cs | 8 + server/models/dto/AyImportData.cs | 19 + server/models/dto/JobOperationsFetchInfo.cs | 40 + server/models/dto/JobOperationsLogInfoItem.cs | 16 + server/models/dto/JobProgress.cs | 26 + .../models/dto/NameIdActiveChargeCostItem.cs | 13 + server/models/dto/NameIdActiveItem.cs | 12 + server/models/dto/NameIdDefaultItem.cs | 4 + server/models/dto/NameIdItem.cs | 10 + server/models/dto/NameItem.cs | 12 + .../dto/NewTextIdConcurrencyTokenItem.cs | 11 + server/models/dto/ParentAndChildItemId.cs | 8 + server/models/dto/ScheduleItemAdjustParams.cs | 14 + server/models/dto/UploadFileData.cs | 8 + server/models/dto/UploadedFileInfo.cs | 16 + server/resource/de.json | 1495 +++++++++++++++++ server/resource/en.json | 1495 +++++++++++++++++ server/resource/es.json | 1495 +++++++++++++++++ server/resource/fr.json | 1495 +++++++++++++++++ server/resource/rpt/ay-bc.js | 73 + server/resource/rpt/ay-hb.js | 29 + server/resource/rpt/ay-md.js | 6 + server/resource/rpt/ay-pf.js | 3 + server/resource/rpt/ay-report.js | 445 +++++ .../rpt/stock-report-templates/150x50.png | Bin 0 -> 2644 bytes .../rpt/stock-report-templates/300x100.png | Bin 0 -> 4696 bytes .../rpt/stock-report-templates/600x200.png | Bin 0 -> 15939 bytes .../stock-report-templates/Clients list.ayrt | 1 + .../stock-report-templates/Contacts list.ayrt | 1 + .../Customer Notes.ayrt | 1 + .../Head Offices list.ayrt | 1 + .../rpt/stock-report-templates/Memos.ayrt | 1 + .../stock-report-templates/Projects list.ayrt | 1 + .../rpt/stock-report-templates/Reminders.ayrt | 1 + .../Reviews grouped by user.ayrt | 1 + .../Users Grouped By User Type.ayrt | 1 + server/sockeye.csproj | 72 + server/sockeye.ico | Bin 0 -> 113054 bytes server/util/ApplicationLogging.cs | 16 + server/util/AySchema.cs | 930 ++++++++++ server/util/CopyObject.cs | 68 + server/util/DataUtil.cs | 80 + server/util/DateUtil.cs | 159 ++ server/util/DbUtil.cs | 875 ++++++++++ server/util/EnumAttributeExtension.cs | 36 + server/util/ExceptionUtil.cs | 32 + server/util/FileHash.cs | 27 + server/util/FileUtil.cs | 879 ++++++++++ server/util/Hasher.cs | 77 + server/util/ImportUtil.cs | 69 + server/util/IsLocalExtension.cs | 31 + server/util/JsonUtil.cs | 106 ++ server/util/Mailer.cs | 91 + server/util/MoneyUtil.cs | 19 + server/util/ObjectCache.cs | 49 + server/util/ReportProcessManager.cs | 164 ++ server/util/ReportRenderTimeOutException.cs | 22 + server/util/RetryHelper.cs | 52 + server/util/RunProgram.cs | 130 ++ server/util/ServerBootConfig.cs | 244 +++ server/util/ServerGlobalBizSettings.cs | 37 + server/util/ServerGlobalOpsSettingsCache.cs | 72 + server/util/ServiceProviderProvider.cs | 62 + server/util/SockeyeVersion.cs | 12 + server/util/StringUtil.cs | 143 ++ server/util/TaskUtil.cs | 45 + server/util/VizCache.cs | 68 + 310 files changed, 48715 insertions(+) create mode 100644 server/ConfigureSwaggerOptions.cs create mode 100644 server/ControllerHelpers/ApiCreatedResponse.cs create mode 100644 server/ControllerHelpers/ApiCustomExceptionFilter.cs create mode 100644 server/ControllerHelpers/ApiDetailError.cs create mode 100644 server/ControllerHelpers/ApiError.cs create mode 100644 server/ControllerHelpers/ApiErrorResponse.cs create mode 100644 server/ControllerHelpers/ApiNotAuthorizedResponse.cs create mode 100644 server/ControllerHelpers/ApiOkResponse.cs create mode 100644 server/ControllerHelpers/ApiServerState.cs create mode 100644 server/ControllerHelpers/ApiUploadProcessor.cs create mode 100644 server/ControllerHelpers/Authorized.cs create mode 100644 server/ControllerHelpers/DisableFormValueModelBindingAttribute.cs create mode 100644 server/ControllerHelpers/MultipartRequestHelper.cs create mode 100644 server/ControllerHelpers/UserIdFromContext.cs create mode 100644 server/ControllerHelpers/UserNameFromContext.cs create mode 100644 server/ControllerHelpers/UserRolesFromContext.cs create mode 100644 server/ControllerHelpers/UserTranslationIdFromContext.cs create mode 100644 server/ControllerHelpers/UserTypeFromContext.cs create mode 100644 server/Controllers/ApiRootController.cs create mode 100644 server/Controllers/AttachmentController.cs create mode 100644 server/Controllers/AuthController.cs create mode 100644 server/Controllers/AuthorizationRolesController.cs create mode 100644 server/Controllers/BackupController.cs create mode 100644 server/Controllers/CustomerController.cs create mode 100644 server/Controllers/CustomerNoteController.cs create mode 100644 server/Controllers/CustomerNotifySubscriptionController.cs create mode 100644 server/Controllers/DashboardViewController.cs create mode 100644 server/Controllers/DataListColumnViewController.cs create mode 100644 server/Controllers/DataListController.cs create mode 100644 server/Controllers/DataListSavedFilterController.cs create mode 100644 server/Controllers/EnumListController.cs create mode 100644 server/Controllers/EventLogController.cs create mode 100644 server/Controllers/ExportController.cs create mode 100644 server/Controllers/FormCustomController.cs create mode 100644 server/Controllers/FormFieldsDefinitionsController.cs create mode 100644 server/Controllers/FormUserOptionsController.cs create mode 100644 server/Controllers/GlobalBizSettingsController.cs create mode 100644 server/Controllers/GlobalOpsBackupSettingsController.cs create mode 100644 server/Controllers/GlobalOpsNotificationSettingsController.cs create mode 100644 server/Controllers/HeadOfficeController.cs create mode 100644 server/Controllers/HealthController.cs create mode 100644 server/Controllers/ImportController.cs create mode 100644 server/Controllers/IntegrationController.cs create mode 100644 server/Controllers/JobOperationsController.cs create mode 100644 server/Controllers/KPIController.cs create mode 100644 server/Controllers/LogFilesController.cs create mode 100644 server/Controllers/LogoController.cs create mode 100644 server/Controllers/MemoController.cs create mode 100644 server/Controllers/NameController.cs create mode 100644 server/Controllers/NotifyController.cs create mode 100644 server/Controllers/NotifySubscriptionController.cs create mode 100644 server/Controllers/PickListController.cs create mode 100644 server/Controllers/ReminderController.cs create mode 100644 server/Controllers/ReportController.cs create mode 100644 server/Controllers/ReviewController.cs create mode 100644 server/Controllers/ScheduleController.cs create mode 100644 server/Controllers/SearchController.cs create mode 100644 server/Controllers/ServerMetricsController.cs create mode 100644 server/Controllers/ServerStateController.cs create mode 100644 server/Controllers/TagController.cs create mode 100644 server/Controllers/TranslationController.cs create mode 100644 server/Controllers/UserController.cs create mode 100644 server/Controllers/UserOptionsController.cs create mode 100644 server/DataList/AttachmentDataList.cs create mode 100644 server/DataList/CustomerDataList.cs create mode 100644 server/DataList/CustomerNoteDataList.cs create mode 100644 server/DataList/CustomerNotificationDeliveryLogDataList.cs create mode 100644 server/DataList/DataListFactory.cs create mode 100644 server/DataList/DataListFetcher.cs create mode 100644 server/DataList/DataListField.cs create mode 100644 server/DataList/DataListFieldDefinition.cs create mode 100644 server/DataList/DataListFilterComparisonOperator.cs create mode 100644 server/DataList/DataListProcessingBase.cs create mode 100644 server/DataList/DataListReturnData.cs create mode 100644 server/DataList/DataListSqlFilterCriteriaBuilder.cs create mode 100644 server/DataList/DataListSqlFilterOrderByBuilder.cs create mode 100644 server/DataList/DataListSqlSelectBuilder.cs create mode 100644 server/DataList/EventDataList.cs create mode 100644 server/DataList/HeadOfficeDataList.cs create mode 100644 server/DataList/IDataListInternalCriteria.cs create mode 100644 server/DataList/IDataListProcessing.cs create mode 100644 server/DataList/InsideUserDataList.cs create mode 100644 server/DataList/IntegrationDataList.cs create mode 100644 server/DataList/MemoDataList.cs create mode 100644 server/DataList/NotificationDeliveryLogDataList.cs create mode 100644 server/DataList/OutsideUserDataList.cs create mode 100644 server/DataList/ReminderDataList.cs create mode 100644 server/DataList/ReportDataList.cs create mode 100644 server/DataList/ReviewDataList.cs create mode 100644 server/DataList/TranslationDataList.cs create mode 100644 server/PickList/AyaPickList.cs create mode 100644 server/PickList/AyaPickListFieldDefinition.cs create mode 100644 server/PickList/CustomerPickList.cs create mode 100644 server/PickList/HeadOfficePickList.cs create mode 100644 server/PickList/IAyaPickList.cs create mode 100644 server/PickList/IAyaPickListVariant.cs create mode 100644 server/PickList/PickListFactory.cs create mode 100644 server/PickList/PickListFetcher.cs create mode 100644 server/PickList/PickListOptions.cs create mode 100644 server/PickList/PickListSqlBuilder.cs create mode 100644 server/PickList/ReportPickList.cs create mode 100644 server/PickList/UserPickList.cs create mode 100644 server/Program.cs create mode 100644 server/Startup.cs create mode 100644 server/appsettings.Development.json create mode 100644 server/appsettings.json create mode 100644 server/biz/ApiErrorCode.cs create mode 100644 server/biz/ApiErrorCodeStockMessage.cs create mode 100644 server/biz/AttachmentBiz.cs create mode 100644 server/biz/AuthorizationRoles.cs create mode 100644 server/biz/BizObject.cs create mode 100644 server/biz/BizObjectExistsInDatabase.cs create mode 100644 server/biz/BizObjectFactory.cs create mode 100644 server/biz/BizObjectNameFetcherDirect.cs create mode 100644 server/biz/BizRoleSet.cs create mode 100644 server/biz/BizRoles.cs create mode 100644 server/biz/CoreBizObjectAttribute.cs create mode 100644 server/biz/CustomFieldType.cs create mode 100644 server/biz/CustomFieldsValidator.cs create mode 100644 server/biz/CustomerBiz.cs create mode 100644 server/biz/CustomerNoteBiz.cs create mode 100644 server/biz/CustomerNotifySubscriptionBiz.cs create mode 100644 server/biz/DashboardViewBiz.cs create mode 100644 server/biz/DataListColumnViewBiz.cs create mode 100644 server/biz/DataListSavedFilterBiz.cs create mode 100644 server/biz/EventLogProcessor.cs create mode 100644 server/biz/FormCustomBiz.cs create mode 100644 server/biz/FormFieldReference.cs create mode 100644 server/biz/FormUserOptionsBiz.cs create mode 100644 server/biz/GlobalBizSettingsBiz.cs create mode 100644 server/biz/GlobalOpsBackupSettingsBiz.cs create mode 100644 server/biz/GlobalOpsNotificationSettingsBiz.cs create mode 100644 server/biz/HeadOfficeBiz.cs create mode 100644 server/biz/IBizObject.cs create mode 100644 server/biz/IExportAbleObject.cs create mode 100644 server/biz/IImportAbleObject.cs create mode 100644 server/biz/IJobObject.cs create mode 100644 server/biz/INotifiableObject.cs create mode 100644 server/biz/IReportAbleObject.cs create mode 100644 server/biz/ISearchAbleObject.cs create mode 100644 server/biz/ImportableBizObjectAttribute.cs create mode 100644 server/biz/IntegrationBiz.cs create mode 100644 server/biz/JobOperationsBiz.cs create mode 100644 server/biz/JobStatus.cs create mode 100644 server/biz/JobType.cs create mode 100644 server/biz/JobsBiz.cs create mode 100644 server/biz/MemoBiz.cs create mode 100644 server/biz/NotifyDeliveryMethod.cs create mode 100644 server/biz/NotifyEventHelper.cs create mode 100644 server/biz/NotifyEventType.cs create mode 100644 server/biz/NotifyMailSecurity.cs create mode 100644 server/biz/NotifySubscriptionBiz.cs create mode 100644 server/biz/PickListBiz.cs create mode 100644 server/biz/PrimeData.cs create mode 100644 server/biz/ReminderBiz.cs create mode 100644 server/biz/ReportBiz.cs create mode 100644 server/biz/ReportPaperFormat.cs create mode 100644 server/biz/ReportRenderType.cs create mode 100644 server/biz/ReportableBizObjectAttribute.cs create mode 100644 server/biz/RequiredFieldsValidator.cs create mode 100644 server/biz/ReviewBiz.cs create mode 100644 server/biz/Search.cs create mode 100644 server/biz/SearchTranslationWordBreakDataCache.cs create mode 100644 server/biz/SockDaysOfWeek.cs create mode 100644 server/biz/SockEvent.cs create mode 100644 server/biz/SockType.cs create mode 100644 server/biz/SockTypeId.cs create mode 100644 server/biz/TagBiz.cs create mode 100644 server/biz/TranslationBiz.cs create mode 100644 server/biz/UiFieldDataType.cs create mode 100644 server/biz/UserBiz.cs create mode 100644 server/biz/UserOptionsBiz.cs create mode 100644 server/biz/UserType.cs create mode 100644 server/biz/ValidationError.cs create mode 100644 server/generator/BackgroundService.cs create mode 100644 server/generator/CoreIntegrationLogSweeper.cs create mode 100644 server/generator/CoreJobBackup.cs create mode 100644 server/generator/CoreJobCustomerNotify.cs create mode 100644 server/generator/CoreJobMetricsSnapshot.cs create mode 100644 server/generator/CoreJobNotify.cs create mode 100644 server/generator/CoreJobReportRenderEngineProcessCleanup.cs create mode 100644 server/generator/CoreJobSweeper.cs create mode 100644 server/generator/CoreJobTempFolderCleanup.cs create mode 100644 server/generator/CoreNotificationSweeper.cs create mode 100644 server/generator/Generate.cs create mode 100644 server/kpi/IAyaKPI.cs create mode 100644 server/kpi/KPIFactory.cs create mode 100644 server/kpi/KPIFetcher.cs create mode 100644 server/kpi/KPIRequestOptions.cs create mode 100644 server/models/AyContext.cs create mode 100644 server/models/Customer.cs create mode 100644 server/models/CustomerNote.cs create mode 100644 server/models/CustomerNotifyDeliveryLog.cs create mode 100644 server/models/CustomerNotifyEvent.cs create mode 100644 server/models/CustomerNotifySubscription.cs create mode 100644 server/models/DashboardView.cs create mode 100644 server/models/DataListColumnFilter.cs create mode 100644 server/models/DataListColumnView.cs create mode 100644 server/models/DataListFilterOption.cs create mode 100644 server/models/DataListProcessingBase.cs create mode 100644 server/models/DataListReportProcessingOptions.cs create mode 100644 server/models/DataListReportRequest.cs create mode 100644 server/models/DataListRequestBase.cs create mode 100644 server/models/DataListSavedFilter.cs create mode 100644 server/models/DataListSelectedProcessingOptions.cs create mode 100644 server/models/DataListSelectedRequest.cs create mode 100644 server/models/DataListSortRequest.cs create mode 100644 server/models/DataListTableProcessingOptions.cs create mode 100644 server/models/DataListTableRequest.cs create mode 100644 server/models/Event.cs create mode 100644 server/models/FileAttachment.cs create mode 100644 server/models/FormCustom.cs create mode 100644 server/models/FormUserOptions.cs create mode 100644 server/models/GlobalBizSettings.cs create mode 100644 server/models/GlobalOpsBackupSettings.cs create mode 100644 server/models/GlobalOpsNotificationSettings.cs create mode 100644 server/models/HeadOffice.cs create mode 100644 server/models/ICoreBizObjectModel.cs create mode 100644 server/models/Integration.cs create mode 100644 server/models/IntegrationItem.cs create mode 100644 server/models/IntegrationLog.cs create mode 100644 server/models/Logo.cs create mode 100644 server/models/Memo.cs create mode 100644 server/models/MetricDD.cs create mode 100644 server/models/MetricMM.cs create mode 100644 server/models/Notification.cs create mode 100644 server/models/NotifyDeliveryLog.cs create mode 100644 server/models/NotifyEvent.cs create mode 100644 server/models/NotifySubscription.cs create mode 100644 server/models/OpsJob.cs create mode 100644 server/models/OpsJobLog.cs create mode 100644 server/models/PickListTemplate.cs create mode 100644 server/models/Reminder.cs create mode 100644 server/models/Report.cs create mode 100644 server/models/Review.cs create mode 100644 server/models/SchemaVersion.cs create mode 100644 server/models/SearchDictionary.cs create mode 100644 server/models/SearchKey.cs create mode 100644 server/models/Tag.cs create mode 100644 server/models/Translation.cs create mode 100644 server/models/TranslationItem.cs create mode 100644 server/models/User.cs create mode 100644 server/models/UserOptions.cs create mode 100644 server/models/dto/AddressRecord.cs create mode 100644 server/models/dto/AyImportData.cs create mode 100644 server/models/dto/JobOperationsFetchInfo.cs create mode 100644 server/models/dto/JobOperationsLogInfoItem.cs create mode 100644 server/models/dto/JobProgress.cs create mode 100644 server/models/dto/NameIdActiveChargeCostItem.cs create mode 100644 server/models/dto/NameIdActiveItem.cs create mode 100644 server/models/dto/NameIdDefaultItem.cs create mode 100644 server/models/dto/NameIdItem.cs create mode 100644 server/models/dto/NameItem.cs create mode 100644 server/models/dto/NewTextIdConcurrencyTokenItem.cs create mode 100644 server/models/dto/ParentAndChildItemId.cs create mode 100644 server/models/dto/ScheduleItemAdjustParams.cs create mode 100644 server/models/dto/UploadFileData.cs create mode 100644 server/models/dto/UploadedFileInfo.cs create mode 100644 server/resource/de.json create mode 100644 server/resource/en.json create mode 100644 server/resource/es.json create mode 100644 server/resource/fr.json create mode 100644 server/resource/rpt/ay-bc.js create mode 100644 server/resource/rpt/ay-hb.js create mode 100644 server/resource/rpt/ay-md.js create mode 100644 server/resource/rpt/ay-pf.js create mode 100644 server/resource/rpt/ay-report.js create mode 100644 server/resource/rpt/stock-report-templates/150x50.png create mode 100644 server/resource/rpt/stock-report-templates/300x100.png create mode 100644 server/resource/rpt/stock-report-templates/600x200.png create mode 100644 server/resource/rpt/stock-report-templates/Clients list.ayrt create mode 100644 server/resource/rpt/stock-report-templates/Contacts list.ayrt create mode 100644 server/resource/rpt/stock-report-templates/Customer Notes.ayrt create mode 100644 server/resource/rpt/stock-report-templates/Head Offices list.ayrt create mode 100644 server/resource/rpt/stock-report-templates/Memos.ayrt create mode 100644 server/resource/rpt/stock-report-templates/Projects list.ayrt create mode 100644 server/resource/rpt/stock-report-templates/Reminders.ayrt create mode 100644 server/resource/rpt/stock-report-templates/Reviews grouped by user.ayrt create mode 100644 server/resource/rpt/stock-report-templates/Users Grouped By User Type.ayrt create mode 100644 server/sockeye.csproj create mode 100644 server/sockeye.ico create mode 100644 server/util/ApplicationLogging.cs create mode 100644 server/util/AySchema.cs create mode 100644 server/util/CopyObject.cs create mode 100644 server/util/DataUtil.cs create mode 100644 server/util/DateUtil.cs create mode 100644 server/util/DbUtil.cs create mode 100644 server/util/EnumAttributeExtension.cs create mode 100644 server/util/ExceptionUtil.cs create mode 100644 server/util/FileHash.cs create mode 100644 server/util/FileUtil.cs create mode 100644 server/util/Hasher.cs create mode 100644 server/util/ImportUtil.cs create mode 100644 server/util/IsLocalExtension.cs create mode 100644 server/util/JsonUtil.cs create mode 100644 server/util/Mailer.cs create mode 100644 server/util/MoneyUtil.cs create mode 100644 server/util/ObjectCache.cs create mode 100644 server/util/ReportProcessManager.cs create mode 100644 server/util/ReportRenderTimeOutException.cs create mode 100644 server/util/RetryHelper.cs create mode 100644 server/util/RunProgram.cs create mode 100644 server/util/ServerBootConfig.cs create mode 100644 server/util/ServerGlobalBizSettings.cs create mode 100644 server/util/ServerGlobalOpsSettingsCache.cs create mode 100644 server/util/ServiceProviderProvider.cs create mode 100644 server/util/SockeyeVersion.cs create mode 100644 server/util/StringUtil.cs create mode 100644 server/util/TaskUtil.cs create mode 100644 server/util/VizCache.cs diff --git a/server/ConfigureSwaggerOptions.cs b/server/ConfigureSwaggerOptions.cs new file mode 100644 index 0000000..28e7200 --- /dev/null +++ b/server/ConfigureSwaggerOptions.cs @@ -0,0 +1,54 @@ +namespace Sockeye +{ + using Microsoft.AspNetCore.Mvc.ApiExplorer; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + using Microsoft.OpenApi.Models; + using Swashbuckle.AspNetCore.SwaggerGen; + using System; + + /// + /// Configures the Swagger generation options. + /// + /// This allows API versioning to define a Swagger document per API version after the + /// service has been resolved from the service container. + public class ConfigureSwaggerOptions : IConfigureOptions + { + readonly IApiVersionDescriptionProvider provider; + + /// + /// Initializes a new instance of the class. + /// + /// The provider used to generate Swagger documents. + public ConfigureSwaggerOptions( IApiVersionDescriptionProvider provider ) => this.provider = provider; + + /// + public void Configure( SwaggerGenOptions options ) + { + // add a swagger document for each discovered API version + // note: you might choose to skip or document deprecated API versions differently + foreach ( var description in provider.ApiVersionDescriptions ) + { + options.SwaggerDoc( description.GroupName, CreateInfoForApiVersion( description ) ); + } + } + + static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription description ) + { + var info = new OpenApiInfo() + { + Title = "Sockeye API", + Version = description.ApiVersion.ToString(), + Description = "Sockeye service management software API", + + }; + + if ( description.IsDeprecated ) + { + info.Description += " This API version has been deprecated."; + } + + return info; + } + } +} \ No newline at end of file diff --git a/server/ControllerHelpers/ApiCreatedResponse.cs b/server/ControllerHelpers/ApiCreatedResponse.cs new file mode 100644 index 0000000..974e8a3 --- /dev/null +++ b/server/ControllerHelpers/ApiCreatedResponse.cs @@ -0,0 +1,19 @@ + +namespace Sockeye.Api.ControllerHelpers +{ + + + + public class ApiCreatedResponse + { + + public object Data { get; } + + public ApiCreatedResponse(object result) + { + Data = result; + } + }//eoc + + +}//eons \ No newline at end of file diff --git a/server/ControllerHelpers/ApiCustomExceptionFilter.cs b/server/ControllerHelpers/ApiCustomExceptionFilter.cs new file mode 100644 index 0000000..ed6205e --- /dev/null +++ b/server/ControllerHelpers/ApiCustomExceptionFilter.cs @@ -0,0 +1,106 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Http; +using System; +using System.Net; +using System.Net.Http; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Microsoft.Extensions.Logging; +using Sockeye.Util; +using Sockeye.Biz; + +namespace Sockeye.Api.ControllerHelpers +{ + + + /// + /// This is essentially an unhandled exception handler + /// + public class ApiCustomExceptionFilter : IExceptionFilter + { + private readonly ILogger log; + + // public ApiCustomExceptionFilter(ILoggerFactory logger) + // { + // if (logger == null) + // { + // throw new ArgumentNullException(nameof(logger)); + // } + + // this.log = logger.CreateLogger("Server Exception"); + // } + + public ApiCustomExceptionFilter(ILogger logger) + { + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + this.log = logger; + } + + + public void OnException(ExceptionContext context) + { + HttpStatusCode status = HttpStatusCode.InternalServerError; + String message = String.Empty; + #region If need to refine this further and deal with specific types + // var exceptionType = context.Exception.GetType(); + // if (exceptionType == typeof(UnauthorizedAccessException)) + // { + // message = "Unauthorized Access"; + // status = HttpStatusCode.Unauthorized; + // } + // else if (exceptionType == typeof(NotImplementedException)) + // { + // message = "A server error occurred."; + // status = HttpStatusCode.NotImplemented; + // } + // // else if (exceptionType == typeof(MyAppException)) + // // { + // // message = context.Exception.ToString(); + // // status = HttpStatusCode.InternalServerError; + // // } + // else + // { + #endregion + message = context.Exception.Message; + status = HttpStatusCode.InternalServerError; + //} + + //No need to log test exceptions to check and filter out + bool loggableError = true; + + if (message.StartsWith("Test exception")) + loggableError = false; + + //LOG IT + if (loggableError) + log.LogError(context.Exception, "Error"); + + //Notify ops notification issue + NotifyEventHelper.AddOpsProblemEvent("Server API internal error, see log for more details", context.Exception).Forget();//.Wait(); + + HttpResponse response = context.HttpContext.Response; + response.StatusCode = (int)status; + response.ContentType = "application/json; charset=utf-8"; + + //This line is critical, without it the response is not proper and fails in various clients (postman, xunit tests with httpclient) + context.ExceptionHandled = true; + + + response.WriteAsync(JsonConvert.SerializeObject( + new ApiErrorResponse(ApiErrorCode.API_SERVER_ERROR, "generalerror", "Server internal error; see server log for details"), + new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + } + )); + } + + + }//eoc + +}//eons \ No newline at end of file diff --git a/server/ControllerHelpers/ApiDetailError.cs b/server/ControllerHelpers/ApiDetailError.cs new file mode 100644 index 0000000..649d422 --- /dev/null +++ b/server/ControllerHelpers/ApiDetailError.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; +using Sockeye.Biz; + +namespace Sockeye.Api.ControllerHelpers +{ + + + /// + /// Detail error for inner part of error response + /// + public class ApiDetailError + { + /* WAIT, why does this have CODE AND Error??! */ + // [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + // public string Code { get; internal set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string Message { get; internal set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string Target { get; internal set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string Error { get; internal set; } + + + }//eoc + + +}//eons \ No newline at end of file diff --git a/server/ControllerHelpers/ApiError.cs b/server/ControllerHelpers/ApiError.cs new file mode 100644 index 0000000..815e2e9 --- /dev/null +++ b/server/ControllerHelpers/ApiError.cs @@ -0,0 +1,37 @@ +using Newtonsoft.Json; +using System.Collections.Generic; +using Sockeye.Biz; + +namespace Sockeye.Api.ControllerHelpers +{ + + + + + /// + /// + /// + public class ApiError + { + public string Code { get; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public List Details { get; internal set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string Message { get; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string Target { get; } + + public ApiError(ApiErrorCode apiCode, string message = null, string target = null) + { + Code = ((int)apiCode).ToString(); + Target = target; + Message=message; + } + + }//eoc + + +}//eons \ No newline at end of file diff --git a/server/ControllerHelpers/ApiErrorResponse.cs b/server/ControllerHelpers/ApiErrorResponse.cs new file mode 100644 index 0000000..449a09b --- /dev/null +++ b/server/ControllerHelpers/ApiErrorResponse.cs @@ -0,0 +1,122 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Sockeye.Biz; + +namespace Sockeye.Api.ControllerHelpers +{ + + + + public class ApiErrorResponse + { + + [JsonIgnore] + private ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger(); + + //Mandatory properties + public ApiError Error { get; } + + + + + //Generic error + public ApiErrorResponse(ApiErrorCode apiCode, string target = null, string message = null) + { + + //try to get a stock message if nothing specified + if (message == null) + { + message = ApiErrorCodeStockMessage.GetTranslationCodeForApiErrorCode(apiCode); + } + + Error = new ApiError(apiCode, message, target); + + log.LogDebug("apiCode={0}, target={1}, message={2}", apiCode, target, message); + } + + + //Bad request (MODELSTATE ISSUE) error response handling + + public ApiErrorResponse(ModelStateDictionary modelState) + { + + if (modelState.IsValid) + { + throw new ArgumentException("ModelState must be invalid", nameof(modelState)); + } + + + //Set outer error and then put validation in details + + Error = new ApiError(ApiErrorCode.VALIDATION_FAILED, ApiErrorCodeStockMessage.GetTranslationCodeForApiErrorCode(ApiErrorCode.VALIDATION_FAILED)); + + + //https://www.jerriepelser.com/blog/validation-response-aspnet-core-webapi/ + //Message = "Validation Failed"; + Error.Details = new List(); + + + foreach (var key in modelState.Keys) + { + var vErrors = modelState[key].Errors; + foreach (ModelError m in vErrors) + { + string msg = ""; + if (!string.IsNullOrWhiteSpace(m.ErrorMessage)) + { + msg += m.ErrorMessage + ". "; + } + if (m.Exception != null && !string.IsNullOrWhiteSpace(m.Exception.Message)) + { + msg += "Exception: " + m.Exception.Message; + } + //example this produces + // + Error.Details.Add(new ApiDetailError() { Target = key, Message = msg, Error = ((int)ApiErrorCode.VALIDATION_INVALID_VALUE).ToString() }); + } + } + + + log.LogDebug("BadRequest - Validation error"); + } + + + //Business rule validation error response + public ApiErrorResponse(List errors) + { + Error = new ApiError(ApiErrorCode.VALIDATION_FAILED, ApiErrorCodeStockMessage.GetTranslationCodeForApiErrorCode(ApiErrorCode.VALIDATION_FAILED)); + Error.Details = new List(); + foreach (ValidationError v in errors) + { + Error.Details.Add(new ApiDetailError() { Target = v.Target, Message = v.Message, Error = ((int)v.Code).ToString() }); + } + log.LogDebug("BadRequest - Validation error"); + } + + + + // public void AddDetailError(ApiErrorCode apiCode, string target = null, string message = null) + // { + // if (Error.Details == null) + // { + // Error.Details = new List(); + // } + + // //try to get a stock message if nothing specified + // if (message == null) + // { + // message = ApiErrorCodeStockMessage.GetMessage(apiCode); + // } + + // Error.Details.Add(new ApiDetailError() { Code = ((int)apiCode).ToString(), Target = target, Message = message }); + // } + + + }//eoc + + +}//eons \ No newline at end of file diff --git a/server/ControllerHelpers/ApiNotAuthorizedResponse.cs b/server/ControllerHelpers/ApiNotAuthorizedResponse.cs new file mode 100644 index 0000000..ad5a371 --- /dev/null +++ b/server/ControllerHelpers/ApiNotAuthorizedResponse.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Sockeye.Biz; + +namespace Sockeye.Api.ControllerHelpers +{ + + + + public class ApiNotAuthorizedResponse + { + + [JsonIgnore] + private ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger(); + + //Mandatory properties + public ApiError Error { get; } + + //Generic error + public ApiNotAuthorizedResponse() + { + Error = new ApiError(ApiErrorCode.NOT_AUTHORIZED, ApiErrorCodeStockMessage.GetTranslationCodeForApiErrorCode(ApiErrorCode.NOT_AUTHORIZED)); + + log.LogDebug("ApiErrorCode={0}, message={1}", (int)ApiErrorCode.NOT_AUTHORIZED, Error.Message); + } + + }//eoc + + +}//eons \ No newline at end of file diff --git a/server/ControllerHelpers/ApiOkResponse.cs b/server/ControllerHelpers/ApiOkResponse.cs new file mode 100644 index 0000000..460ed98 --- /dev/null +++ b/server/ControllerHelpers/ApiOkResponse.cs @@ -0,0 +1,10 @@ +namespace Sockeye.Api.ControllerHelpers +{ + public static class ApiOkResponse + { + public static object Response(object result) + { + return new { Data = result }; + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/ControllerHelpers/ApiServerState.cs b/server/ControllerHelpers/ApiServerState.cs new file mode 100644 index 0000000..fd03fb9 --- /dev/null +++ b/server/ControllerHelpers/ApiServerState.cs @@ -0,0 +1,220 @@ +using Sockeye.Biz; +namespace Sockeye.Api.ControllerHelpers +{ + + + + + /// + /// Contains the current status of the server + /// is injected everywhere for routes and others to check + /// + public class ApiServerState + { + + //private ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger(); + public enum ServerState + { + ///Unknown state, used for parsing + UNKNOWN = 0, + ///No access for anyone API completely locked down. Not set by user but rather by internal server operations like importing or backup. + Closed = 1, + + ///Access only to API Operations routes. Can be set by Ops user + OpsOnly = 3, + ///Open for all users (default). Can be set by Ops user + Open = 4 + } + + private ServerState _currentState = ServerState.Closed; + private ServerState _priorState = ServerState.Closed; + private string _reason = string.Empty; + private string _priorReason = string.Empty; + private bool SYSTEM_LOCK = false;//really this is a license lock but not called that + + public ApiServerState() + { + + } + + + + internal void SetSystemLock(string reason) + { + //Lock down the server for license related issue + //Only SuperUser account (id=1) can login or do anything, treats as if server was set to closed even if they change it to open + //only way to reset it is to fetch a valid license + // This is set to locked in TWO ways: + + // 1) By CoreJobSweeper *if* the user count is mismatched to the license likely due to Users messing directly with the DB + //trying to circumvent licensing + // 2) By License::InitializeAsync upon finding user count mismatch or expired license key + // and initializeasync is called on boot up or when a new key is downloaded and installed only + // + var msg = $"{reason}\r\nLogin as SuperUser to start evaluation / install license"; + SetState(ServerState.OpsOnly, msg); + SYSTEM_LOCK = true; + } + + + //WARNING: if in future this is used for anything other than a license then it will need to see if locked for another reason before unlocking + //recommend putting a code number in the reason then looking to see if it has the matching code + internal void ClearSystemLock() + { + SYSTEM_LOCK = false; + SetState(ServerState.Open, ""); + } + + /// + /// Set the server state + /// + /// + /// + public void SetState(ServerState newState, string reason) + { + //No changes allowed during a system lock + if (SYSTEM_LOCK) return; + + _reason = reason;//keep the reason even if the state doesn't change + if (newState == _currentState) return; + + //Here we will likely need to trigger a notification to users if the state is going to be shutting down or is shut down + _priorState = _currentState;//keep the prior state so it can be resumed easily + _priorReason = _reason;//keep the original reason + + _currentState = newState; + } + + /// + /// Get the current state of the server + /// + /// + public ServerState GetState() + { + return _currentState; + } + + /// + /// Get the current reason for the state of the server + /// + /// + public string Reason + { + get + { + if (_currentState == ServerState.Open) + { + return ""; + } + else + { + return $"\"{_reason}\""; + // if (_currentState == ServerState.Closed) + // return $"{ Sockeye.Biz.TranslationBiz.GetDefaultTranslationAsync("ErrorAPI2000").Result}\r\n{_reason}"; + // else //opsonly + // return $"{ Sockeye.Biz.TranslationBiz.GetDefaultTranslationAsync("ErrorAPI2001").Result}\r\n{_reason}"; + } + } + } + + //get the api error code associated with the server state + public ApiErrorCode ApiErrorCode + { + get + { + switch (_currentState) + { + case ServerState.Open: + throw new System.NotSupportedException("ApiServerState:ApiErrorCode - No error code is associated with server state OPEN"); + case ServerState.OpsOnly: + return ApiErrorCode.API_OPS_ONLY; + + case ServerState.Closed: + return ApiErrorCode.API_CLOSED; + + } + throw new System.NotSupportedException("ApiServerState:ApiErrorCode - No error code is associated with server state UNKNOWN"); + } + + } + + + public void SetOpsOnly(string reason) + { + //No changes allowed during a system lock + if (SYSTEM_LOCK) return; + SetState(ServerState.OpsOnly, reason); + } + + + public void SetClosed(string reason) + { + //No changes allowed during a system lock + if (SYSTEM_LOCK) return; + SetState(ServerState.Closed, reason); + } + + public void SetOpen() + { + //No changes allowed during a system lock + if (SYSTEM_LOCK) return; + SetState(ServerState.Open, string.Empty); + } + + public void ResumePriorState() + { + //No changes allowed during a system lock + if (SYSTEM_LOCK) return; + SetState(_priorState, _priorReason); + } + + + public bool IsOpsOnly + { + get + { + return _currentState == ServerState.OpsOnly && !SYSTEM_LOCK; + } + } + + + public bool IsOpen + { + get + { + return _currentState != ServerState.Closed && _currentState != ServerState.OpsOnly && !SYSTEM_LOCK; + } + } + + + public bool IsClosed + { + get + { + return _currentState == ServerState.Closed || SYSTEM_LOCK; + } + } + + // public bool IsOpenOrOpsOnly + // { + // get + // { + // return (IsOpen || IsOpsOnly) && !SYSTEM_LOCK; + // } + // } + + public bool IsSystemLocked + { + get + { + return SYSTEM_LOCK; + } + } + + + + + }//eoc + + +}//eons \ No newline at end of file diff --git a/server/ControllerHelpers/ApiUploadProcessor.cs b/server/ControllerHelpers/ApiUploadProcessor.cs new file mode 100644 index 0000000..6280636 --- /dev/null +++ b/server/ControllerHelpers/ApiUploadProcessor.cs @@ -0,0 +1,180 @@ +using System; +using System.Threading.Tasks; +using System.IO; +using System.Text; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Net.Http.Headers; +using System.Collections.Generic; +using Sockeye.Models; +using Sockeye.Util; + + +namespace Sockeye.Api.ControllerHelpers +{ + + + //Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming + //See AttachmentController at bottom of class for example of form that works with this code + + /// + /// Handle processing uplod form with potentially huge files being uploaded (which means can't use simplest built in upload handler method) + /// + internal static class ApiUploadProcessor + { + + + /// + /// handle upload + /// + /// + /// list of files and form field data (if present) + internal static async Task ProcessUploadAsync(Microsoft.AspNetCore.Http.HttpContext httpContext) + { + + ApiUploadedFilesResult result = new ApiUploadedFilesResult(); + FormOptions _defaultFormOptions = new FormOptions(); + try + { + // Used to accumulate all the form url encoded key value pairs in the + // request. + var formAccumulator = new KeyValueAccumulator(); + + var boundary = MultipartRequestHelper.GetBoundary( + MediaTypeHeaderValue.Parse(httpContext.Request.ContentType), + _defaultFormOptions.MultipartBoundaryLengthLimit); + var reader = new MultipartReader(boundary, httpContext.Request.Body); + + var section = await reader.ReadNextSectionAsync(); + + while (section != null) + { + ContentDispositionHeaderValue contentDisposition; + var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition); + + if (hasContentDispositionHeader) + { + if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition)) + { + + string filePathAndName = string.Empty; + var CleanedUploadFileName = contentDisposition.FileName.Value.Replace("\"", ""); + + //get temp file path and temp file name + filePathAndName = FileUtil.NewRandomAttachmentFilesFolderFileName; + + //save to disk + using (var stream = new FileStream(filePathAndName, FileMode.Create)) + { + await section.Body.CopyToAsync(stream); + } + result.UploadedFiles.Add(new UploadedFileInfo() + { + InitialUploadedPathName = filePathAndName, + OriginalFileName = CleanedUploadFileName, + MimeType = section.ContentType + + }); + } + else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition)) + { + // Content-Disposition: form-data; name="key" + // + // value + + // Do not limit the key name length here because the + // multipart headers length limit is already in effect. + var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name); + var encoding = GetEncoding(section); + using (var streamReader = new StreamReader( + section.Body, + encoding, + detectEncodingFromByteOrderMarks: true, + bufferSize: 1024, + leaveOpen: true)) + { + // The value length limit is enforced by MultipartBodyLengthLimit + var value = await streamReader.ReadToEndAsync(); + if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase)) + { + value = String.Empty; + } + formAccumulator.Append(key.Value, value); + + if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit) + { + throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded."); + } + } + } + } + + // Drains any remaining section body that has not been consumed and + // reads the headers for the next section. + section = await reader.ReadNextSectionAsync(); + } + + //Get any extra form fields and return them + result.FormFieldData = formAccumulator.GetResults(); + return result; + } + catch (Microsoft.AspNetCore.Http.BadHttpRequestException ex) + { + //most commonly here due to file too large + result.Error = $"Code:{ex.StatusCode}, Error: {ex.Message}"; + return result; + + } + + } + + public static void DeleteTempUploadFile(ApiUploadedFilesResult uploadFormData) + { + if (uploadFormData.UploadedFiles.Count > 0) + { + foreach (UploadedFileInfo a in uploadFormData.UploadedFiles) + { + System.IO.File.Delete(a.InitialUploadedPathName); + } + } + } + + private static Encoding GetEncoding(MultipartSection section) + { + MediaTypeHeaderValue mediaType; + var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType); + // UTF-7 is insecure and should not be honored. UTF-8 will succeed in + // most cases. + if (!hasMediaTypeHeader || Encoding.UTF8.Equals(mediaType.Encoding)) + { + return Encoding.UTF8; + } + return mediaType.Encoding; + } + + + /// + /// Contains result of upload form processor + /// + public class ApiUploadedFilesResult + { + public Dictionary FormFieldData { get; set; } + public List UploadedFiles { get; set; } + public string Error { get; set; } + + public ApiUploadedFilesResult() + { + FormFieldData = new Dictionary(); + UploadedFiles = new List(); + Error = null; + } + + } + + + + + }//eoc + + +}//eons diff --git a/server/ControllerHelpers/Authorized.cs b/server/ControllerHelpers/Authorized.cs new file mode 100644 index 0000000..4fc5523 --- /dev/null +++ b/server/ControllerHelpers/Authorized.cs @@ -0,0 +1,228 @@ +using EnumsNET; +using System.Collections.Generic; +using Sockeye.Biz; + + +namespace Sockeye.Api.ControllerHelpers +{ + + //AUTHORIZATION ROLES: NOTE - this is only 'stage1' of generally checking rights, individual objects can also have business rules that affect access exactly as these roles do + //Most objects won't need more than this but some specialized ones will have further checks depending on biz rules + + internal static class Authorized + { + + /// + /// User has any role restricted or full + /// + /// + /// + /// + internal static bool HasAnyRole(IDictionary HttpContextItems, AuthorizationRoles CheckRoles) + { + AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems); + return HasAnyRole(currentUserRoles, CheckRoles); + } + + /// + /// User has any role restricted or full + /// + /// + /// + /// + internal static bool HasAnyRole(AuthorizationRoles currentUserRoles, AuthorizationRoles CheckRoles) + { + if (currentUserRoles.HasAnyFlags(CheckRoles)) + return true; + return false; + } + + /// + /// any access at all? + /// + /// + /// + /// + internal static bool HasAnyRole(IDictionary HttpContextItems, SockType aType) + { + AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems); + return HasAnyRole(currentUserRoles, aType); + } + + /// + /// User has any access at all to this object? + /// + /// + /// + /// + internal static bool HasAnyRole(AuthorizationRoles currentUserRoles, SockType aType) + { + var RoleSet = BizRoles.GetRoleSet(aType); + if (RoleSet == null) return false; + var AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change | RoleSet.Select; + return currentUserRoles.HasAnyFlags(AllowedRoles); + } + + /// + /// READ FULL RECORD (not just name and id) + /// + /// + /// + /// + internal static bool HasSelectRole(IDictionary HttpContextItems, SockType aType) + { + AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems); + return HasSelectRole(currentUserRoles, aType); + } + + /// + /// SELECT BY NAME + /// + /// + /// + /// + internal static bool HasSelectRole(AuthorizationRoles currentUserRoles, SockType aType) + { + var RoleSet = BizRoles.GetRoleSet(aType); + if (RoleSet == null) return false; + + //NOTE: this assumes that if you can change you can read + if (currentUserRoles.HasAnyFlags(RoleSet.Change)) + return true; + + if (currentUserRoles.HasAnyFlags(RoleSet.ReadFullRecord)) + return true; + + if (currentUserRoles.HasAnyFlags(RoleSet.Select)) + return true; + + return false; + } + + + /// + /// READ FULL RECORD (not just name and id) + /// + /// + /// + /// + internal static bool HasReadFullRole(IDictionary HttpContextItems, SockType aType) + { + AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems); + return HasReadFullRole(currentUserRoles, aType); + } + + /// + /// READ FULL RECORD (not just name and id) + /// + /// + /// + /// + internal static bool HasReadFullRole(AuthorizationRoles currentUserRoles, SockType aType) + { + //NOTE: this assumes that if you can change you can read + var RoleSet = BizRoles.GetRoleSet(aType); + if (RoleSet == null) return false; + var AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + return currentUserRoles.HasAnyFlags(AllowedRoles); + } + + + + /// + /// CREATE + /// + /// + /// + /// + internal static bool HasCreateRole(IDictionary HttpContextItems, SockType aType) + { + AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems); + return HasCreateRole(currentUserRoles, aType); + } + + /// + /// CREATE + /// + /// + /// + /// + internal static bool HasCreateRole(AuthorizationRoles currentUserRoles, SockType aType) + { + var RoleSet = BizRoles.GetRoleSet(aType); + if (RoleSet == null) return false; + if (currentUserRoles.HasAnyFlags(RoleSet.Change)) + return true; + return false; + } + + + /// + /// MODIFY + /// + /// + /// + + /// + internal static bool HasModifyRole(IDictionary HttpContextItems, SockType aType) + { + AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems); + return HasModifyRole(currentUserRoles, aType); + } + + + /// + /// MODIFY + /// + /// + /// + /// + internal static bool HasModifyRole(AuthorizationRoles currentUserRoles, SockType aType) + { + var RoleSet = BizRoles.GetRoleSet(aType); + if (RoleSet == null) return false; + if (currentUserRoles.HasAnyFlags(RoleSet.Change)) + return true; + return false; + } + + + + + /// + /// DELETE + /// + /// + /// + /// + //For now just going to treat as a modify, but for maximum flexibility keeping this as a separate method in case we change our minds in future + internal static bool HasDeleteRole(IDictionary HttpContextItems, SockType aType) + { + AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems); + long currentUserId = UserIdFromContext.Id(HttpContextItems); + return HasDeleteRole(currentUserRoles, aType); + } + + + /// + /// DELETE + /// + /// + /// + /// + //For now just going to treat as a modify, but for maximum flexibility keeping this as a separate method in case we change our minds in future + internal static bool HasDeleteRole(AuthorizationRoles currentUserRoles, SockType aType) + { + var RoleSet = BizRoles.GetRoleSet(aType); + if (RoleSet == null) return false; + if (currentUserRoles.HasAnyFlags(RoleSet.Change)) + return true; + return false; + } + + + + } + + +}//eons \ No newline at end of file diff --git a/server/ControllerHelpers/DisableFormValueModelBindingAttribute.cs b/server/ControllerHelpers/DisableFormValueModelBindingAttribute.cs new file mode 100644 index 0000000..ec0f58e --- /dev/null +++ b/server/ControllerHelpers/DisableFormValueModelBindingAttribute.cs @@ -0,0 +1,50 @@ +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Sockeye.Api.ControllerHelpers +{ + + //FROM DOCS HERE: + //https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming + //https://github.com/aspnet/Docs/tree/74a44669d5e7039e2d4d2cb3f8b0c4ed742d1124/aspnetcore/mvc/models/file-uploads/sample/FileUploadSample + /// + /// + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter + { + /// + /// + /// + /// + public void OnResourceExecuting(ResourceExecutingContext context) + { + var formValueProviderFactory = context.ValueProviderFactories + .OfType() + .FirstOrDefault(); + if (formValueProviderFactory != null) + { + context.ValueProviderFactories.Remove(formValueProviderFactory); + } + + var jqueryFormValueProviderFactory = context.ValueProviderFactories + .OfType() + .FirstOrDefault(); + if (jqueryFormValueProviderFactory != null) + { + context.ValueProviderFactories.Remove(jqueryFormValueProviderFactory); + } + } + +/// +/// +/// +/// + public void OnResourceExecuted(ResourceExecutedContext context) + { + } + } + +} \ No newline at end of file diff --git a/server/ControllerHelpers/MultipartRequestHelper.cs b/server/ControllerHelpers/MultipartRequestHelper.cs new file mode 100644 index 0000000..f0ca3f9 --- /dev/null +++ b/server/ControllerHelpers/MultipartRequestHelper.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using Microsoft.Net.Http.Headers; + +namespace Sockeye.Api.ControllerHelpers +{ + /// + /// + /// + public static class MultipartRequestHelper + { + // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq" + // The spec says 70 characters is a reasonable limit. + + /// + /// + /// + /// + /// + /// + public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit) + { + var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value; + if (string.IsNullOrWhiteSpace(boundary)) + { + throw new InvalidDataException("Missing content-type boundary."); + } + + if (boundary.Length > lengthLimit) + { + throw new InvalidDataException( + $"Multipart boundary length limit {lengthLimit} exceeded."); + } + + return boundary; + } + + /// + /// + /// + /// + /// + public static bool IsMultipartContentType(string contentType) + { + return !string.IsNullOrEmpty(contentType) + && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0; + } + + /// + /// + /// + /// + /// + public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition) + { + // Content-Disposition: form-data; name="key"; + return contentDisposition != null + && contentDisposition.DispositionType.Equals("form-data") + && string.IsNullOrEmpty(contentDisposition.FileName.Value) + && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value); + } + + /// + /// + /// + /// + /// + public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition) + { + // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg" + return contentDisposition != null + && contentDisposition.DispositionType.Equals("form-data") + && (!string.IsNullOrEmpty(contentDisposition.FileName.Value) + || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value)); + } + } +} \ No newline at end of file diff --git a/server/ControllerHelpers/UserIdFromContext.cs b/server/ControllerHelpers/UserIdFromContext.cs new file mode 100644 index 0000000..8915c80 --- /dev/null +++ b/server/ControllerHelpers/UserIdFromContext.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Sockeye.Api.ControllerHelpers +{ + internal static class UserIdFromContext + { + internal static long Id(IDictionary HttpContextItems) + { + + long? l = (long?)HttpContextItems["AY_USER_ID"]; + if (l==null) + return 0L; + return (long)l; + } + } +}//eons \ No newline at end of file diff --git a/server/ControllerHelpers/UserNameFromContext.cs b/server/ControllerHelpers/UserNameFromContext.cs new file mode 100644 index 0000000..43465ce --- /dev/null +++ b/server/ControllerHelpers/UserNameFromContext.cs @@ -0,0 +1,20 @@ +using EnumsNET; +using System.Collections.Generic; + +namespace Sockeye.Api.ControllerHelpers +{ + + + internal static class UserNameFromContext + { + internal static string Name(IDictionary HttpContextItems) + { + string s = (string)HttpContextItems["AY_USERNAME"]; + if (string.IsNullOrWhiteSpace(s)) + return "UNKNOWN USER NAME"; + return s; + } + } + + +}//eons diff --git a/server/ControllerHelpers/UserRolesFromContext.cs b/server/ControllerHelpers/UserRolesFromContext.cs new file mode 100644 index 0000000..b61d4eb --- /dev/null +++ b/server/ControllerHelpers/UserRolesFromContext.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Sockeye.Biz; + +namespace Sockeye.Api.ControllerHelpers +{ + + + internal static class UserRolesFromContext + { + internal static AuthorizationRoles Roles(IDictionary HttpContextItems) + { + return (AuthorizationRoles)HttpContextItems["AY_ROLES"]; + } + } + + +}//eons \ No newline at end of file diff --git a/server/ControllerHelpers/UserTranslationIdFromContext.cs b/server/ControllerHelpers/UserTranslationIdFromContext.cs new file mode 100644 index 0000000..ebdb3f2 --- /dev/null +++ b/server/ControllerHelpers/UserTranslationIdFromContext.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Sockeye.Api.ControllerHelpers +{ + internal static class UserTranslationIdFromContext + { + internal static long Id(IDictionary HttpContextItems) + { + long? l = (long?)HttpContextItems["AY_TRANSLATION_ID"]; + if (l == null) + return Sockeye.Util.ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID; + return (long)l; + } + } +}//eons \ No newline at end of file diff --git a/server/ControllerHelpers/UserTypeFromContext.cs b/server/ControllerHelpers/UserTypeFromContext.cs new file mode 100644 index 0000000..677af58 --- /dev/null +++ b/server/ControllerHelpers/UserTypeFromContext.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Sockeye.Biz; + +namespace Sockeye.Api.ControllerHelpers +{ + + + internal static class UserTypeFromContext + { + internal static UserType Type(IDictionary HttpContextItems) + { + return (UserType)HttpContextItems["AY_USER_TYPE"]; + } + } + + +}//eons \ No newline at end of file diff --git a/server/Controllers/ApiRootController.cs b/server/Controllers/ApiRootController.cs new file mode 100644 index 0000000..b46e3e8 --- /dev/null +++ b/server/Controllers/ApiRootController.cs @@ -0,0 +1,520 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Microsoft.AspNetCore.Authorization; +using System.Linq; +using Sockeye.Biz; + +namespace Sockeye.Api.Controllers +{ + /// + /// Meta controller class + /// + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/")] + [Authorize] + [ApiController] + public class ApiMetaController : ControllerBase + { + private readonly ApiServerState serverState; + private readonly ILogger _log; + + /// + /// + /// + /// + /// + public ApiMetaController(ILogger logger, ApiServerState apiServerState) + { + _log = logger; + serverState = apiServerState; + } + + private static string SupportUrl() + { + /* + contactSupportUrl() { + let dbId = encodeURIComponent( + window.$gz.store.state.globalSettings.serverDbId + ); + let company = encodeURIComponent( + window.$gz.store.state.globalSettings.company + ); + return `https://contact.ayanova.com/contact?dbid=${dbId}&company=${company}`; + } + */ + + + + return $"https://contact.ayanova.com/contact"; + } + + + /// + /// Server landing page + /// + /// + [AllowAnonymous] + [HttpGet] + public ContentResult Index() + { + //https://superuser.com/questions/361297/what-colour-is-the-dark-green-on-old-fashioned-green-screen-computer-displays + var errorBlock = string.Empty; + if (serverState.IsSystemLocked) + errorBlock = $@"

SERVER LOCKED

{serverState.Reason}

"; + var resp = $@" + + + + + Sockeye server + + + + + {errorBlock} + + + "; + System.Text.RegularExpressions.Regex reg = new System.Text.RegularExpressions.Regex(@"(?<=\s)\s+"); + resp = reg.Replace(resp, string.Empty).Replace("\n", "").Replace("\t", ""); + return new ContentResult + { + ContentType = "text/html", + StatusCode = 200, + Content = resp + }; + } + + + /// + /// Get API server info for ABOUT form Sockeye display + /// + /// API server info + [HttpGet("server-info")] + public ActionResult ServerInfo() + { + return Ok(new + { + data = new + { + ServerVersion = SockeyeVersion.FullNameAndVersion, + DBSchemaVersion = AySchema.currentSchema, + ServerLocalTime = DateUtil.ServerDateTimeString(System.DateTime.UtcNow), + ServerTimeZone = TimeZoneInfo.Local.Id, + + + } + }); + } + + /// + /// Get list of types and roles required + /// + /// A list of SockType role rights + [HttpGet("role-rights")] + public ActionResult RoleRights() + { + return Ok(new + { + data = new + { + SockTypes = BizRoles.roles.OrderBy(z => z.Key.ToString()).Select(z => new { SockType = z.Key.ToString(), Change = z.Value.Change.ToString(), ReadFullRecord = z.Value.ReadFullRecord.ToString(), Select = z.Value.Select.ToString() }).ToList() + + } + }); + } + + +#if (DEBUG) + /// + /// Get build mode of server, used for automated testing purposes + /// + /// "DEBUG" or "RELEASE" + [HttpGet("build-mode")] + public ActionResult BuildMode() + { + return Ok(new { data = new { BuildMode = "DEBUG" } }); + } +#else + /// + /// Get build mode of server, used for automated testing purposes + /// + /// "DEBUG" or "RELEASE" + [HttpGet("build-mode")] + public ActionResult BuildMode() + { + return Ok(new { data = new { BuildMode = "RELEASE" } }); + } +#endif + + + + } +} + +#region sigtest script +/* + +
+
+

SIGTEST - 1

+

This is the signature header

+ +

+ Your browser does not support signing
+ The following browsers are supported:
+ IE 9.0 +, FIREFOX 3.0 +, SAFARI 3.0 +, CHROME 3.0 +, OPERA 10.0 +, IPAD 1.0 +, IPHONE + 1.0 +, ANDROID 1.0 +

+
+
+ {SigScript()} + + + + + + private string SigScript(){ + return @""; +} + */ +#endregion diff --git a/server/Controllers/AttachmentController.cs b/server/Controllers/AttachmentController.cs new file mode 100644 index 0000000..0917ce9 --- /dev/null +++ b/server/Controllers/AttachmentController.cs @@ -0,0 +1,666 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Microsoft.EntityFrameworkCore; +using System.IO; +using System.ComponentModel.DataAnnotations; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Util; +using Sockeye.Biz; +using System.Linq; +using System.Collections.Generic; + +namespace Sockeye.Api.Controllers +{ + + //FROM DOCS HERE: + //https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming + //https://github.com/aspnet/Docs/tree/74a44669d5e7039e2d4d2cb3f8b0c4ed742d1124/aspnetcore/mvc/models/file-uploads/sample/FileUploadSample + + + /// + /// Attachment controller + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/attachment")] + [Produces("application/json")] + public class AttachmentController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// + /// + /// + /// + /// + public AttachmentController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + public class UpdateAttachmentInfo + { + [Required] + public uint Concurrency { get; set; } + [Required] + public string DisplayFileName { get; set; } + public string Notes { get; set; } + + } + + /// + /// Update FileAttachment + /// (FileName and notes only) + /// + /// + /// + /// list + [Authorize] + [HttpPut("{id}")] + public async Task PutAttachment([FromRoute] long id, [FromBody] UpdateAttachmentInfo inObj) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var dbObject = await ct.FileAttachment.SingleOrDefaultAsync(z => z.Id == id); + if (dbObject == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + long UserId = UserIdFromContext.Id(HttpContext.Items); + + if (!Authorized.HasModifyRole(HttpContext.Items, dbObject.AttachToAType)) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + + try + { + string ChangeTextra = string.Empty; + if (dbObject.DisplayFileName != inObj.DisplayFileName) + { + ChangeTextra = $"\"{dbObject.DisplayFileName}\" => \"{inObj.DisplayFileName}\""; + } + if (dbObject.Notes != inObj.Notes) + { + if (!string.IsNullOrWhiteSpace(ChangeTextra)) + ChangeTextra += ", "; + ChangeTextra += "Notes"; + } + dbObject.DisplayFileName = inObj.DisplayFileName; + dbObject.Notes = inObj.Notes; + + + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(dbObject).OriginalValues["Concurrency"] = inObj.Concurrency; + + //Log event and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentModified, ChangeTextra), ct); + + + + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationIdFromContext.Id(HttpContext.Items), id, SockType.FileAttachment); + SearchParams.AddText(inObj.Notes).AddText(inObj.DisplayFileName); + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + + //-------------- + + } + catch (DbUpdateConcurrencyException) + { + return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT)); + } + + //Normallyh wouldn't return a whole list but in this case the UI demands it because of reactivity issues + var ret = await GetFileListForObjectAsync(dbObject.AttachToAType, dbObject.AttachToObjectId); + return Ok(ApiOkResponse.Response(ret)); + } + + + /// + /// Get attachments for object type and id specified + /// + /// Required Role: Read full object properties rights to object type specified + /// + /// + /// file attachment list for object + [Authorize] + [HttpGet("list")] + public async Task GetList([FromQuery] SockType sockType, [FromQuery] long sockId) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //Is this a customer user attempting to view a wo attachments?? + // var userType = UserTypeFromContext.Type(HttpContext.Items); + + if (!Authorized.HasReadFullRole(HttpContext.Items, sockType)) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + + var ret = await GetFileListForObjectAsync(sockType, sockId); + return Ok(ApiOkResponse.Response(ret)); + } + + + /// + /// Get parent object type and id + /// for specified attachment id + /// + /// + /// + [Authorize] + [HttpGet("parent/{id}")] + public async Task GetParent([FromRoute] long id) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.FileAttachment)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var at = await ct.FileAttachment.AsNoTracking().Where(z => z.Id == id).FirstOrDefaultAsync(); + if (at == null) + return NotFound(); + + + return Ok(ApiOkResponse.Response(new { id = at.AttachToObjectId, type = at.AttachToAType })); + } + + + + + /// + /// Upload attachment file + /// Max 10GiB total + /// Requires same Authorization roles as object that file is being attached to + /// + /// + /// NameValue list of filenames and attachment id's + [Authorize] + [HttpPost] + [DisableFormValueModelBinding] + [RequestSizeLimit(ServerBootConfig.MAX_ATTACHMENT_UPLOAD_BYTES)] + public async Task UploadAsync() + { + //Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming + + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + // var returnList = new List(); + object ret = null; + SockTypeId attachToObject = null; + try + { + if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Expected a multipart request, but got {Request.ContentType}")); + + + + bool badRequest = false; + string AttachToAType = string.Empty; + string AttachToObjectId = string.Empty; + string errorMessage = string.Empty; + string Notes = string.Empty; + long? OverrideUserId = null; + List FileData = new List(); + + var uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext); + if (!string.IsNullOrWhiteSpace(uploadFormData.Error)) + { + badRequest = true; + errorMessage = uploadFormData.Error; + } + + if (!badRequest + && (!uploadFormData.FormFieldData.ContainsKey("FileData") + || !uploadFormData.FormFieldData.ContainsKey("AttachToAType") + || !uploadFormData.FormFieldData.ContainsKey("AttachToObjectId"))) + { + badRequest = true; + errorMessage = "Missing one or more required FormFieldData values: AttachToAType, AttachToObjectId, FileData"; + } + if (!badRequest) + { + AttachToAType = uploadFormData.FormFieldData["AttachToAType"].ToString(); + //for v8 migrate purposes + if (uploadFormData.FormFieldData.ContainsKey("OverrideUserId")) + OverrideUserId = long.Parse(uploadFormData.FormFieldData["OverrideUserId"].ToString()); + + AttachToObjectId = uploadFormData.FormFieldData["AttachToObjectId"].ToString(); + if (uploadFormData.FormFieldData.ContainsKey("Notes")) + Notes = uploadFormData.FormFieldData["Notes"].ToString(); + //fileData in JSON stringify format which contains the actual last modified dates etc + //"[{\"name\":\"Client.csv\",\"lastModified\":1582822079618},{\"name\":\"wmi4fu06nrs41.jpg\",\"lastModified\":1586900220990}]" + FileData = Newtonsoft.Json.JsonConvert.DeserializeObject>(uploadFormData.FormFieldData["FileData"].ToString()); + + if (string.IsNullOrWhiteSpace(AttachToAType) || string.IsNullOrWhiteSpace(AttachToObjectId)) + { + badRequest = true; + errorMessage = "AttachToAType and / or AttachToObjectId are empty and are required"; + } + } + + + //Get type and id object from post paramters + + if (!badRequest) + { + attachToObject = new SockTypeId(AttachToAType, AttachToObjectId); + if (attachToObject.IsEmpty) + { + badRequest = true; + errorMessage = "AttachToAType and / or AttachToObjectId are not valid and are required"; + } + } + + //Is it an attachable type of object? + if (!badRequest) + { + if (!attachToObject.IsCoreBizObject) + { + badRequest = true; + errorMessage = attachToObject.SockType.ToString() + " - AttachToAType does not support attachments"; + } + } + + + //does attach to object exist? + if (!badRequest) + { + //check if object exists + if (!await BizObjectExistsInDatabase.ExistsAsync(attachToObject.SockType, attachToObject.ObjectId, ct)) + { + badRequest = true; + errorMessage = "Invalid attach object"; + } + else + { + // User needs modify rights to the object type in question + if (!Authorized.HasModifyRole(HttpContext.Items, attachToObject.SockType)) + { + //delete temp files + ApiUploadProcessor.DeleteTempUploadFile(uploadFormData); + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + } + } + + + + if (badRequest) + { + //delete temp files + ApiUploadProcessor.DeleteTempUploadFile(uploadFormData); + //file too large is most likely issue so in that case return this localized properly + if (errorMessage.Contains("413")) + { + var TransId = UserTranslationIdFromContext.Id(HttpContext.Items); + return BadRequest(new ApiErrorResponse( + ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, + null, + "HTTP ERROR CODE 413 " + String.Format(await TranslationBiz.GetTranslationStaticAsync("AyaFileFileTooLarge", TransId, ct), FileUtil.GetBytesReadable(ServerBootConfig.MAX_ATTACHMENT_UPLOAD_BYTES)))); + } + else//not too big, something else + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, errorMessage)); + } + + long UserId = UserIdFromContext.Id(HttpContext.Items); + + //We have our files and a confirmed AyObject, ready to attach and save permanently + if (uploadFormData.UploadedFiles.Count > 0) + { + + foreach (UploadedFileInfo a in uploadFormData.UploadedFiles) + { + //Get the actual date from the separate filedata + //this is because the lastModified date is always empty in the form data files + DateTime theDate = DateTime.MinValue; + foreach (UploadFileData f in FileData) + { + if (f.name == a.OriginalFileName) + { + if (f.lastModified > 0) + { + theDate = DateTimeOffset.FromUnixTimeMilliseconds(f.lastModified).UtcDateTime; + } + } + } + if (theDate == DateTime.MinValue) + theDate = DateTime.UtcNow; + + var v = await FileUtil.StoreFileAttachmentAsync(a.InitialUploadedPathName, a.MimeType, a.OriginalFileName, theDate, attachToObject, Notes, OverrideUserId ?? UserId, ct); + + //EVENT LOG + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, attachToObject.ObjectId, attachToObject.SockType, SockEvent.AttachmentCreate, v.DisplayFileName), ct); + + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationIdFromContext.Id(HttpContext.Items), v.Id, SockType.FileAttachment); + SearchParams.AddText(v.Notes).AddText(v.DisplayFileName); + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + + } + } + ret = await GetFileListForObjectAsync(attachToObject.SockType, attachToObject.ObjectId); + } + catch (InvalidDataException ex) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, ex.Message)); + } + + //Return the list of attachment ids and filenames + return Ok(ApiOkResponse.Response(ret)); + } + + // /// + // /// Utility to delete files that were uploaded but couldn't be stored for some reason, called by Attach route + // /// + // /// + // private static void DeleteTempFileUploadDueToBadRequest(ApiUploadProcessor.ApiUploadedFilesResult uploadFormData) + // { + // if (uploadFormData.UploadedFiles.Count > 0) + // { + // foreach (UploadedFileInfo a in uploadFormData.UploadedFiles) + // { + // System.IO.File.Delete(a.InitialUploadedPathName); + // } + // } + // } + + + /// + /// Delete Attachment + /// + /// + /// Ok + [Authorize] + [HttpDelete("{id}")] + public async Task DeleteAttachmentAsync([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + var dbObject = await ct.FileAttachment.SingleOrDefaultAsync(z => z.Id == id); + if (dbObject == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + long UserId = UserIdFromContext.Id(HttpContext.Items); + + if (!Authorized.HasDeleteRole(HttpContext.Items, dbObject.AttachToAType)) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + //do the delete + //this handles removing the file if there are no refs left and also the db record for the attachment + await FileUtil.DeleteFileAttachmentAsync(dbObject, ct); + + //Event log process delete + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentDelete, dbObject.DisplayFileName), ct); + + //Delete search index + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, SockType.FileAttachment, ct); + + return NoContent(); + } + + /// + /// Batch delete attachments + /// + /// + /// No content + [HttpPost("batch-delete")] + [Authorize] + public async Task PostBatchDelete([FromBody] List idList) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.FileAttachment)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + long UserId = UserIdFromContext.Id(HttpContext.Items); + + foreach (long id in idList) + { + var dbObject = await ct.FileAttachment.FirstOrDefaultAsync(z => z.Id == id); + if (dbObject == null) + continue; + //do the delete + //this handles removing the file if there are no refs left and also the db record for the attachment + await FileUtil.DeleteFileAttachmentAsync(dbObject, ct); + + //Event log process delete + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentDelete, dbObject.DisplayFileName), ct); + + //Delete search index + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, SockType.FileAttachment, ct); + } + return NoContent(); + } + /// + /// Batch move attachments + /// + /// + /// No content + [HttpPost("batch-move")] + [Authorize] + public async Task PostBatchMove([FromBody] dtoBatchMove dt) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.FileAttachment)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await BizObjectExistsInDatabase.ExistsAsync(dt.ToType, dt.ToId, ct)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, null, "LT:ErrorAPI2010")); + + long UserId = UserIdFromContext.Id(HttpContext.Items); + + foreach (long id in dt.IdList) + { + var dbObject = await ct.FileAttachment.FirstOrDefaultAsync(z => z.Id == id); + if (dbObject == null) + continue; + + //do the move + var msg = $"{dbObject.DisplayFileName} moved from {dbObject.AttachToAType}-{dbObject.AttachToObjectId} to {dt.ToType}-{dt.ToId} "; + dbObject.AttachToObjectId = dt.ToId; + dbObject.AttachToAType = dt.ToType; + await ct.SaveChangesAsync(); + + //Event log process move + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentModified, msg), ct); + + } + return NoContent(); + } + + public class dtoBatchMove + { + public List IdList { get; set; } + public SockType ToType { get; set; } + public long ToId { get; set; } + } + + /// + /// Download a file attachment + /// + /// + /// download token + /// + [HttpGet("download/{id}")] + public async Task DownloadAsync([FromRoute] long id, [FromQuery] string t) + { + //https://dotnetcoretutorials.com/2017/03/12/uploading-files-asp-net-core/ + //https://stackoverflow.com/questions/45763149/asp-net-core-jwt-in-uri-query-parameter/45811270#45811270 + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + + var DownloadUser = await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct); + if (DownloadUser == null) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + + //Ok, user has a valid download key and it's not expired yet so get the attachment record + var dbObject = await ct.FileAttachment.SingleOrDefaultAsync(z => z.Id == id); + if (dbObject == null) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//fishing protection + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + //is this allowed? + + if (!Authorized.HasReadFullRole(DownloadUser.Roles, dbObject.AttachToAType)) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + + //they are allowed, let's send the file + string mimetype = dbObject.ContentType; + var filePath = FileUtil.GetPermanentAttachmentFilePath(dbObject.StoredFileName); + if (!System.IO.File.Exists(filePath)) + { + + //TODO: this should reset the validity + + var errText = $"Physical file {dbObject.StoredFileName} not found despite attachment record, this file is missing"; + log.LogError(errText); + await NotifyEventHelper.AddOpsProblemEvent($"File attachment issue: {errText}"); + + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, null, errText)); + } + + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(DownloadUser.Id, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentDownload, dbObject.DisplayFileName), ct); + + return PhysicalFile(filePath, mimetype, dbObject.DisplayFileName); + + } + + //////////////////////////////////////////////////////////////////////////////////// + + + async private Task GetFileListForObjectAsync(SockType sockType, long sockId) + { + var retList = new List(); + using (var cmd = ct.Database.GetDbConnection().CreateCommand()) + { + await ct.Database.OpenConnectionAsync(); + cmd.CommandText = $@"select afileattachment.id, afileattachment.xmin as concurrency, displayfilename,contenttype,lastmodified, afileattachment.notes, size, auser.name as attachedbyuser from afileattachment + left join auser on (afileattachment.attachedByUserId=auser.id) + where attachtosockType={(int)sockType} and attachtoobjectid={sockId} + order by displayfilename"; + + using (var dr = await cmd.ExecuteReaderAsync()) + { + while (dr.Read()) + { + retList.Add(new FileAttachmentListItem() + { + Id = dr.GetInt64(0), + Concurrency = (UInt32)dr.GetValue(1), + DisplayFileName = dr.GetString(2), + ContentType = dr.GetString(3), + LastModified = dr.GetDateTime(4), + Notes = dr.GetString(5), + Size = dr.GetInt64(6), + AttachedByUser = dr.GetString(7) + }); + } + } + } + + return retList; + } + + private class FileAttachmentListItem + { + public long Id { get; set; } + public uint Concurrency { get; set; } + public string DisplayFileName { get; set; } + public string ContentType { get; set; }//mime type + public DateTime LastModified { get; set; } + public string Notes { get; set; } + public string AttachedByUser { get; set; } + public long Size { get; set; } + } + + /// + /// Trigger immediate AttachmentMaintenanceJob + /// + /// + /// Job Id + [HttpPost("maintenance")] + [Authorize] + public async Task PostTriggerAttachmentMaintenanceJob() + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.FileAttachment)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var JobName = $"Attachment maintenance (demand) LT:User {UserNameFromContext.Name(HttpContext.Items)}"; + OpsJob j = new OpsJob(); + j.Name = JobName; + j.SockType = SockType.FileAttachment; + j.JobType = JobType.AttachmentMaintenance; + j.SubType = JobSubType.NotSet; + j.Exclusive = true; + await JobsBiz.AddJobAsync(j); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct); + return Accepted(new { JobId = j.GId }); + } + + + }//eoc +}//eons + + diff --git a/server/Controllers/AuthController.cs b/server/Controllers/AuthController.cs new file mode 100644 index 0000000..848a510 --- /dev/null +++ b/server/Controllers/AuthController.cs @@ -0,0 +1,717 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using TwoFactorAuthNet; +using QRCoder; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using System.Linq; +using System; +using System.Threading.Tasks; +using Sockeye.Biz; + +//required to inject configuration in constructor +using Microsoft.Extensions.Configuration; + +namespace Sockeye.Api.Controllers +{ + /// + /// Authentication controller + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/auth")] + [Produces("application/json")] + [Authorize] + public class AuthController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly IConfiguration _configuration; + private readonly ApiServerState serverState; + private const int JWT_LIFETIME_DAYS = 5; + + /// + /// ctor + /// + /// + /// + /// + /// + public AuthController(AyContext context, ILogger logger, IConfiguration configuration, ApiServerState apiServerState) + { + ct = context; + log = logger; + _configuration = configuration; + serverState = apiServerState; + } + + //AUTHENTICATE CREDS + //RETURN JWT + + /// + /// Create credentials to receive a JSON web token + /// + /// + /// This route is used to authenticate to the Sockeye API. + /// Once you have a token you need to include it in all requests that require authentication like this: + /// Authorization: Bearer [TOKEN] + /// Note the space between Bearer and the token. Also, do not include the square brackets + /// + /// + /// + [HttpPost] + [AllowAnonymous] + public async Task PostCreds([FromBody] AuthController.CredentialsParam creds) //if was a json body then //public JsonResult PostCreds([FromBody] string login, [FromBody] string password) + { + + //NOTE: lockout or other login impacting state is processed later in ReturnUserCredsOnSuccessfulAuthentication() because many of those states need to have exceptions once the user is known + //or return alternate result of auth etc + + + + if (string.IsNullOrWhiteSpace(creds.Login) || string.IsNullOrWhiteSpace(creds.Password)) + { + //Make a failed pw wait + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + + //Multiple users are allowed the same password and login + //Salt will differentiate them so get all users that match login, then try to match pw + var users = await ct.User.Where(z => z.Login == creds.Login && z.Active == true && z.AllowLogin == true).ToListAsync(); + + foreach (User u in users) + { + string hashed = Hasher.hash(u.Salt, creds.Password); + if (hashed == u.Password) + { + + + //TWO FACTOR ENABLED?? + //if 2fa enabled then need to validate it before sending token, so we're halfway there and need to send a 2fa prompt + if (u.TwoFactorEnabled) + { + //Generate a temporary token to identify and verify this is the same user + u.TempToken = Hasher.GenerateSalt().Replace("=", "").Replace("+", ""); + await ct.SaveChangesAsync(); + var UOpt = await ct.UserOptions.AsNoTracking().FirstAsync(z => z.UserId == u.Id); + + List TranslationKeysToFetch = new List { "AuthTwoFactor", "AuthEnterPin", "AuthVerifyCode", "Cancel", "AuthPinInvalid" }; + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, UOpt.TranslationId); + + return Ok(ApiOkResponse.Response(new + { + AuthTwoFactor = LT["AuthTwoFactor"], + AuthEnterPin = LT["AuthEnterPin"], + AuthVerifyCode = LT["AuthVerifyCode"], + AuthPinInvalid = LT["AuthPinInvalid"], + Cancel = LT["Cancel"], + tfa = true, + tt = u.TempToken + })); + } + + //Not 2fa, Valid password, user is authorized + return await ReturnUserCredsOnSuccessfulAuthentication(u); + + + } + } + + //No users matched, it's a failed login + //Make a failed pw wait + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + + + + /// + /// Verify tfa code + /// + /// + /// This route is used to authenticate to the Sockeye API via tfa code. + /// Once you have a token you need to include it in all requests that require authentication like this: + /// Authorization: Bearer [TOKEN] + /// Note the space between Bearer and the token. Also, do not include the square brackets + /// + /// + /// + [HttpPost("tfa-authenticate")] + [AllowAnonymous] + public async Task TfaAuthenticate([FromBody] TFAPinParam pin) + { + //a bit different as ops users can still login if the state is opsonly + //so the only real barrier here would be a completely closed api + + + if (serverState.IsClosed) + { + return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + } + + if (string.IsNullOrWhiteSpace(pin.Pin)) + { + //Make a failed pw wait + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + //Match to temp token that would have been set by initial credentialed login for 2fa User + var user = await ct.User.Where(z => z.TempToken == pin.TempToken && z.Active == true && z.AllowLogin==true && z.TwoFactorEnabled == true).FirstOrDefaultAsync(); + + + if (user != null) + { + //Valid temp token, now check the pin code is right + if (string.IsNullOrWhiteSpace(user.TotpSecret)) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "generalerror", "2fa not activated")); + } + + //ok, something to validate, let's validate it + var tfa = new TwoFactorAuth("Sockeye"); + if (!tfa.VerifyCode(user.TotpSecret, pin.Pin.Replace(" ", "").Trim())) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + //User is valid and authenticated + //clear temp token + user.TempToken = string.Empty; + await ct.SaveChangesAsync(); + + return await ReturnUserCredsOnSuccessfulAuthentication(user); + } + + //No users matched, it's a failed login + //Make a failed pw wait + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + + + //return creds and or process lockout handling here + private async Task ReturnUserCredsOnSuccessfulAuthentication(User u) + { + + bool licenseLockout = false; + //check if server available to SuperUser account only (closed or migrate mode) + //if it is it means we got here either because there is no license + //and only *the* SuperUser account can login now or we're in migrate mode + if (serverState.IsClosed ) + { + //if not SuperUser account then boot closed + //SuperUser account is always ID 1 + if (u.Id != 1) + { + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + } + + } + //Restrict auth due to server state? + //If we're here it's the superuser or the server state is not closed, but it might be ops only + + //If the server is ops only then this user needs to be ops or else they are not allowed in + if ((u.Id != 1) && serverState.IsOpsOnly && + !u.Roles.HasFlag(Biz.AuthorizationRoles.OpsAdmin) && + !u.Roles.HasFlag(Biz.AuthorizationRoles.OpsAdminRestricted)) + { + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + } + + + //build the key (JWT set in startup.cs) + byte[] secretKey = System.Text.Encoding.ASCII.GetBytes(ServerBootConfig.SOCKEYE_JWT_SECRET); + + //create a new datetime offset of now in utc time + var iat = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero);//timespan zero means zero time off utc / specifying this is a UTC datetime + + //################################### + //Lifetime of jwt token + //after this point the user will no longer be able to make requests without logging in again + //and the client will automatically send them to the login screen + //so this is auto logout after this time period + + //security wise the length of time is not an issue how long this is because our system allows to revoke tokens as they are checked on every access + //the adivce online is to make it short and use refresh tokens but that's not an issue with our system since we both issue and validate + //the tokens ourselves + + //The only down side is that an expired license at the server will not prevent people from continuing to work until their token expires + //an expired license only stops a fresh login + //so whatever this value is will allow people who haven't logged out to continue to work until it expires + + //so this really only controls how long we allow them to work with an expired ayanova license which would be a rare occurence I suspect + //so really to prevent fuckery for people 5 days seems fine meaning they won't need to sign in again all business week if they want to continue working + var exp = new DateTimeOffset(DateTime.Now.AddDays(JWT_LIFETIME_DAYS).ToUniversalTime(), TimeSpan.Zero); + + + + //=============== download token =================== + //Generate a download token and store it with the user account + //string DownloadToken = Convert.ToBase64String(Guid.NewGuid().ToByteArray()); + string DownloadToken = Hasher.GenerateSalt(); + DownloadToken = DownloadToken.Replace("=", ""); + DownloadToken = DownloadToken.Replace("+", ""); + u.DlKey = DownloadToken; + u.DlKeyExpire = exp.UtcDateTime; + + //======================================================= + + var payload = new Dictionary() + { + // { "iat", iat.ToUnixTimeSeconds().ToString() }, + { "exp", exp.ToUnixTimeSeconds().ToString() },//in payload exp must be in unix epoch time per standard + { "iss", "rockfish.ayanova.com" }, + { "id", u.Id.ToString() } + }; + + + //NOTE: probably don't need Jose.JWT as am using Microsoft jwt stuff to validate routes so it should also be able to + //issue tokens as well, but it looked cmplex and this works so unless need to remove in future keeping it. + string token = Jose.JWT.Encode(payload, secretKey, Jose.JwsAlgorithm.HS256); + + //save auth token to ensure single sign on only + u.CurrentAuthToken = token; + + u.LastLogin = DateTime.UtcNow; + + await ct.SaveChangesAsync(); + + //KEEP this, masked version of IP address + //Not sure if this is necessary or not but if it turns out to be then make it a boot option + // log.LogInformation($"User number \"{u.Id}\" logged in from \"{Util.StringUtil.MaskIPAddress(HttpContext.Connection.RemoteIpAddress.ToString())}\" ok"); + + log.LogInformation($"User \"{u.Name}\" logged in from \"{HttpContext.Connection.RemoteIpAddress.ToString()}\" ok"); + + + //return appropriate data for user type... + if (u.UserType == UserType.Customer | u.UserType == UserType.HeadOffice) + { + //customer type has special rights restrictions for UI features so return them here so client UI can enable or disable + var effectiveRights = await UserBiz.CustomerUserEffectiveRightsAsync(u.Id); + //A non active Customer or Head Office record's contacts are also not allowed to login + if (!effectiveRights.EntityActive) + { + log.LogInformation($"Customer contact user \"{u.Name}\" attempted login was denied due to inactive parent (Customer or HeadOffice)"); + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + return Ok(ApiOkResponse.Response(new + { + token = token, + name = u.Name, + usertype = u.UserType, + roles = ((int)u.Roles).ToString(), + dlt = DownloadToken, + tfa = u.TwoFactorEnabled, + CustomerRights = effectiveRights + })); + } + else + { + //Non customer user + return Ok(ApiOkResponse.Response(new + { + token = token, + name = u.Name, + usertype = u.UserType, + roles = ((int)u.Roles).ToString(), + dlt = DownloadToken, + tfa = u.TwoFactorEnabled, + l = licenseLockout + })); + } + + //------------------------ /STANDARD BLOCK ------------------------- + } + + + + + + + + + + /// + /// Change Password + /// + /// + /// + [HttpPost("change-password")] + public async Task ChangePassword([FromBody] AuthController.ChangePasswordParam changecreds) + { + //Note: need to be authenticated to use this, only called from own user's UI + //it still asks for old creds in case someone attempts to do this on another user's logged in session + //Also it checks here that this is in fact the same user account calling this method as the user attempting to be modified + + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (string.IsNullOrWhiteSpace(changecreds.OldPassword) || string.IsNullOrWhiteSpace(changecreds.LoginName)) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + if (string.IsNullOrWhiteSpace(changecreds.NewPassword)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "NewPassword")); + + if (changecreds.NewPassword != changecreds.ConfirmPassword) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "ConfirmPassword", "NewPassword does not match ConfirmPassword")); + + //Multiple users are allowed the same password and login + //Salt will differentiate them so get all users that match login, then try to match pw + var users = await ct.User.AsNoTracking().Where(z => z.Login == changecreds.LoginName).ToListAsync(); + + foreach (User u in users) + { + string hashed = Hasher.hash(u.Salt, changecreds.OldPassword); + if (hashed == u.Password) + { + + //If the user is inactive they may not login + if (!u.Active || !u.AllowLogin) + { + //respond like bad creds so as not to leak information + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + //double check it's the currently logged in User's own User object only + //otherwise it's feasible someone could change someone else's password through their own change password form with a mis-type or intentional hack + if (u.Id != UserIdFromContext.Id(HttpContext.Items)) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + //fetch and update user + //Instantiate the business object handler + UserBiz biz = UserBiz.GetBiz(ct, HttpContext); + await biz.ChangePasswordAsync(u.Id, changecreds.NewPassword); + + return NoContent(); + + } + } + + //No users matched, it's a failed login + //Make a failed pw wait + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + + return BadRequest(new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + + /// + /// Change Password via reset token + /// + /// + /// + [HttpPost("reset-password")] + [AllowAnonymous] + public async Task ResetPassword([FromBody] AuthController.ResetPasswordParam resetcreds) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + if (string.IsNullOrWhiteSpace(resetcreds.PasswordResetCode)) + { + //Make a fail wait + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "PasswordResetCode", "Reset code is required")); + } + + if (string.IsNullOrWhiteSpace(resetcreds.Password)) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "Password", "Password is required")); + } + + //look for user with this reset code + var user = await ct.User.AsNoTracking().Where(z => z.PasswordResetCode == resetcreds.PasswordResetCode).FirstOrDefaultAsync(); + if (user == null) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "PasswordResetCode", "Reset code not valid")); + } + + if (string.IsNullOrWhiteSpace(user.PasswordResetCode) || user.PasswordResetCodeExpire == null) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "PasswordResetCode", "Reset code not valid")); + } + + //vet the expiry + var utcNow = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero); + if (user.PasswordResetCodeExpire < utcNow.DateTime) + {//if reset code expired before now + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, "PasswordResetCodeExpire", "Reset code has expired")); + } + //Ok, were in, it's all good, accept the new password and update the user record + UserBiz biz = UserBiz.GetBiz(ct, HttpContext); + await biz.ChangePasswordAsync(user.Id, resetcreds.Password); + return NoContent(); + } + + /// + /// Generate time limited password reset code for User + /// and email link to them so they can set their password + /// + /// + /// User id + /// From route path + /// New concurrency code + [HttpPost("request-reset-password/{id}")] + public async Task SendPasswordResetCode([FromRoute] long id, ApiVersion apiVersion) + { + //Note: this is not allowed for an anonymous users because it's only intended for now to work for staff user's who will send the request on behalf of the User + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + UserBiz biz = UserBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + uint res = await biz.SendPasswordResetCode(id); + if (res == 0) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + else + return Ok(ApiOkResponse.Response(new + { + concurrency = res + })); + } + + + + + /// + /// Generate TOTP secret and return for use in auth app + /// + /// + /// From route path + /// Authentication app activation code + [HttpGet("totp")] + public async Task GenerateAndSendTOTP(ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + UserBiz biz = UserBiz.GetBiz(ct, HttpContext); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //get user and save the secret + var UserId = UserIdFromContext.Id(HttpContext.Items); + + var u = await ct.User.FirstOrDefaultAsync(z => z.Id == UserId); + if (u == null)//should never happen but ? + return StatusCode(403, new ApiNotAuthorizedResponse()); + + //this is to stop someone from messing up someone's login accidentally or maliciously by simply hitting the route logged in as them + if (u.TwoFactorEnabled) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "generalerror", "2fa already enabled")); + + var tfa = new TwoFactorAuth("Sockeye"); + u.TotpSecret = tfa.CreateSecret(160); + await ct.SaveChangesAsync(); + + //https://github.com/google/google-authenticator/wiki/Key-Uri-Format + //otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30 + //this format tested and works with Google, Microsoft Authy, Duo authenticators + string payload = $"otpauth://totp/Sockeye:{u.Name}?secret={u.TotpSecret}&issuer=Sockeye&algorithm=SHA1&digits=6&period=30";//NOTE: the 30 here is seconds the totp code is allowed to be used before a new one is required + + QRCodeGenerator qrGenerator = new QRCodeGenerator(); + QRCodeData qrCodeData = qrGenerator.CreateQrCode(payload, QRCodeGenerator.ECCLevel.Q); + // Base64QRCode qrCode = new Base64QRCode(qrCodeData); + // string qrCodeImageAsBase64 = qrCode.GetGraphic(3); + + PngByteQRCode qpng = new PngByteQRCode(qrCodeData); + string qrCodeImageAsBase64 = Convert.ToBase64String(qpng.GetGraphic(3)); + + return Ok(ApiOkResponse.Response(new + { + s = u.TotpSecret, + qr = qrCodeImageAsBase64 + })); + } + + + /// + /// Confirm 2fa ready to use + /// + /// + /// Auth app 6 digit passcode + /// From route path + /// OK on success + [HttpPost("totp-validate")] + public async Task ValidateTOTP([FromBody] TFAPinParam pin, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //get user + var UserId = UserIdFromContext.Id(HttpContext.Items); + + var u = await ct.User.FirstOrDefaultAsync(z => z.Id == UserId); + if (u == null)//should never happen but ? + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + if (u.TwoFactorEnabled) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "generalerror", "2fa already enabled")); + } + + if (string.IsNullOrWhiteSpace(u.TotpSecret)) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "generalerror", "2fa activation code not requested yet (missed a step?)")); + } + + //ok, something to validate, let's validate it + var tfa = new TwoFactorAuth("Sockeye"); + var ret = tfa.VerifyCode(u.TotpSecret, pin.Pin.Replace(" ", "").Trim()); + if (ret == true) + { + //enable 2fa on user account + u.TwoFactorEnabled = true; + await ct.SaveChangesAsync(); + } + else + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY); + } + + return Ok(ApiOkResponse.Response(new + { + ok = ret + })); + } + + + /// + /// Disable (turn off) 2fa for user account + /// (For other user id requires full privileges) + /// + /// User id + /// From route path + /// OK on success + [HttpPost("totp-disable/{id}")] + public async Task DisableTOTP([FromRoute] long id, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var UserId = UserIdFromContext.Id(HttpContext.Items); + if (id != UserId) //doing it on behalf of someone else + { + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.User)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + + var u = await ct.User.FirstOrDefaultAsync(z => z.Id == id); + if (u == null)//should never happen but ? + return StatusCode(403, new ApiNotAuthorizedResponse()); + + u.TotpSecret = null; + u.TempToken = null; + u.TwoFactorEnabled = false; + await ct.SaveChangesAsync(); + return NoContent(); + } + + + //generate an internal JWT key for reporting purposes used by corejobnotify to send reports as manager account + internal static string GenRpt(long overrideLanguageId) + { + byte[] secretKey = System.Text.Encoding.ASCII.GetBytes(ServerBootConfig.SOCKEYE_JWT_SECRET); + var iat = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero); + var exp = new DateTimeOffset(DateTime.Now.AddMinutes(ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT).ToUniversalTime(), TimeSpan.Zero); + var payload = new Dictionary() + { + { "exp", exp.ToUnixTimeSeconds().ToString() },//in payload exp must be in unix epoch time per standard + { "iss", "rockfish.ayanova.com" }, + { "id", "1"}, + { "rpl",overrideLanguageId.ToString() } + }; + return Jose.JWT.Encode(payload, secretKey, Jose.JwsAlgorithm.HS256); + } + + //------------------------------------------------------ + + public class CredentialsParam + { + [System.ComponentModel.DataAnnotations.Required] + public string Login { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public string Password { get; set; } + + } + + + public class ChangePasswordParam + { + [System.ComponentModel.DataAnnotations.Required] + public string LoginName { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public string OldPassword { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public string NewPassword { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public string ConfirmPassword { get; set; } + + } + + + public class ResetPasswordParam + { + [System.ComponentModel.DataAnnotations.Required] + public string PasswordResetCode { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public string Password { get; set; } + } + + public class TFAPinParam + { + [System.ComponentModel.DataAnnotations.Required] + public string Pin { get; set; } + public string TempToken { get; set; } + + } + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/AuthorizationRolesController.cs b/server/Controllers/AuthorizationRolesController.cs new file mode 100644 index 0000000..2673cb1 --- /dev/null +++ b/server/Controllers/AuthorizationRolesController.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + +namespace Sockeye.Api.Controllers +{ + + /// + /// Enum pick list controller + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/authorization-roles")] + [Produces("application/json")] + [Authorize] + public class AuthorizationRolesController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public AuthorizationRolesController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + /// + /// Get roles + /// + /// Return as compact JSON format + /// Dictionary list of Sockeye object types and their authorization role rights in Sockeye + [HttpGet("list")] + public ActionResult GetRoles([FromQuery] bool AsJson = false) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //as json for client end of things + if (AsJson) + return Ok(ApiOkResponse.Response(Newtonsoft.Json.JsonConvert.SerializeObject(BizRoles.roles, Newtonsoft.Json.Formatting.None))); + else + return Ok(ApiOkResponse.Response(BizRoles.roles)); + } + + + + + + + + }//eoc +}//ens \ No newline at end of file diff --git a/server/Controllers/BackupController.cs b/server/Controllers/BackupController.cs new file mode 100644 index 0000000..eafc520 --- /dev/null +++ b/server/Controllers/BackupController.cs @@ -0,0 +1,160 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using System.Threading.Tasks; +using Sockeye.Util; +using System; +using Microsoft.AspNetCore.Http; + + + + +namespace Sockeye.Api.Controllers +{ + + + /// + /// Backup + /// + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/backup")] + [Produces("application/json")] + public class BackupController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + + /// + /// + /// + /// + /// + /// + public BackupController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + //DANGER: MUST ADD AUTHORIZE ATTRIBUTE TO ANY NEW ROUTES + //[Authorize] + + /// + /// Trigger immediate system backup + /// + /// + /// Job Id + [HttpPost("backup-now")] + [Authorize] + public async Task PostBackupNow() + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.Backup))//technically maybe this could be wider open, but for now keeping as locked down + return StatusCode(403, new ApiNotAuthorizedResponse()); + var JobName = $"LT:BackupNow LT:User {UserNameFromContext.Name(HttpContext.Items)}"; + OpsJob j = new OpsJob(); + j.Name = JobName; + j.SockType = SockType.NoType; + j.JobType = JobType.Backup; + j.SubType = JobSubType.NotSet; + j.Exclusive = true; + await JobsBiz.AddJobAsync(j); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct); + return Accepted(new { JobId = j.GId }); + } + + + /// + /// Get status of backup + /// + /// + [HttpGet("status")] + [Authorize] + public ActionResult BackupStatus() + { + //Need size and more info + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Backup)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + return Ok(ApiOkResponse.Response(FileUtil.BackupStatusReport())); + } + + /// + /// Download a backup file + /// + /// + /// download token + /// + [HttpGet("download/{fileName}")] + public async Task DownloadAsync([FromRoute] string fileName, [FromQuery] string t) + { + + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + var DownloadUser = await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct); + if (DownloadUser == null) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + if (!Authorized.HasModifyRole(DownloadUser.Roles, SockType.Backup))//not technically modify but treating as such as a backup is very sensitive data + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + if (!FileUtil.BackupFileExists(fileName)) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//fishing protection + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + string mimetype = fileName.EndsWith("zip") ? "application/zip" : "application/octet-stream"; + var utilityFilePath = FileUtil.GetFullPathForBackupFile(fileName); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(DownloadUser.Id, 0, SockType.NoType, SockEvent.UtilityFileDownload, fileName), ct); + return PhysicalFile(utilityFilePath, mimetype, fileName); + + } + + + /// + /// Delete Backup + /// + /// + /// NoContent + [HttpDelete("{name}")] + [Authorize] + public ActionResult DeleteBackupFile([FromRoute] string name) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + CustomerBiz biz = CustomerBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, SockType.Backup)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + FileUtil.EraseBackupFile(name); + //never errors only no content + + return NoContent(); + } + + + //DANGER: MUST ADD AUTHORIZE ATTRIBUTE TO ANY NEW ROUTES + //[Authorize] + + }//eoc +}//eons diff --git a/server/Controllers/CustomerController.cs b/server/Controllers/CustomerController.cs new file mode 100644 index 0000000..d5e930f --- /dev/null +++ b/server/Controllers/CustomerController.cs @@ -0,0 +1,233 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/customer")] + [Produces("application/json")] + [Authorize] + public class CustomerController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public CustomerController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Create Customer + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostCustomer([FromBody] Customer newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + CustomerBiz biz = CustomerBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + Customer o = await biz.CreateAsync(newObject); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(CustomerController.GetCustomer), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + + /// + /// Get Customer + /// + /// + /// Customer + [HttpGet("{id}")] + public async Task GetCustomer([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + CustomerBiz biz = CustomerBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(id); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + /// + /// Update Customer + /// + /// + /// + [HttpPut] + public async Task PutCustomer([FromBody] Customer updatedObject) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + CustomerBiz biz = CustomerBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var o = await biz.PutAsync(updatedObject); + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ; + } + + /// + /// Delete Customer + /// + /// + /// NoContent + [HttpDelete("{id}")] + public async Task DeleteCustomer([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + CustomerBiz biz = CustomerBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await biz.DeleteAsync(id)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + + /// + /// Get Alert notes for this customer + /// + /// + /// Alert notes or null + [HttpGet("alert/{id}")] + public async Task GetCustomerAlert([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + return Ok(ApiOkResponse.Response(await ct.Customer.AsNoTracking().Where(x => x.Id == id).Select(x => x.AlertNotes).FirstOrDefaultAsync())); + } + + /// + /// Get addresses of interest for Customer id provided + /// (postal, physical, headoffice postal if billheadoffice=true) + /// + /// + /// Multiple addresses + [HttpGet("address/{id}")] + public async Task GetCustomerBillToAddress([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var cust = await ct.Customer.AsNoTracking().Where(x => x.Id == id).FirstOrDefaultAsync(); + if (cust == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + HeadOffice head = null; + if (cust.BillHeadOffice == true && cust.HeadOfficeId != null) + head = await ct.HeadOffice.AsNoTracking().Where(x => x.Id == cust.HeadOfficeId).FirstOrDefaultAsync(); + + + return Ok(ApiOkResponse.Response(new + { + customerpost = new PostalAddressRecord(cust.Name, cust.PostAddress, cust.PostCity, cust.PostRegion, cust.PostCountry, cust.PostCode), + customerphys = new AddressRecord(cust.Name, cust.Address, cust.City, cust.Region, cust.Country, cust.AddressPostal, cust.Latitude, cust.Longitude), + headofficepost = (head != null ? new PostalAddressRecord(head.Name, head.PostAddress, head.PostCity, head.PostRegion, head.PostCountry, head.PostCode) : new PostalAddressRecord("", "", "", "", "", "")) + })); + } + + + /// + /// Get list for accounting integrations + /// + /// NameIdActive list + [HttpGet("accounting-list")] + public async Task GetAccountingList() + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + CustomerBiz biz = CustomerBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetNameIdActiveItemsAsync(); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + + // /// + // /// Get service (physical) address for this customer + // /// + // /// + // /// Service address + // [HttpGet("service-address/{id}")] + // public async Task GetCustomerServiceAddress([FromRoute] long id) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + // if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + // var cust = await ct.Customer.AsNoTracking().Where(x => x.Id == id).FirstOrDefaultAsync(); + // if (cust == null) + // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + // return Ok(ApiOkResponse.Response(new + // { + // customer = new AddressRecord(cust.Address, cust.City, cust.Region, cust.Country, cust.Latitude, cust.Longitude) + // })); + // } + + + + + //------------ + + + }//eoc + +}//eons \ No newline at end of file diff --git a/server/Controllers/CustomerNoteController.cs b/server/Controllers/CustomerNoteController.cs new file mode 100644 index 0000000..70d1769 --- /dev/null +++ b/server/Controllers/CustomerNoteController.cs @@ -0,0 +1,137 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/customer-note")] + [Produces("application/json")] + [Authorize] + public class CustomerNoteController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public CustomerNoteController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Create CustomerNote + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostCustomerNote([FromBody] CustomerNote newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + CustomerNoteBiz biz = CustomerNoteBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, SockType.Customer)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + CustomerNote o = await biz.CreateAsync(newObject); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(CustomerNoteController.GetCustomerNote), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + + + /// + /// Get CustomerNote + /// + /// + /// CustomerNote + [HttpGet("{id}")] + public async Task GetCustomerNote([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + CustomerNoteBiz biz = CustomerNoteBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(id); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + /// + /// Update CustomerNote + /// + /// + /// + [HttpPut] + public async Task PutCustomerNote([FromBody] CustomerNote updatedObject) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + CustomerNoteBiz biz = CustomerNoteBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.Customer)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var o = await biz.PutAsync(updatedObject); + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));; + } + + /// + /// Delete CustomerNote + /// + /// + /// NoContent + [HttpDelete("{id}")] + public async Task DeleteCustomerNote([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + CustomerNoteBiz biz = CustomerNoteBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, SockType.Customer)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await biz.DeleteAsync(id)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + + + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/CustomerNotifySubscriptionController.cs b/server/Controllers/CustomerNotifySubscriptionController.cs new file mode 100644 index 0000000..12d3aba --- /dev/null +++ b/server/Controllers/CustomerNotifySubscriptionController.cs @@ -0,0 +1,206 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System; + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/customer-notify-subscription")] + [Produces("application/json")] + [Authorize] + public class CustomerNotifySubscriptionController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public CustomerNotifySubscriptionController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Create CustomerNotifySubscription + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostCustomerNotifySubscription([FromBody] CustomerNotifySubscription newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + CustomerNotifySubscriptionBiz biz = CustomerNotifySubscriptionBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + CustomerNotifySubscription o = await biz.CreateAsync(newObject); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(CustomerNotifySubscriptionController.GetCustomerNotifySubscription), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + + /// + /// Get CustomerNotifySubscription + /// + /// + /// CustomerNotifySubscription + [HttpGet("{id}")] + public async Task GetCustomerNotifySubscription([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + CustomerNotifySubscriptionBiz biz = CustomerNotifySubscriptionBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(id); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + /// + /// Update CustomerNotifySubscription + /// + /// + /// + [HttpPut] + public async Task PutCustomerNotifySubscription([FromBody] CustomerNotifySubscription updatedObject) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + CustomerNotifySubscriptionBiz biz = CustomerNotifySubscriptionBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var o = await biz.PutAsync(updatedObject); + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); + } + + /// + /// Delete CustomerNotifySubscription + /// + /// + /// NoContent + [HttpDelete("{id}")] + public async Task DeleteCustomerNotifySubscription([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + CustomerNotifySubscriptionBiz biz = CustomerNotifySubscriptionBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await biz.DeleteAsync(id)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + /// + /// get a list of which customers will potentially receive a notification based on tags + /// + /// + /// From route path + /// + [HttpPost("who")] + public async Task GetWho([FromBody] List customerTags, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!Authorized.HasCreateRole(HttpContext.Items, SockType.CustomerNotifySubscription)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + string custTagsWhere = DataList.DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("acustomer.tags", customerTags, false); + + + List ret = new List(); + using (var cmd = ct.Database.GetDbConnection().CreateCommand()) + { + await ct.Database.OpenConnectionAsync(); + cmd.CommandText = $"select id, name, COALESCE(emailaddress,'') from acustomer where active=true {custTagsWhere} order by name"; + using (var dr = await cmd.ExecuteReaderAsync()) + { + while (dr.Read()) + { + ret.Add(new CustomerNotifySubscriptionWhoRecord(dr.GetInt64(0), dr.GetString(1), dr.GetString(2))); + } + } + } + + // WHERE ARRAY['red','green'::varchar(255)] <@ tags + //array1.All(i => array2.Contains(i)) array1 <@ array2 + //https://www.npgsql.org/efcore/mapping/array.html + // var ret = await ct.Customer.Where(z => z.Active == true && (customerTags.All(i => z.Tags.Contains(i)))).Select(c => new CustomerNotifySubscriptionWhoRecord(c.Id, c.Name, c.EmailAddress)).ToListAsync(); + return Ok(ApiOkResponse.Response(ret)); + } + + private record CustomerNotifySubscriptionWhoRecord(long Id, string Name, string EmailAddress); + + + /// + /// Get Subscription list + /// + /// User's notification subscription list + [HttpGet("list")] + public async Task GetList() + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.CustomerNotifySubscription)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + var subs = await ct.CustomerNotifySubscription.AsNoTracking().OrderBy(z => z.Id).ToListAsync(); + + List ret = new List(); + foreach (var s in subs) + { + ret.Add(new CustomerNotifySubscriptionRecord(s.Id, s.EventType, s.SockType, s.CustomerTags, s.Tags, "na-status", s.AgeValue, s.DecValue)); + } + + return Ok(ApiOkResponse.Response(ret)); + } + private record CustomerNotifySubscriptionRecord( + long id, NotifyEventType eventType, SockType SockType, + List customerTags, List tags, string status, TimeSpan ageValue, decimal decValue + ); + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/DashboardViewController.cs b/server/Controllers/DashboardViewController.cs new file mode 100644 index 0000000..215441a --- /dev/null +++ b/server/Controllers/DashboardViewController.cs @@ -0,0 +1,124 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + +namespace Sockeye.Api.Controllers +{ + + /// + /// + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/dashboard-view")] + [Produces("application/json")] + [Authorize] + public class DashboardViewController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public DashboardViewController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + /// + /// Get DashboardView object for current User + /// There is always one for each user + /// + /// Dashboard view + [HttpGet()] + public async Task GetDashboardView() + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //Instantiate the business object handler + DashboardViewBiz biz = DashboardViewBiz.GetBiz(ct, HttpContext); + + //user always has full access to their own dashboard view and can only access their own through api so no need to check + // if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var o = await biz.GetAsync(); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + return Ok(ApiOkResponse.Response(o)); + } + + + + + /// + /// Update logged in User's Dashboard view + /// + /// + /// + [HttpPut()] + public async Task PutDashboardView([FromBody] string theView) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //Instantiate the business object handler + DashboardViewBiz biz = DashboardViewBiz.GetBiz(ct, HttpContext); + + var o = await biz.GetAsync(); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + //user always has full access to their own dashboard view and can only access their own through api so no need to check + // if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + + try + { + if (!await biz.PutAsync(o, theView)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + catch (DbUpdateConcurrencyException) + { + if (!await biz.ExistsAsync()) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + else + return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); + } + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/DataListColumnViewController.cs b/server/Controllers/DataListColumnViewController.cs new file mode 100644 index 0000000..8ba925d --- /dev/null +++ b/server/Controllers/DataListColumnViewController.cs @@ -0,0 +1,136 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + +namespace Sockeye.Api.Controllers +{ + + /// + /// + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/data-list-column-view")] + [Produces("application/json")] + [Authorize] + public class DataListColumnViewController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public DataListColumnViewController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + /// + /// Get DataListColumnView for current user + /// + /// + /// DataListColumnView + [HttpGet("{listKey}")] + public async Task GetDataListColumnView([FromRoute] string listKey) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + DataListColumnViewBiz biz = DataListColumnViewBiz.GetBiz(ct, HttpContext); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(biz.UserId, listKey, true); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + + + /// + /// Replace DataListColumnView + /// + /// + /// From route path + /// + [HttpPost] + public async Task ReplaceDataListColumnView([FromBody] DataListColumnView newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + DataListColumnViewBiz biz = DataListColumnViewBiz.GetBiz(ct, HttpContext); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + DataListColumnView o = await biz.CreateAsync(newObject); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return Ok(ApiOkResponse.Response(o)); + } + + + /// + /// Reset DataListColumnView to factory defaults + /// + /// + /// Default DataListColumnView + [HttpDelete("{listKey}")] + public async Task ResetDataListColumnView([FromRoute] string listKey) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + DataListColumnViewBiz biz = DataListColumnViewBiz.GetBiz(ct, HttpContext); + var o = await biz.DeleteAsync(biz.UserId, listKey); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return Ok(ApiOkResponse.Response(o)); + } + + + + + /// + /// Update sort order for user's CoulumnView for DataList key specified + /// + /// e.g.{"listKey":"CustomerDataList","sortBy":["CustomerPhone1","CustomerEmail"],"sortDesc":[false,false]} + /// From route path + /// + [HttpPost("sort")] + public async Task SetSort([FromBody] DataListSortRequest sortRequest, ApiVersion apiVersion) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + DataListColumnViewBiz biz = DataListColumnViewBiz.GetBiz(ct, HttpContext); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + if(!await biz.SetSort(sortRequest)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return Ok(); + } + public record SortRequest(string ListKey, string[] sortBy, bool[] sortDesc); + //{"listKey":"CustomerDataList","sortBy":["CustomerPhone1","CustomerEmail"],"sortDesc":[false,false]} + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/DataListController.cs b/server/Controllers/DataListController.cs new file mode 100644 index 0000000..605655a --- /dev/null +++ b/server/Controllers/DataListController.cs @@ -0,0 +1,187 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using Sockeye.DataList; +using System.Threading.Tasks; +using System.Linq; +using EnumsNET; +using Microsoft.EntityFrameworkCore; + +namespace Sockeye.Api.Controllers +{ + + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/data-list")] + [Produces("application/json")] + [Authorize] + public class DataListController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + + /// + /// ctor + /// + /// + /// + /// + public DataListController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + /// + /// Get list of data for selection / viewing + /// + /// Authorization varies list by list, will return 403 - Not Authorized if user has insufficient role + /// + /// + /// List key, Paging, filtering and sorting options + /// Collection with paging data + // [HttpPost("List", Name = nameof(List))] + [HttpPost] + public async Task List([FromBody] DataListTableRequest tableRequest) + { + var UserId = UserIdFromContext.Id(HttpContext.Items); + if (!serverState.IsOpen && UserId != 1)//bypass for superuser to view list of Users to fix license issues + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (tableRequest.Limit == null || tableRequest.Limit < 1) + { + tableRequest.Limit = DataListTableProcessingOptions.DefaultLimit; + } + if (tableRequest.Offset == null) + { + tableRequest.Offset = 0; + } + + + var UserRoles = UserRolesFromContext.Roles(HttpContext.Items); + + var UType = UserTypeFromContext.Type(HttpContext.Items); + + try + { + + DataListColumnViewBiz viewbiz = DataListColumnViewBiz.GetBiz(ct, HttpContext); + var SavedView = await viewbiz.GetAsync(UserId, tableRequest.DataListKey, true); + + DataListSavedFilter SavedFilter = null; + if (tableRequest.FilterId != 0) + { + DataListSavedFilterBiz filterbiz = DataListSavedFilterBiz.GetBiz(ct, HttpContext); + SavedFilter = await filterbiz.GetAsync(tableRequest.FilterId); + } + var DataList = DataListFactory.GetAyaDataList(tableRequest.DataListKey, UserTranslationIdFromContext.Id(HttpContext.Items)); + if (DataList == null) + return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "DataListKey", $"DataList \"{tableRequest.DataListKey}\" specified does not exist")); + + + + //check rights + if (!UserRoles.HasAnyFlags(DataList.AllowedRoles)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + //IF user is a customer type check if they are allowed to view this datalist + //and build the data list internal 'client' criteria + if (UType == UserType.Customer || UType == UserType.HeadOffice) + if (!await HandleCustomerTypeUserDataListRequest(UserId, tableRequest)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + //hydrate the saved view and filter + DataListTableProcessingOptions dataListTableOptions = new DataListTableProcessingOptions(tableRequest, DataList, SavedView, SavedFilter, UserId, UserRoles); + DataListReturnData r = await DataListFetcher.GetResponseAsync(ct, dataListTableOptions, DataList, UserRoles, log, UserId); + return Ok(r); + } + catch (System.UnauthorizedAccessException) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + catch (System.ArgumentOutOfRangeException e) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, null, e.Message)); + + } + } + + private async Task HandleCustomerTypeUserDataListRequest(long currentUserId, DataListTableRequest tableRequest) + { + await Task.CompletedTask; + return false; + // //Is this list allowed for a customer user and also enabled in global settings + // switch (tableRequest.DataListKey) + // { + + // default: + // return false; + // } + + // //Build client criteria if user is of correct type + // var UserInfo = await ct.User.AsNoTracking().Where(x => x.Id == currentUserId).Select(x => new { x.UserType, x.CustomerId, x.HeadOfficeId }).SingleOrDefaultAsync(); + // switch (UserInfo.UserType) + // { + // case UserType.Customer: + // if (UserInfo.CustomerId == null || UserInfo.CustomerId == 0) return false; + // tableRequest.ClientCriteria = $"{UserInfo.CustomerId},{(int)SockType.Customer}"; + // break; + // case UserType.HeadOffice: + // if (UserInfo.HeadOfficeId == null || UserInfo.HeadOfficeId == 0) return false; + // tableRequest.ClientCriteria = $"{UserInfo.HeadOfficeId},{(int)SockType.HeadOffice}"; + // break; + // default://other user type + // return false; + // } + // return true; + } + + /// + /// List of all DataList keys available + /// + /// List of strings + [HttpGet("listkeys")] + public ActionResult GetDataListKeys() + { + //NOTE: not used by Sockeye Client, convenience method for developers api usage + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + return Ok(ApiOkResponse.Response(DataListFactory.GetListOfAllDataListKeyNames())); + } + + + /// + /// List of all fields for data list key specified + /// + /// List of DataListFieldDefinition + [HttpGet("listfields")] + public ActionResult GetDataListFields([FromQuery] string DataListKey) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + var DataList = DataListFactory.GetAyaDataList(DataListKey, UserTranslationIdFromContext.Id(HttpContext.Items)); + //was the name not found as a list? + if (DataList == null) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, null, $"DataList \"{DataListKey}\" specified does not exist")); + } + + var ExternalOnly = DataList.FieldDefinitions.Where(z => z.IsMeta == false); + return Ok(ApiOkResponse.Response(ExternalOnly)); + } + + }//eoc +}//ens \ No newline at end of file diff --git a/server/Controllers/DataListSavedFilterController.cs b/server/Controllers/DataListSavedFilterController.cs new file mode 100644 index 0000000..bbb4a6e --- /dev/null +++ b/server/Controllers/DataListSavedFilterController.cs @@ -0,0 +1,189 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + +namespace Sockeye.Api.Controllers +{ + + /// + /// + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/data-list-filter")] + [Produces("application/json")] + [Authorize] + public class DataListSavedFilterController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public DataListSavedFilterController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + /// + /// Get full DataListSavedFilter object + /// + /// + /// A single DataListSavedFilter + [HttpGet("{id}")] + public async Task GetDataListSavedFilter([FromRoute] long id) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //Instantiate the business object handler + DataListSavedFilterBiz biz = DataListSavedFilterBiz.GetBiz(ct, HttpContext); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var o = await biz.GetAsync(id); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + return Ok(ApiOkResponse.Response(o)); + } + + + + /// + /// Get DataListSavedFilter list + /// + /// List of public or owned data list views listKey provided + [HttpGet("list", Name = nameof(DataListSavedFilterList))] + public async Task DataListSavedFilterList([FromQuery] string ListKey) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //Instantiate the business object handler + DataListSavedFilterBiz biz = DataListSavedFilterBiz.GetBiz(ct, HttpContext); + + var l = await biz.GetViewListAsync(ListKey); + return Ok(ApiOkResponse.Response(l)); + + } + + + /// + /// Update DataListSavedFilter + /// + /// + /// + [HttpPut] + public async Task PutDataListSavedFilter([FromBody] DataListSavedFilter updatedObject) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //Instantiate the business object handler + DataListSavedFilterBiz biz = DataListSavedFilterBiz.GetBiz(ct, HttpContext); + + var o = await biz.PutAsync(updatedObject); + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); + + } + + + /// + /// Create DataListSavedFilter + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostDataListSavedFilter([FromBody] DataListSavedFilter inObj, ApiVersion apiVersion) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //Instantiate the business object handler + DataListSavedFilterBiz biz = DataListSavedFilterBiz.GetBiz(ct, HttpContext); + + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //Default filters can never be created from outside + //they are only ever created from inside so a post with a default=true is always invalid + if (inObj.DefaultFilter == true) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "default", "Default filters can only be created internally")); + } + + //Create and validate + DataListSavedFilter o = await biz.CreateAsync(inObj); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(DataListSavedFilterController.GetDataListSavedFilter), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + + } + + + /// + /// Delete DataListSavedFilter + /// + /// + /// Ok + [HttpDelete("{id}")] + public async Task DeleteDataListSavedFilter([FromRoute] long id) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //Instantiate the business object handler + DataListSavedFilterBiz biz = DataListSavedFilterBiz.GetBiz(ct, HttpContext); + + var o = await biz.GetAsync(id); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + if (!await biz.DeleteAsync(o)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + + return NoContent(); + } + + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/EnumListController.cs b/server/Controllers/EnumListController.cs new file mode 100644 index 0000000..4730881 --- /dev/null +++ b/server/Controllers/EnumListController.cs @@ -0,0 +1,585 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using Sockeye.Util; +using System.Threading.Tasks; + + +namespace Sockeye.Api.Controllers +{ + + /// + /// Enum list controller + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/enum-list")] + [Produces("application/json")] + [Authorize] + public class EnumListController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public EnumListController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + /// + /// Get name value Translated display value list of Sockeye enumerated types for list specified + /// + /// The key name of the enumerated type + /// List + [HttpGet("list/{enumkey}")] + public async Task GetList([FromRoute] string enumkey) + { + if (serverState.IsClosed && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + var ret = await GetEnumList(enumkey, UserTranslationIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + return Ok(ApiOkResponse.Response(ret)); + } + + + + + /// + /// Get all possible enumerated values list key names + /// + /// List of Sockeye enumerated type list key names that can be fetched from the GetList Route + [HttpGet("listkeys")] + public ActionResult GetTypesList() + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + List> ret = new List>(); + ret.Add(new KeyValuePair(StringUtil.TrimTypeName(typeof(UserType).ToString()), "Sockeye user account types")); + ret.Add(new KeyValuePair("insideusertype", "Sockeye user account types for staff / contractors")); + ret.Add(new KeyValuePair("outsideusertype", "Sockeye user account types for customer / headoffice users")); + ret.Add(new KeyValuePair(StringUtil.TrimTypeName(typeof(AuthorizationRoles).ToString()), "Sockeye user account role types")); + ret.Add(new KeyValuePair(StringUtil.TrimTypeName(typeof(SockType).ToString()), "All Sockeye object types untranslated")); + ret.Add(new KeyValuePair("alltranslated", "All Sockeye object types translated")); + ret.Add(new KeyValuePair("coreall", "All Core Sockeye business object types")); + ret.Add(new KeyValuePair("coreview", "All Core Sockeye business object types current user is allowed to view")); + ret.Add(new KeyValuePair("coreedit", "All Core Sockeye business object types current user is allowed to edit")); + ret.Add(new KeyValuePair("reportable", "All Sockeye business object types that are reportable")); + ret.Add(new KeyValuePair("importable", "All Sockeye business object types that are importable")); + ret.Add(new KeyValuePair(StringUtil.TrimTypeName(typeof(UiFieldDataType).ToString()), "Types of data used in Sockeye for display and formatting UI purposes")); + ret.Add(new KeyValuePair(StringUtil.TrimTypeName(typeof(NotifyEventType).ToString()), "Notification event types")); + ret.Add(new KeyValuePair(StringUtil.TrimTypeName(typeof(NotifyDeliveryMethod).ToString()), "Notification delivery methods")); + ret.Add(new KeyValuePair(StringUtil.TrimTypeName(typeof(NotifyMailSecurity).ToString()), "Notification SMTP mail server security method")); + ret.Add(new KeyValuePair(StringUtil.TrimTypeName(typeof(SockEvent).ToString()), "Event log object change types")); + ret.Add(new KeyValuePair(StringUtil.TrimTypeName(typeof(SockDaysOfWeek).ToString()), "Days of the week")); + return Ok(ApiOkResponse.Response(ret)); + } + + + + public static async Task> GetEnumList(string enumKey, long translationId, AuthorizationRoles userRoles) + { + + List TranslationKeysToFetch = new List(); + + List ReturnList = new List(); + + var keyNameInLowerCase = enumKey.ToLowerInvariant(); + + + if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(UiFieldDataType).ToString()).ToLowerInvariant()) + { + //Iterate the enum and get the values + Type t = typeof(UiFieldDataType); + Enum.GetName(t, UiFieldDataType.NoType); + foreach (var dt in Enum.GetValues(t)) + { + ReturnList.Add(new NameIdItem() { Name = Enum.GetName(t, dt), Id = (int)dt }); + } + + } + else if (keyNameInLowerCase == "coreall") + { + //core biz objects for UI facing purposes such as search form limit to object type etc + var values = Enum.GetValues(typeof(SockType)); + foreach (SockType t in values) + { + if (t.HasAttribute(typeof(CoreBizObjectAttribute))) + { + TranslationKeysToFetch.Add(t.ToString()); + } + } + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + foreach (SockType t in values) + { + if (t.HasAttribute(typeof(CoreBizObjectAttribute))) + { + var tName = t.ToString(); + string name = string.Empty; + if (LT.ContainsKey(tName)) + { + name = LT[tName]; + } + else + { + name = tName; + } + ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t }); + } + } + } + else if (keyNameInLowerCase == "reportable") + { + //reportable biz objects for report designer selection + var values = Enum.GetValues(typeof(SockType)); + foreach (SockType t in values) + { + if (t.HasAttribute(typeof(ReportableBizObjectAttribute))) + { + TranslationKeysToFetch.Add(t.ToString()); + } + } + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + foreach (SockType t in values) + { + if (t.HasAttribute(typeof(ReportableBizObjectAttribute))) + { + var tName = t.ToString(); + string name = string.Empty; + if (LT.ContainsKey(tName)) + { + name = LT[tName]; + } + else + { + name = tName; + } + ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t }); + } + } + } + else if (keyNameInLowerCase == "importable") + { + //importable biz objects for administration -> import selection + var values = Enum.GetValues(typeof(SockType)); + foreach (SockType t in values) + { + if (t.HasAttribute(typeof(ImportableBizObjectAttribute))) + { + + var nameToFetch = t.ToString(); + + TranslationKeysToFetch.Add(nameToFetch); + } + } + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + foreach (SockType t in values) + { + if (t.HasAttribute(typeof(ImportableBizObjectAttribute))) + { + var tName = t.ToString(); + + string name = string.Empty; + if (LT.ContainsKey(tName)) + { + name = LT[tName]; + } + else + { + name = tName; + } + ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t }); + } + } + } + else if (keyNameInLowerCase == "coreview")//all core objects user can read + { + + //core biz objects for UI facing purposes + var rawvalues = Enum.GetValues(typeof(SockType)); + + List allowedValues = new List(); + foreach (SockType t in rawvalues) + { + if (Authorized.HasReadFullRole(userRoles, t)) + allowedValues.Add(t); + } + + foreach (SockType t in allowedValues) + { + if (t.HasAttribute(typeof(CoreBizObjectAttribute))) + { + TranslationKeysToFetch.Add(t.ToString()); + } + } + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + foreach (SockType t in allowedValues) + { + if (t.HasAttribute(typeof(CoreBizObjectAttribute))) + { + var tName = t.ToString(); + string name = string.Empty; + if (LT.ContainsKey(tName)) + { + name = LT[tName]; + } + else + { + name = tName; + } + ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t }); + } + } + } + else if (keyNameInLowerCase == "coreedit")//all core objects current user can edit + { + //core biz objects for UI facing purposes + var rawvalues = Enum.GetValues(typeof(SockType)); + + List allowedValues = new List(); + foreach (SockType t in rawvalues) + { + if (Authorized.HasModifyRole(userRoles, t)) + allowedValues.Add(t); + } + + foreach (SockType t in allowedValues) + { + if (t.HasAttribute(typeof(CoreBizObjectAttribute))) + { + TranslationKeysToFetch.Add(t.ToString()); + } + } + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + foreach (SockType t in allowedValues) + { + if (t.HasAttribute(typeof(CoreBizObjectAttribute))) + { + var tName = t.ToString(); + string name = string.Empty; + if (LT.ContainsKey(tName)) + { + name = LT[tName]; + } + else + { + name = tName; + } + ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t }); + } + } + } + else if (keyNameInLowerCase == "alltranslated")//all socktype objects with translations + { + //all types for search results and history UI + var rawvalues = Enum.GetValues(typeof(SockType)); + foreach (SockType t in rawvalues) + { + TranslationKeysToFetch.Add(t.ToString()); + } + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + foreach (SockType t in rawvalues) + { + var tName = t.ToString(); + string name = string.Empty; + if (LT.ContainsKey(tName)) + { + name = LT[tName]; + } + else + { + name = tName; + } + ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t }); + + } + } + else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(SockType).ToString()).ToLowerInvariant()) + { + //All types, used by search form and possibly others + + var values = Enum.GetValues(typeof(SockType)); + + foreach (SockType t in values) + TranslationKeysToFetch.Add(t.ToString()); + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + foreach (SockType t in values) + { + string tName = t.ToString(); + string name = string.Empty; + if (LT.ContainsKey(tName)) + { + name = LT[tName]; + } + else + { + name = tName; + } + ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t }); + } + } + else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(UserType).ToString()).ToLowerInvariant()) + { + + TranslationKeysToFetch.Add("UserTypeService"); + TranslationKeysToFetch.Add("UserTypeNotService"); + TranslationKeysToFetch.Add("UserTypeCustomer"); + TranslationKeysToFetch.Add("UserTypeHeadOffice"); + TranslationKeysToFetch.Add("UserTypeServiceContractor"); + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + ReturnList.Add(new NameIdItem() { Name = LT["UserTypeService"], Id = (long)UserType.Service }); + ReturnList.Add(new NameIdItem() { Name = LT["UserTypeNotService"], Id = (long)UserType.NotService }); + ReturnList.Add(new NameIdItem() { Name = LT["UserTypeCustomer"], Id = (long)UserType.Customer }); + ReturnList.Add(new NameIdItem() { Name = LT["UserTypeHeadOffice"], Id = (long)UserType.HeadOffice }); + ReturnList.Add(new NameIdItem() { Name = LT["UserTypeServiceContractor"], Id = (long)UserType.ServiceContractor }); + } + else if (keyNameInLowerCase == "insideusertype") + { + + TranslationKeysToFetch.Add("UserTypeService"); + TranslationKeysToFetch.Add("UserTypeNotService"); + TranslationKeysToFetch.Add("UserTypeServiceContractor"); + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + ReturnList.Add(new NameIdItem() { Name = LT["UserTypeService"], Id = (long)UserType.Service }); + ReturnList.Add(new NameIdItem() { Name = LT["UserTypeNotService"], Id = (long)UserType.NotService }); + ReturnList.Add(new NameIdItem() { Name = LT["UserTypeServiceContractor"], Id = (long)UserType.ServiceContractor }); + } + else if (keyNameInLowerCase == "outsideusertype") + { + + + TranslationKeysToFetch.Add("UserTypeCustomer"); + TranslationKeysToFetch.Add("UserTypeHeadOffice"); + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + ReturnList.Add(new NameIdItem() { Name = LT["UserTypeCustomer"], Id = (long)UserType.Customer }); + ReturnList.Add(new NameIdItem() { Name = LT["UserTypeHeadOffice"], Id = (long)UserType.HeadOffice }); + } + else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(SockEvent).ToString()).ToLowerInvariant()) + { + TranslationKeysToFetch.Add("EventDeleted"); + TranslationKeysToFetch.Add("EventCreated"); + TranslationKeysToFetch.Add("EventRetrieved"); + TranslationKeysToFetch.Add("EventModified"); + TranslationKeysToFetch.Add("EventAttachmentCreate"); + TranslationKeysToFetch.Add("EventAttachmentDelete"); + TranslationKeysToFetch.Add("EventAttachmentDownload"); + TranslationKeysToFetch.Add("EventLicenseFetch"); + TranslationKeysToFetch.Add("EventLicenseTrialRequest"); + TranslationKeysToFetch.Add("EventServerStateChange"); + TranslationKeysToFetch.Add("EventSeedDatabase"); + TranslationKeysToFetch.Add("EventAttachmentModified"); + TranslationKeysToFetch.Add("AdminEraseDatabase"); + TranslationKeysToFetch.Add("EventResetSerial"); + TranslationKeysToFetch.Add("EventUtilityFileDownload"); + TranslationKeysToFetch.Add("NotifyEventDirectSMTPMessage"); + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + ReturnList.Add(new NameIdItem() { Name = LT["EventDeleted"], Id = (long)SockEvent.Deleted }); + ReturnList.Add(new NameIdItem() { Name = LT["EventCreated"], Id = (long)SockEvent.Created }); + ReturnList.Add(new NameIdItem() { Name = LT["EventRetrieved"], Id = (long)SockEvent.Retrieved }); + ReturnList.Add(new NameIdItem() { Name = LT["EventModified"], Id = (long)SockEvent.Modified }); + ReturnList.Add(new NameIdItem() { Name = LT["EventAttachmentCreate"], Id = (long)SockEvent.AttachmentCreate }); + ReturnList.Add(new NameIdItem() { Name = LT["EventAttachmentDelete"], Id = (long)SockEvent.AttachmentDelete }); + ReturnList.Add(new NameIdItem() { Name = LT["EventAttachmentDownload"], Id = (long)SockEvent.AttachmentDownload }); + ReturnList.Add(new NameIdItem() { Name = LT["EventLicenseFetch"], Id = (long)SockEvent.LicenseFetch }); + ReturnList.Add(new NameIdItem() { Name = LT["EventLicenseTrialRequest"], Id = (long)SockEvent.LicenseTrialRequest }); + ReturnList.Add(new NameIdItem() { Name = LT["EventServerStateChange"], Id = (long)SockEvent.ServerStateChange }); + ReturnList.Add(new NameIdItem() { Name = LT["EventSeedDatabase"], Id = (long)SockEvent.SeedDatabase }); + ReturnList.Add(new NameIdItem() { Name = LT["EventAttachmentModified"], Id = (long)SockEvent.AttachmentModified }); + ReturnList.Add(new NameIdItem() { Name = LT["AdminEraseDatabase"], Id = (long)SockEvent.EraseAllData }); + ReturnList.Add(new NameIdItem() { Name = LT["EventResetSerial"], Id = (long)SockEvent.ResetSerial }); + ReturnList.Add(new NameIdItem() { Name = LT["EventUtilityFileDownload"], Id = (long)SockEvent.UtilityFileDownload }); + ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventDirectSMTPMessage"], Id = (long)SockEvent.DirectSMTP }); + } + else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(AuthorizationRoles).ToString()).ToLowerInvariant()) + { + + // TranslationKeysToFetch.Add("AuthorizationRoleNoRole"); + TranslationKeysToFetch.Add("AuthorizationRoleBizAdminRestricted"); + TranslationKeysToFetch.Add("AuthorizationRoleBizAdmin"); + TranslationKeysToFetch.Add("AuthorizationRoleServiceRestricted"); + TranslationKeysToFetch.Add("AuthorizationRoleService"); + TranslationKeysToFetch.Add("AuthorizationRoleInventoryRestricted"); + TranslationKeysToFetch.Add("AuthorizationRoleInventory"); + TranslationKeysToFetch.Add("AuthorizationRoleAccounting"); + TranslationKeysToFetch.Add("AuthorizationRoleTechRestricted"); + TranslationKeysToFetch.Add("AuthorizationRoleTech"); + TranslationKeysToFetch.Add("AuthorizationRoleSubContractorRestricted"); + TranslationKeysToFetch.Add("AuthorizationRoleSubContractor"); + TranslationKeysToFetch.Add("AuthorizationRoleCustomerRestricted"); + TranslationKeysToFetch.Add("AuthorizationRoleCustomer"); + TranslationKeysToFetch.Add("AuthorizationRoleOpsAdminRestricted"); + TranslationKeysToFetch.Add("AuthorizationRoleOpsAdmin"); + TranslationKeysToFetch.Add("AuthorizationRoleSalesRestricted"); + TranslationKeysToFetch.Add("AuthorizationRoleSales"); + // TranslationKeysToFetch.Add("AuthorizationRoleAll"); + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + // ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleNoRole"], Id = (long)AuthorizationRoles.NoRole }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleBizAdminRestricted"], Id = (long)AuthorizationRoles.BizAdminRestricted }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleBizAdmin"], Id = (long)AuthorizationRoles.BizAdmin }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleServiceRestricted"], Id = (long)AuthorizationRoles.ServiceRestricted }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleService"], Id = (long)AuthorizationRoles.Service }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleInventoryRestricted"], Id = (long)AuthorizationRoles.InventoryRestricted }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleInventory"], Id = (long)AuthorizationRoles.Inventory }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleAccounting"], Id = (long)AuthorizationRoles.Accounting }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleTechRestricted"], Id = (long)AuthorizationRoles.TechRestricted }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleTech"], Id = (long)AuthorizationRoles.Tech }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleSubContractorRestricted"], Id = (long)AuthorizationRoles.SubContractorRestricted }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleSubContractor"], Id = (long)AuthorizationRoles.SubContractor }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleCustomerRestricted"], Id = (long)AuthorizationRoles.CustomerRestricted }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleCustomer"], Id = (long)AuthorizationRoles.Customer }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleOpsAdminRestricted"], Id = (long)AuthorizationRoles.OpsAdminRestricted }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleOpsAdmin"], Id = (long)AuthorizationRoles.OpsAdmin }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleSalesRestricted"], Id = (long)AuthorizationRoles.SalesRestricted }); + ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleSales"], Id = (long)AuthorizationRoles.Sales }); + // ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleAll"], Id = (long)AuthorizationRoles.All }); + + } + else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(NotifyEventType).ToString()).ToLowerInvariant()) + { + TranslationKeysToFetch.Add("NotifyEventObjectDeleted"); + TranslationKeysToFetch.Add("NotifyEventObjectCreated"); + TranslationKeysToFetch.Add("NotifyEventObjectModified"); + TranslationKeysToFetch.Add("NotifyEventWorkorderStatusChange"); + TranslationKeysToFetch.Add("NotifyEventContractExpiring"); + TranslationKeysToFetch.Add("NotifyEventCSRAccepted"); + TranslationKeysToFetch.Add("NotifyEventCSRRejected"); + TranslationKeysToFetch.Add("NotifyEventWorkorderCompleted"); + TranslationKeysToFetch.Add("NotifyEventQuoteStatusChange"); + TranslationKeysToFetch.Add("NotifyEventQuoteStatusAge"); + TranslationKeysToFetch.Add("NotifyEventObjectAge"); + TranslationKeysToFetch.Add("NotifyEventServiceBankDepleted"); + TranslationKeysToFetch.Add("NotifyEventReminderImminent"); + TranslationKeysToFetch.Add("NotifyEventScheduledOnWorkorder"); + TranslationKeysToFetch.Add("NotifyEventScheduledOnWorkorderImminent"); + TranslationKeysToFetch.Add("NotifyEventWorkorderCompletedStatusOverdue"); + TranslationKeysToFetch.Add("NotifyEventOutsideServiceOverdue"); + TranslationKeysToFetch.Add("NotifyEventOutsideServiceReceived"); + TranslationKeysToFetch.Add("NotifyEventPartRequestReceived"); + TranslationKeysToFetch.Add("NotifyEventNotifyHealthCheck"); + TranslationKeysToFetch.Add("NotifyEventBackupStatus"); + TranslationKeysToFetch.Add("NotifyEventCustomerServiceImminent"); + TranslationKeysToFetch.Add("NotifyEventPMStopGeneratingDateReached"); + TranslationKeysToFetch.Add("NotifyEventWorkorderTotalExceedsThreshold"); + TranslationKeysToFetch.Add("NotifyEventWorkorderStatusAge"); + TranslationKeysToFetch.Add("NotifyEventUnitWarrantyExpiry"); + TranslationKeysToFetch.Add("NotifyEventUnitMeterReadingMultipleExceeded"); + TranslationKeysToFetch.Add("NotifyEventServerOperationsProblem"); + TranslationKeysToFetch.Add("NotifyEventGeneralNotification"); + //TranslationKeysToFetch.Add("NotifyEventCopyOfCustomerNotification"); + TranslationKeysToFetch.Add("NotifyEventWorkorderCreatedForCustomer"); + TranslationKeysToFetch.Add("NotifyEventPMGenerationFailed"); + TranslationKeysToFetch.Add("NotifyEventPMInsufficientInventory"); + TranslationKeysToFetch.Add("NotifyEventReviewImminent"); + TranslationKeysToFetch.Add("NotifyEventDirectSMTPMessage"); + + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventObjectDeleted"], Id = (long)NotifyEventType.ObjectDeleted }); + ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventObjectCreated"], Id = (long)NotifyEventType.ObjectCreated }); + ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventObjectModified"], Id = (long)NotifyEventType.ObjectModified }); + ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventObjectAge"], Id = (long)NotifyEventType.ObjectAge }); + ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventReminderImminent"], Id = (long)NotifyEventType.ReminderImminent }); + ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventReviewImminent"], Id = (long)NotifyEventType.ReviewImminent }); + ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventNotifyHealthCheck"], Id = (long)NotifyEventType.NotifyHealthCheck }); + ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventBackupStatus"], Id = (long)NotifyEventType.BackupStatus }); + ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventServerOperationsProblem"], Id = (long)NotifyEventType.ServerOperationsProblem }); + ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventGeneralNotification"], Id = (long)NotifyEventType.GeneralNotification }); + + ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventDirectSMTPMessage"], Id = (long)NotifyEventType.DirectSMTPMessage }); + + } + else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(NotifyDeliveryMethod).ToString()).ToLowerInvariant()) + { + TranslationKeysToFetch.Add("NotifyDeliveryMethodApp"); + TranslationKeysToFetch.Add("NotifyDeliveryMethodSMTP"); + + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + ReturnList.Add(new NameIdItem() { Name = LT["NotifyDeliveryMethodApp"], Id = (long)NotifyDeliveryMethod.App }); + ReturnList.Add(new NameIdItem() { Name = LT["NotifyDeliveryMethodSMTP"], Id = (long)NotifyDeliveryMethod.SMTP }); + } + else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(NotifyMailSecurity).ToString()).ToLowerInvariant()) + { + TranslationKeysToFetch.Add("NotifyMailSecurityNone"); + TranslationKeysToFetch.Add("NotifyMailSecuritySSLTLS"); + TranslationKeysToFetch.Add("NotifyMailSecurityStartTls"); + + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + ReturnList.Add(new NameIdItem() { Name = LT["NotifyMailSecurityNone"], Id = (long)NotifyMailSecurity.None }); + ReturnList.Add(new NameIdItem() { Name = LT["NotifyMailSecuritySSLTLS"], Id = (long)NotifyMailSecurity.SSLTLS }); + ReturnList.Add(new NameIdItem() { Name = LT["NotifyMailSecurityStartTls"], Id = (long)NotifyMailSecurity.StartTls }); + } + + + + + + + + else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(SockDaysOfWeek).ToString()).ToLowerInvariant()) + { + + TranslationKeysToFetch.Add("DayMonday"); + TranslationKeysToFetch.Add("DayTuesday"); + TranslationKeysToFetch.Add("DayWednesday"); + TranslationKeysToFetch.Add("DayThursday"); + TranslationKeysToFetch.Add("DayFriday"); + TranslationKeysToFetch.Add("DaySaturday"); + TranslationKeysToFetch.Add("DaySunday"); + + + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + + ReturnList.Add(new NameIdItem() { Name = LT["DayMonday"], Id = (long)SockDaysOfWeek.Monday }); + ReturnList.Add(new NameIdItem() { Name = LT["DayTuesday"], Id = (long)SockDaysOfWeek.Tuesday }); + ReturnList.Add(new NameIdItem() { Name = LT["DayWednesday"], Id = (long)SockDaysOfWeek.Wednesday }); + ReturnList.Add(new NameIdItem() { Name = LT["DayThursday"], Id = (long)SockDaysOfWeek.Thursday }); + ReturnList.Add(new NameIdItem() { Name = LT["DayFriday"], Id = (long)SockDaysOfWeek.Friday }); + ReturnList.Add(new NameIdItem() { Name = LT["DaySaturday"], Id = (long)SockDaysOfWeek.Saturday }); + ReturnList.Add(new NameIdItem() { Name = LT["DaySunday"], Id = (long)SockDaysOfWeek.Sunday }); + + } + //################################################################################################################# + //################### NEW HERE DO NOT FORGET TO ADD TO LISTS AVAILABLE ABOVE AS WELL ############################## + //################################################################################################################# + else + { + ReturnList.Add(new NameIdItem() { Name = $"Unknown enum type list key value {enumKey}", Id = 0 }); + } + + return ReturnList; + + } + + + }//eoc +}//ens \ No newline at end of file diff --git a/server/Controllers/EventLogController.cs b/server/Controllers/EventLogController.cs new file mode 100644 index 0000000..2597d09 --- /dev/null +++ b/server/Controllers/EventLogController.cs @@ -0,0 +1,196 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + +namespace Sockeye.Api.Controllers +{ + + /// + /// Log files controller + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/event-log")] + [Authorize] + public class EventLogController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public EventLogController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + + /// + /// Get event log for object and range specified + /// + /// Required Role: Read full object properties rights to object type specified + /// + /// + /// Event log entry list for object + [HttpGet("objectlog")] + public async Task GetObjectLog([FromQuery] EventLogOptions opt) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, opt.SockType)) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var ret = await EventLogProcessor.GetLogForObjectAsync(opt, UserTranslationIdFromContext.Id(HttpContext.Items), ct); + return Ok(ApiOkResponse.Response(ret)); + } + + + + /// + /// Get event log entries for a specified user and range + /// + /// Required Role: Read rights to User object or User's own data + /// + /// + /// Event log for user + [HttpGet("userlog")] + public async Task GetUserLog([FromQuery] UserEventLogOptions opt) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + long UserId = UserIdFromContext.Id(HttpContext.Items); + + //If not authorized to read a user and also not the current user asking for their own log then NO LOG FOR YOU! + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.User) && opt.UserId != UserId) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + var ret = await EventLogProcessor.GetLogForUserAsync(opt, UserTranslationIdFromContext.Id(HttpContext.Items), ct); + return Ok(ApiOkResponse.Response(ret)); + } + + + /// + /// V7 export replace log entry + /// (internal use only) + /// + /// + /// From route path + /// + [ApiExplorerSettings(IgnoreApi = true)] + [HttpPost("v7")] + public async Task PostV7Modify([FromBody] V7Event inObj, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + await EventLogProcessor.V7_Modify_LogAsync(inObj, ct); + return NoContent(); + } + public sealed class V7Event + { + public SockType SockType { get; set; } + public long AyId { get; set; } + public long Creator { get; set; } + public long Modifier { get; set; } + public DateTime Created { get; set; } + public DateTime Modified { get; set; } + + } + + //------------ + + + public sealed class EventLogOptions + { + [FromQuery] + public SockType SockType { get; set; } + [FromQuery] + public long AyId { get; set; } + [FromQuery] + public int? Offset { get; set; } + [FromQuery] + public int? Limit { get; set; } + } + + public sealed class UserEventLogOptions + { + + [FromQuery] + public long UserId { get; set; } + [FromQuery] + public int? Offset { get; set; } + [FromQuery] + public int? Limit { get; set; } + } + + public sealed class ObjectEventLog + { + public string Name { get; set; } + public ObjectEventLogItem[] Events { get; set; } + } + + public sealed class ObjectEventLogItem + { + //DateTime, UserId, Event, Textra + public DateTime Date { get; set; } + public long UserId { get; set; } + public string Name { get; set; } + public SockEvent Event { get; set; } + public string Textra { get; set; } + } + + public sealed class UserEventLog + { + public string Name { get; set; } + public UserEventLogItem[] Events { get; set; } + } + + public sealed class UserEventLogItem + { + //DateTime, SockType, ObjectId, Event, Textra + public DateTime Date { get; set; } + public SockType SockType { get; set; } + public long ObjectId { get; set; } + public string Name { get; set; } + public SockEvent Event { get; set; } + public string Textra { get; set; } + } + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/ExportController.cs b/server/Controllers/ExportController.cs new file mode 100644 index 0000000..179163a --- /dev/null +++ b/server/Controllers/ExportController.cs @@ -0,0 +1,160 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using Sockeye.Util; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.IO; +using System; + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/export")] + [Produces("application/json")] + [Authorize] + public class ExportController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public ExportController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + /// + /// Export to file + /// + /// + /// downloadable export file name + [HttpPost("render")] + public async Task RenderExport([FromBody] DataListSelectedRequest selectedRequest) + { + + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (selectedRequest == null) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "DataListSelectedRequest is required")); + + if (!Authorized.HasReadFullRole(HttpContext.Items, selectedRequest.SockType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + + var UserId = UserIdFromContext.Id(HttpContext.Items); + var UserRoles = UserRolesFromContext.Roles(HttpContext.Items); + var UserTranslationId = UserTranslationIdFromContext.Id(HttpContext.Items); + + //Rehydrate id list if necessary + if (selectedRequest.SelectedRowIds.Length == 0) + selectedRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList( + selectedRequest, + ct, + UserRoles, + log, + UserId, + UserTranslationId); + + + log.LogDebug($"Instantiating biz object handler for {selectedRequest.SockType}"); + var biz = BizObjectFactory.GetBizObject(selectedRequest.SockType, ct, UserId, UserRoles, UserTranslationId); + log.LogDebug($"Fetching data for {selectedRequest.SelectedRowIds.Length} {selectedRequest.SockType} items"); + string baseFileName = FileUtil.StringToSafeFileName($"{selectedRequest.SockType.ToString().ToLowerInvariant()}-{FileUtil.GetSafeDateFileName()}"); + string outputSourceFileName = baseFileName + ".json"; + string outputSourceFullPath = System.IO.Path.Combine(FileUtil.TemporaryFilesFolder, outputSourceFileName); + + log.LogDebug($"Calling render export data to file {outputSourceFullPath}"); + try + { + using (StreamWriter file = System.IO.File.CreateText(outputSourceFullPath)) + using (JsonTextWriter writer = new JsonTextWriter(file)) + { + var dat = await ((IExportAbleObject)biz).GetExportData(selectedRequest, Guid.Empty);//todo: jobify + dat.WriteTo(writer); + } + + log.LogDebug($"Completed, returning results"); + return Ok(ApiOkResponse.Response(outputSourceFileName)); + } + catch (ReportRenderTimeOutException) + { + log.LogInformation($"RenderExport timeout data list key: {selectedRequest.DataListKey}, record count:{selectedRequest.SelectedRowIds.LongLength}, user:{UserNameFromContext.Name(HttpContext.Items)} "); + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "timeout - select fewer records")); + } + } + + public static IList ToDynamicList(JArray data) + { + var dynamicData = new List(); + var expConverter = new Newtonsoft.Json.Converters.ExpandoObjectConverter(); + + foreach (var dataItem in data) + { + dynamic obj = JsonConvert.DeserializeObject(dataItem.ToString(), expConverter); + dynamicData.Add(obj); + } + return dynamicData; + } + + /// + /// Download a rendered Export + /// + /// + /// download token + /// + [HttpGet("download/{fileName}")] + [AllowAnonymous] + public async Task DownloadAsync([FromRoute] string fileName, [FromQuery] string t) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct) == null) + { + await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + if (!FileUtil.TemporaryFileExists(fileName)) + { + await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//fishing protection + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + var FilePath = FileUtil.GetFullPathForTemporaryFile(fileName); + + //including the file name triggers save automatically "attachment" rather than viewing it "inline" + return PhysicalFile(FilePath, "application/json", fileName); + } + + + + //----------------------------------------- + + + + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/FormCustomController.cs b/server/Controllers/FormCustomController.cs new file mode 100644 index 0000000..b771ae3 --- /dev/null +++ b/server/Controllers/FormCustomController.cs @@ -0,0 +1,221 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + +namespace Sockeye.Api.Controllers +{ + + /// + /// + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/form-custom")] + [Produces("application/json")] + [Authorize] + public class FormCustomController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public FormCustomController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + + /// + /// Get form customizations for Client form display + /// + /// The official form key used by Sockeye + /// A prior concurrency token used to check if there are any changes without using up bandwidth sending unnecessary data + /// A single FormCustom or nothing and a header 304 not modified + [HttpGet("{formkey}")] + public async Task GetFormCustom([FromRoute] string formkey, [FromQuery] uint? concurrency) + { + if (serverState.IsClosed && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //Instantiate the business object handler + FormCustomBiz biz = FormCustomBiz.GetBiz(ct, HttpContext); + + //Just have to be authenticated for this one + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + + var o = await biz.GetAsync(formkey); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + //If concurrency token specified then check if ours is newer + if (concurrency != null) + { + if (o.Concurrency == concurrency) + { + //returns a code 304 (NOT MODIFIED) + return StatusCode(304); + } + } + + return Ok(ApiOkResponse.Response(o)); + } + + + + + + + + + /// + /// Get available types allowed for Custom fields + /// Used to build UI for customizing a form + /// These values are a subset of the AyaDataTypes values + /// which can be fetched from the EnumList route + /// + /// A list of valid values for custom field types + [HttpGet("availablecustomtypes")] + public ActionResult GetAvailableCustomTypes() + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.FormCustom)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + return Ok(ApiOkResponse.Response(CustomFieldType.ValidCustomFieldTypes)); + } + + + /// + /// Get a list of all customizable form keys + /// Used to build UI for customizing a form + /// + /// A list of string formKey values valid for customization + [HttpGet("availablecustomizableformkeys")] + public ActionResult GetAvailableCustomizableFormKeys() + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.FormCustom)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + return Ok(ApiOkResponse.Response(FormFieldOptionalCustomizableReference.FormFieldKeys)); + } + + + + + /// + /// Update FormCustom + /// + /// + /// + /// + [HttpPut("{formkey}")] + public async Task PutFormCustom([FromRoute] string formkey, [FromBody] FormCustom updatedObject) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //Instantiate the business object handler + FormCustomBiz biz = FormCustomBiz.GetBiz(ct, HttpContext); + + // var o = await biz.GetAsync(formkey); + // if (o == null) + // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + var o = await biz.PutAsync(updatedObject); + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ; + + // try + // { + // if (!await biz.PutAsync(o, inObj)) + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // } + // catch (DbUpdateConcurrencyException) + // { + // if (!await biz.ExistsAsync(formkey)) + // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + // else + // return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT)); + // } + // return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); + } + + + /// + /// Get FormKey from id of FormCustom + /// + /// A formKey string if id found + [HttpGet("form-key/{id}")] + public async Task GetFormKeyFromId(long id) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.FormCustom)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var fc = await ct.FormCustom.FirstOrDefaultAsync(z => z.Id == id); + if (fc == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + return Ok(ApiOkResponse.Response(fc.FormKey)); + } + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/FormFieldsDefinitionsController.cs b/server/Controllers/FormFieldsDefinitionsController.cs new file mode 100644 index 0000000..45ea545 --- /dev/null +++ b/server/Controllers/FormFieldsDefinitionsController.cs @@ -0,0 +1,67 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/form-field-reference")] + [Produces("application/json")] + [Authorize] + public class FormFieldsDefinitionsController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public FormFieldsDefinitionsController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + /// + /// Get field reference list for Form specified + /// Used at UI for customizing forms + /// + /// + /// List of form fields and their properties + [HttpGet("{key}")] + public ActionResult GetFormFields([FromRoute] string key) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (FormFieldOptionalCustomizableReference.IsValidFormFieldKey(key)) + { + return Ok(ApiOkResponse.Response(FormFieldOptionalCustomizableReference.FormFieldReferenceList(key))); + } + else + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + } + + + + + }//eoc +}//ens \ No newline at end of file diff --git a/server/Controllers/FormUserOptionsController.cs b/server/Controllers/FormUserOptionsController.cs new file mode 100644 index 0000000..cc2d6cf --- /dev/null +++ b/server/Controllers/FormUserOptionsController.cs @@ -0,0 +1,135 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/form-user-options")] + [Produces("application/json")] + [Authorize] + public class FormUserOptionsController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public FormUserOptionsController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Create or Replace FormUserOptions + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostFormUserOptions([FromBody] FormUserOptions newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + FormUserOptionsBiz biz = FormUserOptionsBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + FormUserOptions o = await biz.UpsertAsync(newObject); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(FormUserOptionsController.GetFormUserOptions), new { formKey = o.FormKey, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + + /// + /// Get FormUserOptions + /// + /// + /// FormUserOptions + [HttpGet("{formKey}")] + public async Task GetFormUserOptions([FromRoute] string formKey) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + FormUserOptionsBiz biz = FormUserOptionsBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(formKey); + //note: this route unique in that it's expected that a formUserOptions object may not exist so just return null as client end expects + return Ok(ApiOkResponse.Response(o)); + } + + // /// + // /// Update FormUserOptions + // /// + // /// + // /// + // [HttpPut] + // public async Task PutFormUserOptions([FromBody] FormUserOptions updatedObject) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + // FormUserOptionsBiz biz = FormUserOptionsBiz.GetBiz(ct, HttpContext); + // if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + // var o = await biz.PutAsync(updatedObject); + // if (o == null) + // { + // if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + // return StatusCode(409, new ApiErrorResponse(biz.Errors)); + // else + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // } + // return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ; + // } + + /// + /// Delete FormUserOptions + /// + /// + /// NoContent + [HttpDelete("{formKey}")] + public async Task DeleteFormUserOptions([FromRoute] string formKey) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + FormUserOptionsBiz biz = FormUserOptionsBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await biz.DeleteAsync(formKey)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/GlobalBizSettingsController.cs b/server/Controllers/GlobalBizSettingsController.cs new file mode 100644 index 0000000..78ebe6e --- /dev/null +++ b/server/Controllers/GlobalBizSettingsController.cs @@ -0,0 +1,128 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using System.Threading.Tasks; +using System; + +namespace Sockeye.Api.Controllers +{ + + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/global-biz-setting")] + [Produces("application/json")] + [Authorize] + public class GlobalBizSettingsController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + + /// + /// ctor + /// + /// + /// + /// + public GlobalBizSettingsController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Get GlobalBizSettings + /// + /// Global settings object + [HttpGet] + public async Task GetGlobalBizSettings() + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //Instantiate the business object handler + GlobalBizSettingsBiz biz = GlobalBizSettingsBiz.GetBiz(ct, HttpContext); + + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var o = await biz.GetAsync(); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + return Ok(ApiOkResponse.Response(o)); + } + + + /// + /// PUT Global biz settings + /// + /// + /// New concurrency token + [HttpPut] + public async Task ReplaceGlobalBizSettings([FromBody] GlobalBizSettings updatedObject) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //Instantiate the business object handler + GlobalBizSettingsBiz biz = GlobalBizSettingsBiz.GetBiz(ct, HttpContext); + + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + var o = await biz.PutAsync(updatedObject); + if (o == null) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); + } + + /// + /// Get Client app relevant GlobalBizSettings + /// + /// Global settings object + [HttpGet("client")] + public ActionResult GetClientGlobalBizSettings() + { + //## NOTE: these are settings that the Client needs to see for standard operations + //NOT the settings that the user changes in the global settings form which is fetched above + //so do not include anything here unless the client needs it + if (serverState.IsClosed) + { + //Exception for SuperUser account to handle licensing issues + if (UserIdFromContext.Id(HttpContext.Items) != 1) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + } + + + + var ret = new + { + //Actual global settings: + FilterCaseSensitive = Sockeye.Util.ServerGlobalBizSettings.Cache.FilterCaseSensitive, + Company = "GZTW" + }; + + return Ok(ApiOkResponse.Response(ret)); + } + + + + + }//eoc +}//ens \ No newline at end of file diff --git a/server/Controllers/GlobalOpsBackupSettingsController.cs b/server/Controllers/GlobalOpsBackupSettingsController.cs new file mode 100644 index 0000000..5576f83 --- /dev/null +++ b/server/Controllers/GlobalOpsBackupSettingsController.cs @@ -0,0 +1,114 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using System.Threading.Tasks; + +namespace Sockeye.Api.Controllers +{ + + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/global-ops-backup-setting")] + [Produces("application/json")] + [Authorize] + public class GlobalOpsBackupSettingsController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + + /// + /// ctor + /// + /// + /// + /// + public GlobalOpsBackupSettingsController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Get GlobalOpsBackupSettings + /// + /// Global ops backup settings object + [HttpGet] + public async Task GetGlobalOpsBackupSettings() + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //Instantiate the business object handler + GlobalOpsBackupSettingsBiz biz = GlobalOpsBackupSettingsBiz.GetBiz(ct, HttpContext); + + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var o = await biz.GetAsync(); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + return Ok(ApiOkResponse.Response(o)); + } + + + /// + /// PUT Global ops backup settings + /// + /// + /// New concurrency token + [HttpPut] + public async Task ReplaceGlobalOpsBackupSettings([FromBody] GlobalOpsBackupSettings updatedObject) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + GlobalOpsBackupSettingsBiz biz = GlobalOpsBackupSettingsBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var o = await biz.PutAsync(updatedObject); + if (o == null) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); + } + + // /// + // /// Get Client app relevant GlobalOpsBackupSettings + // /// + // /// Global ops backup settings object + // [HttpGet("client")] + // public async Task GetClientGlobalOpsBackupSettings() + // { + // //NOTE: currently this looks like a dupe of get above and it is + // //but it's kept here in case want to return a subset of the settings only to client users + // //where some internal server only stuff might not be desired to send to user + + // if (serverState.IsClosed) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + // GlobalOpsBackupSettingsBiz biz = GlobalOpsBackupSettingsBiz.GetBiz(ct, HttpContext); + // //this route is available to Ops role user only + // if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + // var o = await biz.GetAsync(); + // if (o == null) + // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + // return Ok(ApiOkResponse.Response(o)); + // } + + }//eoc +}//ens \ No newline at end of file diff --git a/server/Controllers/GlobalOpsNotificationSettingsController.cs b/server/Controllers/GlobalOpsNotificationSettingsController.cs new file mode 100644 index 0000000..6adbc1b --- /dev/null +++ b/server/Controllers/GlobalOpsNotificationSettingsController.cs @@ -0,0 +1,137 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using System.Threading.Tasks; + +namespace Sockeye.Api.Controllers +{ + + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/global-ops-notification-setting")] + [Produces("application/json")] + [Authorize] + public class GlobalOpsNotificationSettingsController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + + /// + /// ctor + /// + /// + /// + /// + public GlobalOpsNotificationSettingsController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Get GlobalOpsNotificationSettings + /// + /// Global ops Notification settings object + [HttpGet] + public async Task GetGlobalOpsNotificationSettings() + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //Instantiate the business object handler + GlobalOpsNotificationSettingsBiz biz = GlobalOpsNotificationSettingsBiz.GetBiz(ct, HttpContext); + + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var o = await biz.GetAsync(); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + return Ok(ApiOkResponse.Response(o)); + } + + + /// + /// PUT Global ops Notification settings + /// + /// + /// New concurrency token + [HttpPut] + public async Task ReplaceGlobalOpsNotificationSettings([FromBody] GlobalOpsNotificationSettings updatedObject) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + GlobalOpsNotificationSettingsBiz biz = GlobalOpsNotificationSettingsBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var o = await biz.PutAsync(updatedObject); + if (o == null) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); + } + + + /// + /// Test SMTP mail delivery + /// + /// + /// Status result + [HttpPost("test-smtp-settings/{toAddress}")] + [Authorize] + public async Task PostTestSmtpDelivery([FromRoute] string toAddress) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.OpsNotificationSettings))//technically maybe this could be wider open, but for now keeping as locked down + return StatusCode(403, new ApiNotAuthorizedResponse()); + var res = await CoreJobNotify.TestSMTPDelivery(toAddress); + if(res!="ok"){ + return StatusCode(500, new ApiErrorResponse(ApiErrorCode.API_SERVER_ERROR, null, res)); + } + return Ok(ApiOkResponse.Response(res)); + } + + + + // /// + // /// Get Client app relevant GlobalOpsNotificationSettings + // /// + // /// Global ops Notification settings object + // [HttpGet("client")] + // public async Task GetClientGlobalOpsNotificationSettings() + // { + // //NOTE: currently this looks like a dupe of get above and it is + // //but it's kept here in case want to return a subset of the settings only to client users + // //where some internal server only stuff might not be desired to send to user + + // if (serverState.IsClosed) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + // GlobalOpsNotificationSettingsBiz biz = GlobalOpsNotificationSettingsBiz.GetBiz(ct, HttpContext); + // //this route is available to Ops role user only + // if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + // var o = await biz.GetAsync(); + // if (o == null) + // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + // return Ok(ApiOkResponse.Response(o)); + // } + + }//eoc +}//ens \ No newline at end of file diff --git a/server/Controllers/HeadOfficeController.cs b/server/Controllers/HeadOfficeController.cs new file mode 100644 index 0000000..fa58035 --- /dev/null +++ b/server/Controllers/HeadOfficeController.cs @@ -0,0 +1,159 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/head-office")] + [Produces("application/json")] + [Authorize] + public class HeadOfficeController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public HeadOfficeController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Create HeadOffice + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostHeadOffice([FromBody] HeadOffice newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + HeadOfficeBiz biz = HeadOfficeBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + HeadOffice o = await biz.CreateAsync(newObject); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(HeadOfficeController.GetHeadOffice), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + // /// + // /// Duplicate HeadOffice + // /// (Wiki and Attachments are not duplicated) + // /// + // /// Source object id + // /// From route path + // /// HeadOffice + // [HttpPost("duplicate/{id}")] + // public async Task DuplicateHeadOffice([FromRoute] long id, ApiVersion apiVersion) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + // HeadOfficeBiz biz = HeadOfficeBiz.GetBiz(ct, HttpContext); + // if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + // HeadOffice o = await biz.DuplicateAsync(id); + // if (o == null) + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // else + // return CreatedAtAction(nameof(HeadOfficeController.GetHeadOffice), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + // } + + /// + /// Get HeadOffice + /// + /// + /// HeadOffice + [HttpGet("{id}")] + public async Task GetHeadOffice([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + HeadOfficeBiz biz = HeadOfficeBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(id); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + /// + /// Update HeadOffice + /// + /// + /// + [HttpPut] + public async Task PutHeadOffice([FromBody] HeadOffice updatedObject) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + HeadOfficeBiz biz = HeadOfficeBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var o = await biz.PutAsync(updatedObject); + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));; + } + + /// + /// Delete HeadOffice + /// + /// + /// NoContent + [HttpDelete("{id}")] + public async Task DeleteHeadOffice([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + HeadOfficeBiz biz = HeadOfficeBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await biz.DeleteAsync(id)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + + + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/HealthController.cs b/server/Controllers/HealthController.cs new file mode 100644 index 0000000..e2218f1 --- /dev/null +++ b/server/Controllers/HealthController.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Swashbuckle.AspNetCore.Annotations; + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/health")] + [Produces("text/plain")] + [Authorize] + public class HealthController : ControllerBase + { + private readonly HealthCheckService _healthCheckService; + public HealthController(HealthCheckService healthCheckService) + { + _healthCheckService = healthCheckService; + } + + /// + /// Get Health (verify server AND database connection) + /// Note: for server monitoring or automation / orchestration use such as Docker prefer the mirror of this route at the server root: [api_server_url]/health + /// as it avoids API versioning issues + /// i.e. Docker: HEALTHCHECK CMD curl --fail http://localhost:5000/health || exit + /// + /// Provides an indication about the health of the API + /// API is healthy + /// API is unhealthy or in degraded state + [HttpGet] + [AllowAnonymous] + [ProducesResponseType(typeof(string), 200)] + [SwaggerOperation(OperationId = "Health_Get")] + public async Task Get() + { + var report = await _healthCheckService.CheckHealthAsync(); + Response.Headers.Add("Cache-Control", "no-store, no-cache"); + return report.Status == HealthStatus.Healthy ? Ok(report.Status.ToString()) : StatusCode(503, report.Status.ToString()); + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/ImportController.cs b/server/Controllers/ImportController.cs new file mode 100644 index 0000000..9718dfd --- /dev/null +++ b/server/Controllers/ImportController.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using Newtonsoft.Json.Linq; + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/import")] + [Produces("application/json")] + [Authorize] + public class ImportController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public ImportController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + /// + /// Import / Update JSON data to indicated object type + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostImportData([FromBody] AyImportData importData, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasCreateRole(HttpContext.Items, importData.SockType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + List ImportResult = new List(); + ImportResult.Add("Import results\n"); + log.LogDebug($"Instantiating biz object handler for {importData.SockType}"); + var biz = BizObjectFactory.GetBizObject(importData.SockType, ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + + if (!(biz is IImportAbleObject)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Import not supported for {importData.SockType} objects")); + ImportResult.AddRange(await ((IImportAbleObject)biz).ImportData(importData)); + + return Ok(ApiOkResponse.Response(ImportResult)); + + } + + + + // /// + // /// Upload and import file + // /// Max 100MiB total + // /// + // /// Results + // [Authorize] + // [HttpPost("upload")] + // [DisableFormValueModelBinding] + // [RequestSizeLimit(Sockeye.Util.ServerBootConfig.MAX_IMPORT_FILE_UPLOAD_BYTES)] + // public async Task UploadAsync() + // { + // //Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming + + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + // //This route is ONLY available to users with full rights to Global object + // if (!Authorized.HasModifyRole(HttpContext.Items, SockType.Global)) + // { + // return StatusCode(403, new ApiNotAuthorizedResponse()); + // } + + // // SockTypeId attachToObject = null; + // ApiUploadProcessor.ApiUploadedFilesResult uploadFormData = null; + // List ImportResult = new List(); + // ImportResult.Add("Import results\n"); + // try + // { + // if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) + // return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Expected a multipart request, but got {Request.ContentType}")); + + // //Save uploads to disk under temporary file names until we decide how to handle them + // // uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext);xx + + + // string UploadAType = string.Empty; + + // string errorMessage = string.Empty; + // string Notes = string.Empty; + // List FileData = new List(); + + // //Save uploads to disk under temporary file names until we decide how to handle them + // uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext); + // if (!string.IsNullOrWhiteSpace(uploadFormData.Error)) + // { + // errorMessage = uploadFormData.Error; + // //delete temp files + // ApiUploadProcessor.DeleteTempUploadFile(uploadFormData); + // //file too large is most likely issue so in that case return this localized properly + // if (errorMessage.Contains("413")) + // { + // var TransId = UserTranslationIdFromContext.Id(HttpContext.Items); + // return BadRequest(new ApiErrorResponse( + // ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, + // null, + // String.Format(await TranslationBiz.GetTranslationStaticAsync("AyaFileFileTooLarge", TransId, ct), Sockeye.Util.FileUtil.GetBytesReadable(Sockeye.Util.ServerBootConfig.MAX_IMPORT_FILE_UPLOAD_BYTES)))); + // } + // else//not too big, something else + // return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, errorMessage)); + // } + + // if (!uploadFormData.FormFieldData.ContainsKey("FileData"))//only filedata is required + // return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Missing required FormFieldData value: FileData")); + + + // if (uploadFormData.FormFieldData.ContainsKey("SockType")) + // UploadAType = uploadFormData.FormFieldData["SockType"].ToString(); + // else + // return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Missing required FormFieldData value: SockType")); + + // //fileData in JSON stringify format which contains the actual last modified dates etc + // //"[{\"name\":\"Client.csv\",\"lastModified\":1582822079618},{\"name\":\"wmi4fu06nrs41.jpg\",\"lastModified\":1586900220990}]" + // FileData = Newtonsoft.Json.JsonConvert.DeserializeObject>(uploadFormData.FormFieldData["FileData"].ToString()); + + + + // //Instantiate the business object handler + // SockType TheType = System.Enum.Parse(UploadAType, true); + // log.LogDebug($"Instantiating biz object handler for {TheType}"); + // var biz = BizObjectFactory.GetBizObject(TheType, ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + + // if (!(biz is IImportAbleObject)) + // return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Import not supported for {TheType} objects")); + + // //We have our files now can parse and insert into db + // if (uploadFormData.UploadedFiles.Count > 0) + // { + // //deserialize each file and import + // foreach (UploadedFileInfo a in uploadFormData.UploadedFiles) + // { + // JArray ja = JArray.Parse(System.IO.File.ReadAllText(a.InitialUploadedPathName)); + // ImportResult.AddRange(await ((IImportAbleObject)biz).ImportData(ja)); + // } + // } + // } + // catch (System.IO.InvalidDataException ex) + // { + // return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, ex.Message)); + // } + // finally + // { + // //delete all the files temporarily uploaded and return bad request + + // ApiUploadProcessor.DeleteTempUploadFile(uploadFormData); + // } + + // return Ok(ApiOkResponse.Response(ImportResult)); + // } + + + + + + //----------------------------------------- + + + + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/IntegrationController.cs b/server/Controllers/IntegrationController.cs new file mode 100644 index 0000000..244b45f --- /dev/null +++ b/server/Controllers/IntegrationController.cs @@ -0,0 +1,246 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using System; + + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/integration")] + [Produces("application/json")] + [Authorize] + public class IntegrationController : ControllerBase + { + /* + todo: needs routes for logging and fetching log to view, also mapping collection stuff perhaps?? + + */ + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public IntegrationController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Create Integration + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostIntegration([FromBody] Integration newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + Integration o = await biz.CreateAsync(newObject); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(IntegrationController.GetIntegration), new { integrationAppId = o.IntegrationAppId, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + + /// + /// Get Integration + /// + /// + /// Integration + [HttpGet("{integrationAppId}")] + public async Task GetIntegration([FromRoute] Guid integrationAppId) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(integrationAppId, true); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + /// + /// Get Integration by DB Id + /// + /// + /// Integration + [HttpGet("by-dbid/{id}")] + public async Task GetIntegrationByDbId([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(id, true); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + + + + /// + /// Update Integration to db and return it + /// + /// + /// Entire integration object (differs from most object routes which only return concurrency value) + [HttpPut] + public async Task PutIntegration([FromBody] Integration updatedObject) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var o = await biz.PutAsync(updatedObject); + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(o)); ; + } + + /// + /// Delete Integration + /// + /// + /// NoContent + [HttpDelete("{integrationAppId}")] + public async Task DeleteIntegration([FromRoute] Guid integrationAppId) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await biz.DeleteAsync(integrationAppId)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + /// + /// Delete Integration + /// + /// + /// NoContent + [HttpDelete("by-dbid/{id}")] + public async Task DeleteIntegrationByDbId([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await biz.DeleteAsync(id)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + + + + + /// + /// Check Integration existance + /// + /// + /// Integration + [HttpGet("exists/{integrationAppId}")] + public async Task GetIntegrationExistance([FromRoute] Guid integrationAppId) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + return Ok(ApiOkResponse.Response(await biz.ExistsByIntegrationAppIdAsync(integrationAppId))); + } + + + /// + /// Create Integration log entry + /// + /// id=Integration internal Id (not IntegrationAppId value), name = status text to log + /// From route path + /// NoContent if ok otherwise BadRequest and an error object + [HttpPost("log")] + public async Task PostIntegrationLog([FromBody] NameIdItem logItem, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + bool bResult = await biz.LogAsync(logItem); + if (bResult == false) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return NoContent(); + } + + /// + /// Get Integration log for id of integration specified + /// + /// All log entries available for integration id + [HttpGet("log/{id}")] + public async Task GetAllLogs([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + return Ok(ApiOkResponse.Response(await biz.GetLogAsync(id))); + } + + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/JobOperationsController.cs b/server/Controllers/JobOperationsController.cs new file mode 100644 index 0000000..4987276 --- /dev/null +++ b/server/Controllers/JobOperationsController.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using Newtonsoft.Json.Linq; + + +namespace Sockeye.Api.Controllers +{ + + /// + /// ServerJob controller + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/job-operations")] + [Produces("application/json")] + [Authorize] + public class JobOperationsController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public JobOperationsController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + + + /// + /// Get Operations jobs list + /// + /// List of operations jobs + [HttpGet] + public async Task List() + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerJob)) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + //Instantiate the business object handler + JobOperationsBiz biz = new JobOperationsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + + List l = await biz.GetJobListAsync(); + return Ok(ApiOkResponse.Response(l)); + } + + + /// + /// Get current job status for a job + /// + /// + /// A single job's current status + [HttpGet("status/{gid}")] + public async Task GetJobStatus([FromRoute] Guid gid) + { + //this is called from things that might be running and have server temporarily locked down (e.g. seeding) + //so it should never return an error on closed + // if (serverState.IsClosed) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //This is called by the UI to monitor any operation that triggers a job so it really should be available to any logged in user + // if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerJob)) + // { + // return StatusCode(403, new ApiNotAuthorizedResponse()); + // } + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + return Ok(ApiOkResponse.Response(await JobsBiz.GetJobStatusAsync(gid))); + } + + /// + /// Get current job status and progress for a job + /// + /// + /// A single job's current status and progress + [HttpGet("progress/{gid}")] + public async Task GetJobProgress([FromRoute] Guid gid) + { + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + return Ok(ApiOkResponse.Response(await JobsBiz.GetJobProgressAsync(gid))); + } + + + + + /// + /// Get Operations log for a job + /// + /// + /// A single job's log + [HttpGet("logs/{gid}")] + public async Task GetLogs([FromRoute] Guid gid) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + + //## NOTE: deliberately do *not* check for authorization as this is called by any batch operation users may submit via extensions + //and the user would need the exact Guid to view a job so not likely they will fish for it in a nefarious way + // if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerJob)) + // { + // return StatusCode(403, new ApiNotAuthorizedResponse()); + // } + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + //Instantiate the business object handler + JobOperationsBiz biz = new JobOperationsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + + List l = await biz.GetJobLogListAsync(gid); + return Ok(ApiOkResponse.Response(l)); + } + + + /// + /// Get Operations log for all jobs + /// + + /// Log for all jobs in system + [HttpGet("logs/all-jobs")] + public async Task GetAllLogs() + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerJob)) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + //Instantiate the business object handler + JobOperationsBiz biz = new JobOperationsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + + List l = await biz.GetAllJobsLogsListAsync(); + return Ok(ApiOkResponse.Response(l)); + } + + + /// + /// Trigger a test job that simulates a (30 second) long running operation for testing and ops confirmation + /// + /// Job id + [HttpPost("test-job")] + public async Task TestWidgetJob() + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.ServerJob)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + OpsJob j = new OpsJob(); + j.Name = "TestJob"; + j.JobType = JobType.TestJob; + await JobsBiz.AddJobAsync(j); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, $"{j.JobType} {j.Name}"), ct); + return Accepted(new { JobId = j.GId });//202 accepted + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // EXTENSION BATCH JOBS + // + // + + + /// + /// Batch DELETE list of object id's specified + /// + /// + /// Job Id + [HttpPost("batch-delete")] + public async Task BatchDeleteObjects([FromBody] DataListSelectedRequest selectedRequest) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (selectedRequest == null) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "DataListSelectedRequest is required")); + + + if (!Authorized.HasDeleteRole(HttpContext.Items, selectedRequest.SockType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + //Rehydrate id list if necessary + if (selectedRequest.SelectedRowIds.Length == 0) + selectedRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList( + selectedRequest, + ct, + UserRolesFromContext.Roles(HttpContext.Items), + log, + UserIdFromContext.Id(HttpContext.Items), + UserTranslationIdFromContext.Id(HttpContext.Items)); + + var JobName = $"LT:BatchDeleteJob - LT:{selectedRequest.SockType} ({selectedRequest.SelectedRowIds.LongLength}) LT:User {UserNameFromContext.Name(HttpContext.Items)}"; + JObject o = JObject.FromObject(new + { + idList = selectedRequest.SelectedRowIds + }); + + OpsJob j = new OpsJob(); + j.Name = JobName; + j.SockType = selectedRequest.SockType; + j.JobType = JobType.BatchCoreObjectOperation; + j.SubType = JobSubType.Delete; + j.Exclusive = false; + j.JobInfo = o.ToString(); + await JobsBiz.AddJobAsync(j); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct); + return Accepted(new { JobId = j.GId }); + } + + + + /// + /// Request cancellation of Job. Not all jobs can be cancelled. + /// + /// + /// Accepted + [HttpPost("request-cancel")] + public async Task RequestCancelJob([FromBody] Guid gid) + { + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + await JobsBiz.RequestCancelAsync(gid); + return Accepted(); + } + + + //------------ + + + + + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/KPIController.cs b/server/Controllers/KPIController.cs new file mode 100644 index 0000000..5615575 --- /dev/null +++ b/server/Controllers/KPIController.cs @@ -0,0 +1,85 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using Sockeye.KPI; +using System.Threading.Tasks; +using System.Linq; + +namespace Sockeye.Api.Controllers +{ + + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/kpi")] + [Produces("application/json")] + [Authorize] + public class KPIController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + + /// + /// ctor + /// + /// + /// + /// + public KPIController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + /// + /// Get KPI data + /// + /// Parameters for pick list see api docs for details + /// Filtered list + [HttpPost] + public async Task PostList([FromBody] KPIRequestOptions options) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + //UserTypeFromContext.Type(HttpContext.Items) + return Ok(await KPIFetcher.GetResponseAsync(ct, options, UserRolesFromContext.Roles(HttpContext.Items), log, UserIdFromContext.Id(HttpContext.Items))); + } + + + + + //SAVE FOR LATER IF WANT TO RETURN LIST OF ALL KPI'S (likely something to do with report editing??) + // /// + // /// List of all DataList keys available + // /// + // /// List of strings + // [HttpGet("listkeys")] + // public ActionResult GetDataListKeys() + // { + // //NOTE: not used by Sockeye Client, convenience method for developers api usage + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + // return Ok(ApiOkResponse.Response(DataListFactory.GetListOfAllDataListKeyNames())); + // } + + + + + + + }//eoc +}//ens \ No newline at end of file diff --git a/server/Controllers/LogFilesController.cs b/server/Controllers/LogFilesController.cs new file mode 100644 index 0000000..ce3e57a --- /dev/null +++ b/server/Controllers/LogFilesController.cs @@ -0,0 +1,163 @@ +using System.Linq; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using System.Threading.Tasks; +using System; +using System.IO; + +namespace Sockeye.Api.Controllers +{ + + /// + /// Log files controller + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/log-file")] + //[Produces("application/json")] + [Authorize] + public class LogFilesController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public LogFilesController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + + /// + /// Get server log + /// + /// + /// A single log file in plain text + [HttpGet("{logname}")] + public ActionResult GetLog([FromRoute] string logname) + { + //NOTE: this route deliberately open even when server closed as a troubleshooting measure + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.LogFile)) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + //stream the file contents into a json object and return + + //build the full path from the log file name and defined path + var logFilePath = System.IO.Path.Combine(ServerBootConfig.SOCKEYE_LOG_PATH, logname); + //does file exist? + if (!System.IO.File.Exists(logFilePath)) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + + //Log + + //nlog update now locks file for writing so needs to be opened this way + FileStreamOptions fso = new FileStreamOptions(); + fso.Access = FileAccess.Read; + fso.Mode = FileMode.Open; + fso.Share = FileShare.ReadWrite; + using (StreamReader sr = new StreamReader(logFilePath, fso)) + { + return Content(sr.ReadToEnd()); + } + //return Content(System.IO.File.ReadAllText(logFilePath)); + + } + + + + /// + /// Get list of operations logs + /// + /// + [HttpGet()] + public ActionResult ListLogs() + { + //NOTE: this route deliberately open even when server closed as a troubleshooting measure + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.LogFile)) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + + //Iterate all log files and build return + var files = System.IO.Directory.GetFiles(ServerBootConfig.SOCKEYE_LOG_PATH, "log-sockeye*.txt"); + + var ret = files.Where(z => !z.EndsWith("ayanova.txt")).OrderByDescending(z => z).Select(z => System.IO.Path.GetFileName(z)).ToList(); + ret.Insert(0, "log-sockeye.txt"); + return Ok(ApiOkResponse.Response(ret)); + } + + + /// + /// Download log + /// + /// + /// download token + /// A single log file + [AllowAnonymous] + [HttpGet("download/{logname}")] + public async Task DownloadLog([FromRoute] string logname, [FromQuery] string t) + { + //NOTE: this route deliberately open even when server closed as a troubleshooting measure + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var user = await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct); + if (user == null) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + if (!Authorized.HasReadFullRole(user.Roles, SockType.LogFile)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + var logFilePath = System.IO.Path.Combine(ServerBootConfig.SOCKEYE_LOG_PATH, logname); + if (!System.IO.File.Exists(logFilePath)) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + //Note: this works with new Nlog file locking for write so it must be opening it as read and shareable + return PhysicalFile(logFilePath, "text/plain", $"{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")}-{logname}"); + } + + //------------ + + + } +} \ No newline at end of file diff --git a/server/Controllers/LogoController.cs b/server/Controllers/LogoController.cs new file mode 100644 index 0000000..301adcd --- /dev/null +++ b/server/Controllers/LogoController.cs @@ -0,0 +1,222 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using Sockeye.Util; +using System.IO; + + +namespace Sockeye.Api.Controllers +{ + + /// + /// Logo controller + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/logo")] + [Produces("application/json")] + [Authorize] + public class LogoController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + // private const int MAXIMUM_LOGO_SIZE = 512000;//We really don't want it too big or it will slow the fuck down for everything + + + /// + /// ctor + /// + /// + /// + /// + public LogoController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + + + /// + /// Get Logo + /// + /// One of "small", "medium", "large" + /// A single Logo and it's values + [AllowAnonymous] + [HttpGet("{size}")] + public async Task DownloadLogo([FromRoute] string size) + { + //allowing this because it messes up the login form needlessly + // if (serverState.IsClosed) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (string.IsNullOrWhiteSpace(size)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "size", "Size is required and must be one of 'small', 'medium' or 'large'")); + + size = size.ToLowerInvariant(); + if (size != "small" && size != "medium" && size != "large") + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "size", "Size parameter must be one of 'small', 'medium' or 'large'")); + + var logo = await ct.Logo.SingleOrDefaultAsync(); + if (logo == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + + switch (size) + { + case "small": + if (logo.Small == null) + return NotFound(); + return new FileStreamResult(new MemoryStream(logo.Small), Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse(logo.SmallType)); + + case "medium": + if (logo.Medium == null) + return NotFound(); + return new FileStreamResult(new MemoryStream(logo.Medium), Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse(logo.MediumType)); + + case "large": + if (logo.Large == null) + return NotFound(); + return new FileStreamResult(new MemoryStream(logo.Large), Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse(logo.LargeType)); + } + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + + + /// + /// Upload Logo + /// Max 500 KiB total (512000 bytes) + /// Must have full rights to Global object + /// + /// One of "small", "medium", "large" + /// Accepted + [Authorize] + [HttpPost("{size}")] + //[DisableFormValueModelBinding] + [RequestSizeLimit(ServerBootConfig.MAX_LOGO_UPLOAD_BYTES)] + public async Task UploadAsync([FromRoute] string size) + { + + //https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1#upload-small-files-with-buffered-model-binding-to-a-database + + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Global)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (string.IsNullOrWhiteSpace(size)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "size", "Size is required and must be one of 'small', 'medium' or 'large'")); + + size = size.ToLowerInvariant(); + if (size != "small" && size != "medium" && size != "large") + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "size", "Size parameter must be one of 'small', 'medium' or 'large'")); + + //get the one and only logo object + var logo = await ct.Logo.FirstOrDefaultAsync(); + if (logo == null) + { + logo = new Logo(); + ct.Logo.Add(logo); + await ct.SaveChangesAsync(); + } + + var file = Request.Form.Files[0]; + + //var file=files[0]; + using (var memoryStream = new MemoryStream()) + { + await file.CopyToAsync(memoryStream); + if (memoryStream.Length > ServerBootConfig.MAX_LOGO_UPLOAD_BYTES) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, null, $"Logo files must be smaller than {ServerBootConfig.MAX_LOGO_UPLOAD_BYTES} maximum")); + switch (size) + { + case "small": + logo.Small = memoryStream.ToArray(); + logo.SmallType = file.ContentType; + break; + case "medium": + logo.Medium = memoryStream.ToArray(); + logo.MediumType = file.ContentType; + break; + case "large": + logo.Large = memoryStream.ToArray(); + logo.LargeType = file.ContentType; + break; + + } + await ct.SaveChangesAsync(); + } + return Accepted(); + } + + + /// + /// Delete logo + /// + /// + /// NoContent + [Authorize] + [HttpDelete("{size}")] + public async Task DeleteLogo([FromRoute] string size) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Global)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (string.IsNullOrWhiteSpace(size)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "size", "Size is required and must be one of 'small', 'medium' or 'large'")); + + size = size.ToLowerInvariant(); + if (size != "small" && size != "medium" && size != "large") + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "size", "Size parameter must be one of 'small', 'medium' or 'large'")); + + //get the one and only logo object + var logo = await ct.Logo.FirstOrDefaultAsync(); + if (logo != null) + { + switch (size) + { + case "small": + logo.Small = null; + logo.SmallType = string.Empty; + break; + case "medium": + logo.Medium = null; + logo.MediumType = string.Empty; + break; + case "large": + logo.Large = null; + logo.LargeType = string.Empty; + break; + + } + await ct.SaveChangesAsync(); + } + + return NoContent(); + } + + + + + } +} \ No newline at end of file diff --git a/server/Controllers/MemoController.cs b/server/Controllers/MemoController.cs new file mode 100644 index 0000000..2c90dd9 --- /dev/null +++ b/server/Controllers/MemoController.cs @@ -0,0 +1,228 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/memo")] + [Produces("application/json")] + [Authorize] + public class MemoController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public MemoController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Create Memo + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostMemo([FromBody] SendMemo newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + MemoBiz biz = MemoBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var RouteUserId = UserIdFromContext.Id(HttpContext.Items); + //v8 migrate hacky workaround to allow specifying toid + //-7 to id means it's migrating from v7 so treat as a single object + + + if (newObject.Users.Count == 1 && newObject.Users[0] == -7 && RouteUserId == 1) + { + Memo newMemo = new Memo(); + Sockeye.Util.CopyObject.Copy(newObject.Memo, newMemo); + // newMemo.ToId = newObject.Memo.ToId; + // newMemo.FromId = newObject.Memo.FromId; + Memo o = await biz.CreateAsync(newMemo); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return Ok(ApiOkResponse.Response(new { Id = o.Id }));//v8 migrate needs to id number to fixup the log post migrate + } + else + { + + foreach (long lUserId in newObject.Users) + { + Memo newMemo = new Memo(); + Sockeye.Util.CopyObject.Copy(newObject.Memo, newMemo); + newMemo.ToId = lUserId; + newMemo.FromId = RouteUserId; + Memo o = await biz.CreateAsync(newMemo); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + + //return nothing but ok + return Accepted(); + } + + } + + public class SendMemo + { + public SendMemo() + { + Users = new List(); + } + [Required] + public Memo Memo { get; set; } + + [Required] + public List Users { get; set; } + } + //------------ + + + //NO DUPLICATING MEMOS + // /// + // /// Duplicate Memo + // /// (Wiki and Attachments are not duplicated) + // /// + // /// Source object id + // /// From route path + // /// Memo + // [HttpPost("duplicate/{id}")] + // public async Task DuplicateMemo([FromRoute] long id, ApiVersion apiVersion) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + // MemoBiz biz = MemoBiz.GetBiz(ct, HttpContext); + // if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + // Memo o = await biz.DuplicateAsync(id); + // if (o == null) + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // else + // return CreatedAtAction(nameof(MemoController.GetMemo), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + // } + + /// + /// Get Memo + /// + /// + /// Memo + [HttpGet("{id}")] + public async Task GetMemo([FromRoute] long id) + { + //NOTE: In this case always getting own memo only + //also it's always just for read only purposes so it should include from user name + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + MemoBiz biz = MemoBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(id); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + var fromUser = await ct.User.AsNoTracking().SingleOrDefaultAsync(z => z.Id == o.FromId); + var from = "??"; + if (fromUser != null) from = fromUser.Name; + var ret = new + { + Id = o.Id, + Name = o.Name, + Notes = o.Notes, + Wiki = o.Wiki, + CustomFields = o.CustomFields, + Tags = o.Tags, + Viewed = o.Viewed, + Replied = o.Replied, + FromId = o.FromId, + ToId = o.ToId, + Sent = o.Sent, + FromName = from + }; + return Ok(ApiOkResponse.Response(ret)); + } + + //NO UPDATING MEMOS + // /// + // /// Update Memo + // /// + // /// + // /// + // [HttpPut] + // public async Task PutMemo([FromBody] Memo updatedObject) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + // MemoBiz biz = MemoBiz.GetBiz(ct, HttpContext); + // if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + // var o = await biz.PutAsync(updatedObject); + // if (o == null) + // { + // if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + // return StatusCode(409, new ApiErrorResponse(biz.Errors)); + // else + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // } + // return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));; + // } + + /// + /// Delete Memo + /// + /// + /// NoContent + [HttpDelete("{id}")] + public async Task DeleteMemo([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + MemoBiz biz = MemoBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await biz.DeleteAsync(id)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + + + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/NameController.cs b/server/Controllers/NameController.cs new file mode 100644 index 0000000..7cbdf68 --- /dev/null +++ b/server/Controllers/NameController.cs @@ -0,0 +1,68 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/name")] + [Produces("application/json")] + [Authorize] + public class NameController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public NameController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + /// + /// Get Name of business object + /// not all business objects have names some may return '-' or simply the type name + /// if that is the case + /// + /// SockType + /// Non zero id, if zero returns type name + /// Name + [HttpGet("{aType}/{id}")] + public ActionResult GetName([FromRoute] SockType aType, [FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!Authorized.HasSelectRole(HttpContext.Items, aType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + if (id == 0) + return Ok(ApiOkResponse.Response(aType.ToString())); + + return Ok(ApiOkResponse.Response(BizObjectNameFetcherDirect.Name(aType, id,UserTranslationIdFromContext.Id(HttpContext.Items), ct))); + + } + + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/NotifyController.cs b/server/Controllers/NotifyController.cs new file mode 100644 index 0000000..60f06ae --- /dev/null +++ b/server/Controllers/NotifyController.cs @@ -0,0 +1,353 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Sockeye.Util; + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/notify")] + [Produces("application/json")] + [Authorize] + public class NotifyController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public NotifyController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Pre-login route to confirm server is available + /// + /// + [AllowAnonymous] + [HttpGet("hello")] + public async Task GetPreLoginPing() + { + + //confirm if there are logo's to show as well + var logo = await ct.Logo.AsNoTracking().SingleOrDefaultAsync(); + + bool HasLargeLogo = false; + bool HasMediumLogo = false; + bool HasSmallLogo = false; + if (logo != null) + { + if (logo.Small != null) HasSmallLogo = true; + if (logo.Medium != null) HasMediumLogo = true; + if (logo.Large != null) HasLargeLogo = true; + } + + + return Ok(ApiOkResponse.Response( + new { ll = HasLargeLogo, ml = HasMediumLogo, sl = HasSmallLogo })); + } + + + + /// + /// Get count of new notifications waiting + /// + /// + [HttpGet("new-count")] + public async Task GetNewCount() + { + var UserId = UserIdFromContext.Id(HttpContext.Items); + if (serverState.IsClosed && UserId != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + return Ok(ApiOkResponse.Response(await ct.InAppNotification.CountAsync(z => z.UserId == UserId && z.Fetched == false))); + } + + /// + /// Get all in-app notifications + /// + /// + [HttpGet("app-notifications")] + public async Task GetAppNotifications() + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + var UserId = UserIdFromContext.Id(HttpContext.Items); + var ret = await ct.InAppNotification.AsNoTracking().Where(z => z.UserId == UserId).OrderByDescending(z => z.Created).ToListAsync(); + await ct.Database.ExecuteSqlInterpolatedAsync($"update ainappnotification set fetched={true} where userid = {UserId}"); + return Ok(ApiOkResponse.Response(ret)); + } + + + /// + /// Delete app Notification + /// + /// + /// NoContent + [HttpDelete("{id}")] + public async Task DeleteAppNotification([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var UserId = UserIdFromContext.Id(HttpContext.Items); + var n = await ct.InAppNotification.FirstOrDefaultAsync(z => z.Id == id); + if (n == null) + return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "id")); + if (n.UserId != UserId) + return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, null, "Can't delete notification for another user")); + ct.InAppNotification.Remove(n); + await ct.SaveChangesAsync(); + return NoContent(); + } + + + /// + /// Get Notify Event object list from queue + /// + /// Notify Event objects awaiting delivery + [HttpGet("queue")] + public async Task GetQueue() + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.OpsNotificationSettings)) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + var ret = new List(); + var NotifyEvents = await ct.NotifyEvent.AsNoTracking().ToListAsync(); + foreach (NotifyEvent ne in NotifyEvents) + { + var UserInfo = await ct.User.AsNoTracking().Where(x => x.Id == ne.UserId).Select(x => new { Active = x.Active, Name = x.Name }).FirstOrDefaultAsync(); + var Subscription = await ct.NotifySubscription.AsNoTracking().FirstOrDefaultAsync(x => x.Id == ne.NotifySubscriptionId); + + ret.Add(new NotifyEventQueueItem(ne.Id, ne.Created, ne.EventDate, (ne.EventDate + Subscription.AgeValue - Subscription.AdvanceNotice), ne.UserId, UserInfo.Name, ne.EventType, ne.SockType, ne.Name)); + } + + return Ok(ApiOkResponse.Response(ret)); + } + + public record NotifyEventQueueItem(long Id, DateTime Created, DateTime EventDate, DateTime DeliverAfter, long UserId, string User, NotifyEventType EventType, SockType SockType, string Name); + + /// + /// Delete pending notification event + /// + /// + /// NoContent + [HttpDelete("notify-event/{id}")] + public async Task DeleteNotifyEvent([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + if (!Authorized.HasDeleteRole(HttpContext.Items, SockType.OpsNotificationSettings)) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + var n = await ct.NotifyEvent.FirstOrDefaultAsync(z => z.Id == id); + if (n == null) + return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "id")); + ct.NotifyEvent.Remove(n); + await ct.SaveChangesAsync(); + return NoContent(); + } + + + + /// + /// Send direct message notification to selected users + /// + /// NoContent on success or error + [HttpPost("direct-message")] + public async Task SendNotifyDirectMessage([FromBody] NotifyDirectMessage notifyDirectMessage) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + + foreach (long l in notifyDirectMessage.Users) + { + if (l != 0) + await NotifyEventHelper.AddGeneralNotifyEvent( + NotifyEventType.GeneralNotification, notifyDirectMessage.Message, UserNameFromContext.Name(HttpContext.Items), null, l + ); + } + + return NoContent(); + } + + public class NotifyDirectMessage + { + public NotifyDirectMessage() + { + Users = new List(); + } + [Required] + public string Message { get; set; } + + [Required] + public List Users { get; set; } + } + + /// + /// Send direct SMTP message notification so single object / address + /// Server notification settings must be set and active + /// Currently supported types are Customer, HeadOffice, Vendor, User + /// WARNING: be careful using this method; high volume emailing or spam-like behavior + /// could result in a ban or block of your mail account or mail server or domain + /// Use of this method is logged to Sockeye event log on successful attempted delivery + /// + /// Accepted on success or error + [HttpPost("direct-smtp")] + public async Task SendNotifySmtpDirectMessage([FromBody] NotifyDirectSMTP notifyDirectSMTP) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (!ServerGlobalOpsSettingsCache.Notify.SmtpDeliveryActive) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, null, "Email notifications are set to OFF at server, unable to send 'on request' type SMTP notification")); + + //for now I'm allowing any authenticated user to call this route intentionally as the use case is for our own internal messaging from the Customer form only or for API users who want to send + //email when rendering a report (case 4310). + + //validate incoming data + if (string.IsNullOrWhiteSpace(notifyDirectSMTP.Subject)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "Subject", "Subject field is required")); + + if (string.IsNullOrWhiteSpace(notifyDirectSMTP.TextBody) && string.IsNullOrWhiteSpace(notifyDirectSMTP.HTMLBody)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, null, "TextBody or HTMLBody field is required")); + + if (string.IsNullOrWhiteSpace(notifyDirectSMTP.ToAddress)) + { + //We need to fetch the address from the object type and id + //if no id then can skip the rest here + if (notifyDirectSMTP.ObjectId == 0) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "ObjectId", "No address or object id specified, no where to send this")); + } + //get the address + switch (notifyDirectSMTP.SockType) + { + case SockType.NoType: + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "ToAddress", "No address or object type and id specified no where to send this")); + case SockType.Customer: + { + var o = await ct.Customer.AsNoTracking().Where(x => x.Id == notifyDirectSMTP.ObjectId).Select(x => new { x.Name, x.EmailAddress, x.Active }).FirstOrDefaultAsync(); + if (o == null) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "ObjectId")); + } + if (string.IsNullOrWhiteSpace(o.EmailAddress)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "EmailAddress", $"Customer {o.Name} doesn't have an email address no where to send this")); + if (o.Active == false) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "Active", $"Customer {o.Name} is not active, only active customers can be emailed directly")); + notifyDirectSMTP.ToAddress = o.EmailAddress; + } + break; + case SockType.HeadOffice: + { + var o = await ct.HeadOffice.AsNoTracking().Where(x => x.Id == notifyDirectSMTP.ObjectId).Select(x => new { x.Name, x.EmailAddress, x.Active }).FirstOrDefaultAsync(); + if (o == null) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "ObjectId")); + } + if (string.IsNullOrWhiteSpace(o.EmailAddress)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "EmailAddress", $"HeadOffice {o.Name} doesn't have an email address no where to send this")); + if (o.Active == false) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "Active", $"HeadOffice {o.Name} is not active, only active head offices can be emailed directly")); + notifyDirectSMTP.ToAddress = o.EmailAddress; + } + break; + + case SockType.User: + { + var o = await ct.User.Include(z => z.UserOptions).AsNoTracking().Where(x => x.Id == notifyDirectSMTP.ObjectId).Select(x => new { x.Name, x.UserOptions.EmailAddress, x.Active }).FirstOrDefaultAsync(); + if (o == null) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "ObjectId")); + } + if (string.IsNullOrWhiteSpace(o.EmailAddress)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "EmailAddress", $"Vendor {o.Name} doesn't have an email address no where to send this")); + if (o.Active == false) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "Active", $"Vendor {o.Name} is not active, only active Vendors can be emailed directly")); + notifyDirectSMTP.ToAddress = o.EmailAddress; + } + break; + + default: + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "SockType", "Specified Type not supported for 'on request' smtp")); + + } + } + var UserId = UserIdFromContext.Id(HttpContext.Items); + + IMailer m = Sockeye.Util.ServiceProviderProvider.Mailer; + try + { + await m.SendEmailAsync(notifyDirectSMTP.ToAddress, notifyDirectSMTP.Subject, notifyDirectSMTP.TextBody, ServerGlobalOpsSettingsCache.Notify, null, null, notifyDirectSMTP.HTMLBody); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, notifyDirectSMTP.ObjectId, notifyDirectSMTP.SockType, SockEvent.DirectSMTP, $"\"{notifyDirectSMTP.Subject}\"->{notifyDirectSMTP.ToAddress}"), ct); + } + catch (Exception ex) + { + await NotifyEventHelper.AddOpsProblemEvent("SMTP direct message failed", ex); + return StatusCode(500, new ApiErrorResponse(ApiErrorCode.API_SERVER_ERROR, null, ExceptionUtil.ExtractAllExceptionMessages(ex))); + } + + return Accepted(); + } + + public class NotifyDirectSMTP + { + public NotifyDirectSMTP() + { + + } + + public long ObjectId { get; set; } = 0; + public SockType SockType { get; set; } = SockType.NoType; + public string ToAddress { get; set; } + public string Subject { get; set; } + public string TextBody { get; set; } + public string HTMLBody { get; set; } + } + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/NotifySubscriptionController.cs b/server/Controllers/NotifySubscriptionController.cs new file mode 100644 index 0000000..3c91867 --- /dev/null +++ b/server/Controllers/NotifySubscriptionController.cs @@ -0,0 +1,187 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System; + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/notify-subscription")] + [Produces("application/json")] + [Authorize] + public class NotifySubscriptionController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public NotifySubscriptionController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Create NotifySubscription + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostNotifySubscription([FromBody] NotifySubscription newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + NotifySubscriptionBiz biz = NotifySubscriptionBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + NotifySubscription o = await biz.CreateAsync(newObject); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(NotifySubscriptionController.GetNotifySubscription), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + // /// + // /// Duplicate NotifySubscription + // /// + // /// Source object id + // /// From route path + // /// NotifySubscription + // [HttpPost("duplicate/{id}")] + // public async Task DuplicateNotifySubscription([FromRoute] long id, ApiVersion apiVersion) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + // NotifySubscriptionBiz biz = NotifySubscriptionBiz.GetBiz(ct, HttpContext); + // if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + // NotifySubscription o = await biz.DuplicateAsync(id); + // if (o == null) + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // else + // return CreatedAtAction(nameof(NotifySubscriptionController.GetNotifySubscription), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + // } + + /// + /// Get NotifySubscription + /// + /// + /// NotifySubscription + [HttpGet("{id}")] + public async Task GetNotifySubscription([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + NotifySubscriptionBiz biz = NotifySubscriptionBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(id); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + /// + /// Update NotifySubscription + /// + /// + /// + [HttpPut] + public async Task PutNotifySubscription([FromBody] NotifySubscription updatedObject) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + NotifySubscriptionBiz biz = NotifySubscriptionBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var o = await biz.PutAsync(updatedObject); + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); + } + + /// + /// Delete NotifySubscription + /// + /// + /// NoContent + [HttpDelete("{id}")] + public async Task DeleteNotifySubscription([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + NotifySubscriptionBiz biz = NotifySubscriptionBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await biz.DeleteAsync(id)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + + /// + /// Get Subscription list + /// + /// User's notification subscription list + [HttpGet("list")] + public async Task GetQueue() + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //NOTE: in future if getting list for another user should just duplicate this method but add the parameter for user id + //and checking of rights + long UserId = UserIdFromContext.Id(HttpContext.Items); + + var subs = await ct.NotifySubscription.AsNoTracking().Where(z => z.UserId == UserId).OrderBy(z=>z.Id).ToListAsync(); + + List ret = new List(); + foreach (var s in subs) + { + ret.Add(new NotifySubscriptionRecord(s.Id, s.UserId, s.EventType, s.SockType, s.DeliveryMethod, s.DeliveryAddress, s.Tags, "na-status", s.AgeValue, s.DecValue)); + } + + return Ok(ApiOkResponse.Response(ret)); + } + private record NotifySubscriptionRecord( + long id, long userid, NotifyEventType eventType, SockType SockType, + NotifyDeliveryMethod deliveryMethod, string deliveryAddress, List tags, string status, TimeSpan ageValue, decimal decValue + ); + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/PickListController.cs b/server/Controllers/PickListController.cs new file mode 100644 index 0000000..b99700d --- /dev/null +++ b/server/Controllers/PickListController.cs @@ -0,0 +1,284 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using Sockeye.PickList; +using System.Threading.Tasks; +using System.Linq; + +namespace Sockeye.Api.Controllers +{ + + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/pick-list")] + [Produces("application/json")] + [Authorize] + public class PickListController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + + /// + /// ctor + /// + /// + /// + /// + public PickListController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + /// + /// Get picklist of all Active objects of type specified and filtered by query specified + /// NOTE: Query is valid only if: + /// it is an empty string indicating not filtered just selected + /// if not an empty string, it has at most two space separated strings and one of them is a special TAG specific query that starts with two consecutive periods + /// i.e. "some" is valid (single query on all templated fields) + /// "..zon some" is valid (all tags like zon and all template fields like some) + /// "zon some" is NOT valid (missing TAGS indicator), "..zone some re" is NOT valid (too many strings) + /// Note that this list is capped automatically to return no more than 100 results + /// + /// Parameters for pick list see api docs for details + /// Filtered list + [HttpPost("list")] + public async Task PostList([FromBody] PickListOptions pickListParams) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //NOTE: these sequence of calls are a little different than other objects due to the nature of rights and stuff with picklists being different + + var PickList = PickListFactory.GetAyaPickList(pickListParams.SockType); + + //was the name not found as a pick list? + if (PickList == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + //RIGHTS - NOTE: uniquely to other routes this one checks the actual picklist defined roles itself + if (!Authorized.HasAnyRole(HttpContext.Items, PickList.AllowedRoles)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + + //Instantiate the business object handler + PickListBiz biz = PickListBiz.GetBiz(ct, HttpContext); + + + + + //handle HeadOffice only restricted variants + if (pickListParams.ListVariant == "ho") + { + //add a variant for the current user's head office id in place of ho + var UserId = UserIdFromContext.Id(HttpContext.Items); + var UType = UserTypeFromContext.Type(HttpContext.Items); + if (UType != UserType.HeadOffice) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var HoId = await ct.User.AsNoTracking().Where(x => x.Id == UserId).Select(x => x.HeadOfficeId).SingleOrDefaultAsync(); + if (HoId == null || HoId == 0) + return StatusCode(403, new ApiNotAuthorizedResponse()); + pickListParams.ListVariant = $"{HoId},{(int)SockType.HeadOffice}"; + + } + + var o = await biz.GetPickListAsync(PickList, pickListParams.Query, pickListParams.Inactive, pickListParams.PreselectedIds.ToArray(), pickListParams.ListVariant, log, pickListParams.Template); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return Ok(ApiOkResponse.Response(o)); + } + + + + /// + /// Get a single item's name display in PickList templated format + /// + /// + /// One display string or an empty string if not found or invalid + [HttpPost("single")] + public async Task PostSingle([FromBody] PickListSingleOptions pickListSingleParams) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (!Authorized.HasSelectRole(HttpContext.Items, pickListSingleParams.SockType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + //Instantiate the business object handler + PickListBiz biz = PickListBiz.GetBiz(ct, HttpContext); + + var o = await biz.GetTemplatedNameAsync(pickListSingleParams.SockType, pickListSingleParams.Id, pickListSingleParams.ListVariant, log, pickListSingleParams.Template); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return Ok(ApiOkResponse.Response(o)); + } + + + /// + /// Get PickListTemplate + /// + /// + /// The current effective template, either a customized one or the default + [HttpGet("template/{sockType}")] + public async Task GetPickListTemplate([FromRoute] SockType sockType) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //Instantiate the business object handler + PickListBiz biz = PickListBiz.GetBiz(ct, HttpContext); + + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var o = await biz.GetAsync(sockType); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + return Ok(ApiOkResponse.Response(o)); + } + + + + /// + /// List of all PickList templates + /// + /// List of strings + [HttpGet("template/list")] + public ActionResult GetTemplateList() + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //Instantiate the business object handler + PickListBiz biz = PickListBiz.GetBiz(ct, HttpContext); + + long TranslationId = UserTranslationIdFromContext.Id(HttpContext.Items); + var o = biz.GetListOfAllPickListTypes(TranslationId); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + return Ok(ApiOkResponse.Response(o)); + + } + + + + + /// + /// POST (replace) Pick List template + /// (note: in this case the Id is the SockType numerical value as there is only one template per type) + /// + /// + /// + // [HttpPost("Template/{sockType}")] + [HttpPost("template")] + public async Task ReplacePickListTemplate([FromBody] PickListTemplate template) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //Instantiate the business object handler + PickListBiz biz = PickListBiz.GetBiz(ct, HttpContext); + + // var o = await biz.GetAsync(sockType, false); + // if (o == null) + // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + try + { + if (!await biz.ReplaceAsync(template)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + catch (DbUpdateConcurrencyException) + { + + return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT)); + } + return NoContent(); + } + + + /// + /// Delete customized template + /// (revert to default) + /// + /// + /// Ok + [HttpDelete("template/{sockType}")] + public async Task DeletePickListTemplate([FromRoute] SockType sockType) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //Instantiate the business object handler + PickListBiz biz = PickListBiz.GetBiz(ct, HttpContext); + + + if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!await biz.DeleteAsync(sockType)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + + return NoContent(); + } + + + /// + /// List of all fields for pick list SockType specified + /// + /// List of fields available for template + [HttpGet("template/listfields/{sockType}")] + public ActionResult GetPickListFields([FromRoute] SockType sockType) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + var PickList = PickListFactory.GetAyaPickList(sockType); + //type might not be supported + if (PickList == null) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, null, $"PickList for type \"{sockType.ToString()}\" not supported")); + } + return Ok(ApiOkResponse.Response(PickList.ColumnDefinitions)); + } + + + + + + + }//eoc +}//ens \ No newline at end of file diff --git a/server/Controllers/ReminderController.cs b/server/Controllers/ReminderController.cs new file mode 100644 index 0000000..c62d8d2 --- /dev/null +++ b/server/Controllers/ReminderController.cs @@ -0,0 +1,164 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/reminder")] + [Produces("application/json")] + [Authorize] + public class ReminderController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public ReminderController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Create Reminder + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostReminder([FromBody] Reminder newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ReminderBiz biz = ReminderBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + Reminder o = await biz.CreateAsync(newObject); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(ReminderController.GetReminder), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + + /// + /// Get Reminder + /// + /// + /// Reminder + [HttpGet("{id}")] + public async Task GetReminder([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ReminderBiz biz = ReminderBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(id); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return Ok(ApiOkResponse.Response(o)); + } + + /// + /// Update Reminder + /// + /// + /// + [HttpPut] + public async Task PutReminder([FromBody] Reminder updatedObject) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + ReminderBiz biz = ReminderBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var o = await biz.PutAsync(updatedObject); + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ; + } + + /// + /// Delete Reminder + /// + /// + /// NoContent + [HttpDelete("{id}")] + public async Task DeleteReminder([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + ReminderBiz biz = ReminderBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await biz.DeleteAsync(id)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + + + /// + /// Get Reminder schedule "more" info + /// + /// + /// Information to display in schedule when selected for more info + [HttpGet("sched-info/{id}")] + public async Task GetScheduleInfoView([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ReminderBiz biz = ReminderBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(id); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return Ok(ApiOkResponse.Response(new + { + o.Color, + o.Name, + o.Notes, + o.StartDate, + o.StopDate + })); + } + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/ReportController.cs b/server/Controllers/ReportController.cs new file mode 100644 index 0000000..c07c7e8 --- /dev/null +++ b/server/Controllers/ReportController.cs @@ -0,0 +1,425 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging; +using Microsoft.EntityFrameworkCore; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using Sockeye.Util; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/report")] + [Produces("application/json")] + [Authorize] + public class ReportController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public ReportController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + /// + /// Create Report + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostReport([FromBody] Report newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + Report o = await biz.CreateAsync(newObject); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(ReportController.GetReport), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + + /// + /// Get Report + /// + /// + /// Report + [HttpGet("{id}")] + public async Task GetReport([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(id); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + /// + /// Update Report + /// + /// + /// + [HttpPut] + public async Task PutReport([FromBody] Report updatedObject) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var o = await biz.PutAsync(updatedObject); + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ; + } + + /// + /// Delete Report + /// + /// + /// NoContent + [HttpDelete("{id}")] + public async Task DeleteReport([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await biz.DeleteAsync(id)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + + + /// + /// Get Report list for object + /// + /// Type of object + /// Name / id report list of allowed reports for role of requester + [HttpGet("list/{aType}")] + public async Task GetReportList([FromRoute] SockType aType) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + //extra check if they have rights to the type of object in question, this nips it in the bud before they even get to the fetch data stage later + if (!Authorized.HasReadFullRole(HttpContext.Items, aType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetReportListAsync(aType); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + + /// + /// Get a limited amount of sample data from id list in format used by report designer + /// + /// Data required for report + /// From route path + /// + [HttpPost("data")] + public async Task GetReportData([FromBody] DataListSelectedRequest selectedRequest, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //cap the data returned + selectedRequest.ReportDesignerSample = true; + JArray reportData; + try + { + reportData = await biz.GetReportDataForReportDesigner(selectedRequest); + if (reportData == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return Ok(ApiOkResponse.Response(reportData)); + } + catch (ReportRenderTimeOutException) + { + log.LogInformation($"GetReportData timeout data list key: {selectedRequest.DataListKey}, record count:{selectedRequest.SelectedRowIds.LongLength}, user:{UserNameFromContext.Name(HttpContext.Items)} "); + //note: this route is called by the report designer to get a limited subset of records so we should never see this error but including it for completeness + //report designer should show this as a general error + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "timeout - select fewer records")); + + } + } + + + /// + /// Start Render Report job + /// + /// report id and object id values for object type specified in report template + /// From route path + /// Job Id + [HttpPost("render-job")] + public async Task RequestRenderReport([FromBody] DataListReportRequest reportRequest, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var httpConnectionFeature = HttpContext.Features.Get(); + var API_URL = $"http://127.0.0.1:{httpConnectionFeature.LocalPort}/api/{SockeyeVersion.CurrentApiVersion}/"; + var result = await biz.RequestRenderReport(reportRequest, DateTime.UtcNow.AddMinutes(ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT), API_URL, UserNameFromContext.Name(HttpContext.Items)); + if (result == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return Accepted(new { JobId = result }); + } + + /// + /// Attempt cancel render job + /// + /// + /// nothing + [HttpPost("request-cancel/{gid}")] + public async Task RequestCancelJob([FromRoute] Guid gid) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + log.LogDebug($"request-cancel called for report rendering job id {gid}"); + await biz.CancelJob(gid); + return NoContent(); + } + + + /// + /// Download a rendered report + /// + /// + /// download token + /// + [HttpGet("download/{fileName}")] + [AllowAnonymous] + public async Task DownloadAsync([FromRoute] string fileName, [FromQuery] string t) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct) == null) + { + await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + if (!FileUtil.TemporaryFileExists(fileName)) + { + await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//fishing protection + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + var FilePath = FileUtil.GetFullPathForTemporaryFile(fileName); + return PhysicalFile(FilePath, "application/pdf"); + } + + + + /// + /// Download report template + /// + /// Report id + /// download token + /// A single report template as a file + [AllowAnonymous] + [HttpGet("export/{id}")] + public async Task DownloadTemplate([FromRoute] long id, [FromQuery] string t) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct) == null) + { + await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + var o = await ct.Report.SingleOrDefaultAsync(z => z.Id == id); + //turn into correct format and then send as file + if (o == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + var asText = Newtonsoft.Json.JsonConvert.SerializeObject( + o, + Newtonsoft.Json.Formatting.None, + new JsonSerializerSettings { ContractResolver = new Sockeye.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id" }) }); + var bytes = System.Text.Encoding.UTF8.GetBytes(asText); + var file = new FileContentResult(bytes, "application/octet-stream"); + file.FileDownloadName = Util.FileUtil.StringToSafeFileName(o.Name) + ".ayrt"; + return file; + } + + + + + /// + /// Upload Reprot template export file + /// Max 15mb total + /// + /// Accepted + [Authorize] + [HttpPost("upload")] + [DisableFormValueModelBinding] + [RequestSizeLimit(Sockeye.Util.ServerBootConfig.MAX_REPORT_TEMPLATE_UPLOAD_BYTES)] + public async Task UploadAsync() + { + //Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming + + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + + // SockTypeId attachToObject = null; + ApiUploadProcessor.ApiUploadedFilesResult uploadFormData = null; + try + { + if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Expected a multipart request, but got {Request.ContentType}")); + + //Save uploads to disk under temporary file names until we decide how to handle them + // uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext); + + //Save uploads to disk under temporary file names until we decide how to handle them + uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext); + if (!string.IsNullOrWhiteSpace(uploadFormData.Error)) + { + + //delete temp files + ApiUploadProcessor.DeleteTempUploadFile(uploadFormData); + //file too large is most likely issue so in that case return this localized properly + if (uploadFormData.Error.Contains("413")) + { + var TransId = UserTranslationIdFromContext.Id(HttpContext.Items); + return BadRequest(new ApiErrorResponse( + ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, + null, + String.Format(await TranslationBiz.GetTranslationStaticAsync("AyaFileFileTooLarge", TransId, ct), Sockeye.Util.FileUtil.GetBytesReadable(Sockeye.Util.ServerBootConfig.MAX_REPORT_TEMPLATE_UPLOAD_BYTES)))); + } + else//not too big, something else + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, uploadFormData.Error)); + } + + List FileData = new List(); + + if (!uploadFormData.FormFieldData.ContainsKey("FileData"))//only filedata is required + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Missing required FormFieldData value: FileData")); + } + + //fileData in JSON stringify format + FileData = Newtonsoft.Json.JsonConvert.DeserializeObject>(uploadFormData.FormFieldData["FileData"].ToString()); + + //Instantiate the business object handler + ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext); + + + //We have our files now can parse and insert into db + if (uploadFormData.UploadedFiles.Count > 0) + { + //deserialize each file and import + foreach (UploadedFileInfo a in uploadFormData.UploadedFiles) + { + JObject o = JObject.Parse(System.IO.File.ReadAllText(a.InitialUploadedPathName)); + if (!await biz.ImportAsync(o)) + { + //delete all the files temporarily uploaded and return bad request + ApiUploadProcessor.DeleteTempUploadFile(uploadFormData); + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + } + } + } + catch (System.IO.InvalidDataException ex) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, ex.Message)); + } + finally + { + //delete all the files temporarily uploaded and return bad request + + ApiUploadProcessor.DeleteTempUploadFile(uploadFormData); + } + + //Return the list of attachment ids and filenames + return Accepted(); + } + + // private static void DeleteTempUploadFile(ApiUploadProcessor.ApiUploadedFilesResult uploadFormData) + // { + // if (uploadFormData.UploadedFiles.Count > 0) + // { + // foreach (UploadedFileInfo a in uploadFormData.UploadedFiles) + // { + // System.IO.File.Delete(a.InitialUploadedPathName); + // } + // } + // } + //----------------------------------------- + + + + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/ReviewController.cs b/server/Controllers/ReviewController.cs new file mode 100644 index 0000000..944ddc1 --- /dev/null +++ b/server/Controllers/ReviewController.cs @@ -0,0 +1,168 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/review")] + [Produces("application/json")] + [Authorize] + public class ReviewController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public ReviewController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Create Review + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostReview([FromBody] Review newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ReviewBiz biz = ReviewBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + Review o = await biz.CreateAsync(newObject); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(ReviewController.GetReview), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + + /// + /// Get Review + /// + /// + /// Review + [HttpGet("{id}")] + public async Task GetReview([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ReviewBiz biz = ReviewBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(id); + if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + /// + /// Update Review + /// + /// + /// + [HttpPut] + public async Task PutReview([FromBody] Review updatedObject) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + ReviewBiz biz = ReviewBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var o = await biz.PutAsync(updatedObject); + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency, ReviewObjectViz=o.ReviewObjectViz })); ; + } + + /// + /// Delete Review + /// + /// + /// NoContent + [HttpDelete("{id}")] + public async Task DeleteReview([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + ReviewBiz biz = ReviewBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!await biz.DeleteAsync(id)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + + /// + /// Get Review schedule "more" info + /// + /// + /// Information to display in schedule when selected for more info + [HttpGet("sched-info/{id}")] + public async Task GetScheduleInfoView([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ReviewBiz biz = ReviewBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetAsync(id); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + await biz.PopulateVizFields(o); + return Ok(ApiOkResponse.Response(new + { + o.Name, + o.Notes, + o.ReviewDate, + o.CompletedDate, + o.CompletionNotes, + o.OverDue, + o.ReviewObjectViz, + o.SockType, + o.ObjectId + })); + } + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/ScheduleController.cs b/server/Controllers/ScheduleController.cs new file mode 100644 index 0000000..ea1cb7e --- /dev/null +++ b/server/Controllers/ScheduleController.cs @@ -0,0 +1,404 @@ +using System.Threading.Tasks; +using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using System.Collections.Generic; +using Sockeye.Util; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json.Linq; + +namespace Sockeye.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/schedule")] + [Produces("application/json")] + [Authorize] + public class ScheduleController : ControllerBase + { + private const string WHITE_HEXA = "#FFFFFFFF"; + private const string BLACK_HEXA = "#000000FF"; + private const string GRAY_NEUTRAL_HEXA = "#CACACAFF"; + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public ScheduleController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + + + /// + /// Get service management schedule for parameters specified + /// time zone UTC offset in minutes is required to be passed in + /// timestamps returned are in Unix Epoch milliseconds converted for local time display + /// + /// Service schedule parameters + /// From route path + /// + [HttpPost("svc")] + public async Task PostServiceSchedule([FromBody] ServiceScheduleParams p, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + List r = new List(); + + //Note: query will return records that fall within viewed range even if they start or end outside of it + //However in month view (only, rest are as is) we can see up to 6 days before or after the month so in the interest of filling those voids: + //Adjust query dates to encompass actual potential view range + DateTime ViewStart = p.Start; + DateTime ViewEnd = p.End; + //this covers the largest possible window that could display due to nearly a week of the last or next month showing + if (p.View == ScheduleView.Month) + { + ViewStart = p.Start.AddDays(-6); + ViewEnd = p.End.AddDays(6); + } + + + //Tags to Users + List Users = null; + + if (p.Tags.Count == 0) + Users = await ct.User.AsNoTracking() + .Where(x => x.Active == true && (x.UserType == UserType.ServiceContractor || x.UserType == UserType.Service)) + .OrderBy(x => x.Name) + .Select(x => new NameIdItem { Name = x.Name, Id = x.Id }) + .ToListAsync(); + else + { + Users = new List(); + //add users that match any of the tags, to match they must have at least one of the tags + //iterate available users + var availableUsers = await ct.User.AsNoTracking().Where(x => x.Active == true && (x.UserType == UserType.ServiceContractor || x.UserType == UserType.Service)).OrderBy(x => x.Name).Select(x => new { x.Name, x.Id, x.Tags }).ToListAsync(); + //if user has any of the tags in the list then include them + foreach (var u in availableUsers) + { + //any of the inclusive tags in contact tags? + if (p.Tags.Intersect(u.Tags).Any()) + Users.Add(new NameIdItem { Name = u.Name, Id = u.Id }); + } + } + List userIdList = Users.Select(x => x.Id as long?).ToList(); + userIdList.Add(null); + + var HasUnAssigned = r.Any(x => x.UserId == 0); + return Ok(ApiOkResponse.Response(new { items = r, users = Users, hasUnassigned = HasUnAssigned })); + } + + public class ServiceScheduleParams + { + [Required] + public ScheduleView View { get; set; } + [Required] + public DateTime Start { get; set; } + [Required] + public DateTime End { get; set; } + + [Required] + public List Tags { get; set; } + [Required] + public bool Dark { get; set; }//indicate if Client is set to dark mode or not, used for colorless types to display as black or white + } + + + + //############################################################### + //USER - svc-schedule-user + //############################################################### + + // /// + // /// Get User schedule for parameters specified + // /// This is called when drilling down into specific user from service schedule form and is not the personal schedule + // /// time zone UTC offset in minutes is required to be passed in + // /// timestamps returned are in Unix Epoch milliseconds converted for local time display + // /// + // /// User schedule parameters + // /// From route path + // /// + // [HttpPost("user")] + // public async Task PostUserSchedule([FromBody] PersonalScheduleParams p, ApiVersion apiVersion) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + + // List r = new List(); + + + + // //Note: query will return records that fall within viewed range even if they start or end outside of it + // //However in month view (only, rest are as is) we can see up to 6 days before or after the month so in the interest of filling those voids: + // //Adjust query dates to encompass actual potential view range + // DateTime ViewStart = p.Start; + // DateTime ViewEnd = p.End; + // //this covers the largest possible window that could display due to nearly a week of the last or next month showing + // if (p.View == ScheduleView.Month) + // { + // ViewStart = p.Start.AddDays(-6); + // ViewEnd = p.End.AddDays(6); + // } + + // long? actualUserId = p.UserId == 0 ? null : p.UserId; + + // return Ok(ApiOkResponse.Response(r)); + // } + + + //############################################################### + //PERSONAL + //############################################################### + + /// + /// Get personal schedule for parameters specified + /// time zone UTC offset in minutes is required to be passed in + /// timestamps returned are in Unix Epoch milliseconds converted for local time display + /// + /// Personal schedule parameters + /// From route path + /// + [HttpPost("personal")] + public async Task PostPersonalSchedule([FromBody] PersonalScheduleParams p, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + List r = new List(); + + + + + + + var UserId = UserIdFromContext.Id(HttpContext.Items); + var UType = UserTypeFromContext.Type(HttpContext.Items); + + + + //Note: query will return records that fall within viewed range even if they start or end outside of it + //However in month view (only, rest are as is) we can see up to 6 days before or after the month so in the interest of filling those voids: + //Adjust query dates to encompass actual potential view range + DateTime ViewStart = p.Start; + DateTime ViewEnd = p.End; + //this covers the largest possible window that could display due to nearly a week of the last or next month showing + if (p.View == ScheduleView.Month) + { + ViewStart = p.Start.AddDays(-6); + ViewEnd = p.End.AddDays(6); + } + + + + //REMINDERS + if (p.Reminders) + { + r.AddRange(await ct.Reminder.Where(x => x.UserId == UserId && ViewStart <= x.StopDate && x.StartDate <= ViewEnd).Select(x => MakeReminderSchedItem(x, p)).ToListAsync()); + } + + //REVIEWS + if (p.Reviews) + { + r.AddRange(await ct.Review.Where(x => x.UserId == UserId && ViewStart <= x.ReviewDate && x.ReviewDate <= ViewEnd).Select(x => MakeReviewSchedItem(x, p)).ToListAsync()); + } + return Ok(ApiOkResponse.Response(r)); + } + + /// + /// Adjust a schedule item's start / end timestamp + /// + /// Adjustment parameters parameters + /// From route path + /// Error or OK response + [HttpPost("adjust")] + public async Task AdjustSchedule([FromBody] ScheduleItemAdjustParams ad, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + switch (ad.Type) + { + + case SockType.Reminder: + { + ReminderBiz biz = ReminderBiz.GetBiz(ct, HttpContext); + var o = await biz.PutNewScheduleTimeAsync(ad); + if (o == false) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + } + break; + case SockType.Review: + { + ReviewBiz biz = ReviewBiz.GetBiz(ct, HttpContext); + var o = await biz.PutNewScheduleTimeAsync(ad); + if (o == false) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + } + break; + default: + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "Type", "Type not supported for adjustment")); + } + + //a-ok response + return Ok(ApiOkResponse.Response(true)); + } + + + + //#### UTILITY METHODS ############## + + + + private static PersonalScheduleListItem MakeReminderSchedItem(Reminder v, PersonalScheduleParams p) + { + var s = new PersonalScheduleListItem(); + s.Id = v.Id; + s.Color = v.Color; + s.TextColor = TextColor(v.Color); + s.Start = (DateTime)v.StartDate; + s.End = (DateTime)v.StopDate; + s.Type = SockType.Reminder; + s.Name = v.Name; + s.Editable = true;//personal reminders are always editable + return s; + } + + private static PersonalScheduleListItem MakeReviewSchedItem(Review v, PersonalScheduleParams p) + { + var s = new PersonalScheduleListItem(); + s.Id = v.Id; + s.Color = p.Dark ? WHITE_HEXA : BLACK_HEXA; + s.TextColor = p.Dark ? "black" : "white"; + s.Start = (DateTime)v.ReviewDate; + s.End = (DateTime)v.ReviewDate.AddMinutes(30);//just something to show in schedule as not supporting all day or unscheduled type stuff + s.Type = SockType.Review; + s.Name = v.Name; + s.Editable = v.CompletedDate == null;//not completed yet so can still be changed + return s; + } + + + + + + private static string TextColor(string hexcolor) + { + //Note: we use HEXA format which is 8 hex digits + //this here works even though it's considering as 6 digits because in hexA the last two + //digits are the opacity which this can ignore + if (string.IsNullOrWhiteSpace(hexcolor) || hexcolor.Length < 6) return GRAY_NEUTRAL_HEXA;//gray neutral + hexcolor = hexcolor.Replace("#", ""); + var r = StringUtil.HexToInt(hexcolor.Substring(0, 2)); + var g = StringUtil.HexToInt(hexcolor.Substring(2, 2)); + var b = StringUtil.HexToInt(hexcolor.Substring(4, 2)); + var yiq = (r * 299 + g * 587 + b * 114) / 1000; + //return yiq >= 128 ? WHITE_HEXA : BLACK_HEXA; + return yiq >= 128 ? "black" : "white";//<---NOTE: this MUST be a named color due to how the style is applied at client + } + + + + + + public enum ScheduleView : int + { + Day = 1, + Week = 2, + Month = 3, + Day4 = 4, + Category = 5 + } + + public class PersonalScheduleParams + { + [Required] + public ScheduleView View { get; set; } + [Required] + public DateTime Start { get; set; } + [Required] + public DateTime End { get; set; } + + [Required] + public bool Reviews { get; set; } + [Required] + public bool Reminders { get; set; } + [Required] + public bool Dark { get; set; }//indicate if Client is set to dark mode or not, used for colorless types to display as black or white + [Required] + public long UserId { get; set; }//required due to dual use from home-schedule and svc-schedule-user if it's a 0 zero then it's actually meant to be null not assigned userid + } + + public class PersonalScheduleListItem + { + //Never be null dates in here even though source records might be have null dates because they are not queried for and + //can't be displayed on a calendar anyway + //user can simply filter a data table by null dates to see them + //we shouldn't have allowed null dates in the first place in v7 but here we are :) + public DateTime Start { get; set; } + public DateTime End { get; set; } + public string Name { get; set; } + public string Color { get; set; } + public string TextColor { get; set; } + public SockType Type { get; set; } + public long Id { get; set; } + public bool Editable { get; set; } + } + + public class ServiceScheduleListItem + { + //Never be null dates in here even though source records might be have null dates because they are not queried for and + //can't be displayed on a calendar anyway + //user can simply filter a data table by null dates to see them + //we shouldn't have allowed null dates in the first place in v7 but here we are :) + public DateTime Start { get; set; } + public DateTime End { get; set; } + public string Name { get; set; } + public string Color { get; set; } + public string TextColor { get; set; } + public SockType Type { get; set; } + public long Id { get; set; } + public bool Editable { get; set; } + public long UserId { get; set; } + } + //------------ + + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/SearchController.cs b/server/Controllers/SearchController.cs new file mode 100644 index 0000000..4e701de --- /dev/null +++ b/server/Controllers/SearchController.cs @@ -0,0 +1,145 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + +namespace Sockeye.Api.Controllers +{ + + /// + /// Search + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/search")] + [Produces("application/json")] + [Authorize] + public class SearchController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public SearchController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + /// + /// Create search parameters + /// MaxResults defaults to 500 + /// MaxResults = 0 returns all results + /// + /// + /// SearchResult list + [HttpPost] + public async Task PostSearch([FromBody] Search.SearchRequestParameters searchParams) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + //Do the search + var SearchResults = await Search.DoSearchAsync( + ct, + UserTranslationIdFromContext.Id(HttpContext.Items), + UserRolesFromContext.Roles(HttpContext.Items), + UserIdFromContext.Id(HttpContext.Items), + searchParams + ); + + return Ok(ApiOkResponse.Response(SearchResults)); + } + + /// + /// Get search result summary + /// + /// + /// + /// + /// + /// A search result excerpt of object + [HttpGet("info/{sockType}/{id}")] + public async Task GetInfo([FromRoute] SockType sockType, [FromRoute] long id, [FromQuery] string phrase, [FromQuery] int max = 80) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, sockType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + if (id == 0) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "id", "id can't be zero")); + + + var res = await Search.GetInfoAsync(UserTranslationIdFromContext.Id(HttpContext.Items), + UserRolesFromContext.Roles(HttpContext.Items), UserIdFromContext.Id(HttpContext.Items), phrase, max, sockType, id, ct); + + return Ok(ApiOkResponse.Response(res)); + } + + + + // /// + // /// Get the top level ancestor of provided type and id + // /// (e.g. find the WorkOrder principle for a WorkOrderItemPart object descendant) + // /// + // /// + // /// + // /// A type and id of ancestor + // [HttpGet("ancestor/{sockType}/{id}")] + // public async Task GetAncestor([FromRoute] SockType sockType, [FromRoute] long id) + // { + // if (serverState.IsClosed) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + // //since this is for opening an entire object it's appropriate to check if they have any role first + // if (!Authorized.HasAnyRole(HttpContext.Items, sockType)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + // if (id == 0) + // return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, "id can't be zero")); + + // switch (sockType) + // { + + + + // default: + // return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, "Only types with ancestors are valid")); + + // } + + // } + + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/ServerMetricsController.cs b/server/Controllers/ServerMetricsController.cs new file mode 100644 index 0000000..7d5e84f --- /dev/null +++ b/server/Controllers/ServerMetricsController.cs @@ -0,0 +1,361 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; + +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +//using StackExchange.Profiling; + +namespace Sockeye.Api.Controllers +{ + + /// + /// Server metrics + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/server-metric")] + [Authorize] + public class ServerMetricsController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + private const int DEFAULT_MAX_RECORDS = 400; + private const long MB = (1024 * 1024); + private const long KB = 1024; + + + /// + /// ctor + /// + /// + /// + /// + public ServerMetricsController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + // #if (DEBUG) + // [HttpGet("collect")] + // [AllowAnonymous] + // public ActionResult GetCollect() + // { + // GC.Collect(); + // GC.WaitForPendingFinalizers(); + // GC.Collect(); + + // return Ok(); + // } + + + // [HttpGet("hammer")] + // [AllowAnonymous] + // public async Task GetHammer() + // { + // //test allocation and cleanup + // for (int x = 0; x < 100000; x++) + // { + // using (AyContext ct = ServiceProviderProvider.DBContext) + // var v=await ct.Widget.Where(z=>z.Serial<100).ToListAsync(); + // // int i = await ct.Database.ExecuteSqlRawAsync($"select * from aglobalbizsettings"); + // } + + // return Ok(); + // } + + + + // #endif + + /// + /// Get Memory and CPU server metrics for time period specified + /// + /// Start timestamp UTC + /// End timestamp UTC + /// Optional maximum records to return (downsampled). There is a 400 record maximum fixed default + /// Snapshot of metrics + [HttpGet("memcpu")] + public async Task GetMemCPUMetrics([FromQuery, Required] DateTime? tsStart, [FromQuery, Required] DateTime? tsEnd, [FromQuery] int? maxRecords) + { + //Note: the date and times are nullable and required so that the regular modelstate code kicks in to ensure they are present + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerMetrics)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + maxRecords ??= DEFAULT_MAX_RECORDS; + + List MinuteMetrics = new List(); + //touniversal is because the parameters are converted to local time here + //but then sent to the query as local time as well and not universal time which is what it should be + MinuteMetrics = await ct.MetricMM.AsNoTracking().Where(z => z.t >= ((DateTime)tsStart).ToUniversalTime() && z.t <= ((DateTime)tsEnd).ToUniversalTime()).OrderBy(z => z.t).ToListAsync(); + + + var dsCPU = MinuteMetrics.Select(z => new Tuple(z.t.ToOADate(), z.CPU)).ToList(); + dsCPU = Util.DataUtil.LargestTriangleThreeBuckets(dsCPU, (int)maxRecords) as List>; + + var dsAllocated = MinuteMetrics.Select(z => new Tuple(z.t.ToOADate(), z.Allocated)).ToList(); + dsAllocated = Util.DataUtil.LargestTriangleThreeBuckets(dsAllocated, (int)maxRecords) as List>; + + var dsWorkingSet = MinuteMetrics.Select(z => new Tuple(z.t.ToOADate(), z.WorkingSet)).ToList(); + dsWorkingSet = Util.DataUtil.LargestTriangleThreeBuckets(dsWorkingSet, (int)maxRecords) as List>; + + var dsPrivateBytes = MinuteMetrics.Select(z => new Tuple(z.t.ToOADate(), z.PrivateBytes)).ToList(); + dsPrivateBytes = Util.DataUtil.LargestTriangleThreeBuckets(dsPrivateBytes, (int)maxRecords) as List>; + + var ret = new + { + cpu = dsCPU.Select(z => new MetricDouble(DateTime.FromOADate(z.Item1), z.Item2)).ToArray(), + allocated = dsAllocated.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray(), + workingSet = dsWorkingSet.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray(), + privateBytes = dsPrivateBytes.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray() + }; + + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerMetrics, SockEvent.Retrieved), ct); + return Ok(ApiOkResponse.Response(ret)); + } + + + /// + /// Get storage server metrics for time period specified + /// + /// Start timestamp UTC + /// End timestamp UTC + /// Optional maximum records to return (downsampled). There is a 400 record maximum fixed default + /// Snapshot of metrics + [HttpGet("storage")] + public async Task GetStorageMetrics([FromQuery, Required] DateTime? tsStart, [FromQuery, Required] DateTime? tsEnd, [FromQuery] int? maxRecords) + { + //Note: the date and times are nullable and required so that the regular modelstate code kicks in to ensure they are present + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerMetrics)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + maxRecords ??= DEFAULT_MAX_RECORDS; + + List DailyMetrics = new List(); + //touniversal is because the parameters are converted to local time here + //but then sent to the query as local time as well and not universal time which is what it should be + DailyMetrics = await ct.MetricDD.AsNoTracking().Where(z => z.t >= ((DateTime)tsStart).ToUniversalTime() && z.t <= ((DateTime)tsEnd).ToUniversalTime()).OrderBy(z => z.t).ToListAsync(); + + + var dsAttachmentFileCount = DailyMetrics.Select(z => new Tuple(z.t.ToOADate(), z.AttachmentFileCount)).ToList(); + dsAttachmentFileCount = Util.DataUtil.LargestTriangleThreeBuckets(dsAttachmentFileCount, (int)maxRecords) as List>; + + var dsAttachmentFileSize = DailyMetrics.Select(z => new Tuple(z.t.ToOADate(), z.AttachmentFileSize)).ToList(); + dsAttachmentFileSize = Util.DataUtil.LargestTriangleThreeBuckets(dsAttachmentFileSize, (int)maxRecords) as List>; + + var dsAttachmentFilesAvailableSpace = DailyMetrics.Select(z => new Tuple(z.t.ToOADate(), z.AttachmentFilesAvailableSpace)).ToList(); + dsAttachmentFilesAvailableSpace = Util.DataUtil.LargestTriangleThreeBuckets(dsAttachmentFilesAvailableSpace, (int)maxRecords) as List>; + + var dsUtilityFileCount = DailyMetrics.Select(z => new Tuple(z.t.ToOADate(), z.UtilityFileCount)).ToList(); + dsUtilityFileCount = Util.DataUtil.LargestTriangleThreeBuckets(dsUtilityFileCount, (int)maxRecords) as List>; + + var dsUtilityFileSize = DailyMetrics.Select(z => new Tuple(z.t.ToOADate(), z.UtilityFileSize)).ToList(); + dsUtilityFileSize = Util.DataUtil.LargestTriangleThreeBuckets(dsUtilityFileSize, (int)maxRecords) as List>; + + var dsUtilityFilesAvailableSpace = DailyMetrics.Select(z => new Tuple(z.t.ToOADate(), z.UtilityFilesAvailableSpace)).ToList(); + dsUtilityFilesAvailableSpace = Util.DataUtil.LargestTriangleThreeBuckets(dsUtilityFilesAvailableSpace, (int)maxRecords) as List>; + + var ret = new + { + attachmentFileCount = dsAttachmentFileCount.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2)).ToArray(), + attachmentFileSize = dsAttachmentFileSize.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray(), + attachmentFilesAvailableSpace = dsAttachmentFilesAvailableSpace.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray(), + utilityFileCount = dsUtilityFileCount.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2)).ToArray(), + utilityFileSize = dsUtilityFileSize.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray(), + utilityFilesAvailableSpace = dsUtilityFilesAvailableSpace.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray() + + }; + + + + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerMetrics, SockEvent.Retrieved), ct); + return Ok(ApiOkResponse.Response(ret)); + } + + + + /// + /// Get database related metrics for time period specified + /// + /// Start timestamp UTC + /// End timestamp UTC + /// Optional maximum records to return (downsampled). There is a 400 record maximum fixed default + /// Snapshot of metrics + [HttpGet("db")] + public async Task GetDBMetrics([FromQuery, Required] DateTime? tsStart, [FromQuery, Required] DateTime? tsEnd, [FromQuery] int? maxRecords) + { + //Note: the date and times are nullable and required so that the regular modelstate code kicks in to ensure they are present + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerMetrics)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + maxRecords ??= DEFAULT_MAX_RECORDS; + + + //############ DB SIZE TIME SERIES MB + List DBMetrics = new List(); + //touniversal is because the parameters are converted to local time here + //but then sent to the query as local time as well and not universal time which is what it should be + DBMetrics = await ct.MetricDD.AsNoTracking().Where(z => z.t >= ((DateTime)tsStart).ToUniversalTime() && z.t <= ((DateTime)tsEnd).ToUniversalTime()).OrderBy(z => z.t).ToListAsync(); + var dsDBTotalSize = DBMetrics.Select(z => new Tuple(z.t.ToOADate(), z.DBTotalSize)).ToList(); + dsDBTotalSize = Util.DataUtil.LargestTriangleThreeBuckets(dsDBTotalSize, (int)maxRecords) as List>; + + + + //############# TOP TABLES KB + int AllTableCount=0; + List TopTables = new List(); + using (var command = ct.Database.GetDbConnection().CreateCommand()) + { + /* + SELECT table_name, pg_total_relation_size(table_name) AS total_size +FROM ( SELECT ( table_schema || '.' || table_name ) +AS table_name FROM information_schema.tables +where table_schema not in('pg_catalog','information_schema')) +AS all_tables +ORDER BY total_size DESC + */ + + var cmd = @"SELECT + table_name, + pg_total_relation_size(table_name) AS total_size + FROM ( + SELECT ('""' || table_schema || '"".""' || table_name || '""') AS table_name + FROM information_schema.tables + where table_schema not in('pg_catalog','information_schema') + ) AS all_tables + ORDER BY total_size DESC"; + command.CommandText = cmd; + + ct.Database.OpenConnection(); + using (var dr = command.ExecuteReader()) + { + if (dr.HasRows) + { + while (dr.Read()) + { + AllTableCount++; + long tableSize = dr.GetInt64(1); + string tableName = dr.GetString(0); + tableName = tableName.Replace("\"", "").Replace("public.a", ""); + if (tableSize > 0) + { + tableSize = tableSize / KB; + } + TopTables.Add(new MetricNameLongValue() { name = tableName, value = tableSize }); + } + } + ct.Database.CloseConnection(); + } + } + + //trim out tables less than 1kb (the math above sets anything less than 1kb to zero) + TopTables = TopTables.Where(z => z.value > 48).ToList();//NOTE: empty tables seem to all be 48kb so that's why this is here + int OtherTableCount=AllTableCount-TopTables.Count(); + + long DBTotalSize = 0; + using (var command = ct.Database.GetDbConnection().CreateCommand()) + { + command.CommandText = "select pg_database_size(current_database());"; + ct.Database.OpenConnection(); + using (var dr = command.ExecuteReader()) + { + if (dr.HasRows) + { + DBTotalSize = dr.Read() ? (dr.GetInt64(0) / KB) : 0; + } + ct.Database.CloseConnection(); + } + } + + long ttSize = 0; + foreach (MetricNameLongValue tt in TopTables) + { + ttSize += tt.value; + } + // TopTables.Add(new MetricNameLongValue() { name = $"{OtherTableCount} others", value = DBTotalSize - ttSize }); + + var ret = new + { + TopTables = TopTables.OrderByDescending(z => z.value).ToList(), + totalSize = dsDBTotalSize.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray() + }; + + + + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerMetrics, SockEvent.Retrieved), ct); + return Ok(ApiOkResponse.Response(ret)); + } + + + + //------------ + public class MetricLong + { + public DateTime x { get; set; } + public long y { get; set; } + public MetricLong(DateTime px, double py) + { + x = px; + y = (long)py; + + } + } + + public class MetricInt + { + public DateTime x { get; set; } + public int y { get; set; } + public MetricInt(DateTime px, double py) + { + x = px; + y = (int)py; + + } + } + + public class MetricDouble + { + public DateTime x { get; set; } + public double y { get; set; } + public MetricDouble(DateTime px, double py) + { + x = px; + y = py; + + } + } + + public class MetricNameLongValue + { + public string name { get; set; } + public long value { get; set; } + } + //---------- + + } +} \ No newline at end of file diff --git a/server/Controllers/ServerStateController.cs b/server/Controllers/ServerStateController.cs new file mode 100644 index 0000000..3e10091 --- /dev/null +++ b/server/Controllers/ServerStateController.cs @@ -0,0 +1,363 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using Sockeye.Models; +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Hosting; +using Sockeye.Util; +using System.Linq; +using System.IO; + + +namespace Sockeye.Api.Controllers +{ + + /// + /// Server state controller + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/server-state")] + [Produces("application/json")] + [Authorize] + public class ServerStateController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + private readonly IHostApplicationLifetime _appLifetime; + + + public ServerStateController(ILogger logger, ApiServerState apiServerState, AyContext dbcontext, IHostApplicationLifetime appLifetime) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + _appLifetime = appLifetime; + } + + + /// + /// Get server state + /// + /// Current server state (Closed, MigrateMode, OpsOnly, Open) + [HttpGet] + public ActionResult Get() + { + //any logged in user can get the state + return Ok(ApiOkResponse.Response(new ServerStateModel() { ServerState = serverState.GetState().ToString(), Reason = serverState.Reason })); + } + + + /// + /// Set server state + /// Valid parameters: + /// One of "OpsOnly", "MigrateMode" or "Open" + /// + /// {"serverState":"Open"} + /// New server state + [HttpPost] + public async Task PostServerState([FromBody] ServerStateModel state) + { + if (serverState.IsClosed)//no state change allowed when closed + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.ServerState)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + ApiServerState.ServerState desiredState; + if (!Enum.TryParse(state.ServerState, true, out desiredState)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, "Invalid state - must be one of \"OpsOnly\", \"MigrateMode\" or \"Open\"")); + + //don't allow a server to be set to closed, that's for internal ops only + if (desiredState == ApiServerState.ServerState.Closed) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, "Invalid state - must be one of \"OpsOnly\", \"MigrateMode\" or \"Open\"")); + + var TransId = UserTranslationIdFromContext.Id(HttpContext.Items); + + log.LogInformation($"ServerState change request by user {UserNameFromContext.Name(HttpContext.Items)} from current state of \"{serverState.GetState().ToString()}\" to \"{desiredState.ToString()} {state.Reason}\""); + + + + //Add a message if user didn't enter one so other users know why they can't login + if (string.IsNullOrWhiteSpace(state.Reason)) + { + switch (desiredState) + { + case ApiServerState.ServerState.Open: + break; + case ApiServerState.ServerState.OpsOnly: + state.Reason += await TranslationBiz.GetTranslationStaticAsync("ServerStateLoginRestricted", TransId, ct) + ":\n" + await TranslationBiz.GetTranslationStaticAsync("ServerStateOps", TransId, ct); + break; + case ApiServerState.ServerState.Closed: + state.Reason += await TranslationBiz.GetTranslationStaticAsync("ServerStateLoginRestricted", TransId, ct) + ":\n" + await TranslationBiz.GetTranslationStaticAsync("ErrorAPI2000", TransId, ct); + break; + } + } + else + { + if (desiredState != ApiServerState.ServerState.OpsOnly) + state.Reason = await TranslationBiz.GetTranslationStaticAsync("ServerStateLoginRestricted", TransId, ct) + ":\n" + state.Reason; + } + + serverState.SetState(desiredState, state.Reason); + + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerState, SockEvent.ServerStateChange, $"{state.ServerState}-{state.Reason}"), ct); + + return Ok(ApiOkResponse.Response(new ServerStateModel() { ServerState = serverState.GetState().ToString(), Reason = serverState.Reason })); + + } + + + /// + /// Parameter object + /// + public class ServerStateModel + { + /// + /// "OpsOnly", "MigrateMode" or "Open" + /// + /// + [Required] + public string ServerState { get; set; } + + /// + /// Reason for server state + /// + /// + public string Reason { get; set; } + } + + + + /// + /// Controlled server shut down + /// + /// Accepted + [HttpPost("shutdown")] + public ActionResult PostShutdown() + { + if (serverState.IsClosed)//no state change allowed when closed + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.ServerState)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + log.LogInformation($"### Server shut down requested by user {UserNameFromContext.Name(HttpContext.Items)}, triggering shut down event now..."); + + _appLifetime.StopApplication();//Note: this should also trigger graceful shutdown of JOBS as well + return Accepted(); + + } + + + + + /// + /// Get server configuration + /// + /// Active server configuration + [HttpGet("active-configuration")] + public ActionResult GetActiveConfiguration() + { + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerState)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + + return Ok(ApiOkResponse.Response( + new + { + SOCKEYE_DEFAULT_TRANSLATION = ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION, + SOCKEYE_USE_URLS = ServerBootConfig.SOCKEYE_USE_URLS, + SOCKEYE_DB_CONNECTION = DbUtil.PasswordRedactedConnectionString(ServerBootConfig.SOCKEYE_DB_CONNECTION), + SOCKEYE_REPORT_RENDERING_TIMEOUT = ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT, + SOCKEYE_ATTACHMENT_FILES_PATH = ServerBootConfig.SOCKEYE_ATTACHMENT_FILES_PATH, + SOCKEYE_BACKUP_FILES_PATH = ServerBootConfig.SOCKEYE_BACKUP_FILES_PATH, + SOCKEYE_TEMP_FILES_PATH = ServerBootConfig.SOCKEYE_TEMP_FILES_PATH, + SOCKEYE_BACKUP_PG_DUMP_PATH = ServerBootConfig.SOCKEYE_BACKUP_PG_DUMP_PATH, + SOCKEYE_LOG_PATH = ServerBootConfig.SOCKEYE_LOG_PATH, + SOCKEYE_LOG_LEVEL = ServerBootConfig.SOCKEYE_LOG_LEVEL, + SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG = ServerBootConfig.SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG, + serverinfo = ServerBootConfig.BOOT_DIAGNOSTIC_INFO, + dbserverinfo = ServerBootConfig.DBSERVER_DIAGNOSTIC_INFO + })); + } + + // /// + // /// Download all logs and server configuration setting in one zip package for technical support purposes + // /// + // /// download token + // /// A single zip file + // [AllowAnonymous] + // [HttpGet("tech-support-info")] + // public async Task DownloadSupportInfo([FromQuery] string t) + // { + // //NOTE: this route deliberately open even when server closed as a troubleshooting measure + + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + + // var user = await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct); + // if (user == null) + // { + // await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection + // return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + // } + + // if (!Authorized.HasReadFullRole(user.Roles, SockType.LogFile) || !Authorized.HasReadFullRole(user.Roles, SockType.ServerState)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + + + // System.Text.StringBuilder sbRet = new System.Text.StringBuilder(); + // sbRet.AppendLine("#########################################################"); + // sbRet.AppendLine("SERVER CONFIGURATION"); + // sbRet.AppendLine($"SOCKEYE_USE_URLS: {ServerBootConfig.SOCKEYE_USE_URLS}"); + // sbRet.AppendLine($"SOCKEYE_DB_CONNECTION: {DbUtil.PasswordRedactedConnectionString(ServerBootConfig.SOCKEYE_DB_CONNECTION)}"); + // sbRet.AppendLine($"SOCKEYE_REPORT_RENDERING_TIMEOUT: {ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT}"); + // sbRet.AppendLine($"SOCKEYE_DEFAULT_TRANSLATION: {ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION}"); + // sbRet.AppendLine($"SOCKEYE_ATTACHMENT_FILES_PATH: {ServerBootConfig.SOCKEYE_ATTACHMENT_FILES_PATH}"); + // sbRet.AppendLine($"SOCKEYE_BACKUP_FILES_PATH: {ServerBootConfig.SOCKEYE_BACKUP_FILES_PATH}"); + // sbRet.AppendLine($"SOCKEYE_TEMP_FILES_PATH: {ServerBootConfig.SOCKEYE_TEMP_FILES_PATH}"); + // sbRet.AppendLine($"SOCKEYE_BACKUP_PG_DUMP_PATH: {ServerBootConfig.SOCKEYE_BACKUP_PG_DUMP_PATH}"); + // sbRet.AppendLine($"SOCKEYE_LOG_PATH: {ServerBootConfig.SOCKEYE_LOG_PATH}"); + // sbRet.AppendLine($"SOCKEYE_LOG_LEVEL: {ServerBootConfig.SOCKEYE_LOG_LEVEL}"); + // sbRet.AppendLine($"SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG: {ServerBootConfig.SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG}"); + + // sbRet.AppendLine("#########################################################"); + // sbRet.AppendLine("SERVER DIAGNOSTICS"); + // foreach (var di in ServerBootConfig.BOOT_DIAGNOSTIC_INFO) + // { + // sbRet.AppendLine($"{di.Key}: {di.Value}"); + // } + + // sbRet.AppendLine("#########################################################"); + // sbRet.AppendLine("DB SERVER DIAGNOSTICS"); + // foreach (var di in ServerBootConfig.DBSERVER_DIAGNOSTIC_INFO) + // { + // sbRet.AppendLine($"{di.Key}: {di.Value}"); + // } + + + + + // //System.IO.Compression.ZipFile.CreateFromDirectory(ServerBootConfig.SOCKEYE_LOG_PATH, AttachmentsBackupFile); + + + // var files = System.IO.Directory.GetFiles(ServerBootConfig.SOCKEYE_LOG_PATH, "*.txt").OrderBy(z => new FileInfo(z).LastWriteTime).Select(z => System.IO.Path.GetFileName(z)).ToList(); + + // //Directory.GetFiles(@“C:\RPA\Vector Reports”,“IW28*”).OrderByAscending(d => new FileInfo(d).GetLastWriteTime) + + // sbRet.AppendLine("#########################################################"); + // sbRet.AppendLine($"SERVER LOGS"); + + // foreach (string logfilename in files) + // { + // var logFilePath = System.IO.Path.Combine(ServerBootConfig.SOCKEYE_LOG_PATH, logfilename); + // FileStreamOptions fso = new FileStreamOptions(); + // fso.Access = FileAccess.Read; + // fso.Mode = FileMode.Open; + // fso.Share = FileShare.ReadWrite; + + // using (StreamReader sr = new StreamReader(logFilePath, fso)) + // { + // sbRet.AppendLine(sr.ReadToEnd()); + // } + + // } + // sbRet.AppendLine("#########################################################"); + // return Content(sbRet.ToString()); + + + // } + + + /// + /// Fetch all logs and server configuration setting for technical support purposes + /// + /// text result + [HttpGet("tech-support-info")] + public ActionResult GetTechSupportInfo() + { + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.LogFile) || !Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerState)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + + System.Text.StringBuilder sbRet = new System.Text.StringBuilder(); + sbRet.AppendLine("#########################################################"); + sbRet.AppendLine("SERVER CONFIGURATION"); + sbRet.AppendLine($"SOCKEYE_USE_URLS: {ServerBootConfig.SOCKEYE_USE_URLS}"); + sbRet.AppendLine($"SOCKEYE_DB_CONNECTION: {DbUtil.PasswordRedactedConnectionString(ServerBootConfig.SOCKEYE_DB_CONNECTION)}"); + sbRet.AppendLine($"SOCKEYE_REPORT_RENDERING_TIMEOUT: {ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT}"); + sbRet.AppendLine($"SOCKEYE_DEFAULT_TRANSLATION: {ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION}"); + sbRet.AppendLine($"SOCKEYE_ATTACHMENT_FILES_PATH: {ServerBootConfig.SOCKEYE_ATTACHMENT_FILES_PATH}"); + sbRet.AppendLine($"SOCKEYE_BACKUP_FILES_PATH: {ServerBootConfig.SOCKEYE_BACKUP_FILES_PATH}"); + sbRet.AppendLine($"SOCKEYE_TEMP_FILES_PATH: {ServerBootConfig.SOCKEYE_TEMP_FILES_PATH}"); + sbRet.AppendLine($"SOCKEYE_BACKUP_PG_DUMP_PATH: {ServerBootConfig.SOCKEYE_BACKUP_PG_DUMP_PATH}"); + sbRet.AppendLine($"SOCKEYE_LOG_PATH: {ServerBootConfig.SOCKEYE_LOG_PATH}"); + sbRet.AppendLine($"SOCKEYE_LOG_LEVEL: {ServerBootConfig.SOCKEYE_LOG_LEVEL}"); + sbRet.AppendLine($"SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG: {ServerBootConfig.SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG}"); + + sbRet.AppendLine("#########################################################"); + sbRet.AppendLine("SERVER DIAGNOSTICS"); + sbRet.AppendLine($"Version: {SockeyeVersion.FullNameAndVersion}"); + sbRet.AppendLine($"Schema level: {AySchema.currentSchema}"); + sbRet.AppendLine($"Server current time: {DateUtil.ServerDateTimeString(System.DateTime.UtcNow)}"); + + + foreach (var di in ServerBootConfig.BOOT_DIAGNOSTIC_INFO) + { + sbRet.AppendLine($"{di.Key}: {di.Value}"); + } + + sbRet.AppendLine("#########################################################"); + sbRet.AppendLine("DB SERVER DIAGNOSTICS"); + foreach (var di in ServerBootConfig.DBSERVER_DIAGNOSTIC_INFO) + { + sbRet.AppendLine($"{di.Key}: {di.Value}"); + } + + + var files = System.IO.Directory.GetFiles(ServerBootConfig.SOCKEYE_LOG_PATH, "*.txt").OrderBy(z => new FileInfo(z).LastWriteTime).Select(z => System.IO.Path.GetFileName(z)).ToList(); + + + sbRet.AppendLine("#########################################################"); + sbRet.AppendLine($"ALL SERVER LOGS"); + + foreach (string logfilename in files) + { + var logFilePath = System.IO.Path.Combine(ServerBootConfig.SOCKEYE_LOG_PATH, logfilename); + FileStreamOptions fso = new FileStreamOptions(); + fso.Access = FileAccess.Read; + fso.Mode = FileMode.Open; + fso.Share = FileShare.ReadWrite; + + using (StreamReader sr = new StreamReader(logFilePath, fso)) + { + sbRet.AppendLine(sr.ReadToEnd()); + } + + } + sbRet.AppendLine("#########################################################"); + + return Ok(ApiOkResponse.Response(sbRet.ToString())); + + + } + + //------------ + + } +} \ No newline at end of file diff --git a/server/Controllers/TagController.cs b/server/Controllers/TagController.cs new file mode 100644 index 0000000..9e8e05d --- /dev/null +++ b/server/Controllers/TagController.cs @@ -0,0 +1,370 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using Newtonsoft.Json.Linq; + + + +namespace Sockeye.Api.Controllers +{ + + /// + /// + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/tag-list")] + [Produces("application/json")] + [Authorize] + public class TagController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public TagController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + /// + /// Get tag list + /// + /// The query to filter the returned list by + /// Filtered list (maximum 25 items are returned for any query) + [HttpGet("list")] + public async Task GetList([FromQuery] string query) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + return Ok(ApiOkResponse.Response(await TagBiz.TagListFilteredAsync(ct, query))); + } + + /// + /// Get tag cloud list + /// + /// The query to filter the returned list by + /// List + [HttpGet("cloudlist")] + public async Task GetCloudList([FromQuery] string query) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + return Ok(ApiOkResponse.Response(await TagBiz.CloudListFilteredAsync(ct, query))); + } + + + + + ///////////////////////////////////////////////////////////// + //BATCH OPS + // + // + + /// + /// Batch add tags to list of object id's specified + /// + /// + /// + /// Job Id + [HttpPost("batch-add/{tag}")] + public async Task BatchAdd([FromRoute] string tag, [FromBody] DataListSelectedRequest selectedRequest) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (!selectedRequest.SockType.HasAttribute(typeof(CoreBizObjectAttribute))) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Not a taggable object type")); + + if (!Authorized.HasModifyRole(HttpContext.Items, selectedRequest.SockType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + tag = TagBiz.NormalizeTag(tag); + if (string.IsNullOrWhiteSpace(tag)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "tag required")); + + //Rehydrate id list if necessary + if (selectedRequest.SelectedRowIds.Length == 0) + selectedRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList( + selectedRequest, + ct, + UserRolesFromContext.Roles(HttpContext.Items), + log, + UserIdFromContext.Id(HttpContext.Items), + UserTranslationIdFromContext.Id(HttpContext.Items)); + + var JobName = $"LT:BatchJob LT:Add LT:Tag \"{tag}\" LT:{selectedRequest.SockType} ({selectedRequest.SelectedRowIds.LongLength}) LT:User {UserNameFromContext.Name(HttpContext.Items)}"; + JObject o = JObject.FromObject(new + { + idList = selectedRequest.SelectedRowIds, + tag = tag + }); + + OpsJob j = new OpsJob(); + j.Name = JobName; + j.SockType = selectedRequest.SockType; + j.JobType = JobType.BatchCoreObjectOperation; + j.SubType = JobSubType.TagAdd; + j.Exclusive = false; + j.JobInfo = o.ToString(); + await JobsBiz.AddJobAsync(j); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct); + return Accepted(new { JobId = j.GId }); + } + + /// + /// Batch add tags to all objects of type specified + /// + /// + /// + /// Job Id + [HttpPost("batch-add-any/{sockType}/{tag}")] + public async Task BatchAddAny([FromRoute] SockType sockType, [FromRoute] string tag) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + if (!sockType.HasAttribute(typeof(CoreBizObjectAttribute))) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Not a taggable object type")); + if (!Authorized.HasModifyRole(HttpContext.Items, sockType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + tag = TagBiz.NormalizeTag(tag); + if (string.IsNullOrWhiteSpace(tag)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "tag")); + + var JobName = $"LT:BatchJob LT:Add LT:Tag \"{tag}\" LT:{sockType.ToString()} (LT:GridRowFilterDropDownAllItem) LT:User {UserNameFromContext.Name(HttpContext.Items)}"; + JObject o = JObject.FromObject(new + { + tag = tag + }); + + OpsJob j = new OpsJob(); + j.Name = JobName; + j.SockType = sockType; + j.JobType = JobType.BatchCoreObjectOperation; + j.SubType = JobSubType.TagAddAny; + j.Exclusive = false; + j.JobInfo = o.ToString(); + await JobsBiz.AddJobAsync(j); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct); + return Accepted(new { JobId = j.GId }); + } + + /// + /// Batch remove tags to list of object id's specified + /// + /// + /// + /// Job Id + [HttpPost("batch-remove/{tag}")] + public async Task BatchRemove([FromRoute] string tag, [FromBody] DataListSelectedRequest selectedRequest) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (!selectedRequest.SockType.HasAttribute(typeof(CoreBizObjectAttribute))) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Not a taggable object type")); + if (!Authorized.HasModifyRole(HttpContext.Items, selectedRequest.SockType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + tag = TagBiz.NormalizeTag(tag); + if (string.IsNullOrWhiteSpace(tag)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "tag")); + + //Rehydrate id list if necessary + if (selectedRequest.SelectedRowIds.Length == 0) + selectedRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList( + selectedRequest, + ct, + UserRolesFromContext.Roles(HttpContext.Items), + log, + UserIdFromContext.Id(HttpContext.Items), + UserTranslationIdFromContext.Id(HttpContext.Items)); + + var JobName = $"LT:BatchJob LT:Remove LT:Tag \"{tag}\" LT:{selectedRequest.SockType} ({selectedRequest.SelectedRowIds.LongLength}) LT:User {UserNameFromContext.Name(HttpContext.Items)}"; + JObject o = JObject.FromObject(new + { + idList = selectedRequest.SelectedRowIds, + tag = tag + }); + + OpsJob j = new OpsJob(); + j.Name = JobName; + j.SockType = selectedRequest.SockType; + j.JobType = JobType.BatchCoreObjectOperation; + j.SubType = JobSubType.TagRemove; + j.Exclusive = false; + j.JobInfo = o.ToString(); + await JobsBiz.AddJobAsync(j); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct); + return Accepted(new { JobId = j.GId }); + } + + + /// + /// Batch remove tags to all objects of type specified + /// + /// + /// + /// Job Id + [HttpPost("batch-remove-any/{sockType}/{tag}")] + public async Task BatchRemoveAny([FromRoute] SockType sockType, [FromRoute] string tag) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + if (!sockType.HasAttribute(typeof(CoreBizObjectAttribute))) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Not a taggable object type")); + if (!Authorized.HasModifyRole(HttpContext.Items, sockType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + tag = TagBiz.NormalizeTag(tag); + if (string.IsNullOrWhiteSpace(tag)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "tag")); + + var JobName = $"LT:BatchJob LT:Remove LT:Tag \"{tag}\" LT:{sockType.ToString()} (LT:GridRowFilterDropDownAllItem) LT:User {UserNameFromContext.Name(HttpContext.Items)}"; + JObject o = JObject.FromObject(new + { + tag = tag + }); + + OpsJob j = new OpsJob(); + j.Name = JobName; + j.SockType = sockType; + j.SubType = JobSubType.TagRemoveAny; + j.JobType = JobType.BatchCoreObjectOperation; + j.Exclusive = false; + j.JobInfo = o.ToString(); + await JobsBiz.AddJobAsync(j); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct); + return Accepted(new { JobId = j.GId }); + } + + + /// + /// Batch replace tags to list of object id's specified + /// + /// + /// + /// + /// Job Id + [HttpPost("batch-replace/{fromTag}")] + public async Task BatchReplace([FromRoute] string fromTag, [FromQuery] string toTag, [FromBody] DataListSelectedRequest selectedRequest) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (!selectedRequest.SockType.HasAttribute(typeof(CoreBizObjectAttribute))) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Not a taggable object type")); + if (!Authorized.HasModifyRole(HttpContext.Items, selectedRequest.SockType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + fromTag = TagBiz.NormalizeTag(fromTag); + if (string.IsNullOrWhiteSpace(fromTag)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "fromTag")); + toTag = TagBiz.NormalizeTag(toTag); + if (string.IsNullOrWhiteSpace(toTag)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "toTag")); + + //Rehydrate id list if necessary + if (selectedRequest.SelectedRowIds.Length == 0) + selectedRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList( + selectedRequest, + ct, + UserRolesFromContext.Roles(HttpContext.Items), + log, + UserIdFromContext.Id(HttpContext.Items), + UserTranslationIdFromContext.Id(HttpContext.Items)); + + var JobName = $"LT:BatchJob LT:Replace LT:Tag \"{fromTag}\" -> LT:Tag \"{toTag}\" LT:{selectedRequest.SockType} ({selectedRequest.SelectedRowIds.LongLength}) LT:User {UserNameFromContext.Name(HttpContext.Items)}"; + JObject o = JObject.FromObject(new + { + idList = selectedRequest.SelectedRowIds, + tag = fromTag, + toTag = toTag + }); + + OpsJob j = new OpsJob(); + j.SockType = selectedRequest.SockType; + j.Name = JobName; + j.JobType = JobType.BatchCoreObjectOperation; + j.SubType = JobSubType.TagReplace; + j.Exclusive = false; + j.JobInfo = o.ToString(); + await JobsBiz.AddJobAsync(j); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct); + return Accepted(new { JobId = j.GId }); + } + + /// + /// Batch replace tags to all objects of type specified + /// + /// + /// + /// + /// Job Id + [HttpPost("batch-replace-any/{sockType}/{fromTag}")] + public async Task BatchReplaceAny([FromRoute] SockType sockType, [FromRoute] string fromTag, [FromQuery] string toTag) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + if (!sockType.HasAttribute(typeof(CoreBizObjectAttribute))) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Not a taggable object type")); + if (!Authorized.HasModifyRole(HttpContext.Items, sockType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + fromTag = TagBiz.NormalizeTag(fromTag); + if (string.IsNullOrWhiteSpace(fromTag)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "fromTag")); + + toTag = TagBiz.NormalizeTag(toTag); + if (string.IsNullOrWhiteSpace(toTag)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "toTag")); + + var JobName = $"LT:BatchJob LT:Replace LT:Tag \"{fromTag}\" -> LT:Tag \"{toTag}\" LT:{sockType.ToString()} (LT:GridRowFilterDropDownAllItem) LT:User {UserNameFromContext.Name(HttpContext.Items)}"; + JObject o = JObject.FromObject(new + { + tag = fromTag, + toTag = toTag + }); + + OpsJob j = new OpsJob(); + j.Name = JobName; + j.SockType = sockType; + j.JobType = JobType.BatchCoreObjectOperation; + j.SubType = JobSubType.TagReplaceAny; + j.Exclusive = false; + j.JobInfo = o.ToString(); + await JobsBiz.AddJobAsync(j); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct); + return Accepted(new { JobId = j.GId }); + } + + + }//eoc +}//ens \ No newline at end of file diff --git a/server/Controllers/TranslationController.cs b/server/Controllers/TranslationController.cs new file mode 100644 index 0000000..0d70e19 --- /dev/null +++ b/server/Controllers/TranslationController.cs @@ -0,0 +1,569 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + + + +namespace Sockeye.Api.Controllers +{ + //DOCUMENTATING THE API + //https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/recommended-tags-for-documentation-comments + //https://github.com/domaindrivendev/Swashbuckle.AspNetCore#include-descriptions-from-xml-comments + + /// + /// Translation controller + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/translation")] + [Produces("application/json")] + [Authorize] + public class TranslationController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public TranslationController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + + /// + /// Get Translation all values + /// + /// + /// A single Translation and it's values + [HttpGet("{id}")] + public async Task GetTranslation([FromRoute] long id) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + //Instantiate the business object handler + TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext); + + var o = await biz.GetAsync(id); + + if (o == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + return Ok(ApiOkResponse.Response(o)); + } + + + + /// + /// Update Translation + /// + /// + /// + /// + [HttpPut] + public async Task PutTranslation([FromBody] Translation updatedObject) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var o = await biz.PutAsync(updatedObject); + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ; + } + + + + /// + /// Get Translations list + /// + /// List in alphabetical order of all Translations + [HttpGet("list")] + public async Task TranslationList() + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //Instantiate the business object handler + TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext); + + var l = await biz.GetTranslationListAsync(); + return Ok(ApiOkResponse.Response(l)); + } + + +#if (DEBUG) + /// + /// Get a coverage report of translation keys used versus unused + /// + /// Report of all unique translation keys requested since last server reboot + [HttpGet("translationkeycoverage")] + public async Task TranslationKeyCoverage() + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //Instantiate the business object handler + TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext); + + var l = await biz.TranslationKeyCoverageAsync(); + return Ok(ApiOkResponse.Response(l)); + } +#endif + + + /// + /// Get subset of translation values + /// + /// List of translation key strings + /// A key value array of translation text values + [HttpPost("subset")] + public async Task SubSet([FromBody] List inObj) + { + if (serverState.IsClosed) + { + //Exception for SuperUser account to handle licensing issues + if (UserIdFromContext.Id(HttpContext.Items) != 1) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + } + + //Instantiate the business object handler + + //Instantiate the business object handler + TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext); + + var l = await biz.GetSubsetAsync(inObj); + return Ok(ApiOkResponse.Response(l)); + } + + /// + /// Get subset of translation values for specific translation Id + /// + /// + /// List of translation key strings + /// A key value array of translation text values + [HttpPost("subset/{id}")] + [AllowAnonymous] + public async Task SubSet([FromRoute] long id, [FromBody] List inObj) + { + //## NOTE: This route is ONLY used at present for the reset password form at the client + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + var l = await TranslationBiz.GetSpecifiedTranslationSubsetStaticAsync(inObj, id); + return Ok(ApiOkResponse.Response(l)); + } + + + + /// + /// Duplicate + /// + /// Source object id + /// From route path + /// Duplicate + [HttpPost("duplicate/{id}")] + public async Task DuplicateTranslation([FromRoute] long id, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + Translation o = await biz.DuplicateAsync(id); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(TranslationController.GetTranslation), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + + + /// + /// Delete Translation + /// + /// + /// Ok + [HttpDelete("{id}")] + public async Task DeleteTranslation([FromRoute] long id) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + + //Fetch translation and it's children + //(fetch here so can return proper REST responses on failing basic validity) + var dbObject = await ct.Translation.Include(z => z.TranslationItems).SingleOrDefaultAsync(z => z.Id == id); + if (dbObject == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + if (!Authorized.HasDeleteRole(HttpContext.Items, SockType.Translation)) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + + //Instantiate the business object handler + TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext); + if (!await biz.DeleteAsync(dbObject)) + { + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + + return NoContent(); + } + + + + + + + + + /// + /// Get Translation all values + /// + /// + /// download token + /// A single Translation and it's values + [AllowAnonymous] + [HttpGet("download/{id}")] + public async Task DownloadTranslation([FromRoute] long id, [FromQuery] string t) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + if (await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct) == null) + { + await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + var o = await ct.Translation.Include(z => z.TranslationItems).SingleOrDefaultAsync(z => z.Id == id); + //turn into correct format and then send as file + if (o == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + var asText = Newtonsoft.Json.JsonConvert.SerializeObject( + o, + Newtonsoft.Json.Formatting.None, + new JsonSerializerSettings { ContractResolver = new Sockeye.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "TranslationId" }) }); + var bytes = System.Text.Encoding.UTF8.GetBytes(asText); + var file = new FileContentResult(bytes, "application/octet-stream"); + file.FileDownloadName = Util.FileUtil.StringToSafeFileName(o.Name) + ".json"; + return file; + } + + + + + /// + /// Upload Translation export file + /// Max 15mb total + /// + /// Accepted + [Authorize] + [HttpPost("upload")] + [DisableFormValueModelBinding] + [RequestSizeLimit(Sockeye.Util.ServerBootConfig.MAX_TRANSLATION_UPLOAD_BYTES)] + public async Task UploadAsync() + { + //Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming + + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + + // SockTypeId attachToObject = null; + ApiUploadProcessor.ApiUploadedFilesResult uploadFormData = null; + try + { + if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Expected a multipart request, but got {Request.ContentType}")); + + + bool badRequest = false; + string UploadAType = string.Empty; + string UploadObjectId = string.Empty; + string errorMessage = string.Empty; + string Notes = string.Empty; + List FileData = new List(); + + //Save uploads to disk under temporary file names until we decide how to handle them + uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext); + if (!string.IsNullOrWhiteSpace(uploadFormData.Error)) + { + errorMessage = uploadFormData.Error; + //delete temp files + ApiUploadProcessor.DeleteTempUploadFile(uploadFormData); + //file too large is most likely issue so in that case return this localized properly + if (errorMessage.Contains("413")) + { + var TransId = UserTranslationIdFromContext.Id(HttpContext.Items); + return BadRequest(new ApiErrorResponse( + ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, + null, + String.Format(await TranslationBiz.GetTranslationStaticAsync("AyaFileFileTooLarge", TransId, ct), Sockeye.Util.FileUtil.GetBytesReadable(Sockeye.Util.ServerBootConfig.MAX_TRANSLATION_UPLOAD_BYTES)))); + } + else//not too big, something else + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, errorMessage)); + } + + + if ( + !uploadFormData.FormFieldData.ContainsKey("FileData"))//only filedata is required + { + badRequest = true; + errorMessage = "Missing required FormFieldData value: FileData"; + } + if (!badRequest) + { + if (uploadFormData.FormFieldData.ContainsKey("SockType")) + UploadAType = uploadFormData.FormFieldData["SockType"].ToString(); + if (uploadFormData.FormFieldData.ContainsKey("ObjectId")) + UploadObjectId = uploadFormData.FormFieldData["ObjectId"].ToString(); + if (uploadFormData.FormFieldData.ContainsKey("Notes")) + Notes = uploadFormData.FormFieldData["Notes"].ToString(); + //fileData in JSON stringify format which contains the actual last modified dates etc + //"[{\"name\":\"Client.csv\",\"lastModified\":1582822079618},{\"name\":\"wmi4fu06nrs41.jpg\",\"lastModified\":1586900220990}]" + FileData = Newtonsoft.Json.JsonConvert.DeserializeObject>(uploadFormData.FormFieldData["FileData"].ToString()); + + } + + + // long UserId = UserIdFromContext.Id(HttpContext.Items); + //Instantiate the business object handler + TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext); + + + //We have our files now can parse and insert into db + if (uploadFormData.UploadedFiles.Count > 0) + { + //deserialize each file and import + foreach (UploadedFileInfo a in uploadFormData.UploadedFiles) + { + JObject o = JObject.Parse(System.IO.File.ReadAllText(a.InitialUploadedPathName)); + if (!await biz.ImportAsync(o)) + { + //delete all the files temporarily uploaded and return bad request + ApiUploadProcessor.DeleteTempUploadFile(uploadFormData); + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + } + } + } + catch (System.IO.InvalidDataException ex) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, ex.Message)); + } + finally + { + //delete all the files temporarily uploaded and return bad request + + ApiUploadProcessor.DeleteTempUploadFile(uploadFormData); + } + + //Return nothing + return Accepted(); + } + + + // /// + // /// Put (UpdateTranslationItemDisplayText) + // /// Update a single key with new display text + // /// + // /// NewText/Id/Concurrency token object. NewText is new display text, Id is TranslationItem Id, concurrency token is required + // /// + // [HttpPut("updatetranslationitemdisplaytext")] + // public async Task PutTranslationItemDisplayText([FromBody] NewTextIdConcurrencyTokenItem inObj) + // { + // if (serverState.IsClosed) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + // if (!ModelState.IsValid) + // { + // return BadRequest(new ApiErrorResponse(ModelState)); + // } + + // var oFromDb = await ct.TranslationItem.SingleOrDefaultAsync(z => z.Id == inObj.Id); + + // if (oFromDb == null) + // { + // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + // } + + // //Now fetch translation for rights and to ensure not stock + // var oDbParent = await ct.Translation.SingleOrDefaultAsync(z => z.Id == oFromDb.TranslationId); + // if (oDbParent == null) + // { + // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + // } + + // if (!Authorized.HasModifyRole(HttpContext.Items, SockType.Translation)) + // { + // return StatusCode(403, new ApiNotAuthorizedResponse()); + // } + + // //Instantiate the business object handler + // TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext); + + // try + // { + // if (!await biz.PutTranslationItemDisplayTextAsync(oFromDb, inObj, oDbParent)) + // { + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // } + // } + // catch (DbUpdateConcurrencyException) + // { + // if (!await biz.TranslationItemExistsAsync(inObj.Id)) + // { + // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + // } + // else + // { + // //exists but was changed by another user + // //I considered returning new and old record, but where would it end? + // //Better to let the client decide what to do than to send extra data that is not required + // return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT)); + // } + // } + + + // return Ok(ApiOkResponse.Response(new { Concurrency = oFromDb.Concurrency })); + // } + + + + /// + /// Put (UpdateTranslationItemDisplayText) + /// Update a list of items with new display text + /// + /// Array of NewText/Id/Concurrency token objects. NewText is new display text, Id is TranslationItem Id, concurrency token is required + /// + [HttpPut("updatetranslationitemsdisplaytext")] + public async Task PutTranslationItemsDisplayText([FromBody] List inObj) + { + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + var oFromDb = await ct.TranslationItem.AsNoTracking().SingleOrDefaultAsync(z => z.Id == inObj[0].Id); + + if (oFromDb == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + //Now fetch translation for rights and to ensure not stock + var oDbParent = await ct.Translation.SingleOrDefaultAsync(z => z.Id == oFromDb.TranslationId); + if (oDbParent == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.Translation)) + { + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + //Instantiate the business object handler + TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext); + + try + { + if (!await biz.PutTranslationItemsDisplayTextAsync(inObj, oDbParent)) + { + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + } + catch (DbUpdateConcurrencyException) + { + + //exists but was changed by another user + //I considered returning new and old record, but where would it end? + //Better to let the client decide what to do than to send extra data that is not required + return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT)); + } + return NoContent(); + } + + + + +#if (DEBUG) + public class TranslationCoverageInfo + { + public List RequestedKeys { get; set; } + public int RequestedKeyCount { get; set; } + public List NotRequestedKeys { get; set; } + public int NotRequestedKeyCount { get; set; } + + public TranslationCoverageInfo() + { + RequestedKeys = new List(); + NotRequestedKeys = new List(); + } + + } +#endif + + } +} \ No newline at end of file diff --git a/server/Controllers/UserController.cs b/server/Controllers/UserController.cs new file mode 100644 index 0000000..00d24ec --- /dev/null +++ b/server/Controllers/UserController.cs @@ -0,0 +1,385 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + +namespace Sockeye.Api.Controllers +{ + + /// + /// User + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/user")] + [Produces("application/json")] + [Authorize] + public class UserController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public UserController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + + /// + /// Get User + /// + /// + /// A single User + [HttpGet("{id}")] + public async Task GetUser([FromRoute] long id) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //Instantiate the business object handler + UserBiz biz = UserBiz.GetBiz(ct, HttpContext); + + //Also used for Contacts (customer type user or ho type user) + //by users with no User right so further biz rule required depending on usertype + //this is just phase 1 + bool AllowedOutsideUser = Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer); + bool AllowedInsideUser = Authorized.HasReadFullRole(HttpContext.Items, SockType.User); + + if (!AllowedOutsideUser && !AllowedInsideUser) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + var o = await biz.GetForPublicAsync(id); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + if (o.IsOutsideUser && !AllowedOutsideUser) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!o.IsOutsideUser && !AllowedInsideUser) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + return Ok(ApiOkResponse.Response(o)); + } + + + /// + /// Update User + /// (Login and / or Password are not changed if set to null / omitted) + /// + /// + /// + [HttpPut] + public async Task PutUser([FromBody] User updatedObject) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + UserBiz biz = UserBiz.GetBiz(ct, HttpContext); + + //Also used for Contacts (customer type user or ho type user) + //by users with no User right so further biz rule required depending on usertype + //this is just phase 1 + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.User) && !Authorized.HasModifyRole(HttpContext.Items, SockType.Customer)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + var o = await biz.PutAsync(updatedObject); + if (o == null) + { + if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT)) + return StatusCode(409, new ApiErrorResponse(biz.Errors)); + else + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ; + } + + + /// + /// Create User + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostUser([FromBody] User inObj, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + //Instantiate the business object handler + UserBiz biz = UserBiz.GetBiz(ct, HttpContext); + + //Also used for Contacts (customer type user or ho type user) + //by users with no User right so further biz rule required depending on usertype + //this is just phase 1 + if (!Authorized.HasCreateRole(HttpContext.Items, SockType.User) && !Authorized.HasCreateRole(HttpContext.Items, SockType.Customer)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + + //Create and validate + dtUser o = await biz.CreateAsync(inObj); + + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + { + + //return success and link + //NOTE: this is a USER object so we don't want to return some key fields for security reasons + //which is why the object is "cleaned" before return + return CreatedAtAction(nameof(UserController.GetUser), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + } + + // /// + // /// Duplicate User + // /// (Wiki and Attachments are not duplicated) + // /// + // /// Source object id + // /// From route path + // /// User + // [HttpPost("duplicate/{id}")] + // public async Task DuplicateUser([FromRoute] long id, ApiVersion apiVersion) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + // UserBiz biz = UserBiz.GetBiz(ct, HttpContext); + + + // //Also used for Contacts (customer type user or ho type user) + // //by users with no User right so further biz rule required depending on usertype + // //this is just phase 1 + // if (!Authorized.HasCreateRole(HttpContext.Items, SockType.User) && !Authorized.HasCreateRole(HttpContext.Items, SockType.Customer)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + // User o = await biz.DuplicateAsync(id); + // if (o == null) + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // else + // return CreatedAtAction(nameof(UserController.GetUser), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + // } + + + /// + /// Delete User + /// + /// + /// NoContent + [HttpDelete("{id}")] + public async Task DeleteUser([FromRoute] long id) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + UserBiz biz = UserBiz.GetBiz(ct, HttpContext); + + //Also used for Contacts (customer type user or ho type user) + //by users with no User right so further biz rule required depending on usertype + //this is just phase 1 + if (!Authorized.HasDeleteRole(HttpContext.Items, SockType.User) && !Authorized.HasDeleteRole(HttpContext.Items, SockType.Customer)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + + if (!await biz.DeleteAsync(id)) + return BadRequest(new ApiErrorResponse(biz.Errors)); + return NoContent(); + } + + + /// + /// Get list of Users + /// (rights to User object required) + /// + /// All "inside" Users (except Customer and HeadOffice type) + [HttpGet("list")] + public async Task GetInsideUserList() + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.User)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + + var ret = await ct.User.Where(z => z.UserType != UserType.Customer && z.UserType != UserType.HeadOffice).Select(z => new dtUser + { + Id = z.Id, + Active = z.Active, + Name = z.Name, + Roles = z.Roles, + UserType = z.UserType, + EmployeeNumber = z.EmployeeNumber, + LastLogin = z.LastLogin + + }).ToListAsync(); + return Ok(ApiOkResponse.Response(ret)); + } + + + + // /// + // /// Get list of Customer / Head office Users + // /// (Rights to Customer object required) + // /// + // /// All "outside" Users (No staff or contractors) + // [HttpGet("outlist")] + // public async Task GetOutsideUserList() + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + // if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + + // var ret = await ct.User.Include(c => c.Customer).Include(h => h.HeadOffice).Include(o => o.UserOptions).Where(z => z.UserType == UserType.Customer || z.UserType == UserType.HeadOffice).Select(z => new + // { + // Id = z.Id, + // Active = z.Active, + // Name = z.Name, + // UserType = z.UserType, + // LastLogin = z.LastLogin, + // EmailAddress = z.UserOptions.EmailAddress, + // Phone1 = z.UserOptions.Phone1, + // Phone2 = z.UserOptions.Phone2, + // Phone3 = z.UserOptions.Phone3, + // Organization = z.HeadOffice.Name ?? z.Customer.Name + + // }).ToListAsync(); + // return Ok(ApiOkResponse.Response(ret)); + // } + + /// + /// Get list of Customer Contact Users + /// (Rights to Customer object required) + /// + /// Customer contact users + [HttpGet("customer-contacts/{customerId}")] + public async Task GetCustomerContactList(long customerId) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + var ret = await ct.User.Include(o => o.UserOptions).Where(z => z.UserType == UserType.Customer && z.CustomerId == customerId).Select(z => new + { + Id = z.Id, + Active = z.Active, + Name = z.Name, + UserType = z.UserType, + LastLogin = z.LastLogin, + EmailAddress = z.UserOptions.EmailAddress, + Phone1 = z.UserOptions.Phone1, + Phone2 = z.UserOptions.Phone2, + Phone3 = z.UserOptions.Phone3 + + }).ToListAsync(); + return Ok(ApiOkResponse.Response(ret)); + } + + /// + /// Get list of HeadOffice Contact Users + /// (Rights to HeadOffice object required) + /// + /// HeadOffice contact users + [HttpGet("head-office-contacts/{headofficeId}")] + public async Task GetHeadOfficeContactList(long headofficeId) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.HeadOffice)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + var ret = await ct.User.Include(o => o.UserOptions).Where(z => z.UserType == UserType.HeadOffice && z.HeadOfficeId == headofficeId).Select(z => new + { + Id = z.Id, + Active = z.Active, + Name = z.Name, + UserType = z.UserType, + LastLogin = z.LastLogin, + EmailAddress = z.UserOptions.EmailAddress, + Phone1 = z.UserOptions.Phone1, + Phone2 = z.UserOptions.Phone2, + Phone3 = z.UserOptions.Phone3 + + }).ToListAsync(); + return Ok(ApiOkResponse.Response(ret)); + } + + + + /// + /// Fetch user type (inside meaning staff or subcontractor or outside meaning customer or headoffice type user) + /// + /// + /// All "inside" Users (except Customer and HeadOffice type) + [HttpGet("inside-type/{id}")] + public async Task GetInsideStatus(long id) + { + //This method is used by the Client UI to determine the correct edit form to show + if (serverState.IsClosed && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!Authorized.HasSelectRole(HttpContext.Items, SockType.User)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + var u = await ct.User.FirstOrDefaultAsync(z => z.Id == id); + if (u == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(u.UserType != UserType.Customer && u.UserType != UserType.HeadOffice)); + } + + + + + /// + /// Fetch super user status + /// + /// + /// true or false + [HttpGet("amsu")] + public ActionResult GetAMSU() + { + //This is used by v8 migrate so it doesn't need to decode web tokens + if (serverState.IsClosed) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + return Ok(ApiOkResponse.Response(UserIdFromContext.Id(HttpContext.Items)==1)); + } + + + //------------ + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/UserOptionsController.cs b/server/Controllers/UserOptionsController.cs new file mode 100644 index 0000000..66c5be3 --- /dev/null +++ b/server/Controllers/UserOptionsController.cs @@ -0,0 +1,206 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Biz; + + +namespace Sockeye.Api.Controllers +{ + + /// + /// UserOptions + /// + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/user-option")] + [Produces("application/json")] + [Authorize] + public class UserOptionsController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public UserOptionsController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + /// + /// Get full UserOptions object + /// + /// UserId + /// A single UserOptions + [HttpGet("{id}")] + public async Task GetUserOptions([FromRoute] long id) + { + if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + { + //Exception for SuperUser account to handle licensing issues + if (UserIdFromContext.Id(HttpContext.Items) != 1) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + } + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + var UserId = UserIdFromContext.Id(HttpContext.Items); + + //Different than normal here: a user is *always* allowed to retrieve their own user options object + if (id != UserId) + { + //Not users own options so need to check just as for User object as could be a Contact + + //Also used for Contacts (customer type user or ho type user) + //by users with no User right so further biz rule required depending on usertype + //this is just phase 1 + bool AllowedOutsideUser = Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer); + bool AllowedInsideUser = Authorized.HasReadFullRole(HttpContext.Items, SockType.User); + + if (!AllowedOutsideUser && !AllowedInsideUser) + return StatusCode(403, new ApiNotAuthorizedResponse()); + } + + //Instantiate the business object handler + UserOptionsBiz biz = new UserOptionsBiz(ct, UserId, UserRolesFromContext.Roles(HttpContext.Items)); + var o = await biz.GetAsync(id); + if (o == null) + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + return Ok(ApiOkResponse.Response(o)); + } + + + + //Creating a user creates a user options so no need for create ever + // /// + // /// Create UserOptions + // /// + // /// + // /// From route path + // /// + // [HttpPost] + // public async Task PostUserOptions([FromBody] UserOptions newObject, ApiVersion apiVersion) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + // // var UserId = UserIdFromContext.Id(HttpContext.Items); + + // // //preclearance + // // //the biz object will further check + // // if (newObject.Id != UserId) + // // { + // // //Also used for Contacts (customer type user or ho type user) + // // //by users with no User right so further biz rule required depending on usertype + // // //this is just phase 1 + // // if (!Authorized.HasCreateRole(HttpContext.Items, SockType.User) && !Authorized.HasCreateRole(HttpContext.Items, SockType.Customer)) + // // return StatusCode(403, new ApiNotAuthorizedResponse()); + // // } + // UserOptionsBiz biz = new UserOptionsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + // UserOptions o = await biz.CreateAsync(newObject); + // if (o == null) + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // else + // return CreatedAtAction(nameof(UserOptionsController.GetUserOptions), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + // } + + + /// + /// Update UserOptions + /// + /// User id + /// + /// + [HttpPut("{id}")] + public async Task PutUserOptions([FromRoute] long id, [FromBody] UserOptions inObj) + { + if (serverState.IsClosed && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + var UserId = UserIdFromContext.Id(HttpContext.Items); + + var o = await ct.UserOptions.SingleOrDefaultAsync(z => z.UserId == id); + + if (o == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + //preclearance + //the biz object will further check + if (id != UserId) + { + //Also used for Contacts (customer type user or ho type user) + //by users with no User right so further biz rule required depending on usertype + //this is just phase 1 + if (!Authorized.HasModifyRole(HttpContext.Items, SockType.User) && !Authorized.HasModifyRole(HttpContext.Items, SockType.Customer)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + } + + //Instantiate the business object handler + UserOptionsBiz biz = new UserOptionsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + + try + { + if (!await biz.PutAsync(o, inObj)) + { + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + } + catch (DbUpdateConcurrencyException) + { + if (!UserOptionsExists(id)) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + else + { + //exists but was changed by another user + return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT)); + } + } + + return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); + } + + + + private bool UserOptionsExists(long id) + { + //NOTE: checks by UserId, NOT by Id as in most other objects + return ct.UserOptions.Any(z => z.UserId == id); + } + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/AttachmentDataList.cs b/server/DataList/AttachmentDataList.cs new file mode 100644 index 0000000..2fb84e6 --- /dev/null +++ b/server/DataList/AttachmentDataList.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using Sockeye.Biz; +namespace Sockeye.DataList +{ + internal class AttachmentDataList : DataListProcessingBase + { + public AttachmentDataList(long translationId) + { + DefaultListAType = SockType.FileAttachment; + SQLFrom = "from afileattachment left join auser on (afileattachment.attachedByUserId=auser.id)"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "displayfilename", "object", "size", "username", "notes", "exists" }; + DefaultSortBy = new Dictionary() { { "displayfilename", "+" }, { "size", "-" } }; + + + FieldDefinitions = new List(); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "FileAttachment", + FieldKey = "displayfilename", + SockType = (int)SockType.FileAttachment, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "afileattachment.id", + SqlValueColumnName = "afileattachment.displayfilename", + IsRowId = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "User", + FieldKey = "username", + SockType = (int)SockType.User, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "auser.id", + SqlValueColumnName = "auser.name", + IsRowId = false + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AttachmentExists", + FieldKey = "exists", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "afileattachment.exists" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Object", + FieldKey = "object", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "afileattachment.AttachToObjectid", + SqlValueColumnName = $"AYGETNAME(afileattachment.AttachToObjectid, afileattachment.attachtosockType,{translationId})", + SqlATypeColumnName = "afileattachment.attachtosockType", + Translate = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "FileSize", + FieldKey = "size", + UiFieldDataType = (int)UiFieldDataType.MemorySize, + SqlValueColumnName = "afileattachment.size" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AttachmentNotes", + FieldKey = "notes", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "afileattachment.notes" + }); + + + } + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/CustomerDataList.cs b/server/DataList/CustomerDataList.cs new file mode 100644 index 0000000..ffd9455 --- /dev/null +++ b/server/DataList/CustomerDataList.cs @@ -0,0 +1,326 @@ +using System.Collections.Generic; +using System.Linq; +using Sockeye.Biz; +using Sockeye.Models; + +namespace Sockeye.DataList +{ + internal class CustomerDataList : DataListProcessingBase, IDataListInternalCriteria + { + public CustomerDataList(long translationId) + { + DefaultListAType = SockType.Customer; + SQLFrom = @"FROM ACUSTOMER LEFT JOIN AHEADOFFICE ON (ACUSTOMER.HEADOFFICEID = AHEADOFFICE.ID)"; + + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "customername", "customerphone1", "customeremail", "customerheadoffice" }; + DefaultSortBy = new Dictionary() { { "customername", "+" } }; + + FieldDefinitions = new List(); + + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerName", + FieldKey = "customername", + SockType = (int)SockType.Customer, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "acustomer.id", + SqlValueColumnName = "acustomer.name", + IsRowId = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerNotes", + FieldKey = "customernotes", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.notes" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Active", + FieldKey = "customeractive", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "acustomer.active" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Tags", + FieldKey = "customertags", + UiFieldDataType = (int)UiFieldDataType.Tags, + SqlValueColumnName = "acustomer.tags" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "WebAddress", + FieldKey = "customerwebaddress", + UiFieldDataType = (int)UiFieldDataType.HTTP, + SqlValueColumnName = "acustomer.webaddress" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerAlertNotes", + FieldKey = "CustomerAlertNotes", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.alertnotes" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "HeadOffice", + FieldKey = "customerheadoffice", + UiFieldDataType = (int)UiFieldDataType.Text, + SockType = (int)SockType.HeadOffice, + SqlIdColumnName = "aheadoffice.id", + SqlValueColumnName = "aheadoffice.name" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerBillHeadOffice", + FieldKey = "customerbillheadoffice", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "acustomer.billheadoffice" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerTechNotes", + FieldKey = "customertechnotes", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.technotes" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerAccountNumber", + FieldKey = "customeraccountnumber", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.accountnumber" + }); + + + + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerPhone1", + FieldKey = "customerphone1", + UiFieldDataType = (int)UiFieldDataType.PhoneNumber, + SqlValueColumnName = "acustomer.phone1" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerPhone2", + FieldKey = "customerphone2", + UiFieldDataType = (int)UiFieldDataType.PhoneNumber, + SqlValueColumnName = "acustomer.phone2" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerPhone3", + FieldKey = "customerphone3", + UiFieldDataType = (int)UiFieldDataType.PhoneNumber, + SqlValueColumnName = "acustomer.phone3" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerPhone4", + FieldKey = "customerphone4", + UiFieldDataType = (int)UiFieldDataType.PhoneNumber, + SqlValueColumnName = "acustomer.phone4" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerPhone5", + FieldKey = "customerphone5", + UiFieldDataType = (int)UiFieldDataType.PhoneNumber, + SqlValueColumnName = "acustomer.phone5" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerEmail", + FieldKey = "customeremail", + UiFieldDataType = (int)UiFieldDataType.EmailAddress, + SqlValueColumnName = "acustomer.emailaddress" + }); + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressPostalDeliveryAddress", + FieldKey = "customerpostaddress", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.postaddress" + }); + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressPostalCity", + FieldKey = "customerpostcity", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.postcity" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressPostalStateProv", + FieldKey = "customerpostregion", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.postregion" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressPostalCountry", + FieldKey = "customerpostcountry", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.postcountry" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressPostalPostal", + FieldKey = "customerpostcode", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.postcode" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressDeliveryAddress", + FieldKey = "customeraddress", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.address" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressCity", + FieldKey = "customercity", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.city" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressStateProv", + FieldKey = "customerregion", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.region" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressCountry", + FieldKey = "customercountry", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.country" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressPostal", + FieldKey = "customeraddresspostal", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomer.addresspostal" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressLatitude", + FieldKey = "customerlatitude", + UiFieldDataType = (int)UiFieldDataType.Decimal, + SqlValueColumnName = "acustomer.latitude" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressLongitude", + FieldKey = "customerlongitude", + UiFieldDataType = (int)UiFieldDataType.Decimal, + SqlValueColumnName = "acustomer.longitude" + }); + + + + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom1", FieldKey = "customercustom1", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom2", FieldKey = "customercustom2", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom3", FieldKey = "customercustom3", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom4", FieldKey = "customercustom4", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom5", FieldKey = "customercustom5", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom6", FieldKey = "customercustom6", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom7", FieldKey = "customercustom7", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom8", FieldKey = "customercustom8", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom9", FieldKey = "customercustom9", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom10", FieldKey = "customercustom10", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom11", FieldKey = "customercustom11", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom12", FieldKey = "customercustom12", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom13", FieldKey = "customercustom13", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom14", FieldKey = "customercustom14", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom15", FieldKey = "customercustom15", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom16", FieldKey = "customercustom16", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" }); + + + //META COLUMNS + + FieldDefinitions.Add(new DataListFieldDefinition + { + FieldKey = "metaheadoffice", + UiFieldDataType = (int)UiFieldDataType.InternalId, + SqlIdColumnName = "aheadoffice.id", + SqlValueColumnName = "aheadoffice.id", + IsMeta = true //"I'm So Meta Even This Acronym" + }); + } + + public List DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria) + { + List ret = new List(); + + //ClientCriteria format for this list is "OBJECTID,AYATYPE" + var crit = (clientCriteria ?? "").Split(',').Select(z => z.Trim()).ToArray(); + if (crit.Length > 1) + { + //for now just show all customers of headoffice + int nType = 0; + if (!int.TryParse(crit[1], out nType)) return ret; + SockType forType = (SockType)nType; + if (forType != SockType.HeadOffice) return ret; + + long lId = 0; + if (!long.TryParse(crit[0], out lId)) return ret; + if (lId == 0) return ret; + + //Have valid type, have an id, so filter away + switch (forType) + { + case SockType.HeadOffice: + { + DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metaheadoffice" }; + FilterOption.Items.Add(new DataListColumnFilter() { value = crit[0], op = DataListFilterComparisonOperator.Equality }); + ret.Add(FilterOption); + } + break; + + } + } + return ret; + } + + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/CustomerNoteDataList.cs b/server/DataList/CustomerNoteDataList.cs new file mode 100644 index 0000000..eb942fc --- /dev/null +++ b/server/DataList/CustomerNoteDataList.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using Sockeye.Biz; +using Sockeye.Models; + +namespace Sockeye.DataList +{ + internal class CustomerNoteDataList : DataListProcessingBase, IDataListInternalCriteria + { + public CustomerNoteDataList(long translationId) + { + DefaultListAType = SockType.CustomerNote; + SQLFrom = "from acustomernote left join auser on (acustomernote.userid=auser.id)"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "notedate", "notes", "username" }; + DefaultSortBy = new Dictionary() { { "notedate", "-" } }; + + FieldDefinitions = new List(); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "User", + FieldKey = "username", + SockType = (int)SockType.User, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "auser.id", + SqlValueColumnName = "auser.name", + IsRowId = false + }); + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerNoteNotes", + FieldKey = "notes", + SockType = (int)SockType.CustomerNote, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "acustomernote.id", + SqlValueColumnName = "acustomernote.notes", + IsRowId = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerNoteNoteDate", + FieldKey = "notedate", + UiFieldDataType = (int)UiFieldDataType.DateTime, + SqlValueColumnName = "acustomernote.notedate" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Tags", + FieldKey = "customernotetags", + UiFieldDataType = (int)UiFieldDataType.Tags, + SqlValueColumnName = "acustomernote.tags" + }); + + //META column + FieldDefinitions.Add(new DataListFieldDefinition + { + FieldKey = "metacustomer", + UiFieldDataType = (int)UiFieldDataType.InternalId, + SqlIdColumnName = "acustomernote.customerid", + SqlValueColumnName = "acustomernote.customerid", + IsMeta = true + }); + } + + + public List DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria) + { + List ret = new List(); + //ClientCriteria MUST be CustomerId + if (string.IsNullOrWhiteSpace(clientCriteria)) + throw new System.ArgumentNullException("CustomerNoteDataList - ClientCriteria is empty, should be Customer ID"); + + DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metacustomer" }; + FilterOption.Items.Add(new DataListColumnFilter() { value = clientCriteria, op = DataListFilterComparisonOperator.Equality }); + + ret.Add(FilterOption); + return ret; + } + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/CustomerNotificationDeliveryLogDataList.cs b/server/DataList/CustomerNotificationDeliveryLogDataList.cs new file mode 100644 index 0000000..339e0a3 --- /dev/null +++ b/server/DataList/CustomerNotificationDeliveryLogDataList.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using Sockeye.Biz; +namespace Sockeye.DataList +{ + internal class CustomerNotificationDeliveryLogDataList : DataListProcessingBase + { + public CustomerNotificationDeliveryLogDataList(long translationId) + { + DefaultListAType = SockType.OpsNotificationSettings; + SQLFrom = "from acustomernotifydeliverylog left join acustomernotifysubscription on acustomernotifysubscription.id = acustomernotifydeliverylog.customernotifysubscriptionid"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "CustomerNotifySubscription", "Processed", "NotifyEventType", "CustomerTags", "Failed", "Errors" }; + DefaultSortBy = new Dictionary() { { "Processed", "-" } }; + FieldDefinitions = new List(); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerNotifySubscription", + FieldKey = "CustomerNotifySubscription", + SockType = (int)SockType.CustomerNotifySubscription, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "acustomernotifysubscription.id", + SqlValueColumnName = "acustomernotifysubscription.id", + IsRowId = false + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Processed", + FieldKey = "Processed", + UiFieldDataType = (int)UiFieldDataType.DateTime, + SqlValueColumnName = "acustomernotifydeliverylog.processed" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "NotifyEventType", + FieldKey = "NotifyEventType", + UiFieldDataType = (int)UiFieldDataType.Enum, + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(NotifyEventType).ToString()), + SqlValueColumnName = "acustomernotifysubscription.eventtype" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "CustomerTags", + FieldKey = "CustomerTags", + UiFieldDataType = (int)UiFieldDataType.Tags, + SqlValueColumnName = "acustomernotifysubscription.customertags" + }); + + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Failed", + FieldKey = "Failed", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "acustomernotifydeliverylog.fail" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Errors", + FieldKey = "Errors", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "acustomernotifydeliverylog.error" + }); + + + + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/DataListFactory.cs b/server/DataList/DataListFactory.cs new file mode 100644 index 0000000..afe7564 --- /dev/null +++ b/server/DataList/DataListFactory.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Sockeye.DataList +{ + internal static class DataListFactory + { + + //Instantiate list object specified + //this is safe as it's only attempting to load assemblies in the Sockeye.DataList namespace so can't attempt to instantiate some random object or nefarious object + //returns null if doesn't exist + internal static IDataListProcessing GetAyaDataList(string ListKey, long translationId) + { + System.Reflection.Assembly ass = System.Reflection.Assembly.GetEntryAssembly(); + return ass.CreateInstance($"Sockeye.DataList.{ListKey}", false, System.Reflection.BindingFlags.Default, null, new object[] { translationId }, null, null) as IDataListProcessing; + + } + + //List all the datalist types available + internal static List GetListOfAllDataListKeyNames() + { + //https://stackoverflow.com/a/42574373/8939 + + List ret = new List(); + System.Reflection.Assembly ass = System.Reflection.Assembly.GetEntryAssembly(); + + foreach (System.Reflection.TypeInfo ti in ass.DefinedTypes) + { + if (!ti.IsAbstract && ti.ImplementedInterfaces.Contains(typeof(IDataListProcessing))) + { + ret.Add(ti.Name); + } + } + return ret; + } + + //Verify listkey + internal static bool ListKeyIsValid(string listKey) + { + + System.Reflection.Assembly ass = System.Reflection.Assembly.GetEntryAssembly(); + + foreach (System.Reflection.TypeInfo ti in ass.DefinedTypes) + { + if (!ti.IsAbstract && ti.ImplementedInterfaces.Contains(typeof(IDataListProcessing))) + { + if (ti.Name == listKey) + return true; + } + } + return false; + } + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/DataListFetcher.cs b/server/DataList/DataListFetcher.cs new file mode 100644 index 0000000..5affb9a --- /dev/null +++ b/server/DataList/DataListFetcher.cs @@ -0,0 +1,310 @@ +//#define AYSHOWQUERYINFO + +using System.Collections.Generic; +using System.Linq; +using Sockeye.Biz; +using Newtonsoft.Json.Linq; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace Sockeye.DataList +{ + internal static class DataListFetcher + { + +#if (AYSHOWQUERYINFO) +#if (DEBUG) +#warning FYI AYSHOWQUERYINFO is defined +#else +#error ### HOLDUP: AYSHOWQUERYINFO is defined in a RELEASE BUILD!!!! +#endif +#endif + //////////////////////////////////////////////// + // Get the data list data requested + // + // + internal static async Task GetResponseAsync(AyContext ct, DataListTableProcessingOptions dataListTableProcessingOptions, IDataListProcessing DataList, AuthorizationRoles userRoles, ILogger log, long userId) + { + //#BUILD THE QUERY + + //SELECT CLAUSE + var qSelect = DataListSqlSelectBuilder.BuildForDataTableListResponse(DataList.FieldDefinitions, dataListTableProcessingOptions.AllUniqueColumnKeysReferenced); + + //FROM CLAUSE + var qFrom = DataList.SQLFrom; + + var qWhere = string.Empty; + var qOrderBy = string.Empty; + + //WHERE CLAUSE - FILTER + qWhere = DataListSqlFilterCriteriaBuilder.DataFilterToSQLCriteria(DataList.FieldDefinitions, dataListTableProcessingOptions); + + //ORDER BY CLAUSE - SORT + //BUILD ORDER BY + qOrderBy = DataListSqlFilterOrderByBuilder.DataFilterToSQLOrderBy(DataList.FieldDefinitions, dataListTableProcessingOptions); + + //LIMIT AND OFFSET CLAUSE - PAGING + dataListTableProcessingOptions.Offset = dataListTableProcessingOptions.Offset ?? DataListTableProcessingOptions.DefaultOffset; + dataListTableProcessingOptions.Limit = dataListTableProcessingOptions.Limit ?? DataListTableProcessingOptions.DefaultLimit; + var qLimitOffset = $"LIMIT {dataListTableProcessingOptions.Limit} OFFSET {dataListTableProcessingOptions.Offset}"; + + //PUT IT ALL TOGETHER + string qDataQuery = string.Empty; + string qTotalRecordsQuery = string.Empty; + + qDataQuery = $"{qSelect.Select} {qFrom} {qWhere} {qOrderBy} {qLimitOffset}".Replace(" ", " "); + qTotalRecordsQuery = $"SELECT COUNT(*) {qFrom} {qWhere}".Replace(" ", " "); + + //RETURN OBJECTS + int returnRowColumnCount = dataListTableProcessingOptions.Columns.Count(); + List> rows = new List>(); + long totalRecordCount = 0; +#if (DEBUG && AYSHOWQUERYINFO) + System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch(); + +#endif + + //QUERY THE DB + using (var command = ct.Database.GetDbConnection().CreateCommand()) + { + await ct.Database.OpenConnectionAsync(); + + //GET DATA RETURN ROWS + command.CommandText = qDataQuery; + try + { +#if (DEBUG && AYSHOWQUERYINFO) + stopWatch.Start(); +#endif + using (var dr = await command.ExecuteReaderAsync()) + { +#if (DEBUG && AYSHOWQUERYINFO) + stopWatch.Stop(); + log.LogInformation($"(debug) DataListFetcher:GetResponse DATA query took {stopWatch.ElapsedMilliseconds}ms to execute: {qDataQuery}"); + stopWatch.Reset(); +#endif + while (dr.Read()) + { + List row = new List(returnRowColumnCount); + //INSERT REMAINING FIELDS FROM TEMPLATE INTO THE RETURN ROWS LIST + foreach (string TemplateField in dataListTableProcessingOptions.Columns) + { + //get the AyaObjectFieldDefinition + DataListFieldDefinition f = DataList.FieldDefinitions.FirstOrDefault(z => z.FieldKey == TemplateField); + if (f == null) + { + + log.LogError($"DataListFetcher:GetResponseAsync Template field '{TemplateField}' was NOT found in the field definitions for data list {DataList.ToString()}"); + continue; + } + if (f.IsCustomField) + { + DataListField AyaField = new DataListField(); + //could be null + var rawValue = dr.GetValue(qSelect.map[f.GetSqlValueColumnName()]); + if (rawValue != null) + { + string cust = rawValue.ToString(); + if (!string.IsNullOrWhiteSpace(cust)) + { + JObject j = JObject.Parse(cust); + //convert field name to cust name then get value + var InternalCustomFieldName = FormFieldOptionalCustomizableReference.TranslateLTCustomFieldToInternalCustomFieldName(TemplateField); + //Sometimes a custom field is specified but doesn't exist in the collection so don't assume it's there + JToken o = j[InternalCustomFieldName]; + if (o != null) + AyaField.v = o.Value(); + else + AyaField.v = null; + } + else + { + AyaField.v = null; + } + } + row.Add(AyaField); + } + else + { + DataListField AyaField = new DataListField(); + AyaField.v = dr.GetValue(qSelect.map[f.GetSqlValueColumnName()]); + + if (f.IsRowId) + { + AyaField.rid = true; + } + else + { + AyaField.rid = null; + } + + if (f.SqlIdColumnName != null) + { + var ordinal = qSelect.map[f.SqlIdColumnName]; + if (!await dr.IsDBNullAsync(ordinal)) + AyaField.i = dr.GetInt64(ordinal); + } + + if (f.SqlATypeColumnName != null) + { + var ordinal = qSelect.map[f.SqlATypeColumnName]; + if (!await dr.IsDBNullAsync(ordinal)) + AyaField.ot = dr.GetInt32(ordinal); + } + + if (f.SqlColorColumnName != null) + { + var ordinal = qSelect.map[f.SqlColorColumnName]; + if (!await dr.IsDBNullAsync(ordinal)) + AyaField.clr = dr.GetString(ordinal); + } + + row.Add(AyaField); + } + } + rows.Add(row); + } + } + + //GET TOTAL RECORD COUNT + command.CommandText = qTotalRecordsQuery; +#if (DEBUG && AYSHOWQUERYINFO) + stopWatch.Start(); +#endif + using (var dr = await command.ExecuteReaderAsync()) + { +#if (DEBUG && AYSHOWQUERYINFO) + stopWatch.Stop(); + log.LogInformation($"(debug) DataListFetcher:GetResponse COUNT query took {stopWatch.ElapsedMilliseconds}ms to execute: {qTotalRecordsQuery}"); +#endif + if (dr.Read()) + { + totalRecordCount = dr.GetInt64(0); + } + } + } + catch (Npgsql.PostgresException e) + { + //log out the exception and the query + log.LogError("DataListFetcher:GetResponseAsync query failed. Data Query was:"); + log.LogError(qDataQuery); + log.LogError("Count Query was:"); + log.LogError(qTotalRecordsQuery); + log.LogError(e, "DB Exception"); + throw new System.Exception("DataListFetcher:GetResponseAsync - Query failed see log"); + + } + catch (System.Exception e) + { + //ensure any other type of exception gets surfaced properly + //log out the exception and the query + log.LogError("DataListFetcher:GetResponseAsync unexpected failure. Data Query was:"); + log.LogError(qDataQuery); + log.LogError("Count Query was:"); + log.LogError(qTotalRecordsQuery); + log.LogError(e, "Exception"); + throw new System.Exception("DataListFetcher:GetResponseAsync - unexpected failure see log"); + } + } + + //BUILD THE COLUMNS RETURN PROPERTY JSON FRAGMENT + Newtonsoft.Json.Linq.JArray ColumnsJSON = null; + ColumnsJSON = DataList.GenerateReturnListColumns(dataListTableProcessingOptions.Columns); + return new DataListReturnData(rows, + totalRecordCount, + ColumnsJSON, + dataListTableProcessingOptions.SortBy, + dataListTableProcessingOptions.Filter.Where(z => z.Column.StartsWith("meta") == false).ToList(), + dataListTableProcessingOptions.HiddenAffectiveColumns); + } + + + + ///////////////////////////////////////////////////////////////// + // Get a list of id's of the datalist results for reporting + // (and other uses like job ops, exporting etc) + // called from RehydrateIdList only + // + internal static async Task GetIdListResponseAsync( + AyContext ct, + DataListSelectedProcessingOptions dataListSelectionOptions, + IDataListProcessing DataList, + AuthorizationRoles userRoles, + ILogger log, + long userId, + bool limitForReportDesigner) + { + //#BUILD THE QUERY + + //SELECT FRAGMENT COLUMNS FROM TEMPLATE + var qSelect = DataListSqlSelectBuilder.BuildForIdListResponse(DataList.FieldDefinitions, dataListSelectionOptions); + + //FROM CLAUSE + var qFrom = DataList.SQLFrom; + + var qWhere = string.Empty; + var qOrderBy = string.Empty; + + //WHERE CLAUSE - FILTER + qWhere = DataListSqlFilterCriteriaBuilder.DataFilterToSQLCriteria(DataList.FieldDefinitions, dataListSelectionOptions); + + //ORDER BY CLAUSE - SORT + qOrderBy = DataListSqlFilterOrderByBuilder.DataFilterToSQLOrderBy(DataList.FieldDefinitions, dataListSelectionOptions); + + //LIMIT (if report designer) + var qLimit = string.Empty; + if (limitForReportDesigner) + qLimit = "LIMIT 5"; + + //PUT IT ALL TOGETHER + string qDataQuery = string.Empty; + + qDataQuery = $"{qSelect} {qFrom} {qWhere} {qOrderBy} {qLimit} ".Replace(" ", " "); + + //RETURN OBJECTS + var retList = new List(); + + using (var command = ct.Database.GetDbConnection().CreateCommand()) + { + await ct.Database.OpenConnectionAsync(); + command.CommandText = qDataQuery; + try + { + using (var dr = await command.ExecuteReaderAsync()) + { + while (dr.Read()) + { + //only one column and it's the zeroth id column + if (!dr.IsDBNull(0)) + retList.Add(dr.GetInt64(0)); + } + } + } + catch (Npgsql.PostgresException e) + { + //log out the exception and the query + log.LogError("DataListFetcher:GetIdListResponseAsync query failed unexpectedly. IDList Query was:"); + log.LogError(qDataQuery); + + log.LogError(e, "DB Exception"); + throw new System.Exception("DataListFetcher:GetIdListResponseAsync - Query failed see log"); + } + catch (System.Exception e) + { + //ensure any other type of exception gets surfaced properly + //log out the exception and the query + log.LogError("DataListFetcher:GetIdListResponseAsync unexpected failure. IDList Query was:"); + log.LogError(qDataQuery); + + log.LogError(e, "Exception"); + throw new System.Exception("DataListFetcher:GetIdListResponseAsync - unexpected failure see log"); + } + } + return retList.ToArray(); + } + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/DataListField.cs b/server/DataList/DataListField.cs new file mode 100644 index 0000000..ad7623f --- /dev/null +++ b/server/DataList/DataListField.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; + +namespace Sockeye.DataList +{ + public class DataListField + { + public object v { get; set; }//v for vvvvvvvv? + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] //https://www.newtonsoft.com/json/help/html/JsonPropertyPropertyLevelSetting.htm + public long? i { get; set; }//id value + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] //https://www.newtonsoft.com/json/help/html/JsonPropertyPropertyLevelSetting.htm + public bool? rid { get; set; } //row id for opening entire row by one field id + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] //https://www.newtonsoft.com/json/help/html/JsonPropertyPropertyLevelSetting.htm + public int? ot { get; set; }//openable type + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] //https://www.newtonsoft.com/json/help/html/JsonPropertyPropertyLevelSetting.htm + public string clr { get; set; }//color + } +} \ No newline at end of file diff --git a/server/DataList/DataListFieldDefinition.cs b/server/DataList/DataListFieldDefinition.cs new file mode 100644 index 0000000..b030576 --- /dev/null +++ b/server/DataList/DataListFieldDefinition.cs @@ -0,0 +1,94 @@ +using Sockeye.Biz; +using Newtonsoft.Json; + +namespace Sockeye.DataList +{ + //This class defines a field used for returning data in list format for UI grid lists and reporting + public class DataListFieldDefinition + { + //CLIENT / SERVER Unique identifier used at BOTH client and server + //also the sql valuecolumnname if identical + public string FieldKey { get; set; } + + //CLIENT Use only for display + public string TKey { get; set; } + + //CLIENT Use only for display to disambiguate things like + //Tags in main workorder and Tags in Workorder Item and Tags in Unit (all on same list) + public string TKeySection { get; set; } + + //CLIENT / SERVER - client display server validation purposes + public bool IsCustomField { get; set; } + + //CLIENT / SERVER - client display server validation purposes + public bool IsFilterable { get; set; } + + //CLIENT / SERVER - client display server validation purposes + public bool IsSortable { get; set; } + + //SERVER - indicates internal only meta column, not a client thing + public bool IsMeta { get; set; } + + //CLIENT Use only for display + public int UiFieldDataType { get; set; } + + //CLIENT Use only for display + public string EnumType { get; set; } + + //SERVER / CLIENT - used to identify the column that represents the entire row ID and object + //MUST be present in all datalists and displayed at the client + public bool IsRowId { get; set; } + + //CLIENT / SERVER - client display and to indicate what object to open , Server for formatting return object + public int SockType { get; set; } + + //CLIENT - indicates client must translate the values in this column (typically computed columns based on aygetname procedure) + public bool Translate { get; set; } + + //SERVER - for building sql queries + //don't return these properties when api user fetches field list definitions in DataListController + [JsonIgnore] + public string SqlIdColumnName { get; set; } + [JsonIgnore] + public string SqlValueColumnName { get; set; } + [JsonIgnore] + public string SqlATypeColumnName { get; set; }//column to fetch the SockType openabel for this field to set it dynamically instead of preset + [JsonIgnore] + public string SqlColorColumnName { get; set; }//column to fetch the color if applicable to this field + + public DataListFieldDefinition() + { + //most common defaults + IsCustomField = false; + IsFilterable = true; + IsSortable = true; + IsRowId = false; + IsMeta = false; + Translate = false; + //Set openable object type to no type which is the default and means it's not a link to another object + SockType = (int)Biz.SockType.NoType; + SqlATypeColumnName = null;//must be null as that is checked against specifically + SqlColorColumnName = null;//must be null to be ignored properly + + } + + //Get column to query for display name or use FieldName if there is no difference + public string GetSqlValueColumnName() + { + if (string.IsNullOrEmpty(SqlValueColumnName)) + { + return FieldKey.ToLowerInvariant(); + } + else + { + return SqlValueColumnName; + } + } + + public bool HasIdColumn() + { + return !string.IsNullOrWhiteSpace(SqlIdColumnName); + } + + } +} \ No newline at end of file diff --git a/server/DataList/DataListFilterComparisonOperator.cs b/server/DataList/DataListFilterComparisonOperator.cs new file mode 100644 index 0000000..dba6b0b --- /dev/null +++ b/server/DataList/DataListFilterComparisonOperator.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +namespace Sockeye.DataList +{ + public static class DataListFilterComparisonOperator + { + //NOTE: no LIKE or NOT LIKE deliberately + //StartsWith and EndsWith and Contains and NotContains cover all the most common cases and avoid needing a special set of LIKE characters people have to type and we have to document + public const string Equality = "="; + public const string GreaterThan = ">"; + public const string GreaterThanOrEqualTo = ">="; + public const string LessThan = "<"; + public const string LessThanOrEqualTo = "<="; + public const string NotEqual = "!="; + public const string StartsWith = "%-"; + public const string EndsWith = "-%"; + public const string Contains = "-%-"; + public const string NotContains = "!-%-"; + + public static List operators = null; + public static List Operators + { + get + { + if (operators == null) + { + operators = new List(); + operators.Add(Equality); + operators.Add(GreaterThan); + operators.Add(GreaterThanOrEqualTo); + operators.Add(LessThan); + operators.Add(LessThanOrEqualTo); + operators.Add(NotEqual); + operators.Add(StartsWith); + operators.Add(EndsWith); + operators.Add(Contains); + operators.Add(NotContains); + + } + return operators; + } + } + } +} diff --git a/server/DataList/DataListProcessingBase.cs b/server/DataList/DataListProcessingBase.cs new file mode 100644 index 0000000..beef936 --- /dev/null +++ b/server/DataList/DataListProcessingBase.cs @@ -0,0 +1,210 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Sockeye.Models; +using Sockeye.Biz; +using Newtonsoft.Json.Linq; +using Microsoft.EntityFrameworkCore; + +namespace Sockeye.DataList +{ + + /// + /// DataList object base class + /// + internal abstract class DataListProcessingBase : IDataListProcessing + { + //CoreBizObject add here + //well, not here exactly but add a new DATALIST class if it will be displayed as a list anywhere in the UI or reported on + public DataListProcessingBase() + { + /* + NOTE: all sql identifiers need to be explicitly identified as understood by postgres + + DefaultColumns = new List() { "XXX", "XXXX", "XXXX", "XXXX", "XXXX", "XXX", "XXXX", "XXXX", "XXXX", "XXXX" }; + DefaultSortBy = new Dictionary() { { "XXXX", "+" }, { "XXXX", "-" } }; + */ + } + + public string SQLFrom { get; set; } + public List FieldDefinitions { get; set; } + public AuthorizationRoles AllowedRoles { get; set; } + public SockType DefaultListAType { get; set; } + //public long CurrentUserId { get; set; } + //public long CurrentUserTranslationId { get; set; } + public List DefaultColumns { get; set; } + public Dictionary DefaultSortBy { get; set; } + + //set defaults if not provided in listOptions + public void SetListOptionDefaultsIfNecessary(Models.DataListProcessingBase listOptions) + { + //columns, filter and sortby could all be null + if (listOptions.Filter == null) + listOptions.Filter = new List(); + + if (listOptions.SortBy == null) + listOptions.SortBy = new Dictionary(); + + //Check Columns + if (listOptions is DataListTableProcessingOptions) + { + var dlto = ((DataListTableProcessingOptions)listOptions); + if (dlto.Columns == null) + dlto.Columns = new List(); + //if this doesn't work then just ditch this method in favor of local code, it's not really saving much + if (dlto.Columns.Count == 0) + dlto.Columns = DefaultColumns; + } + + //Check SortBy + if (listOptions.SortBy.Count == 0) + listOptions.SortBy = DefaultSortBy; + + //Check filter + if (listOptions.Filter == null) + { + + } + } + + public Newtonsoft.Json.Linq.JArray GenerateReturnListColumns(List columns) + { + var CustomFieldDefinitions = GetCustomFieldDefinitionsForList(); + + //Generate JSON fragment to return with column definitions + StringBuilder sb = new StringBuilder(); + + sb.Append("["); + + bool FirstColumnAdded = false; + + foreach (string s in columns) + { + DataListFieldDefinition o = FieldDefinitions.FirstOrDefault(z => z.FieldKey == s); +#if (DEBUG) + //Developers little helper + if (o == null) + { + throw new System.ArgumentNullException($"DEV ERROR in AyaDataList::GenerateReturnListColumns - field {s} specified in columns was NOT found in ObjectFields list"); + } +#endif + + if (o != null) + {//Here is where we can vet the field name, if it doesn't exist. For production we'll just ignore those ones + + if (FirstColumnAdded) + sb.Append(","); + sb.Append("{"); + //Build required part of column definition + if (!o.IsCustomField) + sb.Append($"\"cm\":\"{o.TKey}\",\"dt\":{(int)o.UiFieldDataType}"); + else + { + //insert specific type for this custom field + if (CustomFieldDefinitions.ContainsKey(o.TKey)) + { + var customFieldType = CustomFieldDefinitions[o.TKey]; + sb.Append($"\"cm\":\"{o.TKey}\",\"dt\":{customFieldType}"); + } + else + { + //this is normal as there may not be a definition for a Custom field but it's been requested so just treat it like text + sb.Append($"\"cm\":\"{o.TKey}\",\"dt\":{(int)UiFieldDataType.Text}"); + } + } + + //Has a AyAType? (linkable / openable) + if (o.SockType != 0) + sb.Append($",\"sock\":{(int)o.SockType}"); + + //Row ID column? + if (o.IsRowId) + { + sb.Append($",\"rid\":1"); + } + + //Has a Enumtype? + if (!string.IsNullOrEmpty(o.EnumType)) + sb.Append($",\"et\":\"{Sockeye.Util.StringUtil.TrimTypeName(o.EnumType)}\""); + + + //field key needed for sorting etc + sb.Append($",\"fk\":\"{o.FieldKey}\""); + + //Not Sortable? + if (!o.IsSortable) + sb.Append($",\"ns\":1"); + + //Not Filterable? + if (!o.IsFilterable) + sb.Append($",\"nf\":1"); + + //translate required? + if (o.Translate) + sb.Append($",\"tra\":1"); + + + sb.Append("}"); + FirstColumnAdded = true; + + } + } + sb.Append("]"); + + return JArray.Parse(sb.ToString()); + } + + + //Find and return a dictionary of all custom fields definitions for all types in list + //used to build the column array and define specific type defined for custom fields so client datatable + //knows how to format it + private Dictionary GetCustomFieldDefinitionsForList() + { + //all keys and types can go in the same list since they are unique to each type of list + //i.e. both users and widget custom fields can be in the same list + Dictionary ret = new Dictionary(); + List typesProcessed = new List(); + //custom fields handling + foreach (DataListFieldDefinition d in this.FieldDefinitions) + { + if (d.IsCustomField) + { + //this relies on the convention I'm using of SockType name as the first part of all custom fields lT keys, e.g. + //WidgetCustom1 -> Widget + var aysockTypename = d.TKey.Split("Custom")[0]; + if (!typesProcessed.Contains(aysockTypename)) + { + //make sure we do each type only once + typesProcessed.Add(aysockTypename); + //fetch it and set it + using (var ct = Sockeye.Util.ServiceProviderProvider.DBContext) + { + var fc = ct.FormCustom.AsNoTracking().SingleOrDefault(z => z.FormKey == aysockTypename); + //normal condition + if (fc == null) + continue; + + //iterate the fields and add each custom one with a type to the return dictionary + var flds = JArray.Parse(fc.Template); + foreach (JToken t in flds) + { + if (t["type"] != null) + { + ret.Add(t["fld"].Value(), t["type"].Value()); + } + } + + + } + + } + } + } + return ret; + + } + + + }//eoc + +}//eons \ No newline at end of file diff --git a/server/DataList/DataListReturnData.cs b/server/DataList/DataListReturnData.cs new file mode 100644 index 0000000..71528d5 --- /dev/null +++ b/server/DataList/DataListReturnData.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Sockeye.Models; +namespace Sockeye.DataList +{ + public class DataListReturnData + { + public object Data { get; } + public long TotalRecordCount { get; } + public object Columns { get; } + public Dictionary SortBy { get; set; } + public List Filter { get; set; } + //All columns that are hidden but are affecting the query (sorting, filtering) + //so in UI can show that there are hidden columns affecting the result set + public List HiddenAffectiveColumns {get;set;} + + public DataListReturnData(object returnItems, long totalRecordCount, Newtonsoft.Json.Linq.JArray columns, Dictionary sortBy, List filter, List hiddenAffectiveColumns) + { + Data = returnItems; + TotalRecordCount = totalRecordCount; + Columns = columns; + SortBy = sortBy; + Filter = filter; + HiddenAffectiveColumns=hiddenAffectiveColumns; + } + }//eoc + +}//eons \ No newline at end of file diff --git a/server/DataList/DataListSqlFilterCriteriaBuilder.cs b/server/DataList/DataListSqlFilterCriteriaBuilder.cs new file mode 100644 index 0000000..01c7f04 --- /dev/null +++ b/server/DataList/DataListSqlFilterCriteriaBuilder.cs @@ -0,0 +1,1179 @@ +using System.Collections.Generic; +using System; +using System.Globalization; +using System.Text; +using System.Linq; +using Sockeye.Models; +using Sockeye.Biz; +using Sockeye.Util; + +namespace Sockeye.DataList +{ + public static class DataListSqlFilterCriteriaBuilder + { + public static string DataFilterToSQLCriteria(List objectFieldsList, Models.DataListProcessingBase listOptions) + { + if (listOptions.Filter == null || listOptions.Filter.Count == 0) + return ""; + List ColumnWhereClauses = new List(); + + foreach (DataListFilterOption f in listOptions.Filter) + { + StringBuilder sb = new StringBuilder(); + DataListFieldDefinition DataListField = objectFieldsList.FirstOrDefault(z => z.FieldKey == f.Column); + var dataType = DataListField.UiFieldDataType; + + //No filtering on custom fields! + if (DataListField.IsCustomField) + { + continue; + } +#if (DEBUG) + //Developers little helper + if (DataListField == null) + { + throw new System.ArgumentNullException($"DEV ERROR in DataListSqlFilterCriteriaBuilder.cs: field {f.Column} specified in template was NOT found in ObjectFields list"); + } +#endif + + //Iterate filter items building this WHERE segment + bool ThisIsTheFirstFilterItemForThisColumn = true; + var ThereAreMultipleFilterItems = f.Items.Count > 1; + + foreach (DataListColumnFilter filterItem in f.Items) + { + //close or open another parenthetic group + + //Is this the first filter item of multiple for this field? + if (ThisIsTheFirstFilterItemForThisColumn && ThereAreMultipleFilterItems) + { + //Yup, so put the entire where group for this field in parenthesis + sb.Append("("); + } + + //Is this a subsequent filter for this column? + if (!ThisIsTheFirstFilterItemForThisColumn) + { + //Close last filter item and start new one + if (f.Any) + sb.Append(") OR ("); + else + sb.Append(") AND ("); + } + + string columnNameToFilter = string.Empty; + UiFieldDataType DataTypeToFilter = UiFieldDataType.NoType; + DataTypeToFilter = (UiFieldDataType)dataType; + columnNameToFilter = DataListField.GetSqlValueColumnName(); + //Append this filter's criteria + if (DataTypeToFilter != UiFieldDataType.Tags) + sb.Append(DataFilterToColumnCriteria(columnNameToFilter, DataTypeToFilter, filterItem.op, filterItem.value, listOptions.ClientTimeStamp)); + else + sb.Append(TagDataFilterToColumnCriteria(columnNameToFilter, filterItem.op, filterItem.value)); + ThisIsTheFirstFilterItemForThisColumn = false; + } + if (ThereAreMultipleFilterItems) + { + //The whole thing was in a group so close this group + sb.Append(")"); + } + ColumnWhereClauses.Add(sb.ToString()); + } + if (ColumnWhereClauses.Count == 0) + return string.Empty; + else + { + StringBuilder sb = new StringBuilder(); + sb.Append(" where "); + for (int i = 0; i < ColumnWhereClauses.Count; i++) + { + sb.Append("("); + sb.Append(ColumnWhereClauses[i]); + sb.Append(")"); + if (i < ColumnWhereClauses.Count - 1) + { + sb.Append(" AND "); + } + } + return sb.ToString(); + } + } + + //////////////////////////////////////////////////////////////////////// + // + /// + /// Translate DataFilter to PostgreSQL friendly SQL criteria + /// + public static string DataFilterToColumnCriteria(string SqlColumnNameToFilter, + UiFieldDataType DataType, + string sOperator, + string sValue, + DateTimeOffset clientTimeStamp) + { + StringBuilder sb = new StringBuilder(); + + //handle null values separately + if (sValue == "*NULL*") + { + //Column name + sb.Append(SqlColumnNameToFilter); //case doesn't matter for null searches + sb.Append(" "); + + switch (DataType) + { + //ALL TEXT TYPES + case UiFieldDataType.Text: + case UiFieldDataType.PhoneNumber: + case UiFieldDataType.EmailAddress: + case UiFieldDataType.HTTP: + { + if (sOperator == DataListFilterComparisonOperator.Equality) + { + + sb.Append("Is Null"); + sb.Append(" OR "); + sb.Append(SqlColumnNameToFilter); + sb.Append(" = ''"); + } + else + sb.Append(" <> ''"); + } + break; + default: + { + if (sOperator == DataListFilterComparisonOperator.Equality) + sb.Append("Is Null"); + else + sb.Append("Is Not Null"); + } + break; + } + } + else + { + //non null value + + bool ForceToLower = false; + //Force to LOWER case if it's a text data type and filter case sensitive is false + if (!ServerGlobalBizSettings.Cache.FilterCaseSensitive) + { + switch (DataType) + { + //ALL TEXT TYPES + case UiFieldDataType.Text: + case UiFieldDataType.PhoneNumber: + case UiFieldDataType.EmailAddress: + case UiFieldDataType.HTTP: + ForceToLower = true; + break; + + } + } + + //Handle nulls and also insert the column name into the query in case sensitive fashion + switch (sOperator) + { + //case DataListFilterComparisonOperator.Equality: //no specific addition on equals for nulls, let default handle it + //case DataListFilterComparisonOperator.GreaterThan: + //no specific addition on greater than for nulls + //(nulls are going to be assumed to be always at the + //less than end of the scale) so let default handle it + //case DataListFilterComparisonOperator.GreaterThanOrEqualTo: + //no change on greater than for nulls + //(nulls are going to be assumed to be always at the + //less than end of the scale) let default case handle it + + case DataListFilterComparisonOperator.LessThan: + sb.Append($"{SqlColumnNameToFilter} "); + sb.Append("Is Null OR "); + if (ForceToLower) + sb.Append($"lower({SqlColumnNameToFilter})"); + else + sb.Append(SqlColumnNameToFilter); + sb.Append(" "); + break; + case DataListFilterComparisonOperator.LessThanOrEqualTo: + sb.Append($"{SqlColumnNameToFilter} "); + sb.Append("Is Null OR "); + if (ForceToLower) + sb.Append($"lower({SqlColumnNameToFilter})"); + else + sb.Append(SqlColumnNameToFilter); + sb.Append(" "); + break; + case DataListFilterComparisonOperator.NotEqual: + sb.Append($"{SqlColumnNameToFilter} "); + //This is the big one: + sb.Append("Is Null OR "); + if (ForceToLower) + sb.Append($"lower({SqlColumnNameToFilter})"); + else + sb.Append(SqlColumnNameToFilter); + sb.Append(" "); + break; + default: + if (ForceToLower) + sb.Append($"lower({SqlColumnNameToFilter}) "); + else + sb.Append($"{SqlColumnNameToFilter} "); + break; + } + + + #region Build for specific type + switch (DataType) + { + //ALL TEXT TYPES + case UiFieldDataType.Text: + case UiFieldDataType.PhoneNumber: + case UiFieldDataType.EmailAddress: + case UiFieldDataType.HTTP: + //escape any pre-existing apostrophes + //i.e. "O'Flaherty's pub" + sValue = sValue.Replace("'", "''"); + + //case 1951 - unescape ampersands + sValue = sValue.Replace("&", "&"); + + //RAVEN NOTE: Decided not to implement TEXT token criteria for now (TTM, probably not useful) + //meaning the A-H etc group ranges by alphabet chunk + + #region Build TEXT OPS criteria + switch (sOperator) + { + case DataListFilterComparisonOperator.Equality: + if (ServerGlobalBizSettings.Cache.FilterCaseSensitive) + sb.Append($"= '{sValue}'"); + else + sb.Append($"= lower('{sValue}')"); + break; + + case DataListFilterComparisonOperator.GreaterThan: + if (ServerGlobalBizSettings.Cache.FilterCaseSensitive) + sb.Append($"> '{sValue}'"); + else + sb.Append($"> lower('{sValue}')"); + break; + + case DataListFilterComparisonOperator.GreaterThanOrEqualTo: + if (ServerGlobalBizSettings.Cache.FilterCaseSensitive) + sb.Append($">= '{sValue}'"); + else + sb.Append($">= lower('{sValue}')"); + break; + + case DataListFilterComparisonOperator.LessThan: + if (ServerGlobalBizSettings.Cache.FilterCaseSensitive) + sb.Append($"< '{sValue}'"); + else + sb.Append($"< lower('{sValue}')"); + break; + + case DataListFilterComparisonOperator.LessThanOrEqualTo: + if (ServerGlobalBizSettings.Cache.FilterCaseSensitive) + sb.Append($"<= '{sValue}'"); + else + sb.Append($"<= lower('{sValue}')"); + break; + + case DataListFilterComparisonOperator.NotEqual: + if (ServerGlobalBizSettings.Cache.FilterCaseSensitive) + sb.Append($"<> '{sValue}'"); + else + sb.Append($"<> lower('{sValue}')"); + break; + + case DataListFilterComparisonOperator.NotContains: + if (ServerGlobalBizSettings.Cache.FilterCaseSensitive) + sb.Append($"NOT LIKE '%{sValue}%'"); + else + sb.Append($"NOT LIKE lower('%{sValue}%')"); + break; + + case DataListFilterComparisonOperator.Contains: + if (ServerGlobalBizSettings.Cache.FilterCaseSensitive) + sb.Append($"LIKE '%{sValue}%'"); + else + sb.Append($"LIKE lower('%{sValue}%')"); + break; + + case DataListFilterComparisonOperator.StartsWith: + if (ServerGlobalBizSettings.Cache.FilterCaseSensitive) + sb.Append($"LIKE '{sValue}%'"); + else + sb.Append($"LIKE lower('{sValue}%')"); + break; + + case DataListFilterComparisonOperator.EndsWith: + if (ServerGlobalBizSettings.Cache.FilterCaseSensitive) + sb.Append($"LIKE '%{sValue}'"); + else + sb.Append($"LIKE lower('%{sValue}')"); + break; + + default: + throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] IN STRING"); + + } + #endregion build text ops criteria + break; + + + case UiFieldDataType.Roles: + { + /* + So Roles can be queried as contains or NOT contains with a bitwise & + also a Equals or NOt equals with a direct equality comparison + + CONTAINS: + User selects roles, query checks if a user has all the roles the user selected with this query: + single OpsAdminRestricted (8192): + select name, roles from auser where (roles & 8192 = 8192) order by name + + User has OpsAdminRestricted (8192) and SubContractorRestricted (512) which client will return as 8704: + select name, roles from auser where (roles & 8704 = 8704) order by name + brings up users with both opsadminrestricted and subconctractorrestricted and possibly others doesn't matter + + + EQUALITY: + just a simple = or <> i.e. where roles=4096 or roles!=4096 + */ + switch (sOperator) + { + case DataListFilterComparisonOperator.Equality: + sb.Append($"= {sValue} "); + break; + + case DataListFilterComparisonOperator.NotEqual: + sb.Append($"<> {sValue}"); + break; + case DataListFilterComparisonOperator.Contains: + sb.Append($"& {sValue}={sValue} "); + break; + case DataListFilterComparisonOperator.NotContains: + sb.Append($"& {sValue}!={sValue} "); + break; + default: + throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] in ROLES"); + + } + } + break; + + case UiFieldDataType.Bool: + { + switch (sOperator) + { + case DataListFilterComparisonOperator.Equality: + sb.Append("= "); + if (sValue.ToLowerInvariant() == "true") + sb.Append("true"); + else + sb.Append("false"); + break; + + case DataListFilterComparisonOperator.NotEqual: + sb.Append("<> "); + if (sValue.ToLowerInvariant() == "true") + sb.Append("true"); + else + sb.Append("false"); + + break; + default: + throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] in BOOL"); + + } + } + break; + + //Note there are three types here for display purposes but all are stored in the db as a timestamp the same with date and time components + case UiFieldDataType.Date: + case UiFieldDataType.DateTime: + case UiFieldDataType.Time: + { + + if (sValue.StartsWith("*") && sValue.EndsWith("*")) + { + //Note: due to date token relativity, all below calcs done with Client provided current date/time and converted to UTC only at the end for sql + DateTime ClientToday = clientTimeStamp.Date; + DateTime ClientNow = clientTimeStamp.DateTime; + + #region Build criteria for date RANGE TOKEN specified + + //Used as the basis point + System.DateTime dtAfter; + System.DateTime dtBefore; + switch (sValue) + { + //Case 402 + case "*yesterday*": + //Between Day before yesterday at midnight and yesterday at midnight + dtAfter = ClientToday.AddDays(-1); + dtAfter = dtAfter.AddSeconds(-1); + dtBefore = ClientToday; + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*today*": + //Between yesterday at midnight and tommorow at midnight + dtAfter = ClientToday.AddSeconds(-1); + dtBefore = ClientToday.AddDays(1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + case "*tomorrow*": + //Between Tonight at midnight and day after tommorow at midnight + dtAfter = ClientToday.AddDays(1).AddSeconds(-1); + dtBefore = ClientToday.AddDays(2); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + case "*lastweek*": + //Between two Sundays ago at midnight and last sunday at midnight + dtAfter = ClientToday; + //go back a week + dtAfter = dtAfter.AddDays(-7); + //go backwards to Sunday + while (dtAfter.DayOfWeek != DayOfWeek.Sunday) + dtAfter = dtAfter.AddDays(-1); + //go to very start of eighth dayahead + dtBefore = dtAfter.AddDays(8); + dtAfter = dtAfter.AddSeconds(-1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + case "*thisweek*": + //Between Sunday at midnight and Next sunday at midnight + dtAfter = ClientToday; + //go backwards to monday + while (dtAfter.DayOfWeek != DayOfWeek.Monday) + dtAfter = dtAfter.AddDays(-1); + + //Now go back to sunday last second + dtAfter = dtAfter.AddSeconds(-1); + + dtBefore = ClientToday; + //go forwards to monday + if (ClientToday.DayOfWeek == DayOfWeek.Monday) + { + //Monday today? then go to next monday + dtBefore = dtBefore.AddDays(7); + } + else + { + while (dtBefore.DayOfWeek != DayOfWeek.Monday) + dtBefore = dtBefore.AddDays(1); + } + + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + case "*nextweek*": + //Between Next Sunday at midnight and Next Next sunday at midnight + dtAfter = ClientToday; + + //If today is monday skip over it first + if (dtAfter.DayOfWeek == DayOfWeek.Monday) + dtAfter = dtAfter.AddDays(1); + + //go forwards to next monday + while (dtAfter.DayOfWeek != DayOfWeek.Monday) + dtAfter = dtAfter.AddDays(1); + //Now go back to sunday last second + dtAfter = dtAfter.AddDays(-1); + //go seven days ahead + dtBefore = dtAfter.AddDays(7); + dtAfter = dtAfter.AddSeconds(-1); + + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + case "*lastmonth*": + //start with the first day of this month + dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, 1); + //subtract a Month + dtAfter = dtAfter.AddMonths(-1); + + //Add one month to dtAfter to get end date + dtBefore = dtAfter.AddMonths(1); + + //case 1155 + dtAfter = dtAfter.AddSeconds(-1); + + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + case "*thismonth*": + //start with the first day of this month + dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, 1); + + //Add one month to dtAfter to get end date + dtBefore = dtAfter.AddMonths(1); + + //case 1155 + dtAfter = dtAfter.AddSeconds(-1); + + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*nextmonth*": + //start with the first day of this month + dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, 1); + //Add a Month + dtAfter = dtAfter.AddMonths(1); + + //Add one month to dtAfter to get end date + dtBefore = dtAfter.AddMonths(1); + + //case 1155 + dtAfter = dtAfter.AddSeconds(-1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*14daywindow*": + //start with today zero hour + dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, ClientToday.Day); + dtAfter = dtAfter.AddDays(-7); + + //Add 15 days to get end date (zero hour so not really 15 full days) + dtBefore = dtAfter.AddDays(15); + + //case 1155 + dtAfter = dtAfter.AddSeconds(-1); + + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*past*": + //Forever up to Now + dtAfter = new DateTime(1753, 1, 2);//this was for sql server but even then was probably outdated good enough though for our purposes + dtBefore = ClientNow; + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*future*": + //From Now to forever (999 years from now) + dtAfter = ClientNow; + dtBefore = dtAfter.AddYears(999); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*lastyear*": + //From zero hour january 1 a year ago + dtAfter = new DateTime(ClientNow.AddYears(-1).Year, 1, 1, 0, 0, 00).AddSeconds(-1); + //To zero hour January 1 this year + dtBefore = new DateTime(ClientNow.Year, 1, 1, 0, 0, 00); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*thisyear*": + //From zero hour january 1 this year + dtAfter = new DateTime(ClientNow.Year, 1, 1).AddSeconds(-1); + //To zero hour Jan 1 next year + dtBefore = new DateTime(ClientNow.AddYears(1).Year, 1, 1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*last3months*": + //From Now minus 3 months + dtAfter = ClientNow.AddMonths(-3).AddSeconds(-1); + //To Now + dtBefore = ClientNow; + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*last6months*": + //From Now minus 6 months + dtAfter = ClientNow.AddMonths(-6).AddSeconds(-1); + //To Now + dtBefore = ClientNow; + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*pastyear*": //within the prior 365 days before today + //From Now minus 365 days + dtAfter = ClientNow.AddDays(-365).AddSeconds(-1); + //To Now + dtBefore = ClientNow; + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*past90days*": + //From Now minus 90 days + dtAfter = ClientNow.AddDays(-90).AddSeconds(-1); + //To Now + dtBefore = ClientNow; + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + case "*past30days*": + //From Now minus 30 days + dtAfter = ClientNow.AddDays(-30).AddSeconds(-1); + //To Now + dtBefore = ClientNow; + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + case "*past7days*": + //From Now minus 7 days + dtAfter = ClientNow.AddDays(-7).AddSeconds(-1); + //To Now + dtBefore = ClientNow; + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*past24hours*": + //From Now minus 24 hours + dtAfter = ClientNow.AddHours(-24).AddSeconds(-1); + //To Now + dtBefore = ClientNow; + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*past6hours*": + //From Now minus 6 hours + dtAfter = ClientNow.AddHours(-6).AddSeconds(-1); + //To Now + dtBefore = ClientNow; + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*january*": + //From zero hour january 1 this year + dtAfter = new DateTime(ClientNow.Year, 1, 1).AddSeconds(-1); + //To zero hour feb 1 this year + dtBefore = new DateTime(ClientNow.Year, 2, 1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*february*": + dtAfter = new DateTime(ClientNow.Year, 2, 1).AddSeconds(-1); + dtBefore = new DateTime(ClientNow.Year, 3, 1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*march*": + dtAfter = new DateTime(ClientNow.Year, 3, 1).AddSeconds(-1); + dtBefore = new DateTime(ClientNow.Year, 4, 1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*april*": + dtAfter = new DateTime(ClientNow.Year, 4, 1).AddSeconds(-1); + dtBefore = new DateTime(ClientNow.Year, 5, 1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*may*": + dtAfter = new DateTime(ClientNow.Year, 5, 1).AddSeconds(-1); + dtBefore = new DateTime(ClientNow.Year, 6, 1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*june*": + dtAfter = new DateTime(ClientNow.Year, 6, 1).AddSeconds(-1); + dtBefore = new DateTime(ClientNow.Year, 7, 1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*july*": + dtAfter = new DateTime(ClientNow.Year, 7, 1).AddSeconds(-1); + dtBefore = new DateTime(ClientNow.Year, 8, 1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*august*": + dtAfter = new DateTime(ClientNow.Year, 8, 1).AddSeconds(-1); + dtBefore = new DateTime(ClientNow.Year, 9, 1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*september*": + dtAfter = new DateTime(ClientNow.Year, 9, 1).AddSeconds(-1); + dtBefore = new DateTime(ClientNow.Year, 10, 1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*october*": + dtAfter = new DateTime(ClientNow.Year, 10, 1).AddSeconds(-1); + dtBefore = new DateTime(ClientNow.Year, 11, 1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*november*": + dtAfter = new DateTime(ClientNow.Year, 11, 1).AddSeconds(-1); + dtBefore = new DateTime(ClientNow.Year, 12, 1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*december*": + //From zero hour dec 1 this year + dtAfter = new DateTime(ClientNow.Year, 12, 1).AddSeconds(-1); + //To zero hour Jan 1 next year + dtBefore = new DateTime(ClientNow.AddYears(1).Year, 1, 1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + case "*lastyearlastmonth*": + //start with the first day of this month + dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, 1); + //subtract a year and a Month + dtAfter = dtAfter.AddYears(-1).AddMonths(-1); + //Add one month to dtAfter to get end date + dtBefore = dtAfter.AddMonths(1); + dtAfter = dtAfter.AddSeconds(-1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*lastyearthismonth*": + //start with the first day of this month + dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, 1); + //subtract a year + dtAfter = dtAfter.AddYears(-1); + //Add one month to dtAfter to get end date + dtBefore = dtAfter.AddMonths(1); + dtAfter = dtAfter.AddSeconds(-1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + case "*lastyearnextmonth*": + //start with the first day of this month + dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, 1); + //subtract a year + dtAfter = dtAfter.AddYears(-1); + //Add a Month + dtAfter = dtAfter.AddMonths(1); + //Add one month to dtAfter to get end date + dtBefore = dtAfter.AddMonths(1); + dtAfter = dtAfter.AddSeconds(-1); + BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore); + break; + + default: + throw new System.ArgumentOutOfRangeException("TOKEN", sOperator, "DataListSqlFilterCriteriaBuilder invalid filter TOKEN type [" + sValue + "] IN DATE_TIME"); + + //----- + } + + #endregion + } + else + { + + + #region Build criteria for date specified + //dates come in iso8601 UTC format from the client + //suitable for the database to handle as all database dates are in UTC + //Local display and parsing will be considered a CLIENT issue at all times + + //comes in UTC, parse converts it to local but touniversal puts it back in utc, weird but only thing that consistently work + System.DateTime dtData = DateTime.Parse(sValue).ToUniversalTime(); + + //Filter time resolution is in minutes only, you can't select seconds in a filter + //so the filter code below must construct a filter time + //that falls within the 0th second, 0th millisecond and 59th second and 999th millisecond of the specified time + //to ensure it matches the users selections + string sDateValueWithZeroSeconds = PostgresDateFormat(ZeroSeconds(dtData)); + string sDateValueWithMaxSeconds = PostgresDateFormat(MaxSeconds(dtData)); + + switch (sOperator) + { + case DataListFilterComparisonOperator.Equality: + sb.Append(">='"); + sb.Append(sDateValueWithZeroSeconds); + sb.Append("' AND "); + sb.Append(SqlColumnNameToFilter); + sb.Append(" "); + sb.Append("<='"); + sb.Append(sDateValueWithMaxSeconds); + sb.Append("'"); + break; + + case DataListFilterComparisonOperator.GreaterThan: + sb.Append(">'"); + sb.Append(sDateValueWithMaxSeconds); + sb.Append("'"); + break; + + case DataListFilterComparisonOperator.GreaterThanOrEqualTo: + sb.Append(">='"); + sb.Append(sDateValueWithMaxSeconds); + sb.Append("'"); + break; + case DataListFilterComparisonOperator.LessThan: + sb.Append("<'"); + sb.Append(sDateValueWithMaxSeconds); + sb.Append("'"); + break; + case DataListFilterComparisonOperator.LessThanOrEqualTo: + sb.Append("<='"); + sb.Append(sDateValueWithMaxSeconds); + sb.Append("'"); + break; + + case DataListFilterComparisonOperator.NotEqual: + sb.Append("<'"); + sb.Append(sDateValueWithMaxSeconds); + sb.Append("' OR "); + sb.Append(SqlColumnNameToFilter); + sb.Append(" "); + sb.Append(">'"); + sb.Append(sDateValueWithMaxSeconds); + sb.Append("'"); + + break; + default: + throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] IN DATE_TIME"); + + + } + #endregion + } + } + break; + case UiFieldDataType.Enum://enums are just ints to the db, but it's a special type so the client can recognize it + case UiFieldDataType.MemorySize://memory / file size, just a long but this type is for display formatting only + case UiFieldDataType.Decimal: + case UiFieldDataType.Currency: + case UiFieldDataType.InternalId: + case UiFieldDataType.Integer: //whole numbers, not only integer + { + //case 1795 - it's numeric, convert to translation independent format + //RAVEN NOTE: no numbers are coming from the client in any culture format other than xx,xxx.00 but this is just insurance for api users + NumberFormatInfo nfi = System.Globalization.CultureInfo.CurrentCulture.NumberFormat; + switch (DataType) + { + case UiFieldDataType.Decimal: + case UiFieldDataType.Currency: + { + if (nfi.CurrencyDecimalSeparator != ".") + { + sValue = sValue.Replace(nfi.CurrencyGroupSeparator, ""); + sValue = sValue.Replace(nfi.CurrencyDecimalSeparator, "."); + } + } + break; + case UiFieldDataType.Integer: + { + if (nfi.NumberDecimalSeparator != ".") + { + sValue = sValue.Replace(nfi.NumberGroupSeparator, ""); + sValue = sValue.Replace(nfi.NumberDecimalSeparator, "."); + } + } + break; + case UiFieldDataType.InternalId: + { + //do nothing, it's a simple number + } + break; + } + + switch (sOperator) + { + case DataListFilterComparisonOperator.Equality: + sb.Append("="); + sb.Append(sValue); + break; + case DataListFilterComparisonOperator.GreaterThan: + sb.Append(">"); + sb.Append(sValue); + break; + case DataListFilterComparisonOperator.GreaterThanOrEqualTo: + sb.Append(">="); + sb.Append(sValue); + break; + case DataListFilterComparisonOperator.LessThan: + sb.Append("<"); + sb.Append(sValue); + break; + case DataListFilterComparisonOperator.LessThanOrEqualTo: + sb.Append("<="); + sb.Append(sValue); + break; + case DataListFilterComparisonOperator.NotEqual: + sb.Append("<>"); + sb.Append(sValue); + break; + default: + throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] IN NUMBER"); + + } + break; + } + + case UiFieldDataType.TimeSpan: //TIMESPAN / DURATION + { + sValue = TimeSpanToPostgresInterval(sValue); + /* + { name: vm.$sock.t("GridRowFilterDropDownGreaterThan"), id: ">" }, + { + name: vm.$sock.t("GridRowFilterDropDownGreaterThanOrEqualTo"), + id: ">=" + }, + { name: vm.$sock.t("GridRowFilterDropDownLessThan"), id: "<" }, + { name: vm.$sock.t("GridRowFilterDropDownLessThanOrEqualTo"), id: "<=" }, + { name: vm.$sock.t("GridRowFilterDropDownNotEquals"), id: "!=" } + */ + switch (sOperator) + { + case DataListFilterComparisonOperator.Equality: + sb.Append("="); + sb.Append(sValue); + break; + case DataListFilterComparisonOperator.GreaterThan: + sb.Append(">"); + sb.Append(sValue); + break; + case DataListFilterComparisonOperator.GreaterThanOrEqualTo: + sb.Append(">="); + sb.Append(sValue); + break; + case DataListFilterComparisonOperator.LessThan: + sb.Append("<"); + sb.Append(sValue); + break; + case DataListFilterComparisonOperator.LessThanOrEqualTo: + sb.Append("<="); + sb.Append(sValue); + break; + case DataListFilterComparisonOperator.NotEqual: + sb.Append("<>"); + sb.Append(sValue); + break; + default: + throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] IN TIMESPAN / DURATION"); + + } + break; + } + default: + throw new System.ArgumentOutOfRangeException("DATA_TYPE", DataType, "DataListSqlFilterCriteriaBuilder unhandled data type[" + DataType + "]"); + } + #endregion + }//end of nonnull path + + return sb.ToString(); + + + } + + + + //////////////////////////////////////////////////////////////////////// + // + /// + /// Translate TAG DataFilter to PostgreSQL friendly SQL criteria + /// + public static string TagDataFilterToColumnCriteria(string SqlColumnNameToFilter, string sOperator, string sValue) + { + + /* + https://www.postgresql.org/docs/current/functions-array.html + + Filter ui builder allows multiple tag selection just like any other tag selection and sends the filter to the server like this: + filter "[{\"column\":\"customertags\",\"any\":false,\"items\":[{\"op\":\"=\",\"value\":\"completed.reminder,burnaby.dispatchzone,cabling-fibre.user-skill\"}]}]" + Comma separated list. + Current equalto filter doesn't work with this type of query as it directly compares it as if it was one string instead of does it contain all those terms + + ### FILTERS AVAILABLE ### + + Equality: Exactly equal (aside from order) compare entire search term array to tag array in db - all terms in search exactly present and no others in db record + contains and also length is same as search terms length, in this example 1 is the number of search terms as the search is for {'green'} + WHERE ARRAY['green'::varchar(255)] <@ tags and array_length(tags,1) = 1 + + Not equal: Not EXACTLY Equal (aside from order) All terms in search term array NOT present in All terms in db record. IOW if db record has all search terms but also one more tag then it's NOT equal, Order doesn't matter + Specifically exclude rows that exactly match all the search terms + WHERE NOT (ARRAY['green'::varchar(255)] <@ tags and array_length(tags,1) = 1) + + NO value: Empty tag array in db record + db record tags has NO value + where tags = '{}' + + Has value: Non empty tag array in db record + db record tags has value + where tags <> '{}' + + Contains: All terms in search query present in db record, db record may have other tags but that's ok as long as it has the search term ones (order insensitive) + db record Tags contains search tags (and maybe also other tags) + WHERE ARRAY['red','green'::varchar(255)] <@ tags + + Not contains: All terms in search query *NOT* present in db record, don't care what else is in db record as long as it's not the search terms (order insensitive) + db record Tags DOES NOT contain search tags + WHERE NOT ARRAY['red','green'::varchar(255)] <@ tags + + This method and choices ensures users don't need to make a separate row for each one they can just treat the entire tag collection like they treat a single word match in a text field + */ + + //HAS VALUE / NO VALUE + if (sValue == "*NULL*") + { + if (sOperator == DataListFilterComparisonOperator.Equality) + return $"{SqlColumnNameToFilter} = '{{}}'"; + else + return $"{SqlColumnNameToFilter} <> '{{}}'"; + } + else + { + string PostgresTagArrayFragment = string.Empty; + //Filter ui builder allows multiple tag selection just like any other tag selection and sends the filter to the server like this: + //filter "[{\"column\":\"customertags\",\"any\":false,\"items\":[{\"op\":\"=\",\"value\":\"completed.reminder,burnaby.dispatchzone,cabling-fibre.user-skill\"}]}]" + //needs to be "ARRAY['red','green'::varchar(255)]" + StringBuilder sbTemp = new StringBuilder(); + sbTemp = sbTemp.Append("ARRAY["); + List normalizedTags = TagBiz.NormalizeTags(sValue.Split(',').ToList()); + if (normalizedTags.Count == 0) + throw new System.ArgumentNullException("DataListSqlFilterCriteriaBuilder::TagDataFilterToColumnCriteria - NO tags were provided for filtering"); + + foreach (string s in normalizedTags) + { + //escape any pre-existing apostrophes + //i.e. "O'Flaherty's pub" + var cleaned = s.Replace("'", "''"); + sbTemp.Append($"'{cleaned}',"); + } + PostgresTagArrayFragment = sbTemp.ToString().TrimEnd(','); + PostgresTagArrayFragment += "::VARCHAR(255)]"; + + switch (sOperator) + { + case DataListFilterComparisonOperator.Equality: + //ARRAY['green'::varchar(255)] <@ tags and array_length(tags,1) = 1 + return $"{PostgresTagArrayFragment} <@ {SqlColumnNameToFilter} AND ARRAY_LENGTH({SqlColumnNameToFilter},1) = {normalizedTags.Count}"; + case DataListFilterComparisonOperator.NotEqual: + //NOT (ARRAY['green'::varchar(255)] <@ tags and array_length(tags,1) = 1) + return $"NOT ({PostgresTagArrayFragment} <@ {SqlColumnNameToFilter} AND ARRAY_LENGTH({SqlColumnNameToFilter},1) = {normalizedTags.Count})"; + case DataListFilterComparisonOperator.NotContains: + //NOT ARRAY['red','green'::varchar(255)] <@ tags + return $"NOT {PostgresTagArrayFragment} <@ {SqlColumnNameToFilter}"; + case DataListFilterComparisonOperator.Contains: + //ARRAY['red','green'::varchar(255)] <@ tags + return $"{PostgresTagArrayFragment} <@ {SqlColumnNameToFilter}"; + default: + throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] IN TAGS"); + } + } + } + + + + + //This is only used by the token date query code above and that code does NOT convert to UTC time to match the DB so this function will handle that + //The other non tokenized date criteria builders are all working with dates that come from the client in UTC already and they don't use this method + //so nothing required there + private static void BuildSQLBetweenTwoTokenDerivedDates(string sColumn, StringBuilder sb, DateTime dtAfter, DateTime dtBefore) + { + + sb.Append(">'"); + sb.Append(PostgresDateFormat(MaxSeconds(dtAfter.ToUniversalTime()))); + sb.Append("' AND "); + sb.Append(sColumn); + sb.Append(" "); + sb.Append("<'"); + sb.Append(PostgresDateFormat(ZeroSeconds(dtBefore.ToUniversalTime()))); + sb.Append("'"); + } + + + private static DateTime ZeroSeconds(DateTime d) + { + return new DateTime(d.Year, d.Month, d.Day, d.Hour, d.Minute, 0, DateTimeKind.Utc); + } + + + + private static DateTime MaxSeconds(DateTime d) + { + return new DateTime(d.Year, d.Month, d.Day, d.Hour, d.Minute, 59, 999, DateTimeKind.Utc); + } + + + /// + /// PostgreSQL compatible date format + /// https://www.postgresql.org/docs/current/datsockType-datetime.html#DATATYPE-DATETIME-DATE-TABLE + /// + /// + private static string PostgresDateFormat(DateTime theDate) + { + // ISO8601 with 7 decimal places + return theDate.ToString("o", CultureInfo.InvariantCulture); + } + + + + /////////////////////////////////////////// + // + // + private static string TimeSpanToPostgresInterval(string value) + { + //ISO 8601 duration format "brief" or short + // "5.0:0:0" <-what it looks like as a timespan string + // P6Y5M4DT3H2M1S <- What a postgres compatible iso 8601 format looks like + // interval 'P6Y5M4DT3H2M1S' <- actual query fragment + + int theDays = 0; + int theHours = 0; + int theMinutes = 0; + int theSeconds = 0; + + if (string.IsNullOrWhiteSpace(value)) + { + return "INTERVAL 'P0DT0H0M0S'"; + } + else + { + var work = value.Split(":"); + //has days? + if (work[0].Contains(".")) + { + var dh = work[0].Split("."); + theDays = int.Parse(dh[0]); + theHours = int.Parse(dh[1]); + } + else + { + theHours = int.Parse(work[0]); + } + theMinutes = int.Parse(work[1]); + //has milliseconds? (ignore them) + if (work[2].Contains(".")) + { + var dh = work[2].Split("."); + theSeconds = int.Parse(dh[0]); + } + else + { + theSeconds = int.Parse(work[2]); + } + + //format return string + return $"INTERVAL 'P{theDays}DT{theHours}H{theMinutes}M{theSeconds}S'"; + + } + } + + + //////////////////////////////////////////////////////////////////////// + /// + /// Translate tag filter to PostgreSQL friendly SQL criteria + /// + /// + public static string TagFilterToSqlCriteriaHelper(string SqlColumnNameToFilter, List sValue, bool bAny) + { + if(sValue.Count==0) return string.Empty; + //if it's an OR (any) query then need to build individual terms, if it's not then it's just an all or AND query and can pass through as is + if (sValue.Count > 0 && bAny == true) + { + StringBuilder sb = new StringBuilder(); + sb.Append(" AND "); + sb.Append("("); + foreach (string s in sValue) + { + sb.Append($"({TagDataFilterToColumnCriteria(SqlColumnNameToFilter, DataListFilterComparisonOperator.Contains, s)}) OR "); + } + var ret = sb.ToString(); + return ret.Substring(0, ret.LastIndexOf(" OR ")) + ")"; + } + else + { + //it's an AND (not any) query so it's just all inclusive but still contains + return " AND " + TagDataFilterToColumnCriteria(SqlColumnNameToFilter, DataListFilterComparisonOperator.Contains, string.Join(",", sValue)); + } + } + + + + }//eoc +}//ens \ No newline at end of file diff --git a/server/DataList/DataListSqlFilterOrderByBuilder.cs b/server/DataList/DataListSqlFilterOrderByBuilder.cs new file mode 100644 index 0000000..7c17512 --- /dev/null +++ b/server/DataList/DataListSqlFilterOrderByBuilder.cs @@ -0,0 +1,51 @@ +using System.Text; +using System.Linq; +using System.Collections.Generic; + +namespace Sockeye.DataList +{ + public static class DataListSqlFilterOrderByBuilder + { + public static string DataFilterToSQLOrderBy(List objectFieldsList, Models.DataListProcessingBase listOptions) + { + StringBuilder sb = new StringBuilder(); + bool SortItemAdded = false; + foreach (KeyValuePair kvSort in listOptions.SortBy) + { + //Get the correct sql column name + DataListFieldDefinition DataListField = objectFieldsList.FirstOrDefault(z => z.FieldKey == kvSort.Key); + //No sorting on custom fields! + if (DataListField.IsCustomField) + continue; +#if (DEBUG) + //Developers little helper + if (DataListField == null) + throw new System.ArgumentNullException($"DEV ERROR in DataListSqlFilterOrderByBuilder.cs: field {kvSort.Key} specified in template was NOT found in ObjectFields list"); + +#endif + var SQLValueColumnName = DataListField.GetSqlValueColumnName(); + if (SortItemAdded) + sb.Append(", "); + else + sb.Append(" "); + sb.Append(SQLValueColumnName); + sb.Append(" "); + sb.Append(kvSort.Value == "+" ? "ASC" : "DESC"); + SortItemAdded = true; + } + + if (sb.Length == 0) + { + //no sort specified so default it + DataListFieldDefinition rid = objectFieldsList.FirstOrDefault(z => z.IsRowId == true); + if (rid != null) + return $"ORDER BY {rid.SqlIdColumnName} DESC"; + else + return string.Empty; //no default column so no idea how to sort + } + else + return "ORDER BY" + sb.ToString(); + } + + }//eoc +}//ens diff --git a/server/DataList/DataListSqlSelectBuilder.cs b/server/DataList/DataListSqlSelectBuilder.cs new file mode 100644 index 0000000..383e450 --- /dev/null +++ b/server/DataList/DataListSqlSelectBuilder.cs @@ -0,0 +1,126 @@ +using System.Collections.Generic; +using System.Text; +using System.Linq; +using Sockeye.Models; + +namespace Sockeye.DataList +{ + internal class SqlSelectBuilderResult + { + internal Dictionary map { get; set; } + internal string Select { get; set; } + } + internal static class DataListSqlSelectBuilder + { + + //Build the SELECT portion of a list query based on the columns + internal static SqlSelectBuilderResult BuildForDataTableListResponse(List objectFieldsList, List columns) + { + StringBuilder sb = new StringBuilder(); + sb.Append("SELECT "); + //keep track of which custom fields columns were added already + //this ensures that if there is more than one set of custom fields like from two different objects in the list + //only unique ones will be returned by query + //map sql column name to ordinal name + Dictionary map = new Dictionary(); + int nOrdinal = 0; + + var firstColumnAdded = false; + foreach (string ColumnName in columns) + { + DataListFieldDefinition o = objectFieldsList.FirstOrDefault(z => z.FieldKey == ColumnName); +#if (DEBUG) + //Developers little helper + if (o == null) + { + throw new System.ArgumentNullException($"## DEV ERROR in DataListSqlSelectBuilder.cs:BuildForDataTableListResponse() field {ColumnName} specified in columns was NOT found in the data list's ObjectFields list, a defined fieldkey name differs from the columns key name"); + } +#endif + if (o != null) + {//Ignore missing fields in production + if (o.IsCustomField) + { //if any are custom field then add custom fields column to query + var CustomFieldSqlColumnName = o.GetSqlValueColumnName(); + //has it been added yet? + if (!map.ContainsKey(CustomFieldSqlColumnName)) + { //nope + if (firstColumnAdded) + sb.Append(", "); + sb.Append(CustomFieldSqlColumnName); + firstColumnAdded = true; + map.Add(CustomFieldSqlColumnName, nOrdinal++); + } + } + else + { + var valueColumnName = o.GetSqlValueColumnName(); + if (!map.ContainsKey(valueColumnName)) + { + if (firstColumnAdded) + sb.Append(", "); + sb.Append(valueColumnName); + firstColumnAdded = true; + map.Add(valueColumnName, nOrdinal++); + } + + //does it also have an ID column? + var idColumnName = o.SqlIdColumnName; + if (!string.IsNullOrWhiteSpace(idColumnName)) + { + if (!map.ContainsKey(idColumnName)) + { + if (firstColumnAdded) + sb.Append(", "); + sb.Append(idColumnName); + firstColumnAdded = true; + map.Add(idColumnName, nOrdinal++); + } + } + + //does it also have an openable SockType column? + var sockTypeColumnName = o.SqlATypeColumnName; + if (!string.IsNullOrWhiteSpace(sockTypeColumnName)) + { + if (!map.ContainsKey(sockTypeColumnName)) + { + if (firstColumnAdded) + sb.Append(", "); + sb.Append(sockTypeColumnName); + firstColumnAdded = true; + map.Add(sockTypeColumnName, nOrdinal++); + } + } + + //does it also have a Color column? + var ayaColorColumnName = o.SqlColorColumnName; + if (!string.IsNullOrWhiteSpace(ayaColorColumnName)) + { + if (!map.ContainsKey(ayaColorColumnName)) + { + if (firstColumnAdded) + sb.Append(", "); + sb.Append(ayaColorColumnName); + firstColumnAdded = true; + map.Add(ayaColorColumnName, nOrdinal++); + } + } + } + } + } + return new SqlSelectBuilderResult() { map = map, Select = sb.ToString() }; + }//eom + + + //Build the SELECT portion of a list query but only to return rowid's + internal static string BuildForIdListResponse(List fieldDefinitions, DataListSelectedProcessingOptions dataListSelectionOptions) + { + StringBuilder sb = new StringBuilder(); + sb.Append("SELECT "); + //note: only need rowid column for these queries, the where conditions don't rely on any defined column names as they explicitly refer to the exact identifier known to postgres + //Note: IsRowId field should *always* exist for any list that is intended to be used in an idlist response + var o = fieldDefinitions.FirstOrDefault(z => z.IsRowId == true); + sb.Append(o.SqlIdColumnName); + return sb.ToString(); + }//eom + }//eoc +}//ens diff --git a/server/DataList/EventDataList.cs b/server/DataList/EventDataList.cs new file mode 100644 index 0000000..84a218a --- /dev/null +++ b/server/DataList/EventDataList.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using Sockeye.Biz; +namespace Sockeye.DataList +{ + internal class EventDataList : DataListProcessingBase + { + public EventDataList(long translationId) + { + //NOTE: used this type because it's full BizFull and read only Bizrestricted only which is appropriate and there is no event type + DefaultListAType = SockType.Global; + SQLFrom = "from aevent left join auser on (aevent.userid=auser.id)"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "eventcreated", "event", "object", "SockType", "username", "textra" }; + DefaultSortBy = new Dictionary() { { "eventcreated", "-" } }; + + + FieldDefinitions = new List(); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "EventCreated", + FieldKey = "eventcreated", + UiFieldDataType = (int)UiFieldDataType.DateTime, + SqlValueColumnName = "aevent.created" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Event", + FieldKey = "event", + UiFieldDataType = (int)UiFieldDataType.Enum, + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(SockEvent).ToString()), + SqlValueColumnName = "aevent.ayevent" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "SockType", + FieldKey = "SockType", + UiFieldDataType = (int)UiFieldDataType.Enum, + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(SockType).ToString()), + SqlValueColumnName = "aevent.socktype" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Object", + FieldKey = "object", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "aevent.ayid", + SqlValueColumnName = $"AYGETNAME(aevent.ayid, aevent.socktype,{translationId})", + SqlATypeColumnName = "aevent.socktype" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + FieldKey = "username", + TKey = "User", + UiFieldDataType = (int)UiFieldDataType.Text, + SockType = (int)SockType.User, + SqlIdColumnName = "auser.id", + SqlValueColumnName = "auser.name" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "EventTextra", + FieldKey = "textra", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "aevent.textra" + }); + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/HeadOfficeDataList.cs b/server/DataList/HeadOfficeDataList.cs new file mode 100644 index 0000000..4099842 --- /dev/null +++ b/server/DataList/HeadOfficeDataList.cs @@ -0,0 +1,240 @@ +using System.Collections.Generic; +using Sockeye.Biz; +namespace Sockeye.DataList +{ + internal class HeadOfficeDataList : DataListProcessingBase + { + public HeadOfficeDataList(long translationId) + { + DefaultListAType = SockType.HeadOffice; + SQLFrom = "from aheadoffice"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "headofficename", "headofficephone1", "headofficeemail" }; + DefaultSortBy = new Dictionary() { { "headofficename", "+" } }; + + FieldDefinitions = new List(); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "HeadOfficeName", + FieldKey = "headofficename", + SockType = (int)SockType.HeadOffice, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "aheadoffice.id", + SqlValueColumnName = "aheadoffice.name", + IsRowId = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "HeadOfficeNotes", + FieldKey = "headofficenotes", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "aheadoffice.notes" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Active", + FieldKey = "headofficeactive", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "aheadoffice.active" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Tags", + FieldKey = "headofficetags", + UiFieldDataType = (int)UiFieldDataType.Tags, + SqlValueColumnName = "aheadoffice.tags" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "WebAddress", + FieldKey = "headofficewebaddress", + UiFieldDataType = (int)UiFieldDataType.HTTP, + SqlValueColumnName = "aheadoffice.webaddress" + }); + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "HeadOfficeAccountNumber", + FieldKey = "headofficeaccountnumber", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "aheadoffice.accountnumber" + }); + + + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "HeadOfficePhone1", + FieldKey = "headofficephone1", + UiFieldDataType = (int)UiFieldDataType.PhoneNumber, + SqlValueColumnName = "aheadoffice.phone1" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "HeadOfficePhone2", + FieldKey = "headofficephone2", + UiFieldDataType = (int)UiFieldDataType.PhoneNumber, + SqlValueColumnName = "aheadoffice.phone2" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "HeadOfficePhone3", + FieldKey = "headofficephone3", + UiFieldDataType = (int)UiFieldDataType.PhoneNumber, + SqlValueColumnName = "aheadoffice.phone3" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "HeadOfficePhone4", + FieldKey = "headofficephone4", + UiFieldDataType = (int)UiFieldDataType.PhoneNumber, + SqlValueColumnName = "aheadoffice.phone4" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "HeadOfficePhone5", + FieldKey = "headofficephone5", + UiFieldDataType = (int)UiFieldDataType.PhoneNumber, + SqlValueColumnName = "aheadoffice.phone5" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "HeadOfficeEmail", + FieldKey = "headofficeemail", + UiFieldDataType = (int)UiFieldDataType.EmailAddress, + SqlValueColumnName = "aheadoffice.emailaddress" + }); + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressPostalDeliveryAddress", + FieldKey = "headofficepostaddress", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "aheadoffice.postaddress" + }); + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressPostalCity", + FieldKey = "headofficepostcity", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "aheadoffice.postcity" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressPostalStateProv", + FieldKey = "headofficepostregion", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "aheadoffice.postregion" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressPostalCountry", + FieldKey = "headofficepostcountry", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "aheadoffice.postcountry" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressPostalPostal", + FieldKey = "headofficepostcode", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "aheadoffice.postcode" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressDeliveryAddress", + FieldKey = "headofficeaddress", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "aheadoffice.address" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressCity", + FieldKey = "headofficecity", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "aheadoffice.city" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressStateProv", + FieldKey = "headofficeregion", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "aheadoffice.region" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressCountry", + FieldKey = "headofficecountry", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "aheadoffice.country" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressPostal", + FieldKey = "headofficeaddresspostal", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "aheadoffice.addresspostal" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressLatitude", + FieldKey = "headofficelatitude", + UiFieldDataType = (int)UiFieldDataType.Decimal, + SqlValueColumnName = "aheadoffice.latitude" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AddressLongitude", + FieldKey = "headofficelongitude", + UiFieldDataType = (int)UiFieldDataType.Decimal, + SqlValueColumnName = "aheadoffice.longitude" + }); + + + + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom1", FieldKey = "headofficecustom1", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom2", FieldKey = "headofficecustom2", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom3", FieldKey = "headofficecustom3", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom4", FieldKey = "headofficecustom4", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom5", FieldKey = "headofficecustom5", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom6", FieldKey = "headofficecustom6", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom7", FieldKey = "headofficecustom7", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom8", FieldKey = "headofficecustom8", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom9", FieldKey = "headofficecustom9", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom10", FieldKey = "headofficecustom10", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom11", FieldKey = "headofficecustom11", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom12", FieldKey = "headofficecustom12", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom13", FieldKey = "headofficecustom13", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom14", FieldKey = "headofficecustom14", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom15", FieldKey = "headofficecustom15", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom16", FieldKey = "headofficecustom16", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" }); + } + + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/IDataListInternalCriteria.cs b/server/DataList/IDataListInternalCriteria.cs new file mode 100644 index 0000000..4fca48e --- /dev/null +++ b/server/DataList/IDataListInternalCriteria.cs @@ -0,0 +1,11 @@ +using Sockeye.Models; +namespace Sockeye.DataList +{ + internal interface IDataListInternalCriteria + { + //Additional criteria for security or other reasons + //hard coded into some lists (e.g. MemoDataList so users can't get other people's memos) + //clientCriteria is additional criteria provided by client to list to process as it sees fit (e.g. CustomerNoteDataList requires customer id from client) + System.Collections.Generic.List DataListInternalCriteria(long currentUserId, Sockeye.Biz.AuthorizationRoles userRoles, string clientCriteria); + } +} \ No newline at end of file diff --git a/server/DataList/IDataListProcessing.cs b/server/DataList/IDataListProcessing.cs new file mode 100644 index 0000000..339f4d0 --- /dev/null +++ b/server/DataList/IDataListProcessing.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Sockeye.Biz; +namespace Sockeye.DataList +{ + internal interface IDataListProcessing + { + //sql query from fragment with table joins et + string SQLFrom { get; set; } + //List of fields for this object + List FieldDefinitions { get; set; } + //allowed roles to access this list + AuthorizationRoles AllowedRoles { get; set; } + //Default object type to open for rows of this list (use no object if no) + SockType DefaultListAType { get; set; } + //Defaults when none is specified (see DataListOptions for formats and notes) + List DefaultColumns { get; set; } + Dictionary DefaultSortBy { get; set; } + void SetListOptionDefaultsIfNecessary(Models.DataListProcessingBase listOptions); + Newtonsoft.Json.Linq.JArray GenerateReturnListColumns(List columns); + + //long CurrentUserTranslationId { get; set; } + } +} \ No newline at end of file diff --git a/server/DataList/InsideUserDataList.cs b/server/DataList/InsideUserDataList.cs new file mode 100644 index 0000000..bf18f14 --- /dev/null +++ b/server/DataList/InsideUserDataList.cs @@ -0,0 +1,138 @@ +using System.Collections.Generic; +using Sockeye.Biz; +using Sockeye.Models; + +namespace Sockeye.DataList +{ + internal class InsideUserDataList : DataListProcessingBase, IDataListInternalCriteria + { + + public InsideUserDataList(long translationId) + { + DefaultListAType = SockType.User; + SQLFrom = "from auser " + + "left join auseroptions on auser.id=auseroptions.userid " + + "left join atranslation on auseroptions.translationid = atranslation.id"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "name", "employeenumber", "active", "usertype", "lastlogin" }; + DefaultSortBy = new Dictionary() { { "name", "+" } }; + + FieldDefinitions = new List(); + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "User", + FieldKey = "name", + SockType = (int)SockType.User, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "auser.id", + SqlValueColumnName = "auser.name", + IsRowId = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "UserEmployeeNumber", + FieldKey = "employeenumber", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "auser.employeenumber" + }); + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Active", + FieldKey = "active", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "auser.active" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AllowLogin", + FieldKey = "allowlogin", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "auser.allowlogin" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "UserType", + FieldKey = "usertype", + UiFieldDataType = (int)UiFieldDataType.Enum, + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(UserType).ToString()), + SqlValueColumnName = "auser.usertype" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AuthorizationRoles", + FieldKey = "roles", + UiFieldDataType = (int)UiFieldDataType.Roles, + //NOTE: not technically an enum list but this will trigger datagrid at client to fetch roles for special handling + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(AuthorizationRoles).ToString()), + SqlValueColumnName = "auser.roles" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "LastLogin", + FieldKey = "lastlogin", + UiFieldDataType = (int)UiFieldDataType.DateTime, + SqlValueColumnName = "auser.lastlogin" + }); + + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AuthTwoFactor", + FieldKey = "AuthTwoFactor", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "auser.twofactorenabled" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Translation", + FieldKey = "Translation", + UiFieldDataType = (int)UiFieldDataType.Text, + SockType = (int)SockType.Translation, + SqlIdColumnName = "atranslation.id", + SqlValueColumnName = "atranslation.name" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Tags", + FieldKey = "Tags", + UiFieldDataType = (int)UiFieldDataType.Tags, + SqlValueColumnName = "auser.tags" + }); + + //META COLUMNS + FieldDefinitions.Add(new DataListFieldDefinition + { + + FieldKey = "metausertype", + UiFieldDataType = (int)UiFieldDataType.Enum, + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(UserType).ToString()), + SqlValueColumnName = "auser.usertype", + IsMeta = true + }); + } + + public List DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria) + { + List ret = new List(); + + DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metausertype" }; + FilterOption.Items.Add(new DataListColumnFilter() { value = ((int)UserType.Customer).ToString(), op = DataListFilterComparisonOperator.NotEqual }); + FilterOption.Items.Add(new DataListColumnFilter() { value = ((int)UserType.HeadOffice).ToString(), op = DataListFilterComparisonOperator.NotEqual }); + + ret.Add(FilterOption); + return ret; + } + + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/IntegrationDataList.cs b/server/DataList/IntegrationDataList.cs new file mode 100644 index 0000000..933e1b6 --- /dev/null +++ b/server/DataList/IntegrationDataList.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Sockeye.Biz; +namespace Sockeye.DataList +{ + internal class IntegrationDataList : DataListProcessingBase + { + public IntegrationDataList(long translationId) + { + DefaultListAType = SockType.Integration; + SQLFrom = "from aintegration"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "integrationname" }; + DefaultSortBy = new Dictionary() { { "integrationname", "+" } }; + FieldDefinitions = new List(); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "IntegrationName", + FieldKey = "integrationname", + SockType = (int)SockType.Integration, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "aintegration.id", + SqlValueColumnName = "aintegration.name", + IsRowId = true + }); + + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Active", + FieldKey = "partassemblyactive", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "aintegration.active" + }); + + + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/MemoDataList.cs b/server/DataList/MemoDataList.cs new file mode 100644 index 0000000..05d7928 --- /dev/null +++ b/server/DataList/MemoDataList.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using Sockeye.Models; +using Sockeye.Biz; +namespace Sockeye.DataList +{ + internal class MemoDataList : DataListProcessingBase, IDataListInternalCriteria + { + public MemoDataList(long translationId) + { + + DefaultListAType = SockType.Memo; + SQLFrom = "from amemo left join auser on (amemo.fromid=auser.id)"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "MemoSubject", "MemoFromID", "MemoSent", "MemoViewed" }; + DefaultSortBy = new Dictionary() { { "MemoSent", "-" } }; + FieldDefinitions = new List(); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "MemoSubject", + FieldKey = "MemoSubject", + SockType = (int)SockType.Memo, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "amemo.id", + SqlValueColumnName = "amemo.name", + IsRowId = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "MemoMessage", + FieldKey = "MemoMessage", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "amemo.notes" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "MemoFromID", + FieldKey = "MemoFromID", + SockType = (int)SockType.User, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "auser.id", + SqlValueColumnName = "auser.name", + IsRowId = false + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "MemoSent", + FieldKey = "MemoSent", + UiFieldDataType = (int)UiFieldDataType.DateTime, + SqlValueColumnName = "amemo.sent" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "MemoReplied", + FieldKey = "MemoReplied", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "amemo.replied" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "MemoViewed", + FieldKey = "MemoViewed", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "amemo.viewed" + }); + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Tags", + FieldKey = "MemoTags", + UiFieldDataType = (int)UiFieldDataType.Tags, + SqlValueColumnName = "amemo.tags" + }); + + //META column + FieldDefinitions.Add(new DataListFieldDefinition + { + FieldKey = "metamemoto", + UiFieldDataType = (int)UiFieldDataType.InternalId, + SqlIdColumnName = "amemo.toid", + SqlValueColumnName = "amemo.toid", + IsMeta = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom1", FieldKey = "MemoCustom1", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom2", FieldKey = "MemoCustom2", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom3", FieldKey = "MemoCustom3", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom4", FieldKey = "MemoCustom4", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom5", FieldKey = "MemoCustom5", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom6", FieldKey = "MemoCustom6", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom7", FieldKey = "MemoCustom7", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom8", FieldKey = "MemoCustom8", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom9", FieldKey = "MemoCustom9", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom10", FieldKey = "MemoCustom10", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom11", FieldKey = "MemoCustom11", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom12", FieldKey = "MemoCustom12", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom13", FieldKey = "MemoCustom13", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom14", FieldKey = "MemoCustom14", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom15", FieldKey = "MemoCustom15", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom16", FieldKey = "MemoCustom16", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" }); + + } + + public List DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria) + { + List ret = new List(); + + DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metamemoto" }; + FilterOption.Items.Add(new DataListColumnFilter() { value = currentUserId.ToString(), op = DataListFilterComparisonOperator.Equality }); + + ret.Add(FilterOption); + return ret; + } + + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/NotificationDeliveryLogDataList.cs b/server/DataList/NotificationDeliveryLogDataList.cs new file mode 100644 index 0000000..8a8232b --- /dev/null +++ b/server/DataList/NotificationDeliveryLogDataList.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using Sockeye.Biz; +namespace Sockeye.DataList +{ + internal class NotificationDeliveryLogDataList : DataListProcessingBase + { + public NotificationDeliveryLogDataList(long translationId) + { + DefaultListAType = SockType.OpsNotificationSettings; + SQLFrom = @"from anotifydeliverylog + left join anotifysubscription on anotifysubscription.id = anotifydeliverylog.notifysubscriptionid + left join auser on anotifysubscription.userid=auser.id"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "Processed", "NotifyEventType", "SockType", "User", "Failed", "Errors" }; + DefaultSortBy = new Dictionary() { { "Processed", "-" } }; + FieldDefinitions = new List(); + + // FieldDefinitions.Add(new DataListFieldDefinition + // { + // TKey = "NotifySubscription", + // FieldKey = "NotifySubscription", + // SockType = (int)SockType.NotifySubscription, + // UiFieldDataType = (int)UiFieldDataType.Text, + // SqlIdColumnName = "anotifysubscription.id", + // SqlValueColumnName = "anotifysubscription.id", + // IsRowId = false + // }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Processed", + FieldKey = "Processed", + UiFieldDataType = (int)UiFieldDataType.DateTime, + SqlValueColumnName = "anotifydeliverylog.processed" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "NotifyEventType", + FieldKey = "NotifyEventType", + UiFieldDataType = (int)UiFieldDataType.Enum, + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(NotifyEventType).ToString()), + SqlValueColumnName = "anotifysubscription.eventtype" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "SockType", + FieldKey = "SockType", + UiFieldDataType = (int)UiFieldDataType.Enum, + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(SockType).ToString()), + SqlValueColumnName = "anotifysubscription.socktype" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Failed", + FieldKey = "Failed", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "anotifydeliverylog.fail" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Errors", + FieldKey = "Errors", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "anotifydeliverylog.error" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + FieldKey = "User", + TKey = "User", + UiFieldDataType = (int)UiFieldDataType.Text, + SockType = (int)SockType.User, + SqlIdColumnName = "auser.id", + SqlValueColumnName = "auser.name" + }); + + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/OutsideUserDataList.cs b/server/DataList/OutsideUserDataList.cs new file mode 100644 index 0000000..4891f37 --- /dev/null +++ b/server/DataList/OutsideUserDataList.cs @@ -0,0 +1,151 @@ +using System.Collections.Generic; +using Sockeye.Models; +using Sockeye.Biz; +namespace Sockeye.DataList +{ + internal class OutsideUserDataList : DataListProcessingBase, IDataListInternalCriteria + { + + public OutsideUserDataList(long translationId) + { + DefaultListAType = SockType.Customer; + SQLFrom = "from auser left join aheadoffice on (auser.headofficeid=aheadoffice.id) " + + "left join acustomer on (auser.customerid=acustomer.id)" + + "left join auseroptions on auser.id=auseroptions.userid " + + "left join atranslation on auseroptions.translationid = atranslation.id"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "name", "active", "allowlogin", "usercustomer", "userheadoffice", "lastlogin" }; + DefaultSortBy = new Dictionary() { { "name", "+" } }; + + FieldDefinitions = new List(); + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "User", + FieldKey = "name", + SockType = (int)SockType.User, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "auser.id", + SqlValueColumnName = "auser.name", + IsRowId = true + }); + + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Active", + FieldKey = "active", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "auser.active" + }); + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AllowLogin", + FieldKey = "allowlogin", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "auser.allowlogin" + }); + + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "UserType", + FieldKey = "usertype", + UiFieldDataType = (int)UiFieldDataType.Enum, + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(UserType).ToString()), + SqlValueColumnName = "auser.usertype" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AuthorizationRoles", + FieldKey = "roles", + UiFieldDataType = (int)UiFieldDataType.Roles, + //NOTE: not technically an enum list but this will trigger datagrid at client to fetch roles for special handling + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(AuthorizationRoles).ToString()), + SqlValueColumnName = "auser.roles" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "LastLogin", + FieldKey = "lastlogin", + UiFieldDataType = (int)UiFieldDataType.DateTime, + SqlValueColumnName = "auser.lastlogin" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "HeadOffice", + FieldKey = "userheadoffice", + UiFieldDataType = (int)UiFieldDataType.Text, + SockType = (int)SockType.HeadOffice, + SqlIdColumnName = "aheadoffice.id", + SqlValueColumnName = "aheadoffice.name" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Customer", + FieldKey = "usercustomer", + UiFieldDataType = (int)UiFieldDataType.Text, + SockType = (int)SockType.Customer, + SqlIdColumnName = "acustomer.id", + SqlValueColumnName = "acustomer.name" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "AuthTwoFactor", + FieldKey = "AuthTwoFactor", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "auser.twofactorenabled" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Translation", + FieldKey = "Translation", + UiFieldDataType = (int)UiFieldDataType.Text, + SockType = (int)SockType.Translation, + SqlIdColumnName = "atranslation.id", + SqlValueColumnName = "atranslation.name" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Tags", + FieldKey = "Tags", + UiFieldDataType = (int)UiFieldDataType.Tags, + SqlValueColumnName = "auser.tags" + }); + + //META COLUMNS + FieldDefinitions.Add(new DataListFieldDefinition + { + FieldKey = "metausertype", + UiFieldDataType = (int)UiFieldDataType.Enum, + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(UserType).ToString()), + SqlValueColumnName = "auser.usertype", + IsMeta = true + }); + } + + public List DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria) + { + List ret = new List(); + + DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metausertype" }; + FilterOption.Any = true; + FilterOption.Items.Add(new DataListColumnFilter() { value = ((int)UserType.Customer).ToString(), op = DataListFilterComparisonOperator.Equality }); + FilterOption.Items.Add(new DataListColumnFilter() { value = ((int)UserType.HeadOffice).ToString(), op = DataListFilterComparisonOperator.Equality }); + + ret.Add(FilterOption); + return ret; + } + + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/ReminderDataList.cs b/server/DataList/ReminderDataList.cs new file mode 100644 index 0000000..69522c5 --- /dev/null +++ b/server/DataList/ReminderDataList.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using Sockeye.Models; +using Sockeye.Biz; +namespace Sockeye.DataList +{ + internal class ReminderDataList : DataListProcessingBase, IDataListInternalCriteria + { + public ReminderDataList(long translationId) + { + DefaultListAType = SockType.Reminder; + SQLFrom = "from areminder"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "ReminderName", "ReminderNotes", "ReminderStartDate", "ReminderStopDate" }; + DefaultSortBy = new Dictionary() { { "ReminderStartDate", "-" } }; + FieldDefinitions = new List(); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "ReminderName", + FieldKey = "ReminderName", + SockType = (int)SockType.Reminder, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "areminder.id", + SqlValueColumnName = "areminder.name", + SqlColorColumnName = "areminder.color", + IsRowId = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "ReminderNotes", + FieldKey = "ReminderNotes", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "areminder.notes" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "ReminderStartDate", + FieldKey = "ReminderStartDate", + UiFieldDataType = (int)UiFieldDataType.DateTime, + SqlValueColumnName = "areminder.startdate" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "ReminderStopDate", + FieldKey = "ReminderStopDate", + UiFieldDataType = (int)UiFieldDataType.DateTime, + SqlValueColumnName = "areminder.stopdate" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Tags", + FieldKey = "ReminderTags", + UiFieldDataType = (int)UiFieldDataType.Tags, + SqlValueColumnName = "areminder.tags" + }); + + //META column + FieldDefinitions.Add(new DataListFieldDefinition + { + FieldKey = "metareminderuser", + UiFieldDataType = (int)UiFieldDataType.InternalId, + SqlIdColumnName = "areminder.userid", + SqlValueColumnName = "areminder.userid", + IsMeta = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom1", FieldKey = "ReminderCustom1", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom2", FieldKey = "ReminderCustom2", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom3", FieldKey = "ReminderCustom3", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom4", FieldKey = "ReminderCustom4", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom5", FieldKey = "ReminderCustom5", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom6", FieldKey = "ReminderCustom6", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom7", FieldKey = "ReminderCustom7", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom8", FieldKey = "ReminderCustom8", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom9", FieldKey = "ReminderCustom9", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom10", FieldKey = "ReminderCustom10", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom11", FieldKey = "ReminderCustom11", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom12", FieldKey = "ReminderCustom12", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom13", FieldKey = "ReminderCustom13", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom14", FieldKey = "ReminderCustom14", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom15", FieldKey = "ReminderCustom15", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom16", FieldKey = "ReminderCustom16", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" }); + } + + //Ensure only current user can fetch their reminders + public List DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria) + { + List ret = new List(); + + DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metareminderuser" }; + FilterOption.Items.Add(new DataListColumnFilter() { value = currentUserId.ToString(), op = DataListFilterComparisonOperator.Equality }); + + ret.Add(FilterOption); + return ret; + } + + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/ReportDataList.cs b/server/DataList/ReportDataList.cs new file mode 100644 index 0000000..4de205e --- /dev/null +++ b/server/DataList/ReportDataList.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using Sockeye.Biz; +namespace Sockeye.DataList +{ + internal class ReportDataList : DataListProcessingBase + { + public ReportDataList(long translationId) + { + DefaultListAType = SockType.Report; + SQLFrom = "from aReport"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "name", "aType", "ReportNotes", "active" }; + DefaultSortBy = new Dictionary() { { "name", "+" } }; + FieldDefinitions = new List(); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Report", + FieldKey = "name", + SockType = (int)SockType.Report, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "aReport.id", + SqlValueColumnName = "aReport.name", + IsRowId = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "SockType", + FieldKey = "aType", + UiFieldDataType = (int)UiFieldDataType.Enum, + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(SockType).ToString()), + SqlValueColumnName = "areport.SockType" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "ReportNotes", + FieldKey = "ReportNotes", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "areport.notes" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Active", + FieldKey = "active", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "aReport.active" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "SelectRoles", + FieldKey = "roles", + UiFieldDataType = (int)UiFieldDataType.Roles, + //NOTE: not technically an enum list but this will trigger datagrid at client to fetch roles for special handling + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(AuthorizationRoles).ToString()), + SqlValueColumnName = "areport.roles" + }); + + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/ReviewDataList.cs b/server/DataList/ReviewDataList.cs new file mode 100644 index 0000000..f7822f4 --- /dev/null +++ b/server/DataList/ReviewDataList.cs @@ -0,0 +1,208 @@ +using System.Collections.Generic; +using Sockeye.Models; +using Sockeye.Biz; +using System.Linq; +namespace Sockeye.DataList +{ + internal class ReviewDataList : DataListProcessingBase, IDataListInternalCriteria + { + public ReviewDataList(long translationId) + { + + DefaultListAType = SockType.Review; + SQLFrom = "from areview " + + "left join auser uassto on (areview.userid=uassto.id) " + + "left join auser uassby on (areview.assignedbyuserid=uassby.id)"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "ReviewCompletedDate", "ReviewDate", "ReviewName", "Object", "SockType", "ReviewNotes", "ReviewUserId" }; + DefaultSortBy = new Dictionary() { { "ReviewCompletedDate", "-" }, { "ReviewDate", "+" } }; + FieldDefinitions = new List(); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Object", + FieldKey = "Object", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "areview.objectid", + SqlValueColumnName = $"AYGETNAME(areview.objectid, areview.aType,{translationId})", + SqlATypeColumnName = "areview.aType", + Translate=true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "SockType", + FieldKey = "SockType", + UiFieldDataType = (int)UiFieldDataType.Enum, + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(SockType).ToString()), + SqlValueColumnName = "areview.aType" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "ReviewName", + FieldKey = "ReviewName", + SockType = (int)SockType.Review, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "areview.id", + SqlValueColumnName = "areview.name", + IsRowId = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "ReviewNotes", + FieldKey = "ReviewNotes", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "areview.notes" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "ReviewDate", + FieldKey = "ReviewDate", + UiFieldDataType = (int)UiFieldDataType.DateTime, + SqlValueColumnName = "areview.reviewdate" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "ReviewCompletedDate", + FieldKey = "ReviewCompletedDate", + UiFieldDataType = (int)UiFieldDataType.DateTime, + SqlValueColumnName = "areview.completeddate" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "ReviewCompletionNotes", + FieldKey = "ReviewCompletionNotes", + UiFieldDataType = (int)UiFieldDataType.Text, + SqlValueColumnName = "areview.completionnotes" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "ReviewUserId", + FieldKey = "ReviewUserId", + SockType = (int)SockType.User, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "uassto.id", + SqlValueColumnName = "uassto.name" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "ReviewAssignedByUserId", + FieldKey = "ReviewAssignedByUserId", + SockType = (int)SockType.User, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "uassby.id", + SqlValueColumnName = "uassby.name" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Tags", + FieldKey = "ReviewTags", + UiFieldDataType = (int)UiFieldDataType.Tags, + SqlValueColumnName = "areview.tags" + }); + + + + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom1", FieldKey = "ReviewCustom1", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom2", FieldKey = "ReviewCustom2", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom3", FieldKey = "ReviewCustom3", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom4", FieldKey = "ReviewCustom4", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom5", FieldKey = "ReviewCustom5", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom6", FieldKey = "ReviewCustom6", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom7", FieldKey = "ReviewCustom7", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom8", FieldKey = "ReviewCustom8", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom9", FieldKey = "ReviewCustom9", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom10", FieldKey = "ReviewCustom10", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom11", FieldKey = "ReviewCustom11", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom12", FieldKey = "ReviewCustom12", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom13", FieldKey = "ReviewCustom13", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom14", FieldKey = "ReviewCustom14", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom15", FieldKey = "ReviewCustom15", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom16", FieldKey = "ReviewCustom16", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" }); + + + + //META COLUMNS + FieldDefinitions.Add(new DataListFieldDefinition + { + FieldKey = "metareviewuser", + UiFieldDataType = (int)UiFieldDataType.InternalId, + SqlIdColumnName = "areview.userid", + SqlValueColumnName = "areview.userid", + IsMeta = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + FieldKey = "metaobjectid", + UiFieldDataType = (int)UiFieldDataType.InternalId, + SqlIdColumnName = "areview.objectid", + SqlValueColumnName = "areview.objectid", + IsMeta = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + + FieldKey = "metaobjecttype", + UiFieldDataType = (int)UiFieldDataType.Enum, + EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(SockType).ToString()), + SqlValueColumnName = "areview.aType", + IsMeta = true + }); + } + + + public List DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria) + { + List ret = new List(); + bool HasSupervisorRole = + userRoles.HasFlag(AuthorizationRoles.BizAdmin) + || userRoles.HasFlag(AuthorizationRoles.Service) + || userRoles.HasFlag(AuthorizationRoles.Inventory) + || userRoles.HasFlag(AuthorizationRoles.Sales) + || userRoles.HasFlag(AuthorizationRoles.Accounting); + + if (!HasSupervisorRole) + { + DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metareviewuser" }; + FilterOption.Items.Add(new DataListColumnFilter() { value = currentUserId.ToString(), op = DataListFilterComparisonOperator.Equality }); + + ret.Add(FilterOption); + } + + + //ClientCriteria format for this list is "OBJECTID,AYATYPE" + var crit = (clientCriteria ?? "").Split(',').Select(z => z.Trim()).ToArray(); + if (crit.Length > 1) + { + //OBJECTID criteria + if (crit[0] != "0") + { + DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metaobjectid" }; + FilterOption.Items.Add(new DataListColumnFilter() { value = crit[0], op = DataListFilterComparisonOperator.Equality }); + ret.Add(FilterOption); + } + + //AYATYPE criteria + if (!string.IsNullOrWhiteSpace(crit[1])) + { + DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metaobjecttype" }; + FilterOption.Items.Add(new DataListColumnFilter() { value = crit[1], op = DataListFilterComparisonOperator.Equality }); + ret.Add(FilterOption); + } + } + return ret; + } + + }//eoc +}//eons \ No newline at end of file diff --git a/server/DataList/TranslationDataList.cs b/server/DataList/TranslationDataList.cs new file mode 100644 index 0000000..da5e516 --- /dev/null +++ b/server/DataList/TranslationDataList.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using Sockeye.Biz; +namespace Sockeye.DataList +{ + internal class TranslationDataList : DataListProcessingBase + { + public TranslationDataList(long translationId) + { + DefaultListAType = SockType.Translation; + SQLFrom = "from atranslation"; + var RoleSet = BizRoles.GetRoleSet(DefaultListAType); + AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; + DefaultColumns = new List() { "name", "stock", "cjkindex" }; + DefaultSortBy = new Dictionary() { { "name", "+" } }; + FieldDefinitions = new List(); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "Translation", + FieldKey = "name", + SockType = (int)SockType.Translation, + UiFieldDataType = (int)UiFieldDataType.Text, + SqlIdColumnName = "atranslation.id", + SqlValueColumnName = "atranslation.name", + IsRowId = true + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "GlobalCJKIndex", + FieldKey = "cjkindex", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "atranslation.cjkindex" + }); + + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "ReadOnly", + FieldKey = "stock", + UiFieldDataType = (int)UiFieldDataType.Bool, + SqlValueColumnName = "atranslation.stock" + }); + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/PickList/AyaPickList.cs b/server/PickList/AyaPickList.cs new file mode 100644 index 0000000..1d969b6 --- /dev/null +++ b/server/PickList/AyaPickList.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Sockeye.Biz; +using Newtonsoft.Json.Linq; +namespace Sockeye.PickList +{ + /// + /// PickList object base class + /// + internal abstract class AyaPickList : IAyaPickList + { + public AyaPickList() + { } + public string SQLFrom { get; set; } + public List ColumnDefinitions { get; set; } + public AuthorizationRoles AllowedRoles { get; set; } + public SockType DefaultListAType { get; set; } + public string DefaultTemplate { get; set; } + //return array of field keys in list view + public List GetFieldListFromTemplate(JArray template) + { + List ret = new List(); + for (int i = 0; i < template.Count; i++) + { + var cm = template[i]; + ret.Add(cm["fld"].Value()); + } + return ret; + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/PickList/AyaPickListFieldDefinition.cs b/server/PickList/AyaPickListFieldDefinition.cs new file mode 100644 index 0000000..ba61576 --- /dev/null +++ b/server/PickList/AyaPickListFieldDefinition.cs @@ -0,0 +1,42 @@ +using Sockeye.Biz; +using Newtonsoft.Json; +namespace Sockeye.PickList +{ + //This class defines a field used for pick list templating querying processing editing and returning + public class AyaPickListFieldDefinition + { + //CLIENT / SERVER Unique identifier used at BOTH client and server + //also the sql displaycolumnname if identical + public string FieldKey { get; set; } + //CLIENT Use only for display + public string TKey { get; set; } + // Used for casting query + public UiFieldDataType ColumnDataType { get; set; } + public bool IsRowId { get; set; }//both indicates is row ID but also that it's required as the only required field. Not technically necessary maybe but to prevent foot shooting. + public bool IsActiveColumn { get; set; } + [JsonIgnore] + public string SqlIdColumnName { get; set; } + [JsonIgnore] + public string SqlValueColumnName { get; set; } + + public AyaPickListFieldDefinition() + { + //most common defaults + IsRowId = false; + IsActiveColumn = false; + } + + //Get column to query for display name or use FieldName if there is no difference + public string GetSqlValueColumnName() + { + if (string.IsNullOrEmpty(SqlValueColumnName)) + { + return FieldKey.ToLowerInvariant(); + } + else + { + return SqlValueColumnName; + } + } + } +} \ No newline at end of file diff --git a/server/PickList/CustomerPickList.cs b/server/PickList/CustomerPickList.cs new file mode 100644 index 0000000..2cc0e5e --- /dev/null +++ b/server/PickList/CustomerPickList.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Sockeye.Biz; +using System.Linq; + +namespace Sockeye.PickList +{ + internal class CustomerPickList : AyaPickList, IAyaPickListVariant + { + public CustomerPickList() + { + + DefaultListAType = SockType.Customer; + SQLFrom = "from acustomer "; + AllowedRoles = BizRoles.GetRoleSet(DefaultListAType).Select; + dynamic dTemplate = new JArray(); + + dynamic cm = new JObject(); + cm.fld = "customername"; + dTemplate.Add(cm); + + cm = new JObject(); + cm.fld = "customertags"; + dTemplate.Add(cm); + + base.DefaultTemplate = dTemplate.ToString(Newtonsoft.Json.Formatting.None); + + //NOTE: Due to the join, all the sql id and name fields that can conflict with the joined table need to be specified completely + ColumnDefinitions = new List(); + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "Active", + FieldKey = "customeractive", + ColumnDataType = UiFieldDataType.Bool, + SqlValueColumnName = "acustomer.active", + IsActiveColumn = true + }); + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "Name", + FieldKey = "customername", + ColumnDataType = UiFieldDataType.Text, + SqlIdColumnName = "acustomer.id", + SqlValueColumnName = "acustomer.name", + IsRowId = true + }); + + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "Tags", + FieldKey = "customertags", + ColumnDataType = UiFieldDataType.Tags, + SqlValueColumnName = "acustomer.tags" + }); + + } + public string GetVariantCriteria(string variant) + { + //Currently the only variant is a object type and id to indicate headoffice + + //ClientCriteria format for this list is "OBJECTID,AYATYPE" + var crit = (variant ?? "").Split(',').Select(z => z.Trim()).ToArray(); + if (crit.Length > 1) + { + int nType = 0; + if (!int.TryParse(crit[1], out nType)) return string.Empty; + SockType forType = (SockType)nType; + if (forType != SockType.HeadOffice) return string.Empty; + + long lId = 0; + if (!long.TryParse(crit[0], out lId)) return string.Empty; + if (lId == 0) return string.Empty; + + //Have valid type, have an id, so filter away + switch (forType) + { + case SockType.HeadOffice: + { + return $"acustomer.headofficeid = {lId}"; + } + } + } + return string.Empty; + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/PickList/HeadOfficePickList.cs b/server/PickList/HeadOfficePickList.cs new file mode 100644 index 0000000..4f9743d --- /dev/null +++ b/server/PickList/HeadOfficePickList.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Sockeye.Biz; +namespace Sockeye.PickList +{ + internal class HeadOfficePickList : AyaPickList + { + public HeadOfficePickList() + { + + DefaultListAType = SockType.HeadOffice; + SQLFrom = "from aheadoffice"; + AllowedRoles = BizRoles.GetRoleSet(DefaultListAType).Select; + dynamic dTemplate = new JArray(); + + dynamic cm = new JObject(); + cm.fld = "headofficename"; + dTemplate.Add(cm); + + cm = new JObject(); + cm.fld = "headofficetags"; + dTemplate.Add(cm); + + base.DefaultTemplate = dTemplate.ToString(Newtonsoft.Json.Formatting.None); + + //NOTE: Due to the join, all the sql id and name fields that can conflict with the joined table need to be specified completely + ColumnDefinitions = new List(); + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "Active", + FieldKey = "headofficeactive", + ColumnDataType = UiFieldDataType.Bool, + SqlValueColumnName = "aheadoffice.active", + IsActiveColumn = true + }); + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "Name", + FieldKey = "headofficename", + ColumnDataType = UiFieldDataType.Text, + SqlIdColumnName = "aheadoffice.id", + SqlValueColumnName = "aheadoffice.name", + IsRowId = true + }); + + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "Tags", + FieldKey = "headofficetags", + ColumnDataType = UiFieldDataType.Tags, + SqlValueColumnName = "aheadoffice.tags" + }); + + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/PickList/IAyaPickList.cs b/server/PickList/IAyaPickList.cs new file mode 100644 index 0000000..79d94cf --- /dev/null +++ b/server/PickList/IAyaPickList.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Sockeye.Biz; +namespace Sockeye.PickList +{ + internal interface IAyaPickList + { + //sql query from fragment with table joins et + string SQLFrom { get; set; } + //List of fields for this object + List ColumnDefinitions { get; set; } + //allowed roles to access this list + AuthorizationRoles AllowedRoles { get; set; } + //Default object type to open for rows of this list (use no object if no) + SockType DefaultListAType { get; set; } + //Default / STOCK template when none is specified + string DefaultTemplate { get; set; } + List GetFieldListFromTemplate(JArray fieldListArray); + } +} \ No newline at end of file diff --git a/server/PickList/IAyaPickListVariant.cs b/server/PickList/IAyaPickListVariant.cs new file mode 100644 index 0000000..930129f --- /dev/null +++ b/server/PickList/IAyaPickListVariant.cs @@ -0,0 +1,7 @@ +namespace Sockeye.PickList +{ + internal interface IAyaPickListVariant + { + string GetVariantCriteria(string variant); + } +} \ No newline at end of file diff --git a/server/PickList/PickListFactory.cs b/server/PickList/PickListFactory.cs new file mode 100644 index 0000000..3709db2 --- /dev/null +++ b/server/PickList/PickListFactory.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using Sockeye.Biz; +using Sockeye.Models; + + +namespace Sockeye.PickList +{ + internal static class PickListFactory + { + + //Instantiate list object specified from type + internal static IAyaPickList GetAyaPickList(SockType sockType) + { + switch (sockType) + { + //CoreBizObject add here if it will be "picked" on any other form + + case SockType.Customer: + return new CustomerPickList() as IAyaPickList; + case SockType.HeadOffice: + return new HeadOfficePickList() as IAyaPickList; + case SockType.User: + return new UserPickList() as IAyaPickList; + + case SockType.Report: + return new ReportPickList() as IAyaPickList; + + //@##### WARNING: BE SURE TO ADD NEW TYPES BELOW OR USERS WON"T BE ABLE TO EDIT THE TEMPLATE FOR THEM + + default: + throw new System.NotImplementedException($"PICKLIST {sockType} NOT IMPLEMENTED"); + + } + //return null; + } + + //List all the PickList-able object types available + internal static List GetListOfAllPickListTypes(long TranslationId) + { + List TranslationKeysToFetch = new List(); + List ret = new List(); + + List values = new List(); + values.Add(SockType.Customer); + values.Add(SockType.HeadOffice); + values.Add(SockType.Report); + values.Add(SockType.User); + + + //### NEW ONES HERE + + + + foreach (SockType t in values) + { + // if (t.HasAttribute(typeof(CoreBizObjectAttribute))) + // { + var name = t.ToString(); + TranslationKeysToFetch.Add(name); + ret.Add(new NameIdItem() { Name = name, Id = (long)t }); + // } + } + var LT = TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, TranslationId).Result; + foreach (NameIdItem i in ret) + { + i.Name = LT[i.Name]; + } + return ret; + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/PickList/PickListFetcher.cs b/server/PickList/PickListFetcher.cs new file mode 100644 index 0000000..2507633 --- /dev/null +++ b/server/PickList/PickListFetcher.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Sockeye.Models; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + + +namespace Sockeye.PickList +{ + internal static class PickListFetcher + { + internal static async Task> GetResponseAsync(IAyaPickList PickList, string autoCompleteQuery, + string tagSpecificQuery, bool includeInactive, long[] preIds, string variant, AyContext ct, ILogger log, string overrideTemplate) + { + + //Sort out effective Template + string Template = null; + if (string.IsNullOrWhiteSpace(overrideTemplate)) + { + //Attempt to fetch custom template + var t = await ct.PickListTemplate.FirstOrDefaultAsync(z => z.Id == ((long)PickList.DefaultListAType)); + if (t == null) + { + Template = PickList.DefaultTemplate; + } + else + { + Template = t.Template; + } + } + else + { + Template = overrideTemplate; + } + + //parse the template + var jTemplate = JArray.Parse(Template); + + //Get the field key names in a list from the template + List TemplateColumnNames = PickList.GetFieldListFromTemplate(jTemplate); + + //BUILD THE QUERY + var q = PickListSqlBuilder.Build(PickList, TemplateColumnNames, autoCompleteQuery, tagSpecificQuery, includeInactive, preIds, variant); + + //If want to log all queries + //log.LogInformation(q); + + //RETURN OBJECTS + var ret = new List(); + + //QUERY THE DB + using (var command = ct.Database.GetDbConnection().CreateCommand()) + { + await ct.Database.OpenConnectionAsync(); + + //GET DATA RETURN ROWS + command.CommandText = q; + try + { + using (var dr = await command.ExecuteReaderAsync()) + { + while (dr.Read()) + { + //query is always in the same order: + //plId, plActive, plName + ret.Add(new NameIdActiveItem + { + Id = dr.GetInt64(0), + Active = dr.GetBoolean(1), + Name = dr.GetString(2) + }); + } + } + } + catch (Npgsql.PostgresException e) + { + //log out the exception and the query + //This may be called internally in *Biz classes who don't have a log of their own + if (log == null) + log = Sockeye.Util.ApplicationLogging.CreateLogger("PickListFetcher"); + log.LogError("PickList query failed unexpectedly. Query was:"); + log.LogError(q); + log.LogError(e, "DB Exception"); + throw new System.Exception("PickListFetcher - Query failed see log"); + } + catch (System.Exception e) + { + //ensure any other type of exception gets surfaced properly + //log out the exception and the query + log.LogError("PickListFetcher unexpected failure. Query was:"); + log.LogError(q); + + log.LogError(e, "Exception"); + throw new System.Exception("PickListFetcher - unexpected failure see log"); + } + } + return ret; + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/PickList/PickListOptions.cs b/server/PickList/PickListOptions.cs new file mode 100644 index 0000000..ac878c0 --- /dev/null +++ b/server/PickList/PickListOptions.cs @@ -0,0 +1,73 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using Sockeye.Biz; + +namespace Sockeye.PickList +{ + + /* + /// The SockType object type to select from + /// The query to filter the returned list by. Query text as provided will be case sensitively matched to all templated fields. + /// independentely of this, if an addition space separated string that begins with two consecutive periods is encountered that will be considered a separate match to the TAGS collection of each object + /// So a tag query might be entered as "..zon some" which would match all tags LIKE 'zon' and template fields LIKE 'some' + /// Include inactive objects in the returned list + /// Return only specific items (for pre-selected items on forms) + /// Some lists optionally take a variant string, e.g. User type "inside","outside" etc + */ + public sealed class PickListOptions + { + + [FromBody] + public SockType SockType { get; set; } + + [FromBody] + public string Query { get; set; } + + [FromBody] + public bool Inactive { get; set; } + + [FromBody] + public List PreselectedIds { get; set; } + + [FromBody] + public string ListVariant { get; set; } + + [FromBody] + public string Template { get; set; } + + public PickListOptions() + { + SockType = SockType.NoType; + Query = string.Empty; + Inactive = false; + PreselectedIds = new List(); + ListVariant = string.Empty; + Template = string.Empty; + } + } + + public sealed class PickListSingleOptions + { + + [FromBody] + public SockType SockType { get; set; } + + [FromBody] + public long Id { get; set; } + + [FromBody] + public string ListVariant { get; set; } + + [FromBody] + public string Template { get; set; } + + public PickListSingleOptions() + { + SockType = SockType.NoType; + Id = 0; + ListVariant = string.Empty; + Template = string.Empty; + } + } + +} \ No newline at end of file diff --git a/server/PickList/PickListSqlBuilder.cs b/server/PickList/PickListSqlBuilder.cs new file mode 100644 index 0000000..763afc2 --- /dev/null +++ b/server/PickList/PickListSqlBuilder.cs @@ -0,0 +1,318 @@ +using System.Collections.Generic; +using System.Text; +using System.Linq; +using Sockeye.Biz; +using Sockeye.Util; + +namespace Sockeye.PickList +{ + + internal static class PickListSqlBuilder + { + + + //Maximum number of results to return at any given time + //did a little research and may adjust this but it can be fairly girthy in this day and age + //and many people might not want or need to autocomplete type if we provide enough leeway. + + const int MAXIMUM_RESULT_COUNT = 100; + + //Build the query for a picklist request + internal static string Build(IAyaPickList pickList, List templateColumnNames, string autoCompleteQuery, string tagSpecificQuery, bool IncludeInactive, long[] preIds, string variant) + { + + //determine this in advance as it will be used in a loop later + bool HasAutoCompleteQuery = !string.IsNullOrWhiteSpace(autoCompleteQuery); + bool HasTagSpecificQuery = !string.IsNullOrWhiteSpace(tagSpecificQuery); + + + //Variables to collect the data needed to create the actual clauses later + List lSelect = new List(); + List lWhere = new List(); + List lOrderBy = new List(); + + string PlIdSelectFragment = string.Empty; + string ActiveSelectFragment = string.Empty; + string ActiveWhereFragment = string.Empty; + string TagSpecificWhereFragment = string.Empty; + string PredefinedOnlyWhereFragment = string.Empty; + + string VariantWhereFragment = string.Empty; + bool HasVariantWhereFragment = false; + if (!string.IsNullOrWhiteSpace(variant) && pickList is IAyaPickListVariant) + { + VariantWhereFragment = ((IAyaPickListVariant)pickList).GetVariantCriteria(variant); + HasVariantWhereFragment = !string.IsNullOrWhiteSpace(VariantWhereFragment); + } + + //PROCESS ROW ID "VALUE" COLUMN + // + AyaPickListFieldDefinition rowIdColumn = pickList.ColumnDefinitions.FirstOrDefault(z => z.IsRowId == true); + //this should only happen with a development error + if (rowIdColumn == null) + throw new System.ArgumentNullException($"DEV ERROR in PickListSqlBuilder.cs: picklist for {pickList.DefaultListAType.ToString()} has no rowId column specified in columnDefinitions list"); + PlIdSelectFragment = rowIdColumn.SqlIdColumnName + " as plId"; + + + + if (preIds.Length > 0) + { + //select id,name from acustomer where id in(1,3,5,7) + //string.Join(",", arr) + // PredefinedOnlyWhereFragment = rowIdColumn.SqlIdColumnName + " = " + preId.ToString(); + PredefinedOnlyWhereFragment = $"{rowIdColumn.SqlIdColumnName} in ({string.Join(",", preIds)})"; + } + + + //PROCESS ACTIVE COLUMN + // + //NOTE: default is to filter *out* inactive objects because that's the most common need at the Client + //but we provide the override for that if necessary as there are often (management usually) cases where user needs to select inactive records + + //add active column, fake it if necessary + AyaPickListFieldDefinition activeColumn = pickList.ColumnDefinitions.FirstOrDefault(z => z.IsActiveColumn == true); + if (activeColumn == null) + { + + //no active column which is normal for some types of objects + //so make a fake one and return them all as active=true as all lists must return the same format + ActiveSelectFragment = "true as plActive"; + + } + else + { + //we have an active column, set accordingly + + //regardless of wanting to see inactive, we always want to see the column itself + ActiveSelectFragment = activeColumn.SqlValueColumnName + " as plActive"; + + //this is the normal path unless there is an override + //if there is an override to see inactive too then we just don't set the filter on active + if (!IncludeInactive) + { + if (preIds.Length > 0) + { + //pre-selected need to always appear regardless of active status + //ActiveWhereFragment = $"({rowIdColumn.SqlIdColumnName} = {preId}) or ({activeColumn.SqlValueColumnName} = true)"; + ActiveWhereFragment = $"({rowIdColumn.SqlIdColumnName} in ({string.Join(",", preIds)})) or ({activeColumn.SqlValueColumnName} = true)"; + } + else + { + ActiveWhereFragment = activeColumn.SqlValueColumnName + " = true"; + } + + } + } + + //PROCESS TAG SPECIFIC QUERY + // + if (HasTagSpecificQuery) + { + //get the tag column + AyaPickListFieldDefinition tagColumn = pickList.ColumnDefinitions.FirstOrDefault(z => z.ColumnDataType == UiFieldDataType.Tags); + TagSpecificWhereFragment = $"(array_to_string({tagColumn.GetSqlValueColumnName()},',') like '%{tagSpecificQuery}%')"; + } + + + //PROCESS TEMPLATED COLUMNS TO BE RETURNED IN RESULTS + // + foreach (string ColumnName in templateColumnNames) + { + + AyaPickListFieldDefinition o = pickList.ColumnDefinitions.FirstOrDefault(z => z.FieldKey == ColumnName); +#if (DEBUG) + if (o == null) + { + throw new System.ArgumentNullException($"DEV ERROR in PickListSqlBuilder.cs: field {ColumnName} specified in template was NOT found in columnDefinitions list"); + } +#endif + if (o != null) + {//Ignore missing fields in production + + var valueColumnName = o.GetSqlValueColumnName(); + + string sWhere = string.Empty; + + //TAGS COLUMN + // + if (o.ColumnDataType == UiFieldDataType.Tags) + { + lSelect.Add($"(array_to_string({valueColumnName},','))"); + //tags can order by without the arraytostring + lOrderBy.Add(valueColumnName); + + //THIS is the best filter method for a like comparison to each individual tag: + //(array_to_string(acustomer.tags,',') like '%zo%') + //Note that a tag specific query takes precendence over this which exists + //in cases where there are tags in the template and the user has not specified a tag specific query + //so this will handle it as a like query against all tags as a composite string of text just like + //all the other templated fields + if (HasAutoCompleteQuery && !HasTagSpecificQuery) + { + if (ServerGlobalBizSettings.Cache.FilterCaseSensitive) + sWhere = $"(array_to_string({valueColumnName},',') like '%{autoCompleteQuery}%')"; + else + sWhere = $"(lower(array_to_string({valueColumnName},',')) like lower('%{autoCompleteQuery}%'))"; + + } + + } + else if (o.ColumnDataType == UiFieldDataType.Text || o.ColumnDataType == UiFieldDataType.EmailAddress || o.ColumnDataType == UiFieldDataType.HTTP) + { + //TEXT COLUMN + // + lSelect.Add(valueColumnName); + lOrderBy.Add(valueColumnName); + if (HasAutoCompleteQuery) + if (ServerGlobalBizSettings.Cache.FilterCaseSensitive) + sWhere = $"({valueColumnName} like '%{autoCompleteQuery}%')"; + else + sWhere = $"(lower({valueColumnName}) like lower('%{autoCompleteQuery}%'))"; + + } + else + { + //NON-TEXT COLUMN + // + + //Note: if any part of a select contatenation query using the || postgres concat is text then all fields are automatically converted to text + //so no need to make it text here as the automatic spacing character will force the whole thing to a text concat anyway + //ref: https://stackoverflow.com/a/19943343/8939 + lSelect.Add(valueColumnName); + + //order by for now seems to be best as just order by it's value whatever it is + lOrderBy.Add(valueColumnName); + + //Where fragment is different for non text fields: it needs to be cast to text to like query on it + if (HasAutoCompleteQuery) + if (ServerGlobalBizSettings.Cache.FilterCaseSensitive) + sWhere = $"(cast ({valueColumnName} as text) like '%{autoCompleteQuery}%')"; + else + sWhere = $"(lower(cast ({valueColumnName} as text)) like lower('%{autoCompleteQuery}%'))"; + } + + + if (HasAutoCompleteQuery && !string.IsNullOrWhiteSpace(sWhere))//swhere can be empty on a tag in a tag specific query + lWhere.Add(sWhere); + + + } + } + + StringBuilder sb = new StringBuilder(); + + //SELECT + sb.Append("select "); + + //ID COLUMN + sb.Append(PlIdSelectFragment); + sb.Append(", "); + + //ACTIVE COLUMN + sb.Append(ActiveSelectFragment); + sb.Append(", "); + + //nope, this will return null if any of the values are null, very bad for this use, instead + //select name || ' ' || serial || ' ' || array_to_string(tags,',') as display from acustomer + //this, on the other hand will work even if all of them are null + //concat_ws(' ', acustomer.name, acustomer.serial, auser.name) + + sb.Append("concat_ws(' ', "); + foreach (string s in lSelect) + { + sb.Append(s); + sb.Append(","); + } + //clear trailing comma + sb.Length -= 1; + sb.Append(") as plname"); + + //FROM + sb.Append(" "); + sb.Append(pickList.SQLFrom); + + + //WHERE + //there is a condition where there is no where (inactive=true and no query of any kind) + if (preIds.Length > 0 || lWhere.Count > 0 || HasTagSpecificQuery || HasVariantWhereFragment || IncludeInactive == false) + { + sb.Append(" where "); + + if (HasVariantWhereFragment) + { + sb.Append($"({VariantWhereFragment}) and "); + } + + if (HasTagSpecificQuery) + { + sb.Append(TagSpecificWhereFragment); + sb.Append(" and "); + } + + if (!IncludeInactive) + { + sb.Append(ActiveWhereFragment); + sb.Append(" and "); + } + + if (preIds.Length > 0) + { + sb.Append(PredefinedOnlyWhereFragment); + sb.Append(" and "); + } + + if (lWhere.Count > 0) + { + //Put all the regular query terms in parenthesis to ensure it's all treated as one criteria + + sb.Append("("); + foreach (string s in lWhere) + { + sb.Append(s); + sb.Append(" or "); + } + //clear trailing or + + sb.Length -= 4; + //enclosing parenthesis + + + sb.Append(")"); + } + else + { + //we might have a trailing and to remove + //{select acustomer.id as plId, acustomer.active as plActive, concat_ws(' ', acustomer.name) as plname from acustomer where (acustomer.headofficeid = 2) and } + if(sb.ToString().EndsWith(" and ")) + sb.Length-=5; + // + // if (!IncludeInactive || HasTagSpecificQuery || preIds.Length > 0) + // { + // //trailing " and " to remove + // sb.Length -= 5; + // } + } + } + + //ORDER BY + sb.Append(" order by "); + foreach (string s in lOrderBy) + { + sb.Append(s); + sb.Append(","); + } + //clear trailing comma + sb.Length--; + + //LIMIT + sb.Append($" limit {MAXIMUM_RESULT_COUNT}"); + + return sb.ToString(); + + } + //"select acustomer.id as plId || ' 'acustomer.active as plActive || ' 'acustomer.name || ' 'acustomer.serial || ' 'auser.name as plname from acustomer left join auser on (acustomer.userid=auser.id) + //where acustomer.active = true and ((acustomer.name like '%on%') or (cast (acustomer.serial as text) like '%on%') or (auser.name like '%on%')) order by acustomer.name,acustomer.serial,auser.name limit 100" + + + }//eoc +}//ens diff --git a/server/PickList/ReportPickList.cs b/server/PickList/ReportPickList.cs new file mode 100644 index 0000000..81d0d12 --- /dev/null +++ b/server/PickList/ReportPickList.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Sockeye.Biz; +namespace Sockeye.PickList +{ + internal class ReportPickList : AyaPickList, IAyaPickListVariant + { + public ReportPickList() + { + + DefaultListAType = SockType.Report; + SQLFrom = "from areport"; + AllowedRoles = BizRoles.GetRoleSet(DefaultListAType).Select; + dynamic dTemplate = new JArray(); + + dynamic cm = new JObject(); + cm.fld = "reportname"; + dTemplate.Add(cm); + + base.DefaultTemplate = dTemplate.ToString(Newtonsoft.Json.Formatting.None); + + //NOTE: Due to the join, all the sql id and name fields that can conflict with the joined table need to be specified completely + ColumnDefinitions = new List(); + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "Active", + FieldKey = "reportactive", + ColumnDataType = UiFieldDataType.Bool, + SqlValueColumnName = "areport.active", + IsActiveColumn = true + }); + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "Name", + FieldKey = "reportname", + ColumnDataType = UiFieldDataType.Text, + SqlIdColumnName = "areport.id", + SqlValueColumnName = "areport.name", + IsRowId = true + }); + + } + + public string GetVariantCriteria(string variant) + { + //has for type variant + if (string.IsNullOrWhiteSpace(variant)) + return string.Empty; + + int forType; + if (!int.TryParse(variant, out forType)) + return string.Empty; + return $"areport.sockType={forType}"; + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/PickList/UserPickList.cs b/server/PickList/UserPickList.cs new file mode 100644 index 0000000..3af3a02 --- /dev/null +++ b/server/PickList/UserPickList.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Sockeye.Biz; +namespace Sockeye.PickList +{ + internal class UserPickList : AyaPickList, IAyaPickListVariant + { + public UserPickList() + { + + DefaultListAType = SockType.User; + SQLFrom = "from auser"; + AllowedRoles = BizRoles.GetRoleSet(DefaultListAType).Select; + dynamic dTemplate = new JArray(); + + dynamic cm = new JObject(); + cm.fld = "username"; + dTemplate.Add(cm); + + cm = new JObject(); + cm.fld = "useremployeenumber"; + dTemplate.Add(cm); + + cm = new JObject(); + cm.fld = "usertags"; + dTemplate.Add(cm); + + base.DefaultTemplate = dTemplate.ToString(Newtonsoft.Json.Formatting.None); + + //NOTE: Due to the join, all the sql id and name fields that can conflict with the joined table need to be specified completely + ColumnDefinitions = new List(); + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "Active", + FieldKey = "useractive", + ColumnDataType = UiFieldDataType.Bool, + SqlValueColumnName = "auser.active", + IsActiveColumn = true + }); + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "Name", + FieldKey = "username", + ColumnDataType = UiFieldDataType.Text, + SqlIdColumnName = "auser.id", + SqlValueColumnName = "auser.name", + IsRowId = true + }); + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "UserEmployeeNumber", + FieldKey = "useremployeenumber", + ColumnDataType = UiFieldDataType.Text, + SqlValueColumnName = "auser.employeenumber" + }); + + ColumnDefinitions.Add(new AyaPickListFieldDefinition + { + TKey = "Tags", + FieldKey = "usertags", + ColumnDataType = UiFieldDataType.Tags, + SqlValueColumnName = "auser.tags" + }); + } + + public string GetVariantCriteria(string variant) + { + switch (variant) + { + case "inside": + return $"auser.usertype!={(int)UserType.Customer} and auser.usertype!={(int)UserType.HeadOffice}"; + case "outside": + return $"auser.usertype={(int)UserType.Customer} or auser.usertype={(int)UserType.HeadOffice}"; + case "tech": + return $"auser.usertype={(int)UserType.Service} or auser.usertype={(int)UserType.ServiceContractor}"; + } + return string.Empty; + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/Program.cs b/server/Program.cs new file mode 100644 index 0000000..1be6757 --- /dev/null +++ b/server/Program.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using NLog.Web; +using NLog.Targets; +using NLog.Config; +using Sockeye.Util; +//using StackExchange.Profiling; + +namespace Sockeye +{ + + public class Program + { + + public static void Main(string[] args) + { + //https://github.com/npgsql/efcore.pg/issues/2045 + //https://www.npgsql.org/efcore/release-notes/6.0.html#breaking-changes + //AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); + + //first output + Console.WriteLine($"SOCKEYE SERVER {SockeyeVersion.VersionString} BOOTING ..."); + + //Boot lock for generator + ServerGlobalOpsSettingsCache.BOOTING = true; + + //preset, we don't know yet + ServerGlobalOpsSettingsCache.DBAVAILABLE = false; + + //Get config + var config = new ConfigurationBuilder().AddJsonFile("config.json", true).AddEnvironmentVariables().AddCommandLine(args).Build(); + + //Set config or bail with error for missing items + try + { + ServerBootConfig.SetConfiguration(config); + } + catch (Exception ex) + { + Console.WriteLine($"Unable to boot due to configuration error: {ex.Message}"); + return; + } + + + + + + try + { + FileUtil.EnsureUserAndUtilityFoldersExistAndAreNotIdentical(); + } + catch (Exception ex) + { + Console.WriteLine($"Unable to locate or create server file folders error was: {ex.Message}"); + return; + } + + + //Human readable config output to console for diagnosis in case server wont' start + var SockeyeConfig = config.AsEnumerable().Where(z => z.Key.StartsWith("SOCKEYE") && z.Key != "SOCKEYE_JWT_SECRET" && z.Key != "SOCKEYE_SET_SUPERUSER_PW").Select(z => z.Key + "=" + z.Value).ToList(); + var DiagConfig = string.Join(",", SockeyeConfig); + DiagConfig = DbUtil.PasswordRedactedConnectionString(DiagConfig); + + Console.WriteLine($"Config {DiagConfig}"); + + + #region Initialize Logging + //NOTE: there is a logging issue that breaks all this with .net 3.1 hostbuilder vs webhostbuilder but webhostbuilder will be deprecated so we need to work around it + //the discussion about that is here: https://github.com/aspnet/AspNetCore/issues/9337 + //I think I worked around it + + //NLOG OFFICIAL GUIDELINES FOR .net core 3.x https://github.com/NLog/NLog/wiki/Getting-started-with-ASP.NET-Core-3 + + //default log level + NLog.LogLevel NLogLevel = NLog.LogLevel.Info; + bool FilterOutMicrosoftLogItems = true; + + switch (ServerBootConfig.SOCKEYE_LOG_LEVEL.ToLowerInvariant()) + { + case "fatal": + NLogLevel = NLog.LogLevel.Fatal; + break; + case "error": + NLogLevel = NLog.LogLevel.Error; + break; + case "warn": + NLogLevel = NLog.LogLevel.Warn; + break; + case "info": + NLogLevel = NLog.LogLevel.Info; + break; + case "debug": + NLogLevel = NLog.LogLevel.Debug; + break; + case "trace": + NLogLevel = NLog.LogLevel.Trace; + FilterOutMicrosoftLogItems = false;//Only at TRACE level do we want to see all the Microsoft logging stuff + break; + default: + NLogLevel = NLog.LogLevel.Info; + break; + } + + + // Step 1. Create configuration object + var logConfig = new LoggingConfiguration(); + + // Step 2. Create targets and add them to the configuration + var fileTarget = new FileTarget(); + logConfig.AddTarget("file", fileTarget); + + //removed redundant in production use and duplicates lot unnecessarily + // //console target for really serious errors only + // var consoleTarget = new ConsoleTarget(); + // logConfig.AddTarget("console", consoleTarget); + + var nullTarget = new NLog.Targets.NullTarget(); + logConfig.AddTarget("blackhole", nullTarget); + + // Step 3. Set target properties + + fileTarget.FileName = Path.Combine(ServerBootConfig.SOCKEYE_LOG_PATH, "log-sockeye.txt"); + fileTarget.Layout = "${longdate}|${uppercase:${level}}|${logger}|${message:exceptionSeparator==>:withException=true}"; + fileTarget.ArchiveFileName = Path.Combine(ServerBootConfig.SOCKEYE_LOG_PATH, "log-sockeye-{#}.txt"); + fileTarget.ArchiveNumbering = ArchiveNumberingMode.Rolling;//so expect to see 1 to 10 or 0 to ten as may be + fileTarget.ArchiveAboveSize = 1000000;//1mb maximum file size + fileTarget.MaxArchiveFiles = 9;//max 10 files totalling 10mb, the current log-sockeye.txt and log-sockeye-[0-9].txt + + // Step 4. Define rules + + //filter out all Microsoft INFO level logs as they are too much + var logRuleFilterOutMicrosoft = new LoggingRule("Microsoft.*", NLog.LogLevel.Trace, NLog.LogLevel.Info, nullTarget); + logRuleFilterOutMicrosoft.Final = true; + + //filter out httpclient logs at INFO level + var logRuleFilterOutHttpClient = new LoggingRule("System.Net.Http.HttpClient.Default.*", NLog.LogLevel.Trace, NLog.LogLevel.Info, nullTarget); + logRuleFilterOutHttpClient.Final = true; + + + //filter out all Microsoft EF CORE concurrency exceptions, it's a nuisance unless debugging or something + //This is what I have to filter because it's the top exception: Microsoft.EntityFrameworkCore.Update + //But this is what I'm actually trying to filter: Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException + //however there doesn't appear to be a way to filter out based on content so... + + var logRuleFilterOutMicrosoftEfCoreConcurrencyExceptions = new LoggingRule("Microsoft.EntityFrameworkCore.Update", NLog.LogLevel.Trace, NLog.LogLevel.Error, nullTarget); + logRuleFilterOutMicrosoftEfCoreConcurrencyExceptions.Final = true; + + var logRuleFilterOutMicrosoftEfCoreCommandExceptions = new LoggingRule("Microsoft.EntityFrameworkCore.Database.Command", NLog.LogLevel.Trace, NLog.LogLevel.Error, nullTarget); + logRuleFilterOutMicrosoftEfCoreCommandExceptions.Final = true; + + var logRuleFilterOutMicrosoftEfCoreDbUpdateExceptions = new LoggingRule("Microsoft.EntityFrameworkCore.DbUpdateException", NLog.LogLevel.Trace, NLog.LogLevel.Error, nullTarget); + logRuleFilterOutMicrosoftEfCoreDbUpdateExceptions.Final = true; + + //this rule is only intended to filter out this incorrect exception: + //2019-01-16 16:13:03.4808|WARN|Microsoft.EntityFrameworkCore.Query|Query: '(from Widget _4 in DbSet select [_4]).Skip(__p_1).Take(__p_2)' uses a row limiting operation (Skip/Take) without OrderBy which may lead to unpredictable results. + var logRuleFilterOutMicrosoftEfCoreQueryExceptions = new LoggingRule("Microsoft.EntityFrameworkCore.Query", NLog.LogLevel.Trace, NLog.LogLevel.Error, nullTarget); + logRuleFilterOutMicrosoftEfCoreQueryExceptions.Final = true; + + + //Log all other regular items at selected level + var logRuleSockeyeItems = new LoggingRule("*", NLogLevel, fileTarget); + + //Removed due to filling up redundently a system log like systemd in linux + // //Log error or above to console + // var logRuleForConsole = new LoggingRule("*", NLog.LogLevel.Error, consoleTarget); + + // //add console serious error only log rule + // logConfig.LoggingRules.Add(logRuleForConsole); + + //only log microsoft stuff it log is debug level or lower + if (FilterOutMicrosoftLogItems) + { + //filter OUT microsoft stuff + logConfig.LoggingRules.Add(logRuleFilterOutMicrosoft); + logConfig.LoggingRules.Add(logRuleFilterOutMicrosoftEfCoreConcurrencyExceptions); + logConfig.LoggingRules.Add(logRuleFilterOutMicrosoftEfCoreCommandExceptions); + logConfig.LoggingRules.Add(logRuleFilterOutMicrosoftEfCoreDbUpdateExceptions); + logConfig.LoggingRules.Add(logRuleFilterOutMicrosoftEfCoreQueryExceptions); + //also httpclient stuff + logConfig.LoggingRules.Add(logRuleFilterOutHttpClient); + } + + logConfig.LoggingRules.Add(logRuleSockeyeItems); + + + //Turn on internal logging: + if (ServerBootConfig.SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG) + { + NLog.Common.InternalLogger.LogFile = "log-sockeye-logger.txt"; + NLog.Common.InternalLogger.LogLevel = NLog.LogLevel.Debug; + } + + // NLog: setup the logger first to catch all errors + var logger = NLogBuilder.ConfigureNLog(logConfig).GetLogger("BOOT"); + + #endregion + + + //This is the first log entry + logger.Info($"SOCKEYE SERVER {SockeyeVersion.VersionString} BOOTING"); + + + // #if (DEBUG) + // logger.Info($"### DEBUG ONLY - Linker timestamp is {Util.FileUtil.GetLinkerTimestampUtc(System.Reflection.Assembly.GetExecutingAssembly())}"); + + // #endif + + //log configuration + logger.Info($"Config {DiagConfig}"); + logger.Debug($"Full configuration is {config.GetDebugView()}"); + + if (ServerBootConfig.SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG) + logger.Warn("SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG is enabled, this will cause the server to run very slowly"); + + + //Log environmental settings + long UtilityFilesAvailableSpace = 0; + try + { + // Console.WriteLine($"##### program:about to call backupfilesdriveavailablespace, config is [{ServerBootConfig.SOCKEYE_BACKUP_FILES_PATH}] ######"); + UtilityFilesAvailableSpace = new System.IO.DriveInfo(Path.GetPathRoot(ServerBootConfig.SOCKEYE_BACKUP_FILES_PATH)).AvailableFreeSpace; + } + catch (Exception ex) + { + logger.Error(ex, $"BOOT::FileUtil::UtilityFilesDriveAvailableSpace error getting available space from [{Path.GetPathRoot(ServerBootConfig.SOCKEYE_BACKUP_FILES_PATH)}]"); + } + + long AttachmentFilesAvailableSpace = 0; + try + { + AttachmentFilesAvailableSpace = new System.IO.DriveInfo(Path.GetPathRoot(ServerBootConfig.SOCKEYE_ATTACHMENT_FILES_PATH)).AvailableFreeSpace; + } + catch (Exception ex) + { + logger.Error(ex, $"BOOT::FileUtil::AttachmentFilesDriveAvailableSpace error getting available space from [{Path.GetPathRoot(ServerBootConfig.SOCKEYE_ATTACHMENT_FILES_PATH)}]"); + } + + + + //Get boot up folder for logging and later check for wwwroot + string startFolder = Directory.GetCurrentDirectory(); + + logger.Info("Boot path - {0}", startFolder); + logger.Info("OS - {0}", Environment.OSVersion.ToString()); + logger.Info("TimeZone - {0}", TimeZoneInfo.Local.DisplayName); + logger.Info("OS Locale - {0}", System.Globalization.CultureInfo.CurrentCulture.EnglishName); + logger.Info("Machine - {0}", Environment.MachineName); + logger.Info("User - {0}", Environment.UserName); + logger.Info(".Net Version - {0}", Environment.Version.ToString()); + logger.Debug("CPU count - {0}", Environment.ProcessorCount); + logger.Debug("RAM - {0}", FileUtil.GetBytesReadable(GC.GetGCMemoryInfo().TotalAvailableMemoryBytes)); + + logger.Debug("Available data storage - {0}", FileUtil.GetBytesReadable(AttachmentFilesAvailableSpace)); + + ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add("Boot path", startFolder); + ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add("OS", Environment.OSVersion.ToString()); + ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add("Machine", Environment.MachineName); + ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add("User", Environment.UserName); + ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add(".Net Version", Environment.Version.ToString()); + ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add("CPU count", Environment.ProcessorCount.ToString()); + ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add("Sockeye server boot local time", DateTime.Now.ToString("s")); + ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add("Server up time", TimeSpan.FromTicks(Environment.TickCount64).ToString()); + ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add("TimeZone", TimeZoneInfo.Local.DisplayName); + ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add("OS Locale", System.Globalization.CultureInfo.CurrentCulture.EnglishName); + ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add("RAM", FileUtil.GetBytesReadable(GC.GetGCMemoryInfo().TotalAvailableMemoryBytes)); + ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add("Available data storage", FileUtil.GetBytesReadable(AttachmentFilesAvailableSpace)); + + + + + + + //Test for web root path + //If user starts Sockeye from folder that is not the contentRoot then + //Sockeye won't be able to serve static files + if (!Directory.Exists(Path.Combine(startFolder, "wwwroot"))) + { + var err = string.Format("E1010 - Sockeye was not started in the correct folder. Sockeye must be started from the folder that contains the \"wwwroot\" folder but was started instead from this folder: \"{0}\" which does not contain the wwwroot folder.", startFolder); + logger.Fatal(err); + throw new System.ApplicationException(err); + } + + try + { + //BuildWebHost(args, logger).Run(); + BuildHost(args, logger).Build().Run(); + } + catch (Exception e) + { + logger.Fatal(e, "E1090 - Sockeye server can't start due to unexpected exception during initialization"); + //throw; + } + finally + { + // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux) + logger.Info("Sockeye server shutting down"); + NLog.LogManager.Shutdown(); + } + + } + + + + + + + public static IHostBuilder BuildHost(string[] args, NLog.Logger logger) + { + logger.Trace("Building host"); + var configuration = new ConfigurationBuilder().AddCommandLine(args).Build(); + + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder + .UseSetting("detailedErrors", "true") + .UseUrls(ServerBootConfig.SOCKEYE_USE_URLS)//default port and urls, set first can be overridden by any later setting here + .UseConfiguration(configuration)//support command line override of port (dotnet run urls=http://*:8081) + .UseIISIntegration()//support IIS integration just in case, it appears here to override prior settings if necessary (port) + .UseStartup() + .ConfigureLogging((context, logging) => + { + // clear all previously registered providers + //https://stackoverflow.com/a/46336988/8939 + logging.ClearProviders(); + }) + .UseNLog(); // NLog: setup NLog for Dependency injection + }); + } + + }//eoc + +}//eons diff --git a/server/Startup.cs b/server/Startup.cs new file mode 100644 index 0000000..2a094a0 --- /dev/null +++ b/server/Startup.cs @@ -0,0 +1,684 @@ +using System.IO; +using System.Reflection; +using System.Linq; +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.OpenApi.Models; +using Microsoft.Extensions.Options; +using Swashbuckle.AspNetCore.SwaggerGen; + +using Sockeye.Models; +using Sockeye.Util; +using Sockeye.Generator; +using Sockeye.Biz; +using NLog.Extensions.Logging; +using StackExchange.Profiling; + + +namespace Sockeye +{ + public class Startup + { + ///////////////////////////////////////////////////////////// + // + public Startup(Microsoft.AspNetCore.Hosting.IWebHostEnvironment hostingEnvironment) + { + var nlogLoggerProvider = new NLogLoggerProvider(); + _newLog = nlogLoggerProvider.CreateLogger("SERVER"); + _hostingEnvironment = hostingEnvironment; + Sockeye.Util.ApplicationLogging.LoggerProvider = nlogLoggerProvider; + ServerBootConfig.SOCKEYE_CONTENT_ROOT_PATH = hostingEnvironment.ContentRootPath; + + } + + private readonly ILogger _newLog; + private string _connectionString = ""; + private readonly Microsoft.AspNetCore.Hosting.IWebHostEnvironment _hostingEnvironment; + + + + //////////////////////////////////////////////////////////// + // + // + public void ConfigureServices(IServiceCollection services) + { + _newLog.LogDebug("Initializing services..."); + + _newLog.LogDebug("Health"); + services.AddHealthChecks().AddDbContextCheck(); ; + + _newLog.LogDebug("Profiler"); + //https://dotnetthoughts.net/using-miniprofiler-in-aspnetcore-webapi/ + services.AddMemoryCache(); + services.AddMiniProfiler(options => + { + options.RouteBasePath = "/profiler"; + //in testing only ignorepaths was reliable and worked and docs say it prevents any profiling at all + options.IgnorePath("/auth").IgnorePath("/user").IgnorePath("/docs").IgnorePath("/cust").IgnorePath("/notify/hello").IgnorePath("/notify/new-count"); + options.ResultsAuthorize = request => + { + if (request.HttpContext.Items["AY_PROFILER_ALLOWED"] != null) + return true; + return false; + }; + + }).AddEntityFramework(); + + + //Server state service for shutting people out of api + _newLog.LogDebug("ServerState service"); + services.AddSingleton(new Sockeye.Api.ControllerHelpers.ApiServerState()); + + + _newLog.LogDebug("Mail service"); + services.AddSingleton(); + + //Init controllers + _newLog.LogDebug("Controllers"); + var MvcBuilder = services.AddControllers(config => + { + // config.Filters.Add(new Sockeye.Api.ControllerHelpers.ApiCustomExceptionFilter(Sockeye.Util.ApplicationLogging.LoggerFactory)); + config.Filters.Add(new Sockeye.Api.ControllerHelpers.ApiCustomExceptionFilter(_newLog)); + }); + + + + + //Prevent default model binding automatic 400 page so we can consistently show *our* error to our specs + //https://docs.microsoft.com/en-us/aspnet/core/web-api/index?view=aspnetcore-3.1#automatic-http-400-responses + MvcBuilder.ConfigureApiBehaviorOptions(options => + { + options.SuppressModelStateInvalidFilter = true; + }); + + _newLog.LogDebug("JSON"); + + MvcBuilder.AddNewtonsoftJson(options => + { + options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.None; + options.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc; + + }); + + + //HTTP CLIENT FACTORY USED BY LICENSE.CS + //https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1 + _newLog.LogDebug("HTTPClientFactory"); + services.AddHttpClient(); + + + + + + //TODO: ENsure report files here + + #region DATABASE + + + _connectionString = ServerBootConfig.SOCKEYE_DB_CONNECTION; + + //Check DB server exists and can be connected to + _newLog.LogDebug("Testing database server connection..."); + + //parse the connection string properly + DbUtil.ParseConnectionString(_newLog, _connectionString); + + //Probe for database server + //Will retry every 5 seconds for up to 5 minutes before bailing + if (!DbUtil.DatabaseServerExists(_newLog, "Waiting for db server ")) + { + var err = $"E1000 - Sockeye can't connect to the database server after trying for 5 minutes (connection string is:\"{DbUtil.DisplayableConnectionString}\")"; + _newLog.LogCritical(err); + throw new System.ApplicationException(err); + } + + + //We have db available + ServerGlobalOpsSettingsCache.DBAVAILABLE = true; + + _newLog.LogInformation("Connected to database server - {0}", DbUtil.DisplayableConnectionString); + + + + //ensure database is ready and present + DbUtil.EnsureDatabaseExists(_newLog); + + bool LOG_SENSITIVE_DATA = false; + +#if (DEBUG) + LOG_SENSITIVE_DATA = false;//############################################################################ + +#endif + + _newLog.LogDebug("EF Core"); + + + //change to resolve error: + //2020-12-28 09:20:14.3545|WARN|Microsoft.EntityFrameworkCore.Infrastructure|'AddEntityFramework*' was called on the service provider, but 'UseInternalServiceProvider' wasn't called in the DbContext options configuration. Consider removing the 'AddEntityFramework*' call, as in most cases it's not needed and may cause conflicts with other products and services registered in the same service provider. + //https://stackoverflow.com/questions/62917136/addentityframework-was-called-on-the-service-provider-but-useinternalservic + // services.AddEntityFrameworkNpgsql().AddDbContext( + // options => options.UseNpgsql(_connectionString + // //,opt => opt.EnableRetryOnFailure()//REMOVED THIS BECAUSE IT WAS INTEFERING WITH TRANSACTIONS BUT THEN DIDN'T USE THE TRANSACTION BUT IT SEEMS FASTER WITHOUT IT AS WELL SO...?? + // )//http://www.npgsql.org/efcore/misc.html?q=execution%20strategy#execution-strategy + // .ConfigureWarnings(warnings => //https://livebook.manning.com/#!/book/entity-framework-core-in-action/chapter-12/v-10/85 + // warnings.Throw( //Throw an exception on client eval, not necessarily an error but a smell + // // Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.QueryClientEvaluationWarning + // )) + // .EnableSensitiveDataLogging(LOG_SENSITIVE_DATA) + // ); + + services.AddDbContext(options => options.UseNpgsql(_connectionString).ConfigureWarnings(warnings => warnings.Throw()).EnableSensitiveDataLogging(LOG_SENSITIVE_DATA)); + + #endregion + + + // Add service and create Policy with options + _newLog.LogDebug("CORS"); + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", + builder => builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader().SetPreflightMaxAge(TimeSpan.FromSeconds(600)) + ); + }); + + + + + #region Swagger + + services + .AddApiVersioning(options => + { + options.AssumeDefaultVersionWhenUnspecified = true; + options.DefaultApiVersion = Microsoft.AspNetCore.Mvc.ApiVersion.Parse("8.0"); + options.ReportApiVersions = true; + }); + services.AddVersionedApiExplorer(options => + { + + // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service + // note: the specified format code will format the version as "'v'major[.minor][-status]" + options.GroupNameFormat = "'v'VVV"; + + // note: this option is only necessary when versioning by url segment. the SubstitutionFormat + // can also be used to control the format of the API version in route templates + + //THIS IS WHAT ADDS THE API version PARAMETER AUTOMATICALLY so you don't need to type an 8 in every swagger-ui route test + options.SubstituteApiVersionInUrl = true; + }); + services.AddTransient, ConfigureSwaggerOptions>(); + services.AddSwaggerGen( + c => + { + // integrate xml comments + c.IncludeXmlComments(XmlCommentsFilePath); + + //https://stackoverflow.com/questions/56234504/migrating-to-swashbuckle-aspnetcore-version-5 + //First we define the security scheme + c.AddSecurityDefinition("Bearer", //Name the security scheme + new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme.", + Type = SecuritySchemeType.Http, //We set the scheme type to http since we're using bearer authentication + Scheme = "bearer" //The name of the HTTP Authorization scheme to be used in the Authorization header. In this case "bearer". + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement{ + { + new OpenApiSecurityScheme{ + Reference = new OpenApiReference{ + Id = "Bearer", //The name of the previously defined security scheme. + Type = ReferenceType.SecurityScheme + } + },new List() + } + }); + } + ); + + + #endregion + + #region JWT AUTHENTICATION + //get the key if specified + var secretKey = ServerBootConfig.SOCKEYE_JWT_SECRET; + + //If no key specified make a unique one + //This means the jwt creds won't survive a server reboot + //so in that case users need to specify an Sockeye_JWT_SECRET environment variable + if (string.IsNullOrWhiteSpace(secretKey)) + { + _newLog.LogWarning("SOCKEYE_JWT_SECRET configuration setting is missing; Sockeye will randomly generate one. Any Users who were logged in when the server restarted will need to login to get a fresh auth token. See manual 'SOCKEYE_JWT_SECRET' page for details."); + secretKey = Util.Hasher.GenerateSalt(); + } + //WAS "UNLICENSED5G*QQJ8#bQ7$Xr_@sXfHq4" + + + //If secretKey is less than 32 characters, pad it + if (secretKey.Length < 32) + { + secretKey = secretKey.PadRight(32, '-'); + } + + ServerBootConfig.SOCKEYE_JWT_SECRET = secretKey; + var signingKey = new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes(ServerBootConfig.SOCKEYE_JWT_SECRET)); + + _newLog.LogDebug("Authorization"); + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(options => + { + // options.AutomaticAuthenticate = true; + // options.AutomaticChallenge = true; + options.TokenValidationParameters = new TokenValidationParameters + { + // Token signature will be verified using a private key. + ValidateIssuerSigningKey = true, + RequireSignedTokens = true, + IssuerSigningKey = signingKey, + ValidateIssuer = true, + ValidIssuer = "rockfish.ayanova.com", + ValidateAudience = false, + //ValidAudience = "http://localhost:7575/" + + // Token will only be valid if not expired yet, with 5 minutes clock skew. + ValidateLifetime = true, + RequireExpirationTime = true, + ClockSkew = TimeSpan.Zero//new TimeSpan(0, 0, 2), + }; + }); + + + + #endregion + + + + + _newLog.LogDebug("Generator"); + services.AddSingleton(); + + } + + + + //////////////////////////////////////////////////////////// + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + // + public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IWebHostEnvironment env, + AyContext dbContext, IApiVersionDescriptionProvider provider, Sockeye.Api.ControllerHelpers.ApiServerState apiServerState, + IServiceProvider serviceProvider) + { + _newLog.LogDebug("Configuring request pipeline..."); + + //this *may* be useful in the event of an issue so uncomment if necessary but errors during dev are handled equally by the logging, I think + // if (env.IsDevelopment()) + // { + // app.UseDeveloperExceptionPage(); + // } + + //Store a reference to the dependency injection service for static classes + ServiceProviderProvider.Provider = app.ApplicationServices; + + //Enable ability to handle reverse proxy + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedFor | Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedProto + }); + + + //If want to add a header to all (or filtered in here) requests + // app.Use(async (context, next) => + // { + // context.Response.Headers.Add("X-Developed-By", "Ground Zero Tech-Works inc."); + // await next.Invoke(); + // }); + + + #region STATIC FILES + _newLog.LogDebug("Static files"); + app.UseDefaultFiles(); + app.UseStaticFiles(); + //Might need the following if the page doesn't update in the client properly + //however the vue build process will automatically uniquify each build file names so maybe not required + // app.UseStaticFiles(new StaticFileOptions + // { + // OnPrepareResponse = context => + // { + // if (context.File.Name == "kindex.html") + // { + // context.Context.Response.Headers.Add("Cache-Control", "no-cache, no-store"); + // context.Context.Response.Headers.Add("Expires", "-1"); + // } + // } + // }); + #endregion + + _newLog.LogDebug("Routing pipeline"); + app.UseRouting();//this wasn't here for 2.2 but added for 3.0, needs to come before the stuff after + + _newLog.LogDebug("CORS pipeline"); + app.UseCors("CorsPolicy"); + + + #region AUTH / ROLES CUSTOM MIDDLEWARE + _newLog.LogDebug("Authentication pipeline"); + //Use authentication middleware + app.UseAuthentication(); + + _newLog.LogDebug("Authorization pipeline"); + app.UseAuthorization(); + + + //Custom middleware to ensure token still valid and to + //get user roles and put them into the request so + //they can be authorized in routes. + app.Use(async (context, next) => + { + if (!context.User.Identity.IsAuthenticated) + { + #region Profiler workaround + //Is this a profiler route? If so we're going to use the dl token to authorize + if (context.Request.Path.Value.StartsWith("/profiler/results")) + { + //someone is requesting the profiler + //check for a dl token "t" and rehydrate user if found + //Note that the profiler UI triggers it's own requests to to get the token + //we need to check the referer which was the first page of profile + + string token = string.Empty; + //is the token in the request? + if (context.Request.Query.ContainsKey("t")) + { + token = context.Request.Query["t"].ToString(); + } + else if (context.Request.Headers["Referer"].Count > 0) + {//Maybe it's in the referer + //try to split it on the ? + string[] stuff = context.Request.Headers["Referer"].ToString().Split('?'); + if (stuff.Count() > 1) + { + var q = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(stuff[1]); + if (q.ContainsKey("t")) + { + token = q["t"].ToString(); + } + } + } + + if (!string.IsNullOrWhiteSpace(token)) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + var u = ct.User.AsNoTracking().SingleOrDefault(z => z.DlKey == token.ToString() && z.Active == true); + if (u != null) + { + //this is necessary because they might have an expired JWT but this would just keep on working without a date check + //the default is the same timespan as the jwt so it's all good + var utcNow = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero); + if (u.DlKeyExpire > utcNow.DateTime) + { + if (Sockeye.Api.ControllerHelpers.Authorized.HasReadFullRole(u.Roles, SockType.ServerMetrics)) + context.Request.HttpContext.Items["AY_PROFILER_ALLOWED"] = true; + } + } + } + } + } + #endregion profiler workaround + + context.Request.HttpContext.Items["AY_ROLES"] = 0; + await next.Invoke(); + } + else + { + //Get user ID from claims + long userId = Convert.ToInt64(context.User.FindFirst(c => c.Type == "id").Value); + + //Get JWT + string JWT = string.Empty; + var AuthHeaders = context.Request.Headers[Microsoft.Net.Http.Headers.HeaderNames.Authorization]; + foreach (String s in AuthHeaders) + { + if (s.ToLowerInvariant().Contains("bearer")) + { + JWT = s.Split(' ')[1]; + break; + } + } + + //Get the database context + var ct = context.RequestServices.GetService(); + + //get the user record + var u = await ct.User.AsNoTracking().Where(a => a.Id == userId).Select(m => new { roles = m.Roles, name = m.Name, m.UserType, id = m.Id, translationId = m.UserOptions.TranslationId, currentAuthToken = m.CurrentAuthToken }).FirstAsync(); + context.Request.HttpContext.Items["AY_ROLES"] = u.roles; + context.Request.HttpContext.Items["AY_USERNAME"] = u.name; + context.Request.HttpContext.Items["AY_USER_ID"] = u.id; + context.Request.HttpContext.Items["AY_TRANSLATION_ID"] = u.translationId; + context.Request.HttpContext.Items["AY_USER_TYPE"] = u.UserType; + + var currentAuthToken = u.currentAuthToken; + + //turned out didn't need this for v8 migrate so far, but keeping in case it turns out to be handy down the road + // //Is import mode header set? + // if (context.Request.Headers.ContainsKey("X-AY-Import-Mode")) + // context.Request.HttpContext.Items["AY_IMPORT_MODE"] = true; + + + //CHECK JWT + if ( + !context.Request.Path.Value.EndsWith("/auth") && + !context.Request.Path.Value.EndsWith("notify/hello") && + u.currentAuthToken != JWT + )//except "/api/v8/auth" and prelogin notify/hello routes so user can login + { + + //It may be a local report render request from a job + bool bLocalReportRenderRequest = false; + if (context.Request.Host.Host == "127.0.0.1") + { + //check if token has j value set + if (context.Request.IsLocal()) + { + //if this is a generate report internal token this will be the override language set + //and it's existance is enough to indicate it was set internally by this server + var rpl = context.User.Claims.FirstOrDefault(c => c.Type == "rpl"); + if (rpl != null) + { + //it's a local request, the jwt already passed earlier so we issued it and it has the correct claim set so allow it + bLocalReportRenderRequest = true; + //set override language + context.Request.HttpContext.Items["AY_TRANSLATION_ID"] = Convert.ToInt64(rpl.Value); + await next.Invoke(); + } + } + } + if (!bLocalReportRenderRequest) + { + context.Response.StatusCode = 401; + context.Response.Headers.Add("X-Sockeye-Authorization-Error", "E2004 - Authorization token replaced by more recent login"); + await context.Response.WriteAsync("E2004 - Authorization token replaced by more recent login"); + } + } + else + { + await next.Invoke(); + } + } + + }); + + #endregion + + + _newLog.LogDebug("Profiler"); + app.UseMiniProfiler(); + + + + _newLog.LogDebug("Endpoints pipeline"); + app.UseEndpoints(endpoints => + { + endpoints.MapHealthChecks("/health"); + endpoints.MapControllers(); + }); + + #region SWAGGER + + _newLog.LogDebug("API explorer pipeline"); + // Enable middleware to serve generated Swagger as a JSON endpoint. + app.UseSwagger(); + app.UseSwaggerUI( + options => + { + foreach (var description in provider.ApiVersionDescriptions) + { + options.SwaggerEndpoint( + $"/swagger/{description.GroupName}/swagger.json", + description.GroupName.ToUpperInvariant()); + } + options.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None); + options.DefaultModelsExpandDepth(-1);//This is meant to hide the Models section that would appear at the bottom of the swagger ui showing *all* models from the api + options.DocumentTitle = "Sockeye API explorer"; + options.RoutePrefix = "api-docs"; + }); + + + #endregion swagger + + + + + if (ServerBootConfig.SOCKEYE_PERMANENTLY_ERASE_DATABASE) + { + _newLog.LogWarning("SOCKEYE_PERMANENTLY_ERASE_DATABASE has been set - deleting and recreating database"); + Util.DbUtil.DropAndRecreateDbAsync(_newLog).Wait(); + AySchema.CheckAndUpdateAsync(dbContext, _newLog).Wait(); + } + + + + var dbServerVersionInfo = DbUtil.DBServerVersion(dbContext); + var dbServerRunTimeParameters = DbUtil.DBServerRunTimeParameters(dbContext); + //Log server version + _newLog.LogInformation("Database server version - {0}", dbServerVersionInfo); + + //db server extended parameters + _newLog.LogTrace($"Database server runtime parameters{Environment.NewLine}{string.Join(Environment.NewLine, dbServerRunTimeParameters)}"); + + //log each item individually from runtime parameters + ServerBootConfig.DBSERVER_DIAGNOSTIC_INFO.Add("DB SERVER", dbServerVersionInfo); + foreach (var p in dbServerRunTimeParameters) + ServerBootConfig.DBSERVER_DIAGNOSTIC_INFO.Add(p.Key, p.Value); + + + //Check schema + _newLog.LogDebug("DB schema check"); + AySchema.CheckAndUpdateAsync(dbContext, _newLog).Wait(); + + + //Check database integrity + _newLog.LogDebug("DB integrity check"); + DbUtil.CheckFingerPrintAsync(AySchema.EXPECTED_COLUMN_COUNT, + AySchema.EXPECTED_INDEX_COUNT, + AySchema.EXPECTED_CHECK_CONSTRAINTS, + AySchema.EXPECTED_FOREIGN_KEY_CONSTRAINTS, + AySchema.EXPECTED_VIEWS, + AySchema.EXPECTED_ROUTINES, + _newLog).Wait(); + + + //Set static global biz settings + _newLog.LogDebug("Global settings"); + ServerGlobalBizSettings.Initialize(null, dbContext); + + _newLog.LogDebug("Ops settings"); + ServerGlobalOpsSettingsCache.Initialize(dbContext); + + //Ensure translations are present, not missing any keys and that there is a server default translation that exists + TranslationBiz lb = new TranslationBiz(dbContext, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.OpsAdmin); + lb.ValidateTranslationsAsync().Wait(); + + + //SPA FALLBACK ROUTE + app.Use(async (context, next) => + { + //to support html5 pushstate routing in spa + //this ensures that a refresh at the client will not 404 but rather force back to the index.html app page and then handled internally by the client + await next(); + if (!context.Response.HasStarted && !context.Request.Path.Value.StartsWith("/api") && context.Request.Path.Value != "/docs" && context.Request.Path.Value != "/cust" && context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value)) + { + context.Request.Path = "/index.html"; + context.Response.StatusCode = 200; + context.Response.ContentType = "text/html"; + await context.Response.SendFileAsync(Path.Combine(env.WebRootPath, "index.html")); + } + }); + + + //Log schema version into server log (would only otherwise log if schema was out of date) + _newLog.LogInformation($"DB Schema version - {Sockeye.Util.AySchema.currentSchema}"); + ServerBootConfig.BOOT_DIAGNOSTIC_INFO.Add("DB Schema version", Sockeye.Util.AySchema.currentSchema.ToString()); + + + + //Check for SuperUser password override + if (!string.IsNullOrWhiteSpace(ServerBootConfig.SOCKEYE_SET_SUPERUSER_PW)) + { + _newLog.LogWarning($"### SOCKEYE_SET_SUPERUSER_PW IS PRESENT - RESETTING SUPERUSER PASSWORD NOW... ###"); + Sockeye.Biz.UserBiz.ResetSuperUserPassword(); + _newLog.LogWarning($"### SOCKEYE_SET_SUPERUSER_PW HAS BEEN USED TO RESET SUPER USER PASSWORD YOU CAN REMOVE THIS SETTING NOW ###"); + } + + //Boot lock for generator + ServerGlobalOpsSettingsCache.BOOTING = false; + + //Open up the server for visitors + _newLog.LogDebug("Setting server state open"); + apiServerState.SetOpen(); + + //final startup log + _newLog.LogInformation("Boot complete - server open"); + + //flag in console that server is open + Console.WriteLine("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"); + Console.WriteLine("BOOT: COMPLETED - SERVER OPEN"); + Console.WriteLine($"SOCKEYE_USE_URLS setting: \"{ServerBootConfig.SOCKEYE_USE_URLS}\""); + Console.WriteLine("Controlled shutdown: Sockeye APP -> \"Operations\" -> \"ServerState\" -> \"Shut down server\" from menu"); + Console.WriteLine("Forced shutdown: Ctrl+C keyboard shortcut"); + Console.WriteLine("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"); + + } + + + #region Swagger and API Versioning utilities + + static string XmlCommentsFilePath + { + get + { + var basePath = AppContext.BaseDirectory; + var fileName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name + ".xml"; + return Path.Combine(basePath, fileName); + } + } + + + #endregion + } +} + diff --git a/server/appsettings.Development.json b/server/appsettings.Development.json new file mode 100644 index 0000000..8a42341 --- /dev/null +++ b/server/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Trace", + "System": "Trace", + "Microsoft": "Trace" + } + } +} diff --git a/server/appsettings.json b/server/appsettings.json new file mode 100644 index 0000000..5f4a8dd --- /dev/null +++ b/server/appsettings.json @@ -0,0 +1,17 @@ +{ + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Trace" + } + }, + "Console": { + "LogLevel": { + "Default": "Trace" + } + } + } + + +} diff --git a/server/biz/ApiErrorCode.cs b/server/biz/ApiErrorCode.cs new file mode 100644 index 0000000..2b6a382 --- /dev/null +++ b/server/biz/ApiErrorCode.cs @@ -0,0 +1,60 @@ +namespace Sockeye.Biz +{ + + + public enum ApiErrorCode : int + { + /* + DON'T FORGET TO UPDATE THE API-ERROR-CODES.MD DOCUMENTATION + AND UPDATE THE ApiErrorCodeStockMessage.cs + */ + + API_CLOSED = 2000, + API_OPS_ONLY = 2001, + API_SERVER_ERROR = 2002, + AUTHENTICATION_FAILED = 2003, + NOT_AUTHORIZED = 2004, + CONCURRENCY_CONFLICT = 2005, + NOT_FOUND = 2010, + PUT_ID_MISMATCH = 2020, + INVALID_OPERATION = 2030, + INSUFFICIENT_INVENTORY = 2040, + VALIDATION_FAILED = 2200, + VALIDATION_REQUIRED = 2201, + VALIDATION_LENGTH_EXCEEDED = 2202, + VALIDATION_INVALID_VALUE = 2203, + VALIDATION_CUSTOM_REQUIRED_EMPTY = 2204, + VALIDATION_MISSING_PROPERTY = 2205, + VALIDATION_NOT_UNIQUE = 2206, + VALIDATION_STARTDATE_AFTER_ENDDATE = 2207, + VALIDATION_REFERENTIAL_INTEGRITY = 2208, + VALIDATION_NOT_CHANGEABLE = 2209, + CHILD_OBJECT_ERROR = 2210, + VALIDATION_REQUIRED_CUSTOM = 2211, + VALIDATION_WO_MULTIPLE_CONTRACTED_UNITS = 2212, + /* + + | 2000 | API closed - Server is running but access to the API has been closed to all users | + | 2001 | API closed all non OPS routes - Server is running but access to the API has been restricted to only server maintenance operations related functionality | + | 2002 | Internal error from the API server, details in [server log](ops-log.md) file | + | 2003 | Authentication failed, bad login or password, user not found | + | 2004 | Not authorized - current user is not authorized for operation attempted on the resource (insufficient rights) | + | 2005 | Object was changed by another user since retrieval (concurrency token mismatch). A record was attempted to be saved but another user has just modified it so it's invalid. (first save "wins") | + | 2010 | Object not found - API could not find the object requested | + | 2020 | PUT Id mismatch - object Id does not match route Id | + | 2030 | Invalid operation - operation could not be completed, not valid, details in message property | + | 2200 | Validation error - general issue with object overall not valid, specifics in "details" property | + | 2201 | Validation error - Field is required but is empty or null | + | 2202 | Validation error - Field length exceeded. The limit will be returned in the `message` property of the validation error | + | 2203 | Validation error - invalid value. Usually an type mismatch or a logical or business rule mismatch (i.e. only certain values are valid for current state of object) | + | 2204 | Validation error - Customized form property is set to required but has an empty value | + | 2205 | Validation error - Required property is missing entirely. Usually a development or communications error | + | 2206 | Validation error - A text property is required to be unique but an existing record with an identical value was found in the database | + | 2207 | Validation error - When an object requires a start and end date the start date must be earlier than the end date | + | 2208 | Validation error - Modifying the object (usually a delete) would break the link to other records in the database and operation was disallowed to preserve data integrity | + | 2209 | Validation error - Indicates the attempted property change is invalid because the value is fixed and cannot be changed | */ + + } + + +}//eons \ No newline at end of file diff --git a/server/biz/ApiErrorCodeStockMessage.cs b/server/biz/ApiErrorCodeStockMessage.cs new file mode 100644 index 0000000..482931e --- /dev/null +++ b/server/biz/ApiErrorCodeStockMessage.cs @@ -0,0 +1,95 @@ +using System; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using System.Linq; +using System.Collections.Generic; +using Sockeye.Biz; + + +namespace Sockeye.Biz +{ + + + internal static class ApiErrorCodeStockMessage + { + internal static string GetTranslationCodeForApiErrorCode(ApiErrorCode code) + { + return $"ErrorAPI"+((int)code).ToString(); + // switch (code) + // { + // case ApiErrorCode.API_CLOSED: + // return "API Closed"; + // case ApiErrorCode.API_OPS_ONLY: + // return "API Closed to non operations routes"; + // case ApiErrorCode.API_SERVER_ERROR: + // return "Server internal error, details in server log file"; + // case ApiErrorCode.AUTHENTICATION_FAILED: + // return "Authentication failed"; + // case ApiErrorCode.NOT_AUTHORIZED: + // return "User not authorized for this resource operation (insufficient rights)"; + // case ApiErrorCode.CONCURRENCY_CONFLICT: + // return "Object was changed by another user since retrieval (concurrency token mismatch)"; + // case ApiErrorCode.NOT_FOUND: + // return "Object not found"; + // case ApiErrorCode.PUT_ID_MISMATCH: + // return "Update failed: ID mismatch - route ID doesn't match object id"; + // case ApiErrorCode.INVALID_OPERATION: + // return "An attempt was made to perform an invalid operation"; + // case ApiErrorCode.VALIDATION_FAILED: + // return "Object did not pass validation"; + // case ApiErrorCode.VALIDATION_REQUIRED: + // return "Required field empty"; + // case ApiErrorCode.VALIDATION_LENGTH_EXCEEDED: + // return "Field too long"; + // case ApiErrorCode.VALIDATION_INVALID_VALUE: + // return "Field is set to a non allowed value"; + // case ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY: + // return "Customized form property is set to required but has an empty value"; + // case ApiErrorCode.VALIDATION_MISSING_PROPERTY: + // return "Required property is missing entirel"; + // case ApiErrorCode.VALIDATION_NOT_UNIQUE: + // return "Field is required to be unique but an existing record with an identical value was found in the database"; + // case ApiErrorCode.VALIDATION_STARTDATE_AFTER_ENDDATE: + // return "The start date must be earlier than the end date"; + // case ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY: + // return "Modifying the object (usually a delete) would break the link to other records in the database and operation was disallowed to preserve data integrity"; + // case ApiErrorCode.VALIDATION_NOT_CHANGEABLE: + // return "the value is fixed and cannot be changed"; + // case ApiErrorCode.CHILD_OBJECT_ERROR: + // return "Errors in child object during operation"; + + + + + // default: + // return null; + + // } + } + /* + API_CLOSED = 2000, + API_OPS_ONLY = 2001, + API_SERVER_ERROR = 2002, + AUTHENTICATION_FAILED = 2003, + NOT_AUTHORIZED = 2004, + CONCURRENCY_CONFLICT=2005, + NOT_FOUND = 2010, + PUT_ID_MISMATCH = 2020, + INVALID_OPERATION = 2030, + VALIDATION_FAILED = 2200, + VALIDATION_REQUIRED = 2201, + VALIDATION_LENGTH_EXCEEDED = 2202, + VALIDATION_INVALID_VALUE = 2203, + + VALIDATION_CUSTOM_REQUIRED_EMPTY = 2204, + VALIDATION_MISSING_PROPERTY = 2205, + VALIDATION_NOT_UNIQUE = 2206, + VALIDATION_STARTDATE_AFTER_ENDDATE = 2207, + VALIDATION_REFERENTIAL_INTEGRITY = 2208, + VALIDATION_NOT_CHANGEABLE = 2209 + + */ + } + + +}//eons \ No newline at end of file diff --git a/server/biz/AttachmentBiz.cs b/server/biz/AttachmentBiz.cs new file mode 100644 index 0000000..fc72bbd --- /dev/null +++ b/server/biz/AttachmentBiz.cs @@ -0,0 +1,248 @@ +using System.Threading.Tasks; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using Microsoft.Extensions.Logging; +using System; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using System.Collections.Generic; + + +namespace Sockeye.Biz +{ + + + /// + /// Handle attachment file related cleanup and checking + /// + internal class AttachmentBiz : BizObject, IJobObject + { + internal AttachmentBiz(AyContext dbcontext, long currentUserId, AuthorizationRoles userRoles) + { + ct = dbcontext; + UserId = currentUserId; + CurrentUserRoles = userRoles; + BizType = SockType.FileAttachment; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + public async Task HandleJobAsync(OpsJob job) + { + //Hand off the particular job to the corresponding processing code + //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so + //basically any error condition during job processing should throw up an exception if it can't be handled + + //There might be future other job types so doing it like this for all biz job handlers for now + switch (job.JobType) + { + case JobType.AttachmentMaintenance: + await ProcessAttachmentMaintenanceAsync(job); + break; + default: + throw new System.ArgumentOutOfRangeException($"AttachmentBiz.HandleJobAsync -> Invalid job type{job.JobType.ToString()}"); + } + } + + + /// + /// Handle the job + /// + /// + private async Task ProcessAttachmentMaintenanceAsync(OpsJob job) + { + ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("AttachmentMaintenanceJob"); + ApiServerState apiServerState = (ApiServerState)ServiceProviderProvider.Provider.GetService(typeof(ApiServerState)); + + //get the current server state so can set back to it later + ApiServerState.ServerState wasServerState = apiServerState.GetState(); + string wasReason = apiServerState.Reason; + try + { + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); + await JobsBiz.LogJobAsync(job.GId, $"LT:StartJob "); + apiServerState.SetOpsOnly("Attachment file maintenance"); + + //get a list of all attachment files currently on disk + var AllAttachmentFilesOnDisk = FileUtil.GetAllAttachmentFilePaths(); + + List AllDBFileFullPath = new List(); + + + // EXISTENCE CHECK + //iterate all records in chunks, update the existence bool field if it's incorrect only + bool moreRecords = true; + int skip = 0; + int chunkSize = 100; + do + { + var chunk = await ct.FileAttachment.AsNoTracking().OrderBy(z => z.Id).Skip(skip).Take(chunkSize).ToListAsync(); + if (chunk.Count < chunkSize) + { + //we've reached the end + moreRecords = false; + } + skip += chunkSize; + foreach (var i in chunk) + { + var FullPathName = FileUtil.GetPermanentAttachmentFilePath(i.StoredFileName); + AllDBFileFullPath.Add(FullPathName); + var FileExistsInReality = AllAttachmentFilesOnDisk.Contains(FullPathName); + var ParentBizObjectExistsInReality = await BizObjectExistsInDatabase.ExistsAsync(i.AttachToAType, i.AttachToObjectId, ct); + //does the db record reflect the same status as reality? + if (FileExistsInReality != i.Exists || !ParentBizObjectExistsInReality) + { + var f = await ct.FileAttachment.FirstOrDefaultAsync(z => z.Id == i.Id); + if (f != null) + { + f.Exists = FileExistsInReality; + if (!ParentBizObjectExistsInReality) + { + //switch it to notype + f.AttachToAType = SockType.NoType; + f.AttachToObjectId = 0; + } + await ct.SaveChangesAsync(); + } + } + + //DOES THE PARENT OBJECT EXIST? + + } + + } while (moreRecords); + + //I kept this block because I did a lot of work to figure it out but in the end I don't need it because + //a user will be moving attachments so they would no longer be existing on their old NOTYPE orphan location anyway + // //DE-ORPHANIZE ACTION (clean up former orphans) + // //people can attach an orphan to another record so this cleans that up + // //also, potentiallly + // //iterate orphaned file attachments to NOTHING type, if found to be attached to any other object remove the orphaned object attachment record in db + // //but keeping the physical file since it's attached to something else + // var AllOrphansInDb = await ct.FileAttachment.Where(z => z.AttachToAType == SockType.NoType).ToListAsync(); + // foreach (FileAttachment OrphanInDb in AllOrphansInDb) + // { + // if (await ct.FileAttachment.AnyAsync(z => z.StoredFileName==OrphanInDb.StoredFileName && z.AttachToAType != SockType.NoType)) + // { + // //It is also attached to something else so remove it from the nothing type + // ct.FileAttachment.Remove(OrphanInDb); + // await ct.SaveChangesAsync(); + // } + // } + + // ORPHANED FILES CHECK + var FilesOnDiskNotInDb = AllAttachmentFilesOnDisk.Except(AllDBFileFullPath); + if (FilesOnDiskNotInDb.Count() > 0) + await JobsBiz.LogJobAsync(job.GId, $"Found {FilesOnDiskNotInDb.Count()} physical files not known to be existing Attachments, creating attachment records tied to 'NoType' so they show in UI"); + + // FOREIGN FILES placed in folder directly outside of attachment system + // user thinks they can just drop them in or accidentally copies them here + // Or, user renames a folder for some reason? + // This is a good reason not to delete them, because they can just un-rename them to fix it + // SWEEPER JOB + // I think it should delete them outright, but maybe that's a bad idea, not sure + // ID them to see if they *should* be one of ours by the file name I guess since it's the only identifying characteristic + // is it in the correct folder which is named based on it's hash? + // Is it X characters long (they all are or not?) + // Does it have an extension? None of ours have an extension + + //Vet the file and see if it has the characteristics of an attachment before re-attaching it, if not, compile into a list then log it and notify + + //Attach any found into the NOTHING object type with id 0 so they will be represented in attachment list for being dealt with + List ForeignFilesNotLikelyAttachmentsFoundInAttachmentsFolder = new List(); + foreach (string orphan in FilesOnDiskNotInDb) + { + if (FileUtil.AppearsToBeAnOrphanedAttachment(orphan)) + { + FileAttachment fa = new FileAttachment(); + fa.AttachToObjectId = 0; + fa.AttachToAType = SockType.NoType; + fa.ContentType = "application/octet-stream";//most generic type, we don't know what it is + fa.DisplayFileName = "FOUND" + FileUtil.GetSafeDateFileName(); + fa.LastModified = DateTime.UtcNow; + fa.Notes = "Found in attachments folder not linked to an object"; + fa.StoredFileName = System.IO.Path.GetFileName(orphan); + await ct.FileAttachment.AddAsync(fa); + await ct.SaveChangesAsync(); + } + else + { + log.LogDebug($"Foreign file found in attachments folder but doesn't appear to belong {orphan}"); + ForeignFilesNotLikelyAttachmentsFoundInAttachmentsFolder.Add(orphan); + } + } + + //ok, these files could be important and shouldn't be here so notify and log as much as possible + var ffcount = ForeignFilesNotLikelyAttachmentsFoundInAttachmentsFolder.Count; + if (ffcount > 0) + { + string msg = string.Empty; + if (ffcount < 25) + { + //we can list them all + msg = $"{ffcount} files found in attachments folder that don't appear to belong there:"; + } + else + { + msg = $"{ffcount} files found in attachments folder that don't appear to belong there, here are the first 25"; + } + + log.LogDebug(msg); + await JobsBiz.LogJobAsync(job.GId, msg); + await NotifyEventHelper.AddOpsProblemEvent($"Attachments issue:{msg}"); + var outList = ForeignFilesNotLikelyAttachmentsFoundInAttachmentsFolder.Take(25).ToList(); + foreach (string s in outList) + { + log.LogDebug(s); + await JobsBiz.LogJobAsync(job.GId, s); + } + } + + await JobsBiz.LogJobAsync(job.GId, "Finished."); + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed); + } + catch (Exception ex) + { + log.LogError(ex, "AttachmentMaintenanceJob error during ops"); + + await JobsBiz.LogJobAsync(job.GId, $"AttachmentMaintenanceJob error during ops\r\n{ex.Message}"); + throw; + } + finally + { + log.LogInformation($"AttachmentMaintenanceJob: setting server state back to {wasServerState.ToString()}"); + apiServerState.SetState(wasServerState, wasReason); + } + } + + + + //Other job handlers here... + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE ATTACHMENTS TO NEW OBJECT + // + internal static async Task DuplicateAttachments(SockTypeId aSource, SockTypeId aDest, AyContext ct) + { + var sources = await ct.FileAttachment.AsNoTracking() + .Where(z => z.AttachToAType == aSource.SockType && z.AttachToObjectId == aSource.ObjectId) + .ToListAsync(); + if (sources.Count > 0) + { + foreach (var src in sources) + { + ct.FileAttachment.Add(new FileAttachment { AttachToObjectId = aDest.ObjectId, AttachToAType = aDest.SockType, StoredFileName = src.StoredFileName, DisplayFileName = src.DisplayFileName, ContentType = src.ContentType, LastModified = src.LastModified, Notes = src.Notes, Exists = src.Exists, Size = src.Size }); + } + await ct.SaveChangesAsync(); + } + } + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/AuthorizationRoles.cs b/server/biz/AuthorizationRoles.cs new file mode 100644 index 0000000..30ded28 --- /dev/null +++ b/server/biz/AuthorizationRoles.cs @@ -0,0 +1,621 @@ +using System; + +namespace Sockeye.Biz +{ + /// + /// Authorization roles + /// + [Flags] + public enum AuthorizationRoles : int + { + //https://stackoverflow.com/questions/8447/what-does-the-flags-enum-attribute-mean-in-c + //MAX 31 (2147483647)!!! or will overflow int and needs to be turned into a long + //Must be a power of two: https://en.wikipedia.org/wiki/Power_of_two + + ///No role set + NoRole = 0, + ///BizAdminRestricted + BizAdminRestricted = 1, + ///BizAdmin + BizAdmin = 2, + ///ServiceRestricted + ServiceRestricted = 4, + ///Service + Service = 8, + ///InventoryRestricted + InventoryRestricted = 16, + ///Inventory + Inventory = 32, + ///Accounting + Accounting = 64,//No limited role, not sure if there is a need + ///TechRestricted + TechRestricted = 128, + ///Tech + Tech = 256, + ///SubContractorRestricted + SubContractorRestricted = 512, //same as tech but restricted by further business rules (more fine grained) + ///SubContractor + SubContractor = 1024,//same as tech limited but restricted by further business rules (more fine grained) + ///ClientRestricted + CustomerRestricted = 2048, + ///Client + Customer = 4096, + ///OpsAdminRestricted + OpsAdminRestricted = 8192, + ///OpsAdmin + OpsAdmin = 16384, + ///Sales + Sales = 32768, + ///SalesRestricted + SalesRestricted = 65536, + + + ///Anyone of any role + All = BizAdminRestricted | BizAdmin | ServiceRestricted | Service | InventoryRestricted | + Inventory | Accounting | TechRestricted | Tech | SubContractorRestricted | + SubContractor | CustomerRestricted | Customer | OpsAdminRestricted | OpsAdmin | Sales | SalesRestricted + + // ,AllInsideUserRoles = BizAdminRestricted | BizAdmin | ServiceRestricted | Service | InventoryRestricted | + // Inventory | Accounting | TechRestricted | Tech | SubContractorRestricted | + // SubContractor | Sales | SalesRestricted | OpsAdminRestricted | OpsAdmin + + + + }//end AuthorizationRoles + //, 65536, 131072, 262144, 524288, 1,048,576 +}//end namespace GZTW.Sockeye.BLL + +/* +### INFO FOR DOCS #### + +official names for docs +"AuthorizationRoles": "Authorization roles", +"AuthorizationRoleNoRole": "No role", +"AuthorizationRoleBizAdminRestricted": "Business administration - restricted", +"AuthorizationRoleBizAdmin": "Business administration", +"AuthorizationRoleServiceRestricted": "Service - restricted", +"AuthorizationRoleService": "Service", +"AuthorizationRoleInventoryRestricted": "Inventory - restricted", +"AuthorizationRoleInventory": "Inventory", +"AuthorizationRoleAccounting": "Accounting", +"AuthorizationRoleTechRestricted": "Service technician - restricted", +"AuthorizationRoleTech": "Service technician", +"AuthorizationRoleSubContractorRestricted": "Subcontractor - restricted", +"AuthorizationRoleSubContractor": "Subcontractor", +"AuthorizationRoleCustomerRestricted": "Customer user - restricted", +"AuthorizationRoleCustomer": "Customer user", +"AuthorizationRoleOpsAdminRestricted": "System operations - restricted", +"AuthorizationRoleOpsAdmin": "System operations", +"AuthorizationRoleSalesRestricted": "Sales - restricted", +"AuthorizationRoleSales": "Sales", + +v8-beta-0.10 rights by role +{ + "data": { + "typeroles": [ + { + "sockType": "Backup", + "change": "OpsAdmin", + "readFullRecord": "BizAdminRestricted, BizAdmin, OpsAdminRestricted", + "select": "NoRole" + }, + { + "sockType": "BizMetrics", + "change": "BizAdmin", + "readFullRecord": "BizAdminRestricted, Accounting, Sales, SalesRestricted", + "select": "NoRole" + }, + { + "sockType": "Contract", + "change": "BizAdmin, Service, Accounting, Tech, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "Customer", + "change": "BizAdmin, Service, Accounting, Tech, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "CustomerNote", + "change": "BizAdmin, Service, Accounting, Tech, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "CustomerServiceRequest", + "change": "BizAdmin, Service, Customer", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, Tech, CustomerRestricted", + "select": "All" + }, + { + "sockType": "DataListSavedFilter", + "change": "BizAdmin", + "readFullRecord": "All", + "select": "NoRole" + }, + { + "sockType": "FileAttachment", + "change": "BizAdmin", + "readFullRecord": "BizAdminRestricted, BizAdmin", + "select": "NoRole" + }, + { + "sockType": "FormCustom", + "change": "BizAdmin", + "readFullRecord": "All", + "select": "NoRole" + }, + { + "sockType": "FormUserOptions", + "change": "All", + "readFullRecord": "All", + "select": "NoRole" + }, + { + "sockType": "Global", + "change": "BizAdmin", + "readFullRecord": "BizAdminRestricted", + "select": "NoRole" + }, + { + "sockType": "GlobalOps", + "change": "OpsAdmin", + "readFullRecord": "OpsAdminRestricted", + "select": "NoRole" + }, + { + "sockType": "HeadOffice", + "change": "BizAdmin, Service, Accounting, Tech, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "License", + "change": "BizAdmin", + "readFullRecord": "BizAdminRestricted", + "select": "NoRole" + }, + { + "sockType": "LoanUnit", + "change": "BizAdmin, Service, Accounting, Tech, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "LogFile", + "change": "NoRole", + "readFullRecord": "OpsAdminRestricted, OpsAdmin", + "select": "NoRole" + }, + { + "sockType": "Memo", + "change": "BizAdminRestricted, BizAdmin, ServiceRestricted, Service, InventoryRestricted, Inventory, Accounting, TechRestricted, Tech, SubContractorRestricted, SubContractor, OpsAdminRestricted, OpsAdmin, Sales, SalesRestricted", + "readFullRecord": "BizAdminRestricted, BizAdmin, ServiceRestricted, Service, InventoryRestricted, Inventory, Accounting, TechRestricted, Tech, SubContractorRestricted, SubContractor, OpsAdminRestricted, OpsAdmin, Sales, SalesRestricted", + "select": "BizAdminRestricted, BizAdmin, ServiceRestricted, Service, InventoryRestricted, Inventory, Accounting, TechRestricted, Tech, SubContractorRestricted, SubContractor, OpsAdminRestricted, OpsAdmin, Sales, SalesRestricted" + }, + { + "sockType": "Notification", + "change": "All", + "readFullRecord": "All", + "select": "NoRole" + }, + { + "sockType": "NotifySubscription", + "change": "All", + "readFullRecord": "All", + "select": "NoRole" + }, + { + "sockType": "OpsNotificationSettings", + "change": "OpsAdmin", + "readFullRecord": "BizAdminRestricted, BizAdmin, OpsAdminRestricted", + "select": "NoRole" + }, + { + "sockType": "Part", + "change": "BizAdmin, Inventory, Accounting", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, Service, InventoryRestricted", + "select": "All" + }, + { + "sockType": "PartAssembly", + "change": "BizAdmin, Inventory, Accounting", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, Service, InventoryRestricted", + "select": "All" + }, + { + "sockType": "PartInventory", + "change": "BizAdmin, Inventory, Accounting", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, Service, InventoryRestricted", + "select": "All" + }, + { + "sockType": "PartInventoryDataList", + "change": "BizAdmin, Inventory, Accounting", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, Service, InventoryRestricted", + "select": "All" + }, + { + "sockType": "PartInventoryRequest", + "change": "BizAdmin, Inventory, Accounting", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, Service, InventoryRestricted", + "select": "All" + }, + { + "sockType": "PartInventoryRequestDataList", + "change": "BizAdmin, Inventory, Accounting", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, Service, InventoryRestricted", + "select": "All" + }, + { + "sockType": "PartInventoryRestock", + "change": "BizAdmin, Inventory, Accounting", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, Service, InventoryRestricted", + "select": "All" + }, + { + "sockType": "PartWarehouse", + "change": "BizAdmin, Inventory, Accounting", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, Service, InventoryRestricted", + "select": "All" + }, + { + "sockType": "PickListTemplate", + "change": "BizAdmin", + "readFullRecord": "All", + "select": "NoRole" + }, + { + "sockType": "PM", + "change": "BizAdmin, Service", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "PMItem", + "change": "BizAdmin, Service", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "PMItemExpense", + "change": "BizAdmin, Service", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "PMItemLabor", + "change": "BizAdmin, Service", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "PMItemLoan", + "change": "BizAdmin, Service", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "PMItemOutsideService", + "change": "BizAdmin, Service", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "PMItemPart", + "change": "BizAdmin, Service", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "PMItemScheduledUser", + "change": "BizAdmin, Service", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "PMItemTask", + "change": "BizAdmin, Service", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "PMItemTravel", + "change": "BizAdmin, Service", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "PMItemUnit", + "change": "BizAdmin, Service", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "Project", + "change": "BizAdmin, Service, Accounting, Tech, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "PurchaseOrder", + "change": "BizAdmin, Inventory, Accounting", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, Service, InventoryRestricted", + "select": "All" + }, + { + "sockType": "Quote", + "change": "BizAdmin, Service, Accounting, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "QuoteItem", + "change": "BizAdmin, Service, Accounting, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "QuoteItemExpense", + "change": "BizAdmin, Service, Accounting, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "QuoteItemLabor", + "change": "BizAdmin, Service, Accounting, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "QuoteItemLoan", + "change": "BizAdmin, Service, Accounting, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "QuoteItemOutsideService", + "change": "BizAdmin, Service, Accounting, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "QuoteItemPart", + "change": "BizAdmin, Service, Accounting, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "QuoteItemScheduledUser", + "change": "BizAdmin, Service, Accounting, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "QuoteItemTask", + "change": "BizAdmin, Service, Accounting, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "QuoteItemTravel", + "change": "BizAdmin, Service, Accounting, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "QuoteItemUnit", + "change": "BizAdmin, Service, Accounting, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "QuoteStatus", + "change": "BizAdmin, Service, Sales", + "readFullRecord": "All", + "select": "All" + }, + { + "sockType": "Reminder", + "change": "BizAdminRestricted, BizAdmin, ServiceRestricted, Service, InventoryRestricted, Inventory, Accounting, TechRestricted, Tech, SubContractorRestricted, SubContractor, OpsAdminRestricted, OpsAdmin, Sales, SalesRestricted", + "readFullRecord": "BizAdminRestricted, BizAdmin, ServiceRestricted, Service, InventoryRestricted, Inventory, Accounting, TechRestricted, Tech, SubContractorRestricted, SubContractor, OpsAdminRestricted, OpsAdmin, Sales, SalesRestricted", + "select": "BizAdminRestricted, BizAdmin, ServiceRestricted, Service, InventoryRestricted, Inventory, Accounting, TechRestricted, Tech, SubContractorRestricted, SubContractor, OpsAdminRestricted, OpsAdmin, Sales, SalesRestricted" + }, + { + "sockType": "Report", + "change": "BizAdminRestricted, BizAdmin", + "readFullRecord": "All", + "select": "All" + }, + { + "sockType": "Review", + "change": "BizAdminRestricted, BizAdmin, ServiceRestricted, Service, InventoryRestricted, Inventory, Accounting, TechRestricted, Tech, SubContractorRestricted, SubContractor, OpsAdminRestricted, OpsAdmin, Sales, SalesRestricted", + "readFullRecord": "BizAdminRestricted, BizAdmin, ServiceRestricted, Service, InventoryRestricted, Inventory, Accounting, TechRestricted, Tech, SubContractorRestricted, SubContractor, OpsAdminRestricted, OpsAdmin, Sales, SalesRestricted", + "select": "BizAdminRestricted, BizAdmin, ServiceRestricted, Service, InventoryRestricted, Inventory, Accounting, TechRestricted, Tech, SubContractorRestricted, SubContractor, OpsAdminRestricted, OpsAdmin, Sales, SalesRestricted" + }, + { + "sockType": "ServerJob", + "change": "OpsAdmin", + "readFullRecord": "BizAdminRestricted, BizAdmin, OpsAdminRestricted", + "select": "NoRole" + }, + { + "sockType": "ServerMetrics", + "change": "OpsAdmin", + "readFullRecord": "OpsAdminRestricted, OpsAdmin", + "select": "NoRole" + }, + { + "sockType": "ServerState", + "change": "OpsAdmin", + "readFullRecord": "All", + "select": "NoRole" + }, + { + "sockType": "ServiceRate", + "change": "BizAdmin, Service, Accounting", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, Service, Tech, Sales", + "select": "All" + }, + { + "sockType": "TaskGroup", + "change": "BizAdmin, Service", + "readFullRecord": "All", + "select": "All" + }, + { + "sockType": "TaxCode", + "change": "BizAdmin, Accounting", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, Service, TechRestricted, Tech, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "Translation", + "change": "BizAdmin", + "readFullRecord": "BizAdminRestricted", + "select": "All" + }, + { + "sockType": "TravelRate", + "change": "BizAdmin, Service, Accounting", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, Service, Tech, Sales", + "select": "All" + }, + { + "sockType": "TrialSeeder", + "change": "BizAdmin, OpsAdmin", + "readFullRecord": "BizAdminRestricted, OpsAdminRestricted", + "select": "NoRole" + }, + { + "sockType": "Unit", + "change": "BizAdmin, Service, Accounting, Tech, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "UnitMeterReading", + "change": "BizAdmin, Service, Accounting, Tech, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "UnitModel", + "change": "BizAdmin, Service, Accounting, Tech, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "User", + "change": "BizAdmin", + "readFullRecord": "BizAdminRestricted", + "select": "All" + }, + { + "sockType": "UserOptions", + "change": "BizAdmin", + "readFullRecord": "BizAdminRestricted", + "select": "NoRole" + }, + { + "sockType": "Vendor", + "change": "BizAdmin, Service, Inventory, Accounting, Tech, Sales", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrder", + "change": "BizAdmin, Service, Accounting, TechRestricted, Tech, SubContractorRestricted, SubContractor", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractorRestricted, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderItem", + "change": "BizAdmin, Service, Accounting, Tech", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractorRestricted, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderItemExpense", + "change": "BizAdmin, Service, Accounting, TechRestricted, Tech", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractorRestricted, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderItemLabor", + "change": "BizAdmin, Service, Accounting, TechRestricted, Tech, SubContractorRestricted, SubContractor", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractorRestricted, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderItemLoan", + "change": "BizAdmin, Service, Accounting, Tech", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractor, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderItemOutsideService", + "change": "BizAdmin, Service, Accounting, Tech", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderItemPart", + "change": "BizAdmin, Service, Accounting, Tech", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractor, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderItemPartRequest", + "change": "BizAdmin, Service, Accounting, Tech", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractor, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderItemPriority", + "change": "BizAdmin, Service, Accounting, Tech, SubContractor", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractorRestricted, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderItemScheduledUser", + "change": "BizAdmin, Service, Accounting, Tech", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractorRestricted, SubContractor, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderItemStatus", + "change": "BizAdmin, Service, Accounting, Tech, SubContractor", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractorRestricted, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderItemTask", + "change": "BizAdmin, Service, Accounting, TechRestricted, Tech, SubContractorRestricted, SubContractor", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractorRestricted, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderItemTravel", + "change": "BizAdmin, Service, Accounting, TechRestricted, Tech, SubContractorRestricted, SubContractor", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractorRestricted, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderItemUnit", + "change": "BizAdmin, Service, Accounting, Tech", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractor, Sales, SalesRestricted", + "select": "All" + }, + { + "sockType": "WorkOrderStatus", + "change": "BizAdmin, Service, Accounting, Tech, SubContractor", + "readFullRecord": "BizAdminRestricted, ServiceRestricted, TechRestricted, SubContractorRestricted, Sales, SalesRestricted", + "select": "All" + } + ] + } +} +*/ \ No newline at end of file diff --git a/server/biz/BizObject.cs b/server/biz/BizObject.cs new file mode 100644 index 0000000..adba985 --- /dev/null +++ b/server/biz/BizObject.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sockeye.Biz +{ + + /// + /// Business object base class + /// + internal abstract class BizObject : IBizObject + { + + public BizObject() + { + + } + + + + + + #region common props + + internal SockType BizType { get; set; } + internal Sockeye.Models.AyContext ct { get; set; } + internal long UserId { get; set; } + internal long UserTranslationId { get; set; } + internal AuthorizationRoles CurrentUserRoles { get; set; } + + #endregion + + internal async Task Translate(string key) + { + return await TranslationBiz.GetTranslationStaticAsync(key, UserTranslationId, ct); + } + + #region Error handling + private readonly List _errors = new List(); + + + + public List Errors => _errors; + + public bool HasErrors => _errors.Any(); + + public void ClearErrors() => _errors.Clear(); + + // public void AddvalidationError(ValidationError validationError) + // { + // _errors.Add(validationError); + // } + + public bool PropertyHasErrors(string propertyName) + { + if (_errors.Count == 0) return false; + var v = _errors.FirstOrDefault(m => m.Target == propertyName); + return (v != null); + + } + + public void AddError(ApiErrorCode errorCode, string propertyName = "generalerror", string errorMessage = null) + { + //if Target is generalerror that means show in UI in general error box of form + _errors.Add(new ValidationError() { Code = errorCode, Message = errorMessage, Target = propertyName }); + } + + +//TODO: CHILD COLLECTION MOD add error version for indexed child + + // //Add a bunch of errors, generally from a child object failed operastion + // public void AddErrors(List errors) + // { + // _errors.AddRange(errors); + // } + + public string GetErrorsAsString() + { + if (!HasErrors) return string.Empty; + + StringBuilder sb = new StringBuilder(); + // sb.AppendLine("LT:Errors - "); + foreach (ValidationError e in _errors) + { + var msg = $"LT:{ApiErrorCodeStockMessage.GetTranslationCodeForApiErrorCode(e.Code)}"; + if (!string.IsNullOrWhiteSpace(e.Message)) + msg += $", {e.Message}"; + if (!string.IsNullOrWhiteSpace(e.Target) && e.Target != "generalerror") + msg += $", field: {e.Target}"; + sb.AppendLine(msg); + } + return sb.ToString(); + } + + + #endregion error handling + + }//eoc + +}//eons \ No newline at end of file diff --git a/server/biz/BizObjectExistsInDatabase.cs b/server/biz/BizObjectExistsInDatabase.cs new file mode 100644 index 0000000..bf2020f --- /dev/null +++ b/server/biz/BizObjectExistsInDatabase.cs @@ -0,0 +1,75 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sockeye.Models; + + +namespace Sockeye.Biz +{ + + + //THIS IS USED BY THE ATTACHMENT CONTROLLER + //IN THEORY WE ONLY NEED TO CHECK FOR ATTACHABLE TYPES, BUT I CAN SEE IT'S POTENTIAL USEFULNESS DOWN THE ROAD FOR OTHER THINGS + internal static class BizObjectExistsInDatabase + { + + + //Returns existance status of object type and id specified in database + + internal static async Task ExistsAsync(SockType aType, long id, AyContext ct) + { + //new up a context?? + + switch (aType) + { + //CoreBizObject add here + case SockType.NoType://no type always exists and this is used by orphaned attachments + return true; + case SockType.FileAttachment: + return await ct.FileAttachment.AnyAsync(z => z.Id == id); + case SockType.DataListSavedFilter: + return await ct.DataListSavedFilter.AnyAsync(z => z.Id == id); + case SockType.FormCustom: + return await ct.FormCustom.AnyAsync(z => z.Id == id); + case SockType.User: + return await ct.User.AnyAsync(z => z.Id == id); + case SockType.Customer: + return await ct.Customer.AnyAsync(z => z.Id == id); + case SockType.CustomerNote: + return await ct.CustomerNote.AnyAsync(z => z.Id == id); + + case SockType.HeadOffice: + return await ct.HeadOffice.AnyAsync(z => z.Id == id); + + case SockType.Memo: + return await ct.Memo.AnyAsync(z => z.Id == id); + + + case SockType.Report: + return await ct.Report.AnyAsync(z => z.Id == id); + case SockType.Reminder: + return await ct.Reminder.AnyAsync(z => z.Id == id); + case SockType.Review: + return await ct.Review.AnyAsync(z => z.Id == id); + + + case SockType.CustomerNotifySubscription: + return await ct.CustomerNotifySubscription.AnyAsync(z => z.Id == id); + case SockType.Integration: + return await ct.Integration.AnyAsync(z => z.Id == id); + default: + throw new System.NotSupportedException($"Sockeye.Biz.BizObjectExistsInDatabase::ExistsAsync type {aType.ToString()} is not supported"); + } + + } + + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/BizObjectFactory.cs b/server/biz/BizObjectFactory.cs new file mode 100644 index 0000000..adeab6f --- /dev/null +++ b/server/biz/BizObjectFactory.cs @@ -0,0 +1,70 @@ +using Sockeye.Util; +using Sockeye.Models; + + +namespace Sockeye.Biz +{ + + + internal static class BizObjectFactory + { + + + //Returns the biz object class that corresponds to the type presented + //Used by SEARCH, REPORTING and objects with JOBS + internal static BizObject GetBizObject(SockType sockType, + AyContext ct, + long userId, + AuthorizationRoles roles, + long translationId = 0) + { + if (translationId == 0) + translationId = ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID; + + switch (sockType) + { + //CoreBizObject add here + case SockType.ServerJob: + return new JobOperationsBiz(ct, userId, roles); + + case SockType.Translation: + return new TranslationBiz(ct, userId, translationId, roles); + case SockType.DataListSavedFilter: + return new DataListSavedFilterBiz(ct, userId, translationId, roles); + case SockType.FormCustom: + return new FormCustomBiz(ct, userId, translationId, roles); + case SockType.FileAttachment: + return new AttachmentBiz(ct, userId, roles); + case SockType.Customer: + return new CustomerBiz(ct, userId, translationId, roles); + case SockType.CustomerNote: + return new CustomerNoteBiz(ct, userId, translationId, roles); + case SockType.User: + return new UserBiz(ct, userId, translationId, roles); + case SockType.Memo: + return new MemoBiz(ct, userId, translationId, roles); + case SockType.HeadOffice: + return new HeadOfficeBiz(ct, userId, translationId, roles); + case SockType.Reminder: + return new ReminderBiz(ct, userId, translationId, roles); + case SockType.Review: + return new ReviewBiz(ct, userId, translationId, roles); + case SockType.CustomerNotifySubscription: + return new CustomerNotifySubscriptionBiz(ct, userId, translationId, roles); + case SockType.Report: + return new ReportBiz(ct, userId, translationId, roles); + default: + throw new System.NotSupportedException($"Sockeye.BLL.BizObjectFactory::GetBizObject type {sockType.ToString()} is not supported"); + } + + + } + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/BizObjectNameFetcherDirect.cs b/server/biz/BizObjectNameFetcherDirect.cs new file mode 100644 index 0000000..fab2a5a --- /dev/null +++ b/server/biz/BizObjectNameFetcherDirect.cs @@ -0,0 +1,54 @@ +using Microsoft.Extensions.Logging; +using Microsoft.EntityFrameworkCore; +namespace Sockeye.Biz +{ + + //Turn a type and ID into a displayable name + //Used by search and eventlog processor + internal static class BizObjectNameFetcherDirect + { + internal static string Name(SockType sockType, long id, long translationid, System.Data.Common.DbCommand cmd) + { + try + { + string ret; + + cmd.CommandText = $"select PUBLIC.AYGETNAME({id}, {(int)sockType}, {translationid}) as m"; + using (var dr = cmd.ExecuteReader()) + { + if (dr.Read()) + { + if (dr.IsDBNull(0)) + ret = $"?? type:{sockType},id:{id}"; + else + ret = dr.GetString(0); + } + else + { + ret = "-"; + } + //return dr.Read() ? dr.GetString(0) : "-"; + } + return ret; + + } + catch + { + ((ILogger)Sockeye.Util.ApplicationLogging.CreateLogger("BizObjectNameFetcherDirect")).LogError($"### Error fetching for type {sockType}"); + throw; + } + } + + //warning: use the above in a loop, not this one + internal static string Name(SockType sockType, long id, long translationId, Sockeye.Models.AyContext ct) + { + using (var command = ct.Database.GetDbConnection().CreateCommand()) + { + ct.Database.OpenConnection(); + return Name(sockType, id,translationId, command); + } + } + + }//eoc +}//eons + diff --git a/server/biz/BizRoleSet.cs b/server/biz/BizRoleSet.cs new file mode 100644 index 0000000..cd91744 --- /dev/null +++ b/server/biz/BizRoleSet.cs @@ -0,0 +1,15 @@ +namespace Sockeye.Biz +{ + + /// + /// This is a set of roles to be stored in the central BizRoles with a key for each object type + /// + public class BizRoleSet + { + public AuthorizationRoles Change { get; set; } + public AuthorizationRoles ReadFullRecord { get; set; } + public AuthorizationRoles Select { get; set; } + + }//eoc + +}//eons \ No newline at end of file diff --git a/server/biz/BizRoles.cs b/server/biz/BizRoles.cs new file mode 100644 index 0000000..4b04e51 --- /dev/null +++ b/server/biz/BizRoles.cs @@ -0,0 +1,581 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Logging; + +namespace Sockeye.Biz +{ + + /// + /// roles of all business objects + /// + internal static class BizRoles + { + + //NOTE: this *is* efficient, it's static and initialized once only on startup the first time it's required and re-used forever afterwards until next reboot + + internal static Dictionary roles = new Dictionary(); + + static BizRoles() + { + + //Add all object roles here + //NOTE: do not need to add change roles to read roles, Authorized.cs takes care of that automatically + //by assuming if you can change you can read + + //HOW THIS WORKS / WHATS EXPECTED + //Change = CREATE, RETRIEVE, UPDATE, DELETE - Full rights + //ReadFullRecord = You can read *all* the fields of the record, but can't modify it. Change is automatically checked for so only add different roles from change + //SELECT - this role allows user to select (fetch picklist) this type of object on other forms, we have this security level because otherwise a Customer role user for example, could see other customers via api if not prohibited + // Setting SELECT - Select only needs to be set on objects for which there is a defined PickList object because that's where it's used solely (as of now anyway) + //DELETE = There is no specific delete right for now though it's checked for by routes in Authorized.cs in case we want to add it in future as a separate right from create. + + #region All roles initialization + //CoreBizObject add here + + + //BizRules will handle finer grained rights, this is just the big picture rights or default if no finer required + + //////////////////////////////////////////////////////////// + //CUSTOMER + // (any change copy to customer notes, head office) + roles.Add(SockType.Customer, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin + | AuthorizationRoles.Service + | AuthorizationRoles.Sales + | AuthorizationRoles.Accounting, + ReadFullRecord = AuthorizationRoles.BizAdminRestricted + | AuthorizationRoles.ServiceRestricted + | AuthorizationRoles.Tech + | AuthorizationRoles.SalesRestricted + , + Select = AuthorizationRoles.All + }); + + //////////////////////////////////////////////////////////// + //CUSTOMERNOTES + // (duplicate of customer above but required to be here to allow various code to not bomb) + roles.Add(SockType.CustomerNote, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin + | AuthorizationRoles.Service + | AuthorizationRoles.Sales + | AuthorizationRoles.Accounting, + ReadFullRecord = AuthorizationRoles.BizAdminRestricted + | AuthorizationRoles.ServiceRestricted + | AuthorizationRoles.SalesRestricted + | AuthorizationRoles.Tech + , + Select = AuthorizationRoles.All + }); + + + //////////////////////////////////////////////////////////// + //CUSTOMER PROXY NOTIFICATION SUBSCRIPTIONS + // + roles.Add(SockType.CustomerNotifySubscription, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin + | AuthorizationRoles.Service, + ReadFullRecord = AuthorizationRoles.BizAdminRestricted + | AuthorizationRoles.ServiceRestricted + | AuthorizationRoles.Tech + | AuthorizationRoles.SalesRestricted + , + Select = AuthorizationRoles.All + }); + + + + //////////////////////////////////////////////////////////// + //HeadOffice (duplicate of customer) + // + roles.Add(SockType.HeadOffice, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin + | AuthorizationRoles.Service + | AuthorizationRoles.Sales + | AuthorizationRoles.Accounting, + ReadFullRecord = AuthorizationRoles.BizAdminRestricted + | AuthorizationRoles.ServiceRestricted + | AuthorizationRoles.SalesRestricted + | AuthorizationRoles.Tech + , + Select = AuthorizationRoles.All + }); + + + //////////////////////////////////////////////////////////// + //GLOBAL BIZ SETTINGS + // + roles.Add(SockType.Global, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin, + ReadFullRecord = AuthorizationRoles.BizAdminRestricted + }); + + + //////////////////////////////////////////////////////////// + //GLOBAL OPS SETTINGS + // + roles.Add(SockType.GlobalOps, new BizRoleSet() + { + Change = AuthorizationRoles.OpsAdmin, + ReadFullRecord = AuthorizationRoles.OpsAdminRestricted + }); + + + //////////////////////////////////////////////////////////// + //USER + // + roles.Add(SockType.User, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin, + ReadFullRecord = AuthorizationRoles.BizAdminRestricted, + Select = AuthorizationRoles.All + }); + + //////////////////////////////////////////////////////////// + //USEROPTIONS + //(Identical to User, though route also allows own record access full changes) + // + roles.Add(SockType.UserOptions, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin, + ReadFullRecord = AuthorizationRoles.BizAdminRestricted + }); + + //////////////////////////////////////////////////////////// + //SERVERSTATE + // + roles.Add(SockType.ServerState, new BizRoleSet() + { + Change = AuthorizationRoles.OpsAdmin, + ReadFullRecord = AuthorizationRoles.All + }); + + + //////////////////////////////////////////////////////////// + //LOGFILE - server log, not event log + // + roles.Add(SockType.LogFile, new BizRoleSet() + { + Change = AuthorizationRoles.NoRole, + ReadFullRecord = AuthorizationRoles.OpsAdmin | AuthorizationRoles.OpsAdminRestricted + }); + + + //////////////////////////////////////////////////////////// + //BACKUP + //Only opsfull can change Backup + //ops and biz admin can view Backup + roles.Add(SockType.Backup, new BizRoleSet() + { + Change = AuthorizationRoles.OpsAdmin, + ReadFullRecord = AuthorizationRoles.OpsAdminRestricted | AuthorizationRoles.BizAdmin | AuthorizationRoles.BizAdminRestricted + }); + + + //////////////////////////////////////////////////////////// + //FILEATTACHMENT ADMINISTRATION + //This is not for attachments themselves which are tied to the object they are attached to + //this is for things like maintenance jobs or viewing lists of all attachments in general for batch ops etc + // NOTE: Attachments are considered business data and as such are not available to OPS roles + // who are not allowed to see biz data + roles.Add(SockType.FileAttachment, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin,//Need full rights only here because this is the rights checked for batch delete etc so it's simpler than checking all the parent object rights if you know they already have all rights + ReadFullRecord = AuthorizationRoles.BizAdmin | AuthorizationRoles.BizAdminRestricted + }); + + + //////////////////////////////////////////////////////////// + //OPERATIONS / JOBS + //Only opsfull can change operations + //ops and biz admin can view operations + roles.Add(SockType.ServerJob, new BizRoleSet() + { + Change = AuthorizationRoles.OpsAdmin, + ReadFullRecord = AuthorizationRoles.OpsAdminRestricted | AuthorizationRoles.BizAdmin | AuthorizationRoles.BizAdminRestricted + }); + + //////////////////////////////////////////////////////////// + //OPERATIONS / Notification settings + //Only opsfull can change operations + //ops and biz admin can view operations + roles.Add(SockType.OpsNotificationSettings, new BizRoleSet() + { + Change = AuthorizationRoles.OpsAdmin, + ReadFullRecord = AuthorizationRoles.OpsAdminRestricted | AuthorizationRoles.BizAdmin | AuthorizationRoles.BizAdminRestricted + }); + + + //////////////////////////////////////////////////////////// + //SERVERMETRICS + // + roles.Add(SockType.ServerMetrics, new BizRoleSet() + { + Change = AuthorizationRoles.OpsAdmin,//this is to turn on extra metrics (profiler) + ReadFullRecord = AuthorizationRoles.OpsAdmin | AuthorizationRoles.OpsAdminRestricted + }); + + + //////////////////////////////////////////////////////////// + //TRANSLATION + // + roles.Add(SockType.Translation, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin, + //Anyone can read it because they need to to open a form, but also in UI + //only the BizAdminRestricted actually gets a link to see the customization page + ReadFullRecord = AuthorizationRoles.BizAdminRestricted, + Select = AuthorizationRoles.All + }); + + + //////////////////////////////////////////////////////////// + //DATALISTFILTER + // + roles.Add(SockType.DataListSavedFilter, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin, + ReadFullRecord = AuthorizationRoles.All + }); + + //////////////////////////////////////////////////////////// + //FORMUSEROPTIONS + // Note: this is only ever modified by user personally + // so it is accessible by all and biz rules + //restrict to own userid + roles.Add(SockType.FormUserOptions, new BizRoleSet() + { + Change = AuthorizationRoles.All, + ReadFullRecord = AuthorizationRoles.All + }); + + //////////////////////////////////////////////////////////// + //FORMCUSTOM + // + roles.Add(SockType.FormCustom, new BizRoleSet() + { + //Only BizAdmin can modify forms + Change = AuthorizationRoles.BizAdmin, + //Anyone can read it because they need to to open a form, but also in UI + //only the BizAdminRestricted actually gets a link to see the customization page + ReadFullRecord = AuthorizationRoles.All + }); + + //////////////////////////////////////////////////////////// + //PICKLISTTEMPLATE + // + roles.Add(SockType.PickListTemplate, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin, + ReadFullRecord = AuthorizationRoles.All + }); + + //////////////////////////////////////////////////////////// + //BIZMETRICS + // todo: deprecate? Not used for anything as of nov 2020 + roles.Add(SockType.BizMetrics, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin, + ReadFullRecord = AuthorizationRoles.BizAdminRestricted | + AuthorizationRoles.Sales | + AuthorizationRoles.SalesRestricted | + AuthorizationRoles.Accounting + }); + + //////////////////////////////////////////////////////////// + //NOTIFICATION + // + roles.Add(SockType.Notification, new BizRoleSet() + { + Change = AuthorizationRoles.All, + ReadFullRecord = AuthorizationRoles.All + }); + + //////////////////////////////////////////////////////////// + //NOTIFICATION_SUBSCRIPTION + // + roles.Add(SockType.NotifySubscription, new BizRoleSet() + { + Change = AuthorizationRoles.All, + ReadFullRecord = AuthorizationRoles.All + }); + + //////////////////////////////////////////////////////////// + //REPORT + // + roles.Add(SockType.Report, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin | AuthorizationRoles.BizAdminRestricted, + ReadFullRecord = AuthorizationRoles.All, + Select = AuthorizationRoles.All + }); + + + + + + //////////////////////////////////////////////////////////// + //MEMO + // (everyone but outside users Customer and HO can send and receive memos) + roles.Add(SockType.Memo, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdminRestricted + | AuthorizationRoles.BizAdmin + | AuthorizationRoles.ServiceRestricted + | AuthorizationRoles.Service + | AuthorizationRoles.InventoryRestricted + | AuthorizationRoles.Inventory + | AuthorizationRoles.Accounting + | AuthorizationRoles.TechRestricted + | AuthorizationRoles.Tech + | AuthorizationRoles.SubContractorRestricted + | AuthorizationRoles.SubContractor + | AuthorizationRoles.Sales + | AuthorizationRoles.SalesRestricted + | AuthorizationRoles.OpsAdminRestricted + | AuthorizationRoles.OpsAdmin, + ReadFullRecord = AuthorizationRoles.BizAdminRestricted + | AuthorizationRoles.BizAdmin + | AuthorizationRoles.ServiceRestricted + | AuthorizationRoles.Service + | AuthorizationRoles.InventoryRestricted + | AuthorizationRoles.Inventory + | AuthorizationRoles.Accounting + | AuthorizationRoles.TechRestricted + | AuthorizationRoles.Tech + | AuthorizationRoles.SubContractorRestricted + | AuthorizationRoles.SubContractor + | AuthorizationRoles.Sales + | AuthorizationRoles.SalesRestricted + | AuthorizationRoles.OpsAdminRestricted + | AuthorizationRoles.OpsAdmin, + Select = AuthorizationRoles.BizAdminRestricted + | AuthorizationRoles.BizAdmin + | AuthorizationRoles.ServiceRestricted + | AuthorizationRoles.Service + | AuthorizationRoles.InventoryRestricted + | AuthorizationRoles.Inventory + | AuthorizationRoles.Accounting + | AuthorizationRoles.TechRestricted + | AuthorizationRoles.Tech + | AuthorizationRoles.SubContractorRestricted + | AuthorizationRoles.SubContractor + | AuthorizationRoles.Sales + | AuthorizationRoles.SalesRestricted + | AuthorizationRoles.OpsAdminRestricted + | AuthorizationRoles.OpsAdmin, + }); + + //////////////////////////////////////////////////////////// + //REMINDER + // (everyone but outside users Customer and HO) + roles.Add(SockType.Reminder, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdminRestricted + | AuthorizationRoles.BizAdmin + | AuthorizationRoles.ServiceRestricted + | AuthorizationRoles.Service + | AuthorizationRoles.InventoryRestricted + | AuthorizationRoles.Inventory + | AuthorizationRoles.Accounting + | AuthorizationRoles.TechRestricted + | AuthorizationRoles.Tech + | AuthorizationRoles.SubContractorRestricted + | AuthorizationRoles.SubContractor + | AuthorizationRoles.Sales + | AuthorizationRoles.SalesRestricted + | AuthorizationRoles.OpsAdminRestricted + | AuthorizationRoles.OpsAdmin, + ReadFullRecord = AuthorizationRoles.BizAdminRestricted + | AuthorizationRoles.BizAdmin + | AuthorizationRoles.ServiceRestricted + | AuthorizationRoles.Service + | AuthorizationRoles.InventoryRestricted + | AuthorizationRoles.Inventory + | AuthorizationRoles.Accounting + | AuthorizationRoles.TechRestricted + | AuthorizationRoles.Tech + | AuthorizationRoles.SubContractorRestricted + | AuthorizationRoles.SubContractor + | AuthorizationRoles.Sales + | AuthorizationRoles.SalesRestricted + | AuthorizationRoles.OpsAdminRestricted + | AuthorizationRoles.OpsAdmin, + Select = AuthorizationRoles.BizAdminRestricted + | AuthorizationRoles.BizAdmin + | AuthorizationRoles.ServiceRestricted + | AuthorizationRoles.Service + | AuthorizationRoles.InventoryRestricted + | AuthorizationRoles.Inventory + | AuthorizationRoles.Accounting + | AuthorizationRoles.TechRestricted + | AuthorizationRoles.Tech + | AuthorizationRoles.SubContractorRestricted + | AuthorizationRoles.SubContractor + | AuthorizationRoles.Sales + | AuthorizationRoles.SalesRestricted + | AuthorizationRoles.OpsAdminRestricted + | AuthorizationRoles.OpsAdmin, + }); + + //////////////////////////////////////////////////////////// + //REVIEW + // (everyone but outside users and follows object rights) + roles.Add(SockType.Review, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdminRestricted + | AuthorizationRoles.BizAdmin + | AuthorizationRoles.ServiceRestricted + | AuthorizationRoles.Service + | AuthorizationRoles.InventoryRestricted + | AuthorizationRoles.Inventory + | AuthorizationRoles.Accounting + | AuthorizationRoles.TechRestricted + | AuthorizationRoles.Tech + | AuthorizationRoles.SubContractorRestricted + | AuthorizationRoles.SubContractor + | AuthorizationRoles.Sales + | AuthorizationRoles.SalesRestricted + | AuthorizationRoles.OpsAdminRestricted + | AuthorizationRoles.OpsAdmin, + ReadFullRecord = AuthorizationRoles.BizAdminRestricted + | AuthorizationRoles.BizAdmin + | AuthorizationRoles.ServiceRestricted + | AuthorizationRoles.Service + | AuthorizationRoles.InventoryRestricted + | AuthorizationRoles.Inventory + | AuthorizationRoles.Accounting + | AuthorizationRoles.TechRestricted + | AuthorizationRoles.Tech + | AuthorizationRoles.SubContractorRestricted + | AuthorizationRoles.SubContractor + | AuthorizationRoles.Sales + | AuthorizationRoles.SalesRestricted + | AuthorizationRoles.OpsAdminRestricted + | AuthorizationRoles.OpsAdmin, + Select = AuthorizationRoles.BizAdminRestricted + | AuthorizationRoles.BizAdmin + | AuthorizationRoles.ServiceRestricted + | AuthorizationRoles.Service + | AuthorizationRoles.InventoryRestricted + | AuthorizationRoles.Inventory + | AuthorizationRoles.Accounting + | AuthorizationRoles.TechRestricted + | AuthorizationRoles.Tech + | AuthorizationRoles.SubContractorRestricted + | AuthorizationRoles.SubContractor + | AuthorizationRoles.Sales + | AuthorizationRoles.SalesRestricted + | AuthorizationRoles.OpsAdminRestricted + | AuthorizationRoles.OpsAdmin, + }); + + //////////////////////////////////////////////////////////// + //INTEGRATION + // (every unrestricted inside user and not subcontractor) + //this right is for the integration data itself, NOT any other Sockeye data + //so if someone is malicious the worst case scenario is they can mess up the integration data + // but they would still need rights to access any Sockeye data under their account so there is no loophole here + // technically an integration may be used by any role user + // however not likely to be read only or limited rights roles + // so will allow full access for any non restricted user and leave + // finer tuning of authorization to integrating app itself + // Also, integration is only used to store app data conveniently it in no way is required to + // write api accessing apps so any limitations are not preventing 3rd parties from writing Sockeye api consuming apps of any kind + // + roles.Add(SockType.Integration, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdmin + | AuthorizationRoles.Service + | AuthorizationRoles.Inventory + | AuthorizationRoles.Accounting + | AuthorizationRoles.Tech + | AuthorizationRoles.Sales + | AuthorizationRoles.OpsAdmin, + ReadFullRecord = AuthorizationRoles.BizAdmin + | AuthorizationRoles.Service + | AuthorizationRoles.Inventory + | AuthorizationRoles.Accounting + | AuthorizationRoles.Tech + | AuthorizationRoles.Sales + | AuthorizationRoles.OpsAdmin, + Select = AuthorizationRoles.BizAdmin + | AuthorizationRoles.Service + | AuthorizationRoles.Inventory + | AuthorizationRoles.Accounting + | AuthorizationRoles.Tech + | AuthorizationRoles.Sales + | AuthorizationRoles.OpsAdmin, + }); + + //////////////////////////////////////////////////////////////////// + #endregion all roles init + + + #region output as JSON for client side + +#if (DEBUG) + //ONGOING VALIDATION TO CATCH MISMATCH WHEN NEW ROLES ADDED (wont' catch changes to existing unfortunately) + //################## HOW TO USE ########## + //############## Uncomment code block below, put a break on lastRoles, copy from the output in the LOG (good for javascript with quotes formatted that way) ####### + // #### NEED to separately take a copy and update "lastRoles" string here by copying from the variable watch for the "json" variable shown in the debugger because need the C# format escaped quotes string + + //GENERATE CLIENT COMPATIBLE JSON FROM ROLES OUTPUT TO DEBUG LOG + //And seperately, set the JSON variable so can copy from debug variable "value" property for lastRoles here to compare + + + /* + + string json = Newtonsoft.Json.JsonConvert.SerializeObject(roles, Newtonsoft.Json.Formatting.None); + System.Diagnostics.Debugger.Log(1, "JSONFRAGMENTFORCLIENT", "BizRoles.cs -> biz-role-rights.js Client roles JSON fragment:\n\n"); + System.Diagnostics.Debugger.Log(1, "JSONFRAGMENTFORCLIENT", json + "\n\n"); + var lastRoles = "{\"Customer\":{\"Change\":32842,\"ReadFullRecord\":65797,\"Select\":131071},\"CustomerNote\":{\"Change\":32842,\"ReadFullRecord\":65797,\"Select\":131071},\"CustomerNotifySubscription\":{\"Change\":10,\"ReadFullRecord\":65797,\"Select\":131071},\"Contract\":{\"Change\":74,\"ReadFullRecord\":98565,\"Select\":131071},\"HeadOffice\":{\"Change\":32842,\"ReadFullRecord\":65797,\"Select\":131071},\"LoanUnit\":{\"Change\":74,\"ReadFullRecord\":98565,\"Select\":131071},\"Part\":{\"Change\":98,\"ReadFullRecord\":29,\"Select\":131071},\"PartInventory\":{\"Change\":98,\"ReadFullRecord\":29,\"Select\":131071},\"PartWarehouse\":{\"Change\":98,\"ReadFullRecord\":29,\"Select\":131071},\"PartAssembly\":{\"Change\":98,\"ReadFullRecord\":29,\"Select\":131071},\"PurchaseOrder\":{\"Change\":98,\"ReadFullRecord\":29,\"Select\":131071},\"PartInventoryRequest\":{\"Change\":98,\"ReadFullRecord\":29,\"Select\":131071},\"PartInventoryRestock\":{\"Change\":98,\"ReadFullRecord\":29,\"Select\":131071},\"PartInventoryDataList\":{\"Change\":98,\"ReadFullRecord\":29,\"Select\":131071},\"PartInventoryRequestDataList\":{\"Change\":98,\"ReadFullRecord\":29,\"Select\":131071},\"Project\":{\"Change\":74,\"ReadFullRecord\":98565,\"Select\":131071},\"ServiceRate\":{\"Change\":74,\"ReadFullRecord\":33037,\"Select\":131071},\"TravelRate\":{\"Change\":74,\"ReadFullRecord\":33037,\"Select\":131071},\"TaxCode\":{\"Change\":66,\"ReadFullRecord\":98573,\"Select\":131071},\"Unit\":{\"Change\":330,\"ReadFullRecord\":98309,\"Select\":131071},\"UnitModel\":{\"Change\":74,\"ReadFullRecord\":98565,\"Select\":131071},\"UnitMeterReading\":{\"Change\":330,\"ReadFullRecord\":98309,\"Select\":131071},\"Vendor\":{\"Change\":106,\"ReadFullRecord\":98565,\"Select\":131071},\"TaskGroup\":{\"Change\":10,\"ReadFullRecord\":131071,\"Select\":131071},\"WorkOrderStatus\":{\"Change\":74,\"ReadFullRecord\":98565,\"Select\":131071},\"WorkOrderItemStatus\":{\"Change\":74,\"ReadFullRecord\":98565,\"Select\":131071},\"WorkOrderItemPriority\":{\"Change\":74,\"ReadFullRecord\":98565,\"Select\":131071},\"WorkOrder\":{\"Change\":1994,\"ReadFullRecord\":98949,\"Select\":131071},\"WorkOrderItem\":{\"Change\":330,\"ReadFullRecord\":98949,\"Select\":131071},\"WorkOrderItemExpense\":{\"Change\":458,\"ReadFullRecord\":98949,\"Select\":131071},\"WorkOrderItemLabor\":{\"Change\":1994,\"ReadFullRecord\":98949,\"Select\":131071},\"WorkOrderItemLoan\":{\"Change\":330,\"ReadFullRecord\":99461,\"Select\":131071},\"WorkOrderItemPart\":{\"Change\":330,\"ReadFullRecord\":99461,\"Select\":131071},\"WorkOrderItemPartRequest\":{\"Change\":330,\"ReadFullRecord\":99461,\"Select\":131071},\"WorkOrderItemScheduledUser\":{\"Change\":330,\"ReadFullRecord\":99973,\"Select\":131071},\"WorkOrderItemTask\":{\"Change\":1994,\"ReadFullRecord\":98949,\"Select\":131071},\"WorkOrderItemTravel\":{\"Change\":1994,\"ReadFullRecord\":98949,\"Select\":131071},\"WorkOrderItemUnit\":{\"Change\":330,\"ReadFullRecord\":99461,\"Select\":131071},\"WorkOrderItemOutsideService\":{\"Change\":330,\"ReadFullRecord\":98437,\"Select\":131071},\"Quote\":{\"Change\":32842,\"ReadFullRecord\":65541,\"Select\":131071},\"QuoteItem\":{\"Change\":32842,\"ReadFullRecord\":65541,\"Select\":131071},\"QuoteItemExpense\":{\"Change\":32842,\"ReadFullRecord\":65541,\"Select\":131071},\"QuoteItemLabor\":{\"Change\":32842,\"ReadFullRecord\":65541,\"Select\":131071},\"QuoteItemLoan\":{\"Change\":32842,\"ReadFullRecord\":65541,\"Select\":131071},\"QuoteItemPart\":{\"Change\":32842,\"ReadFullRecord\":65541,\"Select\":131071},\"QuoteItemScheduledUser\":{\"Change\":32842,\"ReadFullRecord\":65541,\"Select\":131071},\"QuoteItemTask\":{\"Change\":32842,\"ReadFullRecord\":65541,\"Select\":131071},\"QuoteItemTravel\":{\"Change\":32842,\"ReadFullRecord\":65541,\"Select\":131071},\"QuoteItemUnit\":{\"Change\":32842,\"ReadFullRecord\":65541,\"Select\":131071},\"QuoteItemOutsideService\":{\"Change\":32842,\"ReadFullRecord\":65541,\"Select\":131071},\"QuoteStatus\":{\"Change\":32842,\"ReadFullRecord\":131071,\"Select\":131071},\"PM\":{\"Change\":10,\"ReadFullRecord\":98309,\"Select\":131071},\"PMItem\":{\"Change\":10,\"ReadFullRecord\":98309,\"Select\":131071},\"PMItemExpense\":{\"Change\":10,\"ReadFullRecord\":98309,\"Select\":131071},\"PMItemLabor\":{\"Change\":10,\"ReadFullRecord\":98309,\"Select\":131071},\"PMItemLoan\":{\"Change\":10,\"ReadFullRecord\":98309,\"Select\":131071},\"PMItemPart\":{\"Change\":10,\"ReadFullRecord\":98309,\"Select\":131071},\"PMItemScheduledUser\":{\"Change\":10,\"ReadFullRecord\":98309,\"Select\":131071},\"PMItemTask\":{\"Change\":10,\"ReadFullRecord\":98309,\"Select\":131071},\"PMItemTravel\":{\"Change\":10,\"ReadFullRecord\":98309,\"Select\":131071},\"PMItemUnit\":{\"Change\":10,\"ReadFullRecord\":98309,\"Select\":131071},\"PMItemOutsideService\":{\"Change\":10,\"ReadFullRecord\":98309,\"Select\":131071},\"Global\":{\"Change\":2,\"ReadFullRecord\":1,\"Select\":0},\"GlobalOps\":{\"Change\":16384,\"ReadFullRecord\":8192,\"Select\":0},\"User\":{\"Change\":2,\"ReadFullRecord\":1,\"Select\":131071},\"UserOptions\":{\"Change\":2,\"ReadFullRecord\":1,\"Select\":0},\"ServerState\":{\"Change\":16384,\"ReadFullRecord\":131071,\"Select\":0},\"License\":{\"Change\":2,\"ReadFullRecord\":49515,\"Select\":0},\"TrialSeeder\":{\"Change\":16386,\"ReadFullRecord\":8193,\"Select\":0},\"LogFile\":{\"Change\":0,\"ReadFullRecord\":24576,\"Select\":0},\"Backup\":{\"Change\":16384,\"ReadFullRecord\":8195,\"Select\":0},\"FileAttachment\":{\"Change\":2,\"ReadFullRecord\":3,\"Select\":0},\"ServerJob\":{\"Change\":16384,\"ReadFullRecord\":8195,\"Select\":0},\"OpsNotificationSettings\":{\"Change\":16384,\"ReadFullRecord\":8195,\"Select\":0},\"ServerMetrics\":{\"Change\":16384,\"ReadFullRecord\":24576,\"Select\":0},\"Translation\":{\"Change\":2,\"ReadFullRecord\":1,\"Select\":131071},\"DataListSavedFilter\":{\"Change\":2,\"ReadFullRecord\":131071,\"Select\":0},\"FormUserOptions\":{\"Change\":131071,\"ReadFullRecord\":131071,\"Select\":0},\"FormCustom\":{\"Change\":2,\"ReadFullRecord\":131071,\"Select\":0},\"PickListTemplate\":{\"Change\":2,\"ReadFullRecord\":131071,\"Select\":0},\"BizMetrics\":{\"Change\":2,\"ReadFullRecord\":98369,\"Select\":0},\"Notification\":{\"Change\":131071,\"ReadFullRecord\":131071,\"Select\":0},\"NotifySubscription\":{\"Change\":131071,\"ReadFullRecord\":131071,\"Select\":0},\"Report\":{\"Change\":3,\"ReadFullRecord\":131071,\"Select\":131071},\"CustomerServiceRequest\":{\"Change\":4106,\"ReadFullRecord\":2309,\"Select\":131071},\"Memo\":{\"Change\":124927,\"ReadFullRecord\":124927,\"Select\":124927},\"Reminder\":{\"Change\":124927,\"ReadFullRecord\":124927,\"Select\":124927},\"Review\":{\"Change\":124927,\"ReadFullRecord\":124927,\"Select\":124927},\"Integration\":{\"Change\":49514,\"ReadFullRecord\":49514,\"Select\":49514}}"; + Dictionary lastRolesDeserialized = Newtonsoft.Json.JsonConvert.DeserializeObject>(lastRoles); + if (lastRolesDeserialized.Count != roles.Count) + { + + { + ((ILogger)Sockeye.Util.ApplicationLogging.CreateLogger("BizRoles.cs")).LogWarning("BizRoles::Constructor - roles were modified from last snapshot for client!!!"); + } + } +*/ + + +#endif + #endregion + + + + + }//end of constructor + + + /// + /// Get roleset for biz object + /// + /// + /// + internal static BizRoleSet GetRoleSet(SockType forType) + { + if (roles.ContainsKey(forType)) + { + return roles[forType]; + } + else + { + return null; + } + } + + + + + + }//end of class + + +}//eons + diff --git a/server/biz/CoreBizObjectAttribute.cs b/server/biz/CoreBizObjectAttribute.cs new file mode 100644 index 0000000..4550d66 --- /dev/null +++ b/server/biz/CoreBizObjectAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace Sockeye.Biz +{ + /// + /// Marker attribute indicating that an object is a core business object + /// In other words a real world business relevant object and not a utility or internal type of object + /// something that would be worked with by users in the UI for business purposes + /// This is used to indicate that an object supports these features: + /// PickList selectable (has picklist) + /// Attachable (can attach to it) + /// Reviewable (can set a review on it) + /// ETC + /// Used in + /// + [AttributeUsage(AttributeTargets.All)] + public class CoreBizObjectAttribute : Attribute + { + //No code required, it's just a marker + //https://docs.microsoft.com/en-us/dotnet/standard/attributes/writing-custom-attributes + } +}//eons diff --git a/server/biz/CustomFieldType.cs b/server/biz/CustomFieldType.cs new file mode 100644 index 0000000..635dce1 --- /dev/null +++ b/server/biz/CustomFieldType.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; + +namespace Sockeye.Biz +{ + public static class CustomFieldType + { + + private static List _validCustomFieldTypes = new List(); + static CustomFieldType() + { + //v7 custom field types: + // - Currency + // - DateAndTime + // - TimeOnly + // - DateOnly + // - Number + // - Text + // - Bool + _validCustomFieldTypes.Add((int)UiFieldDataType.Currency); + _validCustomFieldTypes.Add((int)UiFieldDataType.Date); + _validCustomFieldTypes.Add((int)UiFieldDataType.Time); + _validCustomFieldTypes.Add((int)UiFieldDataType.DateTime); + _validCustomFieldTypes.Add((int)UiFieldDataType.Text); + _validCustomFieldTypes.Add((int)UiFieldDataType.Decimal); + _validCustomFieldTypes.Add((int)UiFieldDataType.Integer); + _validCustomFieldTypes.Add((int)UiFieldDataType.Bool); + + } + + + public static List ValidCustomFieldTypes + { + get + { + return _validCustomFieldTypes; + } + } + + } +} diff --git a/server/biz/CustomFieldsValidator.cs b/server/biz/CustomFieldsValidator.cs new file mode 100644 index 0000000..fac1358 --- /dev/null +++ b/server/biz/CustomFieldsValidator.cs @@ -0,0 +1,121 @@ +using Sockeye.Models; +using System.Linq; +using Newtonsoft.Json.Linq; + + +namespace Sockeye.Biz +{ + internal static class CustomFieldsValidator + { + internal static void Validate(BizObject biz, FormCustom formCustom, string customFields) + { + bool hasCustomData = !string.IsNullOrWhiteSpace(customFields); + + //No form custom = no template to check against so nothing to do + if (formCustom == null) + return; + + var FormTemplate = JArray.Parse(formCustom.Template); + var ThisFormCustomFieldsList = FormFieldOptionalCustomizableReference.FormFieldReferenceList(formCustom.FormKey).Where(z => z.IsCustomField == true).Select(z => z.TKey).ToList(); + + //If the customFields string is empty then only validation is if any of the fields are required to be filled in + if (!hasCustomData) + { + //iterate the template + for (int i = 0; i < FormTemplate.Count; i++) + { + //get the field customization + var fldKey = FormTemplate[i]["fld"].Value(); + var fldRequired = FormTemplate[i]["required"].Value(); + //Check if this is an expected custom field and that it was set to required + if (ThisFormCustomFieldsList.Contains(fldKey) && fldRequired == true) + { + //Ok, this field is required but custom fields are all empty so add this error + biz.AddError(ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY, fldKey); + } + } + + return; + } + + //here we have both a bunch of custom fields presumeably and a form customization so let's get cracking... + //parse the custom fields, it should contain an object with 16 keys + //NOTE: to save bandwidth the actual custom fields look like this: + // - {c1:"blah",c2:"blah",c3:"blah".....c16:"blah"} + //However the LT field names might be WidgetCustom1 or UserCustom16 so we need to translate by EndsWith + + //Top level object is a Object not an array when it comes to custom fields and the key names are the custom fields abbreviated + var CustomFieldData = JObject.Parse(customFields); + + //make sure all the *required* keys are present + foreach (string iFldKey in ThisFormCustomFieldsList) + { + + //Translate the LT field key to the actual customFieldData field key + var InternalCustomFieldName = FormFieldOptionalCustomizableReference.TranslateLTCustomFieldToInternalCustomFieldName(iFldKey); + //Check if it's set to required + var isRequired = CustomFieldIsSetToRequired(FormTemplate, iFldKey); + + //if it's not required then we don't care, jump to the next item... + if (!isRequired) + continue; + + //It's required, make sure the key is present and contains data + + if (CustomFieldData.ContainsKey(InternalCustomFieldName)) + { + //validate for now that the custom fields set as required have data in them. Note that we are not validating the sanity of the values, only that they exist + //trying to build in slack for when users inevitably change the TYPE of the custom field + //Maybe in future this will be handled more thoroughly here but for now just make sure it's been filled in + + //validate it + string CurrentValue = CustomFieldData[InternalCustomFieldName].Value(); + + if (string.IsNullOrWhiteSpace(CurrentValue)) + { + biz.AddError(ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY, iFldKey); + } + + // foreach (JObject jo in FormTemplate) + // { + // if (jo["fld"].Value() == iFldKey) + // { + // var fldRequired = jo["required"].Value(); + // if (fldRequired && string.IsNullOrWhiteSpace(CurrentValue)) + // { + // biz.AddError(ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY, iFldKey); + // } + // break; + // } + // } + + } + else + { + //This is a serious issue and invalidates all + biz.AddError(ApiErrorCode.VALIDATION_MISSING_PROPERTY, iFldKey); + } + } + + } + + /// + /// Check if field is required + /// + /// + /// + /// + private static bool CustomFieldIsSetToRequired(JArray FormTemplate, string FieldKey) + { + foreach (JObject jo in FormTemplate) + { + if (jo["fld"].Value() == FieldKey) + { + return jo["required"].Value(); + } + } + return false; + } + + }//eoc +}//ens diff --git a/server/biz/CustomerBiz.cs b/server/biz/CustomerBiz.cs new file mode 100644 index 0000000..0c04c54 --- /dev/null +++ b/server/biz/CustomerBiz.cs @@ -0,0 +1,668 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Linq; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Sockeye.Biz +{ + internal class CustomerBiz : BizObject, IJobObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, IImportAbleObject, INotifiableObject + { + internal CustomerBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.Customer; + } + + internal static CustomerBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new CustomerBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new CustomerBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Customer.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(Customer newObject) + { + await ValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.Customer.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + await SearchIndexAsync(newObject, true); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + await HandlePotentialNotificationEvent(SockEvent.Created, newObject); + return newObject; + } + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.Customer.AsNoTracking().SingleOrDefaultAsync(z => z.Id == id); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, SockEvent.Retrieved), ct); + return ret; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(Customer putObject) + { + var dbObject = await GetAsync(putObject.Id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + + putObject.Tags = TagBiz.NormalizeTags(putObject.Tags); + putObject.CustomFields = JsonUtil.CompactJson(putObject.CustomFields); + await ValidateAsync(putObject, dbObject); + if (HasErrors) return null; + ct.Replace(dbObject, putObject); + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, putObject.Id, BizType, SockEvent.Modified), ct); + await SearchIndexAsync(putObject, false); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags); + await HandlePotentialNotificationEvent(SockEvent.Modified, putObject, dbObject); + return putObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + + Customer dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return false; + } + await ValidateCanDeleteAsync(dbObject); + if (HasErrors) + return false; + + + //DELETE DIRECT CHILD / RELATED OBJECTS + //(note: the convention is to allow deletion of children created *in* the same UI area so this will delete contacts, customer notes, but not workorders of this customer for example) + //Also these are done through their biz objects as there are notification, search and other concerns to be handled + { + var IDList = await ct.User.AsNoTracking().Where(z => z.CustomerId == id).Select(z => z.Id).ToListAsync(); + if (IDList.Count() > 0) + { + UserBiz b = new UserBiz(ct, UserId, UserTranslationId, CurrentUserRoles); + foreach (long ItemId in IDList) + if (!await b.DeleteAsync(ItemId, transaction)) + { + AddError(ApiErrorCode.CHILD_OBJECT_ERROR, null, $"CustomerContact [{ItemId}]: {b.GetErrorsAsString()}"); + return false; + } + } + } + + { + var IDList = await ct.CustomerNote.AsNoTracking().Where(z => z.CustomerId == id).Select(z => z.Id).ToListAsync(); + if (IDList.Count() > 0) + { + CustomerNoteBiz b = new CustomerNoteBiz(ct, UserId, UserTranslationId, CurrentUserRoles); + foreach (long ItemId in IDList) + if (!await b.DeleteAsync(ItemId, transaction)) + { + AddError(ApiErrorCode.CHILD_OBJECT_ERROR, null, $"CustomerNote [{ItemId}]: {b.GetErrorsAsString()}"); + return false; + } + } + } + + { + var IDList = await ct.Review.AsNoTracking().Where(x => x.SockType == SockType.Customer && x.ObjectId == id).Select(x => x.Id).ToListAsync(); + if (IDList.Count() > 0) + { + ReviewBiz b = new ReviewBiz(ct, UserId, UserTranslationId, CurrentUserRoles); + foreach (long ItemId in IDList) + if (!await b.DeleteAsync(ItemId, transaction)) + { + AddError(ApiErrorCode.CHILD_OBJECT_ERROR, null, $"Review [{ItemId}]: {b.GetErrorsAsString()}"); + return false; + } + } + } + + ct.Customer.Remove(dbObject); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct); + await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); + await transaction.CommitAsync(); + await HandlePotentialNotificationEvent(SockEvent.Deleted, dbObject); + + return true; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET LIST FOR QBI MAPPING + // + internal async Task> GetNameIdActiveItemsAsync() + { + return await ct.Customer.AsNoTracking().OrderBy(x => x.Name).Select(x => new NameIdActiveItem { Name = x.Name, Id = x.Id, Active = x.Active }).ToListAsync(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // + private async Task SearchIndexAsync(Customer obj, bool isNew) + { + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + DigestSearchText(obj, SearchParams); + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id, SockType specificType) + { + var obj = await GetAsync(id, false); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + DigestSearchText(obj, SearchParams); + return SearchParams; + } + + public void DigestSearchText(Customer obj, Search.SearchIndexProcessObjectParameters searchParams) + { + if (obj != null) + searchParams.AddText(obj.Notes) + .AddText(obj.Name) + .AddText(obj.Wiki) + .AddText(obj.Tags) + .AddText(obj.WebAddress) + .AddText(obj.AlertNotes) + .AddText(obj.TechNotes) + .AddText(obj.AccountNumber) + .AddText(obj.Phone1) + .AddText(obj.Phone2) + .AddText(obj.Phone3) + .AddText(obj.Phone4) + .AddText(obj.Phone5) + .AddText(obj.EmailAddress) + .AddText(obj.PostAddress) + .AddText(obj.PostCity) + .AddText(obj.PostRegion) + .AddText(obj.PostCountry) + .AddText(obj.PostCode) + .AddText(obj.Address) + .AddText(obj.City) + .AddText(obj.Region) + .AddText(obj.Country) + .AddText(obj.AddressPostal) + .AddCustomFields(obj.CustomFields); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + private async Task ValidateAsync(Customer proposedObj, Customer currentObj) + { + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.Customer.AnyAsync(z => z.Name == proposedObj.Name && z.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + + if (proposedObj.BillHeadOffice && (proposedObj.HeadOfficeId == null || proposedObj.HeadOfficeId == 0)) + { + AddError(ApiErrorCode.VALIDATION_REQUIRED, "HeadOfficeId"); + } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == SockType.Customer.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + private async Task ValidateCanDeleteAsync(Customer inObj) + { + //## NOTE: contact isn't so important, this could be changed to check only more important things like workorders etc + //and just attempt to delete all the contacts if possible, but for now.... + + //The plan is anything that is "in" the same form is automatically deleted (unless really critical) + //However, anything that you select on "another" form is not automatically deleted and is allowed to trigger a ref integrity check + + //So, for a customer, the Customer Notes collection is accessed from within the Customer form, even though it's actually external but user doesn't see it that way + //so they would be deleted automatically, same goes for Contacts (if they can be deleted and don't have connections elsewhere) + + //Workorders and things that you select a Customer for would trigger an error and NOT be deleted automatically + //The Mass delete Extension will be used in those cases to clear out all the workorders etc + + + + //FOREIGN KEY CHECKS + if (await ct.User.AnyAsync(m => m.CustomerId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("User")); + + + // await Task.CompletedTask; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //REPORTING + // + public async Task GetReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + var idList = dataListSelectedRequest.SelectedRowIds; + JArray ReportData = new JArray(); + while (idList.Any()) + { + var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE); + idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray(); + + //query for this batch, comes back in db natural order unfortunately + var batchResults = await ct.Customer.AsNoTracking().Where(z => batch.Contains(z.Id)).ToArrayAsync(); + + //order the results back into original + //What is happening here: + //for performance the query is batching a bunch at once by fetching a block of items from the sql server + //however it's returning in db order which is often not the order the id list is in + //so it needs to be sorted back into the same order as the ide list + //This would not be necessary if just fetching each one at a time individually (like in workorder get report data) + + var orderedList = from id in batch join z in batchResults on id equals z.Id select z; + batchResults = null; + + foreach (Customer w in orderedList) + { + if (!ReportRenderManager.KeepGoing(jobId)) return null; + await PopulateVizFields(w); + var jo = JObject.FromObject(w); + if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"])) + jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]); + ReportData.Add(jo); + } + orderedList = null; + } + vc.Clear(); + return ReportData; + } + private VizCache vc = new VizCache(); + + + //populate viz fields from provided object + private async Task PopulateVizFields(Customer o) + { + + if (o.HeadOfficeId != null) + { + if (!vc.Has("headoffice", o.HeadOfficeId)) + { + vc.Add(await ct.HeadOffice.AsNoTracking().Where(x => x.Id == o.HeadOfficeId).Select(x => x.Name).FirstOrDefaultAsync(), "headoffice", o.HeadOfficeId); + } + o.HeadOfficeViz = vc.Get("headoffice", o.HeadOfficeId); + } + + + + + + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // IMPORT EXPORT + // + + public async Task GetExportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + return await GetReportData(dataListSelectedRequest, jobId); + } + + public async Task> ImportData(AyImportData importData) + { + List ImportResult = new List(); + string ImportTag = ImportUtil.GetImportTag(); + + //ignore these fields + var jsset = JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = new Sockeye.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "CustomFields" }) }); + + foreach (JObject j in importData.Data) + { + try + { + + + + long? ImportHeadOfficeId = -1; + if (j["HeadOfficeViz"] != null) + { + ImportHeadOfficeId = null; + if (!JsonUtil.JTokenIsNullOrEmpty(j["HeadOfficeViz"])) + { + ImportHeadOfficeId = await ct.HeadOffice.AsNoTracking().Where(z => z.Name == (string)j["HeadOfficeViz"]).Select(x => x.Id).FirstOrDefaultAsync(); + if (ImportHeadOfficeId == 0) + AddError(ApiErrorCode.NOT_FOUND, "HeadOfficeViz", $"'{(string)j["HeadOfficeViz"]}'"); + } + } + + long existingId = await ct.Customer.AsNoTracking().Where(z => z.Name == (string)j["Name"]).Select(x => x.Id).FirstOrDefaultAsync(); + + if (existingId == 0) + { + if (importData.DoImport) + { + //import this record + var Target = j.ToObject(jsset); + Target.Tags.Add(ImportTag); + + + //Set linked objects + if (Target.BillHeadOffice) + { + if (ImportHeadOfficeId != -1) + Target.HeadOfficeId = ImportHeadOfficeId; + else + AddError(ApiErrorCode.VALIDATION_REQUIRED, "HeadOfficeViz", "(BillHeadOffice=true)"); + } + + + var res = await CreateAsync(Target); + if (res == null) + { + ImportResult.Add($"❌ {Target.Name}\r\n{this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"✔️ {Target.Name}"); + } + } + } + else + { + if (importData.DoUpdate) + { + //update this record with any data provided + //load existing record + var Target = await GetAsync((long)existingId); + var Source = j.ToObject(jsset); + var propertiesToUpdate = j.Properties().Select(p => p.Name).ToList(); + propertiesToUpdate.Remove("Name"); + ImportUtil.Update(Source, Target, propertiesToUpdate); + + + if (Target.BillHeadOffice) + { + if (ImportHeadOfficeId != -1) + Target.HeadOfficeId = ImportHeadOfficeId; + else + AddError(ApiErrorCode.VALIDATION_REQUIRED, "HeadOfficeViz", "(BillHeadOffice=true)"); + } + + var res = await PutAsync(Target); + + if (res == null) + { + ImportResult.Add($"❌ {Target.Name} - {this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"✔️ {Target.Name}"); + } + } + } + } + catch (Exception ex) + { + ImportResult.Add($"❌ Exception processing import\n record:{j.ToString()}\nError:{ex.Message}\nSource:{ex.Source}\nStack:{ex.StackTrace.ToString()}"); + } + } + return ImportResult; + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + public async Task HandleJobAsync(OpsJob job) + { + //Hand off the particular job to the corresponding processing code + //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so + //basically any error condition during job processing should throw up an exception if it can't be handled + switch (job.JobType) + { + case JobType.BatchCoreObjectOperation: + await ProcessBatchJobAsync(job); + break; + default: + throw new System.ArgumentOutOfRangeException($"CustomerBiz.HandleJob-> Invalid job type{job.JobType.ToString()}"); + } + } + + + + private async Task ProcessBatchJobAsync(OpsJob job) + { + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); + await JobsBiz.LogJobAsync(job.GId, $"LT:StartJob {job.SubType}"); + List idList = new List(); + long FailedObjectCount = 0; + JObject jobData = JObject.Parse(job.JobInfo); + if (jobData.ContainsKey("idList")) + idList = ((JArray)jobData["idList"]).ToObject>(); + else + idList = await ct.Customer.AsNoTracking().Select(z => z.Id).ToListAsync(); + bool SaveIt = false; + + //--------------------------------- + //case 4192 + TimeSpan ProgressAndCancelCheckSpan = new TimeSpan(0, 0, ServerBootConfig.JOB_PROGRESS_UPDATE_AND_CANCEL_CHECK_SECONDS); + DateTime LastProgressCheck = DateTime.UtcNow.Subtract(new TimeSpan(1, 1, 1, 1, 1)); + var TotalRecords = idList.LongCount(); + long CurrentRecord = -1; + //--------------------------------- + + foreach (long id in idList) + { + try + { + //-------------------------------- + //case 4192 + //Update progress / cancel requested? + CurrentRecord++; + if (DateUtil.IsAfterDuration(LastProgressCheck, ProgressAndCancelCheckSpan)) + { + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{CurrentRecord}/{TotalRecords}"); + if (await JobsBiz.GetJobStatusAsync(job.GId) == JobStatus.CancelRequested) + break; + LastProgressCheck = DateTime.UtcNow; + } + //--------------------------------- + + SaveIt = false; + ClearErrors(); + Customer o = null; + //save a fetch if it's a delete + if (job.SubType != JobSubType.Delete) + o = await GetAsync(id, false); + switch (job.SubType) + { + case JobSubType.TagAddAny: + case JobSubType.TagAdd: + case JobSubType.TagRemoveAny: + case JobSubType.TagRemove: + case JobSubType.TagReplaceAny: + case JobSubType.TagReplace: + SaveIt = TagBiz.ProcessBatchTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); + break; + case JobSubType.Delete: + if (!await DeleteAsync(id)) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + break; + default: + throw new System.ArgumentOutOfRangeException($"ProcessBatchJobAsync -> Invalid job Subtype{job.SubType}"); + } + if (SaveIt) + { + o = await PutAsync(o); + if (o == null) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + } + + //delay so we're not tying up all the resources in a tight loop + await Task.Delay(Sockeye.Util.ServerBootConfig.JOB_OBJECT_HANDLE_BATCH_JOB_LOOP_DELAY); + } + catch (Exception ex) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors id({id})"); + await JobsBiz.LogJobAsync(job.GId, ExceptionUtil.ExtractAllExceptionMessages(ex)); + } + } + + //--------------------------------- + //case 4192 + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{++CurrentRecord}/{TotalRecords}"); + //--------------------------------- + + await JobsBiz.LogJobAsync(job.GId, $"LT:BatchJob {job.SubType} {idList.Count}{(FailedObjectCount > 0 ? " - LT:Failed " + FailedObjectCount : "")}"); + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed); + } + + + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // NOTIFICATION PROCESSING + // + public async Task HandlePotentialNotificationEvent(SockEvent ayaEvent, ICoreBizObjectModel proposedObj, ICoreBizObjectModel currentObj = null) + { + ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger(); + + log.LogDebug($"HandlePotentialNotificationEvent processing: [SockType:{this.BizType}, AyaEvent:{ayaEvent}]"); + + bool isNew = currentObj == null; + + + //STANDARD EVENTS FOR ALL OBJECTS + await NotifyEventHelper.ProcessStandardObjectEvents(ayaEvent, proposedObj, ct); + + //SPECIFIC EVENTS FOR THIS OBJECT + Customer o = (Customer)proposedObj; + + //## DELETED EVENTS + //any event added below needs to be removed, so + //just blanket remove any event for this object of eventtype that would be added below here + //do it regardless any time there's an update and then + //let this code below handle the refreshing addition that could have changes + //await NotifyEventHelper.ClearPriorEventsForObject(ct, SockType.Customer, o.Id, NotifyEventType.ContractExpiring); + + + //## CREATED / MODIFIED EVENTS + if (ayaEvent == SockEvent.Created || ayaEvent == SockEvent.Modified) + { + + + + } + + }//end of process notifications + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/CustomerNoteBiz.cs b/server/biz/CustomerNoteBiz.cs new file mode 100644 index 0000000..82dcec6 --- /dev/null +++ b/server/biz/CustomerNoteBiz.cs @@ -0,0 +1,303 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; + +namespace Sockeye.Biz +{ + internal class CustomerNoteBiz : BizObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, INotifiableObject + { + internal CustomerNoteBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.CustomerNote; + } + + internal static CustomerNoteBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new CustomerNoteBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new CustomerNoteBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.CustomerNote.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(CustomerNote newObject) + { + //await ValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); + await ct.CustomerNote.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + await SearchIndexAsync(newObject, true); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + + return newObject; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.CustomerNote.AsNoTracking().SingleOrDefaultAsync(m => m.Id == id); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, SockEvent.Retrieved), ct); + return ret; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(CustomerNote putObject) + { + CustomerNote dbObject = await GetAsync(putObject.Id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + + putObject.Tags = TagBiz.NormalizeTags(putObject.Tags); + //no validate required + if (HasErrors) return null; + ct.Replace(dbObject, putObject); + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + await SearchIndexAsync(putObject, false); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags); + return putObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long id, Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction parentTransaction = null) + { + //this may be part of a larger delete operation involving other objects (e.g. Customer delete and remove contacts) + //if so then there will be a parent transaction otherwise we make our own + Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction transaction = null; + if (parentTransaction == null) + transaction = await ct.Database.BeginTransactionAsync(); + + CustomerNote dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return false; + } + if (HasErrors) + return false; + if (HasErrors) + return false; + ct.CustomerNote.Remove(dbObject); + await ct.SaveChangesAsync(); + + + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, "CustomerNote", ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct); + await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); + //all good do the commit if it's ours + if (parentTransaction == null) + await transaction.CommitAsync(); + + + return true; + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // + private async Task SearchIndexAsync(CustomerNote obj, bool isNew) + { + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + DigestSearchText(obj, SearchParams); + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id, SockType specificType) + { + var obj = await GetAsync(id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + DigestSearchText(obj, SearchParams); + return SearchParams; + } + + public void DigestSearchText(CustomerNote obj, Search.SearchIndexProcessObjectParameters searchParams) + { + if (obj != null) + searchParams.AddText(obj.Notes) + .AddText(obj.Tags); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //REPORTING + // + public async Task GetReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + var idList = dataListSelectedRequest.SelectedRowIds; + JArray ReportData = new JArray(); + while (idList.Any()) + { + var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE); + idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray(); + //query for this batch, comes back in db natural order unfortunately + var batchResults = await ct.CustomerNote.AsNoTracking().Where(z => batch.Contains(z.Id)).ToArrayAsync(); + //order the results back into original + var orderedList = from id in batch join z in batchResults on id equals z.Id select z; + batchResults = null; + foreach (CustomerNote w in orderedList) + { + if (!ReportRenderManager.KeepGoing(jobId)) return null; + await PopulateVizFields(w); + var jo = JObject.FromObject(w); + ReportData.Add(jo); + } + orderedList = null; + } + vc.Clear(); + return ReportData; + } + + //populate viz fields from provided object + private async Task PopulateVizFields(CustomerNote o) + { + if (!vc.Has("customer", o.CustomerId)) + vc.Add(await ct.Customer.AsNoTracking().Where(x => x.Id == o.CustomerId).Select(x => x.Name).FirstOrDefaultAsync(), "customer", o.CustomerId); + o.CustomerViz = vc.Get("customer", o.CustomerId); + + if (!vc.Has("user", o.UserId)) + vc.Add(await ct.User.AsNoTracking().Where(x => x.Id == o.UserId).Select(x => x.Name).FirstOrDefaultAsync(), "user", o.UserId); + o.UserViz = vc.Get("user", o.UserId); + + } + private VizCache vc = new VizCache(); + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // IMPORT EXPORT + // + public async Task GetExportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + //for now just re-use the report data code + //this may turn out to be the pattern for most biz object types but keeping it seperate allows for custom usage from time to time + return await GetReportData(dataListSelectedRequest, jobId); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + // private async Task ValidateAsync(CustomerNote proposedObj, CustomerNote currentObj) + // { + + // // bool isNew = currentObj == null; + + + + + // // //Any form customizations to validate? + // // var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == SockType.CustomerNote.ToString()); + // // if (FormCustomization != null) + // // { + // // //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + // // //validate users choices for required non custom fields + // // RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + // // //validate custom fields + // // CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + // // } + + // } + + // private void ValidateCanDelete(CustomerNote inObj) + // { + // //whatever needs to be check to delete this object + // } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // NOTIFICATION PROCESSING + // + public async Task HandlePotentialNotificationEvent(SockEvent ayaEvent, ICoreBizObjectModel proposedObj, ICoreBizObjectModel currentObj = null) + { + + ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger(); + log.LogDebug($"HandlePotentialNotificationEvent processing: [SockType:{this.BizType}, AyaEvent:{ayaEvent}]"); + + bool isNew = currentObj == null; + var oProposed = (CustomerNote)proposedObj; + oProposed.CustomerViz = await ct.Customer.AsNoTracking().Where(x => x.Id == oProposed.CustomerId).Select(x => x.Name).FirstOrDefaultAsync(); + proposedObj.Name = oProposed.CustomerViz; + + //STANDARD EVENTS FOR ALL OBJECTS + await NotifyEventHelper.ProcessStandardObjectEvents(ayaEvent, proposedObj, ct); + + //SPECIFIC EVENTS FOR THIS OBJECT + + }//end of process notifications + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/CustomerNotifySubscriptionBiz.cs b/server/biz/CustomerNotifySubscriptionBiz.cs new file mode 100644 index 0000000..1a4064d --- /dev/null +++ b/server/biz/CustomerNotifySubscriptionBiz.cs @@ -0,0 +1,187 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using System.Linq; + +namespace Sockeye.Biz +{ + internal class CustomerNotifySubscriptionBiz : BizObject//, IJobObject, ISearchAbleObject + { + internal CustomerNotifySubscriptionBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.CustomerNotifySubscription; + } + + internal static CustomerNotifySubscriptionBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new CustomerNotifySubscriptionBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new CustomerNotifySubscriptionBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.CustomerNotifySubscription.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(CustomerNotifySubscription newObject) + { + ValidateAsync(newObject); + if (HasErrors) + return null; + else + { + newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); + newObject.CustomerTags = TagBiz.NormalizeTags(newObject.CustomerTags); + + await ct.CustomerNotifySubscription.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.CustomerTags, null); + return newObject; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.CustomerNotifySubscription.AsNoTracking().SingleOrDefaultAsync(z => z.Id == id); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, SockEvent.Retrieved), ct); + return ret; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(CustomerNotifySubscription putObject) + { + //TODO: Must remove all prior events and replace them + + var dbObject = await GetAsync(putObject.Id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + putObject.CustomerTags = TagBiz.NormalizeTags(putObject.CustomerTags); + putObject.Tags = TagBiz.NormalizeTags(putObject.Tags); + ValidateAsync(putObject); + if (HasErrors) return null; + ct.Replace(dbObject, putObject); + if (HasErrors) return null; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.CustomerTags, dbObject.CustomerTags); + return putObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long id) + { + + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + var dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return false; + } + //ValidateCanDelete(dbObject); + if (HasErrors) + return false; + { + var IDList = await ct.Review.AsNoTracking().Where(x => x.SockType == SockType.CustomerNotifySubscription && x.ObjectId == id).Select(x => x.Id).ToListAsync(); + if (IDList.Count() > 0) + { + ReviewBiz b = new ReviewBiz(ct, UserId, UserTranslationId, CurrentUserRoles); + foreach (long ItemId in IDList) + if (!await b.DeleteAsync(ItemId, transaction)) + { + AddError(ApiErrorCode.CHILD_OBJECT_ERROR, null, $"Review [{ItemId}]: {b.GetErrorsAsString()}"); + return false; + } + } + } + ct.CustomerNotifySubscription.Remove(dbObject); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.EventType.ToString(), ct); + await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.CustomerTags); + + + await transaction.CommitAsync(); + + return true; + } + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + private void ValidateAsync(CustomerNotifySubscription proposedObj) + { + if (string.IsNullOrWhiteSpace(proposedObj.CurrencyName)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "CurrencyName"); + + if (string.IsNullOrWhiteSpace(proposedObj.LanguageOverride)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "LanguageOverride"); + + if (string.IsNullOrWhiteSpace(proposedObj.TimeZoneOverride)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "TimeZoneOverride"); + + if (proposedObj.TranslationId == 0) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "TranslationId"); + + + + } + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/DashboardViewBiz.cs b/server/biz/DashboardViewBiz.cs new file mode 100644 index 0000000..313df8f --- /dev/null +++ b/server/biz/DashboardViewBiz.cs @@ -0,0 +1,122 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json.Linq; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; + + + +namespace Sockeye.Biz +{ + + //Only one dashboard view per user and there is always one so no delete method, not create method + internal class DashboardViewBiz : BizObject + { + + internal DashboardViewBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.DashboardView; + } + + internal static DashboardViewBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new DashboardViewBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new DashboardViewBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync() + { + return await ct.DashboardView.AnyAsync(z => z.UserId == UserId); + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + + //Get one - will create if there isn't one to get + internal async Task GetAsync() + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.DashboardView.SingleOrDefaultAsync(z => z.UserId == UserId); + if (ret == null) + { + ret = new DashboardView(); + ret.UserId = UserId; + ct.DashboardView.Add(ret); + await ct.SaveChangesAsync(); + } + return ret; + } + + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(DashboardView dbObject, string theView) + { + //todo: check this, is it correct to not be using the standard PUT methodology + dbObject.View = theView; + Validate(dbObject, false); + if (HasErrors) + return false; + await ct.SaveChangesAsync(); + return true; + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private void Validate(DashboardView inObj, bool isNew) + { + //Filter json must parse + //this is all automated normally so not going to do too much parsing here + //just ensure it's basically there + if (!string.IsNullOrWhiteSpace(inObj.View)) + { + try + { + var v = JArray.Parse(inObj.View); + + } + catch (Newtonsoft.Json.JsonReaderException ex) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "DashboardView", "DashboardView is not valid JSON string: " + ex.Message); + + } + } + + return; + } + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/DataListColumnViewBiz.cs b/server/biz/DataListColumnViewBiz.cs new file mode 100644 index 0000000..bd239ff --- /dev/null +++ b/server/biz/DataListColumnViewBiz.cs @@ -0,0 +1,196 @@ +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using Sockeye.DataList; + + +namespace Sockeye.Biz +{ + + + internal class DataListColumnViewBiz : BizObject + { + + internal DataListColumnViewBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.DataListColumnView; + } + + internal static DataListColumnViewBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new DataListColumnViewBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new DataListColumnViewBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.DataListColumnView.AnyAsync(z => z.Id == id); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(DataListColumnView newObject) + { + ValidateAsync(newObject); + if (HasErrors) + return null; + else + { + //delete the existing one in favor of this one + var dbObject = await GetAsync(newObject.UserId, newObject.ListKey, false); + if (dbObject != null) + { + ct.DataListColumnView.Remove(dbObject); + await ct.SaveChangesAsync(); + } + + await ct.DataListColumnView.AddAsync(newObject); + await ct.SaveChangesAsync(); + + return newObject; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SET SORT + // + + internal async Task SetSort(Sockeye.Models.DataListSortRequest newObject) + { + //Get the current column view (in an updateable manner) and update it's sort property and save + var dbObject = await ct.DataListColumnView.SingleOrDefaultAsync(z => z.UserId == UserId && z.ListKey == newObject.ListKey); + if (dbObject == null) + dbObject = await CreateDefaultColumnView(newObject.ListKey); + //convert arrays to proper format for saving + Dictionary newSort = new Dictionary(); + for (int x = 0; x < newObject.sortBy.Length; x++) + newSort.Add(newObject.sortBy[x], (newObject.sortDesc[x] ? "-" : "+")); + dbObject.Sort = JsonConvert.SerializeObject(newSort); + //columnview is always replace + await ct.SaveChangesAsync(); + return true; + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + // + internal async Task GetAsync(long userId, string listKey, bool createDefaultIfNecessary) + { + var ret = await ct.DataListColumnView.AsNoTracking().SingleOrDefaultAsync(z => z.UserId == userId && z.ListKey == listKey); + if (ret == null && createDefaultIfNecessary) + { + return await CreateDefaultColumnView(listKey); + } + return ret; + } + + internal async Task CreateDefaultColumnView(string listKey) + { + if (!DataListFactory.ListKeyIsValid(listKey)) + { + throw new System.ArgumentOutOfRangeException($"ListKey '{listKey}' is not a valid DataListKey"); + } + var ret = new DataListColumnView(); + ret.UserId = UserId; + ret.ListKey = listKey; + var dataList = DataListFactory.GetAyaDataList(listKey, 0); + ret.Columns = JsonConvert.SerializeObject(dataList.DefaultColumns); + ret.Sort = JsonConvert.SerializeObject(dataList.DefaultSortBy); + return await CreateAsync(ret); + } + + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE (RESET) + // + internal async Task DeleteAsync(long userId, string listKey) + { + //this is effectively the RESET route handler + //so it can be called any time even if there is no default list and it's a-ok + //because a new default will be created if needed + + var dbObject = await GetAsync(userId, listKey, false); + if (dbObject != null) + { + ct.DataListColumnView.Remove(dbObject); + await ct.SaveChangesAsync(); + dbObject = await GetAsync(userId, listKey, true); + } + return dbObject; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private void ValidateAsync(DataListColumnView inObj) + { + + if (inObj.UserId != UserId) + AddError(ApiErrorCode.NOT_AUTHORIZED, "UserId", "Only own view can be modified"); + + if (string.IsNullOrWhiteSpace(inObj.ListKey)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "ListKey"); + + if (!DataListFactory.ListKeyIsValid(inObj.ListKey)) + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "ListKey", $"ListKey \"{inObj.ListKey}\" DataListKey is not valid"); + + + //Validate Sort JSON + try + { + JsonConvert.DeserializeObject>(inObj.Sort); + } + catch (System.Exception ex) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Sort", "Sort is not valid JSON string, can't convert to valid Dictionary SortBy, error: " + ex.Message); + } + + //Validate Columns JSON + try + { + + JsonConvert.DeserializeObject>(inObj.Columns); + } + catch (System.Exception ex) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Sort", "Sort is not valid JSON string, can't convert to valid Dictionary SortBy, error: " + ex.Message); + } + + return; + } + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/DataListSavedFilterBiz.cs b/server/biz/DataListSavedFilterBiz.cs new file mode 100644 index 0000000..386c1fd --- /dev/null +++ b/server/biz/DataListSavedFilterBiz.cs @@ -0,0 +1,261 @@ +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using Sockeye.DataList; + + +namespace Sockeye.Biz +{ + + + internal class DataListSavedFilterBiz : BizObject + { + + internal DataListSavedFilterBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.DataListSavedFilter; + } + + internal static DataListSavedFilterBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new DataListSavedFilterBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new DataListSavedFilterBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.DataListSavedFilter.AnyAsync(z => z.Id == id); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + internal async Task CreateAsync(DataListSavedFilter inObj) + { + Validate(inObj, true); + if (HasErrors) + return null; + else + { + //do stuff with datafilter + DataListSavedFilter outObj = inObj; + outObj.UserId = UserId; + await ct.DataListSavedFilter.AddAsync(outObj); + await ct.SaveChangesAsync(); + return outObj; + + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + + //Get one + internal async Task GetAsync(long fetchId) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var ret = await ct.DataListSavedFilter.AsNoTracking().SingleOrDefaultAsync(z => z.Id == fetchId && (z.Public == true || z.UserId == UserId)); + + return ret; + } + + + + + //GET FILTERLIST + internal async Task> GetViewListAsync(string listKey) + { + await EnsureDefaultAsync(listKey); + return await ct.DataListSavedFilter + .AsNoTracking() + .Where(z => z.ListKey == listKey && (z.Public == true || z.UserId == UserId)) + .OrderBy(z => z.Name) + .Select(z => new NameIdDefaultItem(z.Id, z.Name, z.DefaultFilter)).ToListAsync(); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE Default if it doesn't exist already + internal async Task EnsureDefaultAsync(string listKey) + { + if (!await ct.DataListSavedFilter.AnyAsync(z => z.UserId == UserId && z.ListKey == listKey && z.DefaultFilter == true)) + { + if (!DataListFactory.ListKeyIsValid(listKey)) + { + throw new System.ArgumentOutOfRangeException($"ListKey '{listKey}' is not a valid DataListKey"); + } + + //var dataList = DataListFactory.GetAyaDataList(listKey,0); + + DataListSavedFilter d = new DataListSavedFilter(); + d.ListKey = listKey; + d.Name = "-"; + d.DefaultFilter = true; + d.Public = false; + d.UserId = UserId; + d.Filter = "[]";//empty array is default becuase it would be filter:[{column:"PartName",any:true/false,items:[{op: "=",value: "400735"}]}] + await ct.DataListSavedFilter.AddAsync(d); + await ct.SaveChangesAsync(); + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(DataListSavedFilter putObject) + { + var dbObject = await GetAsync(putObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + + //preserve the owner ID if none was specified + if (putObject.UserId == 0) + putObject.UserId = dbObject.UserId; + + Validate(dbObject, false); + if (HasErrors) + return null; + + ct.Replace(dbObject, putObject); + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + + return putObject; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(DataListSavedFilter dbObject) + { + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + ct.DataListSavedFilter.Remove(dbObject); + await ct.SaveChangesAsync(); + + //was it a "reset" of default? + if (dbObject.DefaultFilter) + await EnsureDefaultAsync(dbObject.ListKey); + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + private void ValidateCanDelete(DataListSavedFilter inObj) + { + if (inObj.UserId != UserId) + { + AddError(ApiErrorCode.NOT_AUTHORIZED, "generalerror", "Can't delete another users filter"); + } + } + + + //Can save or update? + private void Validate(DataListSavedFilter inObj, bool isNew) + { + + //UserId required + if (!isNew) + { + if (inObj.UserId == 0) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "UserId"); + } + + //can't save a filter for someone else + if (inObj.UserId != UserId && inObj.Public == false) + { + AddError(ApiErrorCode.NOT_AUTHORIZED, "generalerror", "Can only save private filters for self not other UserId's"); + return; + } + + //technically not "validation" but always ensure default filter has - name + if (inObj.DefaultFilter) + { + inObj.Name = "-"; + } + + //Name required + if (string.IsNullOrWhiteSpace(inObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + if (string.IsNullOrWhiteSpace(inObj.Filter)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Filter"); + + if (string.IsNullOrWhiteSpace(inObj.ListKey)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "ListKey"); + + + if (!DataListFactory.ListKeyIsValid(inObj.ListKey)) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "ListKey", $"ListKey \"{inObj.ListKey}\" DataListKey is not valid"); + } + + if (inObj.ListKey.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "ListKey", "255 max"); + + //check if filter can be reconstructed into a C# filter object + try + { + JsonConvert.DeserializeObject>(inObj.Filter); + } + catch (System.Exception ex) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Filter", "Filter is not valid JSON string, can't convert to List, error: " + ex.Message); + } + + return; + } + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/EventLogProcessor.cs b/server/biz/EventLogProcessor.cs new file mode 100644 index 0000000..57aa280 --- /dev/null +++ b/server/biz/EventLogProcessor.cs @@ -0,0 +1,157 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sockeye.Models; +using System; + + +namespace Sockeye.Biz +{ + internal static class EventLogProcessor + { + private const int DEFAULT_EVENT_LIMIT = 20; + + /// + /// Add an entry to the log + /// + /// + /// + /// + /// + internal static async Task LogEventToDatabaseAsync(Event newEvent, AyContext ct) + { + await ct.Event.AddAsync(newEvent); + await ct.SaveChangesAsync(); + } + + + + /// + /// Handle delete + /// remove all prior entries for object, add one deleted entry + /// + /// + /// + /// + /// + /// + internal static async Task DeleteObjectLogAsync(long userId, SockType sockType, long sockId, string textra, AyContext ct) + { + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from aevent where socktype = {sockType} and sockid={sockId}"); + await ct.Event.AddAsync(new Event(userId, sockId, sockType, SockEvent.Deleted, $"{textra} (id {sockId})")); + await ct.SaveChangesAsync(); + } + + /// + /// Get the event log for a specified object + /// Presentation is the client's responsibility (localization internationalization etc) + /// + internal static async Task GetLogForObjectAsync(Sockeye.Api.Controllers.EventLogController.EventLogOptions opt, long translationId, AyContext ct) + { + Sockeye.Api.Controllers.EventLogController.ObjectEventLog ret = new Api.Controllers.EventLogController.ObjectEventLog(); + + var limit = opt.Limit ?? DEFAULT_EVENT_LIMIT; + var offset = opt.Offset ?? 0; + + //Set up the query + var q = ct.Event.Select(z => z).AsNoTracking(); + q = q.Where(z => z.SockId == opt.AyId && z.SockType == opt.SockType); + q = q.OrderByDescending(z => z.Created); + q = q.Skip(offset).Take(limit); + + //Execute the query + var EventItems = await q.ToArrayAsync(); + + //convert the Event array to the correct return type array + using (var command = ct.Database.GetDbConnection().CreateCommand()) + { + ct.Database.OpenConnection(); + ret.Events = EventItems.Select(z => new Sockeye.Api.Controllers.EventLogController.ObjectEventLogItem() + { + Date = z.Created, + UserId = z.UserId, + Event = z.SockEvent, + Textra = z.Textra, + Name = BizObjectNameFetcherDirect.Name(SockType.User, z.UserId, translationId, command) + }).ToArray(); + + ret.Name = BizObjectNameFetcherDirect.Name(opt.SockType, opt.AyId, translationId, command); + return ret; + } + } + + + + /// + /// Get the event log for a specified User + /// Presentation is the client's responsibility (localization internationalization etc) + /// + internal static async Task GetLogForUserAsync(Sockeye.Api.Controllers.EventLogController.UserEventLogOptions opt, long translationId, AyContext ct) + { + + Sockeye.Api.Controllers.EventLogController.UserEventLog ret = new Api.Controllers.EventLogController.UserEventLog(); + var limit = opt.Limit ?? DEFAULT_EVENT_LIMIT; + var offset = opt.Offset ?? 0; + //Set up the query + + + var q = ct.Event.Select(z => z).AsNoTracking(); + q = q.Where(z => z.UserId == opt.UserId); + q = q.OrderByDescending(z => z.Created); + q = q.Skip(offset).Take(limit); + + + //Execute the query + var EventItems = await q.ToArrayAsync(); + using (var command = ct.Database.GetDbConnection().CreateCommand()) + { + ct.Database.OpenConnection(); + //convert the Event array to the correct return type array + ret.Events = EventItems.Select(z => new Sockeye.Api.Controllers.EventLogController.UserEventLogItem() + { + + Date = z.Created, + SockType = z.SockType, + ObjectId = z.SockId, + Event = z.SockEvent, + Textra = z.Textra, + Name = BizObjectNameFetcherDirect.Name(z.SockType, z.SockId, translationId, command) + + }).ToArray(); + ret.Name = BizObjectNameFetcherDirect.Name(SockType.User, opt.UserId, translationId, command); + return ret; + } + + } + + + /// + /// V7 export handler + /// Exporter needs to fixup event log CREATED entry to show original created and last modified times + /// and users + /// + internal static async Task V7_Modify_LogAsync(Sockeye.Api.Controllers.EventLogController.V7Event ev, AyContext ct) + { + //delete the automatically created entry from the exported object + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from aevent where socktype = {ev.SockType} and ayid={ev.AyId}"); + + //Now create the entries to reflect the original data from v7 + //CREATED + await EventLogProcessor.LogEventToDatabaseAsync(new Event(ev.Creator, ev.AyId, ev.SockType, SockEvent.Created, ev.Created, null), ct); + + //MODIFIED + await EventLogProcessor.LogEventToDatabaseAsync(new Event(ev.Modifier, ev.AyId, ev.SockType, SockEvent.Modified, ev.Modified, null), ct); + + + await ct.SaveChangesAsync(); + } + + + + ///////////////////////////////////////////////////////////////////// + + + }//eoc + +}//eons + diff --git a/server/biz/FormCustomBiz.cs b/server/biz/FormCustomBiz.cs new file mode 100644 index 0000000..c9ee77c --- /dev/null +++ b/server/biz/FormCustomBiz.cs @@ -0,0 +1,334 @@ +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json.Linq; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; + + +namespace Sockeye.Biz +{ + //## NOTE this is a *GLOBAL* form custom that applies to all users as configured by someone with rights to do so + //this is *not* a personal customization system + + internal class FormCustomBiz : BizObject + { + + + internal FormCustomBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.FormCustom; + } + + internal static FormCustomBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new FormCustomBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new FormCustomBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(string formKey) + { + return await ct.FormCustom.AnyAsync(z => z.FormKey == formKey); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + internal async Task CreateAsync(FormCustom inObj) + { + await ValidateAsync(inObj, true); + if (HasErrors) + return null; + else + { + // + FormCustom outObj = inObj; + + outObj.Template = JsonUtil.CompactJson(outObj.Template); + + await ct.FormCustom.AddAsync(outObj); + await ct.SaveChangesAsync(); + + //Handle child and associated items: + + //EVENT LOG + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, SockEvent.Created), ct); + + + return outObj; + + } + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + + //Get one + internal async Task GetAsync(string formKey) + { + //Step 1: check if exists, if it does then just return it + if (await ExistsAsync(formKey)) + { + return await ct.FormCustom.SingleOrDefaultAsync(z => z.FormKey == formKey); + } + + //If it doesn't exist, vet the form key name is ok by checking with this list + if (!FormFieldOptionalCustomizableReference.FormFieldKeys.Contains(formKey)) + { + //Nope, whatever it is, it's not valid + return null; + } + + // Name is valid, make a new formcustom for the name specified, save to db and then return it + //NOTE: This assumes that the client will make it a legal form custom and so it doesn't include any pre-defined stock fields that are required etc + //this just makes an empty template suitable for the client to work with + var fc = new FormCustom() + { + FormKey = formKey, + Template = @"[]" + }; + + //Create and save to db + return await CreateAsync(fc); + + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(FormCustom putObject) + { + + + var dbObject = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == putObject.FormKey); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "formKey"); + return null; + } + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + dbObject.Template = JsonUtil.CompactJson(putObject.Template); + putObject.Id=dbObject.Id;//weird workaround needed because ID is not sent with the putobject for...reasons 🤷? + await ValidateAsync(putObject, false); + if (HasErrors) + return null; + ct.Replace(dbObject, putObject); + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.FormKey)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + //Log modification and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + return putObject; + // //todo: replace with new put methodology + + // //Replace the db object with the PUT object + // CopyObject.Copy(putObject, dbObject, "Id"); + // //Set "original" value of concurrency token to input token + // //this will allow EF to check it out + // ct.Entry(dbObject).OriginalValues["Concurrency"] = putObject.Concurrency; + + // await ValidateAsync(dbObject, false); + // if (HasErrors) + // return false; + + // dbObject.Template = JsonUtil.CompactJson(dbObject.Template); + // await ct.SaveChangesAsync(); + + // //Log modification and save context + // await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct); + + + // return true; + } + + + //NO DELETE, ONLY EDIT + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(FormCustom inObj, bool isNew) + { + + //FormKey required and must be valid + if (string.IsNullOrWhiteSpace(inObj.FormKey)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "FormKey"); + else + { + if (!FormFieldOptionalCustomizableReference.IsValidFormFieldKey(inObj.FormKey)) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "FormKey"); + } + } + + //FormKey must be less than 255 characters + if (inObj.FormKey.Length > 255) + AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "FormKey", "255 max"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("FormKey") && isNew) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.FormCustom.AnyAsync(z => z.FormKey == inObj.FormKey && z.Id != inObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "FormKey"); + } + } + + + + + //Template json must parse + if ((!PropertyHasErrors("FormKey") && !string.IsNullOrWhiteSpace(inObj.Template))) + { + var ValidCustomFieldTypes = CustomFieldType.ValidCustomFieldTypes; + var ValidFormFields = FormFieldOptionalCustomizableReference.FormFieldReferenceList(inObj.FormKey); + try + { + //Parse the json, expecting something like this: + //[{fld:"ltkeyfieldname",hide:"true/false",required:"true/false", type:"bool"},{fld:"ltkeyfieldname",hide:"true/false",required:"true/false", type:"text"] + //Array at root is valid json and saves a bit of bandwidth so minimal is best + + //Only Custom fields that are to be displayed need to be in this fragment. + //fields that are not chosen to display don't need to be in the fragment to be valid + + var v = JArray.Parse(inObj.Template); + + for (int i = 0; i < v.Count; i++) + { + FormField MasterFormField = null; + + var formFieldItem = v[i]; + if (formFieldItem["fld"] == null) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Template", $"Template array item {i}, object is missing required \"fld\" property "); + else + { + var fldKey = formFieldItem["fld"].Value(); + if (string.IsNullOrWhiteSpace(fldKey)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Template", $"Template array item {i}, \"fld\" property exists but is empty, a value is required"); + + //validate the field name if we can + if (ValidFormFields != null) + { + + if (!ValidFormFields.Exists(z => z.FieldKey == fldKey)) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", $"Template array item {i}, fld property value \"{fldKey}\" is not a valid form field value for formKey specified"); + } + else + { + MasterFormField = ValidFormFields.FirstOrDefault(z => z.FieldKey == fldKey); + } + + } + } + + + if (MasterFormField != null) + { + + //removed due to removal of hidden property in ff reference since only customizable fields are hideable by default + // if (formFieldItem["hide"] != null) + // { + // var fieldHideValue = formFieldItem["hide"].Value(); + // if (!MasterFormField.Hideable && fieldHideValue == true) + // AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", $"Template array item {i} (\"{MasterFormField.FieldKey}\"), \"hide\" property value of \"{fieldHideValue}\" is not valid, this field is core and cannot be hidden"); + // } + + //validate if it's a custom field that it has a type specified + if (MasterFormField.IsCustomField && formFieldItem["type"] == null) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", $"Template array item {i} (\"{MasterFormField.FieldKey}\"), \"type\" property value is MISSING for custom field, Custom fields MUST have types specified"); + } + + + + if (formFieldItem["type"] != null) + { + if (!MasterFormField.IsCustomField) + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", $"Template array item {i} (\"{MasterFormField.FieldKey}\"), \"type\" property value is not valid, only Custom fields can have types specified"); + else + {//It is a custom field, is it a valid type value + var templateFieldCustomTypeValue = formFieldItem["type"].Value(); + if (!ValidCustomFieldTypes.Contains(templateFieldCustomTypeValue)) + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", $"Template array item {i} (\"{MasterFormField.FieldKey}\"), \"type\" property value of \"{templateFieldCustomTypeValue}\" is not a valid custom field type"); + } + } + + } + + //other code depends on seeing the required value even if it's not set to true + if (formFieldItem["required"] == null) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Template", $"Template array item {i}, object is missing \"required\" property. All items must contain this property. "); + + + //NOTE: value of nothing, null or empty is a valid value so no checking for it here + } + } + catch (Newtonsoft.Json.JsonReaderException ex) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", "Template is not valid JSON string: " + ex.Message); + + } + } + + + + + + return; + } + + + // //Can delete? + // private void ValidateCanDelete(FormCustom inObj) + // { + // //Leaving this off for now + // } + + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/FormFieldReference.cs b/server/biz/FormFieldReference.cs new file mode 100644 index 0000000..8b34399 --- /dev/null +++ b/server/biz/FormFieldReference.cs @@ -0,0 +1,383 @@ +using System.Collections.Generic; +using System; + +namespace Sockeye.Biz +{ + ////////////////////////////////////////////////////////////////////////////////////////////////////////// + // This contains all the **OPTIONAL** fields that can be customized by user to be required or hide + // on all object edit forms + // it is used for both validation and driving the UI + // it does *NOT* need to contain every field on the form, just user customizable ones + // and should not have mandatory fields since they are not customizable by end user + + //See the DataList folder / namespace for LIST related similar class + + public static class FormFieldOptionalCustomizableReference + { + private static Dictionary> _formFields; + private static List _formFieldKeys = null; + + public static List FormFieldKeys + { + get + { + if (_formFieldKeys == null) + { + _formFieldKeys = new List(); + var values = Enum.GetValues(typeof(SockType)); + foreach (SockType t in values) + { + if (t.HasAttribute(typeof(CoreBizObjectAttribute))) + { + _formFieldKeys.Add(t.ToString()); + } + } + //No type / not corebiz form keys: + _formFieldKeys.Add("Contact"); + + } + return _formFieldKeys; + } + } + + + public static bool IsValidFormFieldKey(string key) + { + return FormFieldKeys.Contains(key); + } + + public static List FormFieldReferenceList(string key) + { + //Initialize the static list here on first retrieval + if (_formFields == null) + { + _formFields = new Dictionary>(); + /* ***************************** WARNING: Be careful here, if a standard field is hideable and also it's DB SCHEMA is set to NON NULLABLE then the CLIENT end needs to set a default + ***************************** Otherwise the hidden field can't be set and the object can't be saved EVER + */ + + #region USER_KEY + { + List l = new List(); + l.Add(new FormField { TKey = "UserEmployeeNumber", FieldKey = "EmployeeNumber" }); + l.Add(new FormField { TKey = "LastLogin", FieldKey = "LastLogin" }); + l.Add(new FormField { TKey = "UserNotes", FieldKey = "Notes" }); + l.Add(new FormField { TKey = "Tags", FieldKey = "Tags" }); + l.Add(new FormField { TKey = "Wiki", FieldKey = "Wiki" }); + l.Add(new FormField { TKey = "Attachments", FieldKey = "Attachments", Requireable = false }); + l.Add(new FormField { TKey = "UserCustom1", FieldKey = "UserCustom1", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom2", FieldKey = "UserCustom2", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom3", FieldKey = "UserCustom3", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom4", FieldKey = "UserCustom4", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom5", FieldKey = "UserCustom5", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom6", FieldKey = "UserCustom6", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom7", FieldKey = "UserCustom7", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom8", FieldKey = "UserCustom8", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom9", FieldKey = "UserCustom9", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom10", FieldKey = "UserCustom10", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom11", FieldKey = "UserCustom11", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom12", FieldKey = "UserCustom12", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom13", FieldKey = "UserCustom13", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom14", FieldKey = "UserCustom14", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom15", FieldKey = "UserCustom15", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom16", FieldKey = "UserCustom16", IsCustomField = true }); + _formFields.Add(SockType.User.ToString(), l); + } + #endregion + + #region CONTACT_KEY + { + List l = new List(); + l.Add(new FormField { TKey = "UserEmployeeNumber", FieldKey = "EmployeeNumber" }); + l.Add(new FormField { TKey = "LastLogin", FieldKey = "LastLogin" }); + l.Add(new FormField { TKey = "UserNotes", FieldKey = "Notes" }); + l.Add(new FormField { TKey = "Tags", FieldKey = "Tags" }); + l.Add(new FormField { TKey = "Wiki", FieldKey = "Wiki" }); + l.Add(new FormField { TKey = "Attachments", FieldKey = "Attachments", Requireable = false }); + l.Add(new FormField { TKey = "UserCustom1", FieldKey = "UserCustom1", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom2", FieldKey = "UserCustom2", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom3", FieldKey = "UserCustom3", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom4", FieldKey = "UserCustom4", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom5", FieldKey = "UserCustom5", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom6", FieldKey = "UserCustom6", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom7", FieldKey = "UserCustom7", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom8", FieldKey = "UserCustom8", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom9", FieldKey = "UserCustom9", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom10", FieldKey = "UserCustom10", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom11", FieldKey = "UserCustom11", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom12", FieldKey = "UserCustom12", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom13", FieldKey = "UserCustom13", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom14", FieldKey = "UserCustom14", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom15", FieldKey = "UserCustom15", IsCustomField = true }); + l.Add(new FormField { TKey = "UserCustom16", FieldKey = "UserCustom16", IsCustomField = true }); + _formFields.Add("Contact", l); + } + #endregion + + + #region Customer + { + List l = new List(); + l.Add(new FormField { TKey = "CustomerAccountNumber", FieldKey = "AccountNumber" }); + l.Add(new FormField { TKey = "WebAddress", FieldKey = "WebAddress" }); + l.Add(new FormField { TKey = "CustomerEmail", FieldKey = "EmailAddress" }); + l.Add(new FormField { TKey = "CustomerPhone1", FieldKey = "Phone1" }); + l.Add(new FormField { TKey = "CustomerPhone2", FieldKey = "Phone2" }); + l.Add(new FormField { TKey = "CustomerPhone3", FieldKey = "Phone3" }); + l.Add(new FormField { TKey = "CustomerPhone4", FieldKey = "Phone4" }); + l.Add(new FormField { TKey = "CustomerPhone5", FieldKey = "Phone5" }); + l.Add(new FormField { TKey = "CustomerBillHeadOffice", FieldKey = "BillHeadOffice" }); + l.Add(new FormField { TKey = "HeadOffice", FieldKey = "HeadOfficeId" }); + l.Add(new FormField { TKey = "Contract", FieldKey = "ContractId" }); + l.Add(new FormField { TKey = "ContractExpires", FieldKey = "ContractExpires" }); + //l.Add(new FormField { TKey = "UsesBanking", FieldKey = "UsesBanking" }); + l.Add(new FormField { TKey = "CustomerNotes", FieldKey = "Notes" }); + l.Add(new FormField { TKey = "CustomerTechNotes", FieldKey = "TechNotes" }); + l.Add(new FormField { TKey = "AlertNotes", FieldKey = "AlertNotes" }); + l.Add(new FormField { TKey = "Tags", FieldKey = "Tags" }); + l.Add(new FormField { TKey = "Wiki", FieldKey = "Wiki" }); + l.Add(new FormField { TKey = "Attachments", FieldKey = "Attachments", Requireable = false }); + l.Add(new FormField { TKey = "AddressDeliveryAddress", FieldKey = "Address" }); + l.Add(new FormField { TKey = "AddressCity", FieldKey = "City" }); + l.Add(new FormField { TKey = "AddressStateProv", FieldKey = "Region" }); + l.Add(new FormField { TKey = "AddressCountry", FieldKey = "Country" }); + l.Add(new FormField { TKey = "AddressPostal", FieldKey = "AddressPostal" }); + l.Add(new FormField { TKey = "AddressLatitude", FieldKey = "Latitude" }); + l.Add(new FormField { TKey = "AddressLongitude", FieldKey = "Longitude" }); + l.Add(new FormField { TKey = "AddressPostalDeliveryAddress", FieldKey = "PostAddress" }); + l.Add(new FormField { TKey = "AddressPostalCity", FieldKey = "PostCity" }); + l.Add(new FormField { TKey = "AddressPostalStateProv", FieldKey = "PostRegion" }); + l.Add(new FormField { TKey = "AddressPostalCountry", FieldKey = "PostCountry" }); + l.Add(new FormField { TKey = "AddressPostalPostal", FieldKey = "PostCode" }); + + l.Add(new FormField { TKey = "CustomerCustom1", FieldKey = "CustomerCustom1", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom2", FieldKey = "CustomerCustom2", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom3", FieldKey = "CustomerCustom3", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom4", FieldKey = "CustomerCustom4", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom5", FieldKey = "CustomerCustom5", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom6", FieldKey = "CustomerCustom6", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom7", FieldKey = "CustomerCustom7", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom8", FieldKey = "CustomerCustom8", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom9", FieldKey = "CustomerCustom9", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom10", FieldKey = "CustomerCustom10", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom11", FieldKey = "CustomerCustom11", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom12", FieldKey = "CustomerCustom12", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom13", FieldKey = "CustomerCustom13", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom14", FieldKey = "CustomerCustom14", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom15", FieldKey = "CustomerCustom15", IsCustomField = true }); + l.Add(new FormField { TKey = "CustomerCustom16", FieldKey = "CustomerCustom16", IsCustomField = true }); + _formFields.Add(SockType.Customer.ToString(), l); + } + #endregion + + #region HeadOffice + { + List l = new List(); + l.Add(new FormField { TKey = "HeadOfficeAccountNumber", FieldKey = "AccountNumber" }); + l.Add(new FormField { TKey = "WebAddress", FieldKey = "WebAddress" }); + l.Add(new FormField { TKey = "HeadOfficeEmail", FieldKey = "EmailAddress" }); + l.Add(new FormField { TKey = "HeadOfficePhone1", FieldKey = "Phone1" }); + l.Add(new FormField { TKey = "HeadOfficePhone2", FieldKey = "Phone2" }); + l.Add(new FormField { TKey = "HeadOfficePhone3", FieldKey = "Phone3" }); + l.Add(new FormField { TKey = "HeadOfficePhone4", FieldKey = "Phone4" }); + l.Add(new FormField { TKey = "HeadOfficePhone5", FieldKey = "Phone5" }); + l.Add(new FormField { TKey = "Contract", FieldKey = "ContractId" }); + l.Add(new FormField { TKey = "ContractExpires", FieldKey = "ContractExpires" }); + //l.Add(new FormField { TKey = "UsesBanking", FieldKey = "UsesBanking" }); + l.Add(new FormField { TKey = "HeadOfficeNotes", FieldKey = "Notes" }); + l.Add(new FormField { TKey = "Tags", FieldKey = "Tags" }); + l.Add(new FormField { TKey = "Wiki", FieldKey = "Wiki" }); + l.Add(new FormField { TKey = "Attachments", FieldKey = "Attachments", Requireable = false }); + l.Add(new FormField { TKey = "AddressDeliveryAddress", FieldKey = "Address" }); + l.Add(new FormField { TKey = "AddressCity", FieldKey = "City" }); + l.Add(new FormField { TKey = "AddressStateProv", FieldKey = "Region" }); + l.Add(new FormField { TKey = "AddressCountry", FieldKey = "Country" }); + l.Add(new FormField { TKey = "AddressPostal", FieldKey = "AddressPostal" }); + l.Add(new FormField { TKey = "AddressLatitude", FieldKey = "Latitude" }); + l.Add(new FormField { TKey = "AddressLongitude", FieldKey = "Longitude" }); + l.Add(new FormField { TKey = "AddressPostalDeliveryAddress", FieldKey = "PostAddress" }); + l.Add(new FormField { TKey = "AddressPostalCity", FieldKey = "PostCity" }); + l.Add(new FormField { TKey = "AddressPostalStateProv", FieldKey = "PostRegion" }); + l.Add(new FormField { TKey = "AddressPostalCountry", FieldKey = "PostCountry" }); + l.Add(new FormField { TKey = "AddressPostalPostal", FieldKey = "PostCode" }); + + l.Add(new FormField { TKey = "HeadOfficeCustom1", FieldKey = "HeadOfficeCustom1", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom2", FieldKey = "HeadOfficeCustom2", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom3", FieldKey = "HeadOfficeCustom3", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom4", FieldKey = "HeadOfficeCustom4", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom5", FieldKey = "HeadOfficeCustom5", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom6", FieldKey = "HeadOfficeCustom6", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom7", FieldKey = "HeadOfficeCustom7", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom8", FieldKey = "HeadOfficeCustom8", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom9", FieldKey = "HeadOfficeCustom9", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom10", FieldKey = "HeadOfficeCustom10", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom11", FieldKey = "HeadOfficeCustom11", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom12", FieldKey = "HeadOfficeCustom12", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom13", FieldKey = "HeadOfficeCustom13", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom14", FieldKey = "HeadOfficeCustom14", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom15", FieldKey = "HeadOfficeCustom15", IsCustomField = true }); + l.Add(new FormField { TKey = "HeadOfficeCustom16", FieldKey = "HeadOfficeCustom16", IsCustomField = true }); + _formFields.Add(SockType.HeadOffice.ToString(), l); + } + #endregion + + + + #region Memo + { + List l = new List(); + l.Add(new FormField { TKey = "Tags", FieldKey = "Tags" }); + l.Add(new FormField { TKey = "Wiki", FieldKey = "Wiki" }); + l.Add(new FormField { TKey = "Attachments", FieldKey = "Attachments", Requireable = false }); + + l.Add(new FormField { TKey = "MemoCustom1", FieldKey = "MemoCustom1", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom2", FieldKey = "MemoCustom2", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom3", FieldKey = "MemoCustom3", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom4", FieldKey = "MemoCustom4", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom5", FieldKey = "MemoCustom5", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom6", FieldKey = "MemoCustom6", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom7", FieldKey = "MemoCustom7", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom8", FieldKey = "MemoCustom8", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom9", FieldKey = "MemoCustom9", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom10", FieldKey = "MemoCustom10", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom11", FieldKey = "MemoCustom11", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom12", FieldKey = "MemoCustom12", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom13", FieldKey = "MemoCustom13", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom14", FieldKey = "MemoCustom14", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom15", FieldKey = "MemoCustom15", IsCustomField = true }); + l.Add(new FormField { TKey = "MemoCustom16", FieldKey = "MemoCustom16", IsCustomField = true }); + _formFields.Add(SockType.Memo.ToString(), l); + } + #endregion + + #region Reminder + { + List l = new List(); + l.Add(new FormField { TKey = "ReminderColor", FieldKey = "Color" }); + + l.Add(new FormField { TKey = "Tags", FieldKey = "Tags" }); + l.Add(new FormField { TKey = "Wiki", FieldKey = "Wiki" }); + l.Add(new FormField { TKey = "Attachments", FieldKey = "Attachments", Requireable = false }); + l.Add(new FormField { TKey = "ReminderCustom1", FieldKey = "ReminderCustom1", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom2", FieldKey = "ReminderCustom2", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom3", FieldKey = "ReminderCustom3", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom4", FieldKey = "ReminderCustom4", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom5", FieldKey = "ReminderCustom5", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom6", FieldKey = "ReminderCustom6", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom7", FieldKey = "ReminderCustom7", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom8", FieldKey = "ReminderCustom8", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom9", FieldKey = "ReminderCustom9", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom10", FieldKey = "ReminderCustom10", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom11", FieldKey = "ReminderCustom11", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom12", FieldKey = "ReminderCustom12", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom13", FieldKey = "ReminderCustom13", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom14", FieldKey = "ReminderCustom14", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom15", FieldKey = "ReminderCustom15", IsCustomField = true }); + l.Add(new FormField { TKey = "ReminderCustom16", FieldKey = "ReminderCustom16", IsCustomField = true }); + _formFields.Add(SockType.Reminder.ToString(), l); + } + #endregion + + #region Review + { + List l = new List(); + l.Add(new FormField { TKey = "Tags", FieldKey = "Tags" }); + l.Add(new FormField { TKey = "Wiki", FieldKey = "Wiki" }); + l.Add(new FormField { TKey = "Attachments", FieldKey = "Attachments", Requireable = false }); + l.Add(new FormField { TKey = "ReviewCustom1", FieldKey = "ReviewCustom1", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom2", FieldKey = "ReviewCustom2", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom3", FieldKey = "ReviewCustom3", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom4", FieldKey = "ReviewCustom4", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom5", FieldKey = "ReviewCustom5", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom6", FieldKey = "ReviewCustom6", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom7", FieldKey = "ReviewCustom7", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom8", FieldKey = "ReviewCustom8", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom9", FieldKey = "ReviewCustom9", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom10", FieldKey = "ReviewCustom10", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom11", FieldKey = "ReviewCustom11", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom12", FieldKey = "ReviewCustom12", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom13", FieldKey = "ReviewCustom13", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom14", FieldKey = "ReviewCustom14", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom15", FieldKey = "ReviewCustom15", IsCustomField = true }); + l.Add(new FormField { TKey = "ReviewCustom16", FieldKey = "ReviewCustom16", IsCustomField = true }); + _formFields.Add(SockType.Review.ToString(), l); + } + #endregion + + //****************************************************** + } + + if (!_formFields.ContainsKey(key)) + throw new System.ArgumentOutOfRangeException($"FormFieldReferenceList: {key} is not valid"); + return _formFields[key]; + + } + + public static string TranslateLTCustomFieldToInternalCustomFieldName(string lTCustomFieldName) + { + var i = System.Convert.ToInt32(System.Text.RegularExpressions.Regex.Replace( + lTCustomFieldName, // Our input + "[^0-9]", // Select everything that is not in the range of 0-9 + "" // Replace that with an empty string. + )); + + return $"c{i}"; + } + + }//eoc ObjectFields + + public class FormField + { + private string tKey; + + //CLIENT / SERVER Unique identifier used at BOTH client and server + //MUST MATCH MODEL PROPERTY NAME EXACTLY UNLESS ModelProperty is set OR REQUIRED FIELD VALIDATION WON"T WORK + //The model name is used for validation and the fieldKey sometimes is not the model name in big forms with repeating model names in which case + //the fieldkey will be unique and the ModelProperty will be set instead + public string FieldKey { get; set; } + + //This exists to handle scenario of repeated identical model property multiple times on workorder quote pm forms + //e.g. need to use a unique fieldkey but it can't match the model property becuase then + //it would need to be "Tags" but there is already a "Tags" on the workorder header and in units + //so here we can specify an exact property tag to check. RequiredfieldsValidator will use this instead when set to issue errors + public string ModelProperty { get; set; } = null; + + //CLIENT Use only for display in customization form, translation key to show translated name on UI customize form + public string TKey + { + get => tKey; + set + { + tKey = value; + if (this.FieldKey == null)//save having to type out fieldkey when it's identical to tkey + { + this.FieldKey = value; + } + } + } + + //CLIENT Use only for display in customization form to disambiguate things like + //Tags in main workorder and Tags in Workorder Item and Tags in Unit (all on same form) + public string TKeySection { get; set; } = null; + + //CLIENT form customization + public bool Hideable { get; set; } + //CLIENT form customization + public bool Requireable { get; set; } + + + //CLIENT / SERVER - client display server validation purposes + public bool IsCustomField { get; set; } + + + public FormField() + { + //most common defaults + Hideable = true; + Requireable = true; + IsCustomField = false; + } + }//eoc + + +}//ens diff --git a/server/biz/FormUserOptionsBiz.cs b/server/biz/FormUserOptionsBiz.cs new file mode 100644 index 0000000..bff5544 --- /dev/null +++ b/server/biz/FormUserOptionsBiz.cs @@ -0,0 +1,158 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; + +namespace Sockeye.Biz +{ + + //## This class manages personal form settings for users + + internal class FormUserOptionsBiz : BizObject + { + internal FormUserOptionsBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.FormUserOptions; + } + + internal static FormUserOptionsBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new FormUserOptionsBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new FormUserOptionsBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.FormUserOptions.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task UpsertAsync(FormUserOptions newObject) + { + //Validate(newObject, null); + newObject.UserId=UserId;//always defaults to currently logged in user + if (HasErrors) + return null; + else + { + //remove any prior version that might exist (or might not) + await DeleteAsync(newObject.FormKey); + newObject.Options = JsonUtil.CompactJson(newObject.Options); + await ct.FormUserOptions.AddAsync(newObject); + await ct.SaveChangesAsync(); + return newObject; + } + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(string formKey) + { + var ret = await ct.FormUserOptions.AsNoTracking().SingleOrDefaultAsync(m => m.FormKey == formKey && m.UserId == UserId); + return ret; + } + + // //////////////////////////////////////////////////////////////////////////////////////////////// + // //UPDATE + // // + // internal async Task PutAsync(FormUserOptions putObject) + // { + // var dbObject = await GetAsync(putObject.FormKey); + // if (dbObject == null) + // { + // AddError(ApiErrorCode.NOT_FOUND, "formKey"); + // return null; + // } + // if (dbObject.Concurrency != putObject.Concurrency) + // { + // AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + // return null; + // } + + // putObject.Options = JsonUtil.CompactJson(putObject.Options); + // Validate(putObject, dbObject); + // if (HasErrors) return null; + // ct.Replace(dbObject, putObject); + // try + // { + // await ct.SaveChangesAsync(); + // } + // catch (DbUpdateConcurrencyException) + // { + // if (!await ExistsAsync(putObject.Id)) + // AddError(ApiErrorCode.NOT_FOUND); + // else + // AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + // return null; + // } + + // return putObject; + // } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(string formKey) + { + // using (var transaction = await ct.Database.BeginTransactionAsync()) + // { + var dbObject = await GetAsync(formKey); + if (dbObject == null) + { + return true; + } + // ValidateCanDelete(dbObject); + if (HasErrors) + return false; + ct.FormUserOptions.Remove(dbObject); + await ct.SaveChangesAsync(); + // await transaction.CommitAsync(); + return true; + // } + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + // private void Validate(FormUserOptions proposedObj, FormUserOptions currentObj) + // { + // if (proposedObj.UserId != UserId) + // { + // AddError(ApiErrorCode.NOT_AUTHORIZED, "generalerror", "A user can only modify their own personal form settings. UserId does not match current api user logged in."); + // } + // } + + // private void ValidateCanDelete(FormUserOptions inObj) + // { + // if (inObj.UserId != UserId) + // { + // AddError(ApiErrorCode.NOT_AUTHORIZED, "generalerror", "A user can only modify their own personal form settings. UserId does not match current api user logged in."); + // } + + // } + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/GlobalBizSettingsBiz.cs b/server/biz/GlobalBizSettingsBiz.cs new file mode 100644 index 0000000..5240233 --- /dev/null +++ b/server/biz/GlobalBizSettingsBiz.cs @@ -0,0 +1,121 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using System.Collections.Generic; + +namespace Sockeye.Biz +{ + + internal class GlobalBizSettingsBiz : BizObject + { + + internal GlobalBizSettingsBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.Global; + } + + internal static GlobalBizSettingsBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new GlobalBizSettingsBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new GlobalBizSettingsBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + + //Get one + internal async Task GetAsync(bool logTheGetEvent = true) + { + //first try to fetch from db + var ret = await ct.GlobalBizSettings.AsNoTracking().SingleOrDefaultAsync(m => m.Id == 1); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, 1, BizType, SockEvent.Retrieved), ct); + } + + //not in db then get the default + if (ret == null) + { + throw new System.Exception("GlobalBizSettingsBiz::GetAsync -> Global settings object not found in database!!"); + } + return ret; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(GlobalBizSettings putObject) + { + var dbObject = await GetAsync(false); + if (dbObject == null) + throw new System.Exception("GlobalBizSettingsBiz::PutAsync -> Global settings object not found in database. Contact support immediately!"); + + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + Validate(putObject, dbObject); + if (HasErrors) return null; + + List originalTags = dbObject.AllTags(); + List newTags = putObject.AllTags(); + + ct.Replace(dbObject, putObject); + + + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + //Update cache + ServerGlobalBizSettings.Initialize(putObject, null); + + //Log modification and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, 1, BizType, SockEvent.Modified), ct); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newTags, originalTags); + + return putObject; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private void Validate(GlobalBizSettings proposedObj, GlobalBizSettings currentObj) + { + //currently nothing to validate + } + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/GlobalOpsBackupSettingsBiz.cs b/server/biz/GlobalOpsBackupSettingsBiz.cs new file mode 100644 index 0000000..9dd2bb5 --- /dev/null +++ b/server/biz/GlobalOpsBackupSettingsBiz.cs @@ -0,0 +1,128 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; + +namespace Sockeye.Biz +{ + + internal class GlobalOpsBackupSettingsBiz : BizObject + { + + internal GlobalOpsBackupSettingsBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.Backup; + } + + internal static GlobalOpsBackupSettingsBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new GlobalOpsBackupSettingsBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new GlobalOpsBackupSettingsBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + + //Get one + internal async Task GetAsync(bool logTheGetEvent = true) + { + //first try to fetch from db + var ret = await ct.GlobalOpsBackupSettings.SingleOrDefaultAsync(m => m.Id == 1); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, 1, BizType, SockEvent.Retrieved), ct); + //expected to exists because it's created on boot if not present + if (ret == null) + throw new System.Exception("GlobalOpsBackupSettings::GetAsync -> Backup settings object not found in database!!"); + + return ret; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(GlobalOpsBackupSettings putObject) + { + //todo: replace with new put methodology? + var dbObject = await ct.GlobalOpsBackupSettings.FirstOrDefaultAsync(m => m.Id == 1); + if (dbObject == null) + throw new System.Exception("GlobalOpsBackupSettings::PutAsync -> Global settings object not found in database!!"); + + + // //testing UTC fuckiness + // var utcNow = DateTime.UtcNow; + + // var desiredBackupTime = new DateTime(2020, 5, 23, 23, 55, 0).ToUniversalTime(); + + // var NextBackup = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, putObject.BackupTime.Hour, putObject.BackupTime.Minute, 0, DateTimeKind.Utc); + // if (NextBackup < utcNow) NextBackup = NextBackup.AddDays(1); + // //theory if nexxtbacup at the end of the adjustment is in the past then add a day to it + + + //If backup time has changed then reset last backup as well as it might block from taking effect + var ResetLastBackup = (putObject.BackupTime.Hour != dbObject.BackupTime.Hour || putObject.BackupTime.Minute != dbObject.BackupTime.Minute); + + CopyObject.Copy(putObject, dbObject, "Id"); + ct.Entry(dbObject).OriginalValues["Concurrency"] = putObject.Concurrency; + Validate(dbObject); + if (HasErrors) + return null; + + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, 1, BizType, SockEvent.Modified), ct); + //Update the static copy for the server + ServerGlobalOpsSettingsCache.Backup = dbObject; + if (ResetLastBackup) + { + ServerGlobalOpsSettingsCache.NextBackup = DateTime.MinValue; + ServerGlobalOpsSettingsCache.SetNextBackup(); + } + return dbObject; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private void Validate(GlobalOpsBackupSettings inObj) + { + + //currently nothing to validate + } + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/GlobalOpsNotificationSettingsBiz.cs b/server/biz/GlobalOpsNotificationSettingsBiz.cs new file mode 100644 index 0000000..a14803c --- /dev/null +++ b/server/biz/GlobalOpsNotificationSettingsBiz.cs @@ -0,0 +1,110 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; + +namespace Sockeye.Biz +{ + + internal class GlobalOpsNotificationSettingsBiz : BizObject + { + + internal GlobalOpsNotificationSettingsBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.OpsNotificationSettings; + } + + internal static GlobalOpsNotificationSettingsBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new GlobalOpsNotificationSettingsBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new GlobalOpsNotificationSettingsBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + + //Get one + internal async Task GetAsync(bool logTheGetEvent = true) + { + //first try to fetch from db + var ret = await ct.GlobalOpsNotificationSettings.SingleOrDefaultAsync(m => m.Id == 1); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, 1, BizType, SockEvent.Retrieved), ct); + //expected to exists because it's created on boot if not present + if (ret == null) + throw new System.Exception("GlobalOpsNotificationSettings::GetAsync -> Settings object not found in database!!"); + + return ret; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task PutAsync(GlobalOpsNotificationSettings putObject) + { + //todo: replace with new put methodology + var dbObject = await ct.GlobalOpsNotificationSettings.FirstOrDefaultAsync(m => m.Id == 1); + if (dbObject == null) + throw new System.Exception("GlobalOpsNotificationSettings::PutAsync -> Settings object not found in database!!"); + + CopyObject.Copy(putObject, dbObject, "Id"); + ct.Entry(dbObject).OriginalValues["Concurrency"] = putObject.Concurrency; + Validate(dbObject); + if (HasErrors) + return null; + + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, 1, BizType, SockEvent.Modified), ct); + //Update the static copy for the server + ServerGlobalOpsSettingsCache.Notify = dbObject; + + return dbObject; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private void Validate(GlobalOpsNotificationSettings inObj) + { + + //currently nothing to validate + } + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/HeadOfficeBiz.cs b/server/biz/HeadOfficeBiz.cs new file mode 100644 index 0000000..3f69020 --- /dev/null +++ b/server/biz/HeadOfficeBiz.cs @@ -0,0 +1,597 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Linq; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Sockeye.Biz +{ + internal class HeadOfficeBiz : BizObject, IJobObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, IImportAbleObject, INotifiableObject + { + internal HeadOfficeBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.HeadOffice; + } + + internal static HeadOfficeBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new HeadOfficeBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new HeadOfficeBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.HeadOffice.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(HeadOffice newObject) + { + await ValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.HeadOffice.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + await SearchIndexAsync(newObject, true); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + await HandlePotentialNotificationEvent(SockEvent.Created, newObject); + return newObject; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.HeadOffice.AsNoTracking().SingleOrDefaultAsync(z => z.Id == id); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, SockEvent.Retrieved), ct); + return ret; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(HeadOffice putObject) + { + var dbObject = await GetAsync(putObject.Id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + putObject.Tags = TagBiz.NormalizeTags(putObject.Tags); + putObject.CustomFields = JsonUtil.CompactJson(putObject.CustomFields); + await ValidateAsync(putObject, dbObject); + if (HasErrors) return null; + ct.Replace(dbObject, putObject); + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + await SearchIndexAsync(putObject, false); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags); + await HandlePotentialNotificationEvent(SockEvent.Modified, putObject, dbObject); + return putObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + var dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return false; + } + await ValidateCanDeleteAsync(dbObject); + if (HasErrors) + return false; + + + //DELETE DIRECT CHILD OBJECTS + { + var ContactIds = await ct.User.AsNoTracking().Where(z => z.HeadOfficeId == id).Select(z => z.Id).ToListAsync(); + if (ContactIds.Count() > 0) + { + UserBiz b = new UserBiz(ct, UserId, UserTranslationId, CurrentUserRoles); + foreach (long ItemId in ContactIds) + if (!await b.DeleteAsync(ItemId, transaction)) + { + AddError(ApiErrorCode.CHILD_OBJECT_ERROR, null, $"HeadOfficeContact [{ItemId}]: {b.GetErrorsAsString()}"); + return false; + } + } + + { + var IDList = await ct.Review.AsNoTracking().Where(x => x.SockType == SockType.HeadOffice && x.ObjectId == id).Select(x => x.Id).ToListAsync(); + if (IDList.Count() > 0) + { + ReviewBiz b = new ReviewBiz(ct, UserId, UserTranslationId, CurrentUserRoles); + foreach (long ItemId in IDList) + if (!await b.DeleteAsync(ItemId, transaction)) + { + AddError(ApiErrorCode.CHILD_OBJECT_ERROR, null, $"Review [{ItemId}]: {b.GetErrorsAsString()}"); + return false; + } + } + } + } + + ct.HeadOffice.Remove(dbObject); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct); + await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); + await transaction.CommitAsync(); + await HandlePotentialNotificationEvent(SockEvent.Deleted, dbObject); + + return true; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // + private async Task SearchIndexAsync(HeadOffice obj, bool isNew) + { + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + DigestSearchText(obj, SearchParams); + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id, SockType specificType) + { + var obj = await GetAsync(id, false); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + DigestSearchText(obj, SearchParams); + return SearchParams; + } + + public void DigestSearchText(HeadOffice obj, Search.SearchIndexProcessObjectParameters searchParams) + { + if (obj != null) + searchParams.AddText(obj.Notes) + .AddText(obj.Name) + .AddText(obj.Wiki) + .AddText(obj.Tags) + .AddText(obj.WebAddress) + .AddText(obj.AccountNumber) + .AddText(obj.Phone1) + .AddText(obj.Phone2) + .AddText(obj.Phone3) + .AddText(obj.Phone4) + .AddText(obj.Phone5) + .AddText(obj.EmailAddress) + .AddText(obj.PostAddress) + .AddText(obj.PostCity) + .AddText(obj.PostRegion) + .AddText(obj.PostCountry) + .AddText(obj.PostCode) + .AddText(obj.Address) + .AddText(obj.City) + .AddText(obj.Region) + .AddText(obj.Country) + .AddText(obj.AddressPostal) + .AddCustomFields(obj.CustomFields); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + private async Task ValidateAsync(HeadOffice proposedObj, HeadOffice currentObj) + { + + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.HeadOffice.AnyAsync(z => z.Name == proposedObj.Name && z.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == SockType.HeadOffice.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + + private async Task ValidateCanDeleteAsync(HeadOffice inObj) + { + //Referential integrity + //FOREIGN KEY CHECKS + if (await ct.User.AnyAsync(m => m.HeadOfficeId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("User")); + if (await ct.Customer.AnyAsync(z => z.HeadOfficeId == inObj.Id) == true) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("Customer")); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //REPORTING + // + public async Task GetReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + var idList = dataListSelectedRequest.SelectedRowIds; + JArray ReportData = new JArray(); + while (idList.Any()) + { + var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE); + idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray(); + //query for this batch, comes back in db natural order unfortunately + var batchResults = await ct.HeadOffice.AsNoTracking().Where(z => batch.Contains(z.Id)).ToArrayAsync(); + //order the results back into original + var orderedList = from id in batch join z in batchResults on id equals z.Id select z; + batchResults = null; + foreach (HeadOffice w in orderedList) + { + if (!ReportRenderManager.KeepGoing(jobId)) return null; + // await PopulateVizFields(w); + var jo = JObject.FromObject(w); + if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"])) + jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]); + ReportData.Add(jo); + } + orderedList = null; + } + vc.Clear(); + return ReportData; + } + + private VizCache vc = new VizCache(); + + // //populate viz fields from provided object + // private async Task PopulateVizFields(HeadOffice o) + // { + + // } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // IMPORT EXPORT + // + public async Task GetExportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + //for now just re-use the report data code + //this may turn out to be the pattern for most biz object types but keeping it seperate allows for custom usage from time to time + return await GetReportData(dataListSelectedRequest, jobId); + } + + + + + public async Task> ImportData(AyImportData importData) + { + List ImportResult = new List(); + string ImportTag = ImportUtil.GetImportTag(); + + //ignore these fields + var jsset = JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = new Sockeye.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "CustomFields" }) }); + + foreach (JObject j in importData.Data) + { + try + { + + + + + long existingId = await ct.HeadOffice.AsNoTracking().Where(z => z.Name == (string)j["Name"]).Select(x => x.Id).FirstOrDefaultAsync(); + + if (existingId == 0) + { + if (importData.DoImport) + { + //import this record + var Target = j.ToObject(jsset); + Target.Tags.Add(ImportTag); + + + var res = await CreateAsync(Target); + if (res == null) + { + ImportResult.Add($"❌ {Target.Name}\r\n{this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"✔️ {Target.Name}"); + } + } + } + else + { + if (importData.DoUpdate) + { + //update this record with any data provided + //load existing record + var Target = await GetAsync((long)existingId); + var Source = j.ToObject(jsset); + var propertiesToUpdate = j.Properties().Select(p => p.Name).ToList(); + propertiesToUpdate.Remove("Name"); + ImportUtil.Update(Source, Target, propertiesToUpdate); + + var res = await PutAsync(Target); + + if (res == null) + { + ImportResult.Add($"❌ {Target.Name} - {this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"✔️ {Target.Name}"); + } + } + } + } + catch (Exception ex) + { + ImportResult.Add($"❌ Exception processing import\n record:{j.ToString()}\nError:{ex.Message}\nSource:{ex.Source}\nStack:{ex.StackTrace.ToString()}"); + } + } + return ImportResult; + } + + // public async Task> ImportData(AyImportData importData) + // { + // List ImportResult = new List(); + // string ImportTag = $"imported-{FileUtil.GetSafeDateFileName()}"; + + // var jsset = JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = new Sockeye.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "CustomFields" }) }); + // foreach (JObject j in importData.Data) + // { + // var w = j.ToObject(jsset); + // if (j["CustomFields"] != null) + // w.CustomFields = j["CustomFields"].ToString(); + // w.Tags.Add(ImportTag);//so user can find them all and revert later if necessary + // var res = await CreateAsync(w); + // if (res == null) + // { + // ImportResult.Add($"* {w.Name} - {this.GetErrorsAsString()}"); + // this.ClearErrors(); + // } + // else + // { + // ImportResult.Add($"{w.Name} - ok"); + // } + // } + // return ImportResult; + // } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + public async Task HandleJobAsync(OpsJob job) + { + //Hand off the particular job to the corresponding processing code + //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so + //basically any error condition during job processing should throw up an exception if it can't be handled + switch (job.JobType) + { + case JobType.BatchCoreObjectOperation: + await ProcessBatchJobAsync(job); + break; + default: + throw new System.ArgumentOutOfRangeException($"HeadOfficeBiz.HandleJob-> Invalid job type{job.JobType.ToString()}"); + } + } + + + + private async Task ProcessBatchJobAsync(OpsJob job) + { + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); + await JobsBiz.LogJobAsync(job.GId, $"LT:StartJob {job.SubType}"); + List idList = new List(); + long FailedObjectCount = 0; + JObject jobData = JObject.Parse(job.JobInfo); + if (jobData.ContainsKey("idList")) + idList = ((JArray)jobData["idList"]).ToObject>(); + else + idList = await ct.HeadOffice.AsNoTracking().Select(z => z.Id).ToListAsync(); + bool SaveIt = false; + //--------------------------------- + //case 4192 + TimeSpan ProgressAndCancelCheckSpan = new TimeSpan(0, 0, ServerBootConfig.JOB_PROGRESS_UPDATE_AND_CANCEL_CHECK_SECONDS); + DateTime LastProgressCheck = DateTime.UtcNow.Subtract(new TimeSpan(1, 1, 1, 1, 1)); + var TotalRecords = idList.LongCount(); + long CurrentRecord = -1; + //--------------------------------- + foreach (long id in idList) + { + try + { + //-------------------------------- + //case 4192 + //Update progress / cancel requested? + CurrentRecord++; + if (DateUtil.IsAfterDuration(LastProgressCheck, ProgressAndCancelCheckSpan)) + { + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{CurrentRecord}/{TotalRecords}"); + if (await JobsBiz.GetJobStatusAsync(job.GId) == JobStatus.CancelRequested) + break; + LastProgressCheck = DateTime.UtcNow; + } + //--------------------------------- + + SaveIt = false; + ClearErrors(); + HeadOffice o = null; + //save a fetch if it's a delete + if (job.SubType != JobSubType.Delete) + o = await GetAsync(id, false); + switch (job.SubType) + { + case JobSubType.TagAddAny: + case JobSubType.TagAdd: + case JobSubType.TagRemoveAny: + case JobSubType.TagRemove: + case JobSubType.TagReplaceAny: + case JobSubType.TagReplace: + SaveIt = TagBiz.ProcessBatchTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); + break; + case JobSubType.Delete: + if (!await DeleteAsync(id)) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + break; + default: + throw new System.ArgumentOutOfRangeException($"ProcessBatchJobAsync -> Invalid job Subtype{job.SubType}"); + } + if (SaveIt) + { + o = await PutAsync(o); + if (o == null) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + } + + //delay so we're not tying up all the resources in a tight loop + await Task.Delay(Sockeye.Util.ServerBootConfig.JOB_OBJECT_HANDLE_BATCH_JOB_LOOP_DELAY); + } + catch (Exception ex) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors id({id})"); + await JobsBiz.LogJobAsync(job.GId, ExceptionUtil.ExtractAllExceptionMessages(ex)); + } + } + + //--------------------------------- + //case 4192 + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{++CurrentRecord}/{TotalRecords}"); + //--------------------------------- + await JobsBiz.LogJobAsync(job.GId, $"LT:BatchJob {job.SubType} {idList.Count}{(FailedObjectCount > 0 ? " - LT:Failed " + FailedObjectCount : "")}"); + await JobsBiz.UpdateJobStatusAsync(job.GId, FailedObjectCount == 0 ? JobStatus.Completed : JobStatus.Failed); + } + + + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // NOTIFICATION PROCESSING + // + public async Task HandlePotentialNotificationEvent(SockEvent ayaEvent, ICoreBizObjectModel proposedObj, ICoreBizObjectModel currentObj = null) + { + ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger(); + + log.LogDebug($"HandlePotentialNotificationEvent processing: [SockType:{this.BizType}, AyaEvent:{ayaEvent}]"); + + bool isNew = currentObj == null; + + + //STANDARD EVENTS FOR ALL OBJECTS + await NotifyEventHelper.ProcessStandardObjectEvents(ayaEvent, proposedObj, ct); + + //SPECIFIC EVENTS FOR THIS OBJECT + HeadOffice o = (HeadOffice)proposedObj; + + //## DELETED EVENTS + //any event added below needs to be removed, so + //just blanket remove any event for this object of eventtype that would be added below here + //do it regardless any time there's an update and then + //let this code below handle the refreshing addition that could have changes + // await NotifyEventHelper.ClearPriorEventsForObject(ct, SockType.HeadOffice, o.Id, NotifyEventType.ContractExpiring); + + + //## CREATED / MODIFIED EVENTS + if (ayaEvent == SockEvent.Created || ayaEvent == SockEvent.Modified) + { + + + + } + + }//end of process notifications + + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/IBizObject.cs b/server/biz/IBizObject.cs new file mode 100644 index 0000000..55c8acc --- /dev/null +++ b/server/biz/IBizObject.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; + + +namespace Sockeye.Biz +{ + /// + /// + /// + internal interface IBizObject + { + //validate via validation attributes + //https://stackoverflow.com/questions/36330981/is-the-validationresult-class-suitable-when-validating-the-state-of-an-object + + + /// + /// Contains list of errors + /// + List Errors { get; } + + /// + /// Is true if there are errors + /// + bool HasErrors { get; } + + /// + /// Is true if the field specified exists in the list + /// + bool PropertyHasErrors(string propertyName); + + /// + /// + /// + /// + /// + /// + void AddError(ApiErrorCode errorCode, string propertyName = null, string errorMessage = null); + + // /// + // /// + // /// + // /// + // void AddvalidationError(ValidationError validationError); + + + + + + + } + +} \ No newline at end of file diff --git a/server/biz/IExportAbleObject.cs b/server/biz/IExportAbleObject.cs new file mode 100644 index 0000000..9b91df7 --- /dev/null +++ b/server/biz/IExportAbleObject.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using Sockeye.Models; +using System; + +namespace Sockeye.Biz +{ + /// + /// Interface for biz objects that support exporting + /// + internal interface IExportAbleObject + { + + //Get items indicated in id list in exportable format + //called by ExportBiz rendering code + Task GetExportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId); + const int EXPORT_DATA_BATCH_SIZE = 100; + + } + +} \ No newline at end of file diff --git a/server/biz/IImportAbleObject.cs b/server/biz/IImportAbleObject.cs new file mode 100644 index 0000000..f1e0956 --- /dev/null +++ b/server/biz/IImportAbleObject.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using System.Collections.Generic; +using Sockeye.Models; + +namespace Sockeye.Biz +{ + /// + /// Interface for biz objects that support importing from JSON + /// + internal interface IImportAbleObject + { + Task> ImportData(AyImportData importData); + } + +} \ No newline at end of file diff --git a/server/biz/IJobObject.cs b/server/biz/IJobObject.cs new file mode 100644 index 0000000..9fae030 --- /dev/null +++ b/server/biz/IJobObject.cs @@ -0,0 +1,22 @@ +using Sockeye.Models; + +namespace Sockeye.Biz +{ + /// + /// Interface for biz objects that support jobs / long running operations + /// + internal interface IJobObject + { + + /// + /// Start and process an operation + /// NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so + /// basically any error condition during job processing should throw up an exception if it can't be handled + /// + /// + System.Threading.Tasks.Task HandleJobAsync(OpsJob job); + + + } + +} \ No newline at end of file diff --git a/server/biz/INotifiableObject.cs b/server/biz/INotifiableObject.cs new file mode 100644 index 0000000..d481548 --- /dev/null +++ b/server/biz/INotifiableObject.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using Sockeye.Models; +namespace Sockeye.Biz +{ + /// + /// Interface for biz objects that support notification + /// + internal interface INotifiableObject + { + Task HandlePotentialNotificationEvent(SockEvent ayaEvent, ICoreBizObjectModel newObject, ICoreBizObjectModel originalObject = null); + } +} \ No newline at end of file diff --git a/server/biz/IReportAbleObject.cs b/server/biz/IReportAbleObject.cs new file mode 100644 index 0000000..402df07 --- /dev/null +++ b/server/biz/IReportAbleObject.cs @@ -0,0 +1,21 @@ +using System; +using System.Threading.Tasks; +using Sockeye.Models; +using Newtonsoft.Json.Linq; +namespace Sockeye.Biz +{ + /// + /// Interface for biz objects that support reporting + /// + internal interface IReportAbleObject + { + + //Get items indicated in id list in report format + //called by ReportBiz rendering code + Task GetReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId); + const int REPORT_DATA_BATCH_SIZE = 100; + + + } + +} \ No newline at end of file diff --git a/server/biz/ISearchAbleObject.cs b/server/biz/ISearchAbleObject.cs new file mode 100644 index 0000000..b708cc3 --- /dev/null +++ b/server/biz/ISearchAbleObject.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +namespace Sockeye.Biz +{ + /// + /// Interface for biz objects that support searching + /// + internal interface ISearchAbleObject + { + + //get all text for the object that would have been indexed for search + //called by search::GetInfoAsync as a result of a user requesting a search result sumary + Task GetSearchResultSummary(long id, SockType specificType); + + } + +} \ No newline at end of file diff --git a/server/biz/ImportableBizObjectAttribute.cs b/server/biz/ImportableBizObjectAttribute.cs new file mode 100644 index 0000000..9048056 --- /dev/null +++ b/server/biz/ImportableBizObjectAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Sockeye.Biz +{ + /// + /// Marker attribute indicating that an object is a importable type + /// Used in + /// + [AttributeUsage(AttributeTargets.All)] + public class ImportableBizObjectAttribute : Attribute + { + //No code required, it's just a marker + //https://docs.microsoft.com/en-us/dotnet/standard/attributes/writing-custom-attributes + } +}//eons diff --git a/server/biz/IntegrationBiz.cs b/server/biz/IntegrationBiz.cs new file mode 100644 index 0000000..9cc6ad0 --- /dev/null +++ b/server/biz/IntegrationBiz.cs @@ -0,0 +1,253 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using System.Linq; +using System.Collections.Generic; + +namespace Sockeye.Biz +{ + internal class IntegrationBiz : BizObject + { + + + /* + todo: needs code to back routes for logging and fetching log to view + + */ + internal IntegrationBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.Integration; + } + + internal static IntegrationBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new IntegrationBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new IntegrationBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Integration.AnyAsync(z => z.Id == id); + } + + internal async Task ExistsByIntegrationAppIdAsync(Guid IntegrationAppId) + { + return await ct.Integration.AnyAsync(z => z.IntegrationAppId == IntegrationAppId); + } + + + /////////////////////////////////////////////////////// + //APPID FROM dbID + internal async Task AppIdFromDbIdAsync(long id) + { + return await ct.Integration.AsNoTracking().Where(z => z.Id == id).Select(z => z.IntegrationAppId).SingleOrDefaultAsync(); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(Integration newObject) + { + await ValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + + await ct.Integration.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + return newObject; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + return await GetAsync(await AppIdFromDbIdAsync(id), logTheGetEvent); + } + + + internal async Task GetAsync(Guid IntegrationAppId, bool logTheGetEvent = true) + { + var ret = await ct.Integration.AsNoTracking().Include(z => z.Items).SingleOrDefaultAsync(m => m.IntegrationAppId == IntegrationAppId); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, ret.Id, BizType, SockEvent.Retrieved), ct); + + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(Integration putObject) + { + //Get the db object with no tracking as about to be replaced not updated + Integration dbObject = await GetAsync(putObject.IntegrationAppId, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "IntegrationAppId"); + return null; + } + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + + + await ValidateAsync(putObject, dbObject); + if (HasErrors) return null; + ct.Replace(dbObject, putObject); + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + + + return putObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + + internal async Task DeleteAsync(long id) + { + return await DeleteAsync(await AppIdFromDbIdAsync(id)); + } + + internal async Task DeleteAsync(Guid IntegrationAppId) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + Integration dbObject = await GetAsync(IntegrationAppId, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return false; + } + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + + ct.Integration.Remove(dbObject); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct); + await transaction.CommitAsync(); + + return true; + } + } + + + ///////////////////////////////////////////////////////////////////////////////// + //LOG to integration log + // + internal async Task LogAsync(NameIdItem logItem) + { + if (string.IsNullOrWhiteSpace(logItem.Name)) + { + AddError(ApiErrorCode.NOT_FOUND, "name", "The log text message (name) is empty, nothing to log"); + return false; + } + if (!await ExistsAsync(logItem.Id)) + { + AddError(ApiErrorCode.NOT_FOUND, "id", "The integration id specified was not found, remember this is the internal id (integer), not the application specific id (Guid)"); + return false; + } + + await ct.IntegrationLog.AddAsync(new IntegrationLog { IntegrationId = logItem.Id, StatusText = logItem.Name }); + await ct.SaveChangesAsync(); + return true; + } + + //GET LOG + internal async Task> GetLogAsync(long id) + { + return await ct.IntegrationLog.AsNoTracking().Where(z=>z.IntegrationId==id).OrderByDescending(z=>z.Created).ToListAsync(); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + private async Task ValidateAsync(Integration proposedObj, Integration currentObj) + { + + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.Integration.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + //Name required + if (proposedObj.IntegrationAppId == Guid.Empty) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "IntegrationAppId"); + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("IntegrationAppId")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + if (await ct.Integration.AnyAsync(m => m.IntegrationAppId == proposedObj.IntegrationAppId && m.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "IntegrationAppId"); + } + } + + } + + private void ValidateCanDelete(Integration inObj) + { + //whatever needs to be check to delete this object + } + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/JobOperationsBiz.cs b/server/biz/JobOperationsBiz.cs new file mode 100644 index 0000000..8ddd8d8 --- /dev/null +++ b/server/biz/JobOperationsBiz.cs @@ -0,0 +1,145 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Sockeye.Models; + + +namespace Sockeye.Biz +{ + + + internal class JobOperationsBiz : BizObject, IJobObject + { + + internal JobOperationsBiz(AyContext dbcontext, long currentUserId, AuthorizationRoles userRoles) + { + ct = dbcontext; + UserId = currentUserId; + CurrentUserRoles = userRoles; + BizType = SockType.ServerJob; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // GET + // + internal async Task> GetJobListAsync() + { + List ret = new List(); + + var jobitems = await ct.OpsJob + .OrderBy(z => z.Created) + .ToListAsync(); + + foreach (OpsJob i in jobitems) + { + //fetch the most recent log time for each job + var mostRecentLogItem = await ct.OpsJobLog + .Where(z => z.JobId == i.GId) + .OrderByDescending(z => z.Created) + .FirstOrDefaultAsync(); + + JobOperationsFetchInfo o = new JobOperationsFetchInfo(); + if (mostRecentLogItem != null) + o.LastAction = mostRecentLogItem.Created; + else + o.LastAction = i.Created; + + o.Created = i.Created; + o.GId = i.GId; + o.JobStatus = i.JobStatus.ToString(); + o.Name = i.Name; + ret.Add(o); + } + + return ret; + } + + + + //Get list of logs for job + internal async Task> GetJobLogListAsync(Guid jobId) + { + List ret = new List(); + + var l = await ct.OpsJobLog + .Where(z => z.JobId == jobId) + .OrderBy(z => z.Created) + .ToListAsync(); + + foreach (OpsJobLog i in l) + { + JobOperationsLogInfoItem o = new JobOperationsLogInfoItem(); + o.Created = i.Created; + o.StatusText = i.StatusText; + o.JobId = jobId; + ret.Add(o); + } + + return ret; + } + + + /////////////////////////////////////////////////////////////////////////////// + //GET LOG OF ALL JOBS FOR CLIENT + // + internal async Task> GetAllJobsLogsListAsync() + { + List ret = new List(); + var l = await ct.OpsJobLog + .OrderByDescending(z => z.Created) + .ToListAsync(); + foreach (OpsJobLog i in l) + { + JobOperationsLogInfoItem o = new JobOperationsLogInfoItem(); + o.Created = i.Created; + o.StatusText = i.StatusText; + o.JobId = i.JobId; + ret.Add(o); + } + return ret; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + public async Task HandleJobAsync(OpsJob job) + { + //Hand off the particular job to the corresponding processing code + //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so + //basically any error condition during job processing should throw up an exception if it can't be handled + switch (job.JobType) + { + + case JobType.TestJob: + await ProcessTestJobAsync(job); + break; + default: + throw new System.ArgumentOutOfRangeException($"JobOperationsBiz.HandleJob-> Invalid job type{job.JobType.ToString()}"); + } + } + + private async Task ProcessTestJobAsync(OpsJob job) + { + var sleepTime = 30 * 1000; + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); + await JobsBiz.LogJobAsync(job.GId, $"LT:StartJob"); + await Task.Delay(sleepTime); + await JobsBiz.LogJobAsync(job.GId, "LT:JobCompleted"); + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed); + } + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/JobStatus.cs b/server/biz/JobStatus.cs new file mode 100644 index 0000000..c86c8a8 --- /dev/null +++ b/server/biz/JobStatus.cs @@ -0,0 +1,19 @@ +namespace Sockeye.Biz +{ + + + /// + /// Job status for opsjobs + /// + public enum JobStatus : int + { + Absent = 0, + Sleeping = 1, + Running = 2, + Completed = 3, + Failed = 4, + CancelRequested = 5 + } + + +}//eons \ No newline at end of file diff --git a/server/biz/JobType.cs b/server/biz/JobType.cs new file mode 100644 index 0000000..f5cded5 --- /dev/null +++ b/server/biz/JobType.cs @@ -0,0 +1,37 @@ +namespace Sockeye.Biz +{ + + + /// + /// All Sockeye Job types, used by OpsJob and biz objects for long running processes + /// + public enum JobType : int + { + NotSet = 0, + TestJob = 1,//test job for unit and OPS admin testing + CoreJobSweeper = 2, + SeedTestData = 4, + BatchCoreObjectOperation = 5, + Backup = 6, + AttachmentMaintenance = 7, + RenderReport=8, + ExportData=9 + + } + + /// + /// SubTypes for jobs that have further distinctions + /// + public enum JobSubType : int + { + NotSet = 0, + TagAdd = 1, + TagAddAny = 2, + TagRemove = 3, + TagRemoveAny = 4, + TagReplace = 5, + TagReplaceAny = 6, + Delete = 7 + } + +}//eons \ No newline at end of file diff --git a/server/biz/JobsBiz.cs b/server/biz/JobsBiz.cs new file mode 100644 index 0000000..1ab7846 --- /dev/null +++ b/server/biz/JobsBiz.cs @@ -0,0 +1,412 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; + + +namespace Sockeye.Biz +{ + + + internal static class JobsBiz + { + private static ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("JobsBiz"); + + #region JOB OPS + + + /// + /// Get a non tracked list of jobs that are ready to process and exclusive only + /// + /// + internal static async Task> GetReadyJobsExclusiveOnlyAsync() + { + return await GetReadyJobsAsync(true); + } + + + /// + /// Get a non tracked list of jobs that are ready to process and exclusive only + /// + /// + internal static async Task> GetReadyJobsNotExlusiveOnlyAsync() + { + return await GetReadyJobsAsync(false); + } + + + /// + /// Get a non tracked list of jobs filtered by exclusivity + /// + /// + private static async Task> GetReadyJobsAsync(bool exclusiveOnly) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + var ret = await ct.OpsJob + .AsNoTracking() + .Where(z => z.StartAfter < System.DateTime.UtcNow && z.Exclusive == exclusiveOnly && z.JobStatus == JobStatus.Sleeping) + .OrderBy(z => z.Created) + .ToListAsync(); + return ret; + } + } + + + /// + /// Add a new job to the database + /// + /// + internal static async Task AddJobAsync(OpsJob newJob) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + log.LogDebug($"Adding new job:{newJob.ToString()}"); + await LogJobAsync(newJob.GId, $"LT:JobCreated \"{newJob.Name}\""); + await ct.OpsJob.AddAsync(newJob); + await ct.SaveChangesAsync(); + } + } + + /// + /// Request the cancellation of a job, not all jobs honour this + /// + /// + internal static async Task RequestCancelAsync(Guid jobId) + { + await UpdateJobStatusAsync(jobId, JobStatus.CancelRequested); + await LogJobAsync(jobId, "LT:Cancel"); + } + + + /// + /// Remove the job and it's logs + /// + /// + internal static async Task RemoveJobAndLogsAsync(Guid jobIdToBeDeleted) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + try + { + log.LogDebug($"RemoveJobAndLogs for job id:{jobIdToBeDeleted}"); + //delete logs + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from aopsjoblog where jobid = {jobIdToBeDeleted}"); + //delete the job + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from aopsjob where gid = {jobIdToBeDeleted}"); + // Commit transaction if all commands succeed, transaction will auto-rollback + // when disposed if either commands fails + await transaction.CommitAsync(); + } + catch + { + throw; + } + } + } + + + + /// + /// Make a log entry for a job + /// + /// (NOTE: Guid.empty indicates internal job) + /// + internal static async Task LogJobAsync(Guid jobId, string statusText) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + if (string.IsNullOrWhiteSpace(statusText)) + statusText = "No status provided"; + OpsJobLog newObj = new OpsJobLog(); + newObj.JobId = jobId; + newObj.StatusText = statusText; + await ct.OpsJobLog.AddAsync(newObj); + await ct.SaveChangesAsync(); + } + } + + + /// + /// Update the status of a job + /// + /// + /// + internal static async Task UpdateJobStatusAsync(Guid jobId, JobStatus newStatus) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + var oFromDb = await ct.OpsJob.SingleOrDefaultAsync(z => z.GId == jobId); + if (oFromDb == null) return; + oFromDb.JobStatus = newStatus; + await ct.SaveChangesAsync(); + } + } + + /// + /// Get the status of a job + /// + /// + internal static async Task GetJobStatusAsync(Guid jobId) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + var o = await ct.OpsJob.AsNoTracking().SingleOrDefaultAsync(z => z.GId == jobId); + if (o == null) return JobStatus.Absent; + return o.JobStatus; + } + } + + + /// + /// Update the progress of a job + /// + /// + /// + internal static async Task UpdateJobProgressAsync(Guid jobId, string progress) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + var oFromDb = await ct.OpsJob.SingleOrDefaultAsync(z => z.GId == jobId); + if (oFromDb == null) return; + oFromDb.Progress = progress; + await ct.SaveChangesAsync(); + } + } + + /// + /// Get the progress and status of a job + /// + /// + internal static async Task GetJobProgressAsync(Guid jobId) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + var o = await ct.OpsJob.AsNoTracking().SingleOrDefaultAsync(z => z.GId == jobId); + if (o == null) return new JobProgress() { JobStatus = JobStatus.Absent, Progress = string.Empty }; + return new JobProgress() { JobStatus = o.JobStatus, Progress = o.Progress }; + } + } + #endregion Job ops + + #region PROCESSOR + + internal static bool KeepOnWorking() + { + ApiServerState serverState = ServiceProviderProvider.ServerState; + + //system lock (no license) is a complete deal breaker for continuation beyond here + if (serverState.IsSystemLocked) return false; + + + return true; + } + + + static bool ActivelyProcessing = false; + /// + /// Process all jobs (stock jobs and those found in operations table) + /// + /// + internal static async Task ProcessJobsAsync() + { + if (ActivelyProcessing) + { + //System.Diagnostics.Debug.WriteLine("ProcessJobs called but actively processing other jobs so returning"); + //log.LogTrace("ProcessJobs called but actively processing other jobs so returning"); + return; + } + + //Do not process if there is no db, everything relies on it below here + if (!ServerGlobalOpsSettingsCache.DBAVAILABLE) + { + //This will set dbavailable flag if it becomes available + DbUtil.CheckDatabaseServerAvailable(log); + return; + } + + ActivelyProcessing = true; + log.LogTrace("Processing internal jobs"); + try + { + log.LogTrace("Processing level 1 internal jobs"); + + //###################################################################################### + //### Critical internal jobs + + //METRICS + CoreJobMetricsSnapshot.DoWork(); + + //###################################################################################### + //## JOBS that will not run in a license or import mode or other system lock scenario from here down + + if (!KeepOnWorking()) return; + log.LogTrace("Processing level 2 internal jobs"); + + //BACKUP + await CoreJobBackup.DoWorkAsync(); + if (!KeepOnWorking()) return; + + //NOTIFICATIONS + await CoreJobNotify.DoWorkAsync(); + if (!KeepOnWorking()) return; + + await CoreNotificationSweeper.DoWorkAsync(); + if (!KeepOnWorking()) return; + + + + //JOB SWEEPER / AND USER COUNT CHECK + await CoreJobSweeper.DoWorkAsync(); + if (!KeepOnWorking()) return; + + //Cleanup temp folder + CoreJobTempFolderCleanup.DoWork(); + if (!KeepOnWorking()) return; + + //Check for and kill stuck report rendering engine processes + await CoreJobReportRenderEngineProcessCleanup.DoWork(); + if (!KeepOnWorking()) return; + + //CUSTOMER NOTIFICATIONS + TaskUtil.Forget(Task.Run(() => CoreJobCustomerNotify.DoWorkAsync()));//must fire and forget as it will call a report render job. In fact probably all of these can be fire and forget + + //INTEGRATION LOG SWEEP + await CoreIntegrationLogSweeper.DoWorkAsync(); + if (!KeepOnWorking()) return; + + + log.LogTrace("Processing exclusive dynamic jobs"); + + //BIZOBJECT DYNAMIC JOBS + //get a list of exclusive jobs that are due to happen + //Call into each item in turn + List exclusiveJobs = await GetReadyJobsExclusiveOnlyAsync(); + foreach (OpsJob j in exclusiveJobs) + { + if (!KeepOnWorking()) return; + try + { + await ProcessJobAsync(j); + } + catch (Exception ex) + { + log.LogError(ex, $"ProcessJobs::Exclusive -> job {j.Name} failed with exception"); + await LogJobAsync(j.GId, "LT:JobFailed"); + await LogJobAsync(j.GId, ExceptionUtil.ExtractAllExceptionMessages(ex)); + await UpdateJobStatusAsync(j.GId, JobStatus.Failed); + } + } + + //### Server state dependent jobs + ApiServerState serverState = ServiceProviderProvider.ServerState; + + //### API Open only jobs + if (!serverState.IsOpen) + { + log.LogDebug("Server state is NOT open, skipping processing non-exclusive dynamic jobs"); + return; + } + + /////////////////////////////////////// + //NON-EXCLUSIVE JOBS + // + log.LogTrace("Processing non-exclusive dynamic jobs"); + if (!KeepOnWorking()) return; + //These fire and forget but use a technique to bubble up exceptions anyway + List sharedJobs = await GetReadyJobsNotExlusiveOnlyAsync(); + foreach (OpsJob j in sharedJobs) + { + if (!KeepOnWorking()) return; + try + { + //System.Diagnostics.Debug.WriteLine($"JobsBiz processing NON-exclusive biz job {j.Name}"); + TaskUtil.Forget(Task.Run(() => ProcessJobAsync(j))); + } + catch (Exception ex) + { + log.LogError(ex, $"ProcessJobs::Shared -> job {j.Name} failed with exception"); + await LogJobAsync(j.GId, "LT:JobFailed"); + await LogJobAsync(j.GId, ExceptionUtil.ExtractAllExceptionMessages(ex)); + await UpdateJobStatusAsync(j.GId, JobStatus.Failed); + } + } + } + catch (Exception ex) + { + var msg = "Server::ProcessJobsAsync unexpected error during processing"; + log.LogError(ex, msg); + DbUtil.HandleIfDatabaseUnavailableTypeException(ex); + await NotifyEventHelper.AddOpsProblemEvent(msg, ex); + } + finally + { + ActivelyProcessing = false; + //System.Diagnostics.Debug.WriteLine($"JobsBiz in Finally - completed run"); + } + + } + + /// + /// Process a job by calling into it's biz object + /// + /// + /// + internal static async Task ProcessJobAsync(OpsJob job) + { + var JobDescription = $"{job.Name} - {job.JobType.ToString()}"; + if (job.SubType != JobSubType.NotSet) + JobDescription += $":{job.SubType}"; + await LogJobAsync(job.GId, $"LT:ProcessingJob \"{JobDescription}\""); + log.LogDebug($"ProcessJobAsync -> Processing job {JobDescription}"); + IJobObject o = null; + using (AyContext ct = ServiceProviderProvider.DBContext) + { + switch (job.JobType) + { + case JobType.Backup: + //This is called when on demand only, normal backups are processed above with normal system jobs + await CoreJobBackup.DoWorkAsync(true); + await UpdateJobStatusAsync(job.GId, JobStatus.Completed); + break; + case JobType.TestJob: + o = (IJobObject)BizObjectFactory.GetBizObject(SockType.ServerJob, ct, 1, AuthorizationRoles.BizAdmin); + break; + + case JobType.AttachmentMaintenance: + o = (IJobObject)BizObjectFactory.GetBizObject(SockType.FileAttachment, ct, 1, AuthorizationRoles.BizAdmin); + break; + case JobType.BatchCoreObjectOperation: + //batch op, hand off to biz object to deal with + //note, convention is that there is an idList in job.jobinfo json if preselected else it's all objects of type + o = (IJobObject)BizObjectFactory.GetBizObject(job.SockType, ct, 1, AuthorizationRoles.BizAdmin); + break; + case JobType.RenderReport: + o = (IJobObject)BizObjectFactory.GetBizObject(SockType.Report, ct, 1, AuthorizationRoles.BizAdmin); + break; + default: + throw new System.NotSupportedException($"ProcessJobAsync type {job.JobType.ToString()} is not supported"); + } + + if (o != null) + await o.HandleJobAsync(job); + } + log.LogDebug($"ProcessJobAsync -> Job completed {JobDescription}"); + } + + + #endregion process jobs + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/MemoBiz.cs b/server/biz/MemoBiz.cs new file mode 100644 index 0000000..603b4cc --- /dev/null +++ b/server/biz/MemoBiz.cs @@ -0,0 +1,511 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Linq; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Sockeye.Biz +{ + internal class MemoBiz : BizObject, IJobObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, IImportAbleObject, INotifiableObject + { + internal MemoBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.Memo; + } + + internal static MemoBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new MemoBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new MemoBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Memo.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(Memo newObject) + { + + newObject.Viewed = false;//default, it's new and not viewed yet but could have been set from a prior forward / reply as it's source + newObject.Replied = false;//'' + await ValidateAsync(newObject);//a bit different, can't update a memo so only need to worry about new objects + if (HasErrors) + return null; + else + { + newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.Memo.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + await SearchIndexAsync(newObject, true); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + await HandlePotentialNotificationEvent(SockEvent.Created, newObject); + return newObject; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.Memo.AsNoTracking().SingleOrDefaultAsync(m => m.Id == id && m.ToId == UserId);//## SECURITY, if need general purpose then make new method + if (logTheGetEvent && ret != null) + { + //Hijack this method to also flag as read + //batch ops will get but will set logthegetevent as false which is helpful for also setting read or not + if (!ret.Viewed) + { + //FLAG VIEWED + //refetch with tracking + ret = await ct.Memo.SingleOrDefaultAsync(m => m.Id == id && m.ToId == UserId); + ret.Viewed = true; + await ct.SaveChangesAsync(); + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, SockEvent.Retrieved), ct); + } + return ret; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE - ## NOTE: only internally, not exposed to controller route only here for batch ops + // + // + internal async Task PutAsync(Memo putObject) + { + var dbObject = await GetAsync(putObject.Id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + + putObject.Tags = TagBiz.NormalizeTags(putObject.Tags); + putObject.CustomFields = JsonUtil.CompactJson(putObject.CustomFields); + + + ct.Replace(dbObject, putObject); + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + await SearchIndexAsync(putObject, false); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags); + await HandlePotentialNotificationEvent(SockEvent.Modified, putObject, dbObject); + return putObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + var dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return false; + } + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + { + var IDList = await ct.Review.AsNoTracking().Where(x => x.SockType == SockType.Memo && x.ObjectId == id).Select(x => x.Id).ToListAsync(); + if (IDList.Count() > 0) + { + ReviewBiz b = new ReviewBiz(ct, UserId, UserTranslationId, CurrentUserRoles); + foreach (long ItemId in IDList) + if (!await b.DeleteAsync(ItemId, transaction)) + { + AddError(ApiErrorCode.CHILD_OBJECT_ERROR, null, $"Review [{ItemId}]: {b.GetErrorsAsString()}"); + return false; + } + } + } + ct.Memo.Remove(dbObject); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct); + await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + + await transaction.CommitAsync(); + await HandlePotentialNotificationEvent(SockEvent.Deleted, dbObject); + + return true; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // + private async Task SearchIndexAsync(Memo obj, bool isNew) + { + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + DigestSearchText(obj, SearchParams); + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id, SockType specificType) + { + var obj = await GetAsync(id, false); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + DigestSearchText(obj, SearchParams); + return SearchParams; + } + + public void DigestSearchText(Memo obj, Search.SearchIndexProcessObjectParameters searchParams) + { + if (obj != null) + searchParams.AddText(obj.Notes) + .AddText(obj.Name) + .AddText(obj.Wiki) + .AddText(obj.Tags) + .AddCustomFields(obj.CustomFields); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + private async Task ValidateAsync(Memo proposedObj) + { + + + + //Only can send a memo from your own account + //with bypass for import if superuser + if (proposedObj.FromId != UserId && UserId != 1) + { + AddError(ApiErrorCode.NOT_AUTHORIZED, "FromId", "No impersonation"); + return;//no need to bother with any other validation, this is not allowed + } + + //valid TO ID? + if (!await ct.User.AnyAsync(m => m.Id == proposedObj.ToId)) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "ToId"); + return;//no need to bother with any other validation + } + + //Name ("subject") is still required for a memo, empty subject not valid + //also, subject was required by biz rule in v7 so no need to worry about that on migrate + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + + + //Name does *NOT* need to be unique because for memo it's actually the subject line + //just kept internal naming the same to make coding easier with less workarounds + + // //If name is otherwise OK, check that name is unique + // if (!PropertyHasErrors("Name")) + // { + // //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + // if (await ct.Memo.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id)) + // { + // AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + // } + // } + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == SockType.Memo.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + private void ValidateCanDelete(Memo inObj) + { + //whatever needs to be check to delete this object + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //REPORTING + // + public async Task GetReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + var idList = dataListSelectedRequest.SelectedRowIds; + JArray ReportData = new JArray(); + while (idList.Any()) + { + var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE); + idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray(); + //query for this batch, comes back in db natural order unfortunately + var batchResults = await ct.Memo.AsNoTracking().Where(z => batch.Contains(z.Id)).ToArrayAsync(); + //order the results back into original + var orderedList = from id in batch join z in batchResults on id equals z.Id select z; + batchResults = null; + foreach (Memo w in orderedList) + { + if (!ReportRenderManager.KeepGoing(jobId)) return null; + await PopulateVizFields(w); + var jo = JObject.FromObject(w); + if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"])) + jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]); + ReportData.Add(jo); + } + orderedList = null; + } + vc.Clear(); + return ReportData; + } + private VizCache vc = new VizCache(); + + //populate viz fields from provided object + private async Task PopulateVizFields(Memo o) + { + if (o.ToId != null) + { + if (!vc.Has("user", o.ToId)) + vc.Add(await ct.User.AsNoTracking().Where(x => x.Id == o.ToId).Select(x => x.Name).FirstOrDefaultAsync(), "user", o.ToId); + o.ToViz = vc.Get("user", o.ToId); + } + + if (o.FromId != null) + { + if (!vc.Has("user", o.FromId)) + vc.Add(await ct.User.AsNoTracking().Where(x => x.Id == o.FromId).Select(x => x.Name).FirstOrDefaultAsync(), "user", o.FromId); + o.FromViz = vc.Get("user", o.FromId); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + // IMPORT EXPORT + // + + public async Task GetExportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + //for now just re-use the report data code + //this may turn out to be the pattern for most biz object types but keeping it seperate allows for custom usage from time to time + return await GetReportData(dataListSelectedRequest, jobId); + } + + public async Task> ImportData(AyImportData importData) + { + List ImportResult = new List(); + string ImportTag = $"imported-{FileUtil.GetSafeDateFileName()}"; + + var jsset = JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = new Sockeye.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "CustomFields" }) }); + foreach (JObject j in importData.Data) + { + var w = j.ToObject(jsset); + if (j["CustomFields"] != null) + w.CustomFields = j["CustomFields"].ToString(); + w.Tags.Add(ImportTag);//so user can find them all and revert later if necessary + var res = await CreateAsync(w); + if (res == null) + { + ImportResult.Add($"* {w.Name} - {this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"{w.Name} - ok"); + } + } + return ImportResult; + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + public async Task HandleJobAsync(OpsJob job) + { + //Hand off the particular job to the corresponding processing code + //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so + //basically any error condition during job processing should throw up an exception if it can't be handled + switch (job.JobType) + { + case JobType.BatchCoreObjectOperation: + await ProcessBatchJobAsync(job); + break; + default: + throw new System.ArgumentOutOfRangeException($"MemoBiz.HandleJob-> Invalid job type{job.JobType.ToString()}"); + } + } + + + + private async Task ProcessBatchJobAsync(OpsJob job) + { + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); + await JobsBiz.LogJobAsync(job.GId, $"LT:StartJob {job.SubType}"); + List idList = new List(); + long FailedObjectCount = 0; + JObject jobData = JObject.Parse(job.JobInfo); + if (jobData.ContainsKey("idList")) + idList = ((JArray)jobData["idList"]).ToObject>(); + else + idList = await ct.Memo.AsNoTracking().Select(z => z.Id).ToListAsync(); + bool SaveIt = false; + //--------------------------------- + //case 4192 + TimeSpan ProgressAndCancelCheckSpan = new TimeSpan(0, 0, ServerBootConfig.JOB_PROGRESS_UPDATE_AND_CANCEL_CHECK_SECONDS); + DateTime LastProgressCheck = DateTime.UtcNow.Subtract(new TimeSpan(1, 1, 1, 1, 1)); + var TotalRecords = idList.LongCount(); + long CurrentRecord = -1; + //--------------------------------- + foreach (long id in idList) + { + try + { + //-------------------------------- + //case 4192 + //Update progress / cancel requested? + CurrentRecord++; + if (DateUtil.IsAfterDuration(LastProgressCheck, ProgressAndCancelCheckSpan)) + { + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{CurrentRecord}/{TotalRecords}"); + if (await JobsBiz.GetJobStatusAsync(job.GId) == JobStatus.CancelRequested) + break; + LastProgressCheck = DateTime.UtcNow; + } + //--------------------------------- + + SaveIt = false; + ClearErrors(); + Memo o = null; + //save a fetch if it's a delete + if (job.SubType != JobSubType.Delete) + o = await GetAsync(id, false); + switch (job.SubType) + { + case JobSubType.TagAddAny: + case JobSubType.TagAdd: + case JobSubType.TagRemoveAny: + case JobSubType.TagRemove: + case JobSubType.TagReplaceAny: + case JobSubType.TagReplace: + SaveIt = TagBiz.ProcessBatchTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); + break; + case JobSubType.Delete: + if (!await DeleteAsync(id)) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + break; + default: + throw new System.ArgumentOutOfRangeException($"ProcessBatchJobAsync -> Invalid job Subtype{job.SubType}"); + } + if (SaveIt) + { + o = await PutAsync(o); + if (o == null) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + } + + //delay so we're not tying up all the resources in a tight loop + await Task.Delay(Sockeye.Util.ServerBootConfig.JOB_OBJECT_HANDLE_BATCH_JOB_LOOP_DELAY); + } + catch (Exception ex) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors id({id})"); + await JobsBiz.LogJobAsync(job.GId, ExceptionUtil.ExtractAllExceptionMessages(ex)); + } + } + //--------------------------------- + //case 4192 + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{++CurrentRecord}/{TotalRecords}"); + //--------------------------------- + await JobsBiz.LogJobAsync(job.GId, $"LT:BatchJob {job.SubType} {idList.Count}{(FailedObjectCount > 0 ? " - LT:Failed " + FailedObjectCount : "")}"); + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed); + } + + + + + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // NOTIFICATION PROCESSING + // + public async Task HandlePotentialNotificationEvent(SockEvent ayaEvent, ICoreBizObjectModel proposedObj, ICoreBizObjectModel currentObj = null) + { + ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger(); + + log.LogDebug($"HandlePotentialNotificationEvent processing: [SockType:{this.BizType}, AyaEvent:{ayaEvent}]"); + + bool isNew = currentObj == null; + + + //STANDARD EVENTS FOR ALL OBJECTS + await NotifyEventHelper.ProcessStandardObjectEvents(ayaEvent, proposedObj, ct); + + //SPECIFIC EVENTS FOR THIS OBJECT + + }//end of process notifications + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/NotifyDeliveryMethod.cs b/server/biz/NotifyDeliveryMethod.cs new file mode 100644 index 0000000..841062f --- /dev/null +++ b/server/biz/NotifyDeliveryMethod.cs @@ -0,0 +1,20 @@ +namespace Sockeye.Biz +{ + + + /// + /// All Sockeye notification delivery methods + /// + /// + public enum NotifyDeliveryMethod : int + { + + App = 1,//deliver in app via notification system + SMTP = 2//deliver to an email address or other entity reachable via smtp such as sms from email etc + + //NEW ITEMS REQUIRE translation KEYS + + } + + +}//eons diff --git a/server/biz/NotifyEventHelper.cs b/server/biz/NotifyEventHelper.cs new file mode 100644 index 0000000..2808327 --- /dev/null +++ b/server/biz/NotifyEventHelper.cs @@ -0,0 +1,406 @@ +using System; +using System.Linq; +using System.Globalization; +using System.Text; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.EntityFrameworkCore; +using Sockeye.Util; +using Sockeye.Models; +//using System.Diagnostics; + + +namespace Sockeye.Biz +{ + + internal static class NotifyEventHelper + { + private static ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("NotifyEventProcessor"); + + + /////////////////////////////////////////// + // ENSURE USER HAS IN APP NOTIFICATION + // + // + public static async Task EnsureDefaultInAppUserNotificationSubscriptionExists(long userId, AyContext ct) + { + var defaultsub = await ct.NotifySubscription.FirstOrDefaultAsync(z => z.EventType == NotifyEventType.GeneralNotification && z.UserId == userId && z.DeliveryMethod == NotifyDeliveryMethod.App); + if (defaultsub == null) + { + //NOTE: agevalue and advanced notice settings here will ensure that direct in app notifications with a future delivery date ("deadman" switch deliveries) set in their + //notifyevent.eventdate will deliver on that date and not immediately to support all the things that are direct built in notifications for future dates + //such as for an overdue Review which doesn't have or need it's own notifyeventtype and subscription independently + + //NEW NOTE: above makes not sense, I'm setting these back to timespan zero + defaultsub = new NotifySubscription() + { + UserId = userId, + EventType = NotifyEventType.GeneralNotification, + DeliveryMethod = NotifyDeliveryMethod.App, + AgeValue = TimeSpan.Zero,//new TimeSpan(0, 0, 1), + AdvanceNotice = TimeSpan.Zero//new TimeSpan(0, 0, 1) + }; + await ct.NotifySubscription.AddAsync(defaultsub); + await ct.SaveChangesAsync(); + } + return; + } + + + ///////////////////////////////////////// + // PROCESS STANDARD EVENTS + // + // + public static async Task ProcessStandardObjectEvents(SockEvent ayaEvent, ICoreBizObjectModel newObject, AyContext ct) + { + switch (ayaEvent) + { + case SockEvent.Created: + await ProcessStandardObjectCreatedEvents(newObject, ct); + break; + case SockEvent.Deleted: + await ProcessStandardObjectDeletedEvents(newObject, ct); + break; + case SockEvent.Modified: + await ProcessStandardObjectModifiedEvents(newObject, ct); + break; + } + } + + ///////////////////////////////////////// + // PROCESS STANDARD CREATE NOTIFICATION + // + // + public static async Task ProcessStandardObjectCreatedEvents(ICoreBizObjectModel newObject, AyContext ct) + { + //CREATED SUBSCRIPTIONS + { + var subs = await ct.NotifySubscription.Where(z => z.EventType == NotifyEventType.ObjectCreated && z.SockType == newObject.SType).ToListAsync(); + foreach (var sub in subs) + { + //not for inactive users + if (!await UserBiz.UserIsActive(sub.UserId)) continue; + if (ObjectHasAllSubscriptionTags(newObject.Tags, sub.Tags)) + { + NotifyEvent n = new NotifyEvent() + { + EventType = NotifyEventType.ObjectCreated, + UserId = sub.UserId, + SockType = newObject.SType, + ObjectId = newObject.Id, + NotifySubscriptionId = sub.Id, + Name = newObject.Name + }; + await ct.NotifyEvent.AddAsync(n); + await ct.SaveChangesAsync(); + log.LogDebug($"Added NotifyEvent: [{n.ToString()}]"); + } + } + } + + + //AGE SUBSCRIPTIONS + { + var subs = await ct.NotifySubscription.Where(z => z.EventType == NotifyEventType.ObjectAge && z.SockType == newObject.SType).ToListAsync(); + foreach (var sub in subs) + { + //not for inactive users + if (!await UserBiz.UserIsActive(sub.UserId)) continue; + if (ObjectHasAllSubscriptionTags(newObject.Tags, sub.Tags)) + { + //Note: age is set by advance notice which is consulted by CoreJobNotify in it's run so the deliver date is not required here only the reference EventDate to check for deliver + //ObjectAge is determined by subscription AgeValue in combo with the EventDate NotifyEvent parameter which together determines at what age from notifyevent.EventDate it's considered for the event to have officially occured + //However delivery is determined by sub.advancenotice so all three values play a part + // + NotifyEvent n = new NotifyEvent() + { + EventType = NotifyEventType.ObjectAge, + UserId = sub.UserId, + SockType = newObject.SType, + ObjectId = newObject.Id, + NotifySubscriptionId = sub.Id, + Name = newObject.Name + }; + await ct.NotifyEvent.AddAsync(n); + await ct.SaveChangesAsync(); + log.LogDebug($"Added NotifyEvent: [{n.ToString()}]"); + } + } + } + + + } + + + /////////////////////////////////////////////// + // PROCESS STANDARD MODIFIED NOTIFICATION + // + // + public static async Task ProcessStandardObjectModifiedEvents(ICoreBizObjectModel newObject, AyContext ct) + { + { + var subs = await ct.NotifySubscription.Where(z => z.EventType == NotifyEventType.ObjectModified && z.SockType == newObject.SType).ToListAsync(); + foreach (var sub in subs) + { + //not for inactive users + if (!await UserBiz.UserIsActive(sub.UserId)) continue; + if (ObjectHasAllSubscriptionTags(newObject.Tags, sub.Tags)) + { + NotifyEvent n = new NotifyEvent() + { + EventType = NotifyEventType.ObjectModified, + UserId = sub.UserId, + SockType = newObject.SType, + ObjectId = newObject.Id, + NotifySubscriptionId = sub.Id, + Name = newObject.Name + }; + await ct.NotifyEvent.AddAsync(n); + await ct.SaveChangesAsync(); + log.LogDebug($"Added NotifyEvent: [{n.ToString()}]"); + } + } + } + + + } + + ///////////////////////////////////////// + // PROCESS STANDARD DELETE NOTIFICATION + // + // + public static async Task ProcessStandardObjectDeletedEvents(ICoreBizObjectModel bizObject, AyContext ct) + { + // It's gone and shouldn't have any events left for it + await ClearPriorEventsForObject(ct, bizObject.SType, bizObject.Id); + + + //------------------------------------------ + //ObjectDeleted notification + // + { + var subs = await ct.NotifySubscription.Where(z => z.EventType == NotifyEventType.ObjectDeleted && z.SockType == bizObject.SType).ToListAsync(); + foreach (var sub in subs) + { + //not for inactive users + if (!await UserBiz.UserIsActive(sub.UserId)) continue; + if (ObjectHasAllSubscriptionTags(bizObject.Tags, sub.Tags)) + { + //TODO: On deliver should point to history event log record or take from there and insert into delivery message? + NotifyEvent n = new NotifyEvent() + { + EventType = NotifyEventType.ObjectDeleted, + UserId = sub.UserId, + SockType = bizObject.SType, + ObjectId = bizObject.Id, + NotifySubscriptionId = sub.Id, + Name = bizObject.Name + }; + await ct.NotifyEvent.AddAsync(n); + await ct.SaveChangesAsync(); + log.LogDebug($"Added NotifyEvent: [{n.ToString()}]"); + } + } + } + } + + + + ////////////////////////////////// + // CLEAN OUT OLD EVENTS + // + // + //Any specific event created in objects biz code (not simply "Modified") + //should trigger this remove code prior to updates etc where it creates a new one + //particularly for future delivery ones but will catch the case of a quick double edit of an object that + //would alter what gets delivered in the notification and before it's sent out yet + public static async Task ClearPriorEventsForObject(AyContext ct, SockType sockType, long objectId, NotifyEventType eventType) + { + var eventsToDelete = await ct.NotifyEvent.Where(z => z.SockType == sockType && z.ObjectId == objectId && z.EventType == eventType).ToListAsync(); + if (eventsToDelete.Count == 0) return; + ct.NotifyEvent.RemoveRange(eventsToDelete); + await ct.SaveChangesAsync(); + } + + public static async Task ClearPriorCustomerNotifyEventsForObject(AyContext ct, SockType sockType, long objectId, NotifyEventType eventType) + { + var eventsToDelete = await ct.CustomerNotifyEvent.Where(z => z.SockType == sockType && z.ObjectId == objectId && z.EventType == eventType).ToListAsync(); + if (eventsToDelete.Count == 0) return; + ct.CustomerNotifyEvent.RemoveRange(eventsToDelete); + await ct.SaveChangesAsync(); + } + + //scorched earth one for outright delete of objects when you don't want any prior events left for it + //probably only ever used for the delete event, can't think of another one right now new years morning early 2021 a bit hungover but possibly there is :) + public static async Task ClearPriorEventsForObject(AyContext ct, SockType sockType, long objectId) + { + var eventsToDelete = await ct.NotifyEvent.Where(z => z.SockType == sockType && z.ObjectId == objectId).ToListAsync(); + if (eventsToDelete.Count == 0) return; + ct.NotifyEvent.RemoveRange(eventsToDelete); + await ct.SaveChangesAsync(); + } + + + ////////////////////////////////// + // COMPARE TAGS COLLECTION + // + // A match here means *all* tags in the subscription are present in the object + // + public static bool ObjectHasAllSubscriptionTags(List objectTags, List subTags) + { + //no subscription tags? Then it always will match + if (subTags.Count == 0) return true; + //have sub tags but object has none? Then it's never going to match + if (objectTags.Count == 0) return false; + + //not enought tags on object to match sub tags? + if (subTags.Count > objectTags.Count) return false; + + //Do ALL the tags in the subscription exist in the object? + return subTags.All(z => objectTags.Any(x => x == z)); + } + + ////////////////////////////////// + // COMPARE TAGS COLLECTION + // + // A match here means *all* tags are the same in both objects (don't have to be in same order) + // + public static bool TwoObjectsHaveSameTags(List firstObjectTags, List secondObjectTags) + { + //no tags on either side? + if (firstObjectTags.Count == 0 && secondObjectTags.Count == 0) return true; + + //different counts will always mean not a match + if (firstObjectTags.Count != secondObjectTags.Count) return false; + + //Do ALL the tags in the first object exist in the second object? + return firstObjectTags.All(z => secondObjectTags.Any(x => x == z)); + } + + ///////////////////////////////////////// + // CREATE OPS PROBLEM EVENT + // + // + internal static async Task AddOpsProblemEvent(string message, Exception ex = null) + { + if (string.IsNullOrWhiteSpace(message) && ex == null) + return; + + //Log as a backup in case there is no one to notify and also for the record and support + if (ex != null) + { + //actually, if there is an exception it's already logged anyway so don't re-log it here, just makes dupes + // log.LogError(ex, $"Ops problem notification: \"{message}\""); + message += $"\nException error: {ExceptionUtil.ExtractAllExceptionMessages(ex)}"; + } + else + log.LogWarning($"Ops problem notification: \"{message}\""); + + await AddGeneralNotifyEvent(NotifyEventType.ServerOperationsProblem, message, "OPS"); + } + + + ///////////////////////////////////////// + // CREATE GENERAL NOTIFY EVENT + // + // + + internal static async Task AddGeneralNotifyEvent(NotifyEventType eventType, string message, string name, Exception except = null, long userId = 0) + { + await AddGeneralNotifyEvent(SockType.NoType, 0, eventType, message, name, except, userId); + } + internal static async Task AddGeneralNotifyEvent(SockType sockType, long objectid, NotifyEventType eventType, string message, string name, Exception except = null, long userId = 0) + { + + //This handles general notification events not requiring a decision or tied to an object that are basically just a immediate message to the user + //e.g. ops problems, GeneralNotification, NotifyHealthCheck etc + //optional user id to send directly to them + log.LogDebug($"AddGeneralNotifyEvent processing: [type:{eventType}, userId:{userId}, message:{message}]"); +#if (DEBUG) + switch (eventType) + { + case NotifyEventType.BackupStatus: + case NotifyEventType.GeneralNotification: + case NotifyEventType.NotifyHealthCheck://created by job processor itself + case NotifyEventType.ServerOperationsProblem: + break; + default://this will likely be a development error, not a production error so no need to log etc + throw (new System.NotSupportedException($"NotifyEventProcessor:AddGeneralNotifyEvent - Type of event {eventType} is unexpected and not supported")); + } + + if (eventType != NotifyEventType.GeneralNotification && userId != 0) + { + throw (new System.NotSupportedException($"NotifyEventProcessor:AddGeneralNotifyEvent - event {eventType} was specified with user id {userId} which is unexpected and not supported")); + } +#endif + + try + { + using (AyContext ct = Sockeye.Util.ServiceProviderProvider.DBContext) + { + + //General notification goes to one specific user only + if (eventType == NotifyEventType.GeneralNotification) + { + if (userId == 0) + { + //this will likely be a development error, not a production error so no need to log etc + throw new System.ArgumentException("NotifyEventProcessor:AddGeneralNotifyEvent: GeneralNotification requires a user id but none was specified"); + } + //not for inactive users + if (!await UserBiz.UserIsActive(userId)) return; + + var UserName = await ct.User.AsNoTracking().Where(z => z.Id == userId).Select(z => z.Name).FirstOrDefaultAsync(); + + //if they don't have a regular inapp subscription create one now + await EnsureDefaultInAppUserNotificationSubscriptionExists(userId, ct); + + if (string.IsNullOrWhiteSpace(name)) + name = UserName; + + var gensubs = await ct.NotifySubscription.Where(z => z.EventType == NotifyEventType.GeneralNotification && z.UserId == userId).ToListAsync(); + foreach (var sub in gensubs) + { + NotifyEvent n = new NotifyEvent() { EventType = eventType, UserId = userId, Message = message, NotifySubscriptionId = sub.Id, Name = name, SockType = sockType, ObjectId = objectid }; + await ct.NotifyEvent.AddAsync(n); + } + if (gensubs.Count > 0) + await ct.SaveChangesAsync(); + return; + } + + + //check subscriptions for event and send accordingly to each user + var subs = await ct.NotifySubscription.Where(z => z.EventType == eventType).ToListAsync(); + + //append exception message if not null + if (except != null) + message += $"\nException error: {ExceptionUtil.ExtractAllExceptionMessages(except)}"; + + foreach (var sub in subs) + { + //not for inactive users + if (!await UserBiz.UserIsActive(sub.UserId)) continue; + //note flag ~SERVER~ means to client to substitute "Server" translation key text instead + NotifyEvent n = new NotifyEvent() { EventType = eventType, UserId = sub.UserId, Message = message, NotifySubscriptionId = sub.Id, Name = "~SERVER~", SockType = sockType, ObjectId = objectid }; + await ct.NotifyEvent.AddAsync(n); + } + if (subs.Count > 0) + await ct.SaveChangesAsync(); + } + + } + catch (Exception ex) + { + log.LogError(ex, $"Error adding general notify event [type:{eventType}, userId:{userId}, message:{message}]"); + DbUtil.HandleIfDatabaseUnavailableTypeException(ex); + } + + }//eom + + + + }//eoc + +}//eons diff --git a/server/biz/NotifyEventType.cs b/server/biz/NotifyEventType.cs new file mode 100644 index 0000000..5fee7a8 --- /dev/null +++ b/server/biz/NotifyEventType.cs @@ -0,0 +1,40 @@ +namespace Sockeye.Biz +{ +/* +Inspiring quotes used to help complete this huge project by myself + + “Accept the things to which fate binds you, and love the people with whom fate brings you together,but do so with all your heart.” + ― Marcus Aurelius, Meditations + +"Make it happen" + In response to me complaining about how hard this project is and how long it's taking. + - Jim Preiss July 28th 2021 +*/ + + /// + /// All Sockeye notification event types + /// + /// + public enum NotifyEventType : int + { + //see core-notifications.txt spec doc for a bit more info on each type (they are named a little bit differently) + //#### NOTE: once event is NOTED IN COMMENT (not necessarily coded yet as some can't be yet) in NotifyEventProcessor I'll mark it with a * in the comment so I know if I miss any + //#### NOTE: once event is fully coded I'll mark it with CODED at the start of the comment line + ObjectDeleted = 1,//* Deletion of any object of conditional specific SockType and optionally conditional tags + ObjectCreated = 2,//* creation of any object of conditional specific SockType and optionally conditional tags + ObjectModified = 3,//* Modification / update of any kind of any object of conditional specific SockType and optionally conditional tags + ObjectAge = 10,//* Any object, Age (conditional on AgeValue) after creation event of any object of conditional specific SockType and optionally conditional tags + ReminderImminent = 12,//*Reminder object, Advance notice setting tag conditional + NotifyHealthCheck = 19,//* NO OBJECT, direct subscription to receive recurring daily notify system "ping" sent out between 8am and 10am once every 24 hours minimum every day server local time + BackupStatus = 20,//* NO OBJECT, direct subscription to receive results of last backup operation + GeneralNotification = 27,//* NO OBJECT old quick notification, refers now to any direct text notification internal or user to user used for system notifications (default delivers in app but user can opt to also get email) + ServerOperationsProblem = 28,//* NO OBJECT and serious issue with server operations requiring intervention, + ReviewImminent = 34,//*Review object, Advance notice setting tag conditional + DirectSMTPMessage= 35 //Used internally when sending a message via email directly to a Customer (initially) and possibly other objects in future. Shows in sent log but not user subscribable + + //NEW ITEMS REQUIRE translation KEYS + + } + + +}//eons diff --git a/server/biz/NotifyMailSecurity.cs b/server/biz/NotifyMailSecurity.cs new file mode 100644 index 0000000..ffdc1bc --- /dev/null +++ b/server/biz/NotifyMailSecurity.cs @@ -0,0 +1,21 @@ +namespace Sockeye.Biz +{ + + + /// + /// All Sockeye smtp notification connection security + /// + /// + public enum NotifyMailSecurity : int + { + + None = 0, + StartTls = 1, + SSLTLS=3 + + //NEW ITEMS REQUIRE translation KEYS + + } + + +}//eons diff --git a/server/biz/NotifySubscriptionBiz.cs b/server/biz/NotifySubscriptionBiz.cs new file mode 100644 index 0000000..a5a6e6e --- /dev/null +++ b/server/biz/NotifySubscriptionBiz.cs @@ -0,0 +1,256 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using System.Linq; + +namespace Sockeye.Biz +{ + internal class NotifySubscriptionBiz : BizObject//, IJobObject, ISearchAbleObject + { + internal NotifySubscriptionBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.NotifySubscription; + } + + internal static NotifySubscriptionBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new NotifySubscriptionBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new NotifySubscriptionBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.NotifySubscription.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(NotifySubscription newObject) + { + await ValidateAsync(newObject); + if (HasErrors) + return null; + else + { + newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); + + await ct.NotifySubscription.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + return newObject; + } + } + + // //////////////////////////////////////////////////////////////////////////////////////////////// + // //DUPLICATE + // // + // internal async Task DuplicateAsync(long id) + // { + + // var dbObject = await GetAsync(id, false); + // if (dbObject == null) + // { + // AddError(ApiErrorCode.NOT_FOUND, "id"); + // return null; + // } + // NotifySubscription newObject = new NotifySubscription(); + // CopyObject.Copy(dbObject, newObject); + + // newObject.Id = 0; + // newObject.Concurrency = 0; + // await ct.NotifySubscription.AddAsync(newObject); + // await ct.SaveChangesAsync(); + // await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct); + // //await SearchIndexAsync(newObject, true); + // await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + // return newObject; + // } + + //////////////////////////////////////////////////////////////////////////////////////////////// + // GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.NotifySubscription.AsNoTracking().SingleOrDefaultAsync(z => z.Id == id && z.UserId == UserId); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, SockEvent.Retrieved), ct); + return ret; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(NotifySubscription putObject) + { + //TODO: Must remove all prior events and replace them + + var dbObject = await GetAsync(putObject.Id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + putObject.Tags = TagBiz.NormalizeTags(putObject.Tags); + await ValidateAsync(putObject); + if (HasErrors) return null; + ct.Replace(dbObject, putObject); + if (HasErrors) return null; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags); + return putObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long id) + { + + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + var dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return false; + } + //ValidateCanDelete(dbObject); + if (HasErrors) + return false; + { + var IDList = await ct.Review.AsNoTracking().Where(x => x.SockType == SockType.NotifySubscription && x.ObjectId == id).Select(x => x.Id).ToListAsync(); + if (IDList.Count() > 0) + { + ReviewBiz b = new ReviewBiz(ct, UserId, UserTranslationId, CurrentUserRoles); + foreach (long ItemId in IDList) + if (!await b.DeleteAsync(ItemId, transaction)) + { + AddError(ApiErrorCode.CHILD_OBJECT_ERROR, null, $"Review [{ItemId}]: {b.GetErrorsAsString()}"); + return false; + } + } + } + ct.NotifySubscription.Remove(dbObject); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.EventType.ToString(), ct); + // await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct); + await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + + //TODO: DELETE RELATED RECORDS HERE + + //all good do the commit + await transaction.CommitAsync(); + + return true; + } + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + private async Task ValidateAsync(NotifySubscription proposedObj) + { + + //############################################################################### + //todo: validate subscription is valid + //perhaps check if customer type user doesn't have non customer notification etc + + //todo: notifysubscriptionbiz Check for duplicate before accepting new / edit in validator + //DISALLOW entirely duplicate notifications (down to email address) + //USE NAME DUPE CHECK PATTERN BELOW + //############################################################################### + + //ensure user exists and need it for other shit later + var user = await ct.User.AsNoTracking().FirstOrDefaultAsync(z => z.Id == proposedObj.UserId); + if (user == null) + { + AddError(ApiErrorCode.VALIDATION_REQUIRED, "UserId"); + } + else + { + + // //Validate user can see this subscription type + // if (user.UserType == UserType.Customer || user.UserType == UserType.HeadOffice) + // { + // //Outside users can't choose inside user type notification events + // switch (proposedObj.EventType) + // { + // case NotifyEventType.CSRAccepted: + // case NotifyEventType.CSRRejected: + // case NotifyEventType.CustomerServiceImminent: + // case NotifyEventType.WorkorderCompleted: + // case NotifyEventType.WorkorderCreatedForCustomer: + // break; + // default: + // AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "EventType"); + // break; + // } + // } + // else + // { + // //Inside users can't use Outside (customer) users notification types + // switch (proposedObj.EventType) + // { + // case NotifyEventType.CSRAccepted: + // case NotifyEventType.CSRRejected: + // case NotifyEventType.CustomerServiceImminent: + // AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "EventType"); + // break; + // default: + // break; + // } + + // } + } + + if (proposedObj.DeliveryMethod == NotifyDeliveryMethod.App && !string.IsNullOrEmpty(proposedObj.DeliveryAddress)) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "DeliveryAddress", "In app delivery should not specify a delivery address"); + } + + + + + } + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/PickListBiz.cs b/server/biz/PickListBiz.cs new file mode 100644 index 0000000..9a5dbb5 --- /dev/null +++ b/server/biz/PickListBiz.cs @@ -0,0 +1,303 @@ +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json.Linq; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using Sockeye.PickList; + +namespace Sockeye.Biz +{ + + + internal class PickListBiz : BizObject + { + + internal PickListBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.PickListTemplate; + } + + internal static PickListBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new PickListBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new PickListBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + + //Get one + internal async Task GetAsync(SockType sockType, bool logTheGetEvent = true) + { + long lTypeId = (long)sockType; + + //first try to fetch from db + var ret = await ct.PickListTemplate.SingleOrDefaultAsync(z => z.Id == lTypeId); + if (logTheGetEvent && ret != null) + { + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, lTypeId, BizType, SockEvent.Retrieved), ct); + } + + //not in db then get the default + if (ret == null) + { + var PickList = PickListFactory.GetAyaPickList(sockType); + if (PickList != null) + { + ret = new PickListTemplate(); + ret.Id = lTypeId; + ret.Template = PickList.DefaultTemplate; + } + } + return ret; + } + + + + + //get picklist + internal async Task> GetPickListAsync(IAyaPickList PickList, string query, bool inactive, long[] preIds, string variant, ILogger log, string template) + { + + //Crack and validate the query part set a broken rule if not valid and return null + //else do the query + string TagSpecificQuery = null; + string AutoCompleteQuery = null; + + //Here need to handle scenario of badly formed query so user knows they did it wrong and doesn't just assume it's not there + //determine if this is a tag query and extract it + bool HasQuery = !string.IsNullOrWhiteSpace(query); + if (HasQuery) + { + AutoCompleteQuery = query; + //is it a dual template and tag query? + if (AutoCompleteQuery.Contains(" ")) + { + // split the query on space + var querySegments = AutoCompleteQuery.Split(' '); + if (querySegments.Length > 2) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "query", await Translate("ErrorPickListQueryInvalid")); + return null; + } + //check the two query segments, it's valid for the user to put the tag first or the template query first + //we handle either way, but if there are no tas in either then it's not a valid query + if (querySegments[0].Contains("..")) + { + TagSpecificQuery = querySegments[0].Replace("..", ""); + AutoCompleteQuery = querySegments[1]; + } + else if (querySegments[1].Contains("..")) + { + TagSpecificQuery = querySegments[1].Replace("..", ""); + AutoCompleteQuery = querySegments[0]; + } + else + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "query", await Translate("ErrorPickListQueryInvalid")); + return null; + } + + } + else + { + //is it a tag only query? + if (AutoCompleteQuery.Contains("..")) + { + TagSpecificQuery = AutoCompleteQuery.Replace("..", ""); + AutoCompleteQuery = null; + } + + } + } + + //Final fixup if user specifies tag query but there are not tags on this object then + //rather than error just accept it as a no tag query + //Note: it's not valid to have more than one field with tags in the picklist definition so this works + if (PickList.ColumnDefinitions.FirstOrDefault(z => z.ColumnDataType == UiFieldDataType.Tags) == null) + { + TagSpecificQuery = null; + } + + //Autocomplete and tagonly query terms now set for consumption by PickListFetcher, ready to fetch... + List items = await PickListFetcher.GetResponseAsync(PickList, AutoCompleteQuery, TagSpecificQuery, inactive, preIds, variant, ct, log, template); + return items; + } + + + //get picklist display for a single item + //used to populate UI with picklist format display for items + internal async Task GetTemplatedNameAsync(SockType sockType, long id, string variant, ILogger log, string template) + { + //short circuit for empty types + if (id == 0) + { + return string.Empty; + } + long[] preIds = { id }; + var PickList = PickListFactory.GetAyaPickList(sockType); + + if (log == null) + log = Sockeye.Util.ApplicationLogging.CreateLogger("PickListBiz::GetTemplatedNameAsync"); + + //Autocomplete and tagonly query terms now set for consumption by PickListFetcher, ready to fetch... + List items = await PickListFetcher.GetResponseAsync(PickList, null, null, true, preIds, variant, ct, log, template); + if (items.Count == 0) + { + return string.Empty; + } + return items[0].Name; + } + + + //get picklist templates, basically all the object types that support picklists + internal List GetListOfAllPickListTypes(long translationId) + { + return PickListFactory.GetListOfAllPickListTypes(translationId); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //put + internal async Task ReplaceAsync(PickListTemplate template) + { + var o = await ct.PickListTemplate.FirstOrDefaultAsync(z => z.Id == (long)template.Id); + bool bAdd = false; + if (o == null) + { + o = new PickListTemplate(); + bAdd = true; + } + o.Id = (long)template.Id; + o.Template = template.Template; + + + Validate(o); + if (HasErrors) + return false; + if (bAdd) + await ct.PickListTemplate.AddAsync(o); + + await ct.SaveChangesAsync(); + + //Log modification and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, o.Id, BizType, SockEvent.Modified), ct); + + + + return true; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE (return to default template) + // + internal async Task DeleteAsync(SockType sockType) + { + //REMOVE ANY RECORD WITH SAME AYATYPE ID + long lTypeId = (long)sockType; + + var o = await ct.PickListTemplate.FirstOrDefaultAsync(z => z.Id == lTypeId); + if (o != null) + { + ct.PickListTemplate.Remove(o); + await ct.SaveChangesAsync(); + //Event log process delete + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, lTypeId, sockType.ToString(), ct); + } + + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private void Validate(PickListTemplate inObj) + { + + //validate that the template is valid, the type is legit etc + var TemplateType = (SockType)inObj.Id; + if (!TemplateType.HasAttribute(typeof(CoreBizObjectAttribute))) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "sockType", "SockType specified is not a Core object type and doesn't support pick list templates"); + return; + } + + + if (string.IsNullOrWhiteSpace(inObj.Template)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Template"); + + var PickList = PickListFactory.GetAyaPickList(TemplateType); + + //Filter json must parse + //this is all automated normally so not going to do too much parsing here + //just ensure it's basically there + if (!string.IsNullOrWhiteSpace(inObj.Template)) + { + try + { + var v = JArray.Parse(inObj.Template); + for (int i = 0; i < v.Count; i++) + { + var filterItem = v[i]; + if (filterItem["fld"] == null) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Template", $"Template array item {i}, object is missing required \"fld\" property "); + else + { + var fld = filterItem["fld"].Value(); + if (string.IsNullOrWhiteSpace(fld)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Template", $"Template array item {i}, \"fld\" property is empty and required"); + + //validate the field name if we can + if (PickList != null) + { + + var TheField = PickList.ColumnDefinitions.SingleOrDefault(z => z.FieldKey.ToLowerInvariant() == fld.ToLowerInvariant()); + + if (TheField == null) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", $"Template array item {i}, fld property value \"{fld}\" is not a valid value for SockType specified"); + } + + } + } + + } + } + catch (Newtonsoft.Json.JsonReaderException ex) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", "Template is not valid JSON string: " + ex.Message); + + } + } + } + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/PrimeData.cs b/server/biz/PrimeData.cs new file mode 100644 index 0000000..739e8a6 --- /dev/null +++ b/server/biz/PrimeData.cs @@ -0,0 +1,147 @@ +using System.Threading.Tasks; +using System.IO; +using Newtonsoft.Json.Linq; +using Sockeye.Util; +using Sockeye.Models; + + +namespace Sockeye.Biz +{ + + //Prime the database with initial, minimum required data to boot and do things (SuperUser account, translations) + public static class PrimeData + { + + /// + /// Prime the database with SuperUser account + /// + public static async Task PrimeSuperUserAccount(AyContext ct) + { + //get a db and logger + //ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("PrimeData"); + User u = new User(); + u.Active = true; + u.AllowLogin=true; + u.Name = "Sockeye SuperUser"; + u.Salt = Hasher.GenerateSalt(); + u.Login = "ss"; + u.Password = Hasher.hash(u.Salt, "ss"); + u.Roles = AuthorizationRoles.Accounting | AuthorizationRoles.BizAdmin | AuthorizationRoles.Inventory | AuthorizationRoles.OpsAdmin | AuthorizationRoles.Sales | AuthorizationRoles.Service ; + + + u.UserType = UserType.NotService; + u.UserOptions = new UserOptions(); + u.UserOptions.TranslationId = ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID;//Ensure primeTranslations is called first + await ct.User.AddAsync(u); + await ct.SaveChangesAsync(); + + } + + + + // //used during development so I don't need to keep installing licenses over and over + // //just to refresh the translations + // public static async Task RePrimeTranslations() + // { + // //delete all the translations + // using (AyContext ct = ServiceProviderProvider.DBContext) + // { + // await ct.Database.ExecuteSqlRawAsync("delete from atranslationitem;"); + // await ct.Database.ExecuteSqlRawAsync("delete from atranslation;"); + // } + // //replace + // await PrimeTranslations(); + // } + + + /// + /// Prime the Translations + /// This may be called before there are any users on a fresh db boot + /// + public static async Task PrimeTranslations() + {// + + + //Read in each stock translation from a text file and then create them in the DB + var ResourceFolderPath = Path.Combine(ServerBootConfig.SOCKEYE_CONTENT_ROOT_PATH, "resource"); + if (!Directory.Exists(ResourceFolderPath)) + { + throw new System.Exception($"E1012: \"resource\" folder not found where expected: \"{ResourceFolderPath}\", installation damaged?"); + } + + + await ImportTranslation(ResourceFolderPath, "en"); + await ImportTranslation(ResourceFolderPath, "es"); + await ImportTranslation(ResourceFolderPath, "fr"); + await ImportTranslation(ResourceFolderPath, "de"); + + //Ensure Translations are present, not missing any keys and that there is a server default translation that exists + using (AyContext ct = ServiceProviderProvider.DBContext) + { + TranslationBiz lb = TranslationBiz.GetBiz(ct); + await lb.ValidateTranslationsAsync(); + } + + } + + private static async Task ImportTranslation(string resourceFolderPath, string translationCode) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + var TranslationPath = Path.Combine(resourceFolderPath, $"{translationCode}.json"); + if (!File.Exists(TranslationPath)) + { + throw new System.Exception($"E1013: stock translation file \"{translationCode}\" not found where expected: \"{TranslationPath}\", installation damaged?"); + } + + JObject o = JObject.Parse(await File.ReadAllTextAsync(TranslationPath)); + + Translation l = new Translation(); + l.Name = translationCode; + l.BaseLanguage=translationCode; + l.Stock = true; + l.CjkIndex = false; + + foreach (JToken t in o.Children()) + { + var key = t.Path; + var display = t.First.Value(); + l.TranslationItems.Add(new TranslationItem() { Key = key, Display = display }); + } + + await ct.Translation.AddAsync(l); + await ct.SaveChangesAsync(); + } + } + + + + /// + /// Prime the Report Templates + /// + public static async Task PrimeReportTemplates() + { + //Read in each stock translation from a text file and then create them in the DB + var ReportFilesPath = Path.Combine(ServerBootConfig.SOCKEYE_CONTENT_ROOT_PATH, "resource", "rpt", "stock-report-templates"); + if (!Directory.Exists(ReportFilesPath)) + { + throw new System.Exception($"E1012: \"stock-report-templates\" folder not found where expected: \"{ReportFilesPath}\", installation damaged?"); + } + + //iterate all ayrt files and import each one + using (AyContext ct = ServiceProviderProvider.DBContext) + { + ReportBiz r = ReportBiz.GetBiz(ct); + + System.IO.DirectoryInfo di = new DirectoryInfo(ReportFilesPath); + foreach (FileInfo file in di.EnumerateFiles()) + { + if (file.Extension.ToLowerInvariant() == ".ayrt") + await r.ImportAsync(JObject.Parse(await File.ReadAllTextAsync(file.FullName)), true); + } + } + } + + }//eoc + +}//eons \ No newline at end of file diff --git a/server/biz/ReminderBiz.cs b/server/biz/ReminderBiz.cs new file mode 100644 index 0000000..acb8d34 --- /dev/null +++ b/server/biz/ReminderBiz.cs @@ -0,0 +1,544 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Sockeye.Biz +{ + internal class ReminderBiz : BizObject, IJobObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, IImportAbleObject, INotifiableObject + { + internal ReminderBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.Reminder; + } + + internal static ReminderBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new ReminderBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new ReminderBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Reminder.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(Reminder newObject) + { + await ValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.Reminder.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + await SearchIndexAsync(newObject, true); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + await HandlePotentialNotificationEvent(SockEvent.Created, newObject); + return newObject; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.Reminder.AsNoTracking().SingleOrDefaultAsync(m => m.Id == id); + if (ret.UserId != UserId) + { + AddError(ApiErrorCode.NOT_AUTHORIZED, "generalerror", "A User may only retrieve their own reminders"); + return null; + } + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, SockEvent.Retrieved), ct); + return ret; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(Reminder putObject) + { + var dbObject = await GetAsync(putObject.Id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + + putObject.Tags = TagBiz.NormalizeTags(putObject.Tags); + putObject.CustomFields = JsonUtil.CompactJson(putObject.CustomFields); + await ValidateAsync(putObject, dbObject); + if (HasErrors) return null; + ct.Replace(dbObject, putObject); + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + await SearchIndexAsync(putObject, false); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags); + await HandlePotentialNotificationEvent(SockEvent.Modified, putObject, dbObject); + return putObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE schedule only + // + internal async Task PutNewScheduleTimeAsync(ScheduleItemAdjustParams p) + { + Reminder dbObject = await ct.Reminder.SingleOrDefaultAsync(z => z.Id == p.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return false; + } + + dbObject.StartDate = p.Start; + dbObject.StopDate = p.End; + + await ValidateAsync(dbObject, dbObject); + if (HasErrors) return false; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(dbObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return false; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, dbObject.SType, SockEvent.Modified), ct); + + await HandlePotentialNotificationEvent(SockEvent.Modified, dbObject, dbObject); + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + Reminder dbObject = await ct.Reminder.SingleOrDefaultAsync(m => m.Id == id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return false; + } + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + { + var IDList = await ct.Review.AsNoTracking().Where(x => x.SockType == SockType.Reminder && x.ObjectId == id).Select(x => x.Id).ToListAsync(); + if (IDList.Count() > 0) + { + ReviewBiz b = new ReviewBiz(ct, UserId, UserTranslationId, CurrentUserRoles); + foreach (long ItemId in IDList) + if (!await b.DeleteAsync(ItemId, transaction)) + { + AddError(ApiErrorCode.CHILD_OBJECT_ERROR, null, $"Review [{ItemId}]: {b.GetErrorsAsString()}"); + return false; + } + } + } + ct.Reminder.Remove(dbObject); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct); + await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); + await transaction.CommitAsync(); + await HandlePotentialNotificationEvent(SockEvent.Deleted, dbObject); + + return true; + } + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // + private async Task SearchIndexAsync(Reminder obj, bool isNew) + { + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + DigestSearchText(obj, SearchParams); + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id, SockType specificType) + { + var obj = await GetAsync(id, false); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + DigestSearchText(obj, SearchParams); + return SearchParams; + } + + public void DigestSearchText(Reminder obj, Search.SearchIndexProcessObjectParameters searchParams) + { + if (obj != null) + searchParams.AddText(obj.Notes) + .AddText(obj.Name) + .AddText(obj.Wiki) + .AddText(obj.Tags) + .AddCustomFields(obj.CustomFields); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + private async Task ValidateAsync(Reminder proposedObj, Reminder currentObj) + { + + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Hexadecimal notation: #RGB[A] R (red), G (green), B (blue), and A (alpha) are hexadecimal characters (0–9, A–F). A is optional. The three-digit notation (#RGB) is a shorter version of the six-digit form (#RRGGBB). For example, #f09 is the same color as #ff0099. Likewise, the four-digit RGB notation (#RGBA) is a shorter version of the eight-digit form (#RRGGBBAA). For example, #0f38 is the same color as #00ff3388. + if (proposedObj.Color.Length > 12 || proposedObj.Color.Length < 4 || proposedObj.Color[0] != '#') + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "UiColor", "UiColor must be valid HEX color value"); + } + + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == SockType.Reminder.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + private void ValidateCanDelete(Reminder inObj) + { + //whatever needs to be check to delete this object + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //REPORTING + // + public async Task GetReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + var idList = dataListSelectedRequest.SelectedRowIds; + JArray ReportData = new JArray(); + while (idList.Any()) + { + var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE); + idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray(); + //query for this batch, comes back in db natural order unfortunately + var batchResults = await ct.Reminder.AsNoTracking().Where(z => batch.Contains(z.Id)).ToArrayAsync(); + //order the results back into original + var orderedList = from id in batch join z in batchResults on id equals z.Id select z; + batchResults = null; + foreach (Reminder w in orderedList) + { + if (!ReportRenderManager.KeepGoing(jobId)) return null; + await PopulateVizFields(w); + var jo = JObject.FromObject(w); + if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"])) + jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]); + ReportData.Add(jo); + } + orderedList = null; + } + vc.Clear(); + return ReportData; + } + private VizCache vc = new VizCache(); + + //populate viz fields from provided object + private async Task PopulateVizFields(Reminder o) + { + if (!vc.Has("user", o.UserId)) + vc.Add(await ct.User.AsNoTracking().Where(x => x.Id == o.UserId).Select(x => x.Name).FirstOrDefaultAsync(), "user", o.UserId); + o.UserViz = vc.Get("user", o.UserId); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + // IMPORT EXPORT + // + + public async Task GetExportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + //for now just re-use the report data code + //this may turn out to be the pattern for most biz object types but keeping it seperate allows for custom usage from time to time + return await GetReportData(dataListSelectedRequest, jobId); + } + + public async Task> ImportData(AyImportData importData) + { + List ImportResult = new List(); + string ImportTag = $"imported-{FileUtil.GetSafeDateFileName()}"; + + var jsset = JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = new Sockeye.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "CustomFields" }) }); + foreach (JObject j in importData.Data) + { + var w = j.ToObject(jsset); + if (j["CustomFields"] != null) + w.CustomFields = j["CustomFields"].ToString(); + w.Tags.Add(ImportTag);//so user can find them all and revert later if necessary + var res = await CreateAsync(w); + if (res == null) + { + ImportResult.Add($"* {w.Name} - {this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"{w.Name} - ok"); + } + } + return ImportResult; + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + public async Task HandleJobAsync(OpsJob job) + { + //Hand off the particular job to the corresponding processing code + //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so + //basically any error condition during job processing should throw up an exception if it can't be handled + switch (job.JobType) + { + case JobType.BatchCoreObjectOperation: + await ProcessBatchJobAsync(job); + break; + default: + throw new System.ArgumentOutOfRangeException($"ReminderBiz.HandleJob-> Invalid job type{job.JobType.ToString()}"); + } + } + + + + private async Task ProcessBatchJobAsync(OpsJob job) + { + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); + await JobsBiz.LogJobAsync(job.GId, $"LT:StartJob {job.SubType}"); + List idList = new List(); + long FailedObjectCount = 0; + JObject jobData = JObject.Parse(job.JobInfo); + if (jobData.ContainsKey("idList")) + idList = ((JArray)jobData["idList"]).ToObject>(); + else + idList = await ct.Reminder.AsNoTracking().Select(z => z.Id).ToListAsync(); + bool SaveIt = false; + + //--------------------------------- + //case 4192 + TimeSpan ProgressAndCancelCheckSpan = new TimeSpan(0, 0, ServerBootConfig.JOB_PROGRESS_UPDATE_AND_CANCEL_CHECK_SECONDS); + DateTime LastProgressCheck = DateTime.UtcNow.Subtract(new TimeSpan(1, 1, 1, 1, 1)); + var TotalRecords = idList.LongCount(); + long CurrentRecord = -1; + //--------------------------------- + foreach (long id in idList) + { + try + { + //-------------------------------- + //case 4192 + //Update progress / cancel requested? + CurrentRecord++; + if (DateUtil.IsAfterDuration(LastProgressCheck, ProgressAndCancelCheckSpan)) + { + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{CurrentRecord}/{TotalRecords}"); + if (await JobsBiz.GetJobStatusAsync(job.GId) == JobStatus.CancelRequested) + break; + LastProgressCheck = DateTime.UtcNow; + } + //--------------------------------- + SaveIt = false; + ClearErrors(); + Reminder o = null; + //save a fetch if it's a delete + if (job.SubType != JobSubType.Delete) + o = await GetAsync(id, false); + switch (job.SubType) + { + case JobSubType.TagAddAny: + case JobSubType.TagAdd: + case JobSubType.TagRemoveAny: + case JobSubType.TagRemove: + case JobSubType.TagReplaceAny: + case JobSubType.TagReplace: + SaveIt = TagBiz.ProcessBatchTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); + break; + case JobSubType.Delete: + if (!await DeleteAsync(id)) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + break; + default: + throw new System.ArgumentOutOfRangeException($"ProcessBatchJobAsync -> Invalid job Subtype{job.SubType}"); + } + if (SaveIt) + { + o = await PutAsync(o); + if (o == null) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + } + + //delay so we're not tying up all the resources in a tight loop + await Task.Delay(Sockeye.Util.ServerBootConfig.JOB_OBJECT_HANDLE_BATCH_JOB_LOOP_DELAY); + } + catch (Exception ex) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors id({id})"); + await JobsBiz.LogJobAsync(job.GId, ExceptionUtil.ExtractAllExceptionMessages(ex)); + } + } + + //--------------------------------- + //case 4192 + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{++CurrentRecord}/{TotalRecords}"); + //--------------------------------- + await JobsBiz.LogJobAsync(job.GId, $"LT:BatchJob {job.SubType} {idList.Count}{(FailedObjectCount > 0 ? " - LT:Failed " + FailedObjectCount : "")}"); + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed); + } + + + + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // NOTIFICATION PROCESSING + // + public async Task HandlePotentialNotificationEvent(SockEvent ayaEvent, ICoreBizObjectModel proposedObj, ICoreBizObjectModel currentObj = null) + { + ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger(); + + log.LogDebug($"HandlePotentialNotificationEvent processing: [SockType:{this.BizType}, AyaEvent:{ayaEvent}]"); + + bool isNew = currentObj == null; + + Reminder o = (Reminder)proposedObj; + + //STANDARD EVENTS FOR ALL OBJECTS + await NotifyEventHelper.ProcessStandardObjectEvents(ayaEvent, proposedObj, ct); + + //SPECIFIC EVENTS FOR THIS OBJECT + + //## DELETED EVENTS + + //any event added below needs to be removed, so + //just blanket remove any event for this object of eventtype that would be added below here + //do it regardless any time there's an update and then + //let this code below handle the refreshing addition that could have changes + await NotifyEventHelper.ClearPriorEventsForObject(ct, SockType.Reminder, o.Id, NotifyEventType.ReminderImminent); + + //## CREATED / MODIFIED EVENTS + if (ayaEvent == SockEvent.Created || ayaEvent == SockEvent.Modified) + { + //# REMINDER IMMINENT + { + + //notify users (time delayed) + var subs = await ct.NotifySubscription.Where(z => z.EventType == NotifyEventType.ReminderImminent).ToListAsync(); + foreach (var sub in subs) + { + //not for inactive users + if (!await UserBiz.UserIsActive(sub.UserId)) continue; + + //Tag match? (will be true if no sub tags so always safe to call this) + if (NotifyEventHelper.ObjectHasAllSubscriptionTags(o.Tags, sub.Tags)) + { + + NotifyEvent n = new NotifyEvent() + { + EventType = NotifyEventType.ReminderImminent, + UserId = sub.UserId, + SockType = o.SType, + ObjectId = o.Id, + NotifySubscriptionId = sub.Id, + Name = o.Name, + EventDate = o.StartDate + }; + await ct.NotifyEvent.AddAsync(n); + log.LogDebug($"Adding NotifyEvent: [{n.ToString()}]"); + await ct.SaveChangesAsync(); + } + } + + }//Reminder imminent event + + }//end of process notifications + } + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/ReportBiz.cs b/server/biz/ReportBiz.cs new file mode 100644 index 0000000..bea9a5d --- /dev/null +++ b/server/biz/ReportBiz.cs @@ -0,0 +1,900 @@ +using System.Threading.Tasks; +using System.Linq; +using System.IO; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using EnumsNET; +using PuppeteerSharp; +using Newtonsoft.Json.Linq; +using System; + +namespace Sockeye.Biz +{ + internal class ReportBiz : BizObject, IJobObject, ISearchAbleObject + { + internal ReportBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.Report; + } + + internal static ReportBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new ReportBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new ReportBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Report.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(Report newObject) + { + await ValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + await ct.Report.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + await SearchIndexAsync(newObject, true); + return newObject; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //IMPORT + // + internal async Task ImportAsync(JObject o, bool skipIfAlreadyPresent = false) + { + + //Report newObject = new Report(); + var newObject = o.ToObject(); + + + var proposedName = (string)o["Name"]; + string newUniqueName = proposedName; + bool NotUnique = true; + long l = 1; + do + { + NotUnique = await ct.Report.AnyAsync(z => z.Name == newUniqueName); + if (NotUnique) + { + if (!skipIfAlreadyPresent) + newUniqueName = Util.StringUtil.UniqueNameBuilder(proposedName, l++, 255); + else + { + return true; + } + } + + } while (NotUnique); + + newObject.Name = newUniqueName; + + await ValidateAsync(newObject, null); + if (HasErrors) return false; + await ct.Report.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + if (skipIfAlreadyPresent) + { + ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger(); + log.LogInformation($"Stock report '{proposedName}' imported"); + } + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.Report.AsNoTracking().SingleOrDefaultAsync(z => z.Id == id); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, SockEvent.Retrieved), ct); + return ret; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(Report putObject) + { + + var dbObject = await GetAsync(putObject.Id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + + + await ValidateAsync(putObject, dbObject); + if (HasErrors) return null; + ct.Replace(dbObject, putObject); + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + await SearchIndexAsync(putObject, false); + return putObject; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + var dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return false; + } + await ValidateCanDelete(dbObject); + if (HasErrors) + return false; + { + var IDList = await ct.Review.AsNoTracking().Where(x => x.SockType == SockType.Report && x.ObjectId == id).Select(x => x.Id).ToListAsync(); + if (IDList.Count() > 0) + { + ReviewBiz b = new ReviewBiz(ct, UserId, UserTranslationId, CurrentUserRoles); + foreach (long ItemId in IDList) + if (!await b.DeleteAsync(ItemId, transaction)) + { + AddError(ApiErrorCode.CHILD_OBJECT_ERROR, null, $"Review [{ItemId}]: {b.GetErrorsAsString()}"); + return false; + } + } + } + ct.Report.Remove(dbObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct); + await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); + await transaction.CommitAsync(); + return true; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET LIST + // + internal async Task> GetReportListAsync(SockType aType) + { + var rpts = await ct.Report.AsNoTracking().Where(z => z.SockType == aType && z.Active == true).Select(z => new { id = z.Id, name = z.Name, roles = z.Roles }).OrderBy(z => z.name).ToListAsync(); + var ret = new List(); + foreach (var item in rpts) + { + if (CurrentUserRoles.HasAnyFlags(item.roles)) + { + ret.Add(new NameIdItem() { Name = item.name, Id = item.id }); + } + } + return ret; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // + private async Task SearchIndexAsync(Report obj, bool isNew) + { + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + DigestSearchText(obj, SearchParams); + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id, SockType specificType) + { + var obj = await GetAsync(id, false); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + DigestSearchText(obj, SearchParams); + return SearchParams; + } + + public void DigestSearchText(Report obj, Search.SearchIndexProcessObjectParameters searchParams) + { + if (obj != null) + searchParams.AddText(obj.Notes) + .AddText(obj.Name) + .AddText(obj.Template) + .AddText(obj.Style) + .AddText(obj.JsPrerender) + .AddText(obj.JsHelpers) + .AddText(obj.HeaderTemplate) + .AddText(obj.FooterTemplate); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + private async Task ValidateAsync(Report proposedObj, Report currentObj) + { + + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + + + //If name is otherwise OK, check that name is unique + if (!PropertyHasErrors("Name")) + { + //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + //NOTE: unlike other objects reports can have the same name as long as the type differs + if (await ct.Report.AnyAsync(z => z.Name == proposedObj.Name && z.SockType == proposedObj.SockType && z.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + } + } + + } + + private async Task ValidateCanDelete(Report inObj) + { + //Referential integrity error + if (await ct.NotifySubscription.AnyAsync(z => z.LinkReportId == inObj.Id) == true) + { + //Note: errorbox will ensure it appears in the general errror box and not field specific + //the translation key is to indicate what the linked object is that is causing the error + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("NotifySubscription")); + } + + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //REPORT DATA + //Data fetched to return to report render or for designer for Client report design usage + + public async Task GetReportDataForReportDesigner(DataListSelectedRequest selectedRequest) + { + var log = Sockeye.Util.ApplicationLogging.CreateLogger("ReportBiz::GetReportDataForReportDesigner"); + AuthorizationRoles effectiveRoles = CurrentUserRoles; + + if (selectedRequest.SockType == SockType.NoType) + { + AddError(ApiErrorCode.VALIDATION_REQUIRED, null, $"SockType is required"); + return null; + } + + //Do we need to rehydrate the ID List from a DataList? + if (selectedRequest.SelectedRowIds.Length == 0) + selectedRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList(selectedRequest, ct, effectiveRoles, log, UserId, UserTranslationId); + + + log.LogDebug($"Instantiating biz object handler for {selectedRequest.SockType}"); + var biz = BizObjectFactory.GetBizObject(selectedRequest.SockType, ct, UserId, CurrentUserRoles, UserTranslationId); + log.LogDebug($"Fetching data for {selectedRequest.SelectedRowIds.Length} {selectedRequest.SockType} items"); + return await ((IReportAbleObject)biz).GetReportData(selectedRequest, Guid.Empty);//Guid.empty signifies it's not a job calling it + } + + + + public async Task GetReportData(DataListSelectedRequest selectedRequest, Guid jobId, bool requestIsCustomerWorkOrderReport = false) + { + var log = Sockeye.Util.ApplicationLogging.CreateLogger("ReportBiz::GetReportData"); + AuthorizationRoles effectiveRoles = CurrentUserRoles; + + if (selectedRequest.SockType == SockType.NoType) + { + AddError(ApiErrorCode.VALIDATION_REQUIRED, null, $"SockType is required"); + return null; + } + + if (!requestIsCustomerWorkOrderReport && !Sockeye.Api.ControllerHelpers.Authorized.HasReadFullRole(effectiveRoles, selectedRequest.SockType)) + { + AddError(ApiErrorCode.NOT_AUTHORIZED, null, $"User not authorized for {selectedRequest.SockType} type object"); + return null; + } + + //Do we need to rehydrate the ID List from a DataList? + if (selectedRequest.SelectedRowIds.Length == 0) + selectedRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList(selectedRequest, ct, effectiveRoles, log, UserId, UserTranslationId); + + if (!ReportRenderManager.KeepGoing(jobId)) + return null; + log.LogDebug($"Instantiating biz object handler for {selectedRequest.SockType}"); + var biz = BizObjectFactory.GetBizObject(selectedRequest.SockType, ct, UserId, CurrentUserRoles, UserTranslationId); + log.LogDebug($"Fetching data for {selectedRequest.SelectedRowIds.Length} {selectedRequest.SockType} items"); + return await ((IReportAbleObject)biz).GetReportData(selectedRequest, jobId); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //RENDER + // + + public async Task RequestRenderReport(DataListReportRequest reportRequest, DateTime renderTimeOutExpiry, string apiUrl, string userName) + { + var log = Sockeye.Util.ApplicationLogging.CreateLogger("ReportBiz::RequestRenderReport"); + log.LogDebug($"report id {reportRequest.ReportId}, timeout @ {renderTimeOutExpiry.ToString()}"); + + + + + //get report, vet security, see what we need before init in case of issue + log.LogDebug($"get report from db"); + var report = await ct.Report.FirstOrDefaultAsync(z => z.Id == reportRequest.ReportId); + if (report == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + + //If we get here via the /viewreport url in the client then there is no object type set so we need to set it here from the report + if (reportRequest.SockType == SockType.NoType) + { + reportRequest.SockType = report.SockType; + } + + + AuthorizationRoles effectiveRoles = CurrentUserRoles; + + + if ( !Sockeye.Api.ControllerHelpers.Authorized.HasReadFullRole(effectiveRoles, report.SockType)) + { + log.LogDebug($"bail: user unauthorized"); + AddError(ApiErrorCode.NOT_AUTHORIZED, null, $"User not authorized for {report.SockType} type object"); + return null; + } + + //Client meta data is required + if (reportRequest.ClientMeta == null) + { + log.LogDebug($"bail: ClientMeta parameter is missing"); + AddError(ApiErrorCode.VALIDATION_MISSING_PROPERTY, null, "ClientMeta parameter is missing and required to render report"); + return null; + } + + //includeWoItemDescendants? + reportRequest.IncludeWoItemDescendants = report.IncludeWoItemDescendants; + + //Do we need to rehydrate the ID List from a DataList? + if (reportRequest.SelectedRowIds.Length == 0) + reportRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList(reportRequest, ct, effectiveRoles, log, UserId, UserTranslationId); + + var JobName = $"LT:Report id: \"{reportRequest.ReportId}\" LT:{reportRequest.SockType} ({reportRequest.SelectedRowIds.LongLength}) LT:User {userName}"; + JObject o = JObject.FromObject(new + { + reportRequest = reportRequest, + apiUrl = apiUrl, + userName = userName + }); + + OpsJob j = new OpsJob(); + j.Name = JobName; + j.SockType = SockType.Report; + j.JobType = JobType.RenderReport; + j.SubType = JobSubType.NotSet; + j.Exclusive = false; + j.JobInfo = o.ToString(); + await JobsBiz.AddJobAsync(j); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, 0, SockType.ServerJob, SockEvent.Created, JobName), ct); + return j.GId; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + } + + + public async Task DoRenderJob(OpsJob job) + { + + var log = Sockeye.Util.ApplicationLogging.CreateLogger("ReportBiz::DoRenderJob"); + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); + + ReportRenderManager.AddJob(job.GId, log); + // await JobsBiz.LogJobAsync(job.GId, $"LT:StartJob {job.JobType}"); + + //rehydrate job objects + log.LogDebug($"Start; rehydrate job {job.Name}"); + JObject jobData = JObject.Parse(job.JobInfo); + var reportRequest = jobData["reportRequest"].ToObject(); + + var apiUrl = jobData["apiUrl"].ToObject(); + var userName = jobData["userName"].ToObject(); + + + + var report = await ct.Report.FirstOrDefaultAsync(z => z.Id == reportRequest.ReportId); + if (report == null) + { + await JobsBiz.LogJobAsync(job.GId, $"rendererror:error,\"LT:ErrorAPI2010 LT:Report({reportRequest.ReportId})\""); + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Failed); + return; + } + if (!ReportRenderManager.KeepGoing(job.GId)) + return; + + //Get data + log.LogDebug("Getting report data now"); + // var watch = new Stopwatch(); + // watch.Start(); + var ReportData = await GetReportData(reportRequest, job.GId); + // watch.Stop(); + // log.LogInformation($"GetReportData took {watch.ElapsedMilliseconds}ms to execute"); + + + //THIS is here to catch scenario where report data returned null because it expired, not because of an issue + if (!ReportRenderManager.KeepGoing(job.GId)) + return; + //if GetReportData errored then will return null so need to return that as well here + if (ReportData == null) + { + log.LogDebug($"bail: ReportData == null"); + await JobsBiz.LogJobAsync(job.GId, $"rendererror:error,\"{this.GetErrorsAsString()}\"");//?? circle back on this one + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Failed); + } + + if (!ReportRenderManager.KeepGoing(job.GId)) + return; + + //initialization + log.LogDebug("Initializing report rendering system"); + bool AutoDownloadChromium = true; + if (string.IsNullOrWhiteSpace(ServerBootConfig.SOCKEYE_REPORT_RENDER_BROWSER_PATH)) + { + log.LogDebug($"Using default Chromium browser (downloaded)"); + } + else + { + log.LogDebug($"Using user specified Chromium browser at {ServerBootConfig.SOCKEYE_REPORT_RENDER_BROWSER_PATH}"); + AutoDownloadChromium = false; + } + + var ReportJSFolderPath = Path.Combine(ServerBootConfig.SOCKEYE_CONTENT_ROOT_PATH, "resource", "rpt"); + + + //Keep for debugging headfully + //var lo = new LaunchOptions { Headless = false }; + var lo = new LaunchOptions { Headless = true }; + + + if (!AutoDownloadChromium) + { + lo.ExecutablePath = ServerBootConfig.SOCKEYE_REPORT_RENDER_BROWSER_PATH; + /* + troubleshooting links: + https://developers.google.com/web/tools/puppeteer/troubleshooting + + These links might be worth looking at in future if diagnosing other issues: + https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#running-on-alpine + https://github.com/puppeteer/puppeteer/issues/1825 + const chromeFlags = [ + '--headless', + '--no-sandbox', + "--disable-gpu", + "--single-process", + "--no-zygote" + ] + */ + } + else + { + log.LogDebug($"Windows: Calling browserFetcher download async now:"); + await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultChromiumRevision); + } + + + //Set Chromium args + //*** DANGER: --disable-dev-shm-usage will crash linux ayanova when it runs out of memory **** + //that was only suitable for dockerized scenario as it had an alt swap system + //var OriginalDefaultArgs = "--disable-dev-shm-usage --single-process --no-sandbox --disable-gpu --no-zygote "; + + + + //Keep for debugging headfully + // var DefaultArgs = "--no-sandbox"; + var DefaultArgs = "--headless --no-sandbox"; + + if (!string.IsNullOrWhiteSpace(ServerBootConfig.SOCKEYE_REPORT_RENDER_BROWSER_PARAMS)) + { + log.LogDebug($"SOCKEYE_REPORT_RENDER_BROWSER_PARAMS will be used: {ServerBootConfig.SOCKEYE_REPORT_RENDER_BROWSER_PARAMS}"); + lo.Args = new string[] { $"{ServerBootConfig.SOCKEYE_REPORT_RENDER_BROWSER_PARAMS}" };// SOCKEYE_REPORT_RENDER_BROWSER_PARAMS + } + else + { + log.LogDebug($"SOCKEYE_REPORT_RENDER_BROWSER_PARAMS not set, using defaults '{DefaultArgs}'"); + lo.Args = new string[] { DefaultArgs }; + } + System.Text.StringBuilder PageLog = new System.Text.StringBuilder(); + + if (!ReportRenderManager.KeepGoing(job.GId)) + return; + + //API DOCS http://www.puppeteersharp.com/api/index.html + log.LogDebug($"Launching headless Browser and new page now:"); + using (var browser = await Puppeteer.LaunchAsync(lo)) + using (var page = (await browser.PagesAsync())[0]) + // using (var page = await browser.NewPageAsync()) + { + //track this process for timeout purposes + ReportRenderManager.SetProcess(job.GId, browser.Process.Id, log); + page.DefaultTimeout = 0;//infinite timeout as we are controlling how long the process can live for with the reportprocessmanager + try + { + //info and error logging + page.Console += async (sender, args) => + { + switch (args.Message.Type) + { + case ConsoleType.Error: + try + { + var errorArgs = await Task.WhenAll(args.Message.Args.Select(arg => arg.ExecutionContext.EvaluateFunctionAsync("(arg) => arg instanceof Error ? arg.message : arg", arg))); + PageLog.AppendLine($"ERROR: {args.Message.Text} args: [{string.Join(", ", errorArgs)}]"); + } + catch { } + break; + case ConsoleType.Warning: + PageLog.AppendLine($"WARNING: {args.Message.Text}"); + break; + default: + PageLog.AppendLine($"INFO: {args.Message.Text}"); + + + break; + } + }; + + log.LogDebug($"Preparing page: adding base reporting scripts to page"); + if (!ReportRenderManager.KeepGoing(job.GId)) + return; + + //Add Handlebars JS for compiling and presenting + //https://handlebarsjs.com/ + await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "sock-hb.js") }); + + //add Marked for markdown processing + //https://github.com/markedjs/marked + await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "sock-md.js") }); + + //add DOM Purify for markdown template sanitization processing + //https://github.com/cure53/DOMPurify + await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "sock-pf.js") }); + + //add Bar code library if our bar code helper is referenced + //https://github.com/metafloor/bwip-js + if (report.Template.Contains("ayBC ") || report.JsHelpers.Contains("ayBC ")) + await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "sock-bc.js") }); + + //add stock helpers + await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "sock-report.js") }); + + //execute to add to handlebars + await page.EvaluateExpressionAsync("ayRegisterHelpers();"); + if (!ReportRenderManager.KeepGoing(job.GId)) + return; + log.LogDebug($"Preparing page: adding this report's scripts, style and templates to page"); + + //add report pre-render, helpers and style + if (string.IsNullOrWhiteSpace(report.JsPrerender)) + { + report.JsPrerender = "async function ayPrepareData(reportData){return reportData;}"; + } + await page.AddScriptTagAsync(new AddTagOptions() { Content = report.JsPrerender }); + + if (!string.IsNullOrWhiteSpace(report.JsHelpers)) + await page.AddScriptTagAsync(new AddTagOptions() { Content = report.JsHelpers }); + + log.LogDebug($"Preparing page: adding Client meta data"); + //Client meta data to JSON string + var clientMeta = reportRequest.ClientMeta.ToString(); + + log.LogDebug($"Preparing page: adding Server meta data"); + //Server meta data + var logo = await ct.Logo.AsNoTracking().SingleOrDefaultAsync(); + + var HasSmallLogo = "false"; + var HasMediumLogo = "false"; + var HasLargeLogo = "false"; + if (logo != null) + { + if (logo.Small != null) HasSmallLogo = "true"; + if (logo.Medium != null) HasMediumLogo = "true"; + if (logo.Large != null) HasLargeLogo = "true"; + } + var HasPostalAddress = !string.IsNullOrWhiteSpace(ServerGlobalBizSettings.Cache.PostAddress) ? "true" : "false"; + var HasStreetAddress = !string.IsNullOrWhiteSpace(ServerGlobalBizSettings.Cache.Address) ? "true" : "false"; + + //case 4209 + //latitude and longitude are the only nullable fields in global biz settings and need to be converted to empty strings if null + string sLatitude = "null"; + string sLongitude = "null"; + if (ServerGlobalBizSettings.Cache.Latitude != null) + sLatitude = ServerGlobalBizSettings.Cache.Latitude.ToString(); + if (ServerGlobalBizSettings.Cache.Longitude != null) + sLongitude = ServerGlobalBizSettings.Cache.Longitude.ToString(); + + + var serverMeta = $"{{ayApiUrl:`{apiUrl}`, HasSmallLogo:{HasSmallLogo}, HasMediumLogo:{HasMediumLogo}, HasLargeLogo:{HasLargeLogo},CompanyName: `Ground Zero Tech-Works Inc.`,CompanyWebAddress:`{ServerGlobalBizSettings.Cache.WebAddress}`,CompanyEmailAddress:`{ServerGlobalBizSettings.Cache.EmailAddress}`,CompanyPhone1:`{ServerGlobalBizSettings.Cache.Phone1}`,CompanyPhone2:`{ServerGlobalBizSettings.Cache.Phone2}`,HasPostalAddress:{HasPostalAddress},CompanyPostAddress:`{ServerGlobalBizSettings.Cache.PostAddress}`,CompanyPostCity:`{ServerGlobalBizSettings.Cache.PostCity}`,CompanyPostRegion:`{ServerGlobalBizSettings.Cache.PostRegion}`,CompanyPostCountry:`{ServerGlobalBizSettings.Cache.PostCountry}`,CompanyPostCode:`{ServerGlobalBizSettings.Cache.PostCode}`,HasStreetAddress:{HasStreetAddress},CompanyAddress:`{ServerGlobalBizSettings.Cache.Address}`,CompanyCity:`{ServerGlobalBizSettings.Cache.City}`,CompanyRegion:`{ServerGlobalBizSettings.Cache.Region}`,CompanyCountry:`{ServerGlobalBizSettings.Cache.Country}`,CompanyAddressPostal:`{ServerGlobalBizSettings.Cache.AddressPostal}`,CompanyLatitude:{sLatitude},CompanyLongitude:{sLongitude}}}"; + + log.LogDebug($"Preparing page: adding Report meta data"); + + //Custom fields definition for report usage + string CustomFieldsTemplate = "null"; + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == report.SockType.ToString()); + if (FormCustomization != null) + { + CustomFieldsTemplate = FormCustomization.Template; + } + + //Report meta data + + + var reportMeta = $"{{Id:{report.Id},Name:`{report.Name}`,Notes:`{report.Notes}`,SockType:`{report.SockType}`,CustomFieldsDefinition:{CustomFieldsTemplate},DataListKey:`{reportRequest.DataListKey}`,SelectedRowIds: `{string.Join(",", reportRequest.SelectedRowIds)}`}}"; + + + //duplicate meta data in report page wide variable for use by our internal functions + await page.AddScriptTagAsync(new AddTagOptions() { Content = $"var AYMETA={{ ayReportMetaData:{reportMeta}, ayClientMetaData:{clientMeta}, ayServerMetaData:{serverMeta} }}" }); + + if (!ReportRenderManager.KeepGoing(job.GId)) + return; + + //prePareData / preRender + var ReportDataObject = $"{{ ayReportData:{ReportData}, ayReportMetaData:{reportMeta}, ayClientMetaData:{clientMeta}, ayServerMetaData:{serverMeta} }}"; + + //case 4209 + log.LogDebug($"ReportData object about to be pre-rendered is:{ReportDataObject}"); + + log.LogDebug($"PageLog before render:{PageLog.ToString()}"); + log.LogDebug($"Calling ayPreRender..."); + await page.WaitForExpressionAsync($"ayPreRender({ReportDataObject})"); + log.LogDebug($"ayPreRender completed"); + + if (!ReportRenderManager.KeepGoing(job.GId)) + return; + //compile the template + log.LogDebug($"Calling Handlebars.compile..."); + + var compileScript = $"Handlebars.compile(`{report.Template}`)(PreParedReportDataObject);"; + if (!ReportRenderManager.KeepGoing(job.GId)) + return; + var compiledHTML = await page.EvaluateExpressionAsync(compileScript); + if (!ReportRenderManager.KeepGoing(job.GId)) + return; + + //render report as HTML + log.LogDebug($"Setting render page content style and compiled HTML"); + await page.SetContentAsync($"{compiledHTML}"); + + + string outputFileName = StringUtil.ReplaceLastOccurrence(FileUtil.NewRandomFileName, ".", "") + ".pdf"; + string outputFullPath = System.IO.Path.Combine(FileUtil.TemporaryFilesFolder, outputFileName); + + + //Set PDF options + //https://pptr.dev/#?product=Puppeteer&version=v5.3.0&show=api-pagepdfoptions + log.LogDebug($"Resolving PDF Options from report settings"); + + var PdfOptions = new PdfOptions() { }; + + PdfOptions.DisplayHeaderFooter = report.DisplayHeaderFooter; + if (report.DisplayHeaderFooter) + { + var ClientPDFDate = reportRequest.ClientMeta["PDFDate"].Value(); + var ClientPDFTime = reportRequest.ClientMeta["PDFTime"].Value(); + PdfOptions.HeaderTemplate = report.HeaderTemplate.Replace("PDFDate", ClientPDFDate).Replace("PDFTime", ClientPDFTime); + PdfOptions.FooterTemplate = report.FooterTemplate.Replace("PDFDate", ClientPDFDate).Replace("PDFTime", ClientPDFTime); + } + + if (report.PaperFormat != ReportPaperFormat.NotSet) + { + switch (report.PaperFormat) + { + case ReportPaperFormat.A0: + PdfOptions.Format = PuppeteerSharp.Media.PaperFormat.A0; + break; + case ReportPaperFormat.A1: + PdfOptions.Format = PuppeteerSharp.Media.PaperFormat.A1; + break; + case ReportPaperFormat.A2: + PdfOptions.Format = PuppeteerSharp.Media.PaperFormat.A2; + break; + case ReportPaperFormat.A3: + PdfOptions.Format = PuppeteerSharp.Media.PaperFormat.A3; + break; + case ReportPaperFormat.A4: + PdfOptions.Format = PuppeteerSharp.Media.PaperFormat.A4; + break; + case ReportPaperFormat.A5: + PdfOptions.Format = PuppeteerSharp.Media.PaperFormat.A5; + break; + case ReportPaperFormat.A6: + PdfOptions.Format = PuppeteerSharp.Media.PaperFormat.A6; + break; + case ReportPaperFormat.Ledger: + PdfOptions.Format = PuppeteerSharp.Media.PaperFormat.Ledger; + break; + case ReportPaperFormat.Legal: + PdfOptions.Format = PuppeteerSharp.Media.PaperFormat.Legal; + break; + case ReportPaperFormat.Letter: + PdfOptions.Format = PuppeteerSharp.Media.PaperFormat.Letter; + break; + case ReportPaperFormat.Tabloid: + PdfOptions.Format = PuppeteerSharp.Media.PaperFormat.Tabloid; + break; + } + } + + PdfOptions.Landscape = report.Landscape; + if (!string.IsNullOrWhiteSpace(report.MarginOptionsBottom)) + PdfOptions.MarginOptions.Bottom = report.MarginOptionsBottom; + + if (!string.IsNullOrWhiteSpace(report.MarginOptionsLeft)) + PdfOptions.MarginOptions.Left = report.MarginOptionsLeft; + + if (!string.IsNullOrWhiteSpace(report.MarginOptionsRight)) + PdfOptions.MarginOptions.Right = report.MarginOptionsRight; + + if (!string.IsNullOrWhiteSpace(report.MarginOptionsTop)) + PdfOptions.MarginOptions.Top = report.MarginOptionsTop; + + PdfOptions.PreferCSSPageSize = report.PreferCSSPageSize; + PdfOptions.PrintBackground = report.PrintBackground; + //Defaults to 1. Scale amount must be between 0.1 and 2. + PdfOptions.Scale = report.Scale; + if (!ReportRenderManager.KeepGoing(job.GId)) + return; + //render to pdf and return + log.LogDebug($"Calling render page contents to PDF"); + await page.PdfAsync(outputFullPath, PdfOptions); + + log.LogDebug($"Closing Page"); + await page.CloseAsync(); + log.LogDebug($"Closing Browser"); + await browser.CloseAsync(); + + log.LogDebug($"Render completed successfully, output filename is: {outputFileName}, logging to job for client"); + var json = Newtonsoft.Json.JsonConvert.SerializeObject(new { reportfilename = outputFileName }, Newtonsoft.Json.Formatting.None); + await JobsBiz.LogJobAsync(job.GId, json); + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed); + return; + } + catch (ReportRenderTimeOutException) + { + await HandleTimeOut(job, log, reportRequest, userName); + return; + } + catch (PuppeteerSharp.TargetClosedException) + { + log.LogDebug("Caught PuppeteerSharp.TargetClosedException - report was cancelled by user OR timed out"); + //we closed it because the timeout hit and the CoreJobReportRenderEngineProcessCleanup job cleaned it out + //so return the error the client expects in this scenario + await HandleTimeOut(job, log, reportRequest, userName); + return; + } + catch (Exception ex) + { + log.LogDebug(ex, $"Error during report rendering"); + //This is the error when a helper is used on the template but doesn't exist: + //Evaluation failed: d + //(it might also mean other things wrong with template) + if (PageLog.Length > 0) + { + log.LogInformation($"Exception caught while rendering report \"{report.Name}\", report Page console log:"); + log.LogInformation(PageLog.ToString()); + var json = Newtonsoft.Json.JsonConvert.SerializeObject(new { rendererror = new { pagelog = PageLog.ToString(), exception = ExceptionUtil.ExtractAllExceptionMessages(ex) } }, Newtonsoft.Json.Formatting.None); + await JobsBiz.LogJobAsync(job.GId, json); + } + else + { + var json = Newtonsoft.Json.JsonConvert.SerializeObject(new { rendererror = new { exception = ExceptionUtil.ExtractAllExceptionMessages(ex) } }, Newtonsoft.Json.Formatting.None); + await JobsBiz.LogJobAsync(job.GId, json); + } + + + + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Failed); + + // var v=await page.GetContentAsync();//for debugging purposes + return; + } + finally + { + log.LogDebug($"reached finally block"); + if (!page.IsClosed) + { + log.LogDebug($"Page not closed in finally block, closing now"); + await page.CloseAsync(); + } + if (!browser.IsClosed) + { + log.LogDebug($"Browser not closed in finally block, closing now"); + await browser.CloseAsync(); + } + log.LogDebug($"Calling ReportRenderManager.RemoveJob to stop tracking this job/process"); + await ReportRenderManager.RemoveJob(job.GId, log, false); + } + } + + static async Task HandleTimeOut(OpsJob job, ILogger log, DataListReportRequest reportRequest, string userName) + { + log.LogDebug($"Report render cancelled by user OR exceeded timeout setting of {ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT} minutes, report id: {reportRequest.ReportId}, record count:{reportRequest.SelectedRowIds.LongLength}, user:{userName}"); + var json = Newtonsoft.Json.JsonConvert.SerializeObject(new { rendererror = new { timeout = true, timeoutsetting = ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT } }, Newtonsoft.Json.Formatting.None); + //"{\"rendererror\":{\"timeout\":1}}" + await JobsBiz.LogJobAsync(job.GId, json); + + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Failed); + } + } + + + public async Task CancelJob(Guid jobId) + { + await ReportRenderManager.RemoveJob(jobId, Sockeye.Util.ApplicationLogging.CreateLogger("ReportBiz::CancelJob"), true); + } + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + public async Task HandleJobAsync(OpsJob job) + { + switch (job.JobType) + { + case JobType.RenderReport: + await DoRenderJob(job); + break; + default: + throw new System.ArgumentOutOfRangeException($"ReportBiz.HandleJob-> Invalid job type{job.JobType.ToString()}"); + } + } + + //Other job handlers here... + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/ReportPaperFormat.cs b/server/biz/ReportPaperFormat.cs new file mode 100644 index 0000000..f9b63c0 --- /dev/null +++ b/server/biz/ReportPaperFormat.cs @@ -0,0 +1,21 @@ +namespace Sockeye.Biz +{ + /// + /// All Sockeye report paper format types, passed to pdf generator + /// + public enum ReportPaperFormat : int + {// http://www.puppeteersharp.com/api/PuppeteerSharp.Media.PaperFormat.html + NotSet=0, + A0 = 1, + A1 = 2, + A2 = 3, + A3 = 4, + A4 = 5, + A5 = 6, + A6 = 7, + Ledger = 8, + Legal = 9, + Letter = 10, + Tabloid = 11 + } +}//eons \ No newline at end of file diff --git a/server/biz/ReportRenderType.cs b/server/biz/ReportRenderType.cs new file mode 100644 index 0000000..28339c9 --- /dev/null +++ b/server/biz/ReportRenderType.cs @@ -0,0 +1,13 @@ +namespace Sockeye.Biz +{ + /// + /// All Sockeye report rendering types + /// + public enum ReportRenderType : int + {// rendertype(type to render, default is pdf, but could be text, csv etc), + PDF = 0, + Text = 1, + CSV = 2, + HTML = 3 + } +}//eons \ No newline at end of file diff --git a/server/biz/ReportableBizObjectAttribute.cs b/server/biz/ReportableBizObjectAttribute.cs new file mode 100644 index 0000000..5007244 --- /dev/null +++ b/server/biz/ReportableBizObjectAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Sockeye.Biz +{ + /// + /// Marker attribute indicating that an object is a reportable type + /// Used in + /// + [AttributeUsage(AttributeTargets.All)] + public class ReportableBizObjectAttribute : Attribute + { + //No code required, it's just a marker + //https://docs.microsoft.com/en-us/dotnet/standard/attributes/writing-custom-attributes + } +}//eons diff --git a/server/biz/RequiredFieldsValidator.cs b/server/biz/RequiredFieldsValidator.cs new file mode 100644 index 0000000..09f44da --- /dev/null +++ b/server/biz/RequiredFieldsValidator.cs @@ -0,0 +1,141 @@ +using Sockeye.Models; +using System.Linq; +using Newtonsoft.Json.Linq; + +namespace Sockeye.Biz +{ + //VALIDATE **USER DEFINED** (not stock) REQUIRED FIELDS THAT ARE NOT CUSTOM + //(fields that are stock required are validated on their own not here) + + internal static class RequiredFieldsValidator + { + internal static void Validate(BizObject biz, FormCustom formCustom, object proposedObject) + { + //No form custom = no template to check against so nothing to do + if (formCustom == null || string.IsNullOrWhiteSpace(formCustom.Template)) + return; + var FormTemplate = JArray.Parse(formCustom.Template); + var FormFields = Biz.FormFieldOptionalCustomizableReference.FormFieldReferenceList(formCustom.FormKey); + + foreach (JObject jo in FormTemplate) + { + if (jo["required"].Value() == true) + { + //First get the LT key + var FldLtKey = jo["fld"].Value(); + // - e.g.: {template:[{fld:"ltkeyfieldname",hide:"true/false",required:"true/false", type:"bool"},{fld:"ltkeyfieldname",hide:"true/false",required:"true/false", type:"text"]} + + //get the FormField object + FormField FF = FormFields.Where(z => z.FieldKey == FldLtKey).Single(); + + //get the type and because of quote and pm subsections "TKeySection" property being named "WorkOrder" in the formFieldReference due to lack of separate + //translations for quote and pm subitems it's necessary to adjust the name here first before matching + var proposedObjectType = proposedObject.GetType().ToString().Replace("Sockeye.Models.", "").Replace("QuoteItem", "WorkOrderItem").Replace("PMItem", "WorkOrderItem"); + + //hacky last minute work around but workorder, quote and pm header objects have no tkeysection normally which can cause interference here with duplicate fields i.e. Tags in subsections + //as they will have the header rule applied if we just leave the tkeysection as null so for here we workaround that by creating a temporary tkeysection + if (FF.TKeySection == null) + { + switch (proposedObjectType) + { + case "WorkOrder": + FF.TKeySection = proposedObjectType; + break; + case "Quote": + FF.TKeySection = proposedObjectType; + break; + case "PM": + FF.TKeySection = proposedObjectType; + break; + } + } + + //don't validate custom fields, just skip them, make sure if it's sectional it matches the section of the object type (workorder sub sections) + if (!FF.IsCustomField && (FF.TKeySection == null || proposedObjectType == FF.TKeySection)) + { + + //bugbug: if section is workorderitem and field is tags but there is a tags required in workorder htat has NOT tkeysection it applies that rule + + + //Now get the actual property name from the available fields using the lt key + string RequiredPropertyName = FF.FieldKey; + //there might be a more specific model property due to being a workorder sub item duplicate such as WorkOrderItemTags vs WorkOrderTags + if (FF.ModelProperty != null) + RequiredPropertyName = FF.ModelProperty; + + //Is it an indexed collection field? + if (RequiredPropertyName.Contains(".")) + { + /* + flag errors to include parent e.g in PO item validation error field name could be "Items[3].QuantityReceived" to indicate poitems collection 4th row has error in qtyreceived + Note: collections are logically never more than 3 deep so for example the deepest would be workorder granchildren: e.g. Workorder.Items.Parts and most + are two deep however like Po.PoItems.field + for purposes of rule checking they would be flagged by their immediate parent "Items[3].QuantityReceived" for po or for workorder it could be + a required UPC field in Workorder.Items.Parts.UPC would result in a target error return of "Items[2].Parts[3].UPC" would be valid + */ + var FieldKeyParts = RequiredPropertyName.Split('.'); + if (FieldKeyParts.Length == 2) + { + //parent collection -> child field + //target name like "Items.FieldName" + var parentCollection = proposedObject.GetType().GetProperty(FieldKeyParts[0]).GetValue(proposedObject, null); + int index = 0; + foreach (object ChildObject in (parentCollection as System.Collections.IEnumerable)) + { + var fieldValue = ChildObject.GetType().GetProperty(FieldKeyParts[1]).GetValue(ChildObject, null); + if (fieldValue == null || string.IsNullOrWhiteSpace(fieldValue.ToString())) + biz.AddError(ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY, $"{FieldKeyParts[0]}[{index}].{FieldKeyParts[1]}"); + index++; + } + } + else if (FieldKeyParts.Length == 3) + { + //grandparent collection -> Parent collection -> Child field + //target name like "WorkOrderItems.WorkOrderItemParts.UPC + var GrandParentCollection = proposedObject.GetType().GetProperty(FieldKeyParts[0]).GetValue(proposedObject, null); + int GrandParentIndex = 0; + foreach (object GrandParentObject in (GrandParentCollection as System.Collections.IEnumerable)) + { + var ParentCollection = GrandParentObject.GetType().GetProperty(FieldKeyParts[1]).GetValue(GrandParentObject, null); + int ParentIndex = 0; + foreach (object ChildObject in (ParentCollection as System.Collections.IEnumerable)) + { + var fieldValue = ChildObject.GetType().GetProperty(FieldKeyParts[1]).GetValue(ChildObject, null); + if (fieldValue == null || string.IsNullOrWhiteSpace(fieldValue.ToString())) + biz.AddError(ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY, $"{FieldKeyParts[0]}[{GrandParentIndex}].{FieldKeyParts[1]}[{ParentIndex}].{FieldKeyParts[2]}"); + ParentIndex++; + } + GrandParentIndex++; + } + } + } + else + { + //It's a simple property on the main object + //use reflection to get the underlying value from the proposed object to be saved + //issue the error on the *Models* property name to mirror how all other server error handling and validation works + //so that client end consumes it properly (fieldkey is just for the UI and showing / hiding overall form values) + object propertyValue = proposedObject.GetType().GetProperty(RequiredPropertyName).GetValue(proposedObject, null); + if (propertyValue == null) + biz.AddError(ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY, RequiredPropertyName); + else + { + + if (RequiredPropertyName == "Tags") + { + if (((System.Collections.Generic.List)propertyValue).Count == 0) + { + biz.AddError(ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY, RequiredPropertyName); + } + } + else + if (string.IsNullOrWhiteSpace(propertyValue.ToString())) + biz.AddError(ApiErrorCode.VALIDATION_CUSTOM_REQUIRED_EMPTY, RequiredPropertyName); + } + } + } + } + } + } + }//eoc +}//ens diff --git a/server/biz/ReviewBiz.cs b/server/biz/ReviewBiz.cs new file mode 100644 index 0000000..7b9e443 --- /dev/null +++ b/server/biz/ReviewBiz.cs @@ -0,0 +1,691 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Linq; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Sockeye.Biz +{ + internal class ReviewBiz : BizObject, IJobObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, IImportAbleObject, INotifiableObject + { + internal ReviewBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.Review; + } + + internal static ReviewBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new ReviewBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new ReviewBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Review.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(Review newObject) + { + await ValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.Review.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + await SearchIndexAsync(newObject, true); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + await HandlePotentialNotificationEvent(SockEvent.Created, newObject); + await PopulateVizFields(newObject); + return newObject; + } + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.Review.AsNoTracking().SingleOrDefaultAsync(m => m.Id == id); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, SockEvent.Retrieved), ct); + await PopulateVizFields(ret); + return ret; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(Review putObject) + { + var dbObject = await GetAsync(putObject.Id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + + putObject.Tags = TagBiz.NormalizeTags(putObject.Tags); + putObject.CustomFields = JsonUtil.CompactJson(putObject.CustomFields); + await ValidateAsync(putObject, dbObject); + if (HasErrors) return null; + ct.Replace(dbObject, putObject); + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + await SearchIndexAsync(putObject, false); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags); + await HandlePotentialNotificationEvent(SockEvent.Modified, putObject, dbObject); + await PopulateVizFields(putObject); + return putObject; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE schedule only + // + internal async Task PutNewScheduleTimeAsync(ScheduleItemAdjustParams p) + { + Review dbObject = await ct.Review.SingleOrDefaultAsync(z => z.Id == p.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return false; + } + + dbObject.ReviewDate = p.Start; + + await ValidateAsync(dbObject, dbObject); + if (HasErrors) return false; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(dbObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return false; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, dbObject.SockType, SockEvent.Modified), ct); + + await HandlePotentialNotificationEvent(SockEvent.Modified, dbObject, dbObject); + return true; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long id, Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction parentTransaction = null) + { + Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction transaction = null; + if (parentTransaction == null) + transaction = await ct.Database.BeginTransactionAsync(); + var dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return false; + } + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + ct.Review.Remove(dbObject); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct); + await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); + //all good do the commit if it's ours + if (parentTransaction == null) + await transaction.CommitAsync(); + await HandlePotentialNotificationEvent(SockEvent.Deleted, dbObject); + return true; + + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // + private async Task SearchIndexAsync(Review obj, bool isNew) + { + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + DigestSearchText(obj, SearchParams); + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id, SockType specificType) + { + var obj = await GetAsync(id, false); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + DigestSearchText(obj, SearchParams); + return SearchParams; + } + + public void DigestSearchText(Review obj, Search.SearchIndexProcessObjectParameters searchParams) + { + if (obj != null) + searchParams.AddText(obj.Notes) + .AddText(obj.Name) + .AddText(obj.Wiki) + .AddText(obj.Tags) + .AddText(obj.CompletionNotes) + .AddCustomFields(obj.CustomFields); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + private async Task ValidateAsync(Review proposedObj, Review currentObj) + { + /* + - RULE Roles: BizAdmin, Service, Inventory, Accounting, Sales can create and assign to anyone else. + - RULE Any other inside role can create for themselves only. (outside roles have no rights to this object so no need to check) + - RULE Restricted roles can only set completed date and enter completion notes not otherwise change or create or delete. + - BIZ RULE users with more than restricted roles can assign other users + */ + + bool isNew = currentObj == null; + bool SelfAssigned = proposedObj.AssignedByUserId == UserId && proposedObj.UserId == UserId; + bool HasSupervisorRole = + CurrentUserRoles.HasFlag(AuthorizationRoles.BizAdmin) || + CurrentUserRoles.HasFlag(AuthorizationRoles.Service) || + CurrentUserRoles.HasFlag(AuthorizationRoles.Inventory) || + CurrentUserRoles.HasFlag(AuthorizationRoles.Sales) || + CurrentUserRoles.HasFlag(AuthorizationRoles.Accounting); + + //Checks for non supervisors + if (!HasSupervisorRole) + { + //Non supervisor can't create a Review and assign to other User + if (isNew && !SelfAssigned) + { + AddError(ApiErrorCode.NOT_AUTHORIZED, "UserId"); + return;//no need to check any further this is disqualifying completely + } + + //Non supervisory roles can only change / set certain fields for non self reviews + if (!isNew && !SelfAssigned) + { + if ( + (currentObj.Name != proposedObj.Name) || + (currentObj.Notes != proposedObj.Notes) || + (currentObj.ReviewDate != proposedObj.ReviewDate) || + (currentObj.UserId != proposedObj.UserId) || + (currentObj.AssignedByUserId != proposedObj.AssignedByUserId)) + { + AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "generalerror"); + return; + } + } + } + + //Can't change assigned object id and type after initial save + if (!isNew) + { + if (proposedObj.ObjectId != currentObj.ObjectId) + { + AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ObjectId"); + return; + } + if (proposedObj.SockType != currentObj.SockType) + { + AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "SockType"); + return; + } + } + + //Can't change Review date after completed + //user must set empty completed before changing start date if they really want to do this + if (!isNew && proposedObj.CompletedDate != null) + { + if (proposedObj.ReviewDate != currentObj.ReviewDate) + { + AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ReviewDate"); + return; + } + } + + //Does the object of this Review actually exist? + if (!await BizObjectExistsInDatabase.ExistsAsync(proposedObj.SockType, proposedObj.ObjectId, ct)) + { + AddError(ApiErrorCode.NOT_FOUND, "generalerror", $"LT:ErrorAPI2010 LT:{proposedObj.SockType} id {proposedObj.ObjectId}"); + return; + } + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == SockType.Review.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + } + + private void ValidateCanDelete(Review inObj) + { + bool SelfAssigned = inObj.AssignedByUserId == UserId && inObj.UserId == UserId; + bool HasSupervisorRole = + CurrentUserRoles.HasFlag(AuthorizationRoles.BizAdmin) || + CurrentUserRoles.HasFlag(AuthorizationRoles.Service) || + CurrentUserRoles.HasFlag(AuthorizationRoles.Inventory) || + CurrentUserRoles.HasFlag(AuthorizationRoles.Sales) || + CurrentUserRoles.HasFlag(AuthorizationRoles.Accounting); + if (!SelfAssigned && !HasSupervisorRole) + AddError(ApiErrorCode.NOT_AUTHORIZED); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //REPORTING + // + public async Task GetReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + var idList = dataListSelectedRequest.SelectedRowIds; + JArray ReportData = new JArray(); + while (idList.Any()) + { + var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE); + idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray(); + //query for this batch, comes back in db natural order unfortunately + var batchResults = await ct.Review.AsNoTracking().Where(z => batch.Contains(z.Id)).ToArrayAsync(); + //order the results back into original + var orderedList = from id in batch join z in batchResults on id equals z.Id select z; + batchResults = null; + foreach (Review w in orderedList) + { + if (!ReportRenderManager.KeepGoing(jobId)) return null; + await PopulateVizFields(w); + var jo = JObject.FromObject(w); + if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"])) + jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]); + ReportData.Add(jo); + } + orderedList = null; + } + vc.Clear(); + return ReportData; + } + private VizCache vc = new VizCache(); + + //populate viz fields from provided object + public async Task PopulateVizFields(Review o) + { + if (!vc.Has("user", o.UserId)) + vc.Add(await ct.User.AsNoTracking().Where(x => x.Id == o.UserId).Select(x => x.Name).FirstOrDefaultAsync(), "user", o.UserId); + o.UserViz = vc.Get("user", o.UserId); + + if (!vc.Has("user", o.AssignedByUserId)) + vc.Add(await ct.User.AsNoTracking().Where(x => x.Id == o.AssignedByUserId).Select(x => x.Name).FirstOrDefaultAsync(), "user", o.AssignedByUserId); + o.AssignedByUserViz = vc.Get("user", o.AssignedByUserId); + + if (!vc.Has($"b{o.SockType}{o.ObjectId}")) + vc.Add(BizObjectNameFetcherDirect.Name((SockType)o.SockType, (long)o.ObjectId, UserTranslationId, ct), $"b{o.SockType}{o.ObjectId}"); + o.ReviewObjectViz = vc.Get($"b{o.SockType}{o.ObjectId}"); + + if (o.ReviewObjectViz.StartsWith("LT:")) + { + if (!vc.Has(o.ReviewObjectViz)) + vc.Add(await Translate(o.ReviewObjectViz), o.ReviewObjectViz); + o.ReviewObjectViz = vc.Get(o.ReviewObjectViz); + } + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // IMPORT EXPORT + // + + public async Task GetExportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + //for now just re-use the report data code + //this may turn out to be the pattern for most biz object types but keeping it seperate allows for custom usage from time to time + return await GetReportData(dataListSelectedRequest, jobId); + } + + public async Task> ImportData(AyImportData importData) + { + List ImportResult = new List(); + string ImportTag = $"imported-{FileUtil.GetSafeDateFileName()}"; + + var jsset = JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = new Sockeye.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "CustomFields" }) }); + foreach (JObject j in importData.Data) + { + var w = j.ToObject(jsset); + if (j["CustomFields"] != null) + w.CustomFields = j["CustomFields"].ToString(); + w.Tags.Add(ImportTag);//so user can find them all and revert later if necessary + var res = await CreateAsync(w); + if (res == null) + { + ImportResult.Add($"* {w.Name} - {this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"{w.Name} - ok"); + } + } + return ImportResult; + } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + public async Task HandleJobAsync(OpsJob job) + { + //Hand off the particular job to the corresponding processing code + //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so + //basically any error condition during job processing should throw up an exception if it can't be handled + switch (job.JobType) + { + case JobType.BatchCoreObjectOperation: + await ProcessBatchJobAsync(job); + break; + default: + throw new System.ArgumentOutOfRangeException($"ReviewBiz.HandleJob-> Invalid job type{job.JobType.ToString()}"); + } + } + + + + private async Task ProcessBatchJobAsync(OpsJob job) + { + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); + await JobsBiz.LogJobAsync(job.GId, $"LT:StartJob {job.SubType}"); + List idList = new List(); + long FailedObjectCount = 0; + JObject jobData = JObject.Parse(job.JobInfo); + if (jobData.ContainsKey("idList")) + idList = ((JArray)jobData["idList"]).ToObject>(); + else + idList = await ct.Review.AsNoTracking().Select(z => z.Id).ToListAsync(); + bool SaveIt = false; + + //--------------------------------- + //case 4192 + TimeSpan ProgressAndCancelCheckSpan = new TimeSpan(0, 0, ServerBootConfig.JOB_PROGRESS_UPDATE_AND_CANCEL_CHECK_SECONDS); + DateTime LastProgressCheck = DateTime.UtcNow.Subtract(new TimeSpan(1, 1, 1, 1, 1)); + var TotalRecords = idList.LongCount(); + long CurrentRecord = -1; + //--------------------------------- + foreach (long id in idList) + { + try + { + //-------------------------------- + //case 4192 + //Update progress / cancel requested? + CurrentRecord++; + if (DateUtil.IsAfterDuration(LastProgressCheck, ProgressAndCancelCheckSpan)) + { + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{CurrentRecord}/{TotalRecords}"); + if (await JobsBiz.GetJobStatusAsync(job.GId) == JobStatus.CancelRequested) + break; + LastProgressCheck = DateTime.UtcNow; + } + //--------------------------------- + SaveIt = false; + ClearErrors(); + Review o = null; + //save a fetch if it's a delete + if (job.SubType != JobSubType.Delete) + o = await GetAsync(id, false); + switch (job.SubType) + { + case JobSubType.TagAddAny: + case JobSubType.TagAdd: + case JobSubType.TagRemoveAny: + case JobSubType.TagRemove: + case JobSubType.TagReplaceAny: + case JobSubType.TagReplace: + SaveIt = TagBiz.ProcessBatchTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); + break; + case JobSubType.Delete: + if (!await DeleteAsync(id)) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + break; + default: + throw new System.ArgumentOutOfRangeException($"ProcessBatchJobAsync -> Invalid job Subtype{job.SubType}"); + } + if (SaveIt) + { + o = await PutAsync(o); + if (o == null) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + } + + //delay so we're not tying up all the resources in a tight loop + await Task.Delay(Sockeye.Util.ServerBootConfig.JOB_OBJECT_HANDLE_BATCH_JOB_LOOP_DELAY); + } + catch (Exception ex) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors id({id})"); + await JobsBiz.LogJobAsync(job.GId, ExceptionUtil.ExtractAllExceptionMessages(ex)); + } + } + + //--------------------------------- + //case 4192 + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{++CurrentRecord}/{TotalRecords}"); + //--------------------------------- + await JobsBiz.LogJobAsync(job.GId, $"LT:BatchJob {job.SubType} {idList.Count}{(FailedObjectCount > 0 ? " - LT:Failed " + FailedObjectCount : "")}"); + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // NOTIFICATION PROCESSING + // + public async Task HandlePotentialNotificationEvent(SockEvent ayaEvent, ICoreBizObjectModel proposedObj, ICoreBizObjectModel currentObj = null) + { + ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger(); + + log.LogDebug($"HandlePotentialNotificationEvent processing: [SockType:{this.BizType}, AyaEvent:{ayaEvent}]"); + + bool isNew = currentObj == null; + + Review o = (Review)proposedObj; + + //STANDARD EVENTS FOR ALL OBJECTS + await NotifyEventHelper.ProcessStandardObjectEvents(ayaEvent, proposedObj, ct); + + //SPECIFIC EVENTS FOR THIS OBJECT + #region OLD + + //OLD general notification code removed in favor of specific event for review imminent + // //CREATED / MODIFIED + // if (ayaEvent == AyaEvent.Created || ayaEvent == AyaEvent.Modified) + // { + // //OVERDUE pseudo event + // { + // //Remove prior + // await NotifyEventHelper.ClearPriorEventsForObject(ct, proposedObj.SockType, proposedObj.Id, NotifyEventType.GeneralNotification);//assumes only general event for this object type is overdue here + + // //set a deadman automatic internal notification if goes past due + // var r = (Review)proposedObj; + + // //it not completed yet and not overdue already (which could indicate an import or something) + // if (r.CompletedDate == null && r.ReviewDate > DateTime.UtcNow) + // { + // //Notify user + // await NotifyEventHelper.EnsureDefaultInAppUserNotificationSubscriptionExists(r.UserId, ct); + // { + // var gensubs = await ct.NotifySubscription.Where(z => z.EventType == NotifyEventType.GeneralNotification && z.UserId == r.UserId).ToListAsync(); + // foreach (var sub in gensubs) + // { + // var eventNameTranslated = await TranslationBiz.GetTranslationForUserStaticAsync("ReviewOverDue", r.UserId); + // NotifyEvent n = new NotifyEvent() + // { + // EventType = NotifyEventType.GeneralNotification, + // UserId = r.UserId, + // ObjectId = proposedObj.Id, + // SockType = SockType.Review, + // NotifySubscriptionId = sub.Id, + // Name = $"{eventNameTranslated} - {proposedObj.Name}", + // EventDate = r.ReviewDate + // }; + // await ct.NotifyEvent.AddAsync(n); + // log.LogDebug($"Adding NotifyEvent: [{n.ToString()}]"); + // } + // if (gensubs.Count > 0) + // await ct.SaveChangesAsync(); + // } + + // //Notify supervisor + // if (r.UserId != r.AssignedByUserId) + // { + // await NotifyEventHelper.EnsureDefaultInAppUserNotificationSubscriptionExists(r.AssignedByUserId, ct); + // var gensubs = await ct.NotifySubscription.Where(z => z.EventType == NotifyEventType.GeneralNotification && z.UserId == r.AssignedByUserId).ToListAsync(); + // foreach (var sub in gensubs) + // { + // var eventNameTranslated = await TranslationBiz.GetTranslationForUserStaticAsync("ReviewOverDue", r.AssignedByUserId); + // NotifyEvent n = new NotifyEvent() + // { + // EventType = NotifyEventType.GeneralNotification, + // UserId = r.AssignedByUserId, + // ObjectId = proposedObj.Id, + // SockType = SockType.Review, + // NotifySubscriptionId = sub.Id, + // Name = $"{eventNameTranslated} - {proposedObj.Name}", + // EventDate = r.ReviewDate + // }; + // await ct.NotifyEvent.AddAsync(n); + // log.LogDebug($"Adding NotifyEvent: [{n.ToString()}]"); + // } + // if (gensubs.Count > 0) + // await ct.SaveChangesAsync(); + // } + // } + // }//overdue event + // }//custom events for created / modified + #endregion old + + //## DELETED EVENTS + + //any event added below needs to be removed, so + //just blanket remove any event for this object of eventtype that would be added below here + //do it regardless any time there's an update and then + //let this code below handle the refreshing addition that could have changes + await NotifyEventHelper.ClearPriorEventsForObject(ct, SockType.Review, o.Id, NotifyEventType.ReviewImminent); + + //## CREATED / MODIFIED EVENTS + if (ayaEvent == SockEvent.Created || ayaEvent == SockEvent.Modified) + { + //# REVIEW IMMINENT + if (o.CompletedDate == null && o.ReviewDate > DateTime.UtcNow) + { + //notify users (time delayed) + var subs = await ct.NotifySubscription.Where(z => z.EventType == NotifyEventType.ReviewImminent).ToListAsync(); + foreach (var sub in subs) + { + //not for inactive users + if (!await UserBiz.UserIsActive(sub.UserId)) continue; + + //Tag match? (will be true if no sub tags so always safe to call this) + if (NotifyEventHelper.ObjectHasAllSubscriptionTags(o.Tags, sub.Tags)) + { + + NotifyEvent n = new NotifyEvent() + { + EventType = NotifyEventType.ReviewImminent, + UserId = sub.UserId, + SockType = o.SockType, + ObjectId = o.Id, + NotifySubscriptionId = sub.Id, + Name = o.Name, + EventDate = o.ReviewDate + }; + await ct.NotifyEvent.AddAsync(n); + log.LogDebug($"Adding NotifyEvent: [{n.ToString()}]"); + await ct.SaveChangesAsync(); + } + } + + }//review imminent + + }//end of process notifications + + }//end of process notifications + + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/Search.cs b/server/biz/Search.cs new file mode 100644 index 0000000..4c887c1 --- /dev/null +++ b/server/biz/Search.cs @@ -0,0 +1,998 @@ +using System.Linq; +using System.Globalization; +using System.Text; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sockeye.Util; +using Sockeye.Models; + +namespace Sockeye.Biz +{ + + //This class handles word breaking, processing keywords and searching for results + public static class Search + { + + #region Search and return results + + public class SearchRequestParameters + { + public string Phrase { get; set; } + + public SockType TypeOnly { get; set; } + + //Note: maxresults of 0 will get all results + public int MaxResults { get; set; } + + public SearchRequestParameters() + { + + TypeOnly = SockType.NoType; + MaxResults = 500; + } + + public bool IsValid + { + get + { + //has a phrase? + if (!string.IsNullOrWhiteSpace(this.Phrase)) + return true; + return false; + } + } + } + + + //Classes to hold search results returned to client + public class SearchResult + { + public string Name { get; set; } + public SockType Type { get; set; } + public long Id { get; set; } + } + + public class SearchReturnObject + { + public long TotalResultsFound { get; set; } + public List SearchResults { get; set; } + public SearchReturnObject() + { + TotalResultsFound = 0; + SearchResults = new List(); + } + } + + + public static async Task DoSearchAsync(AyContext ct, long translationId, AuthorizationRoles currentUserRoles, long currentUserId, SearchRequestParameters searchParameters) + { + var ReturnObject = new SearchReturnObject(); + + //list to hold temporary search/tag hits + List MatchingObjects = new List(); + + if (!searchParameters.IsValid) + { + //this is expected, don't throw, just return nothing + //throw new System.ArgumentException("Search::DoSearch - Search request parameters must contain a phrase or tags"); + return ReturnObject; + } + + //escape literal percentage signs first just in case they are searching for 50% off or something + //https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE + //need to get around breaking possibly losing the symbol so make it text + searchParameters.Phrase = searchParameters.Phrase.Replace("%", "pctsym"); + + //Modify Phrase to replace wildcard * with % as breakcore expects sql style wildcards + searchParameters.Phrase = searchParameters.Phrase.Replace("*", "%"); + + //BREAK SEARCH PHRASE INTO SEPARATE TERMS + var PhraseItems = await BreakSearchPhraseAsync(translationId, searchParameters.Phrase); + + //SPLIT OUT WILDCARDS FROM NON WILDCARDS + List PreWildCardedSearchTerms = new List(); + List SearchTerms = new List(); + + foreach (string PhraseItem in PhraseItems) + { + if (PhraseItem.Contains("%")) + PreWildCardedSearchTerms.Add(PhraseItem.Replace("pctsym", @"\%"));//put back literal percentage symbol if necessary + else + SearchTerms.Add(PhraseItem.Replace("pctsym", @"\%"));//put back literal percentage symbol if necessary + } + + StringBuilder q = new StringBuilder(); + int termCount = 0; + + q.Append("WITH qr AS (SELECT asearchkey.sockType, asearchkey.objectid, "); + + //EXACT MATCH SEARCH TERMS + foreach (string Term in SearchTerms) + q.Append($"COUNT(*) FILTER (WHERE asearchdictionary.word = '{Term}') AS st{++termCount}, "); + + //WILDCARD SEARCH TERMS + foreach (string WildCardSearchTerm in PreWildCardedSearchTerms) + q.Append($"COUNT(*) FILTER (WHERE asearchdictionary.word LIKE '{WildCardSearchTerm}') AS st{++termCount}, "); + + q.Length=q.Length-2;//trim the final comma and space + + var qTypeOnly=string.Empty; + if(searchParameters.TypeOnly!=SockType.NoType){ + //INNER JOIN ASEARCHKEY ON ASEARCHDICTIONARY.ID = ASEARCHKEY.WORDID and asearchkey.sockType=20 + qTypeOnly=$"AND ASEARCHKEY.ATYPE={(int)searchParameters.TypeOnly}"; + } + + q.Append($" FROM asearchdictionary INNER JOIN asearchkey ON asearchdictionary.id = asearchkey.wordid {qTypeOnly} GROUP BY asearchkey.objectid, asearchkey.sockType) SELECT sockType, objectid FROM qr WHERE "); + + for (; termCount > 0; termCount--) + q.Append($"st{termCount} > 0 {(termCount > 1 ? "AND " : "")}"); + + + //execute the query and iterate the results + using (var command = ct.Database.GetDbConnection().CreateCommand()) + { + await ct.Database.OpenConnectionAsync(); + command.CommandText = q.ToString(); + using (var dr = await command.ExecuteReaderAsync()) + { + while (dr.Read()) + { + MatchingObjects.Add(new SockTypeId((SockType)dr.GetInt32(0), dr.GetInt64(1))); + } + } + + } + + + //REMOVE ANY ITEMS THAT USER IS NOT PERMITTED TO READ + //list to hold temporary matches + List CanReadMatchingObjects = new List(); + foreach (SockTypeId t in MatchingObjects) + { + if (t.SockType == SockType.FileAttachment) + { + //have to look up the actual underlying object type and id here + //check if it's readable for user + //then add the PARENT object type and id to the CanREadMatchingObjects list + //this means user will not see it return as an attachment, just as the object + FileAttachment f = await ct.FileAttachment.AsNoTracking().FirstOrDefaultAsync(z => z.Id == t.ObjectId); + if (Sockeye.Api.ControllerHelpers.Authorized.HasReadFullRole(currentUserRoles, f.AttachToAType)) + { + CanReadMatchingObjects.Add(new SockTypeId(f.AttachToAType, f.AttachToObjectId)); + } + } + else if (t.SockType == SockType.Memo) + { + //Users are only permitted to search their own memo's + if (await ct.Memo.AsNoTracking().AnyAsync(z => z.Id == t.ObjectId && z.ToId == currentUserId)) + CanReadMatchingObjects.Add(t); + } + else if (t.SockType == SockType.Reminder) + { + //Users are only permitted to search their own reminder's + if (await ct.Reminder.AsNoTracking().AnyAsync(z => z.Id == t.ObjectId && z.UserId == currentUserId)) + CanReadMatchingObjects.Add(t); + } + else + { + if (Sockeye.Api.ControllerHelpers.Authorized.HasReadFullRole(currentUserRoles, t.SockType)) + { + CanReadMatchingObjects.Add(t); + } + } + } + + //Ok, we're here with the list of allowable objects which is now the master matching objects list so... + MatchingObjects = CanReadMatchingObjects; + + //TOTAL RESULTS + //we have the total results here so set accordingly + ReturnObject.TotalResultsFound = MatchingObjects.Count; + + //MAXIMUM RESULTS FILTER + //The theory is that it should be filtered BEFORE sorting so that you get the most random collection of results + //As the results are not ranked so... + if (searchParameters.MaxResults > 0)//0 = all results + MatchingObjects = MatchingObjects.Take(searchParameters.MaxResults).ToList(); + + //Sort and group the matching objects list in return order + var OrderedMatchingObjects = MatchingObjects.OrderBy(z => z.SockType).ThenByDescending(z => z.ObjectId); + + //Get names using best performing technique + using (var command = ct.Database.GetDbConnection().CreateCommand()) + { + + ct.Database.OpenConnection(); + //Build the return list from the remaining matching objects list + foreach (SockTypeId i in OrderedMatchingObjects) + { + SearchResult SR = new SearchResult(); + SR.Name = BizObjectNameFetcherDirect.Name(i.SockType, + i.ObjectId,translationId, + command); + SR.Id = i.ObjectId; + SR.Type = i.SockType; + ReturnObject.SearchResults.Add(SR); + } + } + + return ReturnObject; + } + + + #endregion dosearch + + #region Get info (excerpt) + public static async Task GetInfoAsync(long translationId, AuthorizationRoles currentUserRoles, long userId, string phrase, int max, SockType sockType, long id, AyContext ct) + { + //escape literal percentage signs first just in case they are searching for 50% off or something + //https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE + //need to get around breaking possibly losing the symbol so make it text + phrase = phrase.Replace("%", "pctsym"); + + //Modify Phrase to replace wildcard * with % as breakcore expects sql style wildcards + phrase = phrase.Replace("*", "%"); + + //BREAK SEARCH PHRASE INTO SEPARATE TERMS + var PhraseItems = await BreakSearchPhraseAsync(translationId, phrase); + PhraseItems.ToArray(); + + //get text + ISearchAbleObject o = (ISearchAbleObject)BizObjectFactory.GetBizObject(sockType, ct, userId, currentUserRoles, translationId); + + //get extract + var searchParams = await o.GetSearchResultSummary(id, sockType); + + //extract and rank here + ExtractAndRank er = new ExtractAndRank(); + er.Process(searchParams, PhraseItems.ToArray(), max); + // sr.Extract = er.Extract; + // sr.Rank = er.Ranking; + + return er.Extract; + + } + + + + #region Search rank and extract + /// + /// Rank and extract best excerpt of specified text and search terms + /// + public sealed class ExtractAndRank + { + + #region Fields + private string[] searchTerms; + private string rawtext; + private string extract = ""; + private bool flattenExtract = true; + private float ranking; + private int extractionThresholdRank = 10; + private int maximumCharactersToExtract = 40; + #endregion + + #region Properties + + /// + /// This is the ranking of the source text as it pertains to the + /// search terms + /// + /// A rank of zero means either there was no match or the rank that was calculated + /// was lower than the threshold ranking, either way, no excerpt extraction is done. + /// + /// It is a percentage value on a scale of 0 to 100 + /// and is weighted: + /// + /// 75% of the score is the percentage of all search terms found in the text + /// 25% of the score is the percentage of all characters in the text that are search term characters + /// + /// + /// + public float Ranking + { + get + { + return ranking; + } + } + + /// + /// Maximum characters to appear in an extraction + /// default is 80 + /// Minimum is 10 + /// + public int MaximumCharactersToExtract + { + get + { + return maximumCharactersToExtract; + } + set + { + + if (value > 10) + maximumCharactersToExtract = value; + else + maximumCharactersToExtract = 10; + + } + } + + /// + /// ExtractionThresholdRank + /// Extraction will only take place if the rank is + /// this value or higher + /// + /// default is 10, maximum is 100 minimum is 0 + /// + public int ExtractionThresholdRank + { + get + { + return extractionThresholdRank; + } + set + { + if (value > 100) + extractionThresholdRank = 100; + else if (value < 0) + extractionThresholdRank = 0; + else + extractionThresholdRank = value; + } + } + + + + /// + /// If true, carriage returns and line feeds will be removed from extract + /// + public bool FlattenExtract + { + get + { + return this.flattenExtract; + } + set + { + this.flattenExtract = value; + } + } + + /// + /// Extracted text excerpt that best reflects search terms + /// + public string Extract + { + get + { + return extract; + } + } + + #endregion + + #region public methods + /// + /// Do the extraction and ranking + /// + public void Process(SearchIndexProcessObjectParameters searchObjectParams, string[] searchTerms, int max) + { + this.maximumCharactersToExtract = max; + + ranking = 0; + extract = ""; + + string rawText = string.Join(" ", searchObjectParams.Words); + + //System.Diagnostics.Debug.Assert(rawText!=null && rawText!="","EXTRACT AND RANK","EMPTY RAWTEXT, CHECK OBJECTS GetSearchResult() CODE TO ENSURE IT'S GOT THE correct SP (CHECK THE SP IF NOT)"); + if (rawText == null || rawText == "") return; + this.rawtext = rawText; + + if (searchTerms == null || searchTerms.Length == 0) return; + this.searchTerms = searchTerms; + + + ranking = score(0, this.rawtext.Length); + if (ranking > extractionThresholdRank) + DoExtract(); + } + #endregion + + #region Calculate score + /// + /// Give a percentage score for a given window of + /// text in the raw text string + /// 75% of the score is the percentage of all search terms found in the window + /// 25% of the score is the percentage of all characters in the search window that are search term characters + /// + /// + /// + /// + /// + /// + /// Float value of zero to one hundred + private float score(int nStartPos, int nEndPos) + { + //rewrite this as an integer based calculation + + System.Diagnostics.Debug.Assert(nStartPos < nEndPos); + if (nStartPos < 0) nStartPos = 0; + if (nEndPos > this.rawtext.Length) nEndPos = this.rawtext.Length; + + int nTermCharsInWindow = 0;//how many of the characters in the window are matching term characters + string SearchString = this.rawtext.Substring(nStartPos, nEndPos - nStartPos).ToLower(System.Globalization.CultureInfo.CurrentCulture); + + int nMatches = 0; + + foreach (string term in searchTerms) + { + //remove the wild card character if present and set to lower case + string lTerm = term.ToLower(System.Globalization.CultureInfo.CurrentCulture).Replace("%", ""); + int nLocation = SearchString.IndexOf(lTerm); + if (nLocation != -1) + { + nMatches++; + while (nLocation != -1) + { + nTermCharsInWindow += lTerm.Length; ; + nLocation = SearchString.IndexOf(lTerm, nLocation + 1); + + } + + } + } + + //If no matches then rank is automatically zero + if (nMatches == 0) + { + return 0; + } + + + + //Rank is calculated on a weighted scale + //75% for matching all search terms + //25% for the quantity of search terms versus other text found + float fTermsFoundPct = 75 * ((float)nMatches / (float)searchTerms.GetLength(0)); + float fTermsVsTextPct = 0; + if (nTermCharsInWindow > 0) + fTermsVsTextPct = 25 * ((float)nTermCharsInWindow / (float)SearchString.Length); + + return fTermsFoundPct + fTermsVsTextPct; + + } + #endregion + + #region Extract best excerpt + /// + /// Extract the best scoring excerpt fragments of + /// raw text + /// + private void DoExtract() + { + //If the whole thing is less than the max to extract + //just save time and return the whole thing + if (this.rawtext.Length < this.maximumCharactersToExtract) + { + this.extract = this.rawtext; + return; + } + + string BestWindow = ""; + float BestScore = 0; + float thisscore = 0; + int BestWindowStartPos = 0; + + //Get the shortest search term length so + //we can save time iterating over the window in the extract + //function below + int shortestSearchTermLength = int.MaxValue; + foreach (string s in this.searchTerms) + { + if (s.Length < shortestSearchTermLength) + shortestSearchTermLength = s.Length; + + } + + + //slide a window over the text and check it's score, the highest scoring window wins + //move the length of the shortest search term so as to ensure we won't + //miss it, but faster than moving one character at a time + for (int z = 0; z < this.rawtext.Length - maximumCharactersToExtract; z += shortestSearchTermLength) + { + thisscore = score(z, z + (maximumCharactersToExtract)); + + if (thisscore == 0) continue; + + if (thisscore > BestScore) + { + BestScore = thisscore; + BestWindow = this.rawtext.Substring(z, maximumCharactersToExtract); + //Best window to get if the future score is equal + //I.E. put the terms in the center of the window if + //the score is equal + BestWindowStartPos = z + (maximumCharactersToExtract / 2); + } + + //If it's equal to the last and we're positioned over + //the best spot (terms in center) then capture that + if (thisscore == BestScore && z == BestWindowStartPos) + { + BestWindow = this.rawtext.Substring(z, maximumCharactersToExtract); + + } + } + + if (this.flattenExtract) + this.extract = "..." + BestWindow.Trim().Replace("\r", "").Replace("\n", "").Replace("\t", "") + "...";//case 1593 added tab character removal + else + this.extract = "..." + BestWindow.Trim() + "..."; + + + } + + + //======================================================================== + + #endregion + + } + #endregion Xtract + + + #endregion + + #region ProcessKeywords into Database + + //Class to hold process input parameters + //also used for getting summary search results + public class SearchIndexProcessObjectParameters + { + public long TranslationId { get; set; } + public long ObjectId { get; set; } + public SockType SockType { get; set; } + public List Words { get; set; } + + + public SearchIndexProcessObjectParameters(long translationId, long objectID, SockType aType) + { + Words = new List(); + TranslationId = translationId; + ObjectId = objectID; + SockType = aType; + } + + //format used for getsummmary by biz objects + public SearchIndexProcessObjectParameters() + { + Words = new List(); + TranslationId = 0; + ObjectId = 0; + SockType = 0; + } + + public SearchIndexProcessObjectParameters AddText(string s) + { + if (!string.IsNullOrWhiteSpace(s)) + { + Words.Add(s); + } + return this; + } + + + public SearchIndexProcessObjectParameters AddText(long l) + { + Words.Add(l.ToString()); + return this; + } + + // public SearchIndexProcessObjectParameters AddText(decimal? d) + // { + // if (d != null) + // Words.Add(d.ToString()); + // return this; + // } + + public SearchIndexProcessObjectParameters AddText(List lWords) + { + if (lWords != null) + { + foreach (string s in lWords) + { + if (!string.IsNullOrWhiteSpace(s)) + { + Words.Add(s); + } + } + } + + return this; + } + public SearchIndexProcessObjectParameters AddCustomFields(string jsonString) + { + //Extract the text from custom fields json fragment as an array of strings and add it here + AddText(JsonUtil.GetCustomFieldsAsStringArrayForSearchIndexing(jsonString)); + return this; + } + } + + public static async Task ProcessNewObjectKeywordsAsync(SearchIndexProcessObjectParameters searchIndexObjectParameters) + { + await ProcessKeywordsAsync(searchIndexObjectParameters, true); + } + + public static async Task ProcessUpdatedObjectKeywordsAsync(SearchIndexProcessObjectParameters searchIndexObjectParameters) + { + await ProcessKeywordsAsync(searchIndexObjectParameters, false); + } + + public static async Task ProcessDeletedObjectKeywordsAsync(long objectID, SockType aType, AyContext ct) + { + //Be careful in future, if you put ToString at the end of each object in the string interpolation + //npgsql driver will assume it's a string and put quotes around it triggering an error that a string can't be compared to an int + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from asearchkey where objectid={objectID} and aType={(int)aType}"); + //nothing to save here, it's a direct command already executed + } + + + /// + /// Process the keywords into the dictionary + /// + private static async Task ProcessKeywordsAsync(SearchIndexProcessObjectParameters p, bool newRecord) + { + // #if (DEBUG) + // if (!p.SockType.HasAttribute(typeof(CoreBizObjectAttribute))) + // throw new System.NotSupportedException($"Search::ProcessKeywords - Invalid type presented {p.SockType}"); + // #endif + List KeyWordList = await BreakAsync(p.TranslationId, p.Words); + + if (KeyWordList.Count == 0) return; + //call stored procedure to do the work right at the server (fastest method by far) + using (AyContext ct = ServiceProviderProvider.DBContext) + await ct.Database.ExecuteSqlInterpolatedAsync($"call aydosearchindex({KeyWordList},{p.ObjectId},{p.SockType},{!newRecord})"); + return; + }//eoc + #endregion + + #region Breaker + + public enum TokenTypes + { Nothing, Separator, CJK, Latin }; + + /// + /// Take an array of strings and + /// return a single string + /// containing unique only, lowercase comma delimited + /// keywords suitable for passing to a + /// stored procedure or other function + /// + /// Use Translation setting CJKIndex=true to handle Chinese, Japanese, Korean etc + /// (languages with no easily identifiable word boundaries as in english) + /// + /// List of strings + internal static async Task> BreakAsync(long translationId, List textStrings) + { + return await BreakCoreAsync(translationId, false, textStrings); + } + + /// + /// + /// + internal static async Task> BreakAsync(long translationId, string textString) + { + List textStrings = new List(1); + textStrings.Add(textString); + return await BreakCoreAsync(translationId, false, textStrings); + } + + /// + /// Used to Process users search phrase and preserve wild + /// cards entered + /// + internal static async Task> BreakSearchPhraseAsync(long translationId, string searchPhrase) + { + List textStrings = new List(); + textStrings.Add(searchPhrase); + //note: we want stopwords if this is a search phrase break because they might type "some" wanting awesome but some is a stopword so.. + return await BreakCoreAsync(translationId, true, textStrings, true); + } + + + + internal static async Task> BreakCoreAsync(long translationId, bool KeepWildCards, List textStrings, bool ignoreStopWords = false) + { + //For stopwords and CJKIndex flag value + var translationWordBreakData = await SearchTranslationWordBreakDataCache.GetWordBreakData(translationId); + + int MAXWORDLENGTH = 255; + int MINWORDLENGTH = 2;//A word isn't a word unless it's got at least two characters in it + StringBuilder sbResults = new StringBuilder(); + //List to temporarily hold parsed words + //used to easily ensure unique words only + List tempParsedWords = new List(); + + StringBuilder sb = new StringBuilder(); + StringBuilder sbWord = new StringBuilder(); + List ReturnList = new List(); + + + //Loop through each of the passed in strings + foreach (string s in textStrings) + { + if (s == null || s == "") continue; + //get all the characters in a unicode compliant manner... + TextElementEnumerator t = StringInfo.GetTextElementEnumerator(s); + //start at the top + t.Reset(); + + TokenTypes LastToken = TokenTypes.Nothing; + + //Used by CJK + bool BasicLatinBlock = true; + + //Process each "character" (text element,glyph whatever) in the + //current string + while (t.MoveNext()) + { + //get it as a character + char c = t.GetTextElement()[0]; + + if (!translationWordBreakData.CJKIndex) + { + #region regular tokenizer + + //Is it a token we want to include? + //Or a wildcard character + if (char.IsLetterOrDigit(c) || (KeepWildCards && c == '%')) + { + #region Include token + //All latin text is converted to lower case + c = char.ToLower(c, System.Globalization.CultureInfo.CurrentCulture); + + //Do we already have a word? + if (sbWord.Length > 0) + { + //Maybe we need to flush this word into the word list + //if we're over the word length limit + if (sbWord.Length >= MAXWORDLENGTH) + { + //flush away... + if (!tempParsedWords.Contains(sbWord.ToString())) + { + tempParsedWords.Add(sbWord.ToString()); + } + sbWord.Length = 0; + sbWord.Append(c); + LastToken = TokenTypes.Latin; + continue; + + } + } + + //append character and go on to next one + sbWord.Append(c); + LastToken = TokenTypes.Latin; + continue; + #endregion + } + else + { + #region Word Boundary token + LastToken = TokenTypes.Separator; + if (sbWord.Length > 0) + { + //flush away... + if (!tempParsedWords.Contains(sbWord.ToString())) + { + tempParsedWords.Add(sbWord.ToString()); + } + sbWord.Length = 0; + continue; + } + + #endregion + } + #endregion + } + else + { + #region CJK Tokenizer + + //Is it a basic latin charater? (ascii basically) + //see: http://www.unicode.org/charts/index.html + //and here for a funky online viewer: + //http://www.fileformat.info/info/unicode/block/index.htm + //we need to know this so that regular english text + //within cjk text gets properly indexed as whole words + BasicLatinBlock = false; + if ((int)c < 256) BasicLatinBlock = true; + + if (BasicLatinBlock) + { + //Is it a token we want to include? + if (char.IsLetterOrDigit(c) || (KeepWildCards && c == '%')) + { + #region Latin Include token + //All latin text is converted to lower case + c = char.ToLower(c, System.Globalization.CultureInfo.CurrentCulture); + + //Do we already have a word? + if (sbWord.Length > 0) + { + //Maybe we need to flush this word into the word list + //if we're over the word length limit or we are going from + //CJK to latin + if (LastToken == TokenTypes.CJK || sbWord.Length >= MAXWORDLENGTH) + { + //flush away... + if (!tempParsedWords.Contains(sbWord.ToString())) + { + tempParsedWords.Add(sbWord.ToString()); + } + sbWord.Length = 0; + sbWord.Append(c); + LastToken = TokenTypes.Latin; + continue; + + } + } + + //append character and go on to next one + sbWord.Append(c); + LastToken = TokenTypes.Latin; + continue; + #endregion + } + else + { + #region Latin Word Boundary token + LastToken = TokenTypes.Separator; + if (sbWord.Length > 0) + { + //flush away... + if (!tempParsedWords.Contains(sbWord.ToString())) + { + tempParsedWords.Add(sbWord.ToString()); + } + sbWord.Length = 0; + + continue; + + } + + #endregion + } + + } + else//CJK character + { + if (char.IsLetter(c) || (KeepWildCards && c == '%')) + { + #region CJK Include token + //Do we already have a word? + if (sbWord.Length > 0) + { + //Maybe we need to flush this word into the word list + //if we're over the word length limit or we are going from + //latin TO CJK + if (LastToken == TokenTypes.Latin || sbWord.Length >= MAXWORDLENGTH) + { + //flush away... + if (!tempParsedWords.Contains(sbWord.ToString())) + { + tempParsedWords.Add(sbWord.ToString()); + } + sbWord.Length = 0; + sbWord.Append(c); + LastToken = TokenTypes.CJK; + continue; + + } + + if (LastToken == TokenTypes.CJK) + { + //we're here because there is more than zero characters already stored + //and the last was CJK so we need append current character + //and flush the resultant 2 character n-gram + sbWord.Append(c); + System.Diagnostics.Debug.Assert(sbWord.Length == 2); + //flush away... + if (!tempParsedWords.Contains(sbWord.ToString())) + { + tempParsedWords.Add(sbWord.ToString()); + } + sbWord.Length = 0; + sbWord.Append(c); + LastToken = TokenTypes.CJK; + continue; + + } + } + + //append character and go on to next one + sbWord.Append(c); + LastToken = TokenTypes.CJK; + continue; + #endregion + + + } + else + { + #region CJK Word Boundary token + LastToken = TokenTypes.Separator; + if (sbWord.Length > 0) + { + //flush away... + if (!tempParsedWords.Contains(sbWord.ToString())) + { + tempParsedWords.Add(sbWord.ToString()); + } + sbWord.Length = 0; + continue; + } + + #endregion + } + + } + + #endregion + } + } + + //Flush out the last word + if (sbWord.Length > 0) + { + //flush away... + if (!tempParsedWords.Contains(sbWord.ToString())) + { + tempParsedWords.Add(sbWord.ToString()); + } + sbWord.Length = 0; + } + } + + + //bail early if there is nothing indexed + if (tempParsedWords.Count == 0) return ReturnList; + + + //Make a return string array + //from the word list + foreach (string s in tempParsedWords) + { + //Filter out short words if we are breaking for indexing + //but keep them if they are part of a wildcard search phrase + if (s.Length >= MINWORDLENGTH || (KeepWildCards && s.Contains('%'))) + { + if (ignoreStopWords) + { + //breaking of search phrase + ReturnList.Add(s); + } + else + { + //Add only non stopwords - regular breaking of object for dictionary entry + if (!translationWordBreakData.StopWords.Contains(s)) + { + ReturnList.Add(s); + } + } + } + } + + //sometimes all the results are stop words so you end up here with nothing + return ReturnList; + + } + + #endregion + + }//eoc + +}//eons \ No newline at end of file diff --git a/server/biz/SearchTranslationWordBreakDataCache.cs b/server/biz/SearchTranslationWordBreakDataCache.cs new file mode 100644 index 0000000..6c74007 --- /dev/null +++ b/server/biz/SearchTranslationWordBreakDataCache.cs @@ -0,0 +1,77 @@ +using System.Linq; +using System.Threading.Tasks; +using System.Threading; +using System.Collections.Generic; +using Sockeye.Util; +using Sockeye.Models; +namespace Sockeye.Biz +{ + + public class SearchTranslationWordBreakDataCache + { + static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + private static Dictionary theCache = new Dictionary(); + public SearchTranslationWordBreakDataCache() { } + public static async Task GetWordBreakData(long id) + { + await semaphoreSlim.WaitAsync(); + try + { + if (!theCache.ContainsKey(id)) + theCache.Add(id, await GetTranslationSearchDataAsync(id)); + return theCache.First(z=>z.Key==id).Value; + } + finally + { + semaphoreSlim.Release(); + } + } + + + + internal static async Task GetTranslationSearchDataAsync(long translationId) + { + TranslationWordBreakingData LSD = new TranslationWordBreakingData(); + using (AyContext ct = ServiceProviderProvider.DBContext) + { + //Get stopwords + //Validate translation id, if not right then use default instead + var Param = new List(); + translationId = await TranslationBiz.ReturnSpecifiedTranslationIdIfExistsOrDefaultTranslationId(translationId, ct); + Param.Add("StopWords1"); + Param.Add("StopWords2"); + Param.Add("StopWords3"); + Param.Add("StopWords4"); + Param.Add("StopWords5"); + Param.Add("StopWords6"); + Param.Add("StopWords7"); + var Stops = await TranslationBiz.GetSubsetStaticAsync(Param, translationId); + + foreach (KeyValuePair kvp in Stops) + { + //Each stopwords translation key is a space delimited list of words and in the case of an empty local string (i.e. StopWords7) it's value is a single question mark + if (kvp.Value != "?") + { + LSD.StopWords.AddRange(kvp.Value.Split(" ")); + } + } + + LSD.CJKIndex = await TranslationBiz.GetCJKIndexAsync(translationId, ct); + } + return LSD; + } + + //Class to hold relevant translation data for breaking text + public class TranslationWordBreakingData + { + public bool CJKIndex { get; set; } + public List StopWords { get; set; } + public TranslationWordBreakingData() + { + CJKIndex = false; + StopWords = new List(); + } + } + + }//eoc +}//eons \ No newline at end of file diff --git a/server/biz/SockDaysOfWeek.cs b/server/biz/SockDaysOfWeek.cs new file mode 100644 index 0000000..3397769 --- /dev/null +++ b/server/biz/SockDaysOfWeek.cs @@ -0,0 +1,25 @@ +using System; +namespace Sockeye.Biz +{ + /// + /// Days of week + /// + [Flags] + public enum SockDaysOfWeek : int + { + //https://stackoverflow.com/questions/8447/what-does-the-flags-enum-attribute-mean-in-c + //MAX 31 (2147483647)!!! or will overflow int and needs to be turned into a long + //Must be a power of two: https://en.wikipedia.org/wiki/Power_of_two + //bitwise selection of days of week + //https://stackoverflow.com/a/24174625/8939 + + Monday = 1, + Tuesday = 2, + Wednesday = 4, + Thursday = 8, + Friday = 16, + Saturday = 32, + Sunday = 64 + } +} + diff --git a/server/biz/SockEvent.cs b/server/biz/SockEvent.cs new file mode 100644 index 0000000..7e0aec4 --- /dev/null +++ b/server/biz/SockEvent.cs @@ -0,0 +1,37 @@ +namespace Sockeye.Biz +{ + + + /// + /// All Sockeye event types + /// Used for central biz logging and notification + /// + public enum SockEvent : int + { + //common events + Deleted = 0, + Created = 1, + Retrieved = 2, + Modified = 3, + + //specific events + AttachmentCreate = 4, + AttachmentDelete = 5, + AttachmentDownload = 6, + + LicenseFetch = 7, + LicenseTrialRequest = 8, + ServerStateChange = 9, + SeedDatabase = 10, + AttachmentModified = 11, + EraseAllData = 12, + ResetSerial = 13, + UtilityFileDownload = 14, + DirectSMTP = 15//NotifyEventDirectSMTPMessage key can work for this too + + //NEW ITEMS REQUIRE translation KEYS and update CLIENT sock-history.vue code in eventypes list and translation fetcher + + } + + +}//eons diff --git a/server/biz/SockType.cs b/server/biz/SockType.cs new file mode 100644 index 0000000..1994215 --- /dev/null +++ b/server/biz/SockType.cs @@ -0,0 +1,85 @@ +namespace Sockeye.Biz +{ + + /// + /// All Sockeye types and their attributes indicating what features that type will support (tagging, attachments etc) + /// + public enum SockType : int + { + //COREBIZOBJECT attribute must be set on objects that are: + //Attachable objects can have attachments, + //wikiable objects can have a wiki + //reviewable objects can have a review which is basically the same as a Reminder but with an object attached (was follow up schedmarker in v7) + //PIckList-able (has picklist template) + //Pretty much everything that represents some kind of real world object is wikiable or attachable as long as it has an ID and a type + //exceptions would be utility type objects like dataListFilter, dataListColumn, formcustom etc that are not + + //NOTE: NEW CORE OBJECTS - All areas of server AND CLIENT code that require adding any new core objects have been tagged with the following comment: + //CoreBizObject add here + //Search for that IN SERVER AND CLIENT CODE and you will see all areas that need coding for the new object + + //***IMPORTANT: Also need to add translations for any new biz objects added that don't match exactly the name here in the key + //because enumlist gets it that way, i.e. "Global" would be the expected key + + NoType = 0, + Global = 1, + FormUserOptions = 2, + [CoreBizObject, ReportableBizObject] + User = 3, + ServerState = 4, + LogFile = 6, + PickListTemplate = 7, + [CoreBizObject, ReportableBizObject, ImportableBizObject] + Customer = 8, + ServerJob = 9, + + ServerMetrics = 12, + Translation = 13, + UserOptions = 14, + [CoreBizObject, ReportableBizObject, ImportableBizObject] + HeadOffice = 15, + FileAttachment = 17, + DataListSavedFilter = 18, + FormCustom = 19, + GlobalOps = 47,//really only used for rights, not an object type of any kind + BizMetrics = 48,//deprecate? Not used for anything as of nov 2020 + Backup = 49, + Notification = 50, + NotifySubscription = 51, + [CoreBizObject, ReportableBizObject] + Reminder = 52, + + OpsNotificationSettings = 56, + Report = 57, + DashboardView = 58, + [CoreBizObject, ReportableBizObject] + CustomerNote = 59, + [CoreBizObject, ReportableBizObject] + Memo = 60, + [CoreBizObject, ReportableBizObject] + Review = 61, + DataListColumnView = 68, + CustomerNotifySubscription = 84,//proxy subs for customers + Integration = 92 //3rd party or add-on integration data store + + + + + + //NOTE: New objects added here need to also be added to the following classes: + //Sockeye.Biz.BizObjectExistsInDatabase + //Sockeye.Biz.BizObjectFactory + //Sockeye.Biz.BizRoles + //Sockeye.Biz.BizObjectNameFetcherDIRECT + //and in the CLIENT in socktype.js + //and their model needs to have the ICoreBizObjectModel interface + + //and need TRANSLATION KEYS because any type could show in the event log at the client end + + //AND QBI mirrors this too + + } + + +}//eons + diff --git a/server/biz/SockTypeId.cs b/server/biz/SockTypeId.cs new file mode 100644 index 0000000..2893a5d --- /dev/null +++ b/server/biz/SockTypeId.cs @@ -0,0 +1,113 @@ +using System; +using Sockeye.Models; + +namespace Sockeye.Biz +{ + + public class SockTypeId : System.Object + { + private long _id; + private SockType _sockType; + + public long ObjectId + { + get + { + return _id; + } + } + + public SockType SockType + { + get + { + return _sockType; + } + } + + public int ATypeAsInt + { + get + { + return (int)_sockType; + } + } + + public SockTypeId(SockType aType, long Id) + { + _id = Id; + _sockType = aType; + } + + [Newtonsoft.Json.JsonConstructor] + public SockTypeId(string sAType, string sId) + { + _id = long.Parse(sId); + int nType = int.Parse(sAType); + if (!SockTypeExists(nType)) + _sockType = SockType.NoType; + else + _sockType = (SockType)Enum.Parse(typeof(SockType), sAType); + } + + + + public bool Equals(SockTypeId x, SockTypeId y) + { + + //Check whether the compared objects reference the same data. + if (Object.ReferenceEquals(x, y)) return true; + + //Check whether any of the compared objects is null. + if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) + return false; + + //Check whether the products' properties are equal. + return x.ObjectId == y.ObjectId && x.SockType == y.SockType; + } + + public bool IsEmpty + { + get + { + return (_sockType == SockType.NoType) || (_id == 0); + } + } + + + + + /// + /// Check if the numeric or name type value is an actual enum value + /// + /// + /// + public bool SockTypeExists(int enumNumber) + { + return Enum.IsDefined(typeof(SockType), enumNumber); + } + + + //Custom attribute checking + + /// + /// Is object a core biz object + /// + /// + public bool IsCoreBizObject + { + get + { + return this.SockType.HasAttribute(typeof(CoreBizObjectAttribute)); + } + } + + + + + + }//eoc + + +}//eons + diff --git a/server/biz/TagBiz.cs b/server/biz/TagBiz.cs new file mode 100644 index 0000000..9345e96 --- /dev/null +++ b/server/biz/TagBiz.cs @@ -0,0 +1,347 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using System.Collections.Generic; +using System; +using System.Linq; + + +namespace Sockeye.Biz +{ + + internal class TagBiz : BizObject//, IJobObject + { + internal TagBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = SockType.NoType; + } + + internal static TagBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new TagBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new TagBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + + #region Utilities + ///////////////////////////////////// + //UTILITIES + // + + //clean up tags from client submission + //remove dupes, substitute dashes for spaces, lowercase and shorten if exceed 255 chars + //and sorts before returning to ensure consistent ordering + public static List NormalizeTags(List inTags) + { + if (inTags == null || inTags.Count == 0) return inTags; + + List outTags = new List(); + foreach (var tag in inTags) + outTags.Add(NormalizeTag(tag)); + outTags.Sort(); + return outTags.Distinct().ToList(); + } + + + public static string NormalizeTag(string inObj) + { + if (string.IsNullOrWhiteSpace(inObj)) return null; + //Must be lowercase per rules + //This may be naive when we get international cust omers but for now supporting utf-8 and it appears it's safe to do this with unicode + inObj = inObj.ToLower(System.Globalization.CultureInfo.CurrentCulture); + //No spaces in tags, replace with dashes + inObj = inObj.Replace(" ", "-"); + //Remove multiple dash sequences + inObj = System.Text.RegularExpressions.Regex.Replace(inObj, "-+", "-"); + //Ensure doesn't start or end with a dash + inObj = inObj.Trim('-'); + //No longer than 255 characters + inObj = StringUtil.MaxLength(inObj, 255); + return inObj; + } + + + + public static async Task ProcessDeleteTagsInRepositoryAsync(AyContext ct, List deleteTags) + { + if (deleteTags.Count == 0) return; + + var existing = await ct.Tag.Where(x => deleteTags.Contains(x.Name)).ToListAsync(); + foreach (string s in deleteTags) + { + var t = existing.FirstOrDefault(x => x.Name == s); + if (t != null) + { + if (t.RefCount < 2)//catch any that fell through the cracks and are maybe zero or negative event + ct.Remove(t); + else + t.RefCount -= 1; + } + + } + await ct.SaveChangesAsync(); + + #region OLD SLOW METHOD FOR REFERENCE IN CASE CONCURRENCY EXCEPTIONS + // foreach (string s in deleteTags) + // { + // bool bDone = false; + // //Keep on trying until there is success + // //this allows for concurrency issues + // //I considered a circuit breaker / timeout here, but theoretically it should not be an issue + // //at some point it should not be a concurrency issue anymore + // //And this is not critical functionality requiring a transaction and locking + // do + // { + // //START: Get tag word and concurrency token and count + // var ExistingTag = await ct.Tag.FirstOrDefaultAsync(z => z.Name == s); + // //if not present, then nothing to do + // if (ExistingTag != null) + // { + // //No longer needed? + // if (ExistingTag.RefCount == 1) + // { + // ct.Remove(ExistingTag); + + // } + // else + // { + // //Decrement the refcount + // ExistingTag.RefCount -= 1; + // } + + // try + // { + // await ct.SaveChangesAsync(); + // bDone = true; + // } + // catch (Exception ex) when (ex is DbUpdateConcurrencyException)//allow for possible other types + // { + // //allow others to flow past + // // string sss = ex.ToString(); + // } + // } + // else + // { + // bDone = true; + // } + // } while (bDone == false); + // } + #endregion old slow method + + + } + + public static async Task ProcessUpdateTagsInRepositoryAsync(AyContext ct, List newTags, List originalTags = null) + { + //todo: Recode this as a procedure like search indexing or at least a direct sql call + + + //just in case no new tags are present which could mean a user removed all tags from a record so this + //needs to proceed with the code below even if newTags is null as long as originalTags isn't also null + if (newTags == null) newTags = new List(); + if (originalTags == null) originalTags = new List(); + + if (newTags.Count == 0 && originalTags.Count == 0) return; + + List deleteTags = new List(); + List addTags = new List(); + + if (originalTags != null) + { + //Update + //This logic to only come up with CHANGES, if the item is in both lists then it will disappear and not need to be dealt with as it's refcount is unchanged in this operation + //testing will validate it + deleteTags = originalTags.Except(newTags).ToList(); + addTags = newTags.Except(originalTags).ToList(); + } + else + { + //Add + addTags = newTags; + } + + #region OLD SLOW METHOD FOR REFERENCE IN CASE CONCURRENCY EXCEPTIONS + // //ADD / INCREMENT TAGS + // //one by one method + // foreach (string s in addTags) + // { + // bool bDone = false; + // //Keep on trying until there is success + // //this allows for concurrency issues + // //I considered a circuit breaker / timeout here, but theoretically it should not be an issue + // //at some point it should not be a concurrency issue anymore + // do + // { + // //START: Get tag word and concurrency token and count + // var ExistingTag = await ct.Tag.FirstOrDefaultAsync(z => z.Name == s); + // //if not present, then add it with a count of 0 + // if (ExistingTag == null) + // { + // await ct.Tag.AddAsync(new Tag() { Name = s, RefCount = 1 }); + // } + // else + // { + // //Update the refcount + // ExistingTag.RefCount += 1; + // } + // try + // { + // await ct.SaveChangesAsync(); + // bDone = true; + // } + // catch (Exception ex) when (ex is DbUpdateConcurrencyException)//this allows for other types + // { + + // Console.WriteLine("TagBiz::Exception udring update tags"); + // //allow others to flow past + // //string sss = ex.ToString(); + // } + // } while (bDone == false); + // } + + #endregion old slow method + + //ADD / INCREMENT TAGS + var existing = await ct.Tag.Where(x => addTags.Contains(x.Name)).ToListAsync(); + foreach (string s in addTags) + { + var t = existing.FirstOrDefault(x => x.Name == s); + if (t != null) + { + t.RefCount += 1; + } + else + { + ct.Tag.Add(new Tag() { Name = s, RefCount = 1 }); + } + } + await ct.SaveChangesAsync(); + + + + //DELETE TAGS + await ProcessDeleteTagsInRepositoryAsync(ct, deleteTags); + } + + + + //Pick list for driving pick list route + //going with contains for now as I think it's more useful in the long run and still captures startswith intent by user + public static async Task> TagListFilteredAsync(AyContext ct, string q) + { + //This path is intended for internal use and accepts that there may not be a filter specified + //however the client will always require a filter to display a tag list for choosing from + if (string.IsNullOrWhiteSpace(q)) + { + return await ct.Tag.OrderBy(z => z.Name) + .Select(z => z.Name) + .AsNoTracking() + .ToListAsync(); + } + else + { + q = NormalizeTag(q); + return await ct.Tag + .Where(z => z.Name.Contains(q)) + .OrderBy(z => z.Name) + .Select(z => z.Name) + .Take(25) + .AsNoTracking() + .ToListAsync(); + } + } + + //Cloud list + public static async Task> CloudListFilteredAsync(AyContext ct, string q) + { + //This path is intended for internal use and accepts that there may not be a filter specified + //however the client will always require a filter to display a tag list for choosing from + if (string.IsNullOrWhiteSpace(q)) + { + return await ct.Tag.OrderByDescending(z => z.RefCount).Select(z => new TagCloudItem() { Name = z.Name, RefCount = z.RefCount }).AsNoTracking().ToListAsync(); + } + else + { + q = NormalizeTag(q); + //TODO: Use the EF CORE TAKE method to restrict the results to a maximum limit + //however need to ensure it doesn't balk when the limit is higher than the number of results (probably not but test that) + return await ct.Tag + .Where(z => z.Name.Contains(q)) + .OrderByDescending(z => z.RefCount) + .Select(z => new TagCloudItem() { Name = z.Name, RefCount = z.RefCount }) + .AsNoTracking() + .ToListAsync(); + } + } + public class TagCloudItem + { + public long RefCount { get; set; } + public string Name { get; set; } + } + + + + + /// + /// Process batch tag operation + /// + /// true if object needs to be saved or false if no changes were made + internal static bool ProcessBatchTagOperation(List tagCollection, string tag, string toTag, JobSubType subType) + { + switch (subType) + { + case JobSubType.TagAddAny: + case JobSubType.TagAdd: + if (!tagCollection.Contains(tag)) + { + tagCollection.Add(tag); + return true; + } + return false; + case JobSubType.TagRemoveAny: + case JobSubType.TagRemove: + return tagCollection.Remove(tag); + case JobSubType.TagReplaceAny: + case JobSubType.TagReplace: + int index = tagCollection.IndexOf(tag); + if (index != -1) + { + tagCollection[index] = toTag; + return true; + } + return false; + default: + throw new System.ArgumentOutOfRangeException($"ProcessBatchTagOperation -> Invalid job Subtype{subType}"); + } + } + + + #endregion utilities + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //JOB / OPERATIONS + // + + + + //Other job handlers here... + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/TranslationBiz.cs b/server/biz/TranslationBiz.cs new file mode 100644 index 0000000..9684284 --- /dev/null +++ b/server/biz/TranslationBiz.cs @@ -0,0 +1,690 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using Microsoft.Extensions.Logging; + +namespace Sockeye.Biz +{ + + internal class TranslationBiz : BizObject + { + + + internal TranslationBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles userRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = userRoles; + BizType = SockType.Translation; + } + + internal static TranslationBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new TranslationBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new TranslationBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.Translation.AnyAsync(z => z.Id == id); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(Translation putObject) + { + //todo: update to use new PUT methodology? + Translation dbObject = await ct.Translation.Include(z => z.TranslationItems).SingleOrDefaultAsync(z => z.Id == putObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + + //No tags and no validation of prior state required so no snapshot required + CopyObject.Copy(putObject, dbObject, "Id");//note: won't update the child collection has to be done independently + foreach (TranslationItem ti in putObject.TranslationItems) + { + dbObject.TranslationItems.Where(z => z.Id == ti.Id).First().Display = ti.Display; + } + + ct.Entry(dbObject).OriginalValues["Concurrency"] = putObject.Concurrency; + await ValidateAsync(dbObject); + if (HasErrors) return null; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + + return dbObject; + } + + + + // internal async Task PutTranslationItemDisplayTextAsync(TranslationItem dbObj, NewTextIdConcurrencyTokenItem inObj, Translation dbParent) + // { + + // if (dbParent.Stock == true) + // { + // AddError(ApiErrorCode.INVALID_OPERATION, "object", "TranslationItem is from a Stock translation and cannot be modified"); + // return false; + // } + + // //Replace the db object with the PUT object + // //CopyObject.Copy(inObj, dbObj, "Id"); + // dbObj.Display = inObj.NewText; + // //Set "original" value of concurrency token to input token + // //this will allow EF to check it out + // ct.Entry(dbObj).OriginalValues["Concurrency"] = inObj.Concurrency; + + // //Only thing to validate is if it has data at all in it + // if (string.IsNullOrWhiteSpace(inObj.NewText)) + // AddError(ApiErrorCode.VALIDATION_REQUIRED, "Display (NewText)"); + + // if (HasErrors) + // return false; + // await ct.SaveChangesAsync(); + + // //Log + // await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbParent.Id, SockType.Translation, AyaEvent.Modified), ct); + + // return true; + // } + + internal async Task PutTranslationItemsDisplayTextAsync(List inObj, Translation dbParent) + { + + if (dbParent.Stock == true) + { + AddError(ApiErrorCode.INVALID_OPERATION, "object", "TranslationItem is from a Stock translation and cannot be modified"); + return false; + } + + foreach (NewTextIdConcurrencyTokenItem tit in inObj) + { + var titem = await ct.TranslationItem.SingleOrDefaultAsync(z => z.Id == tit.Id); + if (titem == null) + { + AddError(ApiErrorCode.NOT_FOUND, $"Translation item ID {tit.Id}"); + return false; + } + //Replace the db object with the PUT object + //CopyObject.Copy(inObj, dbObj, "Id"); + titem.Display = tit.NewText; + + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + ct.Entry(titem).OriginalValues["Concurrency"] = tit.Concurrency; + + //Only thing to validate is if it has data at all in it + if (string.IsNullOrWhiteSpace(tit.NewText)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, $"Display (NewText) for Id: {tit.Id}"); + } + if (HasErrors) + return false; + await ct.SaveChangesAsync(); + + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbParent.Id, SockType.Translation, SockEvent.Modified), ct); + + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DUPLICATE + // + internal async Task DuplicateAsync(long id) + { + Translation dbObject = await ct.Translation.Include(z => z.TranslationItems).SingleOrDefaultAsync(z => z.Id == id); + + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + Translation newObject = new Translation(); + //CopyObject.Copy(dbObject, newObject, "Id, Salt, Login, Password, CurrentAuthToken, DlKey, DlKeyExpire, Wiki, Serial"); + string newUniqueName = string.Empty; + bool NotUnique = true; + long l = 1; + do + { + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObject.Name, l++, 255); + NotUnique = await ct.Translation.AnyAsync(z => z.Name == newUniqueName); + } while (NotUnique); + newObject.Name = newUniqueName; + newObject.BaseLanguage = dbObject.BaseLanguage; + newObject.Stock = false; + newObject.CjkIndex = false; + foreach (TranslationItem i in dbObject.TranslationItems) + { + newObject.TranslationItems.Add(new TranslationItem() { Key = i.Key, Display = i.Display }); + } + + newObject.Id = 0; + newObject.Concurrency = 0; + await ct.Translation.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + // await SearchIndexAsync(newObject, true); + + return newObject; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //IMPORT + // + internal async Task ImportAsync(JObject o) + { + + Translation t = new Translation(); + var proposedName = (string)o["Name"]; + string newUniqueName = proposedName; + bool NotUnique = true; + long l = 1; + do + { + NotUnique = await ct.Translation.AnyAsync(z => z.Name == newUniqueName); + if (NotUnique) + newUniqueName = Util.StringUtil.UniqueNameBuilder(proposedName, l++, 255); + + } while (NotUnique); + + t.Name = newUniqueName; + t.CjkIndex = (bool)o["CjkIndex"]; + t.Stock = false; + + Translation sample = await ct.Translation.Include(z => z.TranslationItems).SingleOrDefaultAsync(z => z.Id == 1); + + int ExpectedKeyCount = sample.TranslationItems.Count(); + JArray tItems = (JArray)o["TranslationItems"]; + if (tItems.Count() < ExpectedKeyCount) + { + AddError(ApiErrorCode.VALIDATION_FAILED, null, $"TranslationItems incomplete, expected {ExpectedKeyCount} but found {tItems.Count()}"); + return false; + } + + foreach (JObject j in tItems) + { + var key = (string)j["Key"]; + var display = (string)j["Display"]; + if (null == sample.TranslationItems.Where(z => z.Key == key).FirstOrDefault()) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, null, $"TranslationItems key {key} is not valid"); + return false; + } + t.TranslationItems.Add(new TranslationItem { Key = key, Display = display }); + } + + await ct.Translation.AddAsync(t); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, t.Id, BizType, SockEvent.Created), ct); + + return true; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + + //Get entire translation + internal async Task GetAsync(long fetchId) + { + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + return await ct.Translation.Include(z => z.TranslationItems).SingleOrDefaultAsync(z => z.Id == fetchId); + } + + + + //get list (simple non-paged) + internal async Task> GetTranslationListAsync() + { + List l = new List(); + l = await ct.Translation + .AsNoTracking() + .OrderBy(z => z.Name) + .Select(z => new NameIdItem() + { + Id = z.Id, + Name = z.Name + }).ToListAsync(); + + return l; + + } + + +#if (DEBUG) + internal async Task TranslationKeyCoverageAsync() + { + Sockeye.Api.Controllers.TranslationController.TranslationCoverageInfo L = new Sockeye.Api.Controllers.TranslationController.TranslationCoverageInfo(); + L.RequestedKeys = ServerBootConfig.TranslationKeysRequested; + L.RequestedKeys.Sort(); + var AllKeys = await GetKeyListAsync(); + foreach (string StockKey in AllKeys) + { + if (!L.RequestedKeys.Contains(StockKey)) + { + L.NotRequestedKeys.Add(StockKey); + } + } + L.NotRequestedKeys.Sort(); + L.RequestedKeyCount = L.RequestedKeys.Count; + L.NotRequestedKeyCount = L.NotRequestedKeys.Count; + + return L; + } + + //Track requests for keys so we can determine which are being used and which are not + //TODO: Ideally this should be paired with tests that either directly request each key that are def. being used + //or the UI needs to be tested in a way that triggers every key to be used even errors etc + internal static void TrackRequestedKey(string key) + { + if (!ServerBootConfig.TranslationKeysRequested.Contains(key)) + ServerBootConfig.TranslationKeysRequested.Add(key); + } + + internal static void TrackRequestedKey(List keys) + { + foreach (string Key in keys) + { + if (!ServerBootConfig.TranslationKeysRequested.Contains(Key)) + ServerBootConfig.TranslationKeysRequested.Add(Key); + } + } + +#endif + + ///////////////////////////////////////////////////////////////// + // Get subset for currently logged in user's translation id + // Standard used by translationcontroller for logged in user + // + // + internal async Task>> GetSubsetAsync(List param) + { + +#if (DEBUG) + TrackRequestedKey(param); +#endif + var ret = await ct.TranslationItem.Where(z => z.TranslationId == UserTranslationId && param.Contains(z.Key)).AsNoTracking().ToDictionaryAsync(z => z.Key, z => z.Display); + + if (ret.Count != param.Count) + { + ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger(); + + var missingItems = param.Where(p => ret.All(p2 => p2.Key != p)).ToList(); + var duplicateItems = param.GroupBy(x => x) + .Where(g => g.Count() > 1) + .Select(y => y.Key) + .ToList(); + + if (missingItems.Count > 0) + log.LogWarning($"Non existant translation keys requested: {string.Join(",", missingItems)}"); + + if (duplicateItems.Count > 0) + log.LogWarning($"Duplicate translation keys requested: {string.Join(",", duplicateItems)}"); + + + } + + + return ret.ToList(); + } + + ///////////////////////////////////////////////////////////////// + // Get subset for specified translation ID + // called from controller and Used when user is not logged in + // e.g. when resetting their password + // ## NOTE: NO other use for this other than the reset password at this point + internal static async Task>> GetSpecifiedTranslationSubsetStaticAsync(List param, long translationId) + { +#if (DEBUG) + TrackRequestedKey(param); +#endif + using (AyContext ct = ServiceProviderProvider.DBContext) + { + if (!await ct.Translation.AnyAsync(e => e.Id == translationId)) + translationId = ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID; + var ret = await ct.TranslationItem.Where(z => z.TranslationId == translationId && param.Contains(z.Key)).AsNoTracking().ToDictionaryAsync(z => z.Key, z => z.Display); + return ret.ToList(); + } + } + + + ///////////////////////////////////////////////////////////////// + // Get subset for specified translation ID statically + // called from internal code differs from + // GetSpecifiedTranslationSubsetStaticAsync above only in return signature + // and used for internal classes to call + // + internal static async Task> GetSubsetStaticAsync(List param, long translationId) + { +#if (DEBUG) + TrackRequestedKey(param); +#endif + using (AyContext ct = ServiceProviderProvider.DBContext) + { + if (!await ct.Translation.AnyAsync(e => e.Id == translationId)) + translationId = ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID; + var ret = await ct.TranslationItem.Where(z => z.TranslationId == translationId && param.Contains(z.Key)).AsNoTracking().ToDictionaryAsync(z => z.Key, z => z.Display); + return ret; + } + } + + + ///////////////////////////////////////////////////////////////// + // Get translation of one key for specified translation id + // Used for internal error message translation to return + // during object validation etc. Intended to be as efficient as possible + // + internal static async Task GetTranslationStaticAsync(string translationKey, long translationId, AyContext ct) + { +#if (DEBUG) + TrackRequestedKey(translationKey); +#endif + return await ct.TranslationItem.Where(z => z.TranslationId == translationId && z.Key == translationKey).AsNoTracking().Select(z => z.Display).FirstOrDefaultAsync(); + + } + + + ///////////////////////////////////////////////////////////////// + // Get subset for specified user (looks up translation id) statically + // called from internal code (e.g. notification processing) + // + internal static async Task> GetSubsetForUserStaticAsync(List param, long userId) + { + long translationId = ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID; + using (AyContext ct = ServiceProviderProvider.DBContext) + { + translationId = await ct.UserOptions.AsNoTracking().Where(z => z.UserId == userId).Select(z => z.TranslationId).SingleAsync(); + } + return await GetSubsetStaticAsync(param, translationId); + } + + ///////////////////////////////////////////////////////////////// + // Get single item for specified user (looks up translation id) statically + // called from internal code (e.g. notification processing) + // + internal static async Task GetTranslationForUserStaticAsync(string translationKey, long userId) + { + long translationId = ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID; + using (AyContext ct = ServiceProviderProvider.DBContext) + { + translationId = await ct.UserOptions.AsNoTracking().Where(z => z.UserId == userId).Select(z => z.TranslationId).SingleAsync(); + } + var param = new List() { translationKey }; + var ret = await GetSubsetStaticAsync(param, translationId); + if (ret.Count > 0) return ret[translationKey]; + return $"??{translationKey}"; + } + + + + + //used by internal notification and other processes i.e. "Server" in all languages for server based notifications + internal static async Task> GetAllTranslationsForKey(string translationKey) + { + +#if (DEBUG) + TrackRequestedKey(translationKey); +#endif + using (AyContext ct = ServiceProviderProvider.DBContext) + { + return await ct.TranslationItem.Where(z => z.Key == translationKey).AsNoTracking().ToDictionaryAsync(z => z.TranslationId, z => z.Display); + + } + } + + + //Get the CJKIndex value for the translation specified + internal static async Task GetCJKIndexAsync(long translationId, AyContext ct) + { + var ret = await ct.Translation.Where(z => z.Id == translationId).AsNoTracking().Select(z => z.CjkIndex).SingleOrDefaultAsync(); + return ret; + } + + + //DEPRECATED + // /// + // /// Get the value of the key provided in the default translation chosen + // /// + // /// + // /// + // internal static async Task GetDefaultTranslationAsync(string key) + // { + // if (string.IsNullOrWhiteSpace(key)) + // return "ERROR: GetDefaultTranslation NO KEY VALUE SPECIFIED"; + // #if (DEBUG) + // TrackRequestedKey(key); + // #endif + // using (AyContext ct = ServiceProviderProvider.DBContext) + // return await ct.TranslationItem.Where(z => z.TranslationId == ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID && z.Key == key).Select(z => z.Display).AsNoTracking().FirstOrDefaultAsync(); + // } + + + + + + //Get all stock keys that are valid (used for key coverage reporting) + internal static async Task> GetKeyListAsync() + { + using (AyContext ct = ServiceProviderProvider.DBContext) + return await ct.TranslationItem.Where(z => z.TranslationId == 1).OrderBy(z => z.Key).Select(z => z.Key).AsNoTracking().ToListAsync(); + } + + + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + + internal async Task DeleteAsync(Translation dbObject) + { + //Determine if the object can be deleted, do the deletion tentatively + await ValidateCanDeleteAsync(dbObject); + if (HasErrors) + return false; + ct.Translation.Remove(dbObject); + await ct.SaveChangesAsync(); + //Log + await EventLogProcessor.DeleteObjectLogAsync(UserId, SockType.Translation, dbObject.Id, dbObject.Name, ct); + return true; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(Translation proposedObj) + { + //run validation and biz rules + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + + + //Name must be unique + if (await ct.Translation.AnyAsync(z => z.Name == proposedObj.Name && z.Id != proposedObj.Id)) + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + + //Ensure there are no empty keys or too long ones + //fixing them up here rather than at the client as it's a bit of fuckery + //to try to validate or fix an item edited inside a data table with vuetify + //rather than try to deal with that just fix it here + foreach (var item in proposedObj.TranslationItems.Where(z => z.Display.Length < 1)) + { + item.Display = item.Key; + } + foreach (var item in proposedObj.TranslationItems.Where(z => z.Display.Length > 255)) + { + item.Display = item.Display.Substring(0, 255); + } + + return; + } + + + //Can delete? + private async Task ValidateCanDeleteAsync(Translation inObj) + { + //Decided to short circuit these; if there is one issue then return immediately (fail fast rule) + + //Ensure it's not a stock translation + if (inObj.Stock == true) + { + AddError(ApiErrorCode.INVALID_OPERATION, null, "Translation is a Stock translation and cannot be deleted"); + return; + } + + if (inObj.Id == ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID) + { + AddError(ApiErrorCode.INVALID_OPERATION, null, "Translation is set as the default server translation (SOCKEYE_DEFAULT_LANGUAGE_ID) and can not be deleted"); + return; + } + + //See if any users exist with this translation selected in which case it's not deleteable + if (await ct.UserOptions.AnyAsync(e => e.TranslationId == inObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, null, "Can't be deleted in use by one or more Users"); + return; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UTILITIES + // + + public async Task TranslationNameToIdAsync(string translationName) + { + var v = await ct.Translation.AsNoTracking().FirstOrDefaultAsync(z => z.Name == translationName); + if (v == null) return 0; + return v.Id; + } + + public static async Task TranslationNameToIdStaticAsync(string translationName) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + var v = await ct.Translation.AsNoTracking().FirstOrDefaultAsync(z => z.Name == translationName); + if (v == null) return ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID; + return v.Id; + } + } + + public async Task TranslationExistsAsync(string translationName) + { + return await ct.Translation.AnyAsync(z => z.Name == translationName); + + } + + public async Task TranslationExistsAsync(long id) + { + return await ct.Translation.AnyAsync(z => z.Id == id); + } + + + + //this is only called by Search.cs to cache a local cjk and stopwords, no one else calls it currently + public static async Task ReturnSpecifiedTranslationIdIfExistsOrDefaultTranslationId(long id, AyContext ct) + { + if (!await ct.Translation.AnyAsync(z => z.Id == id)) + return ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID; + return id; + } + + public async Task TranslationItemExistsAsync(long id) + { + return await ct.TranslationItem.AnyAsync(z => z.Id == id); + } + + + + + /// + /// Ensure stock Translations and setup defaults + /// Called by boot preflight check code AFTER it has already ensured the translation is a two letter code if stock one was chosen + /// + public async Task ValidateTranslationsAsync() + { + //Ensure default translations are present and that there is a server default translation that exists + + if (!await TranslationExistsAsync("en")) + { + throw new System.Exception($"E1015: stock translation English (en) not found in database!"); + } + if (!await TranslationExistsAsync("es")) + { + throw new System.Exception($"E1015: stock translation Spanish (es) not found in database!"); + } + if (!await TranslationExistsAsync("de")) + { + throw new System.Exception($"E1015: stock translation German (de) not found in database!"); + } + if (!await TranslationExistsAsync("fr")) + { + throw new System.Exception($"E1015: stock translation French (fr) not found in database!"); + } + + //Ensure chosen default translation exists + switch (ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION) + { + case "en": + case "es": + case "de": + case "fr": + break; + default: + if (!await TranslationExistsAsync(ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION)) + { + throw new System.Exception($"E1015: stock translation {ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION} not found in database!"); + } + break; + + } + + //Put the default translation ID number into the ServerBootConfig for later use + ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID = await TranslationNameToIdAsync(ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION); + + } + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + // +}//eons + diff --git a/server/biz/UiFieldDataType.cs b/server/biz/UiFieldDataType.cs new file mode 100644 index 0000000..7b63415 --- /dev/null +++ b/server/biz/UiFieldDataType.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Sockeye.Biz +{ + //DataTypes used to format display properly and for custom fields definition etc + public enum UiFieldDataType : int + { + NoType = 0, + DateTime = 1, + Date = 2, + Time = 3, + Text = 4, + Integer = 5, + Bool = 6, + Decimal = 7, + Currency = 8, + Tags = 9, + Enum = 10, + EmailAddress = 11, + HTTP = 12, + InternalId = 13, + MemorySize = 14,//this is so client can convert what would normally be an Integer type to human readable file / ram size value + TimeSpan=15, + PhoneNumber=16,//this is so client can dial directly, + Roles = 17 + // ,//for grid display (users), Percentage = 18 ? YAGNI? + } +} diff --git a/server/biz/UserBiz.cs b/server/biz/UserBiz.cs new file mode 100644 index 0000000..cf5732f --- /dev/null +++ b/server/biz/UserBiz.cs @@ -0,0 +1,1017 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using EnumsNET; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; +using System; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json; + + +namespace Sockeye.Biz +{ + internal class UserBiz : BizObject, IJobObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, IImportAbleObject, INotifiableObject + { + internal UserBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles userRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = userRoles; + BizType = SockType.User; + } + + + + + //check for active status of user + //used by notification processing and others + internal static async Task UserIsActive(long userId) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + return await ct.User.AsNoTracking().Where(z => z.Id == userId).Select(z => z.Active).FirstAsync(); + } + } + + + internal static void ResetSuperUserPassword() + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + User dbObject = ct.User.FirstOrDefault(z => z.Id == 1); + dbObject.Password = Hasher.hash(dbObject.Salt, ServerBootConfig.SOCKEYE_SET_SUPERUSER_PW); + ct.SaveChanges(); + + NotifyEventHelper.AddOpsProblemEvent("SOCKEYE_SET_SUPERUSER_PW setting was used at most recent server boot to reset SuperUser account password").Wait(); + } + } + + //For auth and access in client, also when opening wo and also when reporting wo + internal static async Task CustomerUserEffectiveRightsAsync(long userId, List thisWorkOrderTags = null) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + + var UserInfo = await ct.User.AsNoTracking().Where(x => x.Id == userId).Select(x => new { x.UserType, x.HeadOfficeId, x.CustomerId, x.Tags }).FirstAsync(); + if (UserInfo.UserType != UserType.Customer && UserInfo.UserType != UserType.HeadOffice) + throw new System.NotSupportedException($"UserBiz::CustomerUserEffectiveRights - Requested for non Customer type user with ID {userId} who is UserType: {UserInfo.UserType}"); + + //In global settings there are potentially three separate sets of tags that need to be checked + List ContactCustomerHOTagsCombined = new List();//used for most of the customer access features to determine if can even access that feature + List CustomerWorkOrderReportByTagTags = new List();//CUSTOMER & WORKORDER TAGS COMBINED - used to determine correct report to use with customer wo report + List CustomerWorkOrderWikiAttachmentTags = new List();//CONTACT & CUSTOMER & HO & WO TAGS COMBINED - used to determine wo header access like wiki attachments + + ContactCustomerHOTagsCombined.AddRange(UserInfo.Tags); + CustomerWorkOrderWikiAttachmentTags.AddRange(UserInfo.Tags); + + bool EntityActive = false; + //Contact is for a customer or for a head office not both so... + if (UserInfo.CustomerId != null && UserInfo.CustomerId != 0) + { + var CustomerInfo = await ct.Customer.AsNoTracking().Where(x => x.Id == UserInfo.CustomerId).Select(x => new { x.HeadOfficeId, x.Tags, x.Active }).FirstAsync(); + ContactCustomerHOTagsCombined.AddRange(CustomerInfo.Tags); + CustomerWorkOrderReportByTagTags.AddRange(CustomerInfo.Tags); + CustomerWorkOrderWikiAttachmentTags.AddRange(CustomerInfo.Tags); + EntityActive = CustomerInfo.Active; + //does the customer have a head office?? + if (CustomerInfo.HeadOfficeId != null && CustomerInfo.HeadOfficeId != 0) + ContactCustomerHOTagsCombined.AddRange(await ct.HeadOffice.AsNoTracking().Where(x => x.Id == CustomerInfo.HeadOfficeId).Select(x => x.Tags).FirstAsync()); + } + else if (UserInfo.HeadOfficeId != null && UserInfo.HeadOfficeId != 0) + { + var HOInfo = await ct.HeadOffice.AsNoTracking().Where(x => x.Id == UserInfo.HeadOfficeId).Select(x => new { x.Tags, x.Active }).FirstAsync(); + ContactCustomerHOTagsCombined.AddRange(HOInfo.Tags); + EntityActive = HOInfo.Active; + } + + long EntityId = 0; + if (UserInfo.UserType == UserType.Customer) EntityId = UserInfo.CustomerId ?? 0; + if (UserInfo.UserType == UserType.HeadOffice) EntityId = UserInfo.HeadOfficeId ?? 0; + + + + + + return new CustomerRightsRecord( + + CustomerUserEffectiveRightsAllowed(Sockeye.Util.ServerGlobalBizSettings.Cache.CustomerAllowUserSettings, + ContactCustomerHOTagsCombined, + Sockeye.Util.ServerGlobalBizSettings.Cache.CustomerAllowUserSettingsInTags), + + EntityId, + EntityActive + ); + + } + } + + + private static bool CustomerUserEffectiveRightsAllowed(bool globalSettingsAllows, List contactCustomerHOTagsCombined, List inTags) + { + //Note: tag match rule as planned and documented is that it's a match if *any* single tag in intags are a match to any single tag in contact tags, + //not the whole list, just any one of them which differs from how notifications are checked for example which need to *all* match + + //if outright banned then quickest short circuit here + if (!globalSettingsAllows) return false; + + //No tags to verify means allowed + if (inTags.Count == 0) return true; + + //if tags is empty and inclusive is empty then it's a match and can short circuit + if (contactCustomerHOTagsCombined.Count == 0) return true; + + //if tags is empty and inclusive is not empty then no match is possible + if (contactCustomerHOTagsCombined.Count == 0) return false; + + //any of the inclusive tags in tags? + if (contactCustomerHOTagsCombined.Intersect(inTags).Any()) return true; + + return false;//this is because there are contactCustomerHOTagsCombined and in tags but there is no match + } + + + internal static UserBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new UserBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else//when called internally for internal ops there will be no context so need to set default values for that + return new UserBiz(ct, 1, ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.User.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + internal async Task CreateAsync(User newObject) + { + + //Also used for Contacts (customer type user or ho type user) + //by users with no User right but with Customer rights so need to double check here + if ( + (newObject.IsOutsideCustomerContactTypeUser && !Authorized.HasCreateRole(CurrentUserRoles, SockType.Customer)) || + (!newObject.IsOutsideCustomerContactTypeUser && !Authorized.HasCreateRole(CurrentUserRoles, SockType.User)) + ) + { + AddError(ApiErrorCode.NOT_AUTHORIZED); + return null; + } + + + //password and login are optional but in the sense that they can be left out in a PUT + // but if left out here we need to generate a random value instead so they can't login but the code is happy + //because a login name and password are required always + if (string.IsNullOrWhiteSpace(newObject.Password)) + newObject.Password = Hasher.GenerateSalt();//set it to some big random value + + if (string.IsNullOrWhiteSpace(newObject.Login)) + newObject.Login = Hasher.GenerateSalt();//set it to some big random value + + + //This is a new user so it will have been posted with a password in plaintext which needs to be salted and hashed + newObject.Salt = Hasher.GenerateSalt(); + newObject.Password = Hasher.hash(newObject.Salt, newObject.Password); + + + newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + + //Seeder sets user options in advance so no need to create them here in that case + if (newObject.UserOptions == null) + { + newObject.UserOptions = new UserOptions(); + newObject.UserOptions.TranslationId = ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID; + } + + await ValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + + await ct.User.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct); + await SearchIndexAsync(newObject, true); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + await HandlePotentialNotificationEvent(SockEvent.Created, newObject); + dtUser retUser = new dtUser(); + CopyObject.Copy(newObject, retUser); + return retUser; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + + //Get one + internal async Task GetForPublicAsync(long Id, bool logTheGetEvent = true) + { + + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var dbFullUser = await ct.User.AsNoTracking().SingleOrDefaultAsync(z => z.Id == Id); + if (dbFullUser != null) + { + //Log + if (logTheGetEvent) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, Id, BizType, SockEvent.Retrieved), ct); + + + dtUser retUser = new dtUser(); + CopyObject.Copy(dbFullUser, retUser); + return retUser; + } + else return null; + } + + //Get one for internal use + internal async Task GetAsync(long Id, bool logTheGetEvent = true) + { + + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + var dbObject = await ct.User.AsNoTracking().SingleOrDefaultAsync(z => z.Id == Id); + if (dbObject != null) + { + //Log + if (logTheGetEvent) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, Id, BizType, SockEvent.Retrieved), ct); + return dbObject; + } + else return null; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + internal async Task PutAsync(User putObject) + { + var dbObject = await GetAsync(putObject.Id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + if (dbObject.Concurrency != putObject.Concurrency) + { + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + //Also used for Contacts (customer type user or ho type user) + //by users with no User right but with Customer rights so need to double check here + if ( + (dbObject.IsOutsideCustomerContactTypeUser && !Authorized.HasModifyRole(CurrentUserRoles, SockType.Customer)) || + (!dbObject.IsOutsideCustomerContactTypeUser && !Authorized.HasModifyRole(CurrentUserRoles, SockType.User)) + ) + { + AddError(ApiErrorCode.NOT_AUTHORIZED); + return null; + } + + putObject.Tags = TagBiz.NormalizeTags(putObject.Tags); + putObject.CustomFields = JsonUtil.CompactJson(putObject.CustomFields); + + //Fields not sent with the put object + //(it's only location is in the db and since this putObject is replacing the dbObject we need to set it again here) + putObject.Salt = dbObject.Salt; + putObject.CurrentAuthToken = dbObject.CurrentAuthToken; + putObject.DlKey = dbObject.DlKey; + putObject.DlKeyExpire = dbObject.DlKeyExpire; + putObject.PasswordResetCode = dbObject.PasswordResetCode; + putObject.PasswordResetCodeExpire = dbObject.PasswordResetCodeExpire; + + + //NOTE: It's valid to call this without intending to change login or password (null values) + //Is the user updating the password? + if (!string.IsNullOrWhiteSpace(putObject.Password)) + { + //YES password is being updated: + putObject.Password = Hasher.hash(putObject.Salt, putObject.Password); + } + else + { + //No, use the snapshot password value + putObject.Password = dbObject.Password; + } + //Updating login? + if (string.IsNullOrWhiteSpace(putObject.Login)) + { + //No, use the original value + putObject.Login = dbObject.Login; + } + + //DE-ACTIVATING USER? + if (putObject.Active == false && dbObject.Active == true) + { + //yes, deactivating, so revoke their auth token + putObject.CurrentAuthToken = Hasher.GenerateSalt();//new random token that will definitely not work + } + + await ValidateAsync(putObject, dbObject); + if (HasErrors) return null; + ct.Replace(dbObject, putObject); + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + await SearchIndexAsync(putObject, false); + await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags); + await HandlePotentialNotificationEvent(SockEvent.Modified, putObject, dbObject); + return putObject; + } + + + + ///////////////////////////////////////////// + //PASSWORD + // + internal async Task ChangePasswordAsync(long userId, string newPassword) + { + User dbObject = await ct.User.FirstOrDefaultAsync(z => z.Id == userId); + dbObject.Password = Hasher.hash(dbObject.Salt, newPassword); + + //remove reseet code and date so it can't be used again + dbObject.PasswordResetCode = null; + dbObject.DlKeyExpire = null; + + await ct.SaveChangesAsync(); + + //Log modification and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified), ct); + + return true; + } + + + ///////////////////////////////////////////// + // GENERATE AND EMAIL Password reset code + // + internal async Task SendPasswordResetCode(long userId) + { + User dbObject = await ct.User.Include(o => o.UserOptions).FirstOrDefaultAsync(z => z.Id == userId); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return 0; + } + //Also used for Contacts (customer type user or ho type user) + //by users with no User right but with Customer rights so need to double check here + if ( + (dbObject.IsOutsideCustomerContactTypeUser && !Authorized.HasModifyRole(CurrentUserRoles, SockType.Customer)) || + (!dbObject.IsOutsideCustomerContactTypeUser && !Authorized.HasModifyRole(CurrentUserRoles, SockType.User)) + ) + { + AddError(ApiErrorCode.NOT_AUTHORIZED); + return 0; + } + + if (string.IsNullOrWhiteSpace(dbObject.UserOptions.EmailAddress)) + { + AddError(ApiErrorCode.VALIDATION_REQUIRED, "EmailAddress"); + return 0; + } + var ServerUrl = ServerGlobalOpsSettingsCache.Notify.SockeyeServerURL; + if (string.IsNullOrWhiteSpace(ServerUrl)) + { + await NotifyEventHelper.AddOpsProblemEvent("User::SendPasswordResetCode - The OPS Notification setting is empty for Sockeye Server URL. This prevents Notification system from linking events to openable objects."); + AddError(ApiErrorCode.VALIDATION_REQUIRED, "ServerUrl", "Error: no server url configured in notification settings. Can't direct user to server for login. Set server URL and try again."); + return 0; + } + + var ResetCode = Hasher.GetRandomAlphanumericString(32); + + dbObject.PasswordResetCode = ResetCode; + dbObject.PasswordResetCodeExpire = DateTime.UtcNow.AddHours(48);//This is not enough time to issue a reset code on a friday at 5pm and use it Monday before noon, but it is more understandable and clear + await ct.SaveChangesAsync(); + + //send message + ServerUrl = ServerUrl.Trim().TrimEnd('/'); + + //Translations + List TransKeysRequired = new List(); + TransKeysRequired.Add("PasswordResetMessageBody"); + TransKeysRequired.Add("PasswordResetMessageTitle"); + long EffectiveTranslationId = dbObject.UserOptions.TranslationId; + if (EffectiveTranslationId == 0) EffectiveTranslationId = ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID; + var TransDict = await TranslationBiz.GetSubsetStaticAsync(TransKeysRequired, EffectiveTranslationId); + var Title = TransDict["PasswordResetMessageTitle"]; + var MessageBody = TransDict["PasswordResetMessageBody"]; + var loginName = Uri.EscapeDataString(dbObject.Login); + + //Hello {user_name},\n\nYour online account for service is available to you after you set your password.\nYou can use the following link for the next 48 hours to set your password.\n\nSet your password: {action_link}\n\nIf you did not request an account or password reset, please ignore this email.\n\nThanks,\n{registered_to}" + MessageBody = MessageBody.Replace("{user_name}", dbObject.Name).Replace("{action_link}", $"{ServerUrl}/home-reset?rc={ResetCode}&tr={EffectiveTranslationId}&lg={loginName}").Replace("{registered_to}", "GZTW"); + IMailer m = Sockeye.Util.ServiceProviderProvider.Mailer; + + await m.SendEmailAsync(dbObject.UserOptions.EmailAddress, Title, MessageBody, ServerGlobalOpsSettingsCache.Notify); + + //Log modification and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, SockEvent.Modified, "SendPasswordResetCode"), ct); + return dbObject.Concurrency; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // + private async Task SearchIndexAsync(User obj, bool isNew) + { + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + DigestSearchText(obj, SearchParams); + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id, SockType specificType) + { + var obj = await GetAsync(id, false); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + DigestSearchText(obj, SearchParams); + return SearchParams; + } + + public void DigestSearchText(User obj, Search.SearchIndexProcessObjectParameters searchParams) + { + if (obj != null) + searchParams.AddText(obj.Notes) + .AddText(obj.Name) + .AddText(obj.Wiki) + .AddText(obj.Tags) + .AddText(obj.EmployeeNumber) + .AddCustomFields(obj.CustomFields); + } + + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long id, Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction parentTransaction = null) + { + //this may be part of a larger delete operation involving other objects (e.g. Customer delete and remove contacts) + //if so then there will be a parent transaction otherwise we make our own + Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction transaction = null; + if (parentTransaction == null) + transaction = await ct.Database.BeginTransactionAsync(); + User dbObject = await ct.User.SingleOrDefaultAsync(z => z.Id == id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND); + return false; + } + + + //Also used for Contacts (customer type user or ho type user) + //by users with no User right but with Customer rights so need to double check here + if ( + (dbObject.IsOutsideCustomerContactTypeUser && !Authorized.HasDeleteRole(CurrentUserRoles, SockType.Customer)) || + (!dbObject.IsOutsideCustomerContactTypeUser && !Authorized.HasDeleteRole(CurrentUserRoles, SockType.User)) + ) + { + AddError(ApiErrorCode.NOT_AUTHORIZED); + return false; + } + + await ValidateCanDelete(dbObject); + if (HasErrors) + return false; + + //Delete sibling objects + //USEROPTIONS + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from auseroptions where userid = {dbObject.Id}"); + //NOTIFY SUBSCRIPTIONS + //Note: will cascade delete notifyevent, and notification automatically + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from anotifysubscription where userid = {dbObject.Id}"); + //personal datalist options + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from adatalistsavedfilter where public = {false} and userid = {dbObject.Id}"); + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from adatalistcolumnview where userid = {dbObject.Id}"); + //Dashboard view + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from adashboardview where userid = {dbObject.Id}"); + + //Remove the object + ct.User.Remove(dbObject); + try + { + await ct.SaveChangesAsync(); + } + catch (Microsoft.EntityFrameworkCore.DbUpdateException) + { + //SPECIAL EXCEPTION + //seeded data isn't always attributed to the user who would normally have created data so + //this could fail due to referential integrity as they wouldn't be in the event log check above + //easiest workaround to avoid having to check a whole host of items is to just check if this fails due to ref.integrity and return sane message + AddError(ApiErrorCode.INVALID_OPERATION, "generalerror", await Translate("ErrorDBForeignKeyViolation")); + return false; + } + + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct); + await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct); + + //all good do the commit if it's ours + if (parentTransaction == null) + await transaction.CommitAsync(); + await HandlePotentialNotificationEvent(SockEvent.Deleted, dbObject); + return true; + + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private async Task ValidateAsync(User proposedObj, User currentObj) + { + //run validation and biz rules + bool isNew = currentObj == null; + + //UserType change has Inside / Outside role implications + //a user attempting to change a UserType between inside or outside status must have the correct rights + //to *BOTH* Customer and User since it's affecting both types + if (!isNew && (currentObj.IsOutsideCustomerContactTypeUser != proposedObj.IsOutsideCustomerContactTypeUser)) + { + //only can change if have both rights + if ( + !Authorized.HasModifyRole(CurrentUserRoles, SockType.User) || + !Authorized.HasModifyRole(CurrentUserRoles, SockType.Customer) + ) + { + AddError(ApiErrorCode.NOT_AUTHORIZED, "UserType"); + } + } + + + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + //case 4322 this is a problem with customer contacts and not really required anyway + // //If name is otherwise OK, check that name is unique + // if (!PropertyHasErrors("Name")) + // { + // //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false + // if (await ct.User.AnyAsync(z => z.Name == proposedObj.Name && z.Id != proposedObj.Id)) + // { + // AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name"); + // } + // } + + //LOGIN must be unique + if (await ct.User.AnyAsync(z => z.Login == proposedObj.Login && z.Id != proposedObj.Id)) + { + AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Login"); + } + + + //SUPERUSER ACCOUNT CAN"T BE MODIFIED IN SOME WAYS + if (!isNew && proposedObj.Id == 1) + { + //prevent certain changes to superuser account like roles etc + + if (proposedObj.Roles != currentObj.Roles) + AddError(ApiErrorCode.NOT_AUTHORIZED, "Roles"); + + if (proposedObj.Active != currentObj.Active) + AddError(ApiErrorCode.NOT_AUTHORIZED, "Active"); + + if (proposedObj.AllowLogin != currentObj.AllowLogin) + AddError(ApiErrorCode.NOT_AUTHORIZED, "AllowLogin"); + + if (proposedObj.Name != currentObj.Name) + AddError(ApiErrorCode.NOT_AUTHORIZED, "Name"); + + if (proposedObj.UserType != currentObj.UserType) + AddError(ApiErrorCode.NOT_AUTHORIZED, "UserType"); + + } + + + + if (!proposedObj.UserType.IsValid()) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "UserType"); + } + + //Validate customer type user + if (proposedObj.UserType == UserType.Customer) + { + if (proposedObj.CustomerId == null || proposedObj.CustomerId == 0) + { + AddError(ApiErrorCode.VALIDATION_REQUIRED, "CustomerId"); + } + else + { + //verify chosen customer exists + if (!await ct.Customer.AnyAsync(z => z.Id == proposedObj.CustomerId)) + AddError(ApiErrorCode.NOT_FOUND, "CustomerId"); + } + } + + //Validate headoffice type user + if (proposedObj.UserType == UserType.HeadOffice) + { + if (proposedObj.HeadOfficeId == null || proposedObj.HeadOfficeId == 0) + { + AddError(ApiErrorCode.VALIDATION_REQUIRED, "HeadOfficeId"); + } + else + { + //verify chosen HO exists + if (!await ct.HeadOffice.AnyAsync(z => z.Id == proposedObj.HeadOfficeId)) + AddError(ApiErrorCode.NOT_FOUND, "HeadOfficeId"); + } + } + + + + if (!proposedObj.Roles.IsValid()) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Roles"); + } + + + //Any form customizations to validate? + //contact is type if customer user + var formKey = SockType.User.ToString(); + if (proposedObj.UserType == UserType.Customer || proposedObj.UserType == UserType.HeadOffice) + formKey = "Contact"; + var FormCustomization = await ct.FormCustom.SingleOrDefaultAsync(z => z.FormKey == formKey); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + //validate custom fields + CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields); + } + + return; + } + + private async Task AddErrorIfAtLicenseLimitAnyBuildType(long CurrentActiveCount, long LicensedUserCount) + { + if (CurrentActiveCount >= LicensedUserCount) + { + AddError(ApiErrorCode.INVALID_OPERATION, "generalerror", await Translate("ErrorSecurityUserCapacity"));//THIS IS A GENERIC ERROR GOOD FOR ANY BUILD TYPE + } + } + + + //Can delete? + private async Task ValidateCanDelete(User inObj) + { + //FOREIGN KEY CHECKS + if (await ct.Memo.AnyAsync(m => m.ToId == inObj.Id || m.FromId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("Memo")); + if (await ct.Review.AnyAsync(m => m.AssignedByUserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("Review")); + if (await ct.CustomerNote.AnyAsync(m => m.UserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("CustomerNote")); + if (await ct.FileAttachment.AnyAsync(m => m.AttachedByUserId == inObj.Id)) + AddError(ApiErrorCode.VALIDATION_REFERENTIAL_INTEGRITY, "generalerror", await Translate("FileAttachment")); + + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // Utilities + // + internal static async Task ValidateDownloadTokenAndReturnUserAsync(string dlToken, AyContext ct) + { + if (string.IsNullOrWhiteSpace(dlToken)) + return null; + //get user by key, if not found then reject + var DownloadUser = await ct.User.AsNoTracking().SingleOrDefaultAsync(z => z.DlKey == dlToken && z.Active == true); + if (DownloadUser == null) + return null; + //this is necessary because they might have an expired JWT but this would just keep on working without a date check + //the default is the same timespan as the jwt so it's all good + var utcNow = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero); + if (DownloadUser.DlKeyExpire < utcNow.DateTime) + return null; + return DownloadUser; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //REPORTING + // + public async Task GetReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + var idList = dataListSelectedRequest.SelectedRowIds; + JArray ReportData = new JArray(); + while (idList.Any()) + { + var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE); + idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray(); + //query for this batch, comes back in db natural order unfortunately + var batchResults = await ct.User.AsNoTracking().Include(z => z.UserOptions).Where(z => batch.Contains(z.Id)).ToArrayAsync(); + //order the results back into original + var orderedList = from id in batch join z in batchResults on id equals z.Id select z; + + batchResults = null; + foreach (var w in orderedList) + { + if (!ReportRenderManager.KeepGoing(jobId)) return null; + await PopulateVizFields(w, UserTypesEnumList); + var jo = JObject.FromObject(w); + if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"])) + jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]); + jo.Remove("Login"); + jo.Remove("Password"); + jo["UserOptions"] = JObject.FromObject(w.UserOptions); + + ReportData.Add(jo); + } + orderedList = null; + } + vc.Clear(); + return ReportData; + } + private VizCache vc = new VizCache(); + + //populate viz fields from provided object + private async Task PopulateVizFields(User o, List userTypesEnumList) + { + if (userTypesEnumList == null) + userTypesEnumList = await Sockeye.Api.Controllers.EnumListController.GetEnumList( + StringUtil.TrimTypeName(typeof(UserType).ToString()), + UserTranslationId, + CurrentUserRoles); + + if (o.CustomerId != null) + { + if (!vc.Has("customer", o.CustomerId)) + vc.Add(await ct.Customer.AsNoTracking().Where(x => x.Id == o.CustomerId).Select(x => x.Name).FirstOrDefaultAsync(), "customer", o.CustomerId); + o.CustomerViz = vc.Get("customer", o.CustomerId); + } + + if (o.HeadOfficeId != null) + { + if (!vc.Has("headoffice", o.HeadOfficeId)) + { + vc.Add(await ct.HeadOffice.AsNoTracking().Where(x => x.Id == o.HeadOfficeId).Select(x => x.Name).FirstOrDefaultAsync(), "headoffice", o.HeadOfficeId); + } + o.HeadOfficeViz = vc.Get("headoffice", o.HeadOfficeId); + } + + + + o.UserTypeViz = userTypesEnumList.Where(x => x.Id == (long)o.UserType).Select(x => x.Name).First(); + } + List UserTypesEnumList = null; + + //////////////////////////////////////////////////////////////////////////////////////////////// + // IMPORT EXPORT + // + + + public async Task GetExportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId) + { + //for now just re-use the report data code + //this may turn out to be the pattern for most biz object types but keeping it seperate allows for custom usage from time to time + return await GetReportData(dataListSelectedRequest, jobId); + } + + + + + public async Task> ImportData(AyImportData importData) + { + List ImportResult = new List(); + string ImportTag = $"imported-{FileUtil.GetSafeDateFileName()}"; + + var jsset = JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = new Sockeye.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "CustomFields" }) }); + foreach (JObject j in importData.Data) + { + var w = j.ToObject(jsset); + if (j["CustomFields"] != null) + w.CustomFields = j["CustomFields"].ToString(); + w.Tags.Add(ImportTag);//so user can find them all and revert later if necessary + var res = await CreateAsync(w); + if (res == null) + { + ImportResult.Add($"* {w.Name} - {this.GetErrorsAsString()}"); + this.ClearErrors(); + } + else + { + ImportResult.Add($"{w.Name} - ok"); + } + } + return ImportResult; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + // JOB / OPERATIONS + // + public async Task HandleJobAsync(OpsJob job) + { + switch (job.JobType) + { + case JobType.BatchCoreObjectOperation: + await ProcessBatchJobAsync(job); + break; + default: + throw new System.ArgumentOutOfRangeException($"User.HandleJob-> Invalid job type{job.JobType.ToString()}"); + } + + } + + + + + private async Task ProcessBatchJobAsync(OpsJob job) + { + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running); + await JobsBiz.LogJobAsync(job.GId, $"LT:StartJob {job.SubType}"); + List idList = new List(); + long FailedObjectCount = 0; + JObject jobData = JObject.Parse(job.JobInfo); + if (jobData.ContainsKey("idList")) + idList = ((JArray)jobData["idList"]).ToObject>(); + else + idList = await ct.User.AsNoTracking().Select(z => z.Id).ToListAsync(); + bool SaveIt = false; + + //--------------------------------- + //case 4192 + TimeSpan ProgressAndCancelCheckSpan = new TimeSpan(0, 0, ServerBootConfig.JOB_PROGRESS_UPDATE_AND_CANCEL_CHECK_SECONDS); + DateTime LastProgressCheck = DateTime.UtcNow.Subtract(new TimeSpan(1, 1, 1, 1, 1)); + var TotalRecords = idList.LongCount(); + long CurrentRecord = -1; + //--------------------------------- + foreach (long id in idList) + { + try + { + //-------------------------------- + //case 4192 + //Update progress / cancel requested? + CurrentRecord++; + if (DateUtil.IsAfterDuration(LastProgressCheck, ProgressAndCancelCheckSpan)) + { + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{CurrentRecord}/{TotalRecords}"); + if (await JobsBiz.GetJobStatusAsync(job.GId) == JobStatus.CancelRequested) + break; + LastProgressCheck = DateTime.UtcNow; + } + //--------------------------------- + SaveIt = false; + ClearErrors(); + //a little different than normal here because the built in getasync doesn't return + //a full User object normally + User o = null; + //save a fetch if it's a delete + if (job.SubType != JobSubType.Delete) + o = await GetAsync(id, false); + switch (job.SubType) + { + case JobSubType.TagAddAny: + case JobSubType.TagAdd: + case JobSubType.TagRemoveAny: + case JobSubType.TagRemove: + case JobSubType.TagReplaceAny: + case JobSubType.TagReplace: + SaveIt = TagBiz.ProcessBatchTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); + break; + case JobSubType.Delete: + if (!await DeleteAsync(id)) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + break; + default: + throw new System.ArgumentOutOfRangeException($"ProcessBatchJobAsync -> Invalid job Subtype{job.SubType}"); + } + + if (SaveIt) + { + o = await PutAsync(o); + if (o == null) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}"); + FailedObjectCount++; + } + } + + //delay so we're not tying up all the resources in a tight loop + await Task.Delay(Sockeye.Util.ServerBootConfig.JOB_OBJECT_HANDLE_BATCH_JOB_LOOP_DELAY); + } + catch (Exception ex) + { + await JobsBiz.LogJobAsync(job.GId, $"LT:Errors id({id})"); + await JobsBiz.LogJobAsync(job.GId, ExceptionUtil.ExtractAllExceptionMessages(ex)); + } + } + + //--------------------------------- + //case 4192 + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{++CurrentRecord}/{TotalRecords}"); + //--------------------------------- + await JobsBiz.LogJobAsync(job.GId, $"LT:BatchJob {job.SubType} {idList.Count}{(FailedObjectCount > 0 ? " - LT:Failed " + FailedObjectCount : "")}"); + await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed); + } + + + + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // NOTIFICATION PROCESSING + // + public async Task HandlePotentialNotificationEvent(SockEvent ayaEvent, ICoreBizObjectModel proposedObj, ICoreBizObjectModel currentObj = null) + { + ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger(); + + log.LogDebug($"HandlePotentialNotificationEvent processing: [SockType:{this.BizType}, AyaEvent:{ayaEvent}]"); + + bool isNew = currentObj == null; + + + //STANDARD EVENTS FOR ALL OBJECTS + await NotifyEventHelper.ProcessStandardObjectEvents(ayaEvent, proposedObj, ct); + + //SPECIFIC EVENTS FOR THIS OBJECT + if (ayaEvent == SockEvent.Modified) + { + //USER MODIFIED ROLES CHANGED MIGHT AFFECT ALLOWED SUBS + //This one's a little different, if user has had roles changed, then pre-existing subs may not be allowed anymore + //Remove any notification subscriptions user doesn't have rights to: + if (((User)currentObj).Roles != ((User)proposedObj).Roles) + { + var DeleteList = new List(); + var NewRoles = ((User)proposedObj).Roles; + //iterate subs and remove any user shouldn't have + var userSubs = await ct.NotifySubscription.Where(z => z.UserId == proposedObj.Id).ToListAsync(); + foreach (var sub in userSubs) + { + if (sub.SockType != SockType.NoType) + { + //check if user has rights to it or not still + //must have read rights to be valid + if (!Sockeye.Api.ControllerHelpers.Authorized.HasAnyRole(NewRoles, sub.SockType)) + { + //no rights whatsoever, so delete it + DeleteList.Add(sub.Id); + } + } + } + if (DeleteList.Count > 0) + { + var NSB = NotifySubscriptionBiz.GetBiz(ct); + foreach (var l in DeleteList) + { + await NSB.DeleteAsync(l); + } + } + } + } + + }//end of process notifications + + + + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/UserOptionsBiz.cs b/server/biz/UserOptionsBiz.cs new file mode 100644 index 0000000..2af7802 --- /dev/null +++ b/server/biz/UserOptionsBiz.cs @@ -0,0 +1,155 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sockeye.Util; +using Sockeye.Api.ControllerHelpers; +using Sockeye.Models; + + +namespace Sockeye.Biz +{ + + + internal class UserOptionsBiz : BizObject + { + + internal UserOptionsBiz(AyContext dbcontext, long currentUserId, AuthorizationRoles userRoles) + { + ct = dbcontext; + UserId = currentUserId; + CurrentUserRoles = userRoles; + BizType = SockType.UserOptions; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + /// GET + + //Get one + internal async Task GetAsync(long fetchId) + { + //NOTE: get by UserId as there is a 1:1 relationship, not by useroptions id + //This is simple so nothing more here, but often will be copying to a different output object or some other ops + return await ct.UserOptions.SingleOrDefaultAsync(z => z.UserId == fetchId); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //UPDATE + // + + //Creating a user creates a user options so no need for create ever + + // //////////////////////////////////////////////////////////////////////////////////////////////// + // //CREATE + // // + // internal async Task CreateAsync(UserOptions newObject) + // { + + // User u = await ct.User.AsNoTracking().SingleOrDefaultAsync(z => z.Id == newObject.UserId); + // if (u == null) + // { + // AddError(ApiErrorCode.NOT_FOUND, "id"); + // return null; + // } + // //Also used for Contacts (customer type user or ho type user) + // //by users with no User right but with Customer rights so need to double check here + // if ( + // (u.IsOutsideUser && !Authorized.HasModifyRole(CurrentUserRoles, SockType.Customer)) || + // (!u.IsOutsideUser && !Authorized.HasModifyRole(CurrentUserRoles, SockType.User)) + // ) + // { + // AddError(ApiErrorCode.NOT_AUTHORIZED); + // return null; + // } + + // Validate(newObject); + // if (HasErrors) + // return null; + // else + // { + + // await ct.UserOptions.AddAsync(newObject); + // await ct.SaveChangesAsync(); + // await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct); + + // return newObject; + // } + // } + + + //put + internal async Task PutAsync(UserOptions dbObject, UserOptions inObj) + { + + //if it's not the user's own options then we need to check it just as for User / Contact objects + if (dbObject.Id != UserId) + { + User u = await ct.User.AsNoTracking().SingleOrDefaultAsync(z => z.Id == dbObject.Id); + if (u == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return false; + } + //Also used for Contacts (customer type user or ho type user) + //by users with no User right but with Customer rights so need to double check here + if ( + (u.IsOutsideCustomerContactTypeUser && !Authorized.HasModifyRole(CurrentUserRoles, SockType.Customer)) || + (!u.IsOutsideCustomerContactTypeUser && !Authorized.HasModifyRole(CurrentUserRoles, SockType.User)) + ) + { + AddError(ApiErrorCode.NOT_AUTHORIZED); + return false; + } + } + + //Replace the db object with the PUT object + CopyObject.Copy(inObj, dbObject, "Id, UserId"); + //Set "original" value of concurrency token to input token + //this will allow EF to check it out + //BUT NOT IF IT"S FROM A DUPLICATION OP (CONCURRENCY=0) + if (inObj.Concurrency != 0) + ct.Entry(dbObject).OriginalValues["Concurrency"] = inObj.Concurrency; + + Validate(dbObject); + if (HasErrors) + return false; + + await ct.SaveChangesAsync(); + //Log + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, SockType.User, SockEvent.Modified), ct); + return true; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + //Can save or update? + private void Validate(UserOptions inObj) + { + //UserOptions is never new, it's created with the User object so were only here for an edit + + //UserId required + if (inObj.UserId == 0) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "UserId"); + + + //Hexadecimal notation: #RGB[A] R (red), G (green), B (blue), and A (alpha) are hexadecimal characters (0–9, A–F). A is optional. The three-digit notation (#RGB) is a shorter version of the six-digit form (#RRGGBB). For example, #f09 is the same color as #ff0099. Likewise, the four-digit RGB notation (#RGBA) is a shorter version of the eight-digit form (#RRGGBBAA). For example, #0f38 is the same color as #00ff3388. + if (inObj.UiColor.Length > 12 || inObj.UiColor.Length < 4 || inObj.UiColor[0] != '#') + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "UiColor", "UiColor must be valid HEX color value"); + } + + return; + } + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/biz/UserType.cs b/server/biz/UserType.cs new file mode 100644 index 0000000..7a83016 --- /dev/null +++ b/server/biz/UserType.cs @@ -0,0 +1,14 @@ +namespace Sockeye.Biz +{ + /// + /// Sockeye User types + /// + public enum UserType : int + { + Service = 1, + NotService = 2, + Customer = 3, + HeadOffice = 4, + ServiceContractor = 5 + } +}//eons diff --git a/server/biz/ValidationError.cs b/server/biz/ValidationError.cs new file mode 100644 index 0000000..e470ac3 --- /dev/null +++ b/server/biz/ValidationError.cs @@ -0,0 +1,19 @@ +namespace Sockeye.Biz +{ + + public class ValidationError + { + //TARGET is the Model name of the property which matches the client UI annotations for ref + + //if the target error is a child item collection field the Target must be "items[2].field" + //where "items" is the item collection model name and 2 is the index of the collection with the error and field is the ultimate field model name + //Case doesn't matter as the client will compare in lower case all items anyway + + + public ApiErrorCode Code { get; set; } + public string Target { get; set; } + public string Message { get; set; } + + }//eoc + +}//eons \ No newline at end of file diff --git a/server/generator/BackgroundService.cs b/server/generator/BackgroundService.cs new file mode 100644 index 0000000..88cb1a6 --- /dev/null +++ b/server/generator/BackgroundService.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; + + + +namespace Sockeye.Generator +{ + + + + // Copyright (c) .NET Foundation. Licensed under the Apache License, Version 2.0. + /// + /// Base class for implementing a long running . + /// + public abstract class BackgroundService : IHostedService, IDisposable + { + private Task _executingTask; + private readonly CancellationTokenSource _stoppingCts = + new CancellationTokenSource(); + + protected abstract Task ExecuteAsync(CancellationToken stoppingToken); + + public virtual Task StartAsync(CancellationToken cancellationToken) + { + // Store the task we're executing + _executingTask = ExecuteAsync(_stoppingCts.Token); + + // If the task is completed then return it, + // this will bubble cancellation and failure to the caller + if (_executingTask.IsCompleted) + { + return _executingTask; + } + + // Otherwise it's running + return Task.CompletedTask; + } + + public virtual async Task StopAsync(CancellationToken cancellationToken) + { + // Stop called without start + if (_executingTask == null) + { + return; + } + + try + { + // Signal cancellation to the executing method + _stoppingCts.Cancel(); + } + finally + { + // Wait until the task completes or the stop token triggers + await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, + cancellationToken)); + } + + } + + public virtual void Dispose() + { + _stoppingCts.Cancel(); + } + } + +} \ No newline at end of file diff --git a/server/generator/CoreIntegrationLogSweeper.cs b/server/generator/CoreIntegrationLogSweeper.cs new file mode 100644 index 0000000..ead1eb1 --- /dev/null +++ b/server/generator/CoreIntegrationLogSweeper.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Sockeye.Models; + +namespace Sockeye.Biz +{ + + /// + /// Clear out old integration log data + /// + internal static class CoreIntegrationLogSweeper + { + private static ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("CoreIntegrationLogSweeper"); + private static DateTime lastSweep = DateTime.MinValue; + private static TimeSpan DELETE_AFTER_AGE = new TimeSpan(90, 0, 0, 0);//The same typical 90 days as everything uses + private static TimeSpan SWEEP_EVERY_INTERVAL = new TimeSpan(8, 0, 0);//once every 8 hours, three times a day + + //////////////////////////////////////////////////////////////////////////////////////////////// + // DoSweep + // + public static async Task DoWorkAsync() + { + //This will get triggered roughly every minute, but we don't want to sweep that frequently + if (DateTime.UtcNow - lastSweep < SWEEP_EVERY_INTERVAL) + return; + DateTime dtDeleteCutoff = DateTime.UtcNow - DELETE_AFTER_AGE; + DateTime dtPastEventCutoff = DateTime.UtcNow - SWEEP_EVERY_INTERVAL; + + log.LogDebug("Sweep starting"); + using (AyContext ct = Sockeye.Util.ServiceProviderProvider.DBContext) + { + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from aintegrationlog where created < {dtDeleteCutoff}"); + + } + lastSweep = DateTime.UtcNow; + } + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/generator/CoreJobBackup.cs b/server/generator/CoreJobBackup.cs new file mode 100644 index 0000000..81a61b4 --- /dev/null +++ b/server/generator/CoreJobBackup.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Util; +using System.IO; +using System.Runtime.InteropServices; + +namespace Sockeye.Biz +{ + + + /// + /// Backup + /// + /// + internal static class CoreJobBackup + { + private static ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("CoreJobBackup"); + private static bool BackupIsRunning = false; + private const int MAXIMUM_MS_ALLOWED_FOR_PROCESSING = 10 * 60 * 1000;//wild assed guess 10 minutes maximum to run backup command, ever + + //////////////////////////////////////////////////////////////////////////////////////////////// + // BACK-THE-FUCK-UP + // + public static async Task DoWorkAsync(bool OnDemand = false) + { + if (BackupIsRunning) return; + if (!OnDemand) + { + log.LogTrace("Checking if backup should run"); + if (!ServerGlobalOpsSettingsCache.Backup.Active) + { + log.LogDebug("Automatic backup is set to INACTIVE - not backing up"); + return; + } + + if (DateTime.UtcNow < ServerGlobalOpsSettingsCache.NextBackup) + { + log.LogTrace("Not past backup time yet"); + return; + } + } + Sockeye.Api.ControllerHelpers.ApiServerState apiServerState = null; + + try + { + BackupIsRunning = true; + + //LOCK DOWN SERVER + apiServerState = (Sockeye.Api.ControllerHelpers.ApiServerState)ServiceProviderProvider.Provider.GetService(typeof(Sockeye.Api.ControllerHelpers.ApiServerState)); + apiServerState.SetClosed("BACKUP RUNNING"); + var jobstartmessage = $"LT:Backup LT:StartJob {(OnDemand ? "manual / on demand" : "scheduled")} "; + await JobsBiz.LogJobAsync(Guid.Empty, jobstartmessage); + + DateTime dtStartBackup = DateTime.Now; + log.LogDebug("Backup starting"); + var DemandFileNamePrepend = OnDemand ? "manual-" : string.Empty; + //************* + //DO DATA BACKUP + //build command + //this is valid on windows + //C:\data\code\PostgreSQLPortable_12.0\App\PgSQL\bin\pg_dump --dbname=postgresql://postgres:raven@127.0.0.1:5432/Sockeye -Fc > huge_new.backup + + // await JobsBiz.LogJobAsync(Guid.Empty, $"Data backup starting"); + Npgsql.NpgsqlConnectionStringBuilder PostgresConnectionString = new Npgsql.NpgsqlConnectionStringBuilder(ServerBootConfig.SOCKEYE_DB_CONNECTION); + var DBNameParameter = $"--dbname=postgresql://{PostgresConnectionString.Username}:{PostgresConnectionString.Password}@{PostgresConnectionString.Host}:{PostgresConnectionString.Port}/{PostgresConnectionString.Database}"; + + var DataBackupFile = $"{DemandFileNamePrepend}db-{FileUtil.GetSafeDateFileName()}.backup";//presentation issue so don't use UTC for this one + DataBackupFile = FileUtil.GetFullPathForBackupFile(DataBackupFile); + + var BackupUtilityCommand = "pg_dump"; + + + if (!string.IsNullOrWhiteSpace(ServerBootConfig.SOCKEYE_BACKUP_PG_DUMP_PATH)) + BackupUtilityCommand = Path.Combine(ServerBootConfig.SOCKEYE_BACKUP_PG_DUMP_PATH, BackupUtilityCommand); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + //put quotes around paths if spaces in them + if (BackupUtilityCommand.Contains(' ')) + { + BackupUtilityCommand = $"\"{BackupUtilityCommand}\""; + } + + if (DataBackupFile.Contains(' ')) + { + DataBackupFile = $"\"{DataBackupFile}\""; + } + } + + var Arguments = $"{DBNameParameter} -Fc > {DataBackupFile}"; + + var Result = RunProgram.Run(BackupUtilityCommand, Arguments, log, MAXIMUM_MS_ALLOWED_FOR_PROCESSING); + if (!string.IsNullOrWhiteSpace(Result)) + { + var msg = $"Error during data backup \"{Result}\""; + await JobsBiz.LogJobAsync(Guid.Empty, msg); + log.LogError($"BACKUP ERROR: {Result}"); + await NotifyEventHelper.AddGeneralNotifyEvent(NotifyEventType.BackupStatus, msg, "Backup"); + } + else + { + log.LogDebug("Backup of database completed OK"); + + //DO FILE BACKUP IF ATTACHMENTS BACKED UP + if (ServerGlobalOpsSettingsCache.Backup.BackupAttachments) + { + await JobsBiz.LogJobAsync(Guid.Empty, $"LT:Backup LT:Attachments"); + FileUtil.BackupAttachments(DemandFileNamePrepend); + log.LogDebug("Backup of file attachments completed OK"); + } + + //PRUNE DATA BACKUP SETS NOT KEPT + await JobsBiz.LogJobAsync(Guid.Empty, $"LT:BackupDeleteOld"); + + + FileUtil.DatabaseBackupCleanUp(ServerGlobalOpsSettingsCache.Backup.BackupSetsToKeep); + + + + //v.next - COPY TO ONLINE STORAGE + //*************** + + log.LogDebug("Backup completed"); + var duration = DateTime.Now - dtStartBackup; + await NotifyEventHelper.AddGeneralNotifyEvent(NotifyEventType.BackupStatus, $"Backup ({(OnDemand ? "manual / on demand" : "scheduled")}) completed successfully, duration (h:m:s.ms): {duration.ToString()}, server re-opened", "Backup"); + } + } + catch (Exception ex) + { + await JobsBiz.LogJobAsync(Guid.Empty, "LT:JobFailed"); + await JobsBiz.LogJobAsync(Guid.Empty, ExceptionUtil.ExtractAllExceptionMessages(ex)); + log.LogError(ex, "Backup failed"); + await NotifyEventHelper.AddGeneralNotifyEvent(NotifyEventType.BackupStatus, "Backup failed", "Backup", ex); + throw; + } + finally + { + //bump the backup date if automatic backup + if (!OnDemand) + ServerGlobalOpsSettingsCache.SetNextBackup(); + apiServerState.ResumePriorState(); + BackupIsRunning = false; + await JobsBiz.LogJobAsync(Guid.Empty, "LT:JobCompleted"); + + } + } + + + + ///////////////////////////////////////////////////////////////////// + }//eoc +}//eons + diff --git a/server/generator/CoreJobCustomerNotify.cs b/server/generator/CoreJobCustomerNotify.cs new file mode 100644 index 0000000..18fe496 --- /dev/null +++ b/server/generator/CoreJobCustomerNotify.cs @@ -0,0 +1,285 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Util; +using Newtonsoft.Json.Linq; +using System.Text.RegularExpressions; + +namespace Sockeye.Biz +{ + + /// + /// Notification processor + /// turn notifyEvent records into inappnotification records for in app viewing and / or deliver smtp notifications seperately + /// + /// + internal static class CoreJobCustomerNotify + { + private static bool NotifyIsRunning = false; + private static ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("CoreJobCustomerNotify"); + private static DateTime lastRun = DateTime.MinValue; + +#if (DEBUG) + private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 0, 21);//no more frequently than once every 20 seconds +#else + private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 1, 1);//no more frequently than once every 1 minute +#endif + + //////////////////////////////////////////////////////////////////////////////////////////////// + // DoSweep + // + public static async Task DoWorkAsync() + { + log.LogTrace("Checking if CustomerNotify should run"); + if (NotifyIsRunning) + { + log.LogTrace("CustomerNotify is running already exiting this cycle"); + return; + } + //This will get triggered roughly every minute, but we don't want to deliver that frequently + if (DateTime.UtcNow - lastRun < RUN_EVERY_INTERVAL) + { + log.LogTrace($"CustomerNotify ran less than {RUN_EVERY_INTERVAL} ago, exiting this cycle"); + return; + } + try + { + NotifyIsRunning = true; + log.LogDebug("CustomerNotify set to RUNNING state and starting now"); + + using (AyContext ct = Sockeye.Util.ServiceProviderProvider.DBContext) + { + var customerevents = await ct.CustomerNotifyEvent.AsNoTracking().ToListAsync(); + log.LogDebug($"Found {customerevents.Count} CustomerNotifyEvents to examine for potential delivery"); + + //iterate and deliver + foreach (var customernotifyevent in customerevents) + { + //no notifications for inactive users, just delete it as if it was delivered + var CustInfo = await ct.Customer.AsNoTracking().Where(x => x.Id == customernotifyevent.CustomerId).Select(x => new { x.Name, x.Active, x.Tags, x.EmailAddress }).FirstOrDefaultAsync(); + + + if (!CustInfo.Active) + { + log.LogDebug($"Inactive Customer {CustInfo.Name}, removing notify rather than delivering it: {customernotifyevent}"); + ct.CustomerNotifyEvent.Remove(customernotifyevent); + await ct.SaveChangesAsync(); + continue; + } + + if (string.IsNullOrWhiteSpace(CustInfo.EmailAddress)) + { + log.LogDebug($"Customer {CustInfo.Name} has no email address, removing notify rather than delivering it: {customernotifyevent}"); + ct.CustomerNotifyEvent.Remove(customernotifyevent); + await ct.SaveChangesAsync(); + continue; + } + + //Get subscription for delivery + var Subscription = await ct.CustomerNotifySubscription.AsNoTracking().FirstOrDefaultAsync(x => x.Id == customernotifyevent.CustomerNotifySubscriptionId); + + //NOTE: There is no need to separate out future delivery and immediate delivery because + // All events have an event date, it's either immediate upon creation or it's future + // but not all events have an age value including ones with future event dates, + // and the default agevalue and advancenotice are both zero regardless so the block below works for either future or immediate deliveries + + var deliverAfter = customernotifyevent.EventDate + Subscription.AgeValue - Subscription.AdvanceNotice; + if (deliverAfter < DateTime.UtcNow) + { + //Do the delivery, it's kosher + await DeliverCustomerNotificationSMTP(customernotifyevent, Subscription, CustInfo.EmailAddress, ct); + } + } + } + + } + catch (Exception ex) + { + log.LogError(ex, $"Error processing customer notification event"); + DbUtil.HandleIfDatabaseUnavailableTypeException(ex); + } + finally + { + log.LogDebug("CustomerNotify is done setting to not running state and tagging lastRun timestamp"); + lastRun = DateTime.UtcNow; + NotifyIsRunning = false; + + } + + } + + + //=== + private static async Task DeliverCustomerNotificationSMTP(CustomerNotifyEvent ne, CustomerNotifySubscription subscription, string deliveryAddress, AyContext ct) + { + + var DeliveryLogItem = new CustomerNotifyDeliveryLog() + { + Processed = DateTime.UtcNow, + ObjectId = ne.ObjectId, + CustomerNotifySubscriptionId = ne.CustomerNotifySubscriptionId, + Fail = false + }; + + try + { + log.LogDebug($"DeliverCustomerNotificationSMTP delivering notify event: {ne}"); + if (string.IsNullOrWhiteSpace(deliveryAddress)) + { + DeliveryLogItem.Fail = true; + DeliveryLogItem.Error = $"No email address provided for smtp delivery; event: {ne}"; + } + else + { + + if (!ServerGlobalOpsSettingsCache.Notify.SmtpDeliveryActive) + { + await NotifyEventHelper.AddOpsProblemEvent($"Email notifications are set to OFF at server, unable to send Customer email notification for this event:{ne}"); + log.LogInformation($"** WARNING: SMTP notification is currently set to Active=False; unable to deliver Customer email notification, [CustomerId={ne.CustomerId}, Customer Notify subscription={ne.CustomerNotifySubscriptionId}]. Change this setting or remove all Customer notifications if this is permanent **"); + DeliveryLogItem.Fail = true; + DeliveryLogItem.Error = $"Email notifications are set to OFF at server, unable to send Customer email notification for this event: {ne}"; + } + else + { + + //BUILD SUBJECT AND BODY FROM TOKENS IF REQUIRED + var Subject = subscription.Subject; + var Body = subscription.Template; + + // if (Subject.Contains("{{") || Body.Contains("{{")) + // { + // //fetch the object with viz fields for easy templatization + // switch (ne.SockType) + // { + + + + // } + + + // } + + + IMailer m = Sockeye.Util.ServiceProviderProvider.Mailer; + //generate report if applicable + bool isReportableEvent = false; + // switch (ne.EventType) + // { + // case NotifyEventType.QuoteStatusChange: + // case NotifyEventType.WorkorderCompleted: + // case NotifyEventType.WorkorderStatusChange: + // isReportableEvent = true; + // break; + // } + if (isReportableEvent && subscription.LinkReportId != null) + { + long subTranslationId = (long)subscription.TranslationId; + + ReportBiz biz = new ReportBiz(ct, 1, subTranslationId, AuthorizationRoles.BizAdmin); + //example with workorder report + //{"SockType":34,"selectedRowIds":[355],"ReportId":9,"ClientMeta":{"UserName":"Sockeye SuperUser","Authorization":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxNjQ2NzgyNTc4IiwiaXNzIjoiYXlhbm92YS5jb20iLCJpZCI6IjEifQ.ad7Acq54JCRGitDWKDJFFnqKkidbdaKaFmj-RA_RG5E","DownloadToken":"NdoU8ca3LG4L39Tj2oi3UReeeM7FLevTgbgopTPhGbA","TimeZoneName":"America/Los_Angeles","LanguageName":"en-US","Hour12":true,"CurrencyName":"USD","DefaultLocale":"en","PDFDate":"3/3/22","PDFTime":"3:38 PM"}} + + var reportRequest = new DataListReportRequest(); + + reportRequest.SockType = ne.SockType; + reportRequest.ReportId = (long)subscription.LinkReportId; + reportRequest.SelectedRowIds = new long[] { ne.ObjectId }; + var jwt = Api.Controllers.AuthController.GenRpt(subTranslationId); + + //this could be adjusted by culture if we allow user to set a culture but that's getting a bit into the weeds, likely the server default is fine + var pdfDate = new DateTime().ToShortDateString(); + var pdfTime = new DateTime().ToShortTimeString(); + var h12 = subscription.Hour12 ? "true" : "false"; + reportRequest.ClientMeta = JToken.Parse($"{{'UserName':'-','Authorization':'Bearer {jwt}','TimeZoneName':'{subscription.TimeZoneOverride}','LanguageName':'{subscription.LanguageOverride}','Hour12':{h12},'CurrencyName':'{subscription.CurrencyName}','DefaultLocale':'en','PDFDate':'{pdfDate}','PDFTime':'{pdfTime}'}}"); + //get port number + var match = System.Text.RegularExpressions.Regex.Match(ServerBootConfig.SOCKEYE_USE_URLS, "[0-9]+"); + var API_URL = $"http://127.0.0.1:{match.Value}/api/{SockeyeVersion.CurrentApiVersion}/"; + var jobid = await biz.RequestRenderReport(reportRequest, DateTime.UtcNow.AddMinutes(ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT), API_URL, "CUSTOMER NOTIFICATION - NO USER"); + if (jobid == null) + { + throw new ApplicationException($"Report render job id is null failed to start"); + } + else + { + bool done = false; + DateTime bailAfter = DateTime.Now.AddMinutes(ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT); + while (!done && DateTime.Now < bailAfter) + { + var status = await JobsBiz.GetJobStatusAsync((Guid)jobid); + switch (status) + { + case JobStatus.Completed: + { + done = true; + //get job logs and parse file name from it + JobOperationsBiz jobopsbiz = new JobOperationsBiz(ct, 1, AuthorizationRoles.BizAdmin); + List log = await jobopsbiz.GetJobLogListAsync((Guid)jobid); + var lastLog = log[log.Count - 1]; + var lastLogJ = JObject.Parse(lastLog.StatusText); + var path = (string)lastLogJ["reportfilename"]; + var FilePath = FileUtil.GetFullPathForTemporaryFile(path); + var FileName = FileUtil.StringToSafeFileName(await TranslationBiz.GetTranslationStaticAsync(ne.SockType.ToString(), subTranslationId, ct) + $"-{ne.Name}.pdf").ToLowerInvariant(); + await m.SendEmailAsync(deliveryAddress, Subject, Body, ServerGlobalOpsSettingsCache.Notify, FilePath, FileName); + break; + } + case JobStatus.Failed: + case JobStatus.Absent: + throw new ApplicationException($"REPORT RENDER JOB {jobid} started but failed"); + + } + + } + if (!done) + throw new TimeoutException("JOB FAILED DUE TO REPORT RENDER TIMEOUT"); + } + } + else + await m.SendEmailAsync(deliveryAddress, Subject, Body, ServerGlobalOpsSettingsCache.Notify); + + } + + } + } + catch (Exception ex) + { + await NotifyEventHelper.AddOpsProblemEvent("SMTP Customer Notification failed", ex); + DeliveryLogItem.Fail = true; + DeliveryLogItem.Error = $"SMTP Notification failed to deliver for this Customer notify event: {ne}, message: {ex.Message}"; + log.LogDebug(ex, $"DeliverSMTP Failure delivering Customer notify event: {ne}"); + } + finally + { + //remove event no matter what + ct.CustomerNotifyEvent.Remove(ne); + + //add delivery log item + await ct.CustomerNotifyDeliveryLog.AddAsync(DeliveryLogItem); + await ct.SaveChangesAsync(); + } + } + + + + + + //=== + + + + + + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/generator/CoreJobMetricsSnapshot.cs b/server/generator/CoreJobMetricsSnapshot.cs new file mode 100644 index 0000000..d06a704 --- /dev/null +++ b/server/generator/CoreJobMetricsSnapshot.cs @@ -0,0 +1,190 @@ +using System; +using System.Linq; +using System.Diagnostics; +using Microsoft.Extensions.Logging; +using Sockeye.Util; +using Sockeye.Models; +using Microsoft.EntityFrameworkCore; +//using StackExchange.Profiling; + + +namespace Sockeye.Biz +{ + /// + /// called by Generator to gather server metrics and insert in db + /// + internal static class CoreJobMetricsSnapshot + { + private static ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("CoreJobMetricsSnapshot"); + private static TimeSpan tsDataRetention = new TimeSpan(365, 0, 0, 0, 0);//one year + private static Process _process = Process.GetCurrentProcess(); + + private static TimeSpan _oldCPUTime = TimeSpan.Zero; + private static DateTime _lastMMSnapshot = DateTime.UtcNow.Subtract(new TimeSpan(1, 1, 1, 1, 1));//ensure it captures on a fresh boot; + private static DateTime _lastDDSnapshot = DateTime.UtcNow.Subtract(new TimeSpan(1, 1, 1, 1, 1));//ensure it captures on a fresh boot; + + private static double _cpu = 0; + +#if (DEBUG) + private static TimeSpan tsMMFrequency = new TimeSpan(0, 5, 0); + private static TimeSpan tsDDFrequency = new TimeSpan(24, 0, 0); +#else + private static TimeSpan tsMMFrequency = new TimeSpan(0, 5, 0); + private static TimeSpan tsDDFrequency = new TimeSpan(24, 0, 0);//changed to 12 hours from 24 due to weird issue not gathering like it should diagnosis +#endif + //////////////////////////////////////////////////////////////////////////////////////////////// + // + // + public static void DoWork() + { + + + if (DateUtil.IsAfterDuration(_lastMMSnapshot, tsMMFrequency)) + { + ///////////////////////////////////////////// + //ONE MINUTE SNAPS + // + log.LogDebug("MM metrics snapshot"); + var now = DateTime.UtcNow; + _process.Refresh(); + + //CPU + var cpuElapsedTime = now.Subtract(_lastMMSnapshot).TotalMilliseconds; + var newCPUTime = _process.TotalProcessorTime; + var elapsedCPU = (newCPUTime - _oldCPUTime).TotalMilliseconds; + _cpu = elapsedCPU * 100 / Environment.ProcessorCount / cpuElapsedTime; + _oldCPUTime = newCPUTime; + + //MEMORY + // The memory occupied by objects. + var Allocated = GC.GetTotalMemory(false);//bigint + + // The working set includes both shared and private data. The shared data includes the pages that contain all the + // instructions that the process executes, including instructions in the process modules and the system libraries. + var WorkingSet = _process.WorkingSet64;//bigint + + // The value returned by this property represents the current size of memory used by the process, in bytes, that + // cannot be shared with other processes. + var PrivateBytes = _process.PrivateMemorySize64;//bigint + + + //NOTE: CPU percentage is *our* process cpu percentage over timeframe of last captured avg + //So it does *not* show the entire server cpu load, only for RAVEN. + //Overall, server stats need to be captured / viewed independently (digital ocean control panel for example or windows task manager) + var CPU = _cpu;// double precision + //In some cases the first snapshot taken after a reboot + //is a huge number way beyond 100 which fucks up the charts + //likely due to the algorithm above and new values but not worth looking into atm + if (CPU > 100) + { + CPU = 0; + } + using (AyContext ct = ServiceProviderProvider.DBContext) + { + //write to db + MetricMM mm = new MetricMM(Allocated, WorkingSet, PrivateBytes, CPU); + ct.MetricMM.Add(mm); + ct.SaveChanges(); + } + _lastMMSnapshot = now; + } + + + + ///////////////////////////////////////////// + //ONCE A DAY SNAPS AND CLEANUP + // + if (DateUtil.IsAfterDuration(_lastDDSnapshot, tsDDFrequency)) + { +#if (DEBUG) + log.LogInformation($"DD metrics snapshot, _lastDDSnapshot:{_lastDDSnapshot}, tsDDFrequency:{tsDDFrequency}"); +#endif + log.LogDebug("DD metrics snapshot"); + + var now = DateTime.UtcNow; + //FILES ON DISK + var UtilFilesInfo = FileUtil.GetBackupFolderSizeInfo(); + var AttachmentFilesInfo = FileUtil.GetAttachmentFolderSizeInfo(); + + //Available space + long UtilityFilesAvailableSpace = 0; + try + { + UtilityFilesAvailableSpace = FileUtil.BackupFilesDriveAvailableSpace(); + } + catch (Exception ex) + { + log.LogError(ex, "Metrics::FileUtil::UtilityFilesDriveAvailableSpace error getting available space"); + } + + long AttachmentFilesAvailableSpace = 0; + try + { + AttachmentFilesAvailableSpace = FileUtil.AttachmentFilesDriveAvailableSpace(); + } + catch (Exception ex) + { + log.LogError(ex, "Metrics::FileUtil::AttachmentFilesDriveAvailableSpace error getting available space"); + } + + using (AyContext ct = ServiceProviderProvider.DBContext) + { + //DB total size + long DBTotalSize = 0; + using (var command = ct.Database.GetDbConnection().CreateCommand()) + { + command.CommandText = "select pg_database_size(current_database());"; + ct.Database.OpenConnection(); + using (var dr = command.ExecuteReader()) + { + if (dr.HasRows) + { + DBTotalSize = dr.Read() ? dr.GetInt64(0) : 0; + } + ct.Database.CloseConnection(); + } + } + //write to db + MetricDD dd = new MetricDD() + { + AttachmentFileSize = AttachmentFilesInfo.SizeWithChildren, + AttachmentFileCount = AttachmentFilesInfo.FileCountWithChildren, + AttachmentFilesAvailableSpace = AttachmentFilesAvailableSpace, + UtilityFileSize = UtilFilesInfo.SizeWithChildren, + UtilityFileCount = UtilFilesInfo.FileCountWithChildren, + UtilityFilesAvailableSpace = UtilityFilesAvailableSpace, + DBTotalSize = DBTotalSize + }; + ct.MetricDD.Add(dd); + ct.SaveChanges(); + } + + + ///////////////////////////////// + //CLEAR OLD ENTRIES + // + + DateTime ClearDate = DateTime.UtcNow - tsDataRetention; + using (AyContext ct = ServiceProviderProvider.DBContext) + { + ct.Database.ExecuteSqlInterpolated($"delete from ametricmm where t < {ClearDate.ToUniversalTime()}"); + ct.Database.ExecuteSqlInterpolated($"delete from ametricdd where t < {ClearDate.ToUniversalTime()}"); + } + _lastDDSnapshot = now; + } + + + + + //---- + } + + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc +}//eons + diff --git a/server/generator/CoreJobNotify.cs b/server/generator/CoreJobNotify.cs new file mode 100644 index 0000000..f53c350 --- /dev/null +++ b/server/generator/CoreJobNotify.cs @@ -0,0 +1,411 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Util; + +namespace Sockeye.Biz +{ + + /// + /// Notification processor + /// turn notifyEvent records into inappnotification records for in app viewing and / or deliver smtp notifications seperately + /// + /// + internal static class CoreJobNotify + { + private static bool NotifyIsRunning = false; + private static ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("CoreJobNotify"); + private static DateTime lastRun = DateTime.MinValue; + private static DateTime lastNotifyHealthCheckSentLocal = DateTime.MinValue; + private static TimeSpan TS_24_HOURS = new TimeSpan(24, 0, 0);//used to ensure daily ops happen no more than that + +#if (DEBUG) + private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 0, 21);//no more frequently than once every 20 seconds +#else + private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 1, 1);//no more frequently than once every 1 minute +#endif + + //////////////////////////////////////////////////////////////////////////////////////////////// + // DoSweep + // + public static async Task DoWorkAsync() + { + log.LogTrace("Checking if Notify should run"); + if (NotifyIsRunning) + { + log.LogTrace("Notify is running already exiting this cycle"); + return; + } + //This will get triggered roughly every minute, but we don't want to deliver that frequently + if (DateTime.UtcNow - lastRun < RUN_EVERY_INTERVAL) + { + log.LogTrace($"Notify ran less than {RUN_EVERY_INTERVAL} ago, exiting this cycle"); + return; + } + try + { + NotifyIsRunning = true; + log.LogDebug("Notify set to RUNNING state and starting now"); + + //NotifyHealthCheck processing + //Note this deliberately uses LOCAL time in effort to deliver the health check first thing in the morning for workers + //However if server is on UTC already then that's what is used, there is no adjustment + DateTime dtNowLocal = DateTime.Now; + if (dtNowLocal - lastNotifyHealthCheckSentLocal > TS_24_HOURS) + { + //are we in the 7th to 9th hour? + if (dtNowLocal.Hour > 6 && dtNowLocal.Hour < 10) + { + log.LogDebug("Notify health check submitted to subscribers"); + await NotifyEventHelper.AddGeneralNotifyEvent(NotifyEventType.NotifyHealthCheck, "OK", ""); + lastNotifyHealthCheckSentLocal = dtNowLocal; + } + } + + + using (AyContext ct = Sockeye.Util.ServiceProviderProvider.DBContext) + { + var events = await ct.NotifyEvent.AsNoTracking().ToListAsync(); + log.LogDebug($"Found {events.Count} NotifyEvents to examine for potential delivery"); + + //iterate and deliver + foreach (var notifyevent in events) + { + //no notifications for inactive users, just delete it as if it was delivered + var UserInfo = await ct.User.AsNoTracking().Where(x => x.Id == notifyevent.UserId).Select(x => new { Active = x.Active, Name = x.Name }).FirstOrDefaultAsync(); + if (!UserInfo.Active) + { + log.LogDebug($"Inactive user {UserInfo.Name}, removing notify rather than delivering it: {notifyevent}"); + ct.NotifyEvent.Remove(notifyevent); + await ct.SaveChangesAsync(); + continue; + } + + //Get subscription for delivery + var Subscription = await ct.NotifySubscription.AsNoTracking().FirstOrDefaultAsync(x => x.Id == notifyevent.NotifySubscriptionId); + + //NOTE: There is no need to separate out future delivery and immediate delivery because + // All events have an event date, it's either immediate upon creation or it's future + // but not all events have an age value including ones with future event dates, + // and the default agevalue and advancenotice are both zero regardless so the block below works for either future or immediate deliveries + + var deliverAfter = notifyevent.EventDate + Subscription.AgeValue - Subscription.AdvanceNotice; + if (deliverAfter < DateTime.UtcNow) + { + //Check "circuit breaker" for notification types that could + //repeat rapidly + //(e.g. pm notification error for a fucked up PM that is attempted every few minutes or a + //system exception for something that pops up every few minutes or a thousand times in a hour etc) + //Don't check for ones that are regular object based + //which can and will properly send out the same notification regularly + //(e.g. workorder status change into out of and back into the same staus) + switch (notifyevent.EventType) + { + case NotifyEventType.BackupStatus: + case NotifyEventType.GeneralNotification: + case NotifyEventType.ServerOperationsProblem: + //case NotifyEventType.PMGenerationFailed: + { + //check if we've just delivered this same thing in the last 12 hours which is the hard limit (case 3917) + var twelvehoursago = DateTime.UtcNow - new TimeSpan(12, 0, 0); + + //look for same delivery less than last12hours ago + if (await ct.NotifyDeliveryLog.AnyAsync(z => z.Processed > twelvehoursago && z.NotifySubscriptionId == notifyevent.NotifySubscriptionId && z.ObjectId == notifyevent.ObjectId)) + { + log.LogDebug($"Notification event will not be delivered: repetitive (server system event type and delivered at least once in the last 12 hours to this subscriber: {notifyevent})"); + ct.NotifyEvent.Remove(notifyevent); + await ct.SaveChangesAsync(); +#if (DEBUG) + log.LogInformation($"DeliverInApp event will not be delivered: repetitive (server system event type and delivered at least once in the last 12 hours to this subscriber: {notifyevent})"); +#endif + continue; + } + } + break; + } + + //Do the delivery, it's kosher + if (Subscription.DeliveryMethod == NotifyDeliveryMethod.App) + await DeliverInApp(notifyevent, Subscription.AgeValue, ct); + else if (Subscription.DeliveryMethod == NotifyDeliveryMethod.SMTP) + await DeliverUserNotificationSMTP(notifyevent, Subscription.AgeValue, Subscription.AdvanceNotice, Subscription.DeliveryAddress, ct); + } + } + } + + } + catch (Exception ex) + { + log.LogError(ex, $"Error processing notification event"); + DbUtil.HandleIfDatabaseUnavailableTypeException(ex); + } + finally + { + log.LogDebug("Notify is done setting to not running state and tagging lastRun timestamp"); + lastRun = DateTime.UtcNow; + NotifyIsRunning = false; + + } + + } + + private static async Task DeliverInApp(NotifyEvent ne, TimeSpan ageValue, AyContext ct) + { + log.LogDebug($"DeliverInApp notify event: {ne}"); + + //Place in the In-app notification table for user to view + await ct.InAppNotification.AddAsync( + new InAppNotification() + { + UserId = ne.UserId, + SockType = ne.SockType, + ObjectId = ne.ObjectId, + EventType = ne.EventType, + NotifySubscriptionId = ne.NotifySubscriptionId, + Message = ne.Message, + Name = ne.Name, + AgeValue = ageValue, + DecValue = ne.DecValue + }); + + ct.NotifyEvent.Remove(ne); + //add delivery log item + await ct.NotifyDeliveryLog.AddAsync(new NotifyDeliveryLog() + { + Processed = DateTime.UtcNow, + ObjectId = ne.ObjectId, + NotifySubscriptionId = ne.NotifySubscriptionId, + Fail = false + }); + await ct.SaveChangesAsync(); + } + + + + private static async Task DeliverUserNotificationSMTP(NotifyEvent ne, TimeSpan ageValue, TimeSpan advanceNotice, string deliveryAddress, AyContext ct) + { + var DeliveryLogItem = new NotifyDeliveryLog() + { + Processed = DateTime.UtcNow, + ObjectId = ne.ObjectId, + NotifySubscriptionId = ne.NotifySubscriptionId, + Fail = false + }; + + try + { + log.LogDebug($"DeliverSMTP delivering notify event: {ne}"); + if (string.IsNullOrWhiteSpace(deliveryAddress)) + { + await NotifyEventHelper.AddGeneralNotifyEvent(NotifyEventType.GeneralNotification, $"No email address is set in subscription to deliver email notification. This event will be removed from the delivery queue as undeliverable: {ne}", "Error", null, ne.UserId); + DeliveryLogItem.Fail = true; + DeliveryLogItem.Error = $"No email address provided for smtp delivery; event: {ne}"; + } + else + { + //Email notification requires pre-translated values + List TranslationKeysToFetch = new List(); + TranslationKeysToFetch.Add(ne.SockType.ToString()); + TranslationKeysToFetch.Add("NotifySubscription"); + TranslationKeysToFetch.Add("NotifySubscriptionLinkText"); + if (ne.Name == "~SERVER~") + TranslationKeysToFetch.Add("Server"); + var EventTypeTranslationKey = "NotifyEvent" + ne.EventType.ToString(); + TranslationKeysToFetch.Add(EventTypeTranslationKey); + if (ageValue != TimeSpan.Zero ) + { + TranslationKeysToFetch.Add("TimeSpanDays"); + TranslationKeysToFetch.Add("TimeSpanHours"); + TranslationKeysToFetch.Add("TimeSpanMinutes"); + TranslationKeysToFetch.Add("TimeSpanSeconds"); + } + // if (ne.EventType == NotifyEventType.CustomerServiceImminent) + // TranslationKeysToFetch.Add("NotifyEventCustomerServiceImminentMessage"); + + // if (ne.DecValue != 0 && ne.EventType == NotifyEventType.WorkorderTotalExceedsThreshold) + // { + // TranslationKeysToFetch.Add("Total"); + // } + + //get translations + var transid = await ct.UserOptions.AsNoTracking().Where(x => x.UserId == ne.UserId).Select(x => x.TranslationId).FirstOrDefaultAsync(); + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, transid); + + var NotifySubscriptionLinkText = LT["NotifySubscriptionLinkText"]; + var name = ne.Name; + if (name == "~SERVER~") + name = LT["Server"]; + + //SockType translation + var SockTypeTranslated = "-"; + if (ne.SockType != SockType.NoType) + SockTypeTranslated = $"{LT[ne.SockType.ToString()]}"; + + //subscription name translation + var SubscriptionTypeName = LT[EventTypeTranslationKey]; + + + + //Age relevant notification + string AgeDisplay = ""; + if (ageValue != TimeSpan.Zero) + AgeDisplay = $"({Sockeye.Util.DateUtil.FormatTimeSpan(ageValue, LT["TimeSpanDays"], LT["TimeSpanHours"], LT["TimeSpanMinutes"], LT["TimeSpanSeconds"])})\n"; + + + //DecValue + string DecDisplay = ""; + // //could be money or integer depending on typ + // if (ne.DecValue != 0) + // { + // if (ne.EventType == NotifyEventType.WorkorderTotalExceedsThreshold) + // DecDisplay = $"{LT["Total"]}: {ne.DecValue.ToString("N2", System.Globalization.CultureInfo.InvariantCulture)}\n"; + // else if (ne.EventType == NotifyEventType.UnitMeterReadingMultipleExceeded) + // DecDisplay = $"{System.Convert.ToInt64(ne.DecValue).ToString()}\n"; + // } + + string subject = ""; + + + IMailer m = Sockeye.Util.ServiceProviderProvider.Mailer; + var body = ""; + + //Special notification handling + switch (ne.EventType) + { + // case NotifyEventType.CustomerServiceImminent: + // subject = SubscriptionTypeName; + // body = LT["NotifyEventCustomerServiceImminentMessage"].Replace("{0}", Sockeye.Util.DateUtil.FormatTimeSpan(advanceNotice, LT["TimeSpanDays"], LT["TimeSpanHours"], LT["TimeSpanMinutes"], LT["TimeSpanSeconds"])); + // body += $"\n{OpenObjectUrlBuilder(ne.SockType, ne.ObjectId, ne.EventType)}\n"; + // break; + default: + subject = $"AY:{SockTypeTranslated}:{name}:{SubscriptionTypeName}"; + if (ne.ObjectId != 0 || ne.EventType == NotifyEventType.BackupStatus) + { + body = $"{AgeDisplay}{DecDisplay}{SockTypeTranslated}\n{OpenObjectUrlBuilder(ne.SockType, ne.ObjectId, ne.EventType)}\n"; + } + body += ne.Message; + break; + } + + + //Add link to subscription, all messages have this regardless of content + //http://localhost:8080/open/51/1 //add subscription link, notifysub is object type 51 + if (!body.EndsWith('\n')) + body += "\n"; + + body += $"-----\n{NotifySubscriptionLinkText}\n{OpenSubscriptionUrlBuilder(ne.NotifySubscriptionId)}\n"; + + if (!ServerGlobalOpsSettingsCache.Notify.SmtpDeliveryActive) + { + await NotifyEventHelper.AddGeneralNotifyEvent(NotifyEventType.GeneralNotification, $"Email notifications are set to OFF at server, unable to send email notification for this event:{ne}", "Error", null, ne.UserId); + log.LogInformation($"** WARNING: SMTP notification is currently set to Active=False; unable to deliver email notification, re-routed to in-app notification instead [UserId={ne.UserId}, Notify subscription={ne.NotifySubscriptionId}]. Change this setting or have users remove email delivery notifications if this is permanent **"); + DeliveryLogItem.Fail = true; + DeliveryLogItem.Error = $"Email notifications are set to OFF at server, unable to send email notification for this event: {ne}"; + } + else + await m.SendEmailAsync(deliveryAddress, subject, body, ServerGlobalOpsSettingsCache.Notify); + + } + } + catch (Exception ex) + { + await NotifyEventHelper.AddOpsProblemEvent("SMTP Notification failed", ex); + await NotifyEventHelper.AddGeneralNotifyEvent(NotifyEventType.GeneralNotification, $"An error prevented delivering the following notification via email. System operator users have been notified:{ne}", "Error", null, ne.UserId); + DeliveryLogItem.Fail = true; + DeliveryLogItem.Error = $"SMTP Notification failed to deliver for this event: {ne}, message: {ex.Message}"; + log.LogDebug(ex, $"DeliverSMTP Failure delivering notify event: {ne}"); + } + finally + { + //remove event no matter what + ct.NotifyEvent.Remove(ne); + + //add delivery log item + await ct.NotifyDeliveryLog.AddAsync(DeliveryLogItem); + await ct.SaveChangesAsync(); + } + } + + + //Called from ops notification settings to test smtp setup by delivering to address of choosing + public static async Task TestSMTPDelivery(string toAddress) + { + IMailer m = Sockeye.Util.ServiceProviderProvider.Mailer; + try + { + await m.SendEmailAsync(toAddress, "Test from Notification system", "This is a test to confirm notification system is working", ServerGlobalOpsSettingsCache.Notify); + return "ok"; + } + catch (Exception ex) + { + await NotifyEventHelper.AddOpsProblemEvent("SMTP (TEST) Notification failed", ex); + return ExceptionUtil.ExtractAllExceptionMessages(ex); + } + + } + + + //Open object url + private static string OpenObjectUrlBuilder(SockType aType, long id, NotifyEventType net) + { + var ServerUrl = ServerGlobalOpsSettingsCache.Notify.SockeyeServerURL; + if (string.IsNullOrWhiteSpace(ServerUrl)) + { + NotifyEventHelper.AddOpsProblemEvent("Notification system: The OPS Notification setting is empty for Sockeye Server URL. This prevents Notification system from linking events to openable objects.").Wait(); + return "OPS ERROR NO SERVER URL CONFIGURED"; + } + ServerUrl = ServerUrl.Trim().TrimEnd('/'); + + //HANDLE ITEMS WITHOUT TYPE OR ID + if (net == NotifyEventType.BackupStatus) + { + return $"{ServerUrl}/ops-backup"; + } + + + //Might not have a type or id in which case nothing directly to open + if (aType == SockType.NoType || id == 0) + { + return ServerUrl; + } + + if (NotifyEventType.ObjectDeleted == net) + { + //goto event log for item + // path: "/history/:socktype/:recordid/:userlog?", + return $"{ServerUrl}/history/{(int)aType}/{id}"; + } + + //default is to open the object in question directly + return $"{ServerUrl}/open/{(int)aType}/{id}"; + } + + + + //url to open subscription for editing + private static string OpenSubscriptionUrlBuilder(long id) + { + var ServerUrl = ServerGlobalOpsSettingsCache.Notify.SockeyeServerURL; + if (string.IsNullOrWhiteSpace(ServerUrl)) + { + NotifyEventHelper.AddOpsProblemEvent("Notification system: The OPS Notification setting is empty for Sockeye Server URL. This prevents Notification system from linking events to openable objects.").Wait(); + return "OPS ERROR NO SERVER URL CONFIGURED"; + } + ServerUrl = ServerUrl.Trim().TrimEnd('/'); + + //default is to open the object in question directly + return $"{ServerUrl}/open/{(int)SockType.NotifySubscription}/{id}"; + } + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/generator/CoreJobReportRenderEngineProcessCleanup.cs b/server/generator/CoreJobReportRenderEngineProcessCleanup.cs new file mode 100644 index 0000000..043184a --- /dev/null +++ b/server/generator/CoreJobReportRenderEngineProcessCleanup.cs @@ -0,0 +1,41 @@ +using System; +using Microsoft.Extensions.Logging; +using Sockeye.Util; +using System.Threading.Tasks; + +namespace Sockeye.Biz +{ + /// + /// called by Generator to kill report generation processor stuck in limbo (chromium at this time) + /// + internal static class CoreJobReportRenderEngineProcessCleanup + { + private static ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("CoreJobReportRenderEngineProcessCleanup"); + + private static DateTime _lastRun = DateTime.UtcNow; + //SET LOW INTENTIONALLY AS CAN EAT UP A LOT OF RESOURCES QUICKLY IF RUN'S PAST TIME + private static TimeSpan tsRunEvery = new TimeSpan(0, 0, 20);//every twenty seconds run the cleanup task + + //////////////////////////////////////////////////////////////////////////////////////////////// + // + // + public static async Task DoWork() + { + if (DateUtil.IsAfterDuration(_lastRun, tsRunEvery)) + { + log.LogTrace("Checking for expired report jobs"); + await Util.ReportRenderManager.KillExpiredRenders(log); + var now = DateTime.UtcNow; + _lastRun = now; + } + } + + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc +}//eons + diff --git a/server/generator/CoreJobSweeper.cs b/server/generator/CoreJobSweeper.cs new file mode 100644 index 0000000..6be7854 --- /dev/null +++ b/server/generator/CoreJobSweeper.cs @@ -0,0 +1,149 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Sockeye.Models; + +namespace Sockeye.Biz +{ + + //#################### NOTE: This also does some license checking tasks, put in here instead of the corejoblicense job deliberately ############# + /// + /// JobSweeper - called by Generator to clean out old jobs that are completed and their logs + /// + /// + internal static class CoreJobSweeper + { + private static ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("CoreJobSweeper"); + private static DateTime lastSweep = DateTime.MinValue; + private static TimeSpan SWEEP_EVERY_INTERVAL = new TimeSpan(0, 10, 10);//every ten minutes roughly + private static TimeSpan SUCCEEDED_JOBS_DELETE_AFTER_THIS_TIMESPAN = new TimeSpan(14, 0, 0, 0);//14 days + private static TimeSpan FAILED_JOBS_DELETE_AFTER_THIS_TIMESPAN = new TimeSpan(14, 0, 0, 0);//14 days (gives people time to notice and look into it) + private static TimeSpan INTERNAL_JOBS_LOGS_DELETE_AFTER_THIS_TIMESPAN = new TimeSpan(14, 0, 0, 0);//14 days + + private static TimeSpan RUNNING_JOBS_BECOME_FAILED_AFTER_THIS_TIMESPAN = new TimeSpan(24, 0, 0);//24 hours (time running jobs are allowed to sit in "running" state before considered failed) + + //////////////////////////////////////////////////////////////////////////////////////////////// + // DoSweep + // + public static async Task DoWorkAsync() + { + //This will get triggered roughly every minute, but we don't want to sweep that frequently + if (DateTime.UtcNow - lastSweep < SWEEP_EVERY_INTERVAL) + return; + + log.LogDebug("Sweep starting"); + using (AyContext ct = Sockeye.Util.ServiceProviderProvider.DBContext) + { + //SWEEP SUCCESSFUL JOBS + //calculate cutoff to delete + DateTime dtDeleteCutoff = DateTime.UtcNow - SUCCEEDED_JOBS_DELETE_AFTER_THIS_TIMESPAN; + await sweepAsync(ct, dtDeleteCutoff, JobStatus.Completed); + + //SWEEP FAILED JOBS + //calculate cutoff to delete + dtDeleteCutoff = DateTime.UtcNow - FAILED_JOBS_DELETE_AFTER_THIS_TIMESPAN; + await sweepAsync(ct, dtDeleteCutoff, JobStatus.Failed); + + //KILL STUCK JOBS + //calculate cutoff to delete + DateTime dtRunningDeadline = DateTime.UtcNow - RUNNING_JOBS_BECOME_FAILED_AFTER_THIS_TIMESPAN; + await killStuckJobsAsync(ct, dtRunningDeadline); + + //SWEEP INTERNAL JOB LOG + //calculate cutoff to delete + dtDeleteCutoff = DateTime.UtcNow - INTERNAL_JOBS_LOGS_DELETE_AFTER_THIS_TIMESPAN; + await SweepInternalJobsLogsAsync(ct, dtDeleteCutoff); + + + + } + lastSweep = DateTime.UtcNow; + } + + + private static async Task sweepAsync(AyContext ct, DateTime dtDeleteCutoff, JobStatus jobStatus) + { + //Get the deleteable succeeded jobs list + var jobs = await ct.OpsJob + .AsNoTracking() + .Where(z => z.Created < dtDeleteCutoff && z.JobStatus == jobStatus) + .OrderBy(z => z.Created) + .ToListAsync(); + + log.LogDebug($"SweepAsync processing: cutoff={dtDeleteCutoff.ToString()}, for {jobs.Count.ToString()} jobs of status {jobStatus.ToString()}"); + + foreach (OpsJob j in jobs) + { + try + { + + await JobsBiz.RemoveJobAndLogsAsync(j.GId); + } + catch (Exception ex) + { + log.LogError(ex, "sweepAsync exception calling JobsBiz.RemoveJobAndLogsAsync"); + //for now just throw it but this needs to be removed when logging added and better handling + throw; + } + } + } + + + /// + /// Kill jobs that have been stuck in "running" state for too long + /// + private static async Task killStuckJobsAsync(AyContext ct, DateTime dtRunningDeadline) + { + //Get the deleteable succeeded jobs list + var jobs = await ct.OpsJob + .AsNoTracking() + .Where(z => z.Created < dtRunningDeadline && z.JobStatus == JobStatus.Running) + .OrderBy(z => z.Created) + .ToListAsync(); + + log.LogDebug($"killStuckJobsAsync processing: cutoff={dtRunningDeadline.ToString()}, for {jobs.Count.ToString()} jobs of status {JobStatus.Running.ToString()}"); + + foreach (OpsJob j in jobs) + { + //OPSMETRIC + await JobsBiz.LogJobAsync(j.GId, "LT:JobFailed LT:TimedOut"); + log.LogError($"Job found job stuck in running status and set to failed: deadline={dtRunningDeadline.ToString()}, jobId={j.GId.ToString()}, jobname={j.Name}, jobtype={j.JobType.ToString()}, jobAType={j.SockType.ToString()}, jobObjectId={j.ObjectId.ToString()}"); + await JobsBiz.UpdateJobStatusAsync(j.GId, JobStatus.Failed); + } + } + + + private static async Task SweepInternalJobsLogsAsync(AyContext ct, DateTime dtDeleteCutoff) + { + //Get the deleteable list (this is for reporting, could easily just do it in one go) + var logs = await ct.OpsJobLog + .AsNoTracking() + .Where(z => z.Created < dtDeleteCutoff) + .OrderBy(z => z.Created) + .ToListAsync(); + + log.LogDebug($"SweepInternalJobsLogsAsync processing: cutoff={dtDeleteCutoff.ToString()}, for {logs.Count.ToString()} log entries"); + + foreach (OpsJobLog l in logs) + { + try + { + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from aopsjoblog where gid = {l.GId}"); + } + catch (Exception ex) + { + log.LogError(ex, "SweepInternalJobsLogsAsync exception removed old log entries"); + throw; + } + } + } + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/generator/CoreJobTempFolderCleanup.cs b/server/generator/CoreJobTempFolderCleanup.cs new file mode 100644 index 0000000..9b4f013 --- /dev/null +++ b/server/generator/CoreJobTempFolderCleanup.cs @@ -0,0 +1,56 @@ +using System; +using Microsoft.Extensions.Logging; +using Sockeye.Util; + + +namespace Sockeye.Biz +{ + /// + /// called by Generator to keep temp folder squeaky clean + /// + internal static class CoreJobTempFolderCleanup + { + private static ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("CoreJobTempFolderCleanup"); + + private static DateTime _lastRun = DateTime.UtcNow; + + +#if (DEBUG) + private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 0, 21);//no more frequently than once every 20 seconds +#else + private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 5, 2);//no more frequently than once every 5 minutes +#endif + + // private static TimeSpan tsRunEvery = new TimeSpan(0, 5, 2);//every this minutes run the cleanup task + + //erase any files found to be older than 15 minutes (which coincides with max report rendering timeout) +#if (DEBUG) + private static TimeSpan DELETE_IF_OLDER_THAN = new TimeSpan(0, 2, 1);//2 minutes max +#else + private static TimeSpan DELETE_IF_OLDER_THAN = new TimeSpan(0, 15, 1); +#endif + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // + // + public static void DoWork() + { + if (DateUtil.IsAfterDuration(_lastRun, RUN_EVERY_INTERVAL)) + { + log.LogDebug("Temp cleanup now"); + FileUtil.CleanTemporaryFilesFolder(DELETE_IF_OLDER_THAN); + var now = DateTime.UtcNow; + _lastRun = now; + } + } + + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc +}//eons + diff --git a/server/generator/CoreNotificationSweeper.cs b/server/generator/CoreNotificationSweeper.cs new file mode 100644 index 0000000..86353f7 --- /dev/null +++ b/server/generator/CoreNotificationSweeper.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Sockeye.Models; + +namespace Sockeye.Biz +{ + + /// + /// Clear out old data no longer required for notification system + /// + internal static class CoreNotificationSweeper + { + private static ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("CoreNotificationSweeper"); + private static DateTime lastSweep = DateTime.MinValue; + private static TimeSpan DELETE_AFTER_AGE = new TimeSpan(90, 0, 0, 0);//## WARNING CoreJobPMInventoryCheck EXPECTS THIS TO BE a pretty big value 90 days right now + private static TimeSpan SWEEP_EVERY_INTERVAL = new TimeSpan(8, 0, 0);//once every 8 hours, three times a day + + //////////////////////////////////////////////////////////////////////////////////////////////// + // DoSweep + // + public static async Task DoWorkAsync() + { + //This will get triggered roughly every minute, but we don't want to sweep that frequently + if (DateTime.UtcNow - lastSweep < SWEEP_EVERY_INTERVAL) + return; + DateTime dtDeleteCutoff = DateTime.UtcNow - DELETE_AFTER_AGE; + DateTime dtPastEventCutoff = DateTime.UtcNow - SWEEP_EVERY_INTERVAL; + + log.LogDebug("Sweep starting"); + using (AyContext ct = Sockeye.Util.ServiceProviderProvider.DBContext) + { + //Notification (in-App notifications table) - deletes all APP notifications older than 90 days (if they want to keep it then can turn it into a reminder or SAVE it somehow) + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from ainappnotification where created < {dtDeleteCutoff}"); + + //NotifyEvent - deletes any notifyevent with no event date created more than 90 days ago + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from anotifyevent where eventdate is null and created < {dtDeleteCutoff}"); + + //NotifyEvent - If has event date and it's in the past by more than an 8 hours (or whatever sweep interval is, to allow for items about to be delivered momentarily as delivery is on briefer cycle than sweep) + //then deletes it if created more than 90 days ago (pretty sure there are no back dated events, once it's passed it's past) + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from anotifyevent where eventdate < {dtPastEventCutoff} and created < {dtDeleteCutoff}"); + + //NotifyDeliveryLog - deletes all log items older than 90 days (NOTE: this log is also used to identify and prevent high frequency repetitive dupes) + //Note: also has bearing on CoreJobPMInventoryCheck which uses notifydeliverylog to check if pm inventory notify has been done it's one time already + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from anotifydeliverylog where processed < {dtDeleteCutoff}"); + + //CustomerNotifyDeliveryLog - deletes all log items older than 90 days + await ct.Database.ExecuteSqlInterpolatedAsync($"delete from acustomernotifydeliverylog where processed < {dtDeleteCutoff}"); + + + } + lastSweep = DateTime.UtcNow; + } + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/generator/Generate.cs b/server/generator/Generate.cs new file mode 100644 index 0000000..9e36857 --- /dev/null +++ b/server/generator/Generate.cs @@ -0,0 +1,81 @@ +using System.Threading; +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Sockeye.Biz; +using Sockeye.Util; + +namespace Sockeye.Generator +{ + //Implemented from a example here + //https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/background-tasks-with-ihostedservice + + /* + LOOKAT: Generator tasks that should happen: + - Periodically erase any temp files written to userfiles root (attachments temp files) that are older than a day + - These files should be normally erased within seconds after uploading and processing into their permanent folder but shit will go wrong + */ + + public class GeneratorService : BackgroundService + { + private readonly ILogger log; + // private const int MAXIMUM_MS_ALLOWED_FOR_PROCESSING_ALL_JOBS = 1 * 60 * 1000;//1 minutes TEST TEST TEST ##### +// #if (DEBUG) +// private const int GENERATE_SECONDS = 1; +// #else +// private const int GENERATE_SECONDS = 20; +// #endif + + + + + public GeneratorService(ILogger logger) + { + log = logger; + } + + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + + log.LogInformation($"GeneratorService is starting."); + + stoppingToken.Register(() => + log.LogDebug($" GeneratorService background task is stopping.")); + + + while (!stoppingToken.IsCancellationRequested) + { + if (!ServerGlobalOpsSettingsCache.BOOTING) + { + // log.LogTrace($"GeneratorService running jobs"); + + //================================================================= + try + { + await JobsBiz.ProcessJobsAsync(); + } + catch (Exception ex) + { + log.LogError(ex, "Generate::ProcessJobs result in exception error "); + } + //================================================================= + } + //There *MUST* be a delay here or the server will come to a standstill + //ideally need to move on from this method of generation to something external or more reasonable but + //this does work, we need to get to release, for our projected use load this should be fine and it's + //a documented by MS method to do asp.net core job processing + //https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-6.0&tabs=visual-studio + await Task.Delay(1000); + } + log.LogInformation($"GeneratorService is stopping"); + } + + public override Task StopAsync(CancellationToken stoppingToken) + { + log.LogDebug($"GeneratorService StopAsync triggered"); + return Task.FromResult(0); + // Run any needed clean-up actions + } + }//eoc +}//eons \ No newline at end of file diff --git a/server/kpi/IAyaKPI.cs b/server/kpi/IAyaKPI.cs new file mode 100644 index 0000000..a121a02 --- /dev/null +++ b/server/kpi/IAyaKPI.cs @@ -0,0 +1,17 @@ +using System; +using Sockeye.Biz; +namespace Sockeye.KPI +{ + internal interface IAyaKPI + { + //allowed roles to access this kpi + AuthorizationRoles AllowedRoles { get; } + + //build the data and meta queries based on the criteria and this kpi's standard query + void BuildQuery(KPIRequestOptions options, long userId); + string MetaQuery{get;}//Query to fetch json meta data for report purposes mainly (lookup stuff like names etc where applicable) + string DataQuery{get;}//Query to fetch json format data for result set + string ErrorMessage{get;}//if there was a problem then this is set with the error message + + } +} \ No newline at end of file diff --git a/server/kpi/KPIFactory.cs b/server/kpi/KPIFactory.cs new file mode 100644 index 0000000..03a431b --- /dev/null +++ b/server/kpi/KPIFactory.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; + + +namespace Sockeye.KPI +{ + internal static class KPIFactory + { + + //Instantiate list object specified from type + internal static IAyaKPI GetAyaKPI(string name) + { + switch (name) + { + + default: + throw new System.NotImplementedException($"KPI {name} NOT IMPLEMENTED"); + + } + //return null; + } + + // //List all the KPI types available + // internal static List GetListOfAllKPI() + // { + // List ret = new List(); + + // ret.Add("WorkOrderItemLaborQuantitySummary"); + // ret.Add("WorkOrderUnscheduledOpenList"); + + // return ret; + // } + }//eoc +}//eons \ No newline at end of file diff --git a/server/kpi/KPIFetcher.cs b/server/kpi/KPIFetcher.cs new file mode 100644 index 0000000..9346f08 --- /dev/null +++ b/server/kpi/KPIFetcher.cs @@ -0,0 +1,158 @@ +//#define AYSHOWKPIQUERYINFO + +using Sockeye.Biz; +using Newtonsoft.Json.Linq; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace Sockeye.KPI +{ + internal static class KPIFetcher + { + +#if (AYSHOWKPIQUERYINFO) +#if (DEBUG) +#warning FYI AYSHOWKPIQUERYINFO is defined +#else +#error ### HOLDUP: AYSHOWKPIQUERYINFO is defined in a RELEASE BUILD!!!! +#endif +#endif + + //case 4200 + //### WARNING: IF CHANGE HERE **must** update CLIENT dash-base.js same named constant as it relies on it's own copy of this CONSTANT value to indicate if there are possibly more + internal const int KPI_LIST_MAX_ITEMS_TO_RETURN = 100;//Maximum number of items to return from a KPI dashboard query so as not to overwhelm the client with too much data + + //////////////////////////////////////////////// + // Get the kpi data requested + // + // + internal static async Task GetResponseAsync(AyContext ct, KPIRequestOptions options, AuthorizationRoles userRoles, ILogger log, long userId) + { + + /* + return a jobject like: + { + error: "if error this key present", + meta: {username:"joe blow","date range:blah to blah"} + data:[{r1},{r2},{r3}] + } + */ + //instantiate the list + var kpi = KPIFactory.GetAyaKPI(options.KPIName); + + if (!Sockeye.Api.ControllerHelpers.Authorized.HasAnyRole(userRoles, kpi.AllowedRoles)) + { + return JObject.FromObject(new + { + error = "NOT AUTHORIZED" + }); + } + + kpi.BuildQuery(options, userId); + + if (!string.IsNullOrWhiteSpace(kpi.ErrorMessage)) + { + return JObject.FromObject(new + { + error = kpi.ErrorMessage + }); + } + + + JArray jData = new JArray(); + JArray jMeta = new JArray(); + +#if (DEBUG && AYSHOWKPIQUERYINFO) + System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch(); + +#endif + //QUERY THE DB + using (var command = ct.Database.GetDbConnection().CreateCommand()) + { + await ct.Database.OpenConnectionAsync(); + + //GET DATA RETURN ROWS + command.CommandText = kpi.DataQuery; + try + { +#if (DEBUG && AYSHOWKPIQUERYINFO) + stopWatch.Start(); +#endif + using (var dr = await command.ExecuteReaderAsync()) + { +#if (DEBUG && AYSHOWKPIQUERYINFO) + stopWatch.Stop(); + log.LogInformation($"(debug) KPIFetcher:GetResponse DATA query took {stopWatch.ElapsedMilliseconds}ms to execute: {kpi.DataQuery}"); + stopWatch.Reset(); +#endif + + while (dr.Read()) + { + //only one column and it's the zeroth json string column + if (!dr.IsDBNull(0)) + jData.Add(JObject.Parse(dr.GetString(0))); + } + } + + if (!string.IsNullOrWhiteSpace(kpi.MetaQuery)) + { + //GET META DATA ROWS + command.CommandText = kpi.MetaQuery; +#if (DEBUG && AYSHOWQUERYINFO) + stopWatch.Start(); +#endif + using (var dr = await command.ExecuteReaderAsync()) + { +#if (DEBUG && AYSHOWQUERYINFO) + stopWatch.Stop(); + log.LogInformation($"(debug) KPIFetcher:GetResponse META query took {stopWatch.ElapsedMilliseconds}ms to execute: {qTotalRecordsQuery}"); +#endif + while (dr.Read()) + { + //only one column and it's the zeroth json string column + if (!dr.IsDBNull(0)) + jMeta.Add(JObject.Parse(dr.GetString(0))); + } + } + } + } + catch (Npgsql.PostgresException e) + { + //log out the exception and the query + log.LogError("KPIFetcher:GetResponseAsync query failed. Data Query was:"); + log.LogError(kpi.DataQuery); + log.LogError("Meta Query was:"); + log.LogError(kpi.MetaQuery); + log.LogError(e, "DB Exception"); + throw new System.Exception("KPIFetcher:GetResponseAsync - Query failed see log"); + + } + catch (System.Exception e) + { + //ensure any other type of exception gets surfaced properly + //log out the exception and the query + log.LogError("KPIFetcher:GetResponseAsync unexpected failure. Data Query was:"); + log.LogError(kpi.DataQuery); + log.LogError("Meta Query was:"); + log.LogError(kpi.MetaQuery); + log.LogError(e, "Exception"); + throw new System.Exception("KPIFetcher:GetResponseAsync - unexpected failure see log"); + } + + + return JObject.FromObject(new + { + meta = jMeta, + data = jData + }); + + } + } + + + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/kpi/KPIRequestOptions.cs b/server/kpi/KPIRequestOptions.cs new file mode 100644 index 0000000..5f1b818 --- /dev/null +++ b/server/kpi/KPIRequestOptions.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json.Linq; +using System; + +namespace Sockeye.KPI +{ + + //string kpiName, string criteria, DateTimeOffset clientTimeStamp + + + public sealed class KPIRequestOptions + { + + [FromBody] + public string KPIName { get; set; } + + [FromBody] + public JObject Criteria { get; set; } + + [FromBody] + public DateTimeOffset ClientTimeStamp { get; set; } + + public KPIRequestOptions() + { + KPIName=string.Empty; + Criteria=null; + } + } + +} \ No newline at end of file diff --git a/server/models/AyContext.cs b/server/models/AyContext.cs new file mode 100644 index 0000000..f2973fc --- /dev/null +++ b/server/models/AyContext.cs @@ -0,0 +1,132 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Sockeye.Models +{ + public partial class AyContext : DbContext + { + public virtual DbSet SchemaVersion { get; set; } + public virtual DbSet MetricMM { get; set; } + public virtual DbSet MetricDD { get; set; } + public virtual DbSet User { get; set; } + public virtual DbSet UserOptions { get; set; } + public virtual DbSet GlobalBizSettings { get; set; } + public virtual DbSet GlobalOpsBackupSettings { get; set; } + public virtual DbSet GlobalOpsNotificationSettings { get; set; } + public virtual DbSet Event { get; set; } + public virtual DbSet SearchDictionary { get; set; } + public virtual DbSet SearchKey { get; set; } + public virtual DbSet FileAttachment { get; set; } + public virtual DbSet OpsJob { get; set; } + public virtual DbSet OpsJobLog { get; set; } + public virtual DbSet Translation { get; set; } + public virtual DbSet TranslationItem { get; set; } + public virtual DbSet DataListSavedFilter { get; set; } + public virtual DbSet DataListColumnView { get; set; } + public virtual DbSet Tag { get; set; } + public virtual DbSet FormCustom { get; set; } + public virtual DbSet FormUserOptions { get; set; } + public virtual DbSet PickListTemplate { get; set; } + public virtual DbSet Memo { get; set; } + public virtual DbSet Reminder { get; set; } + public virtual DbSet Review { get; set; } + public virtual DbSet Customer { get; set; } + public virtual DbSet CustomerNote { get; set; } + public virtual DbSet HeadOffice { get; set; } + public virtual DbSet NotifySubscription { get; set; } + public virtual DbSet NotifyEvent { get; set; } + public virtual DbSet InAppNotification { get; set; } + public virtual DbSet NotifyDeliveryLog { get; set; } + + + + + public virtual DbSet Logo { get; set; } + public virtual DbSet Report { get; set; } + public virtual DbSet DashboardView { get; set; } + + + public virtual DbSet CustomerNotifySubscription { get; set; } + public virtual DbSet CustomerNotifyEvent { get; set; } + public virtual DbSet CustomerNotifyDeliveryLog { get; set; } + + + + public virtual DbSet Integration { get; set; } + public virtual DbSet IntegrationItem { get; set; } + public virtual DbSet IntegrationLog { get; set; } + + //Note: had to add this constructor to work with the code in startup.cs that gets the connection string from the appsettings.json file + //and commented out the above on configuring + public AyContext(DbContextOptions options) : base(options) + { } + + //https://stackoverflow.com/a/64053832/8939 + public void Replace(TEntity oldEntity, TEntity newEntity) where TEntity : class + { + ChangeTracker.TrackGraph(oldEntity, e => e.Entry.State = EntityState.Deleted); + ChangeTracker.TrackGraph(newEntity, e => e.Entry.State = e.Entry.IsKeySet ? EntityState.Modified : EntityState.Added); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + + //AUTOMATICALLY MATCH NAMES + //https://andrewlock.net/customising-asp-net-core-identity-ef-core-naming-conventions-for-postgresql/ + foreach (var entity in modelBuilder.Model.GetEntityTypes()) + { + // Replace table names + var entityName = entity.GetTableName().ToLowerInvariant(); + if (!entityName.StartsWith("view")) + entity.SetTableName("a" + entityName); + else + entity.SetTableName(entityName); + + + // Replace column names + foreach (var property in entity.GetProperties()) + { + //Any object that has a concurrencytoken field + //set it up to work properly with PostgreSQL + if (property.Name == "Concurrency") + { + property.SetColumnName("xmin"); + property.SetColumnType("xid"); + property.ValueGenerated = ValueGenerated.OnAddOrUpdate; + property.IsConcurrencyToken = true; + } + else + property.SetColumnName(property.Name.ToLowerInvariant()); + } + + foreach (var key in entity.GetKeys()) + key.SetName(key.GetName().ToLowerInvariant()); + + foreach (var key in entity.GetForeignKeys()) + key.SetConstraintName(key.GetConstraintName().ToLowerInvariant()); + + foreach (var index in entity.GetIndexes()) + index.SetDatabaseName(index.GetDatabaseName().ToLowerInvariant()); + + } + + /////////////////////////////// + //SERIALIZED OBJECTS + // + + //modelBuilder.Entity().Property(z => z.Serial).UseIdentityByDefaultColumn(); + //## NOTE: if more added here then must also update globalbizsettingscontroller.seeds and client + + ////////////////////////////////////////////////////////////// + + + + + + + //----------- + } + + } + +} \ No newline at end of file diff --git a/server/models/Customer.cs b/server/models/Customer.cs new file mode 100644 index 0000000..b93a518 --- /dev/null +++ b/server/models/Customer.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //NOTE: Any non required field (nullable in DB) sb nullable here, i.e. decimal? not decimal, + //otherwise the server will call it an invalid record if the field isn't sent from client + + //#### MIRRORED IN QBI !! + public class Customer : ICoreBizObjectModel + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public string Name { get; set; } + public bool Active { get; set; } + public string Notes { get; set; } + public string Wiki { get; set; } + public string CustomFields { get; set; } + public List Tags { get; set; } + + + //cant use these due to import v7 malformed shit adn can't be arsed to deal with that + //[Url] + public string WebAddress { get; set; } + public string AlertNotes { get; set; } + public bool BillHeadOffice { get; set; } + public long? HeadOfficeId { get; set; } + [NotMapped] + public string HeadOfficeViz { get; set; } + public string TechNotes { get; set; } + public string AccountNumber { get; set; } + + public string Phone1 { get; set; } + public string Phone2 { get; set; } + public string Phone3 { get; set; } + public string Phone4 { get; set; } + public string Phone5 { get; set; } + public string EmailAddress { get; set; } + + //POSTAL ADDRESS + public string PostAddress { get; set; } + public string PostCity { get; set; } + public string PostRegion { get; set; } + public string PostCountry { get; set; } + public string PostCode { get; set; } + + //PHYSICAL ADDRESS + public string Address { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string Country { get; set; } + public string AddressPostal { get; set; } + public decimal? Latitude { get; set; } + public decimal? Longitude { get; set; } + + + public Customer() + { + Tags = new List(); + BillHeadOffice = false; + //UsesBanking = false; + } + + [NotMapped, JsonIgnore] + public SockType SType { get => SockType.Customer; } + + }//eoc + +}//eons +/* +[AID] [uniqueidentifier] NOT NULL, + [ANAME] [nvarchar](255) NOT NULL, + [ACREATED] [datetime] NULL, + [AMODIFIED] [datetime] NULL, + [AACTIVE] [bit] NOT NULL, + [ACREATOR] [uniqueidentifier] NULL, + [AMODIFIER] [uniqueidentifier] NULL, + [ADISPATCHZONEID] [uniqueidentifier] NULL, + [AWEBADDRESS] [nvarchar](255) NULL, + [APOPUPNOTES] [ntext] NULL, + [ACLIENTGROUPID] [uniqueidentifier] NULL, + [ABILLHEADOFFICE] [bit] NOT NULL, + [AHEADOFFICEID] [uniqueidentifier] NULL, + [ANOTES] [ntext] NULL, + [AREGIONID] [uniqueidentifier] NULL, + [ATECHNOTES] [ntext] NULL, + [AACCOUNTNUMBER] [nvarchar](255) NULL, + [ACUSTOM1] [ntext] NULL, + [ACUSTOM2] [ntext] NULL, + [ACUSTOM3] [ntext] NULL, + [ACUSTOM4] [ntext] NULL, + [ACUSTOM5] [ntext] NULL, + [ACUSTOM6] [ntext] NULL, + [ACUSTOM7] [ntext] NULL, + [ACUSTOM8] [ntext] NULL, + [ACUSTOM9] [ntext] NULL, + [ACUSTOM0] [ntext] NULL, + [AUSESBANKING] [bit] NOT NULL, + [ACONTRACTID] [uniqueidentifier] NULL, + [ACONTRACTEXPIRES] [datetime] NULL, + [ALASTWORKORDERID] [uniqueidentifier] NULL,//dropped case 3536 + [ALASTSERVICEDATE] [datetime] NULL,//dropped case 3536 + [ADEFAULTSERVICETEMPLATEID] [uniqueidentifier] NULL, + [ACONTACTNOTES] [ntext] NULL, + [ACONTACT] [nvarchar](500) NULL, + [APHONE1] [nvarchar](255) NULL, + [APHONE2] [nvarchar](255) NULL, + [APHONE3] [nvarchar](255) NULL, + [APHONE4] [nvarchar](255) NULL, + [APHONE5] [nvarchar](255) NULL, + [AEMAIL] [nvarchar](255) NULL, + [ASENDNOTIFICATIONS] [bit] NOT NULL, +*/ \ No newline at end of file diff --git a/server/models/CustomerNote.cs b/server/models/CustomerNote.cs new file mode 100644 index 0000000..8f340d5 --- /dev/null +++ b/server/models/CustomerNote.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + public class CustomerNote : ICoreBizObjectModel + { + public long Id { get; set; } + public uint Concurrency { get; set; } + [Required] + public long CustomerId { get; set; } + [NotMapped] + public string CustomerViz { get; set; } + [Required] + public long UserId { get; set; } + [NotMapped] + public string UserViz { get; set; } + [Required] + public DateTime NoteDate { get; set; } + public string Notes { get; set; } + public List Tags { get; set; } + + + //workaround for notification + [NotMapped, JsonIgnore] + public string Name { get; set; } + + + public CustomerNote() + { + NoteDate = DateTime.UtcNow; + Tags = new List(); + } + + [NotMapped, JsonIgnore] + public SockType SType { get => SockType.CustomerNote; } + + [JsonIgnore] + public Customer Customer { get; set; } + + [JsonIgnore] + public User User { get; set; } + + string ICoreBizObjectModel.CustomFields { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + }//eoc + +}//eons diff --git a/server/models/CustomerNotifyDeliveryLog.cs b/server/models/CustomerNotifyDeliveryLog.cs new file mode 100644 index 0000000..5f0fccf --- /dev/null +++ b/server/models/CustomerNotifyDeliveryLog.cs @@ -0,0 +1,41 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //This model holds the Customer notification deliveries that have been attempted in the past 90 days (cleaned out by corenotifysweeper) + //it is used for verification / troubleshooting purposes from the OPS log + //and also used as a circuit breaker by the corejobnotify to ensure users are not spammed with identical messages + + public class CustomerNotifyDeliveryLog + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public DateTime Processed { get; set; } + + public long ObjectId { get; set; } + + [Required] + public long CustomerNotifySubscriptionId { get; set; } + + [Required] + public bool Fail { get; set; } + public string Error { get; set; } + + + public CustomerNotifyDeliveryLog() + { + Processed = DateTime.UtcNow; + Fail = false; + ObjectId = 0; + } + + //linked entity + public CustomerNotifySubscription CustomerNotifySubscription { get; set; } + + }//eoc + +}//eons diff --git a/server/models/CustomerNotifyEvent.cs b/server/models/CustomerNotifyEvent.cs new file mode 100644 index 0000000..b6bedb4 --- /dev/null +++ b/server/models/CustomerNotifyEvent.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //Customer notification event + public class CustomerNotifyEvent + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public DateTime Created { get; set; } + public SockType SockType { get; set; } + public long ObjectId { get; set; } + [Required] + public string Name { get; set; }//object name or closest equivalent for display + [Required] + public NotifyEventType EventType { get; set; } + [Required] + public long CustomerId { get; set; } + [Required] + public long CustomerNotifySubscriptionId { get; set; }//source subscription that triggered this event to be created + + public decimal DecValue { get; set; } + + //date of the event actually occuring, e.g. WarrantyExpiry date. Compared with subscription to determine if deliverable or not + public DateTime EventDate { get; set; } + // public string Subject { get; set; }//email subject line + // public string Message { get; set; }//email body + + + + public CustomerNotifyEvent() + { + Created = EventDate = DateTime.UtcNow; + // IdValue = 0; + DecValue = 0; + SockType = SockType.NoType; + ObjectId = 0; + Name = string.Empty; + + } + + public override string ToString() + { + return JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.None); + } + + //linked entity + // public NotifySubscription NotifySubscription { get; set; } + // public User User { get; set; } + + }//eoc + +}//eons diff --git a/server/models/CustomerNotifySubscription.cs b/server/models/CustomerNotifySubscription.cs new file mode 100644 index 0000000..81d92c0 --- /dev/null +++ b/server/models/CustomerNotifySubscription.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + + + public class CustomerNotifySubscription + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public long TranslationId { get; set; } + [Required] + public string LanguageOverride { get; set; } + [Required] + public string TimeZoneOverride { get; set; } + [Required] + public string CurrencyName { get; set; } + [Required] + public bool Hour12 { get; set; } + + public List CustomerTags { get; set; }//Tags to match customer with this notification + public TimeSpan AdvanceNotice { get; set; } + public long? LinkReportId { get; set; } + + //CREATE NOTIFY EVENT CONDITIONS - Following fields are all conditions set on whether to create a notify event or not + public SockType SockType { get; set; }//Note: must be specific object, not global for any object related stuff to avoid many role issues and also potential overload + [Required] + public NotifyEventType EventType { get; set; } + [Required] + public long IdValue { get; set; }//if required, this must match, default is zero to match to not set + public decimal DecValue { get; set; }//if required this must match or be greater or something + public List Tags { get; set; }//Tags to filter an event, object *must* have these tags to generate event related to it (AT TIME OF UPDATE) + [Required] + public string Template { get; set; } + + [Required] + public string Subject { get; set; } + + + //DELIVERY CONDITIONS - following are all conditions on *whether* to deliver the existing notify event or not + public TimeSpan AgeValue { get; set; }//for events that depend on an age of something (e.g. WorkorderStatusAge), This value determines when event has "come of age" but advancenotice controls how far in advance of this delivery is made + + public CustomerNotifySubscription() + { + Tags = new List(); + SockType = SockType.NoType; + IdValue = 0; + DecValue = 0; + AgeValue = TimeSpan.Zero; + AdvanceNotice = TimeSpan.Zero; + LinkReportId = 0; + + } + + }//eoc + +}//eons diff --git a/server/models/DashboardView.cs b/server/models/DashboardView.cs new file mode 100644 index 0000000..1443a10 --- /dev/null +++ b/server/models/DashboardView.cs @@ -0,0 +1,23 @@ + +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + public class DashboardView + { + public DashboardView(){ + View="[]";//empty view by default + } + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public long UserId { get; set; } + + [Required] + public string View { get; set; }//JSON Dashboard registry view object + + + } +} diff --git a/server/models/DataListColumnFilter.cs b/server/models/DataListColumnFilter.cs new file mode 100644 index 0000000..42c262d --- /dev/null +++ b/server/models/DataListColumnFilter.cs @@ -0,0 +1,8 @@ +namespace Sockeye.Models +{ + public class DataListColumnFilter + { + public string op { get; set; } + public string value { get; set; } + } +} diff --git a/server/models/DataListColumnView.cs b/server/models/DataListColumnView.cs new file mode 100644 index 0000000..43bbe55 --- /dev/null +++ b/server/models/DataListColumnView.cs @@ -0,0 +1,27 @@ + +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + //this is a hiearchical object so saving as a JSON fragment still best option + + public class DataListColumnView + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public long UserId { get; set; }//only relevant if non-public for fetching either in list or single (public true OR userid matches) + + [Required, MaxLength(255)] + public string ListKey { get; set; }//max 255 characters ascii set + + public string Columns { get; set; }//JSON serialized Column array: columns:["PartInventoryTransactionEntryDate","PartName","PartWarehouseName","PartInventoryTransactionQuantity","PartInventoryTransactionDescription","PartInventoryTransactionSource","PartInventoryBalance"] + public string Sort { get; set; }//JSON serialized SortBy Dictionary: sortBy:[{"PartInventoryTransactionEntryDate":"-"}],//All sorted columns here as keyvalue pairs value is a string of "+" for ascending "-" for descending and are IN ORDER of how to be sorted + + + // + + + } +} diff --git a/server/models/DataListFilterOption.cs b/server/models/DataListFilterOption.cs new file mode 100644 index 0000000..3e9418b --- /dev/null +++ b/server/models/DataListFilterOption.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +namespace Sockeye.Models +{ + public class DataListFilterOption + { + public string Column { get; set; } + public List Items { get; set; } + public bool Any { get; set; }//means "or" the filter conditions + public DataListFilterOption() + { + Items = new List(); + Any = false; + } + } + + /* + columns:["PartInventoryTransactionEntryDate","PartName","PartWarehouseName","PartInventoryTransactionQuantity","PartInventoryTransactionDescription","PartInventoryTransactionSource","PartInventoryBalance"] + sortBy:[{"PartInventoryTransactionEntryDate":"-"}],//All sorted columns here as keyvalue pairs value is a string of "+" for ascending "-" for descending and are IN ORDER of how to be sorted + filter:[{column:"PartName",any:true/false,items:[{op: "=",value: "400735"}]}], + clientCriteria:"2" //could be anything here that makes sense to the list, in this case an example customer id for customernotedatalist + + columns are represented in a higher level object DataListTableOptions + sort and filter are in here + + columns and sort are a singleton per datalistkey,userid persisted automatically (no alternative "views" just one of these). User can set them or reset them to default. + filter is different: user can persist a filter by name for future selection, sharing with others (public). The default filter is no filter. + */ + +} diff --git a/server/models/DataListProcessingBase.cs b/server/models/DataListProcessingBase.cs new file mode 100644 index 0000000..3f71846 --- /dev/null +++ b/server/models/DataListProcessingBase.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System; +namespace Sockeye.Models +{ + //common base class for data table display at client, reporting and mass bulk operations + public class DataListProcessingBase + { + public string DataListKey { get; set; } + public Dictionary SortBy { get; set; } + public List Filter { get; set; } + public string ClientCriteria { get; set; } + public DateTimeOffset ClientTimeStamp { get; set; } + + public DataListProcessingBase() + { + SortBy = new Dictionary(); + Filter = new List(); + } + } +} diff --git a/server/models/DataListReportProcessingOptions.cs b/server/models/DataListReportProcessingOptions.cs new file mode 100644 index 0000000..5e233bb --- /dev/null +++ b/server/models/DataListReportProcessingOptions.cs @@ -0,0 +1,15 @@ +using Sockeye.Biz; +using Sockeye.DataList; +using Newtonsoft.Json.Linq; +namespace Sockeye.Models +{ + public class DataListReportProcessingOptions : DataListSelectedProcessingOptions + { + internal DataListReportProcessingOptions(DataListSelectedRequest request, IDataListProcessing dataList, DataListColumnView savedView, DataListSavedFilter savedFilter, long userId, AuthorizationRoles userRoles) : base(request, dataList, savedView, savedFilter, userId, userRoles) + { + } + + public long ReportId { get; set; } + public JToken ClientMeta { get; set; }//meta JSON data passed from client, not part of biz object data + } +} diff --git a/server/models/DataListReportRequest.cs b/server/models/DataListReportRequest.cs new file mode 100644 index 0000000..db8bfa3 --- /dev/null +++ b/server/models/DataListReportRequest.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json.Linq; +namespace Sockeye.Models +{ + public class DataListReportRequest : DataListSelectedRequest + { + public long ReportId { get; set; } + public JToken ClientMeta { get; set; }//meta JSON data about client for report script processing at server + } +} diff --git a/server/models/DataListRequestBase.cs b/server/models/DataListRequestBase.cs new file mode 100644 index 0000000..3f0dc02 --- /dev/null +++ b/server/models/DataListRequestBase.cs @@ -0,0 +1,18 @@ +using System; +namespace Sockeye.Models +{ + //common base class for REQUESTING a datalist from the client + public class DataListRequestBase + { + public string DataListKey { get; set; } + public string ClientCriteria { get; set; } + public long FilterId {get;set;} + public DateTimeOffset ClientTimeStamp {get;set;} + } + /* + REQUEST + BASE: DataListKey, ClientCriteria, FilterId + TABLEVERSION: Limit, Offset : base + REPORT/BULK OPS VERSION: SockType(socktype),SelectedRowIds(long[]) : base + */ +} diff --git a/server/models/DataListSavedFilter.cs b/server/models/DataListSavedFilter.cs new file mode 100644 index 0000000..5f5bb3b --- /dev/null +++ b/server/models/DataListSavedFilter.cs @@ -0,0 +1,26 @@ + +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + public class DataListSavedFilter + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public long UserId { get; set; }//only relevant if non-public for fetching either in list or single (public true OR userid matches) + [Required, MaxLength(255)] + public string Name { get; set; }//max 255 characters ascii set + [Required] + public bool Public { get; set; } + [Required] + public bool DefaultFilter { get; set; } //is the users default filter for this listkey + [Required, MaxLength(255)] + public string ListKey { get; set; }//max 255 characters ascii set + public string Filter { get; set; }//JSON serialized List object of DataListBase + + + + } +} diff --git a/server/models/DataListSelectedProcessingOptions.cs b/server/models/DataListSelectedProcessingOptions.cs new file mode 100644 index 0000000..b4b8fd8 --- /dev/null +++ b/server/models/DataListSelectedProcessingOptions.cs @@ -0,0 +1,74 @@ +using System.Threading.Tasks; +using Sockeye.Biz; +using System.Collections.Generic; +using Sockeye.DataList; +using Newtonsoft.Json; +using EnumsNET; +namespace Sockeye.Models +{ + //Used to drive processes that rely on selections made at client from a datalist + //either a preselected list of id's or a datalist key and filterid that can + //be used to rehydrate a list of id's + public class DataListSelectedProcessingOptions : DataListProcessingBase + { + internal DataListSelectedProcessingOptions( + DataListSelectedRequest request, + IDataListProcessing dataList, + DataListColumnView savedView, + DataListSavedFilter savedFilter, + long userId, + AuthorizationRoles userRoles) + { + //set some values from request + base.ClientCriteria = request.ClientCriteria; + base.DataListKey = request.DataListKey; + base.ClientTimeStamp = request.ClientTimeStamp; + + //SET SORTBY + base.SortBy = JsonConvert.DeserializeObject>(savedView.Sort); + + //SET FILTER + if (request.FilterId != 0 && savedFilter != null) + base.Filter = JsonConvert.DeserializeObject>(savedFilter.Filter); + + //ADD STATIC SERVER FILTERS + List StaticServerFilterOptions = new List(); + if (dataList is IDataListInternalCriteria) + StaticServerFilterOptions = ((IDataListInternalCriteria)dataList).DataListInternalCriteria(userId, userRoles, request.ClientCriteria); + + //Add the internal filters into the listoptions existing filters + //NOTE: There is currently no overlap between internal filtered columns and filters coming from the client + foreach (DataListFilterOption dfo in StaticServerFilterOptions) + base.Filter.Add(dfo); + + } + + + public static async Task RehydrateIdList(DataListSelectedRequest selectedRequest, AyContext ct, AuthorizationRoles userRoles, Microsoft.Extensions.Logging.ILogger log, long userId, long userTranslationId) + { + DataListColumnViewBiz viewbiz = new DataListColumnViewBiz(ct, userId, userTranslationId, userRoles); + var SavedView = await viewbiz.GetAsync(userId, selectedRequest.DataListKey, true); + + DataListSavedFilter SavedFilter = null; + if (selectedRequest.FilterId != 0) + { + DataListSavedFilterBiz filterbiz = new DataListSavedFilterBiz(ct, userId, userTranslationId, userRoles); + SavedFilter = await filterbiz.GetAsync(selectedRequest.FilterId); + } + var DataList = DataListFactory.GetAyaDataList(selectedRequest.DataListKey, userTranslationId); + if (DataList == null) + throw new System.ArgumentOutOfRangeException($"DataList \"{selectedRequest.DataListKey}\" specified does not exist"); + + //check rights + if (!userRoles.HasAnyFlags(DataList.AllowedRoles)) + throw new System.UnauthorizedAccessException($"DataList \"{selectedRequest.DataListKey}\" required Roles not found for this user"); + + DataListSelectedProcessingOptions d = new DataListSelectedProcessingOptions(selectedRequest, DataList, SavedView, SavedFilter, userId, userRoles); + + return await Sockeye.DataList.DataListFetcher.GetIdListResponseAsync(ct, d, DataList, userRoles, log, userId, selectedRequest.ReportDesignerSample); + } + + + } +} + diff --git a/server/models/DataListSelectedRequest.cs b/server/models/DataListSelectedRequest.cs new file mode 100644 index 0000000..1cf06ac --- /dev/null +++ b/server/models/DataListSelectedRequest.cs @@ -0,0 +1,14 @@ +using Sockeye.Biz; +namespace Sockeye.Models +{ + //Request version of selection request used by report and bulk ops + //handles posts from client + public class DataListSelectedRequest : DataListRequestBase + { + public SockType SockType { get; set; } + public long[] SelectedRowIds { get; set; } + public bool IncludeWoItemDescendants {get;set;} + public bool ReportDesignerSample {get;set;}//set if for report designer to limit rows returned to a sensible limit + + } +} diff --git a/server/models/DataListSortRequest.cs b/server/models/DataListSortRequest.cs new file mode 100644 index 0000000..de84806 --- /dev/null +++ b/server/models/DataListSortRequest.cs @@ -0,0 +1,4 @@ +namespace Sockeye.Models +{ + public record DataListSortRequest(string ListKey, string[] sortBy, bool[] sortDesc); +} \ No newline at end of file diff --git a/server/models/DataListTableProcessingOptions.cs b/server/models/DataListTableProcessingOptions.cs new file mode 100644 index 0000000..d8c969e --- /dev/null +++ b/server/models/DataListTableProcessingOptions.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Linq; +using Sockeye.DataList; +using Sockeye.Biz; +using Newtonsoft.Json; + + +namespace Sockeye.Models +{ + internal sealed class DataListTableProcessingOptions : DataListProcessingBase + { + internal List Columns { get; set; } + internal const int MaxPageSize = 1000; + internal const int DefaultOffset = 0; + internal const int DefaultLimit = 25; + internal int? Offset { get; set; } + internal int? Limit { get; set; } + //All columns that are hidden but are affecting the query (sorting, filtering) + //so in UI can show that there are hidden columns affecting the result set + internal List HiddenAffectiveColumns { get; set; } + + internal DataListTableProcessingOptions( + DataListTableRequest request, + IDataListProcessing dataList, + DataListColumnView savedView, + DataListSavedFilter savedFilter, + long userId, + AuthorizationRoles userRoles) + { + HiddenAffectiveColumns = new List(); + //set some values from request + Limit = request.Limit; + Offset = request.Offset; + base.ClientCriteria = request.ClientCriteria; + base.DataListKey = request.DataListKey; + base.ClientTimeStamp = request.ClientTimeStamp; + + //SET COLUMNS + Columns = JsonConvert.DeserializeObject>(savedView.Columns); + + //SET SORTBY + base.SortBy = JsonConvert.DeserializeObject>(savedView.Sort); + + //SET FILTER + if (request.FilterId != 0 && savedFilter != null) + base.Filter = JsonConvert.DeserializeObject>(savedFilter.Filter); + + //ADD STATIC SERVER FILTERS + List StaticServerFilterOptions = new List(); + if (dataList is IDataListInternalCriteria) + StaticServerFilterOptions = ((IDataListInternalCriteria)dataList).DataListInternalCriteria(userId, userRoles, request.ClientCriteria); + + //Add the internal filters into the listoptions existing filters + //NOTE: There is currently no overlap between internal filtered columns and filters coming from the client + foreach (DataListFilterOption dfo in StaticServerFilterOptions) + base.Filter.Add(dfo); + + SetHiddenAffectiveColumns(dataList); + + } + + internal List AllUniqueColumnKeysReferenced + { + get + { + return Columns.Union(base.Filter.Select(z => z.Column).ToList()).ToList(); + + } + } + + //All columns that are hidden but are affecting the query (sorting, filtering) + //so in UI can show that there are hidden columns affecting the result set + internal void SetHiddenAffectiveColumns(IDataListProcessing dataList) + { + foreach (string s in base.Filter.Select(z => z.Column).ToList()) + { + if (!s.StartsWith("meta") && !Columns.Contains(s)) + { + if (!HiddenAffectiveColumns.Contains(s)) + { + HiddenAffectiveColumns.Add(s); + } + } + } + + + foreach (string s in base.SortBy.Select(z => z.Key).ToList()) + { + if (!s.StartsWith("meta") && !Columns.Contains(s)) + { + if (!HiddenAffectiveColumns.Contains(s)) + { + HiddenAffectiveColumns.Add(s); + } + } + } + } + } +} \ No newline at end of file diff --git a/server/models/DataListTableRequest.cs b/server/models/DataListTableRequest.cs new file mode 100644 index 0000000..fa57415 --- /dev/null +++ b/server/models/DataListTableRequest.cs @@ -0,0 +1,8 @@ +namespace Sockeye.Models +{ + public sealed class DataListTableRequest : DataListRequestBase + { + public int? Offset { get; set; } + public int? Limit { get; set; } + } +} \ No newline at end of file diff --git a/server/models/Event.cs b/server/models/Event.cs new file mode 100644 index 0000000..cb4cb41 --- /dev/null +++ b/server/models/Event.cs @@ -0,0 +1,67 @@ +using System; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + /// + /// Event log entry + /// + public class Event + { + public long Id { get; set; } + public uint Concurrency { get; set; } + public DateTime Created { get; set; } + [Required] + public long UserId { get; set; } + //----------------------------------------- + [Required] + public long SockId { get; set; } + [Required] + public SockType SockType { get; set; } + [Required] + public SockEvent SockEvent { get; set; } + + [MaxLength(255)] + public string Textra { get; set; } + + + public Event() + { + Created = System.DateTime.UtcNow; + } + + public Event(long userId, long sockId, SockType sockType, SockEvent sockEvent, string textra = null) + { + Created = System.DateTime.UtcNow; + UserId = userId; + SockId = sockId; + SockType = sockType; + SockEvent = sockEvent; + if (textra != null) + { + if (textra.Length > 255) + textra = textra.Substring(0, 255); + Textra = textra; + } + } + + public Event(long userId, long sockId, SockType sockType, SockEvent sockEvent, DateTime created, string textra = null) + { + Created = created; + UserId = userId; + SockId = sockId; + SockType = sockType; + SockEvent = sockEvent; + if (textra != null) + { + if (textra.Length > 255) + textra = textra.Substring(0, 255); + Textra = textra; + } + } + + }//eoc + +}//eons diff --git a/server/models/FileAttachment.cs b/server/models/FileAttachment.cs new file mode 100644 index 0000000..f9cc0d6 --- /dev/null +++ b/server/models/FileAttachment.cs @@ -0,0 +1,45 @@ +using System; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + public class FileAttachment + { + public FileAttachment() + { + //all start out as synchronized + Exists = true; + + } + public long Id { get; set; } + public uint Concurrency { get; set; } + + + //----------------------------------------- + [Required] + public long AttachToObjectId { get; set; } + [Required] + public SockType AttachToAType { get; set; }//int + [Required] + public string StoredFileName { get; set; } + [Required] + public string DisplayFileName { get; set; } + [Required] + public string ContentType { get; set; }//mime type + [Required] + public DateTime LastModified { get; set; } + public string Notes { get; set; } + + [Required] + public long AttachedByUserId { get; set; } + + [Required] + public bool Exists { get; set; }//was on disk last sync check + + [Required] + public long Size { get; set; } + + } +} \ No newline at end of file diff --git a/server/models/FormCustom.cs b/server/models/FormCustom.cs new file mode 100644 index 0000000..7e2cbcf --- /dev/null +++ b/server/models/FormCustom.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + /* + ## NOTE: there is only one formcustom per socktype, the formkey is the socktype enum name + + This is a *GLOBAL* setting that applies to ALL users + for personal form settings (such as used by schedule form) see FormUserOptions.cs + + - Like DataFilter, holds a JSON fragment in one field and the form key in another field + - JSON FRAGMENT holds items that differ from stock, Hide not valid for non hideable core fields + - FieldKey "fld" + - Hide "hide" + - Required "required" + - Type One of values from AyDataType but not tags or enum (bool, date, date time, decimal, number, p icklist(FUTURE), and text) + - e.g.: [{fld:"ltkeyfieldname",hide:"true/false",required:"true/false", type:"bool"},{fld:"ltkeyfieldname",hide:"true/false",required:"true/false", type:"text"] + + */ + public class FormCustom + { + public long Id { get; set; }//Only one formcustom per object type + public uint Concurrency { get; set; } + + [Required, MaxLength(255)] + public string FormKey { get; set; }//max 255 characters ascii set + public string Template { get; set; }//JSON fragment of form customization template, top level is array. + + } +} diff --git a/server/models/FormUserOptions.cs b/server/models/FormUserOptions.cs new file mode 100644 index 0000000..c473b7c --- /dev/null +++ b/server/models/FormUserOptions.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + /* + ## NOTE: this is for PERSONAL form settings such as schedule, for globally applicable form settings and customization see formcustom.cs + + + */ + public class FormUserOptions + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required, MaxLength(255)] + public string FormKey { get; set; }//max 255 characters ascii set + [Required] + public string Options { get; set; }//JSON fragment of form customization template, top level is array. + //this is set from logged in user id, not provided + public long UserId { get; set; } + + } +} diff --git a/server/models/GlobalBizSettings.cs b/server/models/GlobalBizSettings.cs new file mode 100644 index 0000000..c996176 --- /dev/null +++ b/server/models/GlobalBizSettings.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +namespace Sockeye.Models +{ + + public class GlobalBizSettings + { + + //This is the replacement for most of the Global object settings in v7 which has been subdivided further in v8 into backup / ops / notification and this general biz + //this object is only interested in Business object related settings and configuration specifically + + + public long Id { get; set; }//this is always 1 as there is only ever one single global biz object + public uint Concurrency { get; set; } + + //Global settings + //############# NOTE: OTHER AREAS THAT MUST MATCH CHANGES HERE ARE: + //GlobalBizSettingsController::GetClientGlobalBizSettings + //ServerGlobalBizSettings + + //Picklist and other searches override the normal case insensitive value + //this is precautionarily added for non latinate languages where it could be an issue + public bool FilterCaseSensitive { get; set; } + + + + //ADDRESS / CONTACT INFO reporting etc + public string WebAddress { get; set; } + public string EmailAddress { get; set; } + public string Phone1 { get; set; } + public string Phone2 { get; set; } + //POSTAL ADDRESS + public string PostAddress { get; set; } + public string PostCity { get; set; } + public string PostRegion { get; set; } + public string PostCountry { get; set; } + public string PostCode { get; set; } + + //PHYSICAL ADDRESS + public string Address { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string Country { get; set; } + public string AddressPostal { get; set; } + public decimal? Latitude { get; set; } + public decimal? Longitude { get; set; } + + + //CUSTOMER ACCESS FEATURES AND SETTINGS + + + + public bool CustomerAllowUserSettings { get; set; } + public List CustomerAllowUserSettingsInTags { get; set; } = new List(); + + + + + + public List AllTags(){ + var t=new List(); + + t.AddRange(this.CustomerAllowUserSettingsInTags); + return t; + } + + public GlobalBizSettings() + { + Id = 1;//always 1 + FilterCaseSensitive = false; + } + } + + //Used internally and at client end as extended rights atop roles system in relation only to Contact (customer type users) + public record CustomerRightsRecord(bool UserSettings, long EntityId, bool EntityActive); +} +/* +CREATE TABLE [dbo].[AGLOBAL]( + [ACREATED] [datetime] NULL, + [AMODIFIED] [datetime] NULL, + [ACREATOR] [uniqueidentifier] NULL, + [AMODIFIER] [uniqueidentifier] NULL, + [ATAXPARTPURCHASEID] [uniqueidentifier] NULL, + [ATAXPARTSALEID] [uniqueidentifier] NULL, + [ATAXRATESALEID] [uniqueidentifier] NULL, + [AALLOWSCHEDULECONFLICTS] [bit] NOT NULL, + [ALANGUAGE] [nvarchar](255) NULL, + [AUSEREGIONS] [bit] NOT NULL, + [AWORKORDERCLOSEDSTATUS] [uniqueidentifier] NULL, + [AWORKORDERSUMMARYTEMPLATE] [nvarchar](500) NOT NULL, + [AUSEINVENTORY] [bit] NOT NULL, + [AUNITNAMEFORMAT] [smallint] NULL, + [ASCHEDULEABLEUSERNAMEFORMAT] [smallint] NULL, + [ACJKINDEX] [bit] NOT NULL, + [APARTFORMAT] [smallint] NULL, + [AWORKORDERCLOSEBYAGE] [int] NOT NULL, + [AUSENOTIFICATION] [bit] NOT NULL, + [ANOTIFYSMTPHOST] [nvarchar](255) NULL, + [ANOTIFYSMTPACCOUNT] [nvarchar](255) NULL, + [ANOTIFYSMTPPASSWORD] [nvarchar](255) NULL, + [ANOTIFYSMTPFROM] [nvarchar](255) NULL, + [ACOORDINATESTYLE] [smallint] NULL, + [ADEFAULTLATITUDE] [smallint] NULL, + [ADEFAULTLONGITUDE] [smallint] NULL, + [ADEFAULTSERVICETEMPLATEID] [uniqueidentifier] NULL, + [AMAXFILESIZEMB] [int] NULL, + [ALABORSCHEDUSERDFLTTIMESPAN] [int] NOT NULL, + [ATRAVELDFLTTIMESPAN] [int] NOT NULL, + [ANOTIFYENCRYPTION] [nvarchar](255) NULL, + [ASMTPRETRY] [bit] NOT NULL, + [AFORMCUSTOM] [ntext] NULL, + [ASIGNATURETITLE] [ntext] NULL, + [ASIGNATUREHEADER] [ntext] NULL, + [ASIGNATUREFOOTER] [ntext] NULL, + [ASCHEDUSERNONTODAYSTARTTIME] [datetime] NULL, + [AMAINGRIDAUTOREFRESH] [bit] NOT NULL +*/ \ No newline at end of file diff --git a/server/models/GlobalOpsBackupSettings.cs b/server/models/GlobalOpsBackupSettings.cs new file mode 100644 index 0000000..e53b694 --- /dev/null +++ b/server/models/GlobalOpsBackupSettings.cs @@ -0,0 +1,23 @@ +using System; +namespace Sockeye.Models +{ + + public class GlobalOpsBackupSettings + { + public long Id { get; set; }//this is always 1 as there is only ever one single global Ops object + public uint Concurrency { get; set; } + public bool Active { get; set; } + public DateTime BackupTime { get; set; } + public int BackupSetsToKeep { get; set; } + public bool BackupAttachments { get; set; } + + public GlobalOpsBackupSettings() + { + DateTime utcNow = DateTime.UtcNow; + Id = 1; + BackupTime = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, 23, 59, 0, DateTimeKind.Local).ToUniversalTime(); + BackupSetsToKeep = 1; + Active = true; + } + } +} diff --git a/server/models/GlobalOpsNotificationSettings.cs b/server/models/GlobalOpsNotificationSettings.cs new file mode 100644 index 0000000..1b69f30 --- /dev/null +++ b/server/models/GlobalOpsNotificationSettings.cs @@ -0,0 +1,46 @@ +using System; +using Sockeye.Biz; +namespace Sockeye.Models +{ + + public class GlobalOpsNotificationSettings + { + public long Id { get; set; }//this is always 1 as there is only ever one single global Ops object + public uint Concurrency { get; set; } + public bool SmtpDeliveryActive { get; set; } + public string SmtpServerAddress { get; set; } + public string SmtpAccount { get; set; } + public string SmtpPassword { get; set; } + public NotifyMailSecurity ConnectionSecurity { get; set; } + public int SmtpServerPort { get; set; } + public string NotifyFromAddress { get; set; } + public string SockeyeServerURL { get; set; } + + public GlobalOpsNotificationSettings() + { + SmtpDeliveryActive = true; + Id = 1; + +#if (DEBUG) + SmtpDeliveryActive = true; + SmtpServerAddress = "smtp.fastmail.com"; + SmtpAccount = "support@onayanova.com"; + SmtpPassword = "mk8pmhzlf5hvzgh5"; + ConnectionSecurity = NotifyMailSecurity.SSLTLS; + SmtpServerPort = 465; + NotifyFromAddress = "support@ayanova.com"; + SockeyeServerURL = "http://localhost:8080"; +#else + SmtpDeliveryActive = false; + SmtpServerAddress = "mail.example.com"; + SmtpAccount = "notifydeliverfromaccount@example.com"; + SmtpPassword = "examplepassword"; + ConnectionSecurity = NotifyMailSecurity.SSLTLS; + SmtpServerPort = 587; + NotifyFromAddress = "noreply@example.com"; + SockeyeServerURL = "https://url_to_my_ayanova_app"; +#endif + + } + } +} diff --git a/server/models/HeadOffice.cs b/server/models/HeadOffice.cs new file mode 100644 index 0000000..3a0dcb4 --- /dev/null +++ b/server/models/HeadOffice.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //NOTE: Any non required field (nullable in DB) sb nullable here, i.e. decimal? not decimal, + //otherwise the server will call it an invalid record if the field isn't sent from client + + public class HeadOffice : ICoreBizObjectModel + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public string Name { get; set; } + public bool Active { get; set; } + public string Notes { get; set; } + public string Wiki { get; set; } + public string CustomFields { get; set; } + public List Tags { get; set; } + + + + public string WebAddress { get; set; } + public string AccountNumber { get; set; } + + public string Phone1 { get; set; } + public string Phone2 { get; set; } + public string Phone3 { get; set; } + public string Phone4 { get; set; } + public string Phone5 { get; set; } + public string EmailAddress { get; set; } + + //POSTAL ADDRESS + public string PostAddress { get; set; } + public string PostCity { get; set; } + public string PostRegion { get; set; } + public string PostCountry { get; set; } + public string PostCode { get; set; } + + //PHYSICAL ADDRESS + public string Address { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string Country { get; set; } + public string AddressPostal { get; set; } + public decimal? Latitude { get; set; } + public decimal? Longitude { get; set; } + + + public HeadOffice() + { + Tags = new List(); + //UsesBanking = false; + } + + [NotMapped, JsonIgnore] + public SockType SType { get => SockType.HeadOffice; } + + }//eoc + +}//eons +/* + [AID] [uniqueidentifier] NOT NULL, + [ACREATED] [datetime] NULL, + [AMODIFIED] [datetime] NULL, + [AACTIVE] [bit] NOT NULL, + [ACREATOR] [uniqueidentifier] NULL, + [AMODIFIER] [uniqueidentifier] NULL, + [ANAME] [nvarchar](255) NULL, + [AWEBADDRESS] [nvarchar](255) NULL, + [ACLIENTGROUPID] [uniqueidentifier] NULL, + [ANOTES] [ntext] NULL, + [AREGIONID] [uniqueidentifier] NULL, + [AACCOUNTNUMBER] [nvarchar](255) NULL, + [ACUSTOM1] [ntext] NULL, + [ACUSTOM2] [ntext] NULL, + [ACUSTOM3] [ntext] NULL, + [ACUSTOM4] [ntext] NULL, + [ACUSTOM5] [ntext] NULL, + [ACUSTOM6] [ntext] NULL, + [ACUSTOM7] [ntext] NULL, + [ACUSTOM8] [ntext] NULL, + [ACUSTOM9] [ntext] NULL, + [ACUSTOM0] [ntext] NULL, + [AUSESBANKING] [bit] NOT NULL, + [ACONTRACTID] [uniqueidentifier] NULL, + [ACONTRACTEXPIRES] [datetime] NULL, + [ACONTACTNOTES] [ntext] NULL, + [ACONTACT] [nvarchar](500) NULL, + [APHONE1] [nvarchar](255) NULL, + [APHONE2] [nvarchar](255) NULL, + [APHONE3] [nvarchar](255) NULL, + [APHONE4] [nvarchar](255) NULL, + [APHONE5] [nvarchar](255) NULL, + [AEMAIL] [nvarchar](255) NULL, +*/ \ No newline at end of file diff --git a/server/models/ICoreBizObjectModel.cs b/server/models/ICoreBizObjectModel.cs new file mode 100644 index 0000000..82c2636 --- /dev/null +++ b/server/models/ICoreBizObjectModel.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +namespace Sockeye.Models +{ + /// + /// Interface for biz object models with standard fields + /// used for batch operations etc + /// + internal interface ICoreBizObjectModel + { + //Note: almost all biz objects support all these propertys, the exceptions are extremely few so auto-implementing them here + //this is not a public api so I don't care about the "contract" aspect, just practicality + //didn't want to end up with a zillion interfaces for all manner of stuff + public long Id { get; set; } + public uint Concurrency { get; set; } + // public string Name { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } + public string Name { get; set; }//any notification on these will require this so making it mandatory but will implement a workaround to return alt fields instead + public bool? Active { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } + public string Wiki { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } + public string CustomFields { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } + // public List Tags { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } + public List Tags { get; set; }//Notification on objects require this so will implement workaround in object instead + public Sockeye.Biz.SockType SType { get; } + + } + +} \ No newline at end of file diff --git a/server/models/Integration.cs b/server/models/Integration.cs new file mode 100644 index 0000000..476c0d3 --- /dev/null +++ b/server/models/Integration.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //https://stackoverflow.com/questions/46517584/how-to-add-a-parent-record-with-its-children-records-in-ef-core#46615455 + + public class Integration + { + public long Id { get; set; } + public uint Concurrency { get; set; } + [Required] + public Guid IntegrationAppId { get; set; } = Guid.Empty;//Guid of integrating application. All data for it is stored under this guid key. This guid should be fixed and unchanged for all users of the integration app, i.e. it shouldn't be unique to each install but unique to itself once, a publish app id + [Required] + public string Name { get; set; } + public bool Active { get; set; }//integration apps should always check if this is true before working or not and give appropriate error if turned off + public string IntegrationData { get; set; }//foreign object data store for entire integration, can be used for custom data, unused by any Sockeye add-on's, expect json or xml or some other text value here + + + public List Items { get; set; } = new List(); + + + + }//eoc + +}//eons + + +// CREATE TABLE [dbo].[AINTEGRATION]( +// [AID] [uniqueidentifier] NOT NULL, +// [ACREATED] [datetime] NULL, +// [AMODIFIED] [datetime] NULL, +// [ACREATOR] [uniqueidentifier] NULL, +// [AMODIFIER] [uniqueidentifier] NULL, +// [AACTIVE] [bit] NOT NULL, <--was set to true at creation of integration in plugins but never used beyond that, was intended to be able to temporarily pause an integration, maybe should enable it to work that way as intended +// [ANAME] [nvarchar](255) NOT NULL, +// [ALASTCONNECT] [datetime] NULL, <=---unused in v7 I'm dropping this as it's always going to be ambiguous and we don't use it anyway, let people put this in the integrationdata as json data in if they want to +// [AIOBJECT] [image] NULL, <--was not used don't bring forward as binary but do bring forward as a IntegrationData string where they can put json or whatever +// [AIOBJECTSIZE] [int] NULL, <- this one either +// [AAPPID] [uniqueidentifier] NOT NULL, +// [AAPPVERSION] [nvarchar](25) NOT NULL, <--unused, not keeping, if people need this they can use the intgrationdata and store as JSON with anything else they need +// [ASYNCCHECKPOINT] [nvarchar](255) NULL, <--unused, not keeping, if people need this they can use the intgrationdata and store as JSON with anything else they need \ No newline at end of file diff --git a/server/models/IntegrationItem.cs b/server/models/IntegrationItem.cs new file mode 100644 index 0000000..b0932d2 --- /dev/null +++ b/server/models/IntegrationItem.cs @@ -0,0 +1,51 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Sockeye.Biz; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //https://stackoverflow.com/questions/46517584/how-to-add-a-parent-record-with-its-children-records-in-ef-core#46615455 + public class IntegrationItem + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public long IntegrationId { get; set; } + + [Required] + public long ObjectId { get; set; } + [Required] + public SockType SockType { get; set; } + [Required] + public string IntegrationItemId { get; set; }//foreign id (e.g. QB Id) + public string IntegrationItemName { get; set; }//foreign object's name for UI, not required if not displayed anywhere + public DateTime? LastSync { get; set; }//optional, used by qbi + public string IntegrationItemData { get; set; }//foreign object data store for this item, unused by any Sockeye add-on's in the past but might be useful for others, expect json or xml or some other text value here + + + [JsonIgnore] + public Integration Integration { get; set; } + + }//eoc + +}//eons + +// CREATE TABLE [dbo].[AINTEGRATIONMAP]( +// [AID] [uniqueidentifier] NOT NULL, +// [ACREATED] [datetime] NOT NULL, +// [AMODIFIED] [datetime] NOT NULL, +// [ACREATOR] [uniqueidentifier] NOT NULL, +// [AMODIFIER] [uniqueidentifier] NOT NULL, +// [AINTEGRATIONID] [uniqueidentifier] NOT NULL, +// [AROOTOBJECTID] [uniqueidentifier] NOT NULL, <--Now "ObjectId" +// [AROOTOBJECTTYPE] [smallint] NOT NULL, <-- now "SockType" +// [AFOREIGNID] [nvarchar](255) NOT NULL, <-- renamed to "IntegrationItemId" still 255 char string +// [ANAME] [nvarchar](255) NULL, <--keep rename to "IntegrationItemName" +// [AFOREIGNCHECKSUM] [nvarchar](255) NULL, drop was not used and thsi is hwat the lastsynch is for anyway +// [ASOCKEYECHECKSUM] [nvarchar](255) NULL, drop was not used and thsi is hwat the lastsynch is for anyway +// [AMAPOBJECT] [image] NULL, Now "IntegrationItemData" +// [AMAPOBJECTSIZE] [int] NULL, dropped, expects to be json data now, size is irrelevant as it's not a binary object anymore +// [ALASTSYNC] [datetime] NOT NULL, \ No newline at end of file diff --git a/server/models/IntegrationLog.cs b/server/models/IntegrationLog.cs new file mode 100644 index 0000000..8c9beae --- /dev/null +++ b/server/models/IntegrationLog.cs @@ -0,0 +1,34 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + /// + /// Integration log + /// + public class IntegrationLog + { + public long Id { get; set; } + [Required] + public long IntegrationId { get; set; } + [Required] + public DateTime Created { get; set; } + [Required] + public string StatusText { get; set; } + + public IntegrationLog() + { + Created = DateTime.UtcNow; + StatusText = "default / not set"; + } + + } + +} + +// CREATE TABLE [dbo].[AINTEGRATIONLOG]( +// [ACREATED] [datetime] NOT NULL, +// [ACREATOR] [uniqueidentifier] NOT NULL, +// [AINTEGRATIONID] [uniqueidentifier] NOT NULL, +// [AMESSAGE] [nvarchar](500) NOT NULL \ No newline at end of file diff --git a/server/models/Logo.cs b/server/models/Logo.cs new file mode 100644 index 0000000..2a02b82 --- /dev/null +++ b/server/models/Logo.cs @@ -0,0 +1,17 @@ +namespace Sockeye.Models +{ + + public class Logo + { + public long Id { get; set; } + public byte[] Large { get; set; } + public string LargeType {get;set;} + public byte[] Medium { get; set; } + public string MediumType {get;set;} + public byte[] Small { get; set; } + public string SmallType {get;set;} + + + } + +} diff --git a/server/models/Memo.cs b/server/models/Memo.cs new file mode 100644 index 0000000..fee276d --- /dev/null +++ b/server/models/Memo.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; +using Sockeye.Biz; + +namespace Sockeye.Models +{ + //NOTE: Any non required field (nullable in DB) sb nullable here, i.e. decimal? not decimal, + //otherwise the server will call it an invalid record if the field isn't sent from client + + public class Memo : ICoreBizObjectModel + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public string Name { get; set; }//"subject" + public string Notes { get; set; }//"message" + public string Wiki { get; set; } + public string CustomFields { get; set; } + public List Tags { get; set; } + + + public bool Viewed { get; set; } + public bool Replied { get; set; } + public long? FromId { get; set; } + [NotMapped] + public string FromViz { get; set; } + public long? ToId { get; set; } + [NotMapped] + public string ToViz { get; set; } + public DateTime Sent { get; set; }//redundant but displayed all over the place in the UI so properly redundant + + public Memo() + { + Tags = new List(); + Sent = DateTime.UtcNow; + } + + [NotMapped, JsonIgnore] + public SockType SType { get => SockType.Memo; } + + }//eoc + +}//eons +/* +CREATE TABLE [dbo].[AMEMO]( + [AID] [uniqueidentifier] NOT NULL, + [ACREATED] [datetime] NULL, + [AMODIFIED] [datetime] NULL, + [ACREATOR] [uniqueidentifier] NULL, + [AMODIFIER] [uniqueidentifier] NULL, + [ASUBJECT] [nvarchar](255) NULL, + [AMESSAGE] [ntext] NULL, + [AFROMID] [uniqueidentifier] NOT NULL, + [ATOID] [uniqueidentifier] NOT NULL, + [AVIEWED] [bit] NOT NULL, + [AREPLIED] [bit] NOT NULL, +*/ \ No newline at end of file diff --git a/server/models/MetricDD.cs b/server/models/MetricDD.cs new file mode 100644 index 0000000..7e36841 --- /dev/null +++ b/server/models/MetricDD.cs @@ -0,0 +1,33 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + /// + /// Metric for ONE DAY interval stats + /// + public class MetricDD + { + [Required] + [Key] + public DateTime t { get; set; } + public long AttachmentFileSize { get; set; } + public long AttachmentFileCount { get; set; } + public long AttachmentFilesAvailableSpace { get; set; } + + public long UtilityFileSize { get; set; } + public long UtilityFileCount { get; set; } + public long UtilityFilesAvailableSpace { get; set; } + + public long DBTotalSize { get; set; } + + + + + //ef core requires this + public MetricDD() { t = System.DateTime.UtcNow; } + + + }//eoc +}//eons diff --git a/server/models/MetricMM.cs b/server/models/MetricMM.cs new file mode 100644 index 0000000..44718e5 --- /dev/null +++ b/server/models/MetricMM.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + /// + /// Metric for ONE MINUTE intervals + /// + public class MetricMM + { + [Required] + [Key] + public DateTime t { get; set; } + public long Allocated { get; set; } + public long WorkingSet { get; set; } + public long PrivateBytes { get; set; } + public double CPU { get; set; } + + //ef core requires this + public MetricMM() { } + + public MetricMM(long allocated, long workingSet, long privateBytes, double cpu) + { + t = System.DateTime.UtcNow; + Allocated = allocated; + WorkingSet = workingSet; + PrivateBytes = privateBytes; + CPU = cpu; + } + }//eoc +}//eons diff --git a/server/models/Notification.cs b/server/models/Notification.cs new file mode 100644 index 0000000..0aeecc6 --- /dev/null +++ b/server/models/Notification.cs @@ -0,0 +1,47 @@ +using System; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + + public class InAppNotification + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public long UserId { get; set; } + [Required] + public DateTime Created { get; set; } + public SockType? SockType { get; set; } + public long? ObjectId { get; set; } + [Required] + public string Name { get; set; }//object name or closest equivalent for display + [Required] + public NotifyEventType EventType { get; set; } + [Required] + public long NotifySubscriptionId { get; set; } + public string Message { get; set; } + public TimeSpan AgeValue { get; set; } + public decimal DecValue { get; set; } + [Required] + public bool Fetched { get; set; } + + public InAppNotification() + { + Created = DateTime.UtcNow; + Fetched = false; + Name = string.Empty; + AgeValue = TimeSpan.Zero; + DecValue = 0m; + } + + //linked entity + [JsonIgnore] + public NotifySubscription NotifySubscription { get; set; } + + }//eoc + +}//eons diff --git a/server/models/NotifyDeliveryLog.cs b/server/models/NotifyDeliveryLog.cs new file mode 100644 index 0000000..a7f1176 --- /dev/null +++ b/server/models/NotifyDeliveryLog.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //This model holds the deliveries that have been attempted in the past 90 days (cleaned out by corenotifysweeper) + //it is used for verification / troubleshooting purposes from the OPS log + //and also used as a circuit breaker by the corejobnotify to ensure users are not spammed with identical messages + + + + public class NotifyDeliveryLog + { + public long Id { get; set; } + public uint Concurrency { get; set; } + +//public SockType NotifyType { get; set; } +//public long CustomerNotifySubscriptionId {get;set;} + + [Required] + public DateTime Processed { get; set; } + + public long ObjectId { get; set; } + + [Required] + public long NotifySubscriptionId { get; set; } + + [Required] + public bool Fail { get; set; } + public string Error { get; set; } + + + public NotifyDeliveryLog() + { + Processed = DateTime.UtcNow; + Fail = false; + ObjectId = 0; + } + + //linked entity + public NotifySubscription NotifySubscription { get; set; } + + }//eoc + +}//eons diff --git a/server/models/NotifyEvent.cs b/server/models/NotifyEvent.cs new file mode 100644 index 0000000..d7452d9 --- /dev/null +++ b/server/models/NotifyEvent.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //NOTE: Any non required field (nullable in DB) sb nullable here, i.e. decimal? not decimal, + //otherwise the server will call it an invalid record if the field isn't sent from client + + + //This class holds events that occur in the database for delivery to users + //it's the result of an event happening, not the subscription which is seperate and decides who gets what + //when an object is modified it may create a NotifyEvent record if anyone subscribes to that event + //it will create one of these for every user with that subscription + //which will then be delivered as a notification later when the corejobnotify job runs if it's deliverable + public class NotifyEvent + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public DateTime Created { get; set; } + public SockType SockType { get; set; } + public long ObjectId { get; set; } + [Required] + public string Name { get; set; }//object name or closest equivalent for display + [Required] + public NotifyEventType EventType { get; set; } + [Required] + public long UserId { get; set; } + [Required] + public long NotifySubscriptionId { get; set; }//source subscription that triggered this event to be created + + public decimal DecValue { get; set; } + + //date of the event actually occuring, e.g. WarrantyExpiry date. Compared with subscription to determine if deliverable or not + public DateTime EventDate { get; set; } + public string Message { get; set; } + + + public NotifyEvent() + { + Created = EventDate = DateTime.UtcNow; + // IdValue = 0; + DecValue = 0; + SockType = SockType.NoType; + ObjectId = 0; + Name = string.Empty; + } + + public override string ToString() + { + return JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.None); + } + + //linked entity + // public NotifySubscription NotifySubscription { get; set; } + // public User User { get; set; } + + }//eoc + +}//eons diff --git a/server/models/NotifySubscription.cs b/server/models/NotifySubscription.cs new file mode 100644 index 0000000..5396faa --- /dev/null +++ b/server/models/NotifySubscription.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //NOTE: Any non required field (nullable in DB) sb nullable here, i.e. decimal? not decimal, + //otherwise the server will call it an invalid record if the field isn't sent from client + + public class NotifySubscription + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public long UserId { get; set; } + public TimeSpan AdvanceNotice { get; set; } + + [Required] + public NotifyDeliveryMethod DeliveryMethod { get; set; } + public string DeliveryAddress { get; set; } + public long LinkReportId { get; set; } + + + //CREATE NOTIFY EVENT CONDITIONS - Following fields are all conditions set on whether to create a notify event or not + public SockType SockType { get; set; }//Note: must be specific object, not global for any object related stuff to avoid many role issues and also potential overload + [Required] + public NotifyEventType EventType { get; set; } + [Required] + public long IdValue { get; set; }//if required, this must match, default is zero to match to not set + public decimal DecValue { get; set; }//if required this must match or be greater or something + public List Tags { get; set; }//Tags to filter an event, object *must* have these tags to generate event related to it (AT TIME OF UPDATE) + + //DELIVERY CONDITIONS - following are all conditions on *whether* to deliver the existing notify event or not + public TimeSpan AgeValue { get; set; }//for events that depend on an age of something (e.g. WorkorderStatusAge), This value determines when event has "come of age" but advancenotice controls how far in advance of this delivery is made + + public NotifySubscription() + { + Tags = new List(); + SockType = SockType.NoType; + IdValue = 0; + DecValue = 0; + AgeValue = TimeSpan.Zero; + AdvanceNotice = TimeSpan.Zero; + LinkReportId = 0; + } + + }//eoc + +}//eons diff --git a/server/models/OpsJob.cs b/server/models/OpsJob.cs new file mode 100644 index 0000000..9cf8c55 --- /dev/null +++ b/server/models/OpsJob.cs @@ -0,0 +1,69 @@ +using System; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + + /// + /// Operations job + /// + public class OpsJob + { + [Key] + public Guid GId { get; set; } + + [Required] + public DateTime Created { get; set; } + public uint Concurrency { get; set; } + + + [Required] + public string Name { get; set; } + + //------------------------ + [Required] + public bool Exclusive { get; set; }//true lock api and don't run other jobs until completed / false=run any time with other jobs async + public DateTime StartAfter { get; set; } + [Required] + public JobType JobType { get; set; } + public JobSubType SubType { get; set; } + public long ObjectId { get; set; } + public SockType SockType { get; set; } + [Required] + public JobStatus JobStatus { get; set; } + + /// + /// Json string of any required extra info for job + /// + public string JobInfo { get; set; }//json as string of any required extra info for job + + public string Progress {get;set;}//any type of text digestible by client showing progress of job, typically just a string i.e. "133/344" + + public OpsJob() + { + GId = new Guid(); + Created = DateTime.UtcNow; + Name = "new job"; + Exclusive = false; + StartAfter = Created; + JobType = JobType.NotSet; + SubType = JobSubType.NotSet; + ObjectId = 0; + SockType = SockType.NoType; + JobStatus = JobStatus.Sleeping; + JobInfo = null; + Progress=string.Empty; + + } + + public override string ToString() + { + return JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.None); + } + + + } + +} diff --git a/server/models/OpsJobLog.cs b/server/models/OpsJobLog.cs new file mode 100644 index 0000000..7b191df --- /dev/null +++ b/server/models/OpsJobLog.cs @@ -0,0 +1,33 @@ +using System; + +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + /// + /// Operations job log + /// + public class OpsJobLog + { + [Key] + public Guid GId { get; set; } + + [Required] + public Guid JobId { get; set; } + [Required] + public DateTime Created { get; set; } + [Required] + public string StatusText { get; set; } + + + + public OpsJobLog(){ + GId=new Guid(); + Created=DateTime.UtcNow; + StatusText="default / not set"; + } + + } + +} diff --git a/server/models/PickListTemplate.cs b/server/models/PickListTemplate.cs new file mode 100644 index 0000000..499b5a4 --- /dev/null +++ b/server/models/PickListTemplate.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + //See core-picklist-autocomplete-template-system.txt for details + public class PickListTemplate + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + public string Template { get; set; }//JSON fragment of picklist template + + } +} diff --git a/server/models/Reminder.cs b/server/models/Reminder.cs new file mode 100644 index 0000000..1e81003 --- /dev/null +++ b/server/models/Reminder.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //NOTE: Any non required field (nullable in DB) sb nullable here, i.e. decimal? not decimal, + //otherwise the server will call it an invalid record if the field isn't sent from client + + public class Reminder : ICoreBizObjectModel + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public string Name { get; set; } + // public bool Active { get; set; } + public string Notes { get; set; } + public string Wiki { get; set; } + public string CustomFields { get; set; } + public List Tags { get; set; } + + [Required] + public DateTime StartDate { get; set; } + [Required] + public DateTime StopDate { get; set; } + [Required] + public long UserId { get; set; } + [NotMapped] + public string UserViz { get; set; } + /* + Hexadecimal notation: #RGB[A] + R (red), G (green), B (blue), and A (alpha) are hexadecimal characters (0–9, A–F). A is optional. The three-digit notation (#RGB) is a shorter version of the six-digit form (#RRGGBB). For example, #f09 is the same color as #ff0099. Likewise, the four-digit RGB notation (#RGBA) is a shorter version of the eight-digit form (#RRGGBBAA). For example, #0f38 is the same color as #00ff3388. + */ + [MaxLength(12)] + public string Color { get; set; } + + public Reminder() + { + Tags = new List(); + Color = "#ffffffff";//white / no color is the default + } + + [NotMapped, JsonIgnore] + public SockType SType { get => SockType.Reminder; } + + }//eoc + +}//eons + +/* +DATES should be indexed for fast viewing +CREATE TABLE [dbo].[ASCHEDULEMARKER]( + [AID] [uniqueidentifier] NOT NULL, + [ACREATED] [datetime] NOT NULL, + [ACREATOR] [uniqueidentifier] NOT NULL, + [AMODIFIER] [uniqueidentifier] NOT NULL, + [ANAME] [nvarchar](255) NULL, + [ANOTES] [ntext] NULL, + [AMODIFIED] [datetime] NULL, + [ASTARTDATE] [datetime] NULL, + [ASTOPDATE] [datetime] NULL, + [ASCHEDULEMARKERSOURCETYPE] [smallint] NULL, + [ASOURCEID] [uniqueidentifier] NOT NULL, + [AARGB] [int] NULL, + + NOPE: these are for Review, not reminder + [AFOLLOWID] [uniqueidentifier] NULL, + [AFOLLOWTYPE] [smallint] NULL, + [ACOMPLETED] [bit] NOT NULL, + +*/ \ No newline at end of file diff --git a/server/models/Report.cs b/server/models/Report.cs new file mode 100644 index 0000000..09d5080 --- /dev/null +++ b/server/models/Report.cs @@ -0,0 +1,66 @@ +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + + public class Report + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public string Name { get; set; } + public bool Active { get; set; } + public string Notes { get; set; } + public AuthorizationRoles Roles { get; set; } + [Required] + public SockType SockType { get; set; } + + public bool IncludeWoItemDescendants {get;set;} + + + public string Template { get; set; } + public string Style { get; set; } + public string JsPrerender { get; set; } + public string JsHelpers { get; set; } + [Required] + public ReportRenderType RenderType { get; set; } + + //PDF options + //http://www.puppeteersharp.com/api/PuppeteerSharp.PdfOptions.html + public string HeaderTemplate { get; set; } + public string FooterTemplate { get; set; } + public bool DisplayHeaderFooter { get; set; } + public ReportPaperFormat PaperFormat { get; set; } + public bool Landscape { get; set; } + public string MarginOptionsBottom { get; set; } + public string MarginOptionsLeft { get; set; } + public string MarginOptionsRight { get; set; } + public string MarginOptionsTop { get; set; } + public string PageRanges { get; set; } + public bool PreferCSSPageSize { get; set; } + public bool PrintBackground { get; set; } + public decimal Scale { get; set; } + + + + public Report() + { + RenderType = ReportRenderType.PDF; + SockType = SockType.NoType; + Roles = AuthorizationRoles.All; + Active = true; + //these are pdf option defaults as per PuppeteerSharp + DisplayHeaderFooter=false; + PaperFormat=ReportPaperFormat.NotSet; + Landscape=false; + PreferCSSPageSize=false; + PrintBackground=false; + Scale=1; + } + + }//eoc + +}//eons diff --git a/server/models/Review.cs b/server/models/Review.cs new file mode 100644 index 0000000..b03c986 --- /dev/null +++ b/server/models/Review.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + //NOTE: Any non required field (nullable in DB) sb nullable here, i.e. decimal? not decimal, + //otherwise the server will call it an invalid record if the field isn't sent from client + + public class Review : ICoreBizObjectModel + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public string Name { get; set; } + // public bool Active { get; set; }//NOT USED + public string Notes { get; set; } + public string Wiki { get; set; } + public string CustomFields { get; set; } + public List Tags { get; set; } + + [Required] + public DateTime ReviewDate { get; set; } + public DateTime? CompletedDate { get; set; } + public string CompletionNotes { get; set; } + [Required] + public long UserId { get; set; } + [NotMapped] + public string UserViz { get; set; } + [Required] + public long AssignedByUserId { get; set; } + [NotMapped] + public string AssignedByUserViz { get; set; } + + [Required] + public long ObjectId { get; set; } + [Required] + public SockType SType { get; set; }//int + [NotMapped] + public string ReviewObjectViz { get; set; } + + [NotMapped] + public bool OverDue + { + get + { + return (ReviewDate > DateTime.UtcNow); + } + } + + + public Review() + { + Tags = new List(); + } + + [NotMapped, JsonIgnore] + public SockType SockType { get => SockType.Review; } + + }//eoc + +}//eons +/* +DATES should be indexed for fast viewing +CREATE TABLE [dbo].[ASCHEDULEMARKER]( + [AID] [uniqueidentifier] NOT NULL, + [ACREATED] [datetime] NOT NULL, + [ACREATOR] [uniqueidentifier] NOT NULL, + [AMODIFIER] [uniqueidentifier] NOT NULL, + [ANAME] [nvarchar](255) NULL, + [ANOTES] [ntext] NULL, + [AMODIFIED] [datetime] NULL, + [ASTARTDATE] [datetime] NULL, + [ASTOPDATE] [datetime] NULL, + [ASCHEDULEMARKERSOURCETYPE] [smallint] NULL, + [ASOURCEID] [uniqueidentifier] NOT NULL, + [AARGB] [int] NULL, + [AFOLLOWID] [uniqueidentifier] NULL, + [AFOLLOWTYPE] [smallint] NULL, + [ACOMPLETED] [bit] NOT NULL, + +*/ \ No newline at end of file diff --git a/server/models/SchemaVersion.cs b/server/models/SchemaVersion.cs new file mode 100644 index 0000000..dafd37e --- /dev/null +++ b/server/models/SchemaVersion.cs @@ -0,0 +1,22 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + /// + /// Sockeye Schema version table + /// + public class SchemaVersion + { + [Required] + [Key] + public string Id { get; set; } + public int Schema { get; set; } + + //ef core requires this + public SchemaVersion() { } + + + }//eoc +}//eons diff --git a/server/models/SearchDictionary.cs b/server/models/SearchDictionary.cs new file mode 100644 index 0000000..18833b1 --- /dev/null +++ b/server/models/SearchDictionary.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + public class SearchDictionary + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required, MaxLength(255)] + public string Word { get; set; }//max 255 characters ascii set + + } +} diff --git a/server/models/SearchKey.cs b/server/models/SearchKey.cs new file mode 100644 index 0000000..cdc4805 --- /dev/null +++ b/server/models/SearchKey.cs @@ -0,0 +1,24 @@ +using Sockeye.Biz; + +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + public class SearchKey + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + + [Required] + public long WordId { get; set; } + [Required] + public long ObjectId { get; set; } + [Required] + public SockType SockType { get; set; } + + + + } +} diff --git a/server/models/Tag.cs b/server/models/Tag.cs new file mode 100644 index 0000000..134f87f --- /dev/null +++ b/server/models/Tag.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; + +namespace Sockeye.Models +{ + + public class Tag + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required, MaxLength(255)] + public string Name { get; set; }//max 255 characters + + public long RefCount { get; set; } + + } +} diff --git a/server/models/Translation.cs b/server/models/Translation.cs new file mode 100644 index 0000000..ff02ec3 --- /dev/null +++ b/server/models/Translation.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; + +using System.ComponentModel.DataAnnotations; + +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + public class Translation + { + // public Translation() { } + // public Translation(Translation t) + // { + + // Id = t.Id; + // Concurrency = t.Concurrency; + // Name = t.Name; + // CjkIndex = t.CjkIndex; + // _translationItems = new List(); + // foreach (var titem in t.TranslationItems) + // { + // _translationItems.Add(new TranslationItem(titem)); + // } + // } + + public long Id { get; set; } + public uint Concurrency { get; set; } + + + [Required] + public string Name { get; set; } + [Required] + public string BaseLanguage { get; set; } + public bool? Stock { get; set; } + public bool CjkIndex { get; set; } + + + + //Relationship + //was this but.. + // public ICollection TranslationItems { get; set; } + + //Not perhaps so useful here but this is a good way to lazy initialize collections which + //is more efficient when there are many child collections (but when would that ever be desired for Sockeye?)and means no need to null check the collection + //https://stackoverflow.com/a/20773057/8939 + + private ICollection _translationItems; + public virtual ICollection TranslationItems + { + get + { + return this._translationItems ?? (this._translationItems = new HashSet()); + } + } + + + }//eoc + +}//eons diff --git a/server/models/TranslationItem.cs b/server/models/TranslationItem.cs new file mode 100644 index 0000000..9f38073 --- /dev/null +++ b/server/models/TranslationItem.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; + +using System.ComponentModel.DataAnnotations; + +using Newtonsoft.Json; + +namespace Sockeye.Models +{ + + public class TranslationItem + { + public TranslationItem() { } + public TranslationItem(TranslationItem t) + { + Id = t.Id; + Concurrency = t.Concurrency; + Key = t.Key; + Display = t.Display; + TranslationId = t.TranslationId; + } + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public string Key { get; set; } + //NOTE: due to various UI fuckery this is not set to required here so that the biz object + //validate code can fix it up automatically instead + public string Display { get; set; } + + public long TranslationId { get; set; } + + //Relation + [JsonIgnore] + public Translation Translation { get; set; } + } +} diff --git a/server/models/User.cs b/server/models/User.cs new file mode 100644 index 0000000..16ebef7 --- /dev/null +++ b/server/models/User.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using Sockeye.Biz; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; +namespace Sockeye.Models +{ + + public class dtUser : ICoreBizObjectModel//used to return user object without auth related fields in UserGet route + { + public dtUser() + { + Tags = new List(); + + } + public long Id { get; set; } + public uint Concurrency { get; set; } + public bool Active { get; set; } + public bool AllowLogin { get; set; } + [Required, MaxLength(255)] + public string Name { get; set; } + public AuthorizationRoles Roles { get; set; } + public UserType UserType { get; set; } + public string EmployeeNumber { get; set; } + public string Notes { get; set; } + public long? CustomerId { get; set; } + public long? HeadOfficeId { get; set; } + public long? VendorId { get; set; } + public string Wiki { get; set; } + public string CustomFields { get; set; } + public List Tags { get; set; } + public bool TwoFactorEnabled { get; set; } + public DateTime? LastLogin { get; set; } + + [NotMapped, JsonIgnore] + public SockType SType { get => SockType.User; } + + public bool IsOutsideUser + { + get + { + return this.UserType == UserType.Customer || this.UserType == UserType.HeadOffice; + } + } + + // [JsonIgnore]//hide from being returned (as null anyway) with User object in routes + // public UserOptions UserOptions { get; set; } + }//eoc + + public class User : ICoreBizObjectModel + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public bool Active { get; set; } + public bool AllowLogin { get; set; } + [Required, MaxLength(255)] + public string Name { get; set; } + public DateTime? LastLogin { get; set; } + public string Login { get; set; } + public string Password { get; set; } + public bool TwoFactorEnabled { get; set; } + [Required] + public AuthorizationRoles Roles { get; set; } + [Required] + public UserType UserType { get; set; } + [NotMapped] + public string UserTypeViz { get; set; } + [MaxLength(255)] + public string EmployeeNumber { get; set; } + public string Notes { get; set; } + public long? CustomerId { get; set; } + [NotMapped] + public string CustomerViz { get; set; } + public long? HeadOfficeId { get; set; } + [NotMapped] + public string HeadOfficeViz { get; set; } + public long? VendorId { get; set; } + [NotMapped] + public string VendorViz { get; set; } + public string Wiki { get; set; } + public string CustomFields { get; set; } + public List Tags { get; set; } + + //====================== + //NOT IN DTUSER CLASS + [JsonIgnore] + public string Salt { get; set; }//--- + [JsonIgnore] + public string CurrentAuthToken { get; set; }//--- + [JsonIgnore] + public string DlKey { get; set; }//--- + [JsonIgnore] + public DateTime? DlKeyExpire { get; set; }//--- + [JsonIgnore] + public string PasswordResetCode { get; set; }//--- + [JsonIgnore] + public DateTime? PasswordResetCodeExpire { get; set; }//--- + [JsonIgnore] + public string TotpSecret { get; set; }//--- + [JsonIgnore] + public string TempToken { get; set; }//--- Used for TFA verification step 2 + //========================== + + //relations + //https://docs.microsoft.com/en-us/ef/core/modeling/relationships#other-relationship-patterns + [JsonIgnore]//hide from being returned (as null anyway) with User object in routes + public UserOptions UserOptions { get; set; } + + [JsonIgnore] + public Customer Customer { get; set; } + + [JsonIgnore] + public HeadOffice HeadOffice { get; set; } + + + + + public User() + { + Tags = new List(); + + + } + + public bool IsTech + { + get + { + return this.UserType == UserType.ServiceContractor || this.UserType == UserType.Service; + } + } + + public bool IsOutsideCustomerContactTypeUser + { + get + { + return this.UserType == UserType.Customer || this.UserType == UserType.HeadOffice; + } + } + + + [NotMapped, JsonIgnore] + public SockType SType { get => SockType.User; } + + }//eoc +} \ No newline at end of file diff --git a/server/models/UserOptions.cs b/server/models/UserOptions.cs new file mode 100644 index 0000000..1fbd725 --- /dev/null +++ b/server/models/UserOptions.cs @@ -0,0 +1,135 @@ +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +namespace Sockeye.Models +{ + + public class UserOptions + { + public long Id { get; set; } + public uint Concurrency { get; set; } + + [Required] + public long TranslationId { get; set; } + + //------------- + //[EmailAddress] + public string EmailAddress { get; set; } + // [Phone] + public string Phone1 { get; set; } + //[Phone] + public string Phone2 { get; set; } + //in v7 this was pager address so not attributing it with Phone as not sure what would be in that field + public string Phone3 { get; set; } + + /* + Hexadecimal notation: #RGB[A] + R (red), G (green), B (blue), and A (alpha) are hexadecimal characters (0–9, A–F). A is optional. The three-digit notation (#RGB) is a shorter version of the six-digit form (#RRGGBB). For example, #f09 is the same color as #ff0099. Likewise, the four-digit RGB notation (#RGBA) is a shorter version of the eight-digit form (#RRGGBBAA). For example, #0f38 is the same color as #00ff3388. + */ + + [MaxLength(12)] + public string UiColor { get; set; } + + //browser forced overrides + public string LanguageOverride { get; set; } + public string TimeZoneOverride { get; set; } + public string CurrencyName { get; set; } + public bool Hour12 { get; set; } + + public string MapUrlTemplate { get; set; } + + + //relations + //https://docs.microsoft.com/en-us/ef/core/modeling/relationships#other-relationship-patterns + [JsonIgnore]//hide from being returned (as null anyway) in routes + public User User { get; set; } + [Required] + public long UserId { get; set; }//will be auto-set by EF due to relationship defined + + + public UserOptions() + { + CurrencyName = "USD"; + Hour12 = true; + UiColor = "#ffffffff";//black is the default + } + } + + +} +/* +v7 export record sample +{ + "DefaultLanguage": "Custom English", + "DefaultServiceTemplateID": "ca83a7b8-4e5f-4a7b-a02b-9cf78d5f983f", + "UserType": 2, + "Active": true, + "ClientID": "00000000-0000-0000-0000-000000000000", + "HeadOfficeID": "00000000-0000-0000-0000-000000000000", + "MemberOfGroup": "0f8a80ff-4b03-4114-ae51-2d13b812dd65", + "Created": "03/21/2005 07:19 AM", + "Modified": "09/15/2015 12:22 PM", + "Creator": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed", + "Modifier": "1d859264-3f32-462a-9b0c-a67dddfdf4d3", + "ID": "1d859264-3f32-462a-9b0c-a67dddfdf4d3", + "FirstName": "Hank", + "LastName": "Rearden", + "Initials": "HR", + "EmployeeNumber": "EMP1236", + "PageAddress": "", + "PageMaxText": 24, + "Phone1": "", + "Phone2": "", + "EmailAddress": "", + "UserCertifications": [ + { + "Created": "12/22/2005 02:07 PM", + "Creator": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed", + "Modified": "12/22/2005 02:08 PM", + "Modifier": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed", + "ID": "4492360c-43e4-4209-9f33-30691b0808ed", + "UserCertificationID": "b2f26359-7c42-4218-923a-e949f3ef1f85", + "UserID": "1d859264-3f32-462a-9b0c-a67dddfdf4d3", + "ValidStartDate": "2005-10-11T00:00:00-07:00", + "ValidStopDate": "2006-10-11T00:00:00-07:00" + } + ], + "UserSkills": [ + { + "Created": "12/22/2005 02:06 PM", + "Creator": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed", + "Modified": "12/22/2005 02:08 PM", + "Modifier": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed", + "ID": "1dc5ce96-f411-4885-856e-5bdb3ad79728", + "UserSkillID": "2e6f8b65-594c-4f6c-9cd6-e14a562daba8", + "UserID": "1d859264-3f32-462a-9b0c-a67dddfdf4d3" + }, + { + "Created": "12/22/2005 02:06 PM", + "Creator": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed", + "Modified": "12/22/2005 02:08 PM", + "Modifier": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed", + "ID": "88e476d3-7526-45f5-a0dd-706c8053a63f", + "UserSkillID": "47a4ee94-b0e9-41b5-afe5-4b4f2c981877", + "UserID": "1d859264-3f32-462a-9b0c-a67dddfdf4d3" + } + ], + "Notes": "", + "VendorID": "06e502c2-69ba-4e88-8efb-5b53c1687740", + "RegionID": "f856423a-d468-4344-b7b8-121e466738c6", + "DispatchZoneID": "00000000-0000-0000-0000-000000000000", + "SubContractor": false, + "DefaultWarehouseID": "d45eab37-b6e6-4ad2-9163-66d7ba83a98c", + "Custom1": "", + "Custom2": "", + "Custom3": "", + "Custom4": "", + "Custom5": "", + "Custom6": "", + "Custom7": "", + "Custom8": "", + "Custom9": "", + "Custom0": "", + "ScheduleBackColor": -2097216, + "TimeZoneOffset": null +} + */ diff --git a/server/models/dto/AddressRecord.cs b/server/models/dto/AddressRecord.cs new file mode 100644 index 0000000..4384d0c --- /dev/null +++ b/server/models/dto/AddressRecord.cs @@ -0,0 +1,8 @@ + +namespace Sockeye.Models +{ + //physical + public record AddressRecord(string Name, string Address, string City, string Region, string Country, string AddressPostal, decimal? Latitude, decimal? Longitude); + //postal + public record PostalAddressRecord(string Name, string PostAddress, string PostCity, string PostRegion, string PostCountry, string PostCode); +} diff --git a/server/models/dto/AyImportData.cs b/server/models/dto/AyImportData.cs new file mode 100644 index 0000000..ffdfc4c --- /dev/null +++ b/server/models/dto/AyImportData.cs @@ -0,0 +1,19 @@ +using System; +using Sockeye.Biz; +using Newtonsoft.Json.Linq; + +namespace Sockeye.Models +{ + + /// + /// Import data object used by admin->import feature + /// + public class AyImportData + { + public SockType SockType { get; set; } + public JArray Data { get; set; } + public bool DoImport {get;set;} + public bool DoUpdate {get;set;} + } + +} diff --git a/server/models/dto/JobOperationsFetchInfo.cs b/server/models/dto/JobOperationsFetchInfo.cs new file mode 100644 index 0000000..2a364a8 --- /dev/null +++ b/server/models/dto/JobOperationsFetchInfo.cs @@ -0,0 +1,40 @@ +using Sockeye.Biz; +using System; + +namespace Sockeye.Models +{ + /// + /// Job info fetch data + /// + public class JobOperationsFetchInfo + { + + /// + /// Identity of the job + /// + /// id value of job, can be used to fetch logs + public Guid GId { get; set; } + /// + /// Date job was submitted + /// + /// UTC date/time + public DateTime Created { get; set; } + /// + /// Date of the most recent operation for this job + /// + /// UTC date/time + public DateTime LastAction { get; set; } + /// + /// Descriptive name of the job + /// + /// string + public string Name { get; set; } + /// + /// Status of the job + /// + /// Job status as string + public string JobStatus { get; set; } + + } + +} diff --git a/server/models/dto/JobOperationsLogInfoItem.cs b/server/models/dto/JobOperationsLogInfoItem.cs new file mode 100644 index 0000000..856774f --- /dev/null +++ b/server/models/dto/JobOperationsLogInfoItem.cs @@ -0,0 +1,16 @@ +using System; + +namespace Sockeye.Models +{ + /// + /// Job log item + /// + public class JobOperationsLogInfoItem + { + public DateTime Created { get; set; } + public string StatusText { get; set; } + public Guid JobId { get; set; } + + } + +} diff --git a/server/models/dto/JobProgress.cs b/server/models/dto/JobProgress.cs new file mode 100644 index 0000000..502f2fa --- /dev/null +++ b/server/models/dto/JobProgress.cs @@ -0,0 +1,26 @@ +using Sockeye.Biz; +using System; + +namespace Sockeye.Models +{ + /// + /// Job Progress + /// + public class JobProgress + { + + + /// + /// Progress string + /// + /// string + public string Progress { get; set; } + /// + /// Status of the job + /// + /// Job status + public JobStatus JobStatus { get; set; } + + } + +} diff --git a/server/models/dto/NameIdActiveChargeCostItem.cs b/server/models/dto/NameIdActiveChargeCostItem.cs new file mode 100644 index 0000000..a786c39 --- /dev/null +++ b/server/models/dto/NameIdActiveChargeCostItem.cs @@ -0,0 +1,13 @@ +namespace Sockeye.Models +{ + //Used by QBI for part and service /travel rate lists for caching and mapping + public class NameIdActiveChargeCostItem + { + public long Id { get; set; } + public string Name { get; set; } + public bool Active { get; set; } + public decimal Cost { get; set; } + public decimal Charge { get; set; } + } + +} diff --git a/server/models/dto/NameIdActiveItem.cs b/server/models/dto/NameIdActiveItem.cs new file mode 100644 index 0000000..9a85200 --- /dev/null +++ b/server/models/dto/NameIdActiveItem.cs @@ -0,0 +1,12 @@ +namespace Sockeye.Models +{ + + public class NameIdActiveItem + { + public long Id { get; set; } + public string Name { get; set; } + public bool? Active { get; set; } + + } + +} diff --git a/server/models/dto/NameIdDefaultItem.cs b/server/models/dto/NameIdDefaultItem.cs new file mode 100644 index 0000000..bc25920 --- /dev/null +++ b/server/models/dto/NameIdDefaultItem.cs @@ -0,0 +1,4 @@ +namespace Sockeye.Models +{ + public record NameIdDefaultItem(long Id, string Name, bool Default); +} diff --git a/server/models/dto/NameIdItem.cs b/server/models/dto/NameIdItem.cs new file mode 100644 index 0000000..a614cf3 --- /dev/null +++ b/server/models/dto/NameIdItem.cs @@ -0,0 +1,10 @@ +namespace Sockeye.Models +{ + + public class NameIdItem + { + public long Id { get; set; } + public string Name { get; set; } + } + +} diff --git a/server/models/dto/NameItem.cs b/server/models/dto/NameItem.cs new file mode 100644 index 0000000..e41fee4 --- /dev/null +++ b/server/models/dto/NameItem.cs @@ -0,0 +1,12 @@ +namespace Sockeye.Models +{ + + /// + /// Dto object for name only parameters in routes + /// + public class NameItem + { + public string Name { get; set; } + } + +} diff --git a/server/models/dto/NewTextIdConcurrencyTokenItem.cs b/server/models/dto/NewTextIdConcurrencyTokenItem.cs new file mode 100644 index 0000000..85dd3db --- /dev/null +++ b/server/models/dto/NewTextIdConcurrencyTokenItem.cs @@ -0,0 +1,11 @@ +namespace Sockeye.Models +{ + + public class NewTextIdConcurrencyTokenItem + { + public long Id { get; set; } + public string NewText { get; set; } + public uint Concurrency { get; set; } + } + +} diff --git a/server/models/dto/ParentAndChildItemId.cs b/server/models/dto/ParentAndChildItemId.cs new file mode 100644 index 0000000..eaf1eae --- /dev/null +++ b/server/models/dto/ParentAndChildItemId.cs @@ -0,0 +1,8 @@ +namespace Sockeye.Models +{ + public class ParentAndChildItemId + { + public long ParentId { get; set; } + public long ChildItemId { get; set; } + } +} diff --git a/server/models/dto/ScheduleItemAdjustParams.cs b/server/models/dto/ScheduleItemAdjustParams.cs new file mode 100644 index 0000000..53ae497 --- /dev/null +++ b/server/models/dto/ScheduleItemAdjustParams.cs @@ -0,0 +1,14 @@ +using System; +using Sockeye.Biz; + +namespace Sockeye.Models +{ + public class ScheduleItemAdjustParams + { + public SockType Type { get; set; } + public long Id { get; set; } + public long? UserId { get; set; } + public DateTime Start { get; set; } + public DateTime End { get; set; } + } +} \ No newline at end of file diff --git a/server/models/dto/UploadFileData.cs b/server/models/dto/UploadFileData.cs new file mode 100644 index 0000000..ebdfa48 --- /dev/null +++ b/server/models/dto/UploadFileData.cs @@ -0,0 +1,8 @@ +namespace Sockeye.Models +{ + public class UploadFileData + { + public string name { get; set; } + public long lastModified { get; set; } + } +} diff --git a/server/models/dto/UploadedFileInfo.cs b/server/models/dto/UploadedFileInfo.cs new file mode 100644 index 0000000..f961dea --- /dev/null +++ b/server/models/dto/UploadedFileInfo.cs @@ -0,0 +1,16 @@ +using System; +namespace Sockeye.Models +{ + + /// + /// Uploaded file info in it's temporary state + /// + public class UploadedFileInfo + { + public string InitialUploadedPathName { get; set; } + public string OriginalFileName { get; set; } + public string MimeType { get; set; } + public DateTime LastModified {get;set;} + } + +} diff --git a/server/resource/de.json b/server/resource/de.json new file mode 100644 index 0000000..eabe406 --- /dev/null +++ b/server/resource/de.json @@ -0,0 +1,1495 @@ +{ + "Accounting": "Buchhaltung", + "Active": "Aktiv", + "Activity": "Aktivität", + "Add": "Hinzufügen", + "AddMultipleUnits": "Mehrere Einheiten hinzufügen", + "AddNewUnit": "Neue Einheit hinzufügen", + "Address": "Adresse", + "AddressCity": "Stadt", + "AddressCopyToPhysical": "Copy to physical address", + "AddressCopyToPostal": "Copy to postal address", + "AddressCountry": "Land", + "AddressDeliveryAddress": "Straße", + "AddressLatitude": "Breite", + "AddressLongitude": "Länge", + "AddressPostalCity": "Stadt (Post)", + "AddressPostalCountry": "Land (Post)", + "AddressPostalDeliveryAddress": "Adresse (Post)", + "AddressPostalPostal": "Postleitzahl (Post)", + "AddressPostalStateProv": "Bundesland (Post)", + "AddressStateProv": "Bundesland", + "AddressTypePhysical": "Physikalische Adresse", + "AddressTypePostal": "Postanschrift", + "AdminEraseDatabase": "Gesamte Sockeye-Datenbank löschen", + "AdminEraseDatabaseLastWarning": "Warnung: Dies ist Ihre letzte Chance, das dauerhafte Löschen aller Daten zu vermeiden. Möchten Sie wirklich alle Daten löschen?", + "AdminEraseDatabaseWarning": "Warnung: Sie möchten alle Daten in Sockeye dauerhaft löschen. Möchten Sie wirklich fortfahren?", + "Administration": "Verwaltung", + "AdministrationGlobalSettings": "Globale Einstellungen", + "AlertNotes": "Warnhinweise", + "AllItemsInList": "Alle Elemente in der Liste", + "AnyUser": "All users", + "AppendTasks": "Aufgaben anhängen", + "ApplyUnitContract": "Vertrag '{0}' dieser Einheit auf Arbeitsauftrag anwenden?", + "AreYouSureBackupNow": "Der Server wird während der Sicherung gesperrt. Bist du sicher?", + "AreYouSureShutDown": "Sind Sie sicher, dass Sie den Server herunterfahren möchten?", + "AreYouSureUnsavedChanges": "Möchten Sie den Vorgang wirklich beenden? Alle nicht gespeicherten Änderungen werden verworfen.", + "AttachFile": "Datei anhängen", + "AttachmentExists": "Datei existiert", + "AttachmentFileName": "Dateiname", + "AttachmentNotes": "Notizen", + "Attachments": "Angefügte Dateien", + "AttachReport":"Bericht anhängen", + "AuthConnectAppManualEntry": "Haben Sie Probleme beim Scannen des Codes? Geben Sie Folgendes manuell in Ihre Authentifizierungs-App ein:", + "AuthConnectAppSubTitle": "Scannen Sie den QR-Code mit einer Authentifizierungs-App wie Google Authenticator, Duo, Microsoft Authenticator oder Authy. Es wird ein 6-stelliger Passcode angezeigt, den Sie unten eingeben müssen.", + "AuthConnectAppTitle": "Verbinden Sie Ihre App", + "AuthConnectCompleted": "Die Zwei-Faktor-Authentifizierung ist jetzt aktiviert", + "AuthDisableTwoFactor": "Deaktivieren Sie die Zwei-Faktor-Authentifizierung", + "AuthEnterPin": "Geben Sie einen 6-stelligen Code ein", + "AuthorizationRoleAccounting": "Buchhaltung", + "AuthorizationRoleAll": "Alle rollen", + "AuthorizationRoleBizAdmin": "Betriebswirtschaftslehre", + "AuthorizationRoleBizAdminRestricted": "Betriebswirtschaftslehre - eingeschränkt", + "AuthorizationRoleCustomer": "Kundenbenutzer", + "AuthorizationRoleCustomerRestricted": "Kundenbenutzer - eingeschränkt", + "AuthorizationRoleInventory": "Inventar", + "AuthorizationRoleInventoryRestricted": "Inventar - eingeschränkt", + "AuthorizationRoleNoRole": "Keine Rolle", + "AuthorizationRoleOpsAdmin": "Systemvorgänge", + "AuthorizationRoleOpsAdminRestricted": "Systemvorgänge - eingeschränkt", + "AuthorizationRoles": "Autorisierungsrollen", + "AuthorizationRoleSales": "Verkaufsabteilung", + "AuthorizationRoleSalesRestricted": "Verkaufsabteilung - eingeschränkt", + "AuthorizationRoleService": "Serviceleiter", + "AuthorizationRoleServiceRestricted": "Serviceleiter - eingeschränkt", + "AuthorizationRoleSubContractor": "Subunternehmer", + "AuthorizationRoleSubContractorRestricted": "Subunternehmer - eingeschränkt", + "AuthorizationRoleTech": "Servicetechniker", + "AuthorizationRoleTechRestricted": "Servicetechniker - eingeschränkt", + "AuthPinInvalid": "Code ungültig", + "AuthTwoFactor": "Zwei-Faktor-Authentifizierung", + "AuthTwoFactorDisabled": "Die Zwei-Faktor-Authentifizierung ist jetzt deaktiviert", + "AuthVerifyCode": "Code überprüfen", + "AvailableSpace": "Verfügbarer Platz", + "AyaFileFileTooLarge": "File size exceeds limit of {0}", + "SockeyeServerURL": "Sockeye Server URL", + "SockType": "Typ", + "Backup": "Datensicherung", + "BackupAttachments": "Backup-Anhänge", + "BackupDeleteOld": "Alte Sicherungsdateien löschen", + "BackupFiles": "Liste der Sicherungsdateien", + "BackupLast": "Letzte Sicherungszeit", + "BackupNow": "Starten Sie jetzt das Backup", + "BackupSetsToKeep": "Anzahl der zu speichernden Backups", + "BackupSettings": "Sicherungseinstellungen", + "BackupTime": "Sicherungszeit", + "Backward": "Rückwärts bewegen", + "BatchDeleteJob": "Batch-Löschauftrag", + "BatchJob": "Batch-Job", + "BizMetrics": "Metrik", + "Browser": "Browser", + "BusinessSettings": "Geschäftseinstellungen", + "Cancel": "Abbrechen", + "CheckForLicense": "Lizenz installieren", + "Close": "Beenden", + "Columns": "Spalten", + "CompanyEmail": "Email", + "CompanyInformation": "Firmeninformation", + "CompanyPhone1": "Geschäftstelefon", + "CompanyPhone2": "Telefon 2", + "ConfirmPassword": "Kennwort bestätigen", + "ConfirmUpdatePartCost": "Teilekosten von erhaltenen Kosten aktualisieren?", + "ConnectionSecurity": "SMTP-Verbindungssicherheit", + "Contact": "Ansprechpartner", + "ContactCustomerHeadOfficeTaggedWith": "Nur Kontakt, Kunde oder Zentrale mit einem dieser etikett zulassen", + "ContactPhone": "Telefon des Ansprechpartners", + "Contacts": "Kontakte", + "ContactTitle": "Anrede des Ansprechpartners", + "Contract": "Vertrag", + "ContractAdjustment": "Preisanpassung", + "ContractCustom1": "Angepasstes Feld 1", + "ContractCustom10": "Angepasstes Feld 10", + "ContractCustom11": "Angepasstes Feld 11", + "ContractCustom12": "Angepasstes Feld 12", + "ContractCustom13": "Angepasstes Feld 13", + "ContractCustom14": "Angepasstes Feld 14", + "ContractCustom15": "Angepasstes Feld 15", + "ContractCustom16": "Angepasstes Feld 16", + "ContractCustom2": "Angepasstes Feld 2", + "ContractCustom3": "Angepasstes Feld 3", + "ContractCustom4": "Angepasstes Feld 4", + "ContractCustom5": "Angepasstes Feld 5", + "ContractCustom6": "Angepasstes Feld 6", + "ContractCustom7": "Angepasstes Feld 7", + "ContractCustom8": "Angepasstes Feld 8", + "ContractCustom9": "Angepasstes Feld 9", + "ContractDefaultAdjustments": "Standardpreisanpassungen", + "ContractDefaultResponseTime": "Reaktionszeit", + "ContractDiscountParts": "Rabatt auf alle Teile angewendet", + "ContractExpires": "Vertrag läuft ab", + "ContractList": "Verträge", + "ContractName": "Vertragsname", + "ContractNotes": "Anmerkungen", + "ContractOverrideType": "Preisanpassungsart", + "ContractOverrideTypeMarkup": "Kosten plus Prozentsatz", + "ContractOverrideTypePriceDiscount": "Preis minus Prozentsatz", + "ContractRate": "Vertragssatz", + "ContractRateList": "Vertragssätze", + "ContractServiceRatesOnly": "Lassen Sie nur diese Serviceraten zu", + "ContractTaggedAdjustments": "Verschlagwortet mit Preisanpassungen", + "ContractTravelRatesOnly": "Erlaube nur diese Reiseraten", + "Copy": "Kopieren", + "CopyAttachments": "Anhänge kopieren", + "CopyDbId": "Datenbank-ID kopieren", + "CopySupportInfo": "Supportinformationen kopieren", + "CopyToClipboard": "In die Zwischenablage kopieren", + "CopyToWorkOrder": "In Arbeitsauftrag kopieren", + "CopyWiki": "WIKI kopieren", + "Cost": "Kosten", + "Created": "Datensatz erstellt", + "CSRInfoText": "Nachricht an den Kunden", + "CurrencyCode": "Währungscode (ISO 4217)", + "Customer": "Kunde", + "CustomerAccessSettings": "Kundenzugriffseinstellungen", + "CustomerAccessWorkOrderAttachments": "Anhänge des Arbeitsauftragskopfes öffnen", + "CustomerAccessWorkOrderReport": "Kundenversion Arbeitsauftragsbericht", + "CustomerAccessWorkOrderWiki": "Arbeitsauftragskopf anzeigen WIKI", + "CustomerAccountNumber": "Kontonummer", + "CustomerAlertNotes": "Warnhinweise", + "CustomerAllowCreateUnit": "Dem Kunden erlauben, eine Einheit zu erstellen", + "CustomerBillHeadOffice": "Hauptsitz belasten", + "CustomerCustom1": "Angepasstes Feld 1", + "CustomerCustom10": "Angepasstes Feld 10", + "CustomerCustom11": "Angepasstes Feld 11", + "CustomerCustom12": "Angepasstes Feld 12", + "CustomerCustom13": "Angepasstes Feld 13", + "CustomerCustom14": "Angepasstes Feld 14", + "CustomerCustom15": "Angepasstes Feld 15", + "CustomerCustom16": "Angepasstes Feld 16", + "CustomerCustom2": "Angepasstes Feld 2", + "CustomerCustom3": "Angepasstes Feld 3", + "CustomerCustom4": "Angepasstes Feld 4", + "CustomerCustom5": "Angepasstes Feld 5", + "CustomerCustom6": "Angepasstes Feld 6", + "CustomerCustom7": "Angepasstes Feld 7", + "CustomerCustom8": "Angepasstes Feld 8", + "CustomerCustom9": "Angepasstes Feld 9", + "CustomerEmail": "Email", + "CustomerList": "Kunden", + "CustomerName": "Kundenname", + "CustomerNote": "Kundenanmerkung", + "CustomerNoteList": "Kundenanmerkungen", + "CustomerNoteNoteDate": "Datum der Anmerkung", + "CustomerNoteNotes": "Anmerkungen", + "CustomerNotes": "Allgemeine Anmerkungen", + "CustomerNotifySubscription": "Abonnement für Kundenbenachrichtigungen", + "CustomerNotifySubscriptionList":"Abonnements für Kundenbenachrichtigungen", + "CustomerPhone1": "Geschäftstelefon", + "CustomerPhone2": "Fax", + "CustomerPhone3": "Festnetztelefon", + "CustomerPhone4": "Handy Nummer", + "CustomerPhone5": "Pager", + "CustomerServiceRequest": "Kundenserviceanforderung", + "CustomerServiceRequestAcceptToExisting": "Accept to existing work order", + "CustomerServiceRequestAcceptToNew": "Accept to new work order", + "CustomerServiceRequestCustom1": "Angepasstes Feld 1", + "CustomerServiceRequestCustom10": "Angepasstes Feld 10", + "CustomerServiceRequestCustom11": "Angepasstes Feld 11", + "CustomerServiceRequestCustom12": "Angepasstes Feld 12", + "CustomerServiceRequestCustom13": "Angepasstes Feld 13", + "CustomerServiceRequestCustom14": "Angepasstes Feld 14", + "CustomerServiceRequestCustom15": "Angepasstes Feld 15", + "CustomerServiceRequestCustom16": "Angepasstes Feld 16", + "CustomerServiceRequestCustom2": "Angepasstes Feld 2", + "CustomerServiceRequestCustom3": "Angepasstes Feld 3", + "CustomerServiceRequestCustom4": "Angepasstes Feld 4", + "CustomerServiceRequestCustom5": "Angepasstes Feld 5", + "CustomerServiceRequestCustom6": "Angepasstes Feld 6", + "CustomerServiceRequestCustom7": "Angepasstes Feld 7", + "CustomerServiceRequestCustom8": "Angepasstes Feld 8", + "CustomerServiceRequestCustom9": "Angepasstes Feld 9", + "CustomerServiceRequestCustomerReferenceNumber": "Referenznummer", + "CustomerServiceRequestDetails": "Einzelheiten", + "CustomerServiceRequestItemUnitID": "Einheit", + "CustomerServiceRequestList": "Kundendienstanfragen", + "CustomerServiceRequestPriority": "Priorität", + "CustomerServiceRequestPriorityASAP": "So schnell wie möglich", + "CustomerServiceRequestPriorityEmergency": "Notfall", + "CustomerServiceRequestPriorityNotUrgent": "Nicht dringlich", + "CustomerServiceRequestReject": "Anfrage ablehnen", + "CustomerServiceRequestRequestedBy": "Angefordert von", + "CustomerServiceRequestStatus": "Status", + "CustomerServiceRequestStatusAccepted": "Akzeptiert", + "CustomerServiceRequestStatusDeclined": "Abgelehnt", + "CustomerServiceRequestStatusOpen": "Öffnen", + "CustomerServiceRequestTitle": "Anfrage", + "CustomerSignature": "Unterschrift des Kunden", + "CustomerTags":"Kunden-Tags", + "CustomerTechNotes": "Notizen des Servicetechnikers", + "Customize": "Anpassen ...", + "DarkMode": "Dunkler Modus", + "Dashboard": "Dashboard", + "DashboardNotAssigned": "Not assigned", + "DashboardNotScheduled": "Außerplanmäßig", + "DashboardOverdue": "Überfällig", + "DashboardOverdueAll": "Überfällig - alle", + "DashboardReminders": "Reminders", + "DashboardScheduled": "Scheduled", + "DashboardServiceRateQuantityAllUsers":"Servicemenge - alle", + "DashboardOpenCSR":"Offene Serviceanfragen", + "DashboardCountWorkOrdersCreated": "Anzahl der erstellten Arbeitsaufträge", + "DashboardPctWorkOrderCompletedOnTime": "% Arbeitsaufträge pünktlich abgeschlossen", + "DashboardWorkOrderByStatusList":"Liste der Arbeitsaufträge nach Status", + "DashboardWorkOrderStatusCount":"Anzahl der Arbeitsaufträge nach Status", + "DashboardWorkOrderStatusPct":"% der Arbeitsaufträge nach Status", + "Database": "Datenbank", + "DatabaseID": "Datenbank-ID", + "DataListSavedFilter": "Listenfilter", + "DateRange14DayWindow": "Fenster - 14 Tage", + "DateRangeApril": "April", + "DateRangeAugust": "August", + "DateRangeDecember": "Dezember", + "DateRangeFebruary": "Februar", + "DateRangeFuture": "Zukunft", + "DateRangeInTheLastSixMonths": "In den letzten 6 Monaten", + "DateRangeInTheLastThreeMonths": "In den letzten 3 Monaten", + "DateRangeJanuary": "Januar", + "DateRangeJuly": "Juli", + "DateRangeJune": "Juni", + "DateRangeLastMonth": "Monat - letzter", + "DateRangeLastWeek": "Woche - Vorherige", + "DateRangeLastYear": "Jahr - Vorherige", + "DateRangeMarch": "März", + "DateRangeMay": "Mai", + "DateRangeNextMonth": "Monat - nächster", + "DateRangeNextWeek": "Woche - nächste", + "DateRangeNovember": "November", + "DateRangeOctober": "Oktober", + "DateRangePast": "Vergangenheit", + "DateRangePast24Hours": "Letzte 24 Stunden", + "DateRangePast30Days": "Letzte 30 Tage", + "DateRangePast6Hours": "Letzte 6 Stunden", + "DateRangePast7Days": "Letzte 7 Tage", + "DateRangePast90Days": "Letzte 90 Tage", + "DateRangePastYear": "Letztes Jahr (365 Tage)", + "DateRangePreviousYearLastMonth": "Vorjahr - letzter Monat", + "DateRangePreviousYearNextMonth": "Vorjahr - nächster Monat", + "DateRangePreviousYearThisMonth": "Vorjahr - diesen Monat", + "DateRangeSeptember": "September", + "DateRangeThisMonth": "Monat - aktueller", + "DateRangeThisWeek": "Woche - aktuelle", + "DateRangeThisYear": "Jahr - aktuell", + "DateRangeToday": "Heute", + "DateRangeTomorrow": "Morgen", + "DateRangeYesterday": "Gestern", + "DayFriday": "Freitag", + "DayMonday": "Montag", + "DaySaturday": "Samstag", + "DaySunday": "Sonntag", + "DayThursday": "Donnerstag", + "DayTuesday": "Dienstag", + "DayWednesday": "Mittwoch", + "DefaultLanguage": "Standardsprache", + "DefaultReport": "Standardbericht", + "Delete": "Löschen", + "DeletePrompt": "Möchten Sie diesen Datensatz wirklich dauerhaft löschen?", + "DeleteSelected": "Ausgewählte Elemente löschen", + "DeliverAfter": "Liefern nach", + "Description": "Beschreibung", + "DirectNotification": "Direkte Benachrichtigung", + "Download": "Herunterladen", + "DropFilesHere": "Dateien hier ablegen", + "Duplicate": "Duplizieren", + "DuplicateToPM": "Duplikat zur vorbeugenden Wartung", + "DuplicateToQuote": "Zum Angebot duplizieren", + "DuplicateToWorkOrder": "Zum Arbeitsauftrag duplizieren", + "Duration": "Dauer", + "EmailSubject":"E-Mail-Betreff-Vorlage", + "EmailTemplate":"E-Mail-Textvorlage", + "EraseMultipleObjectsWarning": "Warnung: Sie sind dabei, mehrere Objekte dauerhaft zu löschen.\nBist du sicher?", + "ErrorAPI2000": "Server ist geschlossen", + "ErrorAPI2001": "Server ist wegen Wartungsarbeiten geschlossen", + "ErrorAPI2002": "Interner Serverfehler", + "ErrorAPI2003": "Authentifizieren fehlgeschlagen", + "ErrorAPI2004": "Nicht autorisiert", + "ErrorAPI2005": "Das Objekt wurde kürzlich von einem anderen Benutzer geändert und kann nicht gespeichert werden", + "ErrorAPI2006": "Der Server ist wegen Migration geschlossen", + "ErrorAPI2010": "Objekt nicht gefunden", + "ErrorAPI2020": "Die Routen-ID stimmt nicht mit der Objectid überein", + "ErrorAPI2030": "Ungültiger Vorgang", + "ErrorAPI2040": "Nicht genügend Inventar", + "ErrorAPI2200": "Validierungsfehler", + "ErrorAPI2201": "Fehlendes Pflichtfeld", + "ErrorAPI2202": "Maximale Länge überschritten", + "ErrorAPI2203": "Ungültiger Wert", + "ErrorAPI2204": "Pflichtfeld (benutzerdefiniert)", + "ErrorAPI2205": "Erwartete Daten fehlen", + "ErrorAPI2206": "Muss einzigartig sein", + "ErrorAPI2207": "Das Startdatum muss vor dem Enddatum liegen", + "ErrorAPI2208": "Dieses Objekt ist mit anderen verknüpft und kann auf diese Weise nicht geändert werden", + "ErrorAPI2209": "Dieser Wert kann nicht geändert werden", + "ErrorAPI2210": "Untergeordneter Objektfehler", + "ErrorAPI2212": "Pro Arbeitsauftrag ist nur eine vertraglich vereinbarte Einheit zulässig", + "ErrorDBForeignKeyViolation": "Dieses Objekt kann nicht gelöscht werden, weil es mit einem oder mehreren verwandten Objekten verknüpft ist", + "ErrorFieldLengthExceeded": "{0} darf {1} Zeichen nicht überschreiten", + "ErrorFieldValueNotDecimal": "Wert muss eine Zahl sein", + "ErrorFieldValueNotInteger": "Der Wert muss eine ganze Zahl sein", + "ErrorFieldValueNumberGreaterThanMax": "Der Wert muss kleiner als {0} sein", + "ErrorFieldValueNumberLessThanMin": "Der Wert muss größer als {0} sein", + "ErrorGenBeforeTooSmall": "Muss kleiner sein als Wiederholungsintervall", + "ErrorNoMatch": "Werte stimmen nicht überein", + "ErrorPickListQueryInvalid": "Abfrage ungültig - Klicken Sie auf das Hilfesymbol, um weitere Informationen zu erhalten", + "ErrorRepeatIntervalTooSmall": "Mindestens eine Stunde", + "ErrorRequiredFieldEmpty": "{0} ist ein Pflichtfeld. Geben Sie einen Wert für {0} ein.", + "Errors": "Fehler", + "ErrorSecurityAdministratorOnlyMessage": "Sie müssen für diese Aufgabe als 'SuperUser' angemeldet sein", + "ErrorSecurityUserCapacity": "Es sind nicht genügend verfügbare Lizenzen vorhanden, um mit diesem Vorgang fortzufahren", + "ErrorServerUnresponsive": "Der Server reagiert nicht (E17)", + "ErrorStartDateAfterEndDate": "Startdatum muss vor dem Stopp-/Enddatum liegen", + "ErrorUserNotAuthenticated": "Nicht authentifiziert (E16)", + "ErrorUserNotAuthorized": "Nicht autorisiert", + "Evaluate": "Bewerten", + "EvaluateForceEmail": "Setzen Sie alle Beispiel-E-Mail-Adressen auf diese (optional)", + "EvaluationGuide": "Bewertungsleitfaden", + "EvaluationRequestReceived": "Anfrage erhalten, überprüfen Sie Ihre E-Mail zur Bestätigung", + "Event": "Veranstaltung", + "EventAttachmentCreate": "Dateianhang erstellt", + "EventAttachmentDelete": "Dateianhang gelöscht", + "EventAttachmentDownload": "Dateianhang heruntergeladen", + "EventAttachmentModified": "Dateianhang geändert", + "EventCreated": "Erstellt", + "EventDeleted": "Gelöscht", + "EventLicenseFetch": "Lizenz abgerufen", + "EventLicenseTrialRequest": "Testlizenz angefordert", + "EventModified": "Geändert", + "EventResetSerial": "Seriennummer zurückgesetzt", + "EventRetrieved": "Abgerufen", + "EventSeedDatabase": "Datenbank mit Beispieldaten erstellt", + "EventServerStateChange": "Serverstatus geändert", + "EventTextra": "Anmerkungen", + "EventUtilityFileDownload": "Betriebsdatei heruntergeladen", + "ExcludeDaysOfWeek": "Wochentage ausschließen", + "Export": "Exportieren", + "Extensions": "Erweiterungen", + "Failed": "Fehlgeschlagen", + "False": "Falsch", + "FileAttachment": "Dateianhang", + "FileDate": "Datum", + "FileName": "Name", + "FileSize": "Größe", + "Filter": "Filter", + "FilterUsers": "Benutzer filtern", + "Find": "Suchen", + "FindAndReplace": "Suchen und ersetzen", + "First": "Zuerst", + "FormCustom": "Formularanpassung", + "FormFieldEntryRequired": "Pflichtfeld", + "FormFieldVisible": "Sichtbar", + "Forward": "Vorwärts gehen", + "GenerateBefore": "Vorher generieren", + "GenerateSampleData": "Beispieldaten generieren", + "GeoCapture": "Auf aktuellen Standort einstellen", + "GeoView": "Ansicht auf Karte", + "Global": "Global", + "GlobalAllowScheduleConflicts": "Planungskonflikte zulassen", + "GlobalCJKIndex": "CJK-Index verwenden", + "GlobalCJKIndexDescription": "WAHR nur festgelegt, wenn chinesische, japanische oder koreanische Zeichen in Felder und Bezeichnungen eingegeben werden", + "GlobalFilterCaseSensitive": "Bei Filtervorgängen muss die Groß-/Kleinschreibung beachtet werden", + "GlobalLaborSchedUserDfltTimeSpan": "Scheduled / Labor default minutes", + "GlobalLogo": "Geschäftslogos", + "GlobalNextSeeds": "Stellen Sie die nächste Nummer ein", + "GlobalOps": "Global ops", + "GlobalSignatureFooter": "Signature footer", + "GlobalSignatureHeader": "Signature header", + "GlobalSignatureTitle": "Signature title", + "GlobalTaxPartPurchaseID": "Einkaufssteuer für Teile - Standard", + "GlobalTaxPartSaleID": "Umsatzsteuer für Teile - Standard", + "GlobalTaxRateSaleID": "Umsatzsteuer für Service - Standard", + "GlobalTravelDfltTimeSpan": "Travel default minutes", + "GlobalUseInventory": "Bestand verwenden", + "GlobalWorkOrderCompleteByAge": "Standardalter für die Fertigstellung des Arbeitsauftrags", + "GridFilterDialogAndRadioText": "Und-Bedingungen", + "GridFilterDialogOrRadioText": "Oder-Bedingungen", + "GridFilterName": "Filter name", + "GridRowFilterDropDownBlanksItem": "(Kein Wert)", + "GridRowFilterDropDownContains": "Enthält", + "GridRowFilterDropDownDoesNotContain": "Beinhaltet nicht", + "GridRowFilterDropDownEndsWith": "Endet mit", + "GridRowFilterDropDownEquals": "Gleich", + "GridRowFilterDropDownGreaterThan": "Größer als", + "GridRowFilterDropDownGreaterThanOrEqualTo": "Größer als oder gleich", + "GridRowFilterDropDownLessThan": "Kleiner als", + "GridRowFilterDropDownLessThanOrEqualTo": "Kleiner als oder gleich", + "GridRowFilterDropDownNonBlanksItem": "(Hat Wert)", + "GridRowFilterDropDownNotEquals": "Nicht gleich", + "GridRowFilterDropDownStartsWith": "Beginnt mit", + "HaveLicense": "Vorhandene Lizenz verwenden", + "Heading": "Überschrift", + "HeadOffice": "Hauptsitz", + "HeadOfficeAccountNumber": "Kontonummer", + "HeadOfficeCustom1": "Angepasstes Feld 1", + "HeadOfficeCustom10": "Angepasstes Feld 10", + "HeadOfficeCustom11": "Angepasstes Feld 11", + "HeadOfficeCustom12": "Angepasstes Feld 12", + "HeadOfficeCustom13": "Angepasstes Feld 13", + "HeadOfficeCustom14": "Angepasstes Feld 14", + "HeadOfficeCustom15": "Angepasstes Feld 15", + "HeadOfficeCustom16": "Angepasstes Feld 16", + "HeadOfficeCustom2": "Angepasstes Feld 2", + "HeadOfficeCustom3": "Angepasstes Feld 3", + "HeadOfficeCustom4": "Angepasstes Feld 4", + "HeadOfficeCustom5": "Angepasstes Feld 5", + "HeadOfficeCustom6": "Angepasstes Feld 6", + "HeadOfficeCustom7": "Angepasstes Feld 7", + "HeadOfficeCustom8": "Angepasstes Feld 8", + "HeadOfficeCustom9": "Angepasstes Feld 9", + "HeadOfficeEmail": "Email", + "HeadOfficeList": "Hauptsitze", + "HeadOfficeName": "Hauptsitz - Name", + "HeadOfficeNotes": "Anmerkungen", + "HeadOfficePhone1": "Geschäftstelefon", + "HeadOfficePhone2": "Fax", + "HeadOfficePhone3": "Festnetztelefon", + "HeadOfficePhone4": "Handy Nummer", + "HeadOfficePhone5": "Pager", + "HelpAboutSockeye": "Über Sockeye", + "HelpCheckForUpdates": "Nach Aktualisierungen suchen", + "HelpLicense": "Lizenz", + "HelpReleaseKey": "Vorhandene Lizenz erneut verwenden", + "HelpRestore": "So stellen Sie Daten wieder her", + "HelpTechSupport": "Technische Unterstützung", + "History": "Verlauf", + "Home": "Start", + "Hour12": "12 Stunden Zeitformat", + "ID": "Id", + "ImageDescription": "Bildbeschreibung", + "ImageUrl": "Bild-URL", + "Import": "Importieren", + "Include": "Umfassen", + "InsertImage": "Bild einfügen", + "InsertLink": "Link einfügen", + "Interval": "Intervall", + "Inventory": "Bestand", + "InventoryPurchaseOrders": "Einkaufsaufträge", + "InventoryRoleRequired": "Der Benutzer muss für diesen Vorgang eine Inventarrolle haben", + "JobCompleted": "Arbeit erledigt", + "JobCreated": "Serverjob erstellt", + "JobExclusiveWarning": "WARNUNG: Dieser Job unterbricht vorübergehend den gesamten Benutzerzugriff auf den Server", + "JobFailed": "Job fehlgeschlagen", + "KnownPasswordWarning": "GEFAHR: Das aktuelle Passwort ist nicht sicher und sollte sofort geändert werden", + "LanguageCode": "Standard-Sprachcode überschreiben", + "LargeLogo": "Großes Logo", + "Last": "Letzte", + "LastLogin": "Letzte Anmeldezeit", + "LastServiceWorkOrder": "Letzter Arbeitsauftrag", + "LastServiceWorkOrderServiceDate": "Letztes Servicedatum", + "Leave": "Beenden", + "License": "Lizenz", + "LicenseCompanyName": "Name der Firma", + "LicenseContactName": "Kontaktname", + "LicensedOptions": "Lizenzoptionen", + "LicenseEmail": "E-Mail-Addresse", + "LicenseEmailVerficationHint": "(Adresse wird überprüft)", + "LicenseExpiration": "Lizenziert bis", + "LicenseSerial": "Seriennummer der Lizenz", + "LineTotal": "Zeilensumme", + "LinkText": "Text verknüpfen", + "LinkUrl": "Link-URL", + "ListPrice": "Listenpreis", + "Loading": "Wird geladen...", + "LoanUnit": "Leihposten", + "LoanUnitCurrentWorkOrderItemLoan": "Aktueller Arbeitsauftragsposten - Leih-ID", + "LoanUnitCustom1": "Angepasstes Feld 1", + "LoanUnitCustom10": "Angepasstes Feld 10", + "LoanUnitCustom11": "Angepasstes Feld 11", + "LoanUnitCustom12": "Angepasstes Feld 12", + "LoanUnitCustom13": "Angepasstes Feld 13", + "LoanUnitCustom14": "Angepasstes Feld 14", + "LoanUnitCustom15": "Angepasstes Feld 15", + "LoanUnitCustom16": "Angepasstes Feld 16", + "LoanUnitCustom2": "Angepasstes Feld 2", + "LoanUnitCustom3": "Angepasstes Feld 3", + "LoanUnitCustom4": "Angepasstes Feld 4", + "LoanUnitCustom5": "Angepasstes Feld 5", + "LoanUnitCustom6": "Angepasstes Feld 6", + "LoanUnitCustom7": "Angepasstes Feld 7", + "LoanUnitCustom8": "Angepasstes Feld 8", + "LoanUnitCustom9": "Angepasstes Feld 9", + "LoanUnitList": "Leihposten", + "LoanUnitName": "Name", + "LoanUnitNotes": "Anmerkungen", + "LoanUnitRateDay": "Tagesrate", + "LoanUnitRateDayCost": "Tägliche Kosten", + "LoanUnitRateHalfDay": "Halbtagespreis", + "LoanUnitRateHalfDayCost": "Halber Tag Kosten", + "LoanUnitRateHour": "Stundensatz", + "LoanUnitRateHourCost": "Stundenkosten", + "LoanUnitRateMonth": "Monatliche Rate", + "LoanUnitRateMonthCost": "Monatliche Kosten", + "LoanUnitRateNone": "-", + "LoanUnitRateWeek": "Wöchentliche Rate", + "LoanUnitRateWeekCost": "Wöchentliche Kosten", + "LoanUnitRateYear": "Jahresrate", + "LoanUnitRateYearCost": "Jährliche Kosten", + "LoanUnitSerial": "Seriennummer", + "LoanUnitShadowUnit": "Schatteneinheit", + "Log": "Protokoll", + "LogFile": "Logdatei", + "Logout": "Abmelden", + "MaintenanceExpired": "Wartung abgelaufen", + "MaintenanceExpiredNote": "Das Support- und Update-Abonnement ist jetzt abgelaufen.\nSockeye kann nicht aktualisiert werden und der Support ist nicht mehr verfügbar", + "MapUrlTemplate": "Karten-URL-Vorlage", + "MediumLogo": "Mittelgroßes Logo", + "Memo": "Memo", + "MemoCustom1": "Angepasstes Feld 1", + "MemoCustom10": "Angepasstes Feld 10", + "MemoCustom11": "Angepasstes Feld 11", + "MemoCustom12": "Angepasstes Feld 12", + "MemoCustom13": "Angepasstes Feld 13", + "MemoCustom14": "Angepasstes Feld 14", + "MemoCustom15": "Angepasstes Feld 15", + "MemoCustom16": "Angepasstes Feld 16", + "MemoCustom2": "Angepasstes Feld 2", + "MemoCustom3": "Angepasstes Feld 3", + "MemoCustom4": "Angepasstes Feld 4", + "MemoCustom5": "Angepasstes Feld 5", + "MemoCustom6": "Angepasstes Feld 6", + "MemoCustom7": "Angepasstes Feld 7", + "MemoCustom8": "Angepasstes Feld 8", + "MemoCustom9": "Angepasstes Feld 9", + "MemoForward": "Weiterleiten", + "MemoFromID": "Von", + "MemoList": "Memos", + "MemoMessage": "Nachricht", + "MemoRe": "AW:", + "MemoReplied": "Beantwortet", + "MemoReply": "Antworten", + "MemoSent": "Gesendet", + "MemoSubject": "Betreff", + "MemoToID": "An", + "MemoViewed": "Angezeigt", + "MenuHelp": "Hilfe", + "MetricAllocatedMemory": "Zugewiesener (MiB)", + "MetricAttachmentsCount": "Angefügte Dateien (anzahl)", + "MetricAttachmentsMB": "Angefügte Dateien (MiB)", + "MetricAvailableDiskSpace": "Verfügbarer Platz (MiB)", + "MetricBackupMB": "Sicherungsdateien (MiB)", + "MetricCPUMemory": "CPU / Arbeitsspeicher", + "MetricDBSize": "Datenbankgröße (MiB)", + "MetricFileStorage": "Dateispeicher", + "MetricPrivateBytes": "Private bytes (MiB)", + "MetricTopTablesSize": "Top-Datenbanktabellen (KiB)", + "MetricWorkingSet": "Arbeitssatz (MiB)", + "More": "Mehr...", + "MoveSelected": "Verschieben Sie ausgewählte Elemente", + "Name": "Name", + "NativeDateTimeInput": "Verwenden Sie die standardmäßigen Eingabesteuerelemente für Datum und Uhrzeit des Browsers", + "NetPrice": "Netto", + "New": "Neu", + "NewLicenseInstalled": "Neue Lizenz wurde installiert. Sie werden jetzt abgemeldet.", + "NewLicenseNotFound": "Derzeit ist keine neue Lizenz verfügbar", + "NewLogin": "Neuer Anmeldename", + "NewPassword": "Neues Kennwort", + "NewStatus": "Neuer Status", + "NextPMNumber": "Nächste vorbeugende Wartung", + "NextPONumber": "Nächste Bestellung", + "NextQuoteNumber": "Nächste Angebotsnummer", + "NextWorkorderNumber": "Nächster Arbeitsauftrag", + "NoColor": "Keine Farbe", + "NoData": "Keine Daten", + "NoFeaturesAvailable": "Für Ihr Konto sind keine Funktionen aktiviert", + "NoLicenseTitle": "Nicht lizenziert", + "NoResults": "Keine Ergebnisse", + "Notification": "Benachrichtigung", + "Notifications": "Benachrichtigungen", + "NotificationDeliveryLog":"Benutzerbenachrichtigungen", + "NotificationCustomerDeliveryLog":"Kundenbenachrichtigungen", + "NotifyDeliveryAddress": "An Adresse liefern", + "NotifyDeliveryMethod": "Benachrichtigungsversandmethode", + "NotifyDeliveryMethodApp": "In Anwendung liefern", + "NotifyDeliveryMethodSMTP": "An E-Mail-Adresse liefern", + "NotifyEventBackupStatus": "Status der Sicherung", + "NotifyEventContractExpiring": "Vertrag läuft aus", + "NotifyEventCSRAccepted": "Kundendienstanfrage angenommen", + "NotifyEventCSRRejected": "Kundendienstanfrage abgelehnt", + "NotifyEventCustomerServiceImminent": "Servicetermin", + "NotifyEventCustomerServiceImminentMessage": "Sie haben einen bevorstehenden Servicetermin in {0}\nVerwenden Sie den folgenden Link, um die Details anzuzeigen:", + "NotifyEventGeneralNotification": "Allgemeine Benachrichtigung", + "NotifyEventNotifyHealthCheck": "Integritätsprüfung des Benachrichtigungssystems", + "NotifyEventObjectAge": "Objektalter seit der Erstellung", + "NotifyEventObjectCreated": "Objekt erstellt", + "NotifyEventObjectDeleted": "Objekt gelöscht", + "NotifyEventObjectModified": "Objekt geändert", + "NotifyEventOutsideServiceOverdue": "Drittanbieter-Service überfällig", + "NotifyEventOutsideServiceReceived": "Service von Drittanbietern abgeschlossen, Gerät erhalten", + "NotifyEventPartRequestReceived": "Angeforderter Teil erhalten", + "NotifyEventPMGenerationFailed": "Vorbeugende Wartung Generatorausfall", + "NotifyEventPMInsufficientInventory": "Vorbeugende Wartung unzureichender Lagerbestand", + "NotifyEventPMStopGeneratingDateReached": "Stoppdatum für vorbeugende Wartung erreicht", + "NotifyEventQuoteStatusAge": "Angebotsstatus für den Zeitraum unverändert", + "NotifyEventQuoteStatusChange": "Angebotsstatus geändert", + "NotifyEventReminderImminent": "Erinnerung - unmittelbar bevorstehend", + "NotifyEventReviewImminent": "Überprüfung - unmittelbar bevorstehend", + "NotifyEventScheduledOnWorkorder": "Geplant auf Arbeitsauftrag", + "NotifyEventScheduledOnWorkorderImminent": "Arbeitsauftragsplan steht kurz bevor", + "NotifyEventServerOperationsProblem": "Problem mit dem Serverbetrieb", + "NotifyEventType": "Benachrichtigungsereignis", + "NotifyEventUnitMeterReadingMultipleExceeded": "Zählerstand überschritten (Vielfaches)", + "NotifyEventUnitWarrantyExpiry": "Die Garantie für das Gerät läuft ab", + "NotifyEventWorkorderCompleted": "Arbeitsauftrag abgeschlossen", + "NotifyEventWorkorderCompletedStatusOverdue": "Arbeitsauftrag nicht rechtzeitig fertig", + "NotifyEventWorkorderCreatedForCustomer": "Arbeitsauftrag für Kunden erstellt", + "NotifyEventWorkorderStatusAge": "Arbeitsauftragsstatus für die Zeitspanne unverändert", + "NotifyEventWorkorderStatusChange": "Arbeitsauftragsstatus gesetzt", + "NotifyEventWorkorderTotalExceedsThreshold": "Der Gesamtbetrag des Arbeitsauftrags überschreitet den Schwellenwert", + "NotifyFromAddress": "SMTP-Benachrichtigungsadresse", + "NotifyMailSecurityNone": "Keiner", + "NotifyMailSecuritySSLTLS": "SSL\\TLS", + "NotifyMailSecurityStartTls": "StartTLS", + "NotifyQueue": "Warteschlange für Benachrichtigungszustellung", + "NotifySubscription": "Benachrichtigungsabonnement", + "NotifySubscriptionLinkText": "Benachrichtigungseinstellung ändern:", + "NotifySubscriptionList": "Benachrichtigungsabonnements", + "NotifySubscriptionPendingSpan": "Vor Ereignis benachrichtigen", + "NoType": "Kein Typ", + "Now": "Jetzt", + "Object": "Objekt", + "ObjectCustomFieldCustomGrid": "Anpassbare Felder", + "OK": "OK", + "OldPassword": "Altes Kennwort", + "Open": "Öffnen", + "Operations": "Server Vorgänge", + "OpsNotificationSettings": "Benachrichtigungseinstellungen", + "OpsTestJob": "Testjob senden", + "OutsideServiceList": "Fremdleistungen - Liste", + "PageOfPageText": "{0}-{1} von {2}", + "Part": "Teil", + "PartAlternativeWholesalerID": "Alternativer Großhändler", + "PartAlternativeWholesalerNumber": "Alternativer Großhändler - Nummer", + "PartAssembly": "Teilebaugruppe", + "PartAssemblyCustom1": "Angepasstes Feld 1", + "PartAssemblyCustom10": "Angepasstes Feld 10", + "PartAssemblyCustom11": "Angepasstes Feld 11", + "PartAssemblyCustom12": "Angepasstes Feld 12", + "PartAssemblyCustom13": "Angepasstes Feld 13", + "PartAssemblyCustom14": "Angepasstes Feld 14", + "PartAssemblyCustom15": "Angepasstes Feld 15", + "PartAssemblyCustom16": "Angepasstes Feld 16", + "PartAssemblyCustom2": "Angepasstes Feld 2", + "PartAssemblyCustom3": "Angepasstes Feld 3", + "PartAssemblyCustom4": "Angepasstes Feld 4", + "PartAssemblyCustom5": "Angepasstes Feld 5", + "PartAssemblyCustom6": "Angepasstes Feld 6", + "PartAssemblyCustom7": "Angepasstes Feld 7", + "PartAssemblyCustom8": "Angepasstes Feld 8", + "PartAssemblyCustom9": "Angepasstes Feld 9", + "PartAssemblyList": "Teilebaugruppen", + "PartAssemblyName": "Teilebaugruppe - Name", + "PartAssemblyNotes": "Anmerkungen", + "PartByWarehouseInventoryList": "Teilebestand", + "PartByWarehouseInventoryMinStockLevel": "Mindestniveau", + "PartByWarehouseInventoryQtyOnOrderCommitted": "Zugesagte bestellte Menge", + "PartByWarehouseInventoryQuantityOnOrder": "Bestellt", + "PartByWarehouseInventoryReorderQuantity": "Nachbestellungsmenge", + "PartCost": "Kosten", + "PartCustom1": "Angepasstes Feld 1", + "PartCustom10": "Angepasstes Feld 10", + "PartCustom11": "Angepasstes Feld 11", + "PartCustom12": "Angepasstes Feld 12", + "PartCustom13": "Angepasstes Feld 13", + "PartCustom14": "Angepasstes Feld 14", + "PartCustom15": "Angepasstes Feld 15", + "PartCustom16": "Angepasstes Feld 16", + "PartCustom2": "Angepasstes Feld 2", + "PartCustom3": "Angepasstes Feld 3", + "PartCustom4": "Angepasstes Feld 4", + "PartCustom5": "Angepasstes Feld 5", + "PartCustom6": "Angepasstes Feld 6", + "PartCustom7": "Angepasstes Feld 7", + "PartCustom8": "Angepasstes Feld 8", + "PartCustom9": "Angepasstes Feld 9", + "PartDescription": "Teilebeschreibung", + "PartInventoryAdjustment": "Teilebestandberichtigung", + "PartInventoryBalance": "Vorrätig", + "PartInventoryId": "Inventar-ID", + "PartInventoryDataList": "Teileinventar", + "PartInventoryTransaction": "Inventartransaktion", + "PartInventoryTransactionDescription": "Beschreibung", + "PartInventoryTransactionEntryDate": "Datum", + "PartInventoryTransactionList": "Inventurtransaktionen", + "PartInventoryTransactionQuantity": "Menge", + "PartInventoryTransactionSource": "Transaktionsquelle", + "PartList": "Teile", + "PartManufacturerID": "Hersteller", + "PartManufacturerNumber": "Herstellernummer", + "PartName": "Teilname", + "PartNotes": "Anmerkungen", + "PartRestockRequiredByVendorList": "Teileaufstockung durch Lieferant erforderlich", + "PartRetail": "Einzelhandel", + "PartSerial": "Serienteil", + "PartSerialNumbersAvailable": "Verfügbare Seriennummern", + "PartSerialWarehouseID": "Teilelager", + "PartStockingLevels": "Mindestbestand an Teilen", + "PartUPC": "EAN", + "PartWarehouse": "Teilelager", + "PartWarehouseCustom1": "Angepasstes Feld 1", + "PartWarehouseCustom10": "Angepasstes Feld 10", + "PartWarehouseCustom11": "Angepasstes Feld 11", + "PartWarehouseCustom12": "Angepasstes Feld 12", + "PartWarehouseCustom13": "Angepasstes Feld 13", + "PartWarehouseCustom14": "Angepasstes Feld 14", + "PartWarehouseCustom15": "Angepasstes Feld 15", + "PartWarehouseCustom16": "Angepasstes Feld 16", + "PartWarehouseCustom2": "Angepasstes Feld 2", + "PartWarehouseCustom3": "Angepasstes Feld 3", + "PartWarehouseCustom4": "Angepasstes Feld 4", + "PartWarehouseCustom5": "Angepasstes Feld 5", + "PartWarehouseCustom6": "Angepasstes Feld 6", + "PartWarehouseCustom7": "Angepasstes Feld 7", + "PartWarehouseCustom8": "Angepasstes Feld 8", + "PartWarehouseCustom9": "Angepasstes Feld 9", + "PartWarehouseList": "Teilelager", + "PartWarehouseName": "Teilelager - Name", + "PartWarehouseNotes": "Anmerkungen", + "PartWholesalerID": "Großhändler", + "PartWholesalerNumber": "Großhändlernummer", + "PasswordResetMessageBody": "Hallo {user_name},\n\nIhr Online-Konto für den Dienst steht Ihnen nach dem Festlegen Ihres Kennworts zur Verfügung.\nSie können den Kennwort für die nächsten 48 Stunden über den folgenden Link festlegen.\n\nStellen Sie Ihr Kennwort ein: {action_link}\n\nWenn Sie kein Zurücksetzen des Kontos oder Kennworts angefordert haben, ignorieren Sie diese E-Mail.\n\nDanke,\n{registered_to}", + "PasswordResetMessageTitle": "Ihr Online-Konto ist fertig", + "PickListTemplate": "Auswahllistenvorlage", + "PickListTemplates": "Wählen Sie Listenvorlagen aus", + "PM": "Vorbeugende Wartung", + "PMItem": "Element für vorbeugende Wartung", + "PMItemExpense": "Vorbeugende Wartung - Aufwendungen", + "PMItemLabor": "Vorbeugende Wartung - Arbeit", + "PMItemLoan": "Vorbeugende Wartung - Ausleihe", + "PMItemOutsideService":"Vorbeugende Wartung - Fremdleistung", + "PMItemPart":"Vorbeugende Wartung - Teile", + "PMItemScheduledUser":"Vorbeugende Wartung - Geplante Benutzer", + "PMItemTravel":"Vorbeugende Wartung - Reisen", + "PMItemTask":"Vorbeugende Wartung - Aufgaben", + "PMItemUnit":"Vorbeugende Wartung - Einheiten", + "PMList": "Liste der vorbeugenden Wartung", + "PMNextServiceDate": "Nächster Service am", + "PMNextWoGenerateDate": "Nächstes Generierungsdatum", + "PMSerialNumber": "Nummer", + "PMStopGeneratingDate": "Erstellung beenden am", + "Price": "Preis", + "PriceOverride": "Preisüberschreibung", + "Print": "Drucken", + "Processed":"Verarbeitet", + "ProcessingJob": "Serverjob verarbeiten", + "Project": "Projekt", + "ProjectAccountNumber": "Kontonummer", + "ProjectCustom1": "Angepasstes Feld 1", + "ProjectCustom10": "Angepasstes Feld 10", + "ProjectCustom11": "Angepasstes Feld 11", + "ProjectCustom12": "Angepasstes Feld 12", + "ProjectCustom13": "Angepasstes Feld 13", + "ProjectCustom14": "Angepasstes Feld 14", + "ProjectCustom15": "Angepasstes Feld 15", + "ProjectCustom16": "Angepasstes Feld 16", + "ProjectCustom2": "Angepasstes Feld 2", + "ProjectCustom3": "Angepasstes Feld 3", + "ProjectCustom4": "Angepasstes Feld 4", + "ProjectCustom5": "Angepasstes Feld 5", + "ProjectCustom6": "Angepasstes Feld 6", + "ProjectCustom7": "Angepasstes Feld 7", + "ProjectCustom8": "Angepasstes Feld 8", + "ProjectCustom9": "Angepasstes Feld 9", + "ProjectDateCompleted": "Abgeschlossen am", + "ProjectDateStarted": "Begonnen am", + "ProjectList": "Projekte", + "ProjectName": "Projektname", + "ProjectNotes": "Anmerkungen", + "ProjectProjectOverseerID": "Projektleiter", + "PurchaseLicense": "Kaufen Sie eine Lizenz", + "PurchaseOrder": "Einkaufsauftrag", + "PurchaseOrderCustom1": "Angepasstes Feld 1", + "PurchaseOrderCustom10": "Angepasstes Feld 10", + "PurchaseOrderCustom11": "Angepasstes Feld 11", + "PurchaseOrderCustom12": "Angepasstes Feld 12", + "PurchaseOrderCustom13": "Angepasstes Feld 13", + "PurchaseOrderCustom14": "Angepasstes Feld 14", + "PurchaseOrderCustom15": "Angepasstes Feld 15", + "PurchaseOrderCustom16": "Angepasstes Feld 16", + "PurchaseOrderCustom2": "Angepasstes Feld 2", + "PurchaseOrderCustom3": "Angepasstes Feld 3", + "PurchaseOrderCustom4": "Angepasstes Feld 4", + "PurchaseOrderCustom5": "Angepasstes Feld 5", + "PurchaseOrderCustom6": "Angepasstes Feld 6", + "PurchaseOrderCustom7": "Angepasstes Feld 7", + "PurchaseOrderCustom8": "Angepasstes Feld 8", + "PurchaseOrderCustom9": "Angepasstes Feld 9", + "PurchaseOrderDropShipToCustomerID": "Streckengeschäft an Kunden", + "PurchaseOrderExpectedReceiveDate": "Erwartet", + "PurchaseOrderItem": "Einkaufsauftragsposten", + "PurchaseOrderItemLineTotal": "Zeilensumme", + "PurchaseOrderItemList": "Bestellpositionen", + "PurchaseOrderItemNetTotal": "Netto - Gesamt", + "PurchaseOrderItemPartName": "Teil - Name", + "PurchaseOrderItemPartNumber": "Teilenummer", + "PurchaseOrderItemPartRequestedByID": "Angefordert von", + "PurchaseOrderItemPurchaseOrderCost": "EA-Kosten", + "PurchaseOrderItemQuantityOrdered": "Bestellte Menge", + "PurchaseOrderItemQuantityReceived": "Empfangene Menge", + "PurchaseOrderItemSerialNumbers": "Seriennummer", + "PurchaseOrderItemUIOrderedFrom": "Bestellt von", + "PurchaseOrderItemVendorPartNumber": "Lieferantennummer", + "PurchaseOrderItemWorkOrderNumber": "Arbeitsauftragsnummer", + "PurchaseOrderNotes": "Anmerkungen", + "PurchaseOrderOrderedDate": "Bestellt am", + "PurchaseOrderPONumber": "EA-Nummer", + "PurchaseOrderReceiptItemQuantityReceivedErrorInvalid": "Der Wert darf positiv sein und darf nicht mehr als den bestellten Betrag betragen", + "PurchaseOrderReceiptItemReceiptCost": "Erhaltene Kosten", + "PurchaseOrderReceiptReceivedDate": "Empfangen am", + "PurchaseOrderReceiptText1": "Text1", + "PurchaseOrderReceiptText2": "Text2", + "PurchaseOrderReferenceNumber": "Referenznummer", + "PurchaseOrderStatus": "Einkaufsauftragsstatus", + "PurchaseOrderStatusClosedFullReceived": "Geschlossen - vollständig empfangen", + "PurchaseOrderStatusClosedNoneReceived": "Geschlossen - nichts empfangen", + "PurchaseOrderStatusClosedPartialReceived": "Geschlossen - teilweise empfangen", + "PurchaseOrderStatusOpenNotYetOrdered": "Offen - noch nicht bestellt", + "PurchaseOrderStatusOpenOrdered": "Offen - bestellt", + "PurchaseOrderStatusOpenPartialReceived": "Offen - teilweise empfangen", + "PurchaseOrderUICopyToPurchaseOrder": "Auf EA kopieren", + "PurchaseOrderUIRestockList": "Aufstockungsliste", + "PurchaseOrderVendorMemo": "Lieferant - Memo", + "QuantityRequired": "Erforderliche Menge", + "Quote": "Angebot", + "QuoteDateApproved": "Genehmigt", + "QuoteDateSubmitted": "Übermittelt", + "QuoteIntroduction": "Einleitungstext", + "QuoteItem": "Angebot artikel", + "QuoteItemExpense": "Angebot - Aufwendungen", + "QuoteItemLabor": "Angebot - Arbeit", + "QuoteItemLoan": "Angebot - Ausleihe", + "QuoteItemOutsideService":"Angebot - Fremdleistung", + "QuoteItemPart":"Angebot - Teile", + "QuoteItemScheduledUser":"Angebot - Geplante Benutzer", + "QuoteItemTravel":"Angebot - Reisen", + "QuoteItemTask":"Angebot - Aufgaben", + "QuoteItemUnit":"Angebot - Einheiten", + "QuoteList": "Angebote", + "QuotePreparedByID": "Vorbereitet von Benutzer", + "QuoteQuoteRequestDate": "Angefordert", + "QuoteQuoteStatusType": "Status", + "QuoteSerialNumber": "Nummer", + "QuoteStatusList": "Angebotsstatusliste", + "QuoteValidUntilDate": "Gültig bis", + "RateAccountNumber": "Kontonummer", + "RateCharge": "Einzelhandelsgebühr", + "RateContractRate": "Vertragssatz", + "RateUnitChargeDescriptionID": "Einheitengebühr - Beschreibung", + "ReadOnly": "Schreibgeschützt", + "ReceiveAll": "Erhalte alle", + "RecentWorkOrders": "Letzte Arbeitsaufträge", + "RecordHistory": "Datensatzverlauf", + "Refresh": "Aktualisieren ...", + "Region": "Region", + "RegisteredUser": "Registrierter Benutzer", + "Reminder": "Erinnerung", + "ReminderColor": "Farbe", + "ReminderCustom1": "Angepasstes Feld 1", + "ReminderCustom10": "Angepasstes Feld 10", + "ReminderCustom11": "Angepasstes Feld 11", + "ReminderCustom12": "Angepasstes Feld 12", + "ReminderCustom13": "Angepasstes Feld 13", + "ReminderCustom14": "Angepasstes Feld 14", + "ReminderCustom15": "Angepasstes Feld 15", + "ReminderCustom16": "Angepasstes Feld 16", + "ReminderCustom2": "Angepasstes Feld 2", + "ReminderCustom3": "Angepasstes Feld 3", + "ReminderCustom4": "Angepasstes Feld 4", + "ReminderCustom5": "Angepasstes Feld 5", + "ReminderCustom6": "Angepasstes Feld 6", + "ReminderCustom7": "Angepasstes Feld 7", + "ReminderCustom8": "Angepasstes Feld 8", + "ReminderCustom9": "Angepasstes Feld 9", + "ReminderList": "Erinnerungen", + "ReminderName": "Name", + "ReminderNotes": "Anmerkungen", + "ReminderStartDate": "Start", + "ReminderStopDate": "Ende", + "Remove": "Entfernen", + "RemoveRoles": "Wer kann entfernen", + "RenderingReport": "Berichterstellung im Gange", + "RepeatInterval": "Wiederholungsintervall", + "Replace": "Ersetzen", + "Report": "Bericht", + "ReportDesignReport": "Bericht bearbeiten", + "ReportDisplayHeaderFooter": "Kopf- und Fußzeile anzeigen", + "ReportEditorData": "Beispieldaten", + "ReportEditorMobileWarning": "Editor wird auf Mobilgeräten nicht unterstützt", + "ReportEditorProperties": "Eigenschaften", + "ReportFooterTemplate": "Fußzeilenvorlage", + "ReportHeaderTemplate": "Header-Vorlage", + "ReportIncludeAllWorkOrderItemDescendants": "Alle Nachkommen der Arbeitsauftragsposition einschließen", + "ReportLandscape": "Landschaft", + "ReportList": "Berichtsvorlagen", + "ReportMarginOptionsBottom": "Unterer Rand", + "ReportMarginOptionsLeft": "Linker Rand", + "ReportMarginOptionsRight": "Rechter Rand", + "ReportMarginOptionsTop": "Oberer Rand", + "ReportName": "Name", + "ReportNotes": "Anmerkungen", + "ReportPaperFormat": "Papierformat", + "ReportPdfOptions": "PDF-Optionen", + "ReportPreferCSSPageSize": "Bevorzugen Sie die CSS-Seitengröße", + "ReportPrintBackground": "Hintergrund drucken", + "ReportRenderTimeOut": "Die Verarbeitung des Berichts dauerte zu viele Minuten, das Limit ist auf {0} festgelegt", + "ReportScale": "Skalieren", + "ReportTemplate": "Vorlage", + "RequestEvaluationLicense": "Fordern Sie eine Evaluierungslizenz an", + "ResetToDefault": "Auf Standard zurücksetzen", + "Review": "Rezension", + "ReviewAssignedByUserId": "Zugeteilt von", + "ReviewCompletedDate": "Fertigstellungstermin", + "ReviewCompletionNotes": "Abschlussnotizen", + "ReviewCustom1": "Angepasstes Feld 1", + "ReviewCustom10": "Angepasstes Feld 10", + "ReviewCustom11": "Angepasstes Feld 11", + "ReviewCustom12": "Angepasstes Feld 12", + "ReviewCustom13": "Angepasstes Feld 13", + "ReviewCustom14": "Angepasstes Feld 14", + "ReviewCustom15": "Angepasstes Feld 15", + "ReviewCustom16": "Angepasstes Feld 16", + "ReviewCustom2": "Angepasstes Feld 2", + "ReviewCustom3": "Angepasstes Feld 3", + "ReviewCustom4": "Angepasstes Feld 4", + "ReviewCustom5": "Angepasstes Feld 5", + "ReviewCustom6": "Angepasstes Feld 6", + "ReviewCustom7": "Angepasstes Feld 7", + "ReviewCustom8": "Angepasstes Feld 8", + "ReviewCustom9": "Angepasstes Feld 9", + "ReviewDate": "Überprüfungsdatum", + "ReviewList": "Überprüfungsliste", + "ReviewName": "Name", + "ReviewNotes": "Notizen", + "ReviewOverDue": "Überfällig", + "ReviewUserId": "Zugewiesen an", + "RowsPerPage": "Zeilen pro Seite", + "Save": "Speichern", + "SaveACopy": "Kopie speichern", + "SaveRecordToProceed": "Dieser Datensatz muss gespeichert werden, um fortzufahren", + "Schedule": "Zeitplan", + "Schedule4Day": "4 Tage", + "ScheduleCategory": "Team - Tagesansicht", + "ScheduleConflict": "Terminkonflikt", + "ScheduleDay": "Tag", + "ScheduleDayView": "1-Tag-Ansicht", + "ScheduleEditReminder": "Bearbeiten Sie die ausgewählte Erinnerung", + "ScheduleEditScheduleableUserGroup": "Planbare Benutzergruppen bearbeiten", + "ScheduleEditWorkOrder": "Ausgewählten Arbeitsauftrag bearbeiten", + "ScheduleFirstHour": "Erste Stunde zur Anzeige in der Tagesansicht", + "ScheduleMonth": "Monat", + "ScheduleOptions": "Zeitplaneinstellungen", + "ScheduleShowTypes": "Elemente zum Anzeigen", + "ScheduleWeek": "Woche", + "ScheduleWOColorFrom": "Farbquelle für Arbeitsauftrag", + "SchemaVersion": "Schemaversion", + "Search": "Suchen", + "SeedLevel": "Beispieldatengröße", + "SeedLevelHuge": "Riesig - sehr großer Datensatz (ca. 1 Stunde Verarbeitungszeit)", + "SeedLevelLarge": "Groß - mehrere Regionen mit vollem Personal (ca. 10 Minuten Bearbeitungszeit)", + "SeedLevelMedium": "Mittel - viele Servicetechniker und Supportmitarbeiter (ca. 5 Minuten Bearbeitungszeit)", + "SeedLevelSmall": "Kleiner Serviceshop (ca. 1 Minute Bearbeitungszeit)", + "SelectAlternateAddress": "Stellen Sie eine alternative Adresse ein", + "SelectedItems": "Ausgewählte Elemente", + "SelectItem": "Auswählen", + "SelectRoles": "Wer kann auswählen", + "SendEvaluationRequest": "Anfrage senden", + "SendPasswordResetCode": "E-Mail zum Zurücksetzen des Passworts senden", + "Sequence": "Reihenfolge", + "Server": "Server", + "ServerAddress": "Serveradresse", + "ServerJob": "Serverjob", + "ServerJobs": "Auftragswarteschlange", + "ServerLog": "Server-Protokoll", + "ServerMetrics": "Server-Metriken", + "ServerProfiler": "Profiler", + "ServerState": "Serverstatus", + "ServerStateLoginRestricted": "Anmeldung aufgrund der Serverstatuseinstellung eingeschränkt", + "ServerStateMigrateMode": "Server-Migrationsmodus", + "ServerStateOpen": "Öffnen", + "ServerStateOps": "Nur Systembetrieb", + "ServerStateReason": "Grund", + "ServerTime": "Serverzeit", + "Service": "Service", + "ServiceHistory": "Serviceverlauf", + "ServicePreventiveMaintenance": "W/I", + "ServiceRate": "Service-Rate", + "ServiceRateCustom1": "Angepasstes Feld 1", + "ServiceRateCustom10": "Angepasstes Feld 10", + "ServiceRateCustom11": "Angepasstes Feld 11", + "ServiceRateCustom12": "Angepasstes Feld 12", + "ServiceRateCustom13": "Angepasstes Feld 13", + "ServiceRateCustom14": "Angepasstes Feld 14", + "ServiceRateCustom15": "Angepasstes Feld 15", + "ServiceRateCustom16": "Angepasstes Feld 16", + "ServiceRateCustom2": "Angepasstes Feld 2", + "ServiceRateCustom3": "Angepasstes Feld 3", + "ServiceRateCustom4": "Angepasstes Feld 4", + "ServiceRateCustom5": "Angepasstes Feld 5", + "ServiceRateCustom6": "Angepasstes Feld 6", + "ServiceRateCustom7": "Angepasstes Feld 7", + "ServiceRateCustom8": "Angepasstes Feld 8", + "ServiceRateCustom9": "Angepasstes Feld 9", + "ServiceRateList": "Serviceraten", + "ServiceRateNotes": "Notizen", + "SetLoginPassword": "Anmeldenamen und Kennwort festlegen", + "Settings":"Einstellungen", + "ShutDownServer": "Server herunterfahren", + "SmallLogo": "Kleines Logo", + "SmtpAccount": "SMTP-Serverkonto", + "SmtpDeliveryActive": "SMTP-Benachrichtigung aktiv", + "SmtpPassword": "SMTP-Serverkennwort", + "SmtpServerAddress": "SMTP-Serveradresse", + "SmtpServerPort": "SMTP-Server-Port", + "SoftDelete": "Zum Löschen markieren", + "SoftDeleteAll": "* alle * zum Löschen markieren", + "Sort": "Sortieren", + "StartAttachmentMaintenanceJob": "Starten Sie den Wartungsjob für Dateianhänge", + "StartEvaluation": "Starten Sie die Testauswertung", + "StartJob": "Job starten", + "Statistics": "Statistik", + "Status": "Status", + "StatusColor": "Farbe", + "StatusCompleted": "Ist ein abgeschlossener Status", + "StatusLocked": "Ist ein Sperrstatus", + "StatusName": "Name", + "StatusNotes": "Anmerkungen", + "StopWords1": "sie sind soll sollen sollst sollt sonst soweit sowie und unser unsere unter vom von vor wann warum was weiter weitere wenn wer werde werden werdet weshalb wie wieder wieso wir wird wirst wo woher wohin zu zum zur über", + "StopWords2": "aber als am an auch auf aus bei bin bis bist da dadurch daher darum das daß dass dein deine dem den der des dessen deshalb die dies dieser dieses doch dort du durch ein eine einem einen einer eines er es euer eure für hatte hatten", + "StopWords3": "hattest hattet hier hinter ich ihr ihre im in ist ja jede jedem jeden jeder jedes jener jenes jetzt kann kannst können könnt machen mein meine mit muß mußt musst müssen müßt nach nachdem nein nicht nun oder seid sein seine sich", + "StopWords4": "?", + "StopWords5": "?", + "StopWords6": "?", + "StopWords7": "?", + "SupportedUntil": "Support- und Aktualisierungsdatum", + "Table": "Tabelle", + "Tag": "Etikett", + "TaggedWith": "Etikett mit", + "Tags": "Etiketts", + "Task": "Aufgabe", + "TaskGroup": "Aufgabengruppe", + "TaskGroupList": "Aufgabengruppen", + "TaskGroupName": "Aufgabengruppe - Name", + "TaskGroupNotes": "Anmerkungen", + "TaskList": "Aufgaben", + "Tax": "Steuer", + "TaxAAmt": "Steuerbetrag 'A'", + "TaxBAmt": "Steuerbetrag 'B'", + "TaxCode": "Steuercode", + "TaxCodeCustom1": "Angepasstes Feld 1", + "TaxCodeCustom10": "Angepasstes Feld 10", + "TaxCodeCustom11": "Angepasstes Feld 11", + "TaxCodeCustom12": "Angepasstes Feld 12", + "TaxCodeCustom13": "Angepasstes Feld 13", + "TaxCodeCustom14": "Angepasstes Feld 14", + "TaxCodeCustom15": "Angepasstes Feld 15", + "TaxCodeCustom16": "Angepasstes Feld 16", + "TaxCodeCustom2": "Angepasstes Feld 2", + "TaxCodeCustom3": "Angepasstes Feld 3", + "TaxCodeCustom4": "Angepasstes Feld 4", + "TaxCodeCustom5": "Angepasstes Feld 5", + "TaxCodeCustom6": "Angepasstes Feld 6", + "TaxCodeCustom7": "Angepasstes Feld 7", + "TaxCodeCustom8": "Angepasstes Feld 8", + "TaxCodeCustom9": "Angepasstes Feld 9", + "TaxCodeDefault": "Fehler: Weil dieser Steuercode in den globalen Einstellungen ein Standardwert ist, kann er nicht gelöscht oder deaktiviert werden", + "TaxCodeList": "Steuercodes", + "TaxCodeName": "Steuercode - Name", + "TaxCodeNotes": "Anmerkungen", + "TaxCodeTaxA": "Steuer \"A\"", + "TaxCodeTaxB": "Steuer \"B\"", + "TaxCodeTaxOnTax": "Steuer auf Steuer", + "TechSignature": "Unterschrift des Technikers", + "TemplateTokens":"Vorlagen-Token", + "TestSMTPSettings": "Testnachricht senden", + "TestToAddress": "Test senden an", + "ThankYouForEvaluating": "Vielen Dank, dass Sie Sockeye ausprobiert haben. Verwenden Sie die folgenden Links, um Sockeye zu erkunden und festzustellen, ob es zu Ihrer Organisation passt.", + "TimedOut": "Zeitüberschreitung", + "TimeSpan":"Zeitspanne", + "TimeSpanDays": "Tage", + "TimeSpanHours": "Stunden", + "TimeSpanMinutes": "Minuten", + "TimeSpanMonths": "Monate", + "TimeSpanSeconds": "Sekunden", + "TimeSpanYears": "Jahre", + "TimeStamp": "Zeitstempel", + "TimeToCompletion": "Zeit bis zur Fertigstellung", + "TimeZone": "Standardzeitzone außer Kraft setzen", + "TooManyResults": "Zu viele Ergebnisse, um alle anzuzeigen", + "Total": "Summe", + "Translation": "Übersetzung", + "TranslationDisplayText": "Standardanzeigetext", + "TranslationKey": "Schlüssel", + "TranslationList": "Übersetzungen", + "TravelRate": "Reiserate", + "TravelRateCustom1": "Angepasstes Feld 1", + "TravelRateCustom10": "Angepasstes Feld 10", + "TravelRateCustom11": "Angepasstes Feld 11", + "TravelRateCustom12": "Angepasstes Feld 12", + "TravelRateCustom13": "Angepasstes Feld 13", + "TravelRateCustom14": "Angepasstes Feld 14", + "TravelRateCustom15": "Angepasstes Feld 15", + "TravelRateCustom16": "Angepasstes Feld 16", + "TravelRateCustom2": "Angepasstes Feld 2", + "TravelRateCustom3": "Angepasstes Feld 3", + "TravelRateCustom4": "Angepasstes Feld 4", + "TravelRateCustom5": "Angepasstes Feld 5", + "TravelRateCustom6": "Angepasstes Feld 6", + "TravelRateCustom7": "Angepasstes Feld 7", + "TravelRateCustom8": "Angepasstes Feld 8", + "TravelRateCustom9": "Angepasstes Feld 9", + "TravelRateList": "Reiseratenliste", + "TravelRateNotes": "Notizen", + "TrialSeeder": "Probesaatgutdaten", + "True": "Wahr", + "TypeToSearchOrAdd": "Beginnen Sie mit der Eingabe, um zu suchen oder hinzuzufügen", + "UiFieldDataType": "Felddatentyp", + "UiFieldDataTypesCurrency": "Geld", + "UiFieldDataTypesDateOnly": "Datum", + "UiFieldDataTypesDateTime": "Datum und Zeit", + "UiFieldDataTypesDecimal": "Dezimal", + "UiFieldDataTypesInteger": "Ganzzahl", + "UiFieldDataTypesText": "Text", + "UiFieldDataTypesTimeOnly": "Zeit", + "UiFieldDataTypesTrueFalse": "Wahr/Falsch", + "Undelete": "Wiederherstellen", + "Unit": "Einheit", + "UnitBoughtHere": "Hier gekauft", + "UnitCustom1": "Angepasstes Feld 1", + "UnitCustom10": "Angepasstes Feld 10", + "UnitCustom11": "Angepasstes Feld 11", + "UnitCustom12": "Angepasstes Feld 12", + "UnitCustom13": "Angepasstes Feld 13", + "UnitCustom14": "Angepasstes Feld 14", + "UnitCustom15": "Angepasstes Feld 15", + "UnitCustom16": "Angepasstes Feld 16", + "UnitCustom2": "Angepasstes Feld 2", + "UnitCustom3": "Angepasstes Feld 3", + "UnitCustom4": "Angepasstes Feld 4", + "UnitCustom5": "Angepasstes Feld 5", + "UnitCustom6": "Angepasstes Feld 6", + "UnitCustom7": "Angepasstes Feld 7", + "UnitCustom8": "Angepasstes Feld 8", + "UnitCustom9": "Angepasstes Feld 9", + "UnitDescription": "Beschreibung", + "UnitList": "Kunden Einheiten", + "UnitMetered": "Gemessene Einheit", + "UnitMeterReading": "Einheitenzählerstand", + "UnitMeterReadingDescription": "Zähler - Beschreibung", + "UnitMeterReadingList": "Einheitenzählerstand - Liste", + "UnitMeterReadingMeter": "Einheitenzählerstand", + "UnitMeterReadingMeterDate": "Zählerstand am", + "UnitMeterReadingWorkOrderItemID": "Auf Arbeitsauftrag abgelesener Zähler", + "UnitModel": "Einheitenmodell", + "UnitModelCustom1": "Angepasstes Feld 1", + "UnitModelCustom10": "Angepasstes Feld 10", + "UnitModelCustom11": "Angepasstes Feld 11", + "UnitModelCustom12": "Angepasstes Feld 12", + "UnitModelCustom13": "Angepasstes Feld 13", + "UnitModelCustom14": "Angepasstes Feld 14", + "UnitModelCustom15": "Angepasstes Feld 15", + "UnitModelCustom16": "Angepasstes Feld 16", + "UnitModelCustom2": "Angepasstes Feld 2", + "UnitModelCustom3": "Angepasstes Feld 3", + "UnitModelCustom4": "Angepasstes Feld 4", + "UnitModelCustom5": "Angepasstes Feld 5", + "UnitModelCustom6": "Angepasstes Feld 6", + "UnitModelCustom7": "Angepasstes Feld 7", + "UnitModelCustom8": "Angepasstes Feld 8", + "UnitModelCustom9": "Angepasstes Feld 9", + "UnitModelDiscontinued": "Nicht mehr verfügbar", + "UnitModelDiscontinuedDate": "Nicht mehr verfügbar seit", + "UnitModelIntroducedDate": "Eingeführt am", + "UnitModelLifeTimeWarranty": "Lebenslange Garantie", + "UnitModelName": "Einheitenmodell - Name", + "UnitModelNotes": "Anmerkungen", + "UnitModels": "Einheitenmodelle", + "UnitModelUPC": "EAN", + "UnitModelVendorID": "Unit model vendor", + "UnitModelWarrantyLength": "Garantiedauer", + "UnitModelWarrantyTerms": "Garantiebedingungen", + "UnitNotes": "Anmerkungen", + "UnitOfMeasure": "Maßeinheit", + "UnitOverrideLength": "Länge überschreiben", + "UnitOverrideLifeTime": "Lebensdauer überschreiben", + "UnitOverrideWarranty": "Garantie überschreiben", + "UnitOverrideWarrantyTerms": "Garantiebedingungen überschreiben", + "UnitParentUnitID": "Übergeordnete Einheit dieser Einheit", + "UnitPurchasedDate": "Kaufdatum", + "UnitPurchaseFromID": "Gekauft von", + "UnitReceipt": "Eingangsnummer", + "UnitReplacedByUnitID": "Ersetzt von Einheit", + "UnitSerial": "Seriennummer", + "UnitText1": "Text1", + "UnitText2": "Text2", + "UnitText3": "Text3", + "UnitText4": "Text4", + "UnitUnitHasOwnAddress": "Einheit hat eigene Adresse", + "UnitWarrantyInfo": "Garantieinformationen", + "UpdateAvailable": "Update ist verfügbar. Jetzt installieren?", + "Upload": "Hochladen", + "User": "Benutzer", + "UserColor": "Benutzerfarbe", + "UserCountExceeded": "Server wegen Überschreitung der Lizenz für aktive Servicetyp-Benutzer geschlossen", + "UserCustom1": "Angepasstes Feld 1", + "UserCustom10": "Angepasstes Feld 10", + "UserCustom11": "Angepasstes Feld 11", + "UserCustom12": "Angepasstes Feld 12", + "UserCustom13": "Angepasstes Feld 13", + "UserCustom14": "Angepasstes Feld 14", + "UserCustom15": "Angepasstes Feld 15", + "UserCustom16": "Angepasstes Feld 16", + "UserCustom2": "Angepasstes Feld 2", + "UserCustom3": "Angepasstes Feld 3", + "UserCustom4": "Angepasstes Feld 4", + "UserCustom5": "Angepasstes Feld 5", + "UserCustom6": "Angepasstes Feld 6", + "UserCustom7": "Angepasstes Feld 7", + "UserCustom8": "Angepasstes Feld 8", + "UserCustom9": "Angepasstes Feld 9", + "UserEmailAddress": "Benutzer - E-Mail-Adresse", + "UserEmployeeNumber": "Arbeitnehmernummer", + "UserInterfaceSettings": "Benutzeroberfläche", + "UserList": "Benutzer", + "UserLogin": "Anmeldename", + "UserNotes": "Anmerkungen", + "UserOptions": "Benutzeroptionen", + "UserPageAddress": "Pager-Adresse", + "UserPhone1": "Telefon 1", + "UserPhone2": "Telefon 2", + "UserPreferences": "Benutzervoreinstellungen", + "UserSettings": "Benutzereinstellungen", + "UserTimeZoneOffset": "UTC-Abweichung", + "UserType": "Benutzertyp", + "UserTypeCustomer": "Benutzer beim Kunden", + "UserTypeHeadOffice": "Benutzer am Hauptsitz des Kunden", + "UserTypeNotService": "Kein Diensttypbenutzer", + "UserTypeService": "Dienstnutzer", + "UserTypeServiceContractor": "Subunternehmer", + "Vendor": "Lieferant", + "VendorAccountNumber": "Kontonummer", + "VendorAlertNotes": "Warnhinweise", + "VendorContact": "Contact", + "VendorContactNotes": "Other contacts", + "VendorCustom1": "Angepasstes Feld 1", + "VendorCustom10": "Angepasstes Feld 10", + "VendorCustom11": "Angepasstes Feld 11", + "VendorCustom12": "Angepasstes Feld 12", + "VendorCustom13": "Angepasstes Feld 13", + "VendorCustom14": "Angepasstes Feld 14", + "VendorCustom15": "Angepasstes Feld 15", + "VendorCustom16": "Angepasstes Feld 16", + "VendorCustom2": "Angepasstes Feld 2", + "VendorCustom3": "Angepasstes Feld 3", + "VendorCustom4": "Angepasstes Feld 4", + "VendorCustom5": "Angepasstes Feld 5", + "VendorCustom6": "Angepasstes Feld 6", + "VendorCustom7": "Angepasstes Feld 7", + "VendorCustom8": "Angepasstes Feld 8", + "VendorCustom9": "Angepasstes Feld 9", + "VendorEmail": "Email", + "VendorList": "Lieferanten", + "VendorName": "Lieferant - Name", + "VendorNotes": "Anmerkungen", + "VendorPhone1": "Geschäftstelefon", + "VendorPhone2": "Fax", + "VendorPhone3": "Festnetztelefon", + "VendorPhone4": "Handy Nummer", + "VendorPhone5": "Pager", + "Version": "Version", + "ViewEULA": "Lizenzvertrag anzeigen", + "ViewServerConfiguration": "Serverinformation", + "Warranty": "Garantie", + "WarrantyExpires": "Gültig bis", + "WebAddress": "Webadresse", + "Welcome": "Willkommen in Sockeye", + "WhoWillBeNotified":"Wer wird benachrichtigt?", + "WorkOrder": "Arbeitsauftrag", + "WorkOrderAge": "Age", + "WorkOrderCloseByDate": "Fertigstellungsdatum", + "WorkOrderContactCustomerHeadOfficeTaggedWith": "Nur Arbeitsauftrag, Kontakt, Kunde oder Zentrale mit einem dieser etikett zulassen", + "WorkOrderConvertAllScheduledUsersToLabor": "Alles in Arbeit umwandeln", + "WorkOrderConvertScheduledUserToLabor": "Geplanten Benutzer in \"Arbeit\" kopieren", + "WorkOrderCustom1": "Angepasstes Feld 1", + "WorkOrderCustom10": "Angepasstes Feld 10", + "WorkOrderCustom11": "Angepasstes Feld 11", + "WorkOrderCustom12": "Angepasstes Feld 12", + "WorkOrderCustom13": "Angepasstes Feld 13", + "WorkOrderCustom14": "Angepasstes Feld 14", + "WorkOrderCustom15": "Angepasstes Feld 15", + "WorkOrderCustom16": "Angepasstes Feld 16", + "WorkOrderCustom2": "Angepasstes Feld 2", + "WorkOrderCustom3": "Angepasstes Feld 3", + "WorkOrderCustom4": "Angepasstes Feld 4", + "WorkOrderCustom5": "Angepasstes Feld 5", + "WorkOrderCustom6": "Angepasstes Feld 6", + "WorkOrderCustom7": "Angepasstes Feld 7", + "WorkOrderCustom8": "Angepasstes Feld 8", + "WorkOrderCustom9": "Angepasstes Feld 9", + "WorkOrderCustomerContactName": "Ansprechpartner", + "WorkOrderCustomerReferenceNumber": "Kundenreferenznummer", + "WorkOrderCustomerTaggedWith": "Arbeitsauftrag oder Kunde mit einem dieser etikette", + "WorkOrderErrorLocked": "Der Arbeitsauftrag ist derzeit gesperrt und kann nicht geändert werden", + "WorkOrderFromPMID": "Übergeordnete W/I", + "WorkOrderFromQuoteID": "Übergeordnetes Angebot", + "WorkOrderGenerateUnit": "Generate unit from selected part", + "WorkOrderInternalReferenceNumber": "Interne Referenznummer", + "WorkOrderInvoiceNumber": "Rechnungsnummer", + "WorkOrderItem": "Arbeitsauftragsposten", + "WorkOrderItemCustom1": "Angepasstes Feld 1", + "WorkOrderItemCustom10": "Angepasstes Feld 10", + "WorkOrderItemCustom11": "Angepasstes Feld 11", + "WorkOrderItemCustom12": "Angepasstes Feld 12", + "WorkOrderItemCustom13": "Angepasstes Feld 13", + "WorkOrderItemCustom14": "Angepasstes Feld 14", + "WorkOrderItemCustom15": "Angepasstes Feld 15", + "WorkOrderItemCustom16": "Angepasstes Feld 16", + "WorkOrderItemCustom2": "Angepasstes Feld 2", + "WorkOrderItemCustom3": "Angepasstes Feld 3", + "WorkOrderItemCustom4": "Angepasstes Feld 4", + "WorkOrderItemCustom5": "Angepasstes Feld 5", + "WorkOrderItemCustom6": "Angepasstes Feld 6", + "WorkOrderItemCustom7": "Angepasstes Feld 7", + "WorkOrderItemCustom8": "Angepasstes Feld 8", + "WorkOrderItemCustom9": "Angepasstes Feld 9", + "WorkOrderItemExpense": "Arbeitsauftragsposten - versch. Aufwendungen", + "WorkOrderItemExpenseChargeAmount": "Gebühr - Betrag", + "WorkOrderItemExpenseChargeTaxCodeID": "Gebühr - Steuercode", + "WorkOrderItemExpenseChargeToCustomer": "Kunden belasten", + "WorkOrderItemExpenseDescription": "Beschreibung", + "WorkOrderItemExpenseList": "Versch. Aufwendungen - Posten", + "WorkOrderItemExpenseName": "Versch. Aufw. - Zusammenfassung", + "WorkOrderItemExpenseReimburseUser": "Benutzer rückerstatten", + "WorkOrderItemExpenseTaxPaid": "Gezahlte Steuern", + "WorkOrderItemExpenseTotalCost": "Gesamtkosten", + "WorkOrderItemExpenseUserID": "Benutzer", + "WorkOrderItemLabor": "Arbeitsauftragsposten - Arbeit", + "WorkOrderItemLaborList": "Arbeitsposten", + "WorkOrderItemLaborNoChargeQuantity": "Ohne Gebühren - Menge", + "WorkOrderItemLaborPrice": "Nettopreis", + "WorkOrderItemLaborServiceDetails": "Servicedetails", + "WorkOrderItemLaborServiceRateID": "Servicesatz", + "WorkOrderItemLaborServiceRateQuantity": "Servicesatz - Menge", + "WorkOrderItemLaborServiceStartDate": "Service - Startdatum und -zeit", + "WorkOrderItemLaborServiceStopDate": "Service - Enddatum und -zeit", + "WorkOrderItemLaborTaxRateSaleID": "Umsatzsteuer", + "WorkOrderItemLaborUserID": "Benutzer", + "WorkOrderItemList": "Posten", + "WorkOrderItemLoan": "Arbeitsauftragsposten - Ausleihe", + "WorkOrderItemLoanDueDate": "Rückgabe fällig am", + "WorkOrderItemLoanList": "Leihposten", + "WorkOrderItemLoanNotes": "Anmerkungen", + "WorkOrderItemLoanOutDate": "Ausgeliehen", + "WorkOrderItemLoanQuantity": "Rate quantity", + "WorkOrderItemLoanRate": "Rate", + "WorkOrderItemLoanReturnDate": "Zurückgegeben", + "WorkOrderItemLoanTaxCodeID": "Umsatzsteuer", + "WorkOrderItemLoanUnit": "Leihposten", + "WorkOrderItemOutsideService": "Fremdleistung", + "WorkOrderItemOutsideServiceDateETA": "Vor. Ank.-Termin", + "WorkOrderItemOutsideServiceDateReturned": "Zurückgegeben am", + "WorkOrderItemOutsideServiceDateSent": "Gesendet am", + "WorkOrderItemOutsideServiceList": "Externe Serviceliste", + "WorkOrderItemOutsideServiceNotes": "Anmerkungen", + "WorkOrderItemOutsideServiceRepairCost": "Reparaturkosten", + "WorkOrderItemOutsideServiceRepairPrice": "Reparaturpreis", + "WorkOrderItemOutsideServiceRMANumber": "RMA-Nummer", + "WorkOrderItemOutsideServiceShippingCost": "Versandkosten", + "WorkOrderItemOutsideServiceShippingPrice": "Versandpreis", + "WorkOrderItemOutsideServiceTrackingNumber": "Verfolgungsnummer", + "WorkOrderItemOutsideServiceVendorSentToID": "Gesendet an", + "WorkOrderItemOutsideServiceVendorSentViaID": "Gesendet mit", + "WorkOrderItemPart": "Arbeitsauftragsposten - Teil", + "WorkOrderItemPartDescription": "Beschreibung", + "WorkOrderItemPartList": "Teileposten", + "WorkOrderItemPartPartID": "Teil", + "WorkOrderItemPartPartWarehouseID": "Lager", + "WorkOrderItemPartQuantity": "Menge", + "WorkOrderItemPartRealizeSuggested": "Realisieren Sie vorgeschlagene Teilemengen", + "WorkOrderItemPartRequest": "Arbeitsauftragsposten - Teileanforderung", + "WorkOrderItemPartRequestList": "Teileanforderungen", + "WorkOrderItemPartRequestMore": "Fordern Sie {n} mehr an", + "WorkOrderItemPartRequestOnOrder": "Bestellt", + "WorkOrderItemPartRequestPartID": "Teil", + "WorkOrderItemPartRequestPartWarehouseID": "Lager", + "WorkOrderItemPartRequestQuantity": "Menge", + "WorkOrderItemPartRequestReceived": "Empfangen", + "WorkOrderItemPartRequests": "Teileanforderungen", + "WorkOrderItemPartSuggestedQuantity": "Empfohlene Menge", + "WorkOrderItemPartTaxPartSaleID": "Umsatzsteuer", + "WorkOrderItemPartUsed": "Beim Service verwendet", + "WorkOrderItemPriorityColor": "Farbe", + "WorkOrderItemPriorityID": "Priorität", + "WorkOrderItemPriorityList": "Prioritätsliste für Arbeitsauftragspositionen", + "WorkOrderItemPriorityName": "Name", + "WorkOrderItemRequestDate": "Angefordert am", + "WorkOrderItemScheduledUser": "Arbeitsauftragsposten - geplanter Benutzer", + "WorkOrderItemScheduledUserEstimatedQuantity": "Geschätzte Menge", + "WorkOrderItemScheduledUserList": "Geplante Benutzer - Posten", + "WorkOrderItemScheduledUserServiceRateID": "Empfohlener Satz", + "WorkOrderItemScheduledUserStartDate": "Startdatum und -zeit", + "WorkOrderItemScheduledUserStopDate": "Enddatum und -zeit", + "WorkOrderItemScheduledUserUserID": "Benutzer", + "WorkOrderItemStatusColor": "Farbe", + "WorkOrderItemStatusList": "Artikelstatusliste", + "WorkOrderItemStatusName": "Name", + "WorkOrderItemStatusNotes": "Anmerkungen", + "WorkOrderItemSummary": "Postenzusammenfassung", + "WorkOrderItemTags": "Etikett Arbeitsauftragsposten", + "WorkOrderItemTask": "Arbeitsauftragsposten - Aufgabe", + "WorkOrderItemTaskCompletedDate": "Fertigstellungstermin", + "WorkOrderItemTaskCompletionTypeComplete": "Abgeschlossen", + "WorkOrderItemTaskCompletionTypeFailed": "Gescheitert", + "WorkOrderItemTaskCompletionTypeIncomplete": "Aufgabe", + "WorkOrderItemTaskCompletionTypeNotApplicable": "n/v", + "WorkOrderItemTasks": "Aufgaben", + "WorkOrderItemTaskTaskID": "Aufgabe", + "WorkOrderItemTaskUser": "Benutzer", + "WorkOrderItemTaskWorkOrderItemTaskCompletionType": "Status", + "WorkOrderItemTechNotes": "Serviceanmerkungen", + "WorkOrderItemTravel": "Arbeitsauftragsposten - Reisen", + "WorkOrderItemTravelDetails": "Reisedetails", + "WorkOrderItemTravelDistance": "Distanz", + "WorkOrderItemTravelList": "Reiseposten", + "WorkOrderItemTravelNoChargeQuantity": "Ohne Gebühren - Menge", + "WorkOrderItemTravelRateQuantity": "Menge", + "WorkOrderItemTravelServiceRateID": "Reisesatz", + "WorkOrderItemTravelStartDate": "Startdatum", + "WorkOrderItemTravelStopDate": "Stoppdatum", + "WorkOrderItemTravelTaxRateSaleID": "Umsatzsteuer", + "WorkOrderItemTravelUserID": "Benutzer", + "WorkOrderItemUnit": "Arbeitsauftragsposition - Einheit", + "WorkOrderItemUnitCustom1": "Angepasstes Feld 1", + "WorkOrderItemUnitCustom10": "Angepasstes Feld 10", + "WorkOrderItemUnitCustom11": "Angepasstes Feld 11", + "WorkOrderItemUnitCustom12": "Angepasstes Feld 12", + "WorkOrderItemUnitCustom13": "Angepasstes Feld 13", + "WorkOrderItemUnitCustom14": "Angepasstes Feld 14", + "WorkOrderItemUnitCustom15": "Angepasstes Feld 15", + "WorkOrderItemUnitCustom16": "Angepasstes Feld 16", + "WorkOrderItemUnitCustom2": "Angepasstes Feld 2", + "WorkOrderItemUnitCustom3": "Angepasstes Feld 3", + "WorkOrderItemUnitCustom4": "Angepasstes Feld 4", + "WorkOrderItemUnitCustom5": "Angepasstes Feld 5", + "WorkOrderItemUnitCustom6": "Angepasstes Feld 6", + "WorkOrderItemUnitCustom7": "Angepasstes Feld 7", + "WorkOrderItemUnitCustom8": "Angepasstes Feld 8", + "WorkOrderItemUnitCustom9": "Angepasstes Feld 9", + "WorkOrderItemUnitList": "Einheiten", + "WorkOrderItemUnitNotes": "Anmerkungen", + "WorkOrderItemUnitTags": "Etikett Arbeitsauftragsposition - Einheit", + "WorkOrderItemWarrantyService": "Garantieservice", + "WorkOrderItemWorkOrderStatusID": "Arbeitsauftragsposten - Status", + "WorkOrderList": "Servicearbeitsaufträge", + "WorkOrderOnsite": "Vor Ort", + "WorkOrderSerialNumber": "Nummer", + "WorkOrderServiceDate": "Servicedatum", + "WorkOrderStatus": "Arbeitsauftragsstatus", + "WorkOrderStatusList": "Arbeitsauftragsstatusangaben", + "WorkOrderSummary": "Zusammenfassung" +} \ No newline at end of file diff --git a/server/resource/en.json b/server/resource/en.json new file mode 100644 index 0000000..992d5d8 --- /dev/null +++ b/server/resource/en.json @@ -0,0 +1,1495 @@ +{ + "Accounting": "Accounting", + "Active": "Active", + "Activity": "Activity", + "Add": "Add", + "AddMultipleUnits": "Add multiple units", + "AddNewUnit": "Add new Unit", + "Address": "Address", + "AddressCity": "City", + "AddressCopyToPhysical": "Copy to street address", + "AddressCopyToPostal": "Copy to postal address", + "AddressCountry": "Country", + "AddressDeliveryAddress": "Street", + "AddressLatitude": "Latitude", + "AddressLongitude": "Longitude", + "AddressPostalCity": "City (mail)", + "AddressPostalCountry": "Country (mail)", + "AddressPostalDeliveryAddress": "Address (mail)", + "AddressPostalPostal": "Postal / ZIP code (mail)", + "AddressPostalStateProv": "State / Province (mail)", + "AddressStateProv": "State or Province", + "AddressTypePhysical": "Street Address", + "AddressTypePostal": "Postal Address", + "AdminEraseDatabase": "Erase database", + "AdminEraseDatabaseLastWarning": "Warning: This is your last chance to avoid erasing all the business data permanently.\r\nAre you sure you want to erase all business data?", + "AdminEraseDatabaseWarning": "Warning: you are about to permanently erase all business data in Sockeye.\r\nAre you sure?", + "Administration": "Administration", + "AdministrationGlobalSettings": "Global settings", + "AlertNotes": "Alert notes", + "AllItemsInList": "All items in list", + "AnyUser": "All users", + "AppendTasks": "Append tasks", + "ApplyUnitContract": "Apply this Unit's Contract '{0}' to Work order?", + "AreYouSureBackupNow": "Server will be locked during backup. Are you sure?", + "AreYouSureShutDown": "Are you certain you want to shut down the server?", + "AreYouSureUnsavedChanges": "Are you sure you want to leave? You'll lose your unsaved changes.", + "AttachFile": "Attach file", + "AttachmentExists": "File exists", + "AttachmentFileName": "File name", + "AttachmentNotes": "Notes", + "Attachments": "Attached files", + "AttachReport":"Attach report", + "AuthConnectAppManualEntry": "Having trouble scanning the code? Enter the following manually into your authenticator app:", + "AuthConnectAppSubTitle": "Using an authenticator app such as Google Authenticator, Duo, Microsoft Authenticator, Authy etc, scan the QR code. It will display a 6 digit pass code which you need to enter below.", + "AuthConnectAppTitle": "Connect your app", + "AuthConnectCompleted": "Two-Factor authentication is now enabled", + "AuthDisableTwoFactor": "Disable Two-Factor authentication", + "AuthEnterPin": "Enter 6-digit code", + "AuthorizationRoleAccounting": "Accounting", + "AuthorizationRoleAll": "All roles", + "AuthorizationRoleBizAdmin": "Business administration", + "AuthorizationRoleBizAdminRestricted": "Business administration - restricted", + "AuthorizationRoleCustomer": "Customer user", + "AuthorizationRoleCustomerRestricted": "Customer user - restricted", + "AuthorizationRoleInventory": "Inventory", + "AuthorizationRoleInventoryRestricted": "Inventory - restricted", + "AuthorizationRoleNoRole": "No role", + "AuthorizationRoleOpsAdmin": "System operations", + "AuthorizationRoleOpsAdminRestricted": "System operations - restricted", + "AuthorizationRoles": "Authorization roles", + "AuthorizationRoleSales": "Sales", + "AuthorizationRoleSalesRestricted": "Sales - restricted", + "AuthorizationRoleService": "Service manager", + "AuthorizationRoleServiceRestricted": "Service manager - restricted", + "AuthorizationRoleSubContractor": "Subcontractor", + "AuthorizationRoleSubContractorRestricted": "Subcontractor - restricted", + "AuthorizationRoleTech": "Service technician", + "AuthorizationRoleTechRestricted": "Service technician - restricted", + "AuthPinInvalid": "Code not valid", + "AuthTwoFactor": "Two-Factor Authentication", + "AuthTwoFactorDisabled": "Two-Factor authentication is now disabled", + "AuthVerifyCode": "Verify code", + "AvailableSpace": "Available space", + "AyaFileFileTooLarge": "File size exceeds limit of {0}", + "SockeyeServerURL": "Sockeye server URL", + "SockType": "Type", + "Backup": "Backup", + "BackupAttachments": "Backup attachment files", + "BackupDeleteOld": "Deleting old backup files", + "BackupFiles": "Backup files", + "BackupLast": "Last backup", + "BackupNow": "Backup now", + "BackupSetsToKeep": "Number of backups to keep", + "BackupSettings": "Backup settings", + "BackupTime": "Backup time", + "Backward": "Backward", + "BatchDeleteJob": "Batch delete job", + "BatchJob": "Batch job", + "BizMetrics": "Metrics", + "Browser": "Browser", + "BusinessSettings": "Business", + "Cancel": "Cancel", + "CheckForLicense": "Install license", + "Close": "Exit", + "Columns": "Columns", + "CompanyEmail": "Email", + "CompanyInformation": "Company information", + "CompanyPhone1": "Business phone number", + "CompanyPhone2": "Phone 2", + "ConfirmPassword": "Confirm password", + "ConfirmUpdatePartCost": "Update part cost from received cost?", + "ConnectionSecurity": "SMTP connection security", + "Contact": "Contact", + "ContactCustomerHeadOfficeTaggedWith": "Restrict to Contact, Customer or Head Office with any of these tags", + "ContactPhone": "Contact phone", + "Contacts": "Contacts", + "ContactTitle": "Contact Title", + "Contract": "Contract", + "ContractAdjustment": "Adjustment", + "ContractCustom1": "Custom1", + "ContractCustom10": "Custom10", + "ContractCustom11": "Custom11", + "ContractCustom12": "Custom12", + "ContractCustom13": "Custom13", + "ContractCustom14": "Custom14", + "ContractCustom15": "Custom15", + "ContractCustom16": "Custom16", + "ContractCustom2": "Custom2", + "ContractCustom3": "Custom3", + "ContractCustom4": "Custom4", + "ContractCustom5": "Custom5", + "ContractCustom6": "Custom6", + "ContractCustom7": "Custom7", + "ContractCustom8": "Custom8", + "ContractCustom9": "Custom9", + "ContractDefaultAdjustments": "Default price adjustments", + "ContractDefaultResponseTime": "Response time", + "ContractDiscountParts": "Discount Applied to All Parts", + "ContractExpires": "Contract expires", + "ContractList": "Contracts", + "ContractName": "Contract Name", + "ContractNotes": "Notes", + "ContractOverrideType": "Price adjustment type", + "ContractOverrideTypeMarkup": "Cost plus percentage", + "ContractOverrideTypePriceDiscount": "Price minus percentage", + "ContractRate": "Contract rate", + "ContractRateList": "Contract Rates", + "ContractServiceRatesOnly": "Allow only these service rates", + "ContractTaggedAdjustments": "Tagged price adjustments", + "ContractTravelRatesOnly": "Allow only these travel rates", + "Copy": "Copy", + "CopyAttachments": "Copy attachments", + "CopyDbId": "Copy database id", + "CopySupportInfo": "Copy support information", + "CopyToClipboard": "Copy to clipboard", + "CopyToWorkOrder": "Copy to work order", + "CopyWiki": "Copy WIKI", + "Cost": "Cost", + "Created": "Record Created", + "CSRInfoText": "Message to Customer", + "CurrencyCode": "Currency code (ISO 4217)", + "Customer": "Customer", + "CustomerAccessSettings": "Customer access", + "CustomerAccessWorkOrderAttachments": "Open work order header attachments", + "CustomerAccessWorkOrderReport": "Customer work order report", + "CustomerAccessWorkOrderWiki": "View work order header WIKI", + "CustomerAccountNumber": "Account Number", + "CustomerAlertNotes": "Alert notes", + "CustomerAllowCreateUnit": "Allow Customer to create Unit", + "CustomerBillHeadOffice": "Bill Head Office", + "CustomerCustom1": "Custom1", + "CustomerCustom10": "Custom10", + "CustomerCustom11": "Custom11", + "CustomerCustom12": "Custom12", + "CustomerCustom13": "Custom13", + "CustomerCustom14": "Custom14", + "CustomerCustom15": "Custom15", + "CustomerCustom16": "Custom16", + "CustomerCustom2": "Custom2", + "CustomerCustom3": "Custom3", + "CustomerCustom4": "Custom4", + "CustomerCustom5": "Custom5", + "CustomerCustom6": "Custom6", + "CustomerCustom7": "Custom7", + "CustomerCustom8": "Custom8", + "CustomerCustom9": "Custom9", + "CustomerEmail": "Email", + "CustomerList": "Customers", + "CustomerName": "Customer name", + "CustomerNote": "Customer note", + "CustomerNoteList": "Customer Notes", + "CustomerNoteNoteDate": "Note Date", + "CustomerNoteNotes": "Notes", + "CustomerNotes": "General Notes", + "CustomerNotifySubscription": "Customer notify subscription", + "CustomerNotifySubscriptionList":"Customer notifications", + "CustomerPhone1": "Business", + "CustomerPhone2": "Fax", + "CustomerPhone3": "Home", + "CustomerPhone4": "Mobile", + "CustomerPhone5": "Pager", + "CustomerServiceRequest": "Customer service request", + "CustomerServiceRequestAcceptToExisting": "Accept to existing work order", + "CustomerServiceRequestAcceptToNew": "Accept to new work order", + "CustomerServiceRequestCustom1": "Custom1", + "CustomerServiceRequestCustom10": "Custom10", + "CustomerServiceRequestCustom11": "Custom11", + "CustomerServiceRequestCustom12": "Custom12", + "CustomerServiceRequestCustom13": "Custom13", + "CustomerServiceRequestCustom14": "Custom14", + "CustomerServiceRequestCustom15": "Custom15", + "CustomerServiceRequestCustom16": "Custom16", + "CustomerServiceRequestCustom2": "Custom2", + "CustomerServiceRequestCustom3": "Custom3", + "CustomerServiceRequestCustom4": "Custom4", + "CustomerServiceRequestCustom5": "Custom5", + "CustomerServiceRequestCustom6": "Custom6", + "CustomerServiceRequestCustom7": "Custom7", + "CustomerServiceRequestCustom8": "Custom8", + "CustomerServiceRequestCustom9": "Custom9", + "CustomerServiceRequestCustomerReferenceNumber": "Reference Number", + "CustomerServiceRequestDetails": "Details", + "CustomerServiceRequestItemUnitID": "Unit", + "CustomerServiceRequestList": "Customer service requests", + "CustomerServiceRequestPriority": "Priority", + "CustomerServiceRequestPriorityASAP": "ASAP", + "CustomerServiceRequestPriorityEmergency": "Emergency", + "CustomerServiceRequestPriorityNotUrgent": "Not urgent", + "CustomerServiceRequestReject": "Reject service request", + "CustomerServiceRequestRequestedBy": "Requested by", + "CustomerServiceRequestStatus": "Status", + "CustomerServiceRequestStatusAccepted": "Accepted", + "CustomerServiceRequestStatusDeclined": "Rejected", + "CustomerServiceRequestStatusOpen": "Open", + "CustomerServiceRequestTitle": "Request", + "CustomerSignature": "Customer signature", + "CustomerTags":"Customer tags", + "CustomerTechNotes": "Service tech notes", + "Customize": "Customize....", + "DarkMode": "Dark mode", + "Dashboard": "Dashboard", + "DashboardNotAssigned": "Not assigned", + "DashboardNotScheduled": "Not scheduled", + "DashboardOverdue": "Overdue", + "DashboardOverdueAll": "Overdue - all", + "DashboardReminders": "Reminders", + "DashboardScheduled": "Scheduled", + "DashboardServiceRateQuantityAllUsers": "Service quantity - all", + "DashboardOpenCSR": "Open service requests", + "DashboardCountWorkOrdersCreated": "Count of Work orders created", + "DashboardPctWorkOrderCompletedOnTime": "% Work orders completed on time", + "DashboardWorkOrderByStatusList":"List of work orders by status", + "DashboardWorkOrderStatusCount":"Count of work orders by status", + "DashboardWorkOrderStatusPct":"% of work orders by status", + "Database": "Database", + "DatabaseID": "Database ID", + "DataListSavedFilter": "List filter", + "DateRange14DayWindow": "Window - 14 days", + "DateRangeApril": "April", + "DateRangeAugust": "August", + "DateRangeDecember": "December", + "DateRangeFebruary": "February", + "DateRangeFuture": "Future", + "DateRangeInTheLastSixMonths": "In the last 6 months", + "DateRangeInTheLastThreeMonths": "In the last 3 months", + "DateRangeJanuary": "January", + "DateRangeJuly": "July", + "DateRangeJune": "June", + "DateRangeLastMonth": "Month - Previous", + "DateRangeLastWeek": "Week - Previous", + "DateRangeLastYear": "Year - Previous", + "DateRangeMarch": "March", + "DateRangeMay": "May", + "DateRangeNextMonth": "Month - Next", + "DateRangeNextWeek": "Week - Next", + "DateRangeNovember": "November", + "DateRangeOctober": "October", + "DateRangePast": "Past", + "DateRangePast24Hours": "Past 24 hours", + "DateRangePast30Days": "Past 30 days", + "DateRangePast6Hours": "Past 6 hours", + "DateRangePast7Days": "Past 7 days", + "DateRangePast90Days": "Past 90 days", + "DateRangePastYear": "Year - Past (last 365 days)", + "DateRangePreviousYearLastMonth": "Previous year - last month", + "DateRangePreviousYearNextMonth": "Previous year - next month", + "DateRangePreviousYearThisMonth": "Previous year - this month", + "DateRangeSeptember": "September", + "DateRangeThisMonth": "Month - Current", + "DateRangeThisWeek": "Week - Current", + "DateRangeThisYear": "Year - Current", + "DateRangeToday": "Today", + "DateRangeTomorrow": "Tomorrow", + "DateRangeYesterday": "Yesterday", + "DayFriday": "Friday", + "DayMonday": "Monday", + "DaySaturday": "Saturday", + "DaySunday": "Sunday", + "DayThursday": "Thursday", + "DayTuesday": "Tuesday", + "DayWednesday": "Wednesday", + "DefaultLanguage": "Default language", + "DefaultReport": "Default report", + "Delete": "Delete", + "DeletePrompt": "Are you sure you want to delete this record permanently?", + "DeleteSelected": "Delete selected items", + "DeliverAfter": "Deliver after", + "Description": "Description", + "DirectNotification": "Direct notification", + "Download": "Download", + "DropFilesHere": "Drop files here", + "Duplicate": "Duplicate", + "DuplicateToPM": "Duplicate to Preventive Maintenance", + "DuplicateToQuote": "Duplicate to Quote", + "DuplicateToWorkOrder": "Duplicate to Work order", + "Duration": "Duration", + "EmailSubject":"Email subject template", + "EmailTemplate":"Email body template", + "EraseMultipleObjectsWarning": "Warning: you are about to permanently erase multiple objects.\r\nAre you sure?", + "ErrorAPI2000": "The server is closed", + "ErrorAPI2001": "The server is closed for maintenance", + "ErrorAPI2002": "Internal server error", + "ErrorAPI2003": "Authentication failed", + "ErrorAPI2004": "Not authorized", + "ErrorAPI2005": "Object was recently changed by another user and can't be saved", + "ErrorAPI2006": "The server is closed for migration", + "ErrorAPI2010": "Object not found", + "ErrorAPI2020": "Route id doesn't match object id", + "ErrorAPI2030": "Invalid operation", + "ErrorAPI2040": "Not enough inventory", + "ErrorAPI2200": "Validation error", + "ErrorAPI2201": "Required field", + "ErrorAPI2202": "Length exceeded", + "ErrorAPI2203": "Invalid value", + "ErrorAPI2204": "Required field (custom)", + "ErrorAPI2205": "Expected field missing", + "ErrorAPI2206": "Must be unique", + "ErrorAPI2207": "Start date must be before end date", + "ErrorAPI2208": "This object is linked to others and can not be changed this way", + "ErrorAPI2209": "This value cannot be changed", + "ErrorAPI2210": "Child object error", + "ErrorAPI2212": "Only one Contracted Unit allowed per work order", + "ErrorDBForeignKeyViolation": "This object can not be deleted because it is linked to one or more related objects", + "ErrorFieldLengthExceeded": "{0} can not exceed {1} characters.", + "ErrorFieldValueNotDecimal": "Value must be a number", + "ErrorFieldValueNotInteger": "Value must be an integer", + "ErrorFieldValueNumberGreaterThanMax": "Value must be less than {0}", + "ErrorFieldValueNumberLessThanMin": "Value must be greater than {0}", + "ErrorGenBeforeTooSmall": "Must be smaller than Repeat interval", + "ErrorNoMatch": "Values don't match", + "ErrorPickListQueryInvalid": "Query not valid - click help icon to learn more", + "ErrorRepeatIntervalTooSmall": "Minimum one hour", + "ErrorRequiredFieldEmpty": "{0} is a required field. Please enter a value for {0}", + "Errors": "Errors", + "ErrorSecurityAdministratorOnlyMessage": "You must be logged on as the 'SuperUser' for this task", + "ErrorSecurityUserCapacity": "There are not enough available licenses to continue this operation.", + "ErrorServerUnresponsive": "Server not responding (E17)", + "ErrorStartDateAfterEndDate": "Start date must be earlier than stop / end date", + "ErrorUserNotAuthenticated": "Not authenticated (E16)", + "ErrorUserNotAuthorized": "Not authorized", + "Evaluate": "Evaluate", + "EvaluateForceEmail": "Set all sample email addresses to this (optional)", + "EvaluationGuide": "Evaluation guide", + "EvaluationRequestReceived": "Request received, check your email for verification", + "Event": "Event", + "EventAttachmentCreate": "Attachment created", + "EventAttachmentDelete": "Attachment deleted", + "EventAttachmentDownload": "Attachment downloaded", + "EventAttachmentModified": "Attachment modified", + "EventCreated": "Created", + "EventDeleted": "Deleted", + "EventLicenseFetch": "License retrieved", + "EventLicenseTrialRequest": "Trial license requested", + "EventModified": "Modified", + "EventResetSerial": "Serial number reset", + "EventRetrieved": "Retrieved", + "EventSeedDatabase": "Database seeded with trial data", + "EventServerStateChange": "Server state changed", + "EventTextra": "Notes", + "EventUtilityFileDownload": "Operations file downloaded", + "ExcludeDaysOfWeek": "Exclude days of week", + "Export": "Export", + "Extensions": "Extensions", + "Failed": "Failed", + "False": "False", + "FileAttachment": "Attachment", + "FileDate": "Date", + "FileName": "Name", + "FileSize": "Size", + "Filter": "Filter", + "FilterUsers": "Filter Users", + "Find": "Find", + "FindAndReplace": "Find and replace", + "First": "First", + "FormCustom": "Form customization", + "FormFieldEntryRequired": "Required", + "FormFieldVisible": "Visible", + "Forward": "Forward", + "GenerateBefore": "Generate beforehand", + "GenerateSampleData": "Generate sample data", + "GeoCapture": "Set co-ordinates to current location", + "GeoView": "View on map", + "Global": "Global", + "GlobalAllowScheduleConflicts": "Allow Schedule Conflicts", + "GlobalCJKIndex": "CJK Index", + "GlobalCJKIndexDescription": "Only set to TRUE if entry of Chinese, Japanese or Korean characters into fields and labels", + "GlobalFilterCaseSensitive": "Filtering is case sensitive", + "GlobalLaborSchedUserDfltTimeSpan": "Scheduled / Labor default minutes", + "GlobalLogo": "Business logos", + "GlobalNextSeeds": "Set next seed number", + "GlobalOps": "Global ops", + "GlobalSignatureFooter": "Signature footer", + "GlobalSignatureHeader": "Signature header", + "GlobalSignatureTitle": "Signature title", + "GlobalTaxPartPurchaseID": "Default parts purchase tax", + "GlobalTaxPartSaleID": "Defaults parts sales tax", + "GlobalTaxRateSaleID": "Default service sales tax", + "GlobalTravelDfltTimeSpan": "Travel default minutes", + "GlobalUseInventory": "Use Inventory", + "GlobalWorkOrderCompleteByAge": "Default work order completion age", + "GridFilterDialogAndRadioText": "And conditions", + "GridFilterDialogOrRadioText": "Or conditions", + "GridFilterName": "Filter name", + "GridRowFilterDropDownBlanksItem": "(No value)", + "GridRowFilterDropDownContains": "Contains", + "GridRowFilterDropDownDoesNotContain": "Does not contain", + "GridRowFilterDropDownEndsWith": "Ends with", + "GridRowFilterDropDownEquals": "Equal to", + "GridRowFilterDropDownGreaterThan": "Greater than", + "GridRowFilterDropDownGreaterThanOrEqualTo": "Greater than or equal to", + "GridRowFilterDropDownLessThan": "Less than", + "GridRowFilterDropDownLessThanOrEqualTo": "Less than or equal to", + "GridRowFilterDropDownNonBlanksItem": "(Has value)", + "GridRowFilterDropDownNotEquals": "Not equal to", + "GridRowFilterDropDownStartsWith": "Starts with", + "HaveLicense": "Use existing license", + "Heading": "Heading", + "HeadOffice": "Head Office", + "HeadOfficeAccountNumber": "Account Number", + "HeadOfficeCustom1": "Custom1", + "HeadOfficeCustom10": "Custom10", + "HeadOfficeCustom11": "Custom11", + "HeadOfficeCustom12": "Custom12", + "HeadOfficeCustom13": "Custom13", + "HeadOfficeCustom14": "Custom14", + "HeadOfficeCustom15": "Custom15", + "HeadOfficeCustom16": "Custom16", + "HeadOfficeCustom2": "Custom2", + "HeadOfficeCustom3": "Custom3", + "HeadOfficeCustom4": "Custom4", + "HeadOfficeCustom5": "Custom5", + "HeadOfficeCustom6": "Custom6", + "HeadOfficeCustom7": "Custom7", + "HeadOfficeCustom8": "Custom8", + "HeadOfficeCustom9": "Custom9", + "HeadOfficeEmail": "Email", + "HeadOfficeList": "Head Offices", + "HeadOfficeName": "Head Office Name", + "HeadOfficeNotes": "Notes", + "HeadOfficePhone1": "Business", + "HeadOfficePhone2": "Fax", + "HeadOfficePhone3": "Home", + "HeadOfficePhone4": "Mobile", + "HeadOfficePhone5": "Pager", + "HelpAboutSockeye": "About Sockeye", + "HelpCheckForUpdates": "Check For Updates", + "HelpLicense": "License", + "HelpReleaseKey": "Re-use existing license", + "HelpRestore": "How to restore data", + "HelpTechSupport": "Technical support", + "History": "History", + "Home": "Home", + "Hour12": "12 hour clock", + "ID": "Id", + "ImageDescription": "Image description", + "ImageUrl": "Image url", + "Import": "Import", + "Include": "Include", + "InsertImage": "Insert image", + "InsertLink": "Insert link", + "Interval": "Interval", + "Inventory": "Inventory", + "InventoryPurchaseOrders": "Purchase Orders", + "InventoryRoleRequired": "User must have Inventory Role for this operation", + "JobCompleted": "Job completed", + "JobCreated": "Server job created", + "JobExclusiveWarning": "WARNING: this job will temporarily suspend all user access to the server", + "JobFailed": "Job failed", + "KnownPasswordWarning": "DANGER: the current password is not secure and should be changed immediately", + "LanguageCode": "Override browser language code", + "LargeLogo": "Large sized logo", + "Last": "Last", + "LastLogin": "Last login", + "LastServiceWorkOrder": "Last work order", + "LastServiceWorkOrderServiceDate": "Last service date", + "Leave": "Leave", + "License": "License", + "LicenseCompanyName": "Company name", + "LicenseContactName": "Contact name", + "LicensedOptions": "Licensed options", + "LicenseEmail": "Email address", + "LicenseEmailVerficationHint": "(Address will be verified)", + "LicenseExpiration": "License expiration date", + "LicenseSerial": "License serial number", + "LineTotal": "Line Total", + "LinkText": "Link text", + "LinkUrl": "Link url", + "ListPrice": "List price", + "Loading": "Loading...", + "LoanUnit": "Loan item", + "LoanUnitCurrentWorkOrderItemLoan": "Current workorder item loan ID", + "LoanUnitCustom1": "Custom1", + "LoanUnitCustom10": "Custom10", + "LoanUnitCustom11": "Custom11", + "LoanUnitCustom12": "Custom12", + "LoanUnitCustom13": "Custom13", + "LoanUnitCustom14": "Custom14", + "LoanUnitCustom15": "Custom15", + "LoanUnitCustom16": "Custom16", + "LoanUnitCustom2": "Custom2", + "LoanUnitCustom3": "Custom3", + "LoanUnitCustom4": "Custom4", + "LoanUnitCustom5": "Custom5", + "LoanUnitCustom6": "Custom6", + "LoanUnitCustom7": "Custom7", + "LoanUnitCustom8": "Custom8", + "LoanUnitCustom9": "Custom9", + "LoanUnitList": "Loaner Items", + "LoanUnitName": "Name", + "LoanUnitNotes": "Notes", + "LoanUnitRateDay": "Daily charge", + "LoanUnitRateDayCost": "Daily cost", + "LoanUnitRateHalfDay": "Half day charge", + "LoanUnitRateHalfDayCost": "Half day cost", + "LoanUnitRateHour": "Hourly charge", + "LoanUnitRateHourCost": "Hourly cost", + "LoanUnitRateMonth": "Monthly charge", + "LoanUnitRateMonthCost": "Monthly cost", + "LoanUnitRateNone": "-", + "LoanUnitRateWeek": "Weekly charge", + "LoanUnitRateWeekCost": "Weekly cost", + "LoanUnitRateYear": "Yearly charge", + "LoanUnitRateYearCost": "Yearly cost", + "LoanUnitSerial": "Serial number", + "LoanUnitShadowUnit": "Shadow Unit", + "Log": "Log", + "LogFile": "Log file", + "Logout": "Log out", + "MaintenanceExpired": "Maintenance expired", + "MaintenanceExpiredNote": "The Maintenance plan has now expired\nSockeye can not be updated and support is no longer available", + "MapUrlTemplate": "Map URL template", + "MediumLogo": "Medium sized logo", + "Memo": "Memo", + "MemoCustom1": "Custom1", + "MemoCustom10": "Custom10", + "MemoCustom11": "Custom11", + "MemoCustom12": "Custom12", + "MemoCustom13": "Custom13", + "MemoCustom14": "Custom14", + "MemoCustom15": "Custom15", + "MemoCustom16": "Custom16", + "MemoCustom2": "Custom2", + "MemoCustom3": "Custom3", + "MemoCustom4": "Custom4", + "MemoCustom5": "Custom5", + "MemoCustom6": "Custom6", + "MemoCustom7": "Custom7", + "MemoCustom8": "Custom8", + "MemoCustom9": "Custom9", + "MemoForward": "Forward", + "MemoFromID": "From", + "MemoList": "Memos", + "MemoMessage": "Message", + "MemoRe": "RE:", + "MemoReplied": "Replied", + "MemoReply": "Reply", + "MemoSent": "Sent", + "MemoSubject": "Subject", + "MemoToID": "To", + "MemoViewed": "Viewed", + "MenuHelp": "Help", + "MetricAllocatedMemory": "Allocated (MiB)", + "MetricAttachmentsCount": "Attachments (count)", + "MetricAttachmentsMB": "Attachments (MiB)", + "MetricAvailableDiskSpace": "Available space (MiB)", + "MetricBackupMB": "Backups (MiB)", + "MetricCPUMemory": "CPU / Memory", + "MetricDBSize": "Database size (MiB)", + "MetricFileStorage": "File storage", + "MetricPrivateBytes": "Private bytes (MiB)", + "MetricTopTablesSize": "Top tables (KiB)", + "MetricWorkingSet": "Working set (MiB)", + "More": "More...", + "MoveSelected": "Move selected items", + "Name": "Name", + "NativeDateTimeInput": "Use browser's standard date time input controls", + "NetPrice": "Net", + "New": "New", + "NewLicenseInstalled": "New license has been installed. You will now be logged out.", + "NewLicenseNotFound": "No new license is currently available", + "NewLogin": "New login name", + "NewPassword": "New password", + "NewStatus": "New status", + "NextPMNumber": "Next preventive maintenance", + "NextPONumber": "Next purchase order", + "NextQuoteNumber": "Next quote", + "NextWorkorderNumber": "Next work order", + "NoColor": "No color", + "NoData": "No data", + "NoFeaturesAvailable": "No features are enabled for your account", + "NoLicenseTitle": "Not licensed", + "NoResults": "No results", + "Notification": "Notification", + "Notifications": "Notifications", + "NotificationDeliveryLog":"User notifications log", + "NotificationCustomerDeliveryLog":"Customer notification log", + "NotifyDeliveryAddress": "Deliver to address", + "NotifyDeliveryMethod": "Notification delivery method", + "NotifyDeliveryMethodApp": "Deliver in application", + "NotifyDeliveryMethodSMTP": "Deliver to email address", + "NotifyEventBackupStatus": "Backup status", + "NotifyEventContractExpiring": "Contract expiring", + "NotifyEventCSRAccepted": "Customer service request accepted", + "NotifyEventCSRRejected": "Customer service request rejected", + "NotifyEventCustomerServiceImminent": "Service reminder", + "NotifyEventCustomerServiceImminentMessage": "You have an upcoming service appointment in {0}\nUse the following link to view the details:", + "NotifyEventGeneralNotification": "General notification", + "NotifyEventNotifyHealthCheck": "Notification system health check", + "NotifyEventObjectAge": "Object age since created", + "NotifyEventObjectCreated": "Object created", + "NotifyEventObjectDeleted": "Object deleted", + "NotifyEventObjectModified": "Object changed", + "NotifyEventOutsideServiceOverdue": "Outside service overdue", + "NotifyEventOutsideServiceReceived": "Outside service unit received back", + "NotifyEventPartRequestReceived": "Requested part received", + "NotifyEventPMGenerationFailed": "Preventive maintenance generation failure", + "NotifyEventPMInsufficientInventory": "Preventive maintenance insufficient inventory", + "NotifyEventPMStopGeneratingDateReached": "Preventive maintenance stop date", + "NotifyEventQuoteStatusAge": "Quote status age", + "NotifyEventQuoteStatusChange": "Quote status changed", + "NotifyEventReminderImminent": "Reminder - imminent", + "NotifyEventReviewImminent": "Review - imminent", + "NotifyEventScheduledOnWorkorder": "Scheduled on work order", + "NotifyEventScheduledOnWorkorderImminent": "Work order scheduled service imminent", + "NotifyEventServerOperationsProblem": "Server operations problem", + "NotifyEventType": "Notification event", + "NotifyEventUnitMeterReadingMultipleExceeded": "Unit meter reading multiple exceeded", + "NotifyEventUnitWarrantyExpiry": "Unit warranty expiry", + "NotifyEventWorkorderCompleted": "Work order completed", + "NotifyEventWorkorderCompletedStatusOverdue": "Work order not completed on time", + "NotifyEventWorkorderCreatedForCustomer": "Work order created for Customer", + "NotifyEventWorkorderStatusAge": "Work order status age", + "NotifyEventWorkorderStatusChange": "Work order status set", + "NotifyEventWorkorderTotalExceedsThreshold": "Work order total exceeds threshold", + "NotifyFromAddress": "SMTP notify from address", + "NotifyMailSecurityNone": "None", + "NotifyMailSecuritySSLTLS": "SSL\\TLS", + "NotifyMailSecurityStartTls": "StartTLS", + "NotifyQueue": "Notify event delivery queue", + "NotifySubscription": "Notification subscription", + "NotifySubscriptionLinkText": "Change notification setting:", + "NotifySubscriptionList": "Notification subscriptions", + "NotifySubscriptionPendingSpan": "Notify before event", + "NoType": "No type", + "Now": "Now", + "Object": "Object", + "ObjectCustomFieldCustomGrid": "Custom Fields", + "OK": "OK", + "OldPassword": "Old password", + "Open": "Open", + "Operations": "Server operations", + "OpsNotificationSettings": "Notification settings", + "OpsTestJob": "Submit test job", + "OutsideServiceList": "Outside services", + "PageOfPageText": "{0}-{1} of {2}", + "Part": "Part", + "PartAlternativeWholesalerID": "Alternative Wholesaler", + "PartAlternativeWholesalerNumber": "Alternative Wholesaler Number", + "PartAssembly": "Part Assembly", + "PartAssemblyCustom1": "Custom1", + "PartAssemblyCustom10": "Custom10", + "PartAssemblyCustom11": "Custom11", + "PartAssemblyCustom12": "Custom12", + "PartAssemblyCustom13": "Custom13", + "PartAssemblyCustom14": "Custom14", + "PartAssemblyCustom15": "Custom15", + "PartAssemblyCustom16": "Custom16", + "PartAssemblyCustom2": "Custom2", + "PartAssemblyCustom3": "Custom3", + "PartAssemblyCustom4": "Custom4", + "PartAssemblyCustom5": "Custom5", + "PartAssemblyCustom6": "Custom6", + "PartAssemblyCustom7": "Custom7", + "PartAssemblyCustom8": "Custom8", + "PartAssemblyCustom9": "Custom9", + "PartAssemblyList": "Part Assemblies", + "PartAssemblyName": "Part Assembly Name", + "PartAssemblyNotes": "Notes", + "PartByWarehouseInventoryList": "Part Inventory", + "PartByWarehouseInventoryMinStockLevel": "Minimum quantity", + "PartByWarehouseInventoryQtyOnOrderCommitted": "Quantity on order committed", + "PartByWarehouseInventoryQuantityOnOrder": "On Order", + "PartByWarehouseInventoryReorderQuantity": "Reorder quantity", + "PartCost": "Cost", + "PartCustom1": "Custom1", + "PartCustom10": "Custom10", + "PartCustom11": "Custom11", + "PartCustom12": "Custom12", + "PartCustom13": "Custom13", + "PartCustom14": "Custom14", + "PartCustom15": "Custom15", + "PartCustom16": "Custom16", + "PartCustom2": "Custom2", + "PartCustom3": "Custom3", + "PartCustom4": "Custom4", + "PartCustom5": "Custom5", + "PartCustom6": "Custom6", + "PartCustom7": "Custom7", + "PartCustom8": "Custom8", + "PartCustom9": "Custom9", + "PartDescription": "Part description", + "PartInventoryAdjustment": "Part inventory adjustment", + "PartInventoryBalance": "On Hand", + "PartInventoryId": "Inventory Id", + "PartInventoryDataList": "Part inventory", + "PartInventoryTransaction": "Inventory transaction", + "PartInventoryTransactionDescription": "Description", + "PartInventoryTransactionEntryDate": "Date", + "PartInventoryTransactionList": "Inventory transactions", + "PartInventoryTransactionQuantity": "Quantity", + "PartInventoryTransactionSource": "Transaction source", + "PartList": "Parts", + "PartManufacturerID": "Manufacturer", + "PartManufacturerNumber": "Manufacturer Number", + "PartName": "Part name", + "PartNotes": "Notes", + "PartRestockRequiredByVendorList": "Part Restock Required By Vendor", + "PartRetail": "Retail", + "PartSerial": "Serialized part", + "PartSerialNumbersAvailable": "Available serial numbers", + "PartSerialWarehouseID": "Part Warehouse", + "PartStockingLevels": "Part stocking levels", + "PartUPC": "UPC", + "PartWarehouse": "Part Warehouse", + "PartWarehouseCustom1": "Custom1", + "PartWarehouseCustom10": "Custom10", + "PartWarehouseCustom11": "Custom11", + "PartWarehouseCustom12": "Custom12", + "PartWarehouseCustom13": "Custom13", + "PartWarehouseCustom14": "Custom14", + "PartWarehouseCustom15": "Custom15", + "PartWarehouseCustom16": "Custom16", + "PartWarehouseCustom2": "Custom2", + "PartWarehouseCustom3": "Custom3", + "PartWarehouseCustom4": "Custom4", + "PartWarehouseCustom5": "Custom5", + "PartWarehouseCustom6": "Custom6", + "PartWarehouseCustom7": "Custom7", + "PartWarehouseCustom8": "Custom8", + "PartWarehouseCustom9": "Custom9", + "PartWarehouseList": "Warehouses", + "PartWarehouseName": "Warehouse Name", + "PartWarehouseNotes": "Notes", + "PartWholesalerID": "Wholesaler", + "PartWholesalerNumber": "Wholesaler Number", + "PasswordResetMessageBody": "Hello {user_name},\n\nYour online account for service is available to you after you set your password.\nYou can use the following link for the next 48 hours to set your password.\n\nSet your password: {action_link}\n\nIf you did not request an account or password reset, please ignore this email.\n\nThanks,\n{registered_to}", + "PasswordResetMessageTitle": "Your online account is ready", + "PickListTemplate": "Autocomplete list template", + "PickListTemplates": "Autocomplete list templates", + "PM": "Preventive maintenance", + "PMItem": "P.M. item", + "PMItemExpense": "P.M. item - expense", + "PMItemLabor": "P.M. item - labor", + "PMItemLoan": "P.M. item - loan unit", + "PMItemOutsideService": "P.M. item - outside service", + "PMItemPart": "P.M. item - part", + "PMItemScheduledUser": "P.M. item - scheduled user", + "PMItemTravel": "P.M. item - travel", + "PMItemTask": "P.M. item - task", + "PMItemUnit": "P.M. item - unit", + "PMList": "Preventive maintenance", + "PMNextServiceDate": "Next service date", + "PMNextWoGenerateDate": "Next generate event", + "PMSerialNumber": "Number", + "PMStopGeneratingDate": "Stop generating date", + "Price": "Price", + "PriceOverride": "Price override", + "Print": "Print", + "Processed":"Processed", + "ProcessingJob": "Processing server job", + "Project": "Project", + "ProjectAccountNumber": "Account Number", + "ProjectCustom1": "Custom1", + "ProjectCustom10": "Custom10", + "ProjectCustom11": "Custom11", + "ProjectCustom12": "Custom12", + "ProjectCustom13": "Custom13", + "ProjectCustom14": "Custom14", + "ProjectCustom15": "Custom15", + "ProjectCustom16": "Custom16", + "ProjectCustom2": "Custom2", + "ProjectCustom3": "Custom3", + "ProjectCustom4": "Custom4", + "ProjectCustom5": "Custom5", + "ProjectCustom6": "Custom6", + "ProjectCustom7": "Custom7", + "ProjectCustom8": "Custom8", + "ProjectCustom9": "Custom9", + "ProjectDateCompleted": "Date Completed", + "ProjectDateStarted": "Date Started", + "ProjectList": "Projects", + "ProjectName": "Project Name", + "ProjectNotes": "Notes", + "ProjectProjectOverseerID": "Project Overseer", + "PurchaseLicense": "Purchase a license", + "PurchaseOrder": "Purchase Order", + "PurchaseOrderCustom1": "Custom1", + "PurchaseOrderCustom10": "Custom10", + "PurchaseOrderCustom11": "Custom11", + "PurchaseOrderCustom12": "Custom12", + "PurchaseOrderCustom13": "Custom13", + "PurchaseOrderCustom14": "Custom14", + "PurchaseOrderCustom15": "Custom15", + "PurchaseOrderCustom16": "Custom16", + "PurchaseOrderCustom2": "Custom2", + "PurchaseOrderCustom3": "Custom3", + "PurchaseOrderCustom4": "Custom4", + "PurchaseOrderCustom5": "Custom5", + "PurchaseOrderCustom6": "Custom6", + "PurchaseOrderCustom7": "Custom7", + "PurchaseOrderCustom8": "Custom8", + "PurchaseOrderCustom9": "Custom9", + "PurchaseOrderDropShipToCustomerID": "Drop Ship to Customer", + "PurchaseOrderExpectedReceiveDate": "Expected", + "PurchaseOrderItem": "Purchase Order Item", + "PurchaseOrderItemLineTotal": "Line Total", + "PurchaseOrderItemList": "Order items", + "PurchaseOrderItemNetTotal": "Net Total", + "PurchaseOrderItemPartName": "Part Name", + "PurchaseOrderItemPartNumber": "Part Number", + "PurchaseOrderItemPartRequestedByID": "Requested by", + "PurchaseOrderItemPurchaseOrderCost": "P.O. Cost", + "PurchaseOrderItemQuantityOrdered": "Quantity Ordered", + "PurchaseOrderItemQuantityReceived": "Quantity Received", + "PurchaseOrderItemSerialNumbers": "Serial numbers", + "PurchaseOrderItemUIOrderedFrom": "Ordered from", + "PurchaseOrderItemVendorPartNumber": "Vendor number", + "PurchaseOrderItemWorkOrderNumber": "Work order #", + "PurchaseOrderNotes": "Notes", + "PurchaseOrderOrderedDate": "Ordered Date", + "PurchaseOrderPONumber": "PO Number", + "PurchaseOrderReceiptItemQuantityReceivedErrorInvalid": "Value must be positive and no more than Ordered amount", + "PurchaseOrderReceiptItemReceiptCost": "Received Cost", + "PurchaseOrderReceiptReceivedDate": "Received Date", + "PurchaseOrderReceiptText1": "Text1", + "PurchaseOrderReceiptText2": "Text2", + "PurchaseOrderReferenceNumber": "Reference Number", + "PurchaseOrderStatus": "Purchase Order Status", + "PurchaseOrderStatusClosedFullReceived": "Closed - fully received", + "PurchaseOrderStatusClosedNoneReceived": "Closed - none received", + "PurchaseOrderStatusClosedPartialReceived": "Closed - partially received", + "PurchaseOrderStatusOpenNotYetOrdered": "Open - not yet ordered", + "PurchaseOrderStatusOpenOrdered": "Open - on order", + "PurchaseOrderStatusOpenPartialReceived": "Open - partially received", + "PurchaseOrderUICopyToPurchaseOrder": "Copy to P.O.", + "PurchaseOrderUIRestockList": "Restock list", + "PurchaseOrderVendorMemo": "Vendor Memo", + "QuantityRequired": "Quantity required", + "Quote": "Quote", + "QuoteDateApproved": "Approved", + "QuoteDateSubmitted": "Submitted", + "QuoteIntroduction": "Introductory Text", + "QuoteItem": "QuoteItem", + "QuoteItemExpense": "Quote item - expense", + "QuoteItemLabor": "Quote item - labor", + "QuoteItemLoan": "Quote item - loan unit", + "QuoteItemOutsideService": "Quote item - outside service", + "QuoteItemPart": "Quote item - part", + "QuoteItemScheduledUser": "Quote item - scheduled user", + "QuoteItemTravel": "Quote item - travel", + "QuoteItemTask": "Quote item - task", + "QuoteItemUnit": "Quote item - unit", + "QuoteList": "Quotes", + "QuotePreparedByID": "Prepared by User", + "QuoteQuoteRequestDate": "Requested", + "QuoteQuoteStatusType": "Quote Status", + "QuoteSerialNumber": "Number", + "QuoteStatusList": "Quote status list", + "QuoteValidUntilDate": "Valid Until", + "RateAccountNumber": "Account Number", + "RateCharge": "Retail Charge", + "RateContractRate": "Contract Rate", + "RateUnitChargeDescriptionID": "Unit Charge Description", + "ReadOnly": "Read only", + "ReceiveAll": "Receive all", + "RecentWorkOrders": "Recent Work orders", + "RecordHistory": "Record History", + "Refresh": "Refresh...", + "Region": "Region", + "RegisteredUser": "Licensed to", + "Reminder": "Reminder", + "ReminderColor": "Color", + "ReminderCustom1": "Custom1", + "ReminderCustom10": "Custom10", + "ReminderCustom11": "Custom11", + "ReminderCustom12": "Custom12", + "ReminderCustom13": "Custom13", + "ReminderCustom14": "Custom14", + "ReminderCustom15": "Custom15", + "ReminderCustom16": "Custom16", + "ReminderCustom2": "Custom2", + "ReminderCustom3": "Custom3", + "ReminderCustom4": "Custom4", + "ReminderCustom5": "Custom5", + "ReminderCustom6": "Custom6", + "ReminderCustom7": "Custom7", + "ReminderCustom8": "Custom8", + "ReminderCustom9": "Custom9", + "ReminderList": "Reminders", + "ReminderName": "Name", + "ReminderNotes": "Notes", + "ReminderStartDate": "Start", + "ReminderStopDate": "Stop", + "Remove": "Remove", + "RemoveRoles": "Who can remove", + "RenderingReport": "Generating report", + "RepeatInterval": "Repeat interval", + "Replace": "Replace", + "Report": "Report", + "ReportDesignReport": "Edit report", + "ReportDisplayHeaderFooter": "Display header & footer", + "ReportEditorData": "Sample data", + "ReportEditorMobileWarning": "Editor not supported on mobile devices", + "ReportEditorProperties": "Properties", + "ReportFooterTemplate": "Footer template", + "ReportHeaderTemplate": "Header template", + "ReportIncludeAllWorkOrderItemDescendants": "Include all descendants of work order item", + "ReportLandscape": "Landscape", + "ReportList": "Report Templates", + "ReportMarginOptionsBottom": "Bottom margin", + "ReportMarginOptionsLeft": "Left margin", + "ReportMarginOptionsRight": "Right margin", + "ReportMarginOptionsTop": "Top margin", + "ReportName": "Name", + "ReportNotes": "Notes", + "ReportPaperFormat": "Paper format", + "ReportPdfOptions": "PDF options", + "ReportPreferCSSPageSize": "Prefer CSS page size", + "ReportPrintBackground": "Print background", + "ReportRenderTimeOut": "The report was taking too many minutes to process, the limit is set to {0}", + "ReportScale": "Scale", + "ReportTemplate": "Template", + "RequestEvaluationLicense": "Request an evaluation license", + "ResetToDefault": "Reset to default", + "Review": "Review", + "ReviewAssignedByUserId": "Assigned by", + "ReviewCompletedDate": "Completed date", + "ReviewCompletionNotes": "Completion notes", + "ReviewCustom1": "Custom1", + "ReviewCustom10": "Custom10", + "ReviewCustom11": "Custom11", + "ReviewCustom12": "Custom12", + "ReviewCustom13": "Custom13", + "ReviewCustom14": "Custom14", + "ReviewCustom15": "Custom15", + "ReviewCustom16": "Custom16", + "ReviewCustom2": "Custom2", + "ReviewCustom3": "Custom3", + "ReviewCustom4": "Custom4", + "ReviewCustom5": "Custom5", + "ReviewCustom6": "Custom6", + "ReviewCustom7": "Custom7", + "ReviewCustom8": "Custom8", + "ReviewCustom9": "Custom9", + "ReviewDate": "Review date", + "ReviewList": "Reviews", + "ReviewName": "Name", + "ReviewNotes": "Notes", + "ReviewOverDue": "Overdue", + "ReviewUserId": "Assigned to", + "RowsPerPage": "Rows per page", + "Save": "Save", + "SaveACopy": "Save a copy", + "SaveRecordToProceed": "This record must be saved to continue", + "Schedule": "Schedule", + "Schedule4Day": "4 Day", + "ScheduleCategory": "Team - day", + "ScheduleConflict": "Schedule conflict", + "ScheduleDay": "Day", + "ScheduleDayView": "Single Day View", + "ScheduleEditReminder": "Edit selected reminder", + "ScheduleEditScheduleableUserGroup": "Edit Service tech Groups", + "ScheduleEditWorkOrder": "Edit selected Work order", + "ScheduleFirstHour": "First time to display in the day view", + "ScheduleMonth": "Month", + "ScheduleOptions": "Schedule settings", + "ScheduleShowTypes": "Show", + "ScheduleWeek": "Week", + "ScheduleWOColorFrom": "Work order color source", + "SchemaVersion": "Schema version", + "Search": "Search", + "SeedLevel": "Seed level", + "SeedLevelHuge": "Huge - very large dataset (about 1 hour to process)", + "SeedLevelLarge": "Large - multi-region fully staffed (about 10 minutes to process)", + "SeedLevelMedium": "Medium - many service techs and support staff (about 5 minutes to process)", + "SeedLevelSmall": "Small service shop (about 1 minute to process)", + "SelectAlternateAddress": "Select alternate address", + "SelectedItems": "Selected items", + "SelectItem": "Select", + "SelectRoles": "Who can select", + "SendEvaluationRequest": "Send request", + "SendPasswordResetCode": "Send password reset email", + "Sequence": "Sequence", + "Server": "Server", + "ServerAddress": "Server address", + "ServerJob": "Server job", + "ServerJobs": "Job queue", + "ServerLog": "Server log", + "ServerMetrics": "Server metrics", + "ServerProfiler": "Profiler", + "ServerState": "Server state", + "ServerStateLoginRestricted": "Login restricted due to server state setting", + "ServerStateMigrateMode": "Server migration mode", + "ServerStateOpen": "Open", + "ServerStateOps": "Server operations only", + "ServerStateReason": "Reason", + "ServerTime": "Server time", + "Service": "Service", + "ServiceHistory": "Service History", + "ServicePreventiveMaintenance": "PM", + "ServiceRate": "Service rate", + "ServiceRateCustom1": "Custom1", + "ServiceRateCustom10": "Custom10", + "ServiceRateCustom11": "Custom11", + "ServiceRateCustom12": "Custom12", + "ServiceRateCustom13": "Custom13", + "ServiceRateCustom14": "Custom14", + "ServiceRateCustom15": "Custom15", + "ServiceRateCustom16": "Custom16", + "ServiceRateCustom2": "Custom2", + "ServiceRateCustom3": "Custom3", + "ServiceRateCustom4": "Custom4", + "ServiceRateCustom5": "Custom5", + "ServiceRateCustom6": "Custom6", + "ServiceRateCustom7": "Custom7", + "ServiceRateCustom8": "Custom8", + "ServiceRateCustom9": "Custom9", + "ServiceRateList": "Service rates", + "ServiceRateNotes": "Notes", + "SetLoginPassword": "Set Login Password", + "Settings": "Settings", + "ShutDownServer": "Shut down server", + "SmallLogo": "Small sized logo", + "SmtpAccount": "SMTP server account", + "SmtpDeliveryActive": "SMTP notification active", + "SmtpPassword": "SMTP server password", + "SmtpServerAddress": "SMTP server address", + "SmtpServerPort": "SMTP server port", + "SoftDelete": "Mark for deletion", + "SoftDeleteAll": "Mark all for deletion", + "Sort": "Sort", + "StartAttachmentMaintenanceJob": "Start attachment maintenance job", + "StartEvaluation": "Start trial evaluation", + "StartJob": "Start job", + "Statistics": "Statistics", + "Status": "Status", + "StatusColor": "Color", + "StatusCompleted": "Is a completed status", + "StatusLocked": "Is a locking status", + "StatusName": "Name", + "StatusNotes": "Notes", + "StopWords1": "do said his both could with take like still much they been will her your how through were other or there never is here where then my must as when him them most re which if he had at", + "StopWords2": "want many so to the be else did than that of only being got about you their our on this too has any might can before are way now since we should another also into me it by after and in", + "StopWords3": "would some what such make come while its use those see out who ll but get have same up well because between for all each does came just from was an these himself very under over more", + "StopWords4": "?", + "StopWords5": "?", + "StopWords6": "?", + "StopWords7": "?", + "SupportedUntil": "Maintenance plan expiration date", + "Table": "Table", + "Tag": "Tag", + "TaggedWith": "Tagged with", + "Tags": "Tags", + "Task": "Task", + "TaskGroup": "Task group", + "TaskGroupList": "Task groups", + "TaskGroupName": "Task group name", + "TaskGroupNotes": "Notes", + "TaskList": "Tasks", + "Tax": "Tax", + "TaxAAmt": "Tax A amount", + "TaxBAmt": "Tax B amount", + "TaxCode": "Tax code", + "TaxCodeCustom1": "Custom1", + "TaxCodeCustom10": "Custom10", + "TaxCodeCustom11": "Custom11", + "TaxCodeCustom12": "Custom12", + "TaxCodeCustom13": "Custom13", + "TaxCodeCustom14": "Custom14", + "TaxCodeCustom15": "Custom15", + "TaxCodeCustom16": "Custom16", + "TaxCodeCustom2": "Custom2", + "TaxCodeCustom3": "Custom3", + "TaxCodeCustom4": "Custom4", + "TaxCodeCustom5": "Custom5", + "TaxCodeCustom6": "Custom6", + "TaxCodeCustom7": "Custom7", + "TaxCodeCustom8": "Custom8", + "TaxCodeCustom9": "Custom9", + "TaxCodeDefault": "Error: while this tax code is a default in global settings, it can not be deleted or set inactive", + "TaxCodeList": "Tax Codes", + "TaxCodeName": "Tax Code Name", + "TaxCodeNotes": "Notes", + "TaxCodeTaxA": "Tax \"A\"", + "TaxCodeTaxB": "Tax \"B\"", + "TaxCodeTaxOnTax": "Tax on Tax", + "TechSignature": "Technician signature", + "TemplateTokens":"Template tokens", + "TestSMTPSettings": "Send test message", + "TestToAddress": "Send test to", + "ThankYouForEvaluating": "Use the following links to get started exploring Sockeye", + "TimedOut": "Timed out", + "TimeSpan": "Time span", + "TimeSpanDays": "days", + "TimeSpanHours": "hours", + "TimeSpanMinutes": "minutes", + "TimeSpanMonths": "months", + "TimeSpanSeconds": "seconds", + "TimeSpanYears": "years", + "TimeStamp": "Time stamp", + "TimeToCompletion": "Time to completion", + "TimeZone": "Override browser time zone", + "TooManyResults": "Too many results to show all", + "Total": "Total", + "Translation": "Translation", + "TranslationDisplayText": "Display", + "TranslationKey": "Key", + "TranslationList": "Translations", + "TravelRate": "Travel rate", + "TravelRateCustom1": "Custom1", + "TravelRateCustom10": "Custom10", + "TravelRateCustom11": "Custom11", + "TravelRateCustom12": "Custom12", + "TravelRateCustom13": "Custom13", + "TravelRateCustom14": "Custom14", + "TravelRateCustom15": "Custom15", + "TravelRateCustom16": "Custom16", + "TravelRateCustom2": "Custom2", + "TravelRateCustom3": "Custom3", + "TravelRateCustom4": "Custom4", + "TravelRateCustom5": "Custom5", + "TravelRateCustom6": "Custom6", + "TravelRateCustom7": "Custom7", + "TravelRateCustom8": "Custom8", + "TravelRateCustom9": "Custom9", + "TravelRateList": "Travel rates", + "TravelRateNotes": "Notes", + "TrialSeeder": "Trial seed data", + "True": "True", + "TypeToSearchOrAdd": "Start typing to search or add", + "UiFieldDataType": "Field data type", + "UiFieldDataTypesCurrency": "Money", + "UiFieldDataTypesDateOnly": "Date", + "UiFieldDataTypesDateTime": "Date Time", + "UiFieldDataTypesDecimal": "Decimal", + "UiFieldDataTypesInteger": "Integer", + "UiFieldDataTypesText": "Text", + "UiFieldDataTypesTimeOnly": "Time", + "UiFieldDataTypesTrueFalse": "True/False", + "Undelete": "Undelete", + "Unit": "Unit", + "UnitBoughtHere": "Purchased Here", + "UnitCustom1": "Custom1", + "UnitCustom10": "Custom10", + "UnitCustom11": "Custom11", + "UnitCustom12": "Custom12", + "UnitCustom13": "Custom13", + "UnitCustom14": "Custom14", + "UnitCustom15": "Custom15", + "UnitCustom16": "Custom16", + "UnitCustom2": "Custom2", + "UnitCustom3": "Custom3", + "UnitCustom4": "Custom4", + "UnitCustom5": "Custom5", + "UnitCustom6": "Custom6", + "UnitCustom7": "Custom7", + "UnitCustom8": "Custom8", + "UnitCustom9": "Custom9", + "UnitDescription": "Description", + "UnitList": "Customer units", + "UnitMetered": "Unit Metered", + "UnitMeterReading": "Unit Meter Reading", + "UnitMeterReadingDescription": "Meter description", + "UnitMeterReadingList": "Unit Meter Reading List", + "UnitMeterReadingMeter": "Unit Meter Reading", + "UnitMeterReadingMeterDate": "Meter reading date", + "UnitMeterReadingWorkOrderItemID": "Meter read on workorder", + "UnitModel": "Unit model", + "UnitModelCustom1": "Custom1", + "UnitModelCustom10": "Custom10", + "UnitModelCustom11": "Custom11", + "UnitModelCustom12": "Custom12", + "UnitModelCustom13": "Custom13", + "UnitModelCustom14": "Custom14", + "UnitModelCustom15": "Custom15", + "UnitModelCustom16": "Custom16", + "UnitModelCustom2": "Custom2", + "UnitModelCustom3": "Custom3", + "UnitModelCustom4": "Custom4", + "UnitModelCustom5": "Custom5", + "UnitModelCustom6": "Custom6", + "UnitModelCustom7": "Custom7", + "UnitModelCustom8": "Custom8", + "UnitModelCustom9": "Custom9", + "UnitModelDiscontinued": "Discontinued", + "UnitModelDiscontinuedDate": "Discontinued Date", + "UnitModelIntroducedDate": "Introduced Date", + "UnitModelLifeTimeWarranty": "Life Time Warranty", + "UnitModelName": "Unit Model Name", + "UnitModelNotes": "Notes", + "UnitModels": "Unit Models", + "UnitModelUPC": "UPC", + "UnitModelVendorID": "Unit model vendor", + "UnitModelWarrantyLength": "Warranty Length", + "UnitModelWarrantyTerms": "Warranty Terms", + "UnitNotes": "Notes", + "UnitOfMeasure": "Unit of measure", + "UnitOverrideLength": "Override Length", + "UnitOverrideLifeTime": "Life-time warranty", + "UnitOverrideWarranty": "Override Warranty", + "UnitOverrideWarrantyTerms": "Override Warranty Terms", + "UnitParentUnitID": "Parent Unit of this Unit", + "UnitPurchasedDate": "Purchased Date", + "UnitPurchaseFromID": "Purchased From", + "UnitReceipt": "Receipt Number", + "UnitReplacedByUnitID": "Replaced by Unit", + "UnitSerial": "Serial Number", + "UnitText1": "Text1", + "UnitText2": "Text2", + "UnitText3": "Text3", + "UnitText4": "Text4", + "UnitUnitHasOwnAddress": "Unit Has Own Address", + "UnitWarrantyInfo": "Warranty information", + "UpdateAvailable": "Update is available. Install it now?", + "Upload": "Upload", + "User": "User", + "UserColor": "User color", + "UserCountExceeded": "Server locked due to exceeding licensed active scheduleable User limit", + "UserCustom1": "Custom1", + "UserCustom10": "Custom10", + "UserCustom11": "Custom11", + "UserCustom12": "Custom12", + "UserCustom13": "Custom13", + "UserCustom14": "Custom14", + "UserCustom15": "Custom15", + "UserCustom16": "Custom16", + "UserCustom2": "Custom2", + "UserCustom3": "Custom3", + "UserCustom4": "Custom4", + "UserCustom5": "Custom5", + "UserCustom6": "Custom6", + "UserCustom7": "Custom7", + "UserCustom8": "Custom8", + "UserCustom9": "Custom9", + "UserEmailAddress": "User Email Address", + "UserEmployeeNumber": "Employee Number", + "UserInterfaceSettings": "User interface", + "UserList": "Users", + "UserLogin": "Login Name", + "UserNotes": "Notes", + "UserOptions": "User options", + "UserPageAddress": "Pager Address", + "UserPhone1": "Phone1", + "UserPhone2": "Phone 2", + "UserPreferences": "User Preferences", + "UserSettings": "User settings", + "UserTimeZoneOffset": "UTC offset", + "UserType": "User type", + "UserTypeCustomer": "Customer user", + "UserTypeHeadOffice": "Head office user", + "UserTypeNotService": "Non-service user", + "UserTypeService": "Service user", + "UserTypeServiceContractor": "SubContractor user", + "Vendor": "Vendor", + "VendorAccountNumber": "Account Number", + "VendorAlertNotes": "Alert notes", + "VendorContact": "Contact", + "VendorContactNotes": "Other contacts", + "VendorCustom1": "Custom1", + "VendorCustom10": "Custom10", + "VendorCustom11": "Custom11", + "VendorCustom12": "Custom12", + "VendorCustom13": "Custom13", + "VendorCustom14": "Custom14", + "VendorCustom15": "Custom15", + "VendorCustom16": "Custom16", + "VendorCustom2": "Custom2", + "VendorCustom3": "Custom3", + "VendorCustom4": "Custom4", + "VendorCustom5": "Custom5", + "VendorCustom6": "Custom6", + "VendorCustom7": "Custom7", + "VendorCustom8": "Custom8", + "VendorCustom9": "Custom9", + "VendorEmail": "Email", + "VendorList": "Vendors", + "VendorName": "Vendor Name", + "VendorNotes": "Notes", + "VendorPhone1": "Business", + "VendorPhone2": "Fax", + "VendorPhone3": "Home", + "VendorPhone4": "Mobile", + "VendorPhone5": "Pager", + "Version": "Version", + "ViewEULA": "View license agreement", + "ViewServerConfiguration": "Server information", + "Warranty": "Warranty", + "WarrantyExpires": "Valid until", + "WebAddress": "Web Address", + "Welcome": "Welcome to Sockeye", + "WhoWillBeNotified":"Who will be notified?", + "WorkOrder": "Work order", + "WorkOrderAge": "Age", + "WorkOrderCloseByDate": "Date to be completed", + "WorkOrderContactCustomerHeadOfficeTaggedWith": "Restrict to Work order, Contact, Customer or Head Office with any of these tags", + "WorkOrderConvertAllScheduledUsersToLabor": "Convert all to labor", + "WorkOrderConvertScheduledUserToLabor": "Convert to labor", + "WorkOrderCustom1": "Custom1", + "WorkOrderCustom10": "Custom10", + "WorkOrderCustom11": "Custom11", + "WorkOrderCustom12": "Custom12", + "WorkOrderCustom13": "Custom13", + "WorkOrderCustom14": "Custom14", + "WorkOrderCustom15": "Custom15", + "WorkOrderCustom16": "Custom16", + "WorkOrderCustom2": "Custom2", + "WorkOrderCustom3": "Custom3", + "WorkOrderCustom4": "Custom4", + "WorkOrderCustom5": "Custom5", + "WorkOrderCustom6": "Custom6", + "WorkOrderCustom7": "Custom7", + "WorkOrderCustom8": "Custom8", + "WorkOrderCustom9": "Custom9", + "WorkOrderCustomerContactName": "Contact", + "WorkOrderCustomerReferenceNumber": "Customer Reference #", + "WorkOrderCustomerTaggedWith": "Work order or Customer with any of these tags", + "WorkOrderErrorLocked": "Work order is set to a locked status and can't be changed", + "WorkOrderFromPMID": "P.M. parent", + "WorkOrderFromQuoteID": "Quote parent", + "WorkOrderGenerateUnit": "Generate unit from selected part", + "WorkOrderInternalReferenceNumber": "Internal Reference #", + "WorkOrderInvoiceNumber": "Invoice Number", + "WorkOrderItem": "Work order Item", + "WorkOrderItemCustom1": "Custom1", + "WorkOrderItemCustom10": "Custom10", + "WorkOrderItemCustom11": "Custom11", + "WorkOrderItemCustom12": "Custom12", + "WorkOrderItemCustom13": "Custom13", + "WorkOrderItemCustom14": "Custom14", + "WorkOrderItemCustom15": "Custom15", + "WorkOrderItemCustom16": "Custom16", + "WorkOrderItemCustom2": "Custom2", + "WorkOrderItemCustom3": "Custom3", + "WorkOrderItemCustom4": "Custom4", + "WorkOrderItemCustom5": "Custom5", + "WorkOrderItemCustom6": "Custom6", + "WorkOrderItemCustom7": "Custom7", + "WorkOrderItemCustom8": "Custom8", + "WorkOrderItemCustom9": "Custom9", + "WorkOrderItemExpense": "Work order item expense", + "WorkOrderItemExpenseChargeAmount": "Charge Amount", + "WorkOrderItemExpenseChargeTaxCodeID": "Charge Tax Code", + "WorkOrderItemExpenseChargeToCustomer": "Charge to customer", + "WorkOrderItemExpenseDescription": "Description", + "WorkOrderItemExpenseList": "Expenses", + "WorkOrderItemExpenseName": "Summary", + "WorkOrderItemExpenseReimburseUser": "Reimburse User", + "WorkOrderItemExpenseTaxPaid": "Tax Paid", + "WorkOrderItemExpenseTotalCost": "Total Cost", + "WorkOrderItemExpenseUserID": "User", + "WorkOrderItemLabor": "Work order item labor", + "WorkOrderItemLaborList": "Labors", + "WorkOrderItemLaborNoChargeQuantity": "No Charge Quantity", + "WorkOrderItemLaborPrice": "Net price", + "WorkOrderItemLaborServiceDetails": "Service Details", + "WorkOrderItemLaborServiceRateID": "Service Rate", + "WorkOrderItemLaborServiceRateQuantity": "Service Rate Quantity", + "WorkOrderItemLaborServiceStartDate": "Service Start Date Time", + "WorkOrderItemLaborServiceStopDate": "Service Stop Date Time", + "WorkOrderItemLaborTaxRateSaleID": "Sales tax", + "WorkOrderItemLaborUserID": "User", + "WorkOrderItemList": "Items", + "WorkOrderItemLoan": "Work order item loan", + "WorkOrderItemLoanDueDate": "Due back", + "WorkOrderItemLoanList": "Loans", + "WorkOrderItemLoanNotes": "Notes", + "WorkOrderItemLoanOutDate": "Loaned", + "WorkOrderItemLoanQuantity": "Rate quantity", + "WorkOrderItemLoanRate": "Rate", + "WorkOrderItemLoanReturnDate": "Returned", + "WorkOrderItemLoanTaxCodeID": "Sales tax", + "WorkOrderItemLoanUnit": "Loan Item", + "WorkOrderItemOutsideService": "Work order item Outside Service", + "WorkOrderItemOutsideServiceDateETA": "ETA Date", + "WorkOrderItemOutsideServiceDateReturned": "Date Returned", + "WorkOrderItemOutsideServiceDateSent": "Date Sent", + "WorkOrderItemOutsideServiceList": "Outside service list", + "WorkOrderItemOutsideServiceNotes": "Notes", + "WorkOrderItemOutsideServiceRepairCost": "Repair Cost", + "WorkOrderItemOutsideServiceRepairPrice": "Repair Price", + "WorkOrderItemOutsideServiceRMANumber": "RMA Number", + "WorkOrderItemOutsideServiceShippingCost": "Shipping Cost", + "WorkOrderItemOutsideServiceShippingPrice": "Shipping Price", + "WorkOrderItemOutsideServiceTrackingNumber": "Tracking Number", + "WorkOrderItemOutsideServiceVendorSentToID": "Sent To", + "WorkOrderItemOutsideServiceVendorSentViaID": "Sent Via", + "WorkOrderItemPart": "Work order item part", + "WorkOrderItemPartDescription": "Description", + "WorkOrderItemPartList": "Parts", + "WorkOrderItemPartPartID": "Part", + "WorkOrderItemPartPartWarehouseID": "Warehouse", + "WorkOrderItemPartQuantity": "Quantity", + "WorkOrderItemPartRealizeSuggested": "Realize suggested part quantities", + "WorkOrderItemPartRequest": "Work order item part request", + "WorkOrderItemPartRequestList": "Part requests", + "WorkOrderItemPartRequestMore": "Request {n} more", + "WorkOrderItemPartRequestOnOrder": "On order", + "WorkOrderItemPartRequestPartID": "Part", + "WorkOrderItemPartRequestPartWarehouseID": "Warehouse", + "WorkOrderItemPartRequestQuantity": "Quantity", + "WorkOrderItemPartRequestReceived": "Received", + "WorkOrderItemPartRequests": "Part requests", + "WorkOrderItemPartSuggestedQuantity": "Suggested quantity", + "WorkOrderItemPartTaxPartSaleID": "Sales tax", + "WorkOrderItemPartUsed": "Used in service", + "WorkOrderItemPriorityColor": "Color", + "WorkOrderItemPriorityID": "Priority", + "WorkOrderItemPriorityList": "Work order item priority list", + "WorkOrderItemPriorityName": "Name", + "WorkOrderItemRequestDate": "Request Date", + "WorkOrderItemScheduledUser": "Work order item Scheduled User", + "WorkOrderItemScheduledUserEstimatedQuantity": "Estimated quantity", + "WorkOrderItemScheduledUserList": "Scheduled Users", + "WorkOrderItemScheduledUserServiceRateID": "Suggested rate", + "WorkOrderItemScheduledUserStartDate": "Start Date Time", + "WorkOrderItemScheduledUserStopDate": "Stop Date Time", + "WorkOrderItemScheduledUserUserID": "User", + "WorkOrderItemStatusColor": "Color", + "WorkOrderItemStatusList": "Work order item status list", + "WorkOrderItemStatusName": "Name", + "WorkOrderItemStatusNotes": "Notes", + "WorkOrderItemSummary": "Summary", + "WorkOrderItemTags": "WO Item Tags", + "WorkOrderItemTask": "Work order item task", + "WorkOrderItemTaskCompletedDate": "Completed", + "WorkOrderItemTaskCompletionTypeComplete": "Completed", + "WorkOrderItemTaskCompletionTypeFailed": "Failed", + "WorkOrderItemTaskCompletionTypeIncomplete": "To Do", + "WorkOrderItemTaskCompletionTypeNotApplicable": "N/A", + "WorkOrderItemTasks": "Tasks", + "WorkOrderItemTaskTaskID": "Task", + "WorkOrderItemTaskUser": "User", + "WorkOrderItemTaskWorkOrderItemTaskCompletionType": "Status", + "WorkOrderItemTechNotes": "Service Notes", + "WorkOrderItemTravel": "Work order item travel", + "WorkOrderItemTravelDetails": "Travel Details", + "WorkOrderItemTravelDistance": "Distance", + "WorkOrderItemTravelList": "Travels", + "WorkOrderItemTravelNoChargeQuantity": "No Charge Quantity", + "WorkOrderItemTravelRateQuantity": "Quantity", + "WorkOrderItemTravelServiceRateID": "Travel Rate", + "WorkOrderItemTravelStartDate": "Start Date", + "WorkOrderItemTravelStopDate": "Stop Date", + "WorkOrderItemTravelTaxRateSaleID": "Sales tax", + "WorkOrderItemTravelUserID": "User", + "WorkOrderItemUnit": "Work order item unit", + "WorkOrderItemUnitCustom1": "Custom1", + "WorkOrderItemUnitCustom10": "Custom10", + "WorkOrderItemUnitCustom11": "Custom11", + "WorkOrderItemUnitCustom12": "Custom12", + "WorkOrderItemUnitCustom13": "Custom13", + "WorkOrderItemUnitCustom14": "Custom14", + "WorkOrderItemUnitCustom15": "Custom15", + "WorkOrderItemUnitCustom16": "Custom16", + "WorkOrderItemUnitCustom2": "Custom2", + "WorkOrderItemUnitCustom3": "Custom3", + "WorkOrderItemUnitCustom4": "Custom4", + "WorkOrderItemUnitCustom5": "Custom5", + "WorkOrderItemUnitCustom6": "Custom6", + "WorkOrderItemUnitCustom7": "Custom7", + "WorkOrderItemUnitCustom8": "Custom8", + "WorkOrderItemUnitCustom9": "Custom9", + "WorkOrderItemUnitList": "Units", + "WorkOrderItemUnitNotes": "Notes", + "WorkOrderItemUnitTags": "WO Item Unit Tags", + "WorkOrderItemWarrantyService": "Warranty Service", + "WorkOrderItemWorkOrderStatusID": "Work order item status", + "WorkOrderList": "Work orders", + "WorkOrderOnsite": "Onsite", + "WorkOrderSerialNumber": "Number", + "WorkOrderServiceDate": "Service Date", + "WorkOrderStatus": "Work order status", + "WorkOrderStatusList": "Work order status list", + "WorkOrderSummary": "Summary" +} \ No newline at end of file diff --git a/server/resource/es.json b/server/resource/es.json new file mode 100644 index 0000000..02884cf --- /dev/null +++ b/server/resource/es.json @@ -0,0 +1,1495 @@ +{ + "Accounting": "Contabilidad", + "Active": "Activo", + "Activity": "Actividad", + "Add": "Añadir", + "AddMultipleUnits": "Agregar varias unidades", + "AddNewUnit": "Agregar nueva unidad", + "Address": "Dirección", + "AddressCity": "Ciudad", + "AddressCopyToPhysical": "Copy to physical address", + "AddressCopyToPostal": "Copy to postal address", + "AddressCountry": "País", + "AddressDeliveryAddress": "Calle", + "AddressLatitude": "Latitud", + "AddressLongitude": "Longitud", + "AddressPostalCity": "Ciudad (correo)", + "AddressPostalCountry": "País (correo)", + "AddressPostalDeliveryAddress": "Dirección (correo)", + "AddressPostalPostal": "Código postal (correo)", + "AddressPostalStateProv": "Provincia (correo)", + "AddressStateProv": "Provincia", + "AddressTypePhysical": "Dirección física", + "AddressTypePostal": "Dirección postal", + "AdminEraseDatabase": "Borrar toda la base de datos Sockeye", + "AdminEraseDatabaseLastWarning": "Advertencia: Esta es su última oportunidad para evitar borrar todos los datos permanentemente. ¿Seguro que desea borrar todos los datos?", + "AdminEraseDatabaseWarning": "Advertencia: está a punto de borrar de forma permanente todos los datos de Sockeye. ¿Está seguro?", + "Administration": "Administración", + "AdministrationGlobalSettings": "Ajustes globales", + "AlertNotes": "Notas de alerta", + "AllItemsInList": "Todos los elementos de la lista", + "AnyUser": "All users", + "AppendTasks": "Agregar tareas", + "ApplyUnitContract": "¿Aplicar el contrato '{0}' de esta unidad a la orden de trabajo?", + "AreYouSureBackupNow": "El servidor se bloqueará durante la copia de seguridad. ¿Estás seguro?", + "AreYouSureShutDown": "¿Estás segura de que quieres cerrar el servidor?", + "AreYouSureUnsavedChanges": "¿Estás seguro que deseas abandonar? Perderás tus cambios no guardados.", + "AttachFile": "Adjuntar archivo", + "AttachmentExists": "El archivo existe", + "AttachmentFileName": "Nombre archivo", + "AttachmentNotes": "Notas", + "Attachments": "Archivos adjuntos", + "AttachReport":"Adjuntar informe", + "AuthConnectAppManualEntry": "¿Tiene problemas para escanear el código? Ingrese lo siguiente manualmente en su aplicación de autenticación:", + "AuthConnectAppSubTitle": "Con una aplicación de autenticación como Google Authenticator, Duo, Microsoft Authenticator o Authy, escanee el código QR. Aparecerá un código de acceso de 6 dígitos que debe ingresar a continuación.", + "AuthConnectAppTitle": "Conecta tu aplicación", + "AuthConnectCompleted": "La autenticación de dos factores ahora está habilitada", + "AuthDisableTwoFactor": "Deshabilitar la autenticación de dos factores", + "AuthEnterPin": "Ingrese un código de 6 dígitos", + "AuthorizationRoleAccounting": "Contabilidad", + "AuthorizationRoleAll": "Todos los roles", + "AuthorizationRoleBizAdmin": "Administración de empresas", + "AuthorizationRoleBizAdminRestricted": "Administración de empresas - restringido", + "AuthorizationRoleCustomer": "Usuario cliente", + "AuthorizationRoleCustomerRestricted": "Usuario cliente - restringido", + "AuthorizationRoleInventory": "Inventario", + "AuthorizationRoleInventoryRestricted": "Inventario - restringido", + "AuthorizationRoleNoRole": "Sin rol", + "AuthorizationRoleOpsAdmin": "Operaciones del sistema", + "AuthorizationRoleOpsAdminRestricted": "Operaciones del sistema - restringidas", + "AuthorizationRoles": "Roles de autorización", + "AuthorizationRoleSales": "Ventas", + "AuthorizationRoleSalesRestricted": "Ventas - restringidas", + "AuthorizationRoleService": "Supervisor de servicio", + "AuthorizationRoleServiceRestricted": "Supervisor de servicio - restringido", + "AuthorizationRoleSubContractor": "Subcontratista", + "AuthorizationRoleSubContractorRestricted": "Subcontratista - restringido", + "AuthorizationRoleTech": "Técnico de servicio", + "AuthorizationRoleTechRestricted": "Técnico de servicio - restringido", + "AuthPinInvalid": "Código no válido", + "AuthTwoFactor": "Autenticación de dos factores", + "AuthTwoFactorDisabled": "La autenticación de dos factores ahora está deshabilitada", + "AuthVerifyCode": "Código de verificación", + "AvailableSpace": "Espacio disponible", + "AyaFileFileTooLarge": "File size exceeds limit of {0}", + "SockeyeServerURL": "URL del servidor Sockeye", + "SockType": "Tipo", + "Backup": "Copia de seguridad", + "BackupAttachments": "Adjuntos de respaldo", + "BackupDeleteOld": "Eliminar archivos de respaldo antiguos", + "BackupFiles": "Lista de archivos de respaldo", + "BackupLast": "Último tiempo de respaldo", + "BackupNow": "Iniciar copia de seguridad ahora", + "BackupSetsToKeep": "Número de copias de seguridad para mantener", + "BackupSettings": "Configuración de copia de seguridad", + "BackupTime": "Tiempo de respaldo", + "Backward": "Mover hacia atrás", + "BatchDeleteJob": "Trabajo de eliminación por lotes", + "BatchJob": "Trabajo por lotes", + "BizMetrics": "Métricas", + "Browser": "Navegador", + "BusinessSettings": "Configuración empresarial", + "Cancel": "Cancelar", + "CheckForLicense": "Instalar licencia", + "Close": "Salir", + "Columns": "Columnas", + "CompanyEmail": "Email", + "CompanyInformation": "Información de la empresa", + "CompanyPhone1": "Teléfono", + "CompanyPhone2": "Teléfono 2", + "ConfirmPassword": "Confirmar contraseña", + "ConfirmUpdatePartCost": "¿Actualizar el costo de la pieza del costo recibido?", + "ConnectionSecurity": "Seguridad de conexión del servidor SMTP", + "Contact": "Contacto", + "ContactCustomerHeadOfficeTaggedWith": "Permitir solo contactar, cliente u oficina central con cualquiera de estas etiquetas", + "ContactPhone": "Teléfono del contacto", + "Contacts": "Contactos", + "ContactTitle": "Tratamiento contacto", + "Contract": "Contrato", + "ContractAdjustment": "Ajuste de precio", + "ContractCustom1": "Campo personalizado 1", + "ContractCustom10": "Campo personalizado 10", + "ContractCustom11": "Campo personalizado 11", + "ContractCustom12": "Campo personalizado 12", + "ContractCustom13": "Campo personalizado 13", + "ContractCustom14": "Campo personalizado 14", + "ContractCustom15": "Campo personalizado 15", + "ContractCustom16": "Campo personalizado 16", + "ContractCustom2": "Campo personalizado 2", + "ContractCustom3": "Campo personalizado 3", + "ContractCustom4": "Campo personalizado 4", + "ContractCustom5": "Campo personalizado 5", + "ContractCustom6": "Campo personalizado 6", + "ContractCustom7": "Campo personalizado 7", + "ContractCustom8": "Campo personalizado 8", + "ContractCustom9": "Campo personalizado 9", + "ContractDefaultAdjustments": "Ajustes de precio predeterminados", + "ContractDefaultResponseTime": "Tiempo de respuesta", + "ContractDiscountParts": "Descuento aplicado a todas las piezas", + "ContractExpires": "El contrato expira", + "ContractList": "Contratos", + "ContractName": "Nombre del contrato", + "ContractNotes": "Notas", + "ContractOverrideType": "Tipo de ajuste de precio", + "ContractOverrideTypeMarkup": "Costo más porcentaje", + "ContractOverrideTypePriceDiscount": "Precio menos porcentaje", + "ContractRate": "Tarifa de contrato", + "ContractRateList": "Tarifas de contrato", + "ContractServiceRatesOnly": "Permitir solo estas tarifas de servicio", + "ContractTaggedAdjustments": "Ajustes de precio etiquetados", + "ContractTravelRatesOnly": "Permita solo estas tarifas de viaje", + "Copy": "Copiar", + "CopyAttachments": "Copiar adjuntos", + "CopyDbId": "Copiar ID de base de datos", + "CopySupportInfo": "Copiar información de soporte", + "CopyToClipboard": "Copiar al Portapapeles", + "CopyToWorkOrder": "Copiar a la orden de trabajo", + "CopyWiki": "Copiar WIKI", + "Cost": "Coste", + "Created": "Registro creado", + "CSRInfoText": "Mensaje al cliente", + "CurrencyCode": "Código de moneda (ISO 4217)", + "Customer": "Cliente", + "CustomerAccessSettings": "Configuración de acceso de clientes", + "CustomerAccessWorkOrderAttachments": "Abrir archivos adjuntos del encabezado de la orden de trabajo", + "CustomerAccessWorkOrderReport": "Informe de orden de trabajo del cliente", + "CustomerAccessWorkOrderWiki": "Ver el encabezado de la orden de trabajo WIKI", + "CustomerAccountNumber": "Número de cuenta", + "CustomerAlertNotes": "Notas emergentes", + "CustomerAllowCreateUnit": "Permitir al cliente crear unidad", + "CustomerBillHeadOffice": "Sede para facturación", + "CustomerCustom1": "Campo personalizado 1", + "CustomerCustom10": "Campo personalizado 10", + "CustomerCustom11": "Campo personalizado 11", + "CustomerCustom12": "Campo personalizado 12", + "CustomerCustom13": "Campo personalizado 13", + "CustomerCustom14": "Campo personalizado 14", + "CustomerCustom15": "Campo personalizado 15", + "CustomerCustom16": "Campo personalizado 16", + "CustomerCustom2": "Campo personalizado 2", + "CustomerCustom3": "Campo personalizado 3", + "CustomerCustom4": "Campo personalizado 4", + "CustomerCustom5": "Campo personalizado 5", + "CustomerCustom6": "Campo personalizado 6", + "CustomerCustom7": "Campo personalizado 7", + "CustomerCustom8": "Campo personalizado 8", + "CustomerCustom9": "Campo personalizado 9", + "CustomerEmail": "Email", + "CustomerList": "Lista de clientes", + "CustomerName": "Nombre del cliente", + "CustomerNote": "Nota del cliente", + "CustomerNoteList": "Notas cliente", + "CustomerNoteNoteDate": "Fecha nota", + "CustomerNoteNotes": "Notas", + "CustomerNotes": "Notas generales", + "CustomerNotifySubscription": "Suscripción de notificación al cliente", + "CustomerNotifySubscriptionList":"Suscripciones de notificaciones de clientes", + "CustomerPhone1": "Teléfono", + "CustomerPhone2": "Fax", + "CustomerPhone3": "Teléfono de casa", + "CustomerPhone4": "Móvil", + "CustomerPhone5": "Buscapersonas", + "CustomerServiceRequest": "Solicitud servicio cliente", + "CustomerServiceRequestAcceptToExisting": "Accept to existing work order", + "CustomerServiceRequestAcceptToNew": "Accept to new work order", + "CustomerServiceRequestCustom1": "Campo personalizado 1", + "CustomerServiceRequestCustom10": "Campo personalizado 10", + "CustomerServiceRequestCustom11": "Campo personalizado 11", + "CustomerServiceRequestCustom12": "Campo personalizado 12", + "CustomerServiceRequestCustom13": "Campo personalizado 13", + "CustomerServiceRequestCustom14": "Campo personalizado 14", + "CustomerServiceRequestCustom15": "Campo personalizado 15", + "CustomerServiceRequestCustom16": "Campo personalizado 16", + "CustomerServiceRequestCustom2": "Campo personalizado 2", + "CustomerServiceRequestCustom3": "Campo personalizado 3", + "CustomerServiceRequestCustom4": "Campo personalizado 4", + "CustomerServiceRequestCustom5": "Campo personalizado 5", + "CustomerServiceRequestCustom6": "Campo personalizado 6", + "CustomerServiceRequestCustom7": "Campo personalizado 7", + "CustomerServiceRequestCustom8": "Campo personalizado 8", + "CustomerServiceRequestCustom9": "Campo personalizado 9", + "CustomerServiceRequestCustomerReferenceNumber": "Número de referencia", + "CustomerServiceRequestDetails": "Detalles", + "CustomerServiceRequestItemUnitID": "Unidad", + "CustomerServiceRequestList": "Solicitudes de servicio al cliente", + "CustomerServiceRequestPriority": "Prioridad", + "CustomerServiceRequestPriorityASAP": "Lo antes posible", + "CustomerServiceRequestPriorityEmergency": "Emergencia", + "CustomerServiceRequestPriorityNotUrgent": "No urgente", + "CustomerServiceRequestReject": "Rechazar solicitud", + "CustomerServiceRequestRequestedBy": "Solicitado por", + "CustomerServiceRequestStatus": "Estado", + "CustomerServiceRequestStatusAccepted": "Aceptado", + "CustomerServiceRequestStatusDeclined": "Rechazado", + "CustomerServiceRequestStatusOpen": "Abierto", + "CustomerServiceRequestTitle": "Solicitud", + "CustomerSignature": "Firma del cliente", + "CustomerTags":"Etiquetas de clientes", + "CustomerTechNotes": "Notas del técnico de servicio", + "Customize": "Personalizar....", + "DarkMode": "Modo oscuro", + "Dashboard": "Panel", + "DashboardNotAssigned": "Not assigned", + "DashboardNotScheduled": "No programado", + "DashboardOverdue": "Atrasado", + "DashboardOverdueAll": "Atrasado - todo", + "DashboardReminders": "Reminders", + "DashboardScheduled": "Scheduled", + "DashboardServiceRateQuantityAllUsers":"Cantidad de servicio - todo", + "DashboardOpenCSR":"Solicitudes de servicio abiertas", + "DashboardCountWorkOrdersCreated": "Recuento de órdenes de trabajo creadas", + "DashboardPctWorkOrderCompletedOnTime": "% de órdenes de trabajo completadas a tiempo", + "DashboardWorkOrderByStatusList":"Lista de órdenes de trabajo por estado", + "DashboardWorkOrderStatusCount":"Recuento de órdenes de trabajo por estado", + "DashboardWorkOrderStatusPct":"% de órdenes de trabajo por estado", + "Database": "Base de datos", + "DatabaseID": "Id. de base de datos", + "DataListSavedFilter": "Filtro de lista", + "DateRange14DayWindow": "Ventana - 14 días", + "DateRangeApril": "Abril", + "DateRangeAugust": "Agosto", + "DateRangeDecember": "Diciembre", + "DateRangeFebruary": "Febrero", + "DateRangeFuture": "Futuro", + "DateRangeInTheLastSixMonths": "En los últimos 6 meses", + "DateRangeInTheLastThreeMonths": "En los últimos 3 meses", + "DateRangeJanuary": "Enero", + "DateRangeJuly": "Julio", + "DateRangeJune": "Junio", + "DateRangeLastMonth": "Mes - Anterior", + "DateRangeLastWeek": "Week - Previous", + "DateRangeLastYear": "Year - Last", + "DateRangeMarch": "Marzo", + "DateRangeMay": "Mayo", + "DateRangeNextMonth": "Mes - Siguiente", + "DateRangeNextWeek": "Semana - Siguiente", + "DateRangeNovember": "Noviembre", + "DateRangeOctober": "Octubre", + "DateRangePast": "Pasado", + "DateRangePast24Hours": "Últimas 24 horas", + "DateRangePast30Days": "Últimos 30 días", + "DateRangePast6Hours": "Últimas 6 horas", + "DateRangePast7Days": "Últimos 7 días", + "DateRangePast90Days": "Últimos 90 días", + "DateRangePastYear": "Año pasado (365 días)", + "DateRangePreviousYearLastMonth": "Año anterior - mes pasado", + "DateRangePreviousYearNextMonth": "Año anterior - mes siguiente", + "DateRangePreviousYearThisMonth": "Año anterior - este mes", + "DateRangeSeptember": "Septiembre", + "DateRangeThisMonth": "Mes - Actual", + "DateRangeThisWeek": "Semana - Actual", + "DateRangeThisYear": "Año - Actual", + "DateRangeToday": "Hoy", + "DateRangeTomorrow": "Mañana", + "DateRangeYesterday": "Ayer", + "DayFriday": "Viernes", + "DayMonday": "Lunes", + "DaySaturday": "Sábado", + "DaySunday": "Domingo", + "DayThursday": "Jueves", + "DayTuesday": "Martes", + "DayWednesday": "Miércoles", + "DefaultLanguage": "Idioma por omisión", + "DefaultReport": "Informe predeterminado", + "Delete": "Borrar", + "DeletePrompt": "¿Seguro que desea borrar este registro para siempre?", + "DeleteSelected": "Eliminar elementos seleccionados", + "DeliverAfter": "Entregar después", + "Description": "Descripción", + "DirectNotification": "Notificación directa", + "Download": "Descargar", + "DropFilesHere": "Suelta los archivos aquí", + "Duplicate": "Duplicado", + "DuplicateToPM": "Duplicado de mantenimiento preventivo", + "DuplicateToQuote": "Duplicar para cotizar", + "DuplicateToWorkOrder": "Duplicar a la orden de trabajo", + "Duration": "Duración", + "EmailSubject":"Plantilla de asunto del mensaje", + "EmailTemplate":"Plantilla de cuerpo de mensaje", + "EraseMultipleObjectsWarning": "Advertencia: está a punto de borrar de forma permanente varios objetos.\n¿Estás seguro?", + "ErrorAPI2000": "El servidor esta cerrado", + "ErrorAPI2001": "El servidor está cerrado por mantenimiento", + "ErrorAPI2002": "Error interno del servidor", + "ErrorAPI2003": "Error de autenticación", + "ErrorAPI2004": "No autorizado", + "ErrorAPI2005": "El objeto fue cambiado recientemente por otro usuario y no se puede guardar", + "ErrorAPI2006": "El servidor está cerrado por migración", + "ErrorAPI2010": "Objeto no encontrado", + "ErrorAPI2020": "La ID de ruta no coincide con la ID de objeto", + "ErrorAPI2030": "Operación no válida", + "ErrorAPI2040": "No hay suficiente inventario", + "ErrorAPI2200": "Error de validación", + "ErrorAPI2201": "Campo requerido", + "ErrorAPI2202": "Longitud excedida", + "ErrorAPI2203": "Valor no válido", + "ErrorAPI2204": "Campo obligatorio (personalizado)", + "ErrorAPI2205": "Datos esperados faltantes", + "ErrorAPI2206": "Debe ser único", + "ErrorAPI2207": "La fecha de inicio debe ser anterior a la fecha de finalización", + "ErrorAPI2208": "Este objeto está vinculado a otros y no puede ser cambiado de esta manera", + "ErrorAPI2209": "Este valor no puede ser cambiado", + "ErrorAPI2210": "Error de objeto secundario", + "ErrorAPI2212": "Solo se permite una unidad contratada por orden de trabajo", + "ErrorDBForeignKeyViolation": "Este objeto no puede borrarse porque está vinculado con uno o más objetos relacionados", + "ErrorFieldLengthExceeded": "{0} no puede pasar de {1} caracteres", + "ErrorFieldValueNotDecimal": "El valor debe ser un número", + "ErrorFieldValueNotInteger": "El valor debe ser un entero", + "ErrorFieldValueNumberGreaterThanMax": "El valor debe ser inferior a {0}", + "ErrorFieldValueNumberLessThanMin": "El valor debe ser superior a {0}", + "ErrorGenBeforeTooSmall": "Debe ser menor que el intervalo de repetición", + "ErrorNoMatch": "Los valores no coinciden", + "ErrorPickListQueryInvalid": "Consulta no válida: haga clic en el icono de ayuda para obtener más información", + "ErrorRepeatIntervalTooSmall": "Mínimo una hora", + "ErrorRequiredFieldEmpty": "{0} es un campo necesario. Introduzca un valor para {0}", + "Errors": "Errores", + "ErrorSecurityAdministratorOnlyMessage": "Debe iniciar sesión como 'SuperUser' para esta tarea", + "ErrorSecurityUserCapacity": "No hay suficientes licencias disponibles para continuar con esta operación", + "ErrorServerUnresponsive": "El servidor no responde (E17)", + "ErrorStartDateAfterEndDate": "La fecha de inicio debe ser anterior a la de fin", + "ErrorUserNotAuthenticated": "No autenticado (E16)", + "ErrorUserNotAuthorized": "No autorizado", + "Evaluate": "Evaluar", + "EvaluateForceEmail": "Establezca todas las direcciones de correo electrónico de muestra en esto (opcional)", + "EvaluationGuide": "Guía de evaluación", + "EvaluationRequestReceived": "Solicitud recibida, verifique su correo electrónico para verificar", + "Event": "Evento", + "EventAttachmentCreate": "Adjunto creado", + "EventAttachmentDelete": "Adjunto eliminado", + "EventAttachmentDownload": "Adjunto descargado", + "EventAttachmentModified": "Archivo adjunto modificado", + "EventCreated": "Creado", + "EventDeleted": "Eliminado", + "EventLicenseFetch": "Licencia recuperada", + "EventLicenseTrialRequest": "Licencia de prueba solicitada", + "EventModified": "Modificado", + "EventResetSerial": "Restablecimiento de número de serie", + "EventRetrieved": "Recuperados", + "EventSeedDatabase": "Base de datos creada con datos de muestra", + "EventServerStateChange": "Estado del servidor cambiado", + "EventTextra": "Notas", + "EventUtilityFileDownload": "Archivo de operaciones descargado", + "ExcludeDaysOfWeek": "Excluir días de la semana", + "Export": "Exportar", + "Extensions": "Extensiones", + "Failed": "Fallido", + "False": "Falso", + "FileAttachment": "Archivo adjunto", + "FileDate": "Date", + "FileName": "Nombre", + "FileSize": "Tamaño", + "Filter": "Filtro", + "FilterUsers": "Filtrar usuarios", + "Find": "Buscar", + "FindAndReplace": "Buscar y reemplazar", + "First": "Primero", + "FormCustom": "Personalización de formularios", + "FormFieldEntryRequired": "Campo obligatorio", + "FormFieldVisible": "Visible", + "Forward": "Avanzar", + "GenerateBefore": "Generar de antemano", + "GenerateSampleData": "Generar datos de muestra", + "GeoCapture": "Establecer en la ubicación actual", + "GeoView": "Ver en el mapa", + "Global": "Global", + "GlobalAllowScheduleConflicts": "Permitir conflictos de programación", + "GlobalCJKIndex": "Usar índice asiático", + "GlobalCJKIndexDescription": "Ajustar a verdadero sólo si hay entradas con caracteres chinos, japoneses o coreanos en los campos y etiquetas", + "GlobalFilterCaseSensitive": "El filtrado distingue entre mayúsculas y minúsculas", + "GlobalLaborSchedUserDfltTimeSpan": "Scheduled / Labor default minutes", + "GlobalLogo": "Logotipos comerciales", + "GlobalNextSeeds": "Establecer el siguiente número", + "GlobalOps": "Global ops", + "GlobalSignatureFooter": "Signature footer", + "GlobalSignatureHeader": "Signature header", + "GlobalSignatureTitle": "Signature title", + "GlobalTaxPartPurchaseID": "Impuesto por omisión para compra de piezas", + "GlobalTaxPartSaleID": "Impuesto por omisión sobre venta de piezas", + "GlobalTaxRateSaleID": "Impuesto por omisión sobre venta de servicio", + "GlobalTravelDfltTimeSpan": "Travel default minutes", + "GlobalUseInventory": "Usar inventario", + "GlobalWorkOrderCompleteByAge": "Âge d'achèvement des bons de travail par défaut", + "GridFilterDialogAndRadioText": "Condiciones \"y\"", + "GridFilterDialogOrRadioText": "Condiciones \"o\"", + "GridFilterName": "Filter name", + "GridRowFilterDropDownBlanksItem": "(Sin valor)", + "GridRowFilterDropDownContains": "Contiene", + "GridRowFilterDropDownDoesNotContain": "No contiene", + "GridRowFilterDropDownEndsWith": "Termina con", + "GridRowFilterDropDownEquals": "Igual a", + "GridRowFilterDropDownGreaterThan": "Mayor que", + "GridRowFilterDropDownGreaterThanOrEqualTo": "Mayor o igual a", + "GridRowFilterDropDownLessThan": "Menor que", + "GridRowFilterDropDownLessThanOrEqualTo": "Menor o igual a", + "GridRowFilterDropDownNonBlanksItem": "(Tiene valor)", + "GridRowFilterDropDownNotEquals": "No es igual a", + "GridRowFilterDropDownStartsWith": "Comienza con", + "HaveLicense": "Usar licencia existente", + "Heading": "Título", + "HeadOffice": "Oficina central", + "HeadOfficeAccountNumber": "Número de cuenta", + "HeadOfficeCustom1": "Campo personalizado 1", + "HeadOfficeCustom10": "Campo personalizado 10", + "HeadOfficeCustom11": "Campo personalizado 11", + "HeadOfficeCustom12": "Campo personalizado 12", + "HeadOfficeCustom13": "Campo personalizado 13", + "HeadOfficeCustom14": "Campo personalizado 14", + "HeadOfficeCustom15": "Campo personalizado 15", + "HeadOfficeCustom16": "Campo personalizado 16", + "HeadOfficeCustom2": "Campo personalizado 2", + "HeadOfficeCustom3": "Campo personalizado 3", + "HeadOfficeCustom4": "Campo personalizado 4", + "HeadOfficeCustom5": "Campo personalizado 5", + "HeadOfficeCustom6": "Campo personalizado 6", + "HeadOfficeCustom7": "Campo personalizado 7", + "HeadOfficeCustom8": "Campo personalizado 8", + "HeadOfficeCustom9": "Campo personalizado 9", + "HeadOfficeEmail": "Email", + "HeadOfficeList": "Oficinas centrales", + "HeadOfficeName": "Nombre de la oficina central", + "HeadOfficeNotes": "Notas", + "HeadOfficePhone1": "Teléfono", + "HeadOfficePhone2": "Fax", + "HeadOfficePhone3": "Teléfono de casa", + "HeadOfficePhone4": "Móvil", + "HeadOfficePhone5": "Buscapersonas", + "HelpAboutSockeye": "Acerca de Sockeye", + "HelpCheckForUpdates": "Comprobar actualizaciones", + "HelpLicense": "Licencia", + "HelpReleaseKey": "Reutilizar licencia existente", + "HelpRestore": "Cómo restaurar datos", + "HelpTechSupport": "Asistencia técnica", + "History": "Historial", + "Home": "Inicio", + "Hour12": "Reloj de 12 horas", + "ID": "Id", + "ImageDescription": "Descripción de la imagen", + "ImageUrl": "Dirección URL de la imagen", + "Import": "Importar", + "Include": "Incluir", + "InsertImage": "Insertar imagen", + "InsertLink": "Insertar vínculo", + "Interval": "Intervalo", + "Inventory": "Inventario", + "InventoryPurchaseOrders": "Órdenes de compra", + "InventoryRoleRequired": "El usuario debe tener un rol de inventario para esta operación", + "JobCompleted": "Trabajo completo", + "JobCreated": "Trabajo de servidor creado", + "JobExclusiveWarning": "ADVERTENCIA: este trabajo suspenderá temporalmente todo acceso de usuario al servidor", + "JobFailed": "Trabajo fallido", + "KnownPasswordWarning": "PELIGRO: la contraseña actual no es segura y debe cambiarse de inmediato", + "LanguageCode": "Anular el código de idioma predeterminado", + "LargeLogo": "Logotipo de gran tamaño", + "Last": "Último", + "LastLogin": "Última hora de inicio de sesión", + "LastServiceWorkOrder": "Última orden de trabajo", + "LastServiceWorkOrderServiceDate": "Última fecha de servicio", + "Leave": "Abandonar", + "License": "Licencia", + "LicenseCompanyName": "Nombre de empresa", + "LicenseContactName": "Nombre de contacto", + "LicensedOptions": "Opciones de licencia", + "LicenseEmail": "Dirección de correo electrónico", + "LicenseEmailVerficationHint": "(Se verificará la dirección)", + "LicenseExpiration": "Licencia hasta el", + "LicenseSerial": "Número de serie de licencia", + "LineTotal": "Total línea", + "LinkText": "Texto del vínculo", + "LinkUrl": "URL del vínculo", + "ListPrice": "Precio de lista", + "Loading": "Cargando...", + "LoanUnit": "Elemento en préstamo", + "LoanUnitCurrentWorkOrderItemLoan": "ID préstamo elemento pedido actual", + "LoanUnitCustom1": "Campo personalizado 1", + "LoanUnitCustom10": "Campo personalizado 10", + "LoanUnitCustom11": "Campo personalizado 11", + "LoanUnitCustom12": "Campo personalizado 12", + "LoanUnitCustom13": "Campo personalizado 13", + "LoanUnitCustom14": "Campo personalizado 14", + "LoanUnitCustom15": "Campo personalizado 15", + "LoanUnitCustom16": "Campo personalizado 16", + "LoanUnitCustom2": "Campo personalizado 2", + "LoanUnitCustom3": "Campo personalizado 3", + "LoanUnitCustom4": "Campo personalizado 4", + "LoanUnitCustom5": "Campo personalizado 5", + "LoanUnitCustom6": "Campo personalizado 6", + "LoanUnitCustom7": "Campo personalizado 7", + "LoanUnitCustom8": "Campo personalizado 8", + "LoanUnitCustom9": "Campo personalizado 9", + "LoanUnitList": "Elementos préstamo", + "LoanUnitName": "Nombre", + "LoanUnitNotes": "Notas", + "LoanUnitRateDay": "Tarifa diaria", + "LoanUnitRateDayCost": "Costo diario", + "LoanUnitRateHalfDay": "Tarifa medio día", + "LoanUnitRateHalfDayCost": "Costo de medio día", + "LoanUnitRateHour": "Tarifa por hora", + "LoanUnitRateHourCost": "Costo por hora", + "LoanUnitRateMonth": "Tasa mensual", + "LoanUnitRateMonthCost": "Costo mensual", + "LoanUnitRateNone": "-", + "LoanUnitRateWeek": "Tarifa semanal", + "LoanUnitRateWeekCost": "Costo semanal", + "LoanUnitRateYear": "Tasa anual", + "LoanUnitRateYearCost": "Costo anual", + "LoanUnitSerial": "Número de serie", + "LoanUnitShadowUnit": "Unidad de sombra", + "Log": "Registro", + "LogFile": "Archivo de registro", + "Logout": "Desconectar", + "MaintenanceExpired": "Mantenimiento expirado", + "MaintenanceExpiredNote": "La suscripción de soporte y actualizaciones ha caducado\nSockeye no se puede actualizar y el soporte ya no está disponible", + "MapUrlTemplate": "Plantilla de URL de mapa", + "MediumLogo": "Logotipo de tamaño mediano", + "Memo": "Memorándum", + "MemoCustom1": "Campo personalizado 1", + "MemoCustom10": "Campo personalizado 10", + "MemoCustom11": "Campo personalizado 11", + "MemoCustom12": "Campo personalizado 12", + "MemoCustom13": "Campo personalizado 13", + "MemoCustom14": "Campo personalizado 14", + "MemoCustom15": "Campo personalizado 15", + "MemoCustom16": "Campo personalizado 16", + "MemoCustom2": "Campo personalizado 2", + "MemoCustom3": "Campo personalizado 3", + "MemoCustom4": "Campo personalizado 4", + "MemoCustom5": "Campo personalizado 5", + "MemoCustom6": "Campo personalizado 6", + "MemoCustom7": "Campo personalizado 7", + "MemoCustom8": "Campo personalizado 8", + "MemoCustom9": "Campo personalizado 9", + "MemoForward": "Reenviar", + "MemoFromID": "Desde", + "MemoList": "Memorándums", + "MemoMessage": "Mensaje", + "MemoRe": "RE:", + "MemoReplied": "Respondido", + "MemoReply": "Respuesta", + "MemoSent": "Enviado", + "MemoSubject": "Asunto", + "MemoToID": "A", + "MemoViewed": "Visualizado", + "MenuHelp": "Ayuda", + "MetricAllocatedMemory": "Asignado (MiB)", + "MetricAttachmentsCount": "Archivos adjuntos (número)", + "MetricAttachmentsMB": "Archivos adjuntos (MiB)", + "MetricAvailableDiskSpace": "Espacio disponible (MiB)", + "MetricBackupMB": "Archivos de respaldo (MiB)", + "MetricCPUMemory": "CPU / Memoria", + "MetricDBSize": "Tamaño de base de datos (MiB)", + "MetricFileStorage": "Almacenamiento de archivos", + "MetricPrivateBytes": "Bytes privados (MiB)", + "MetricTopTablesSize": "Tablas de bases de datos principales (KiB)", + "MetricWorkingSet": "Espacio de trabajo (MiB)", + "More": "Más...", + "MoveSelected": "Mover elementos seleccionados", + "Name": "Nombre", + "NativeDateTimeInput": "Utilice los controles de entrada de fecha y hora estándar del navegador", + "NetPrice": "Neto", + "New": "Nuevo", + "NewLicenseInstalled": "Se ha instalado una nueva licencia. Ahora se cerrará sesión.", + "NewLicenseNotFound": "Actualmente no hay una nueva licencia disponible", + "NewLogin": "Nuevo nombre de inicio de sesión", + "NewPassword": "Nueva contraseña", + "NewStatus": "Nuevo estado", + "NextPMNumber": "Siguiente mantenimiento preventivo", + "NextPONumber": "Siguiente orden de compra", + "NextQuoteNumber": "Próxima cotización", + "NextWorkorderNumber": "Siguiente orden de trabajo", + "NoColor": "Sin color", + "NoData": "Sin datos", + "NoFeaturesAvailable": "No hay funciones habilitadas para su cuenta", + "NoLicenseTitle": "Sin licencia", + "NoResults": "No hay resultados", + "Notification": "Notificación", + "Notifications": "Notificaciones", + "NotificationDeliveryLog":"Notificaciones de usuario", + "NotificationCustomerDeliveryLog":"Notificaciones de clientes", + "NotifyDeliveryAddress": "Entregar a la dirección de correo electrónico", + "NotifyDeliveryMethod": "Método de entrega de notificaciones", + "NotifyDeliveryMethodApp": "Entregar en programa", + "NotifyDeliveryMethodSMTP": "Entregar a la dirección de correo electrónico", + "NotifyEventBackupStatus": "Estado de copia de seguridad", + "NotifyEventContractExpiring": "Vencimiento del contrato", + "NotifyEventCSRAccepted": "Solicitud de servicio al cliente aceptada", + "NotifyEventCSRRejected": "Solicitud de servicio al cliente rechazada", + "NotifyEventCustomerServiceImminent": "Cita de servicio", + "NotifyEventCustomerServiceImminentMessage": "Tiene una próxima cita de servicio en {0}\nUtilice el siguiente enlace para ver los detalles:", + "NotifyEventGeneralNotification": "Notificación general", + "NotifyEventNotifyHealthCheck": "Verificación del estado del sistema de notificación", + "NotifyEventObjectAge": "Edad del objeto desde su creación", + "NotifyEventObjectCreated": "Objeto creado", + "NotifyEventObjectDeleted": "Objeto eliminado", + "NotifyEventObjectModified": "Objeto cambiado", + "NotifyEventOutsideServiceOverdue": "El servicio de terceros está atrasado", + "NotifyEventOutsideServiceReceived": "Servicio de terceros completado, unidad recibida de vuelta", + "NotifyEventPartRequestReceived": "Parte solicitada recibida", + "NotifyEventPMGenerationFailed": "Fallo de generación de mantenimiento preventivo", + "NotifyEventPMInsufficientInventory": "Inventario insuficiente de mantenimiento preventivo", + "NotifyEventPMStopGeneratingDateReached": "Se alcanzó la fecha de 'detención' del mantenimiento preventivo", + "NotifyEventQuoteStatusAge": "Estado de la cotización sin cambios durante el período de tiempo", + "NotifyEventQuoteStatusChange": "Cambio de estado de cotización", + "NotifyEventReminderImminent": "Recordatorio inminente", + "NotifyEventReviewImminent": "Revisión - inminente", + "NotifyEventScheduledOnWorkorder": "Programado en orden de trabajo", + "NotifyEventScheduledOnWorkorderImminent": "Servicio de orden de trabajo programado inminente", + "NotifyEventServerOperationsProblem": "Problema de operaciones del servidor", + "NotifyEventType": "Evento de notificación", + "NotifyEventUnitMeterReadingMultipleExceeded": "Lectura de medidor de unidad excedida (múltiple)", + "NotifyEventUnitWarrantyExpiry": "Garantía de la unidad que vence", + "NotifyEventWorkorderCompleted": "Orden de trabajo completada", + "NotifyEventWorkorderCompletedStatusOverdue": "Orden de trabajo no completada a tiempo", + "NotifyEventWorkorderCreatedForCustomer": "Orden de trabajo creada para la Cliente", + "NotifyEventWorkorderStatusAge": "Estado de la orden de trabajo sin cambios durante el período de tiempo", + "NotifyEventWorkorderStatusChange": "Conjunto de estado de orden de trabajo", + "NotifyEventWorkorderTotalExceedsThreshold": "El monto total de la orden de trabajo excede el umbral", + "NotifyFromAddress": "Notificación SMTP desde la dirección", + "NotifyMailSecurityNone": "Ninguna", + "NotifyMailSecuritySSLTLS": "SSL\\TLS", + "NotifyMailSecurityStartTls": "StartTLS", + "NotifyQueue": "Notificar cola de entrega de eventos", + "NotifySubscription": "Suscripción a notificaciones", + "NotifySubscriptionLinkText": "Cambiar la configuración de notificación:", + "NotifySubscriptionList": "Suscripciones a notificaciones", + "NotifySubscriptionPendingSpan": "Notificar antes del evento", + "NoType": "Sin tipo", + "Now": "Ahora", + "Object": "Objeto", + "ObjectCustomFieldCustomGrid": "Campos personalizados", + "OK": "Aceptar", + "OldPassword": "Contraseña anterior", + "Open": "Abrir", + "Operations": "Operaciones del servidor", + "OpsNotificationSettings": "Configuración de notificaciones", + "OpsTestJob": "Enviar trabajo de prueba", + "OutsideServiceList": "Lista de servicios externos", + "PageOfPageText": "{0}-{1} de {2}", + "Part": "Pieza", + "PartAlternativeWholesalerID": "Mayorista alternativo", + "PartAlternativeWholesalerNumber": "Número de mayorista alternativo", + "PartAssembly": "Montaje de la pieza", + "PartAssemblyCustom1": "Campo personalizado 1", + "PartAssemblyCustom10": "Campo personalizado 10", + "PartAssemblyCustom11": "Campo personalizado 11", + "PartAssemblyCustom12": "Campo personalizado 12", + "PartAssemblyCustom13": "Campo personalizado 13", + "PartAssemblyCustom14": "Campo personalizado 14", + "PartAssemblyCustom15": "Campo personalizado 15", + "PartAssemblyCustom16": "Campo personalizado 16", + "PartAssemblyCustom2": "Campo personalizado 2", + "PartAssemblyCustom3": "Campo personalizado 3", + "PartAssemblyCustom4": "Campo personalizado 4", + "PartAssemblyCustom5": "Campo personalizado 5", + "PartAssemblyCustom6": "Campo personalizado 6", + "PartAssemblyCustom7": "Campo personalizado 7", + "PartAssemblyCustom8": "Campo personalizado 8", + "PartAssemblyCustom9": "Campo personalizado 9", + "PartAssemblyList": "Montajes de piezas", + "PartAssemblyName": "Nombre de montaje de la pieza", + "PartAssemblyNotes": "Notas", + "PartByWarehouseInventoryList": "Inventario de piezas", + "PartByWarehouseInventoryMinStockLevel": "Cantidad mínima", + "PartByWarehouseInventoryQtyOnOrderCommitted": "Cantidad en pedido comprometida", + "PartByWarehouseInventoryQuantityOnOrder": "En pedido", + "PartByWarehouseInventoryReorderQuantity": "Cantidad de reposición", + "PartCost": "Coste", + "PartCustom1": "Campo personalizado 1", + "PartCustom10": "Campo personalizado 10", + "PartCustom11": "Campo personalizado 11", + "PartCustom12": "Campo personalizado 12", + "PartCustom13": "Campo personalizado 13", + "PartCustom14": "Campo personalizado 14", + "PartCustom15": "Campo personalizado 15", + "PartCustom16": "Campo personalizado 16", + "PartCustom2": "Campo personalizado 2", + "PartCustom3": "Campo personalizado 3", + "PartCustom4": "Campo personalizado 4", + "PartCustom5": "Campo personalizado 5", + "PartCustom6": "Campo personalizado 6", + "PartCustom7": "Campo personalizado 7", + "PartCustom8": "Campo personalizado 8", + "PartCustom9": "Campo personalizado 9", + "PartDescription": "Parte descripción", + "PartInventoryAdjustment": "Ajuste inventario de piezas", + "PartInventoryBalance": "Disponible", + "PartInventoryId": "ID de inventario", + "PartInventoryDataList": "Inventario de piezas", + "PartInventoryTransaction": "Transacción de inventario", + "PartInventoryTransactionDescription": "Descripción", + "PartInventoryTransactionEntryDate": "Fecha", + "PartInventoryTransactionList": "Transacciones de inventario", + "PartInventoryTransactionQuantity": "Cantidad", + "PartInventoryTransactionSource": "Origen de la transacción", + "PartList": "Piezas", + "PartManufacturerID": "Fabricante", + "PartManufacturerNumber": "Número de fabricante", + "PartName": "Nombre de la pieza", + "PartNotes": "Notas", + "PartRestockRequiredByVendorList": "Reposición de piezas requerida proveedor", + "PartRetail": "Minorista", + "PartSerial": "Pieza registrada", + "PartSerialNumbersAvailable": "Números de serie disponibles", + "PartSerialWarehouseID": "Almacén de la pieza", + "PartStockingLevels": "Niveles de existencias de piezas", + "PartUPC": "UPC", + "PartWarehouse": "Almacén de la pieza", + "PartWarehouseCustom1": "Campo personalizado 1", + "PartWarehouseCustom10": "Campo personalizado 10", + "PartWarehouseCustom11": "Campo personalizado 11", + "PartWarehouseCustom12": "Campo personalizado 12", + "PartWarehouseCustom13": "Campo personalizado 13", + "PartWarehouseCustom14": "Campo personalizado 14", + "PartWarehouseCustom15": "Campo personalizado 15", + "PartWarehouseCustom16": "Campo personalizado 16", + "PartWarehouseCustom2": "Campo personalizado 2", + "PartWarehouseCustom3": "Campo personalizado 3", + "PartWarehouseCustom4": "Campo personalizado 4", + "PartWarehouseCustom5": "Campo personalizado 5", + "PartWarehouseCustom6": "Campo personalizado 6", + "PartWarehouseCustom7": "Campo personalizado 7", + "PartWarehouseCustom8": "Campo personalizado 8", + "PartWarehouseCustom9": "Campo personalizado 9", + "PartWarehouseList": "Almacenes de piezas", + "PartWarehouseName": "Nombre de almacén de la pieza", + "PartWarehouseNotes": "Notas", + "PartWholesalerID": "Mayorista", + "PartWholesalerNumber": "Número de mayorista", + "PasswordResetMessageBody": "Hola, {user_name}:\n\nTu cuenta en línea para el servicio estará disponible después de que establezcas tu contraseña.\nPuedes usar el siguiente enlace durante las próximas 48 horas para establecer tu contraseña.\n\nConfigura tu contraseña: {action_link}\n\nSi no solicitó un restablecimiento de cuenta o contraseña, ignore este correo electrónico.\n\nGracias,\n{registered_to}", + "PasswordResetMessageTitle": "Tu cuenta en línea está lista", + "PickListTemplate": "Plantilla de lista de selección", + "PickListTemplates": "Seleccionar plantillas de lista", + "PM": "Mantenimiento preventivo", + "PMItem": "Elemento de mantenimiento preventivo", + "PMItemExpense": "Mantenimiento preventivo - gasto", + "PMItemLabor": "Mantenimiento preventivo - labor", + "PMItemLoan": "Mantenimiento preventivo - préstamo", + "PMItemOutsideService":"Mantenimiento preventivo - servicio externo", + "PMItemPart":"Mantenimiento preventivo - pieza", + "PMItemScheduledUser":"Mantenimiento preventivo - usuario programado", + "PMItemTravel":"Mantenimiento preventivo - viajes", + "PMItemTask":"Mantenimiento preventivo - tareas", + "PMItemUnit":"Mantenimiento preventivo - unidades", + "PMList": "Mantenimiento preventivo", + "PMNextServiceDate": "Fecha del próximo servicio", + "PMNextWoGenerateDate": "Siguiente evento de generación", + "PMSerialNumber": "Número", + "PMStopGeneratingDate": "Fecha fin de generación", + "Price": "Precio", + "PriceOverride": "Anulación de precio", + "Print": "Imprimir", + "Processed":"Fecha de procesamiento", + "ProcessingJob": "Procesando trabajo del servidor", + "Project": "Proyecto", + "ProjectAccountNumber": "Número de cuenta", + "ProjectCustom1": "Campo personalizado 1", + "ProjectCustom10": "Campo personalizado 10", + "ProjectCustom11": "Campo personalizado 11", + "ProjectCustom12": "Campo personalizado 12", + "ProjectCustom13": "Campo personalizado 13", + "ProjectCustom14": "Campo personalizado 14", + "ProjectCustom15": "Campo personalizado 15", + "ProjectCustom16": "Campo personalizado 16", + "ProjectCustom2": "Campo personalizado 2", + "ProjectCustom3": "Campo personalizado 3", + "ProjectCustom4": "Campo personalizado 4", + "ProjectCustom5": "Campo personalizado 5", + "ProjectCustom6": "Campo personalizado 6", + "ProjectCustom7": "Campo personalizado 7", + "ProjectCustom8": "Campo personalizado 8", + "ProjectCustom9": "Campo personalizado 9", + "ProjectDateCompleted": "Completado con fecha", + "ProjectDateStarted": "Fecha de inicio", + "ProjectList": "Proyectos", + "ProjectName": "Nombre del proyecto", + "ProjectNotes": "Notas", + "ProjectProjectOverseerID": "Supervisor del proyecto", + "PurchaseLicense": "Comprar una licencia", + "PurchaseOrder": "Orden de compra", + "PurchaseOrderCustom1": "Campo personalizado 1", + "PurchaseOrderCustom10": "Campo personalizado 10", + "PurchaseOrderCustom11": "Campo personalizado 11", + "PurchaseOrderCustom12": "Campo personalizado 12", + "PurchaseOrderCustom13": "Campo personalizado 13", + "PurchaseOrderCustom14": "Campo personalizado 14", + "PurchaseOrderCustom15": "Campo personalizado 15", + "PurchaseOrderCustom16": "Campo personalizado 16", + "PurchaseOrderCustom2": "Campo personalizado 2", + "PurchaseOrderCustom3": "Campo personalizado 3", + "PurchaseOrderCustom4": "Campo personalizado 4", + "PurchaseOrderCustom5": "Campo personalizado 5", + "PurchaseOrderCustom6": "Campo personalizado 6", + "PurchaseOrderCustom7": "Campo personalizado 7", + "PurchaseOrderCustom8": "Campo personalizado 8", + "PurchaseOrderCustom9": "Campo personalizado 9", + "PurchaseOrderDropShipToCustomerID": "Envío directo al cliente", + "PurchaseOrderExpectedReceiveDate": "Previsión", + "PurchaseOrderItem": "Elemento de la orden de compra", + "PurchaseOrderItemLineTotal": "Total línea", + "PurchaseOrderItemList": "Encargar artículos", + "PurchaseOrderItemNetTotal": "Total neto", + "PurchaseOrderItemPartName": "Nombre de la pieza", + "PurchaseOrderItemPartNumber": "Número de pieza", + "PurchaseOrderItemPartRequestedByID": "Solicitado por", + "PurchaseOrderItemPurchaseOrderCost": "Coste orden de compra", + "PurchaseOrderItemQuantityOrdered": "Cantidad solicitada", + "PurchaseOrderItemQuantityReceived": "Cantidad recibida", + "PurchaseOrderItemSerialNumbers": "Números seriales", + "PurchaseOrderItemUIOrderedFrom": "Pedido a", + "PurchaseOrderItemVendorPartNumber": "Proveedor Número", + "PurchaseOrderItemWorkOrderNumber": "Núm. pedido", + "PurchaseOrderNotes": "Notas", + "PurchaseOrderOrderedDate": "Fecha del pedido", + "PurchaseOrderPONumber": "Número de pedido", + "PurchaseOrderReceiptItemQuantityReceivedErrorInvalid": "El valor debe ser positivo y no mayor que la cantidad ordenada", + "PurchaseOrderReceiptItemReceiptCost": "Costo recibido", + "PurchaseOrderReceiptReceivedDate": "Recibido con fecha", + "PurchaseOrderReceiptText1": "Text1", + "PurchaseOrderReceiptText2": "Text2", + "PurchaseOrderReferenceNumber": "Número de referencia", + "PurchaseOrderStatus": "Estado de la orden de compra", + "PurchaseOrderStatusClosedFullReceived": "Cerrado - todo recibido", + "PurchaseOrderStatusClosedNoneReceived": "Cerrado - nada recibido", + "PurchaseOrderStatusClosedPartialReceived": "Cerrado - recibido en parte", + "PurchaseOrderStatusOpenNotYetOrdered": "Abierto - aún no pedido", + "PurchaseOrderStatusOpenOrdered": "Abierto - en pedido", + "PurchaseOrderStatusOpenPartialReceived": "Abierto - recibido en parte", + "PurchaseOrderUICopyToPurchaseOrder": "Copiar en orden de compra", + "PurchaseOrderUIRestockList": "Lista de reposición", + "PurchaseOrderVendorMemo": "Memorándum proveedor", + "QuantityRequired": "Cantidad requerida", + "Quote": "Presupuesto", + "QuoteDateApproved": "Aprobado", + "QuoteDateSubmitted": "Presentado", + "QuoteIntroduction": "Texto de presentación", + "QuoteItem": "Elemento de presupuesto", + "QuoteItemExpense": "Presupuesto - gasto", + "QuoteItemLabor": "Presupuesto - labor", + "QuoteItemLoan": "Presupuesto - préstamo", + "QuoteItemOutsideService":"Presupuesto - servicio externo", + "QuoteItemPart":"Presupuesto - pieza", + "QuoteItemScheduledUser":"Presupuesto - usuario programado", + "QuoteItemTravel":"Presupuesto - viajes", + "QuoteItemTask":"Presupuesto - tareas", + "QuoteItemUnit":"Presupuesto - unidades", + "QuoteList": "Presupuestos", + "QuotePreparedByID": "Preparado por el usuario", + "QuoteQuoteRequestDate": "Solicitado", + "QuoteQuoteStatusType": "Estado", + "QuoteSerialNumber": "Número", + "QuoteStatusList": "Lista de estado de cotización", + "QuoteValidUntilDate": "Válido hasta", + "RateAccountNumber": "Número de cuenta", + "RateCharge": "Cargo minorista", + "RateContractRate": "Tarifa de contrato", + "RateUnitChargeDescriptionID": "Descripción cargo unidad", + "ReadOnly": "Sólo lectura", + "ReceiveAll": "Recibe todo", + "RecentWorkOrders": "Órdenes de trabajo recientes", + "RecordHistory": "Historial del registro", + "Refresh": "Refrescar...", + "Region": "Región", + "RegisteredUser": "Usuario registrado", + "Reminder": "Recordatorio", + "ReminderColor": "Color", + "ReminderCustom1": "Campo personalizado 1", + "ReminderCustom10": "Campo personalizado 10", + "ReminderCustom11": "Campo personalizado 11", + "ReminderCustom12": "Campo personalizado 12", + "ReminderCustom13": "Campo personalizado 13", + "ReminderCustom14": "Campo personalizado 14", + "ReminderCustom15": "Campo personalizado 15", + "ReminderCustom16": "Campo personalizado 16", + "ReminderCustom2": "Campo personalizado 2", + "ReminderCustom3": "Campo personalizado 3", + "ReminderCustom4": "Campo personalizado 4", + "ReminderCustom5": "Campo personalizado 5", + "ReminderCustom6": "Campo personalizado 6", + "ReminderCustom7": "Campo personalizado 7", + "ReminderCustom8": "Campo personalizado 8", + "ReminderCustom9": "Campo personalizado 9", + "ReminderList": "Recordatorios", + "ReminderName": "Nombre", + "ReminderNotes": "Notas", + "ReminderStartDate": "Inicio", + "ReminderStopDate": "Fin", + "Remove": "Eliminar", + "RemoveRoles": "Quien puede remover", + "RenderingReport": "Generación de informes en curso", + "RepeatInterval": "Intervalo de repetición", + "Replace": "Reemplazar", + "Report": "Informe", + "ReportDesignReport": "Editar informe", + "ReportDisplayHeaderFooter": "Mostrar encabezado y pie de página", + "ReportEditorData": "Data de muestra", + "ReportEditorMobileWarning": "Editor no compatible con dispositivos móviles", + "ReportEditorProperties": "Propiedades", + "ReportFooterTemplate": "Plantilla de pie de página", + "ReportHeaderTemplate": "Plantilla de encabezado", + "ReportIncludeAllWorkOrderItemDescendants": "Incluir todos los descendientes del artículo de la orden de trabajo", + "ReportLandscape": "Paisaje", + "ReportList": "Plantillas de informes", + "ReportMarginOptionsBottom": "Margen inferior", + "ReportMarginOptionsLeft": "Margen izquierdo", + "ReportMarginOptionsRight": "Margen derecho", + "ReportMarginOptionsTop": "Margen superior", + "ReportName": "Nombre", + "ReportNotes": "Notas", + "ReportPaperFormat": "Formato de papel", + "ReportPdfOptions": "Opciones de PDF", + "ReportPreferCSSPageSize": "Prefiere el tamaño de página CSS", + "ReportPrintBackground": "Imprimir fondo", + "ReportRenderTimeOut": "El informe estaba tardando demasiados minutos en procesarse, el límite está establecido en {0}", + "ReportScale": "Escala", + "ReportTemplate": "Modelo", + "RequestEvaluationLicense": "Solicitar una licencia de evaluación", + "ResetToDefault": "Restablecen a los predeterminados", + "Review": "Revisión", + "ReviewAssignedByUserId": "Asignado por", + "ReviewCompletedDate": "Fecha completada", + "ReviewCompletionNotes": "Notas de finalización", + "ReviewCustom1": "Campo personalizado 1", + "ReviewCustom10": "Campo personalizado 10", + "ReviewCustom11": "Campo personalizado 11", + "ReviewCustom12": "Campo personalizado 12", + "ReviewCustom13": "Campo personalizado 13", + "ReviewCustom14": "Campo personalizado 14", + "ReviewCustom15": "Campo personalizado 15", + "ReviewCustom16": "Campo personalizado 16", + "ReviewCustom2": "Campo personalizado 2", + "ReviewCustom3": "Campo personalizado 3", + "ReviewCustom4": "Campo personalizado 4", + "ReviewCustom5": "Campo personalizado 5", + "ReviewCustom6": "Campo personalizado 6", + "ReviewCustom7": "Campo personalizado 7", + "ReviewCustom8": "Campo personalizado 8", + "ReviewCustom9": "Campo personalizado 9", + "ReviewDate": "Fecha de revisión", + "ReviewList": "Lista de revisión", + "ReviewName": "Nombre", + "ReviewNotes": "Notas", + "ReviewOverDue": "Atrasado", + "ReviewUserId": "Asignado a", + "RowsPerPage": "Filas por página", + "Save": "Guardar", + "SaveACopy": "Guardar una copia", + "SaveRecordToProceed": "Este registro debe guardarse para continuar", + "Schedule": "Programación", + "Schedule4Day": "4 días", + "ScheduleCategory": "Equipo - día", + "ScheduleConflict": "Conflicto de horario", + "ScheduleDay": "Día", + "ScheduleDayView": "Sólo un día", + "ScheduleEditReminder": "Editar recordatorio seleccionado", + "ScheduleEditScheduleableUserGroup": "Editar grupos de usuarios programables", + "ScheduleEditWorkOrder": "Editar pedido seleccionado", + "ScheduleFirstHour": "Primera hora para mostrar en la vista de día", + "ScheduleMonth": "Mes", + "ScheduleOptions": "Configuración de programación", + "ScheduleShowTypes": "Elementos para mostrar", + "ScheduleWeek": "Semana", + "ScheduleWOColorFrom": "Fuente de color de la orden de trabajo", + "SchemaVersion": "Versión de esquema", + "Search": "Buscar", + "SeedLevel": "Tamaño de datos de muestra", + "SeedLevelHuge": "Enorme: conjunto de datos muy grande (aproximadamente 1 hora para procesar)", + "SeedLevelLarge": "Grande: varias regiones con personal completo (aproximadamente 10 minutos para procesar)", + "SeedLevelMedium": "Medio: muchos técnicos de servicio y personal de soporte (aproximadamente 5 minutos para procesar)", + "SeedLevelSmall": "Pequeño taller de servicio (aproximadamente 1 minuto para procesar)", + "SelectAlternateAddress": "Seleccionar dirección alternativa", + "SelectedItems": "Elementos seleccionados", + "SelectItem": "Seleccionar", + "SelectRoles": "Quien puede seleccionar", + "SendEvaluationRequest": "Enviar petición", + "SendPasswordResetCode": "Enviar correo electrónico de restablecimiento de contraseña", + "Sequence": "Secuencia", + "Server": "Servidor", + "ServerAddress": "Dirección del servidor", + "ServerJob": "Trabajo del servidor", + "ServerJobs": "Cola de trabajo", + "ServerLog": "Registro del servidor", + "ServerMetrics": "Métricas del servidor", + "ServerProfiler": "Profiler", + "ServerState": "Estado del servidor", + "ServerStateLoginRestricted": "Inicio de sesión restringido debido a la configuración del estado del servidor", + "ServerStateMigrateMode": "Modo de migración del servidor", + "ServerStateOpen": "Abrir", + "ServerStateOps": "Solo operaciones del sistema", + "ServerStateReason": "Razón", + "ServerTime": "Hora del servidor", + "Service": "Servicio", + "ServiceHistory": "Historial del servicio", + "ServicePreventiveMaintenance": "MP", + "ServiceRate": "Tasa de trabajo", + "ServiceRateCustom1": "Campo personalizado 1", + "ServiceRateCustom10": "Campo personalizado 10", + "ServiceRateCustom11": "Campo personalizado 11", + "ServiceRateCustom12": "Campo personalizado 12", + "ServiceRateCustom13": "Campo personalizado 13", + "ServiceRateCustom14": "Campo personalizado 14", + "ServiceRateCustom15": "Campo personalizado 15", + "ServiceRateCustom16": "Campo personalizado 16", + "ServiceRateCustom2": "Campo personalizado 2", + "ServiceRateCustom3": "Campo personalizado 3", + "ServiceRateCustom4": "Campo personalizado 4", + "ServiceRateCustom5": "Campo personalizado 5", + "ServiceRateCustom6": "Campo personalizado 6", + "ServiceRateCustom7": "Campo personalizado 7", + "ServiceRateCustom8": "Campo personalizado 8", + "ServiceRateCustom9": "Campo personalizado 9", + "ServiceRateList": "Tasas laborales", + "ServiceRateNotes": "Notas", + "SetLoginPassword": "Definir acceso y contraseña", + "Settings":"Ajustes", + "ShutDownServer": "Apagar el servidor", + "SmallLogo": "Logotipo de tamaño pequeño", + "SmtpAccount": "Nombre de usuario de SMTP", + "SmtpDeliveryActive": "Notificación SMTP activa", + "SmtpPassword": "Contraseña SMTP", + "SmtpServerAddress": "Dirección SMTP", + "SmtpServerPort": "Puerto SMTP", + "SoftDelete": "Marcar para borrar", + "SoftDeleteAll": "Marcar * todo * para borrar", + "Sort": "Ordenar", + "StartAttachmentMaintenanceJob": "Iniciar trabajo de mantenimiento de archivos adjuntos", + "StartEvaluation": "Iniciar evaluación de prueba", + "StartJob": "Comenzar trabajo", + "Statistics": "Estadísticas", + "Status": "Estado", + "StatusColor": "Color", + "StatusCompleted": "Es un estado completo", + "StatusLocked": "Es un estado de bloqueo", + "StatusName": "Nombre", + "StatusNotes": "Notas", + "StopWords1": "a acuerdo adelante ademas además adrede ahi ahí ahora al alli allí alrededor antano antaño ante antes apenas aproximadamente aquel aquél aquella aquélla aquellas aquéllas aquello aquellos aquéllos aqui aquí arribaabajo asi así aun aún aunque b bajo", + "StopWords2": "bastante bien breve c casi cerca claro como cómo con conmigo contigo contra cual cuál cuales cuáles cuando cuándo cuanta cuánta cuantas cuántas cuanto cuánto cuantos cuántos d de debajo del delante demasiado dentro deprisa desde despacio despues después", + "StopWords3": "detras detrás dia día dias días donde dónde dos durante e el él ella ellas ellos en encima enfrente enseguida entre es esa ésa esas ésas ese ése eso esos ésos esta está ésta estado estados estan están estar estas éstas este éste esto estos éstos ex", + "StopWords4": "excepto f final fue fuera fueron g general gran h ha habia había habla hablan hace hacia han hasta hay horas hoy i incluso informo informó j junto k l la lado las le lejos lo los luego m mal mas más mayor me medio mejor menos menudo mi mí mia mía mias", + "StopWords5": "mías mientras mio mío mios míos mis mismo mucho muy n nada nadie ninguna no nos nosotras nosotros nuestra nuestras nuestro nuestros nueva nuevo nunca o os otra otros p pais paìs para parte pasado peor pero poco por porque pronto proximo próximo puede q", + "StopWords6": "qeu que qué quien quién quienes quiénes quiza quizá quizas quizás raras repente s salvo se sé segun según ser sera será si sí sido siempre sin sobre solamente solo sólo son soyos su supuesto sus suya suyas suyo t tal tambien también tampoco tarde te", + "StopWords7": "temprano ti tiene todavia todavía todo todos tras tu tú tus tuya tuyas tuyo tuyos u un una unas uno unos usted ustedes v veces vez vosotras vosotros vuestra vuestras vuestro vuestros w x y ya yo z", + "SupportedUntil": "Soporte y actualizaciones fecha de caducidad", + "Table": "Tabla", + "Tag": "Etiqueta", + "TaggedWith": "Etiquetado con", + "Tags": "Etiquetas", + "Task": "Tarea", + "TaskGroup": "Grupo de tareas", + "TaskGroupList": "Grupos de tareas", + "TaskGroupName": "Nombre grupo de tareas", + "TaskGroupNotes": "Notas", + "TaskList": "Tareas", + "Tax": "Impuesto", + "TaxAAmt": "Importe del impuesto 'A'", + "TaxBAmt": "Importe del impuesto 'B'", + "TaxCode": "Código fiscal", + "TaxCodeCustom1": "Campo personalizado 1", + "TaxCodeCustom10": "Campo personalizado 10", + "TaxCodeCustom11": "Campo personalizado 11", + "TaxCodeCustom12": "Campo personalizado 12", + "TaxCodeCustom13": "Campo personalizado 13", + "TaxCodeCustom14": "Campo personalizado 14", + "TaxCodeCustom15": "Campo personalizado 15", + "TaxCodeCustom16": "Campo personalizado 16", + "TaxCodeCustom2": "Campo personalizado 2", + "TaxCodeCustom3": "Campo personalizado 3", + "TaxCodeCustom4": "Campo personalizado 4", + "TaxCodeCustom5": "Campo personalizado 5", + "TaxCodeCustom6": "Campo personalizado 6", + "TaxCodeCustom7": "Campo personalizado 7", + "TaxCodeCustom8": "Campo personalizado 8", + "TaxCodeCustom9": "Campo personalizado 9", + "TaxCodeDefault": "Error: este código fiscal se usa por omisión en los ajustes globales y no puede borrarse ni desactivarse", + "TaxCodeList": "Códigos fiscales", + "TaxCodeName": "Nombre código fiscal", + "TaxCodeNotes": "Notas", + "TaxCodeTaxA": "Impuesto \"A\"", + "TaxCodeTaxB": "Impuesto \"B\"", + "TaxCodeTaxOnTax": "Impuesto sobre impuesto", + "TechSignature": "Firma del técnico", + "TemplateTokens":"Fichas de sustitución", + "TestSMTPSettings": "Enviar mensaje de prueba", + "TestToAddress": "Enviar prueba a", + "ThankYouForEvaluating": "Gracias por probar Sockeye. Utilice los siguientes enlaces para ayudar a explorar Sockeye y ver si es una buena opción para su organización.", + "TimedOut": "Tiempo de espera agotado", + "TimeSpan":"Espacio de tiempo", + "TimeSpanDays": "días", + "TimeSpanHours": "horas", + "TimeSpanMinutes": "minutos", + "TimeSpanMonths": "meses", + "TimeSpanSeconds": "segundos", + "TimeSpanYears": "años", + "TimeStamp": "Sello de tiempo", + "TimeToCompletion": "Tiempo hasta el estado 'Completado'", + "TimeZone": "Anular la zona horaria predeterminada", + "TooManyResults": "Demasiados resultados para mostrar todos", + "Total": "Total", + "Translation": "Traducción", + "TranslationDisplayText": "Texto estándar de visualización", + "TranslationKey": "Clave", + "TranslationList": "Traducciones", + "TravelRate": "Tasa de viaje", + "TravelRateCustom1": "Campo personalizado 1", + "TravelRateCustom10": "Campo personalizado 10", + "TravelRateCustom11": "Campo personalizado 11", + "TravelRateCustom12": "Campo personalizado 12", + "TravelRateCustom13": "Campo personalizado 13", + "TravelRateCustom14": "Campo personalizado 14", + "TravelRateCustom15": "Campo personalizado 15", + "TravelRateCustom16": "Campo personalizado 16", + "TravelRateCustom2": "Campo personalizado 2", + "TravelRateCustom3": "Campo personalizado 3", + "TravelRateCustom4": "Campo personalizado 4", + "TravelRateCustom5": "Campo personalizado 5", + "TravelRateCustom6": "Campo personalizado 6", + "TravelRateCustom7": "Campo personalizado 7", + "TravelRateCustom8": "Campo personalizado 8", + "TravelRateCustom9": "Campo personalizado 9", + "TravelRateList": "Tarifas de viaje", + "TravelRateNotes": "Notas", + "TrialSeeder": "Datos iniciales de prueba", + "True": "Verdadero", + "TypeToSearchOrAdd": "Comience a escribir para buscar o agregar", + "UiFieldDataType": "Tipo de datos del campo", + "UiFieldDataTypesCurrency": "Dinero", + "UiFieldDataTypesDateOnly": "Fecha", + "UiFieldDataTypesDateTime": "Fecha y hora", + "UiFieldDataTypesDecimal": "Decimal", + "UiFieldDataTypesInteger": "Entero", + "UiFieldDataTypesText": "Texto", + "UiFieldDataTypesTimeOnly": "Hora", + "UiFieldDataTypesTrueFalse": "Verdadero/falso", + "Undelete": "Recuperar", + "Unit": "Unidad", + "UnitBoughtHere": "Adquirido aquí", + "UnitCustom1": "Campo personalizado 1", + "UnitCustom10": "Campo personalizado 10", + "UnitCustom11": "Campo personalizado 11", + "UnitCustom12": "Campo personalizado 12", + "UnitCustom13": "Campo personalizado 13", + "UnitCustom14": "Campo personalizado 14", + "UnitCustom15": "Campo personalizado 15", + "UnitCustom16": "Campo personalizado 16", + "UnitCustom2": "Campo personalizado 2", + "UnitCustom3": "Campo personalizado 3", + "UnitCustom4": "Campo personalizado 4", + "UnitCustom5": "Campo personalizado 5", + "UnitCustom6": "Campo personalizado 6", + "UnitCustom7": "Campo personalizado 7", + "UnitCustom8": "Campo personalizado 8", + "UnitCustom9": "Campo personalizado 9", + "UnitDescription": "Descripción", + "UnitList": "Unidades de clientes", + "UnitMetered": "Unidad medida", + "UnitMeterReading": "Lectura de medición de unidad", + "UnitMeterReadingDescription": "Descripción de la medición", + "UnitMeterReadingList": "Lista lectura de medición de unidad", + "UnitMeterReadingMeter": "Lectura de medición de unidad", + "UnitMeterReadingMeterDate": "Fecha lectura de medición", + "UnitMeterReadingWorkOrderItemID": "Lectura medición en pedido", + "UnitModel": "Modelo de unidad", + "UnitModelCustom1": "Campo personalizado 1", + "UnitModelCustom10": "Campo personalizado 10", + "UnitModelCustom11": "Campo personalizado 11", + "UnitModelCustom12": "Campo personalizado 12", + "UnitModelCustom13": "Campo personalizado 13", + "UnitModelCustom14": "Campo personalizado 14", + "UnitModelCustom15": "Campo personalizado 15", + "UnitModelCustom16": "Campo personalizado 16", + "UnitModelCustom2": "Campo personalizado 2", + "UnitModelCustom3": "Campo personalizado 3", + "UnitModelCustom4": "Campo personalizado 4", + "UnitModelCustom5": "Campo personalizado 5", + "UnitModelCustom6": "Campo personalizado 6", + "UnitModelCustom7": "Campo personalizado 7", + "UnitModelCustom8": "Campo personalizado 8", + "UnitModelCustom9": "Campo personalizado 9", + "UnitModelDiscontinued": "Descontinuado", + "UnitModelDiscontinuedDate": "Descontinuado con fecha", + "UnitModelIntroducedDate": "Introducido con fecha", + "UnitModelLifeTimeWarranty": "Garantía de por vida", + "UnitModelName": "Nombre modelo unidad", + "UnitModelNotes": "Notas", + "UnitModels": "Modelos de unidad", + "UnitModelUPC": "UPC", + "UnitModelVendorID": "Unit model vendor", + "UnitModelWarrantyLength": "Duración de la garantía", + "UnitModelWarrantyTerms": "Condiciones de la garantía", + "UnitNotes": "Notas", + "UnitOfMeasure": "Unidad de medida", + "UnitOverrideLength": "Duración garantía", + "UnitOverrideLifeTime": "Anular garantía de por vida", + "UnitOverrideWarranty": "Garantía específica", + "UnitOverrideWarrantyTerms": "Condiciones garantía específica", + "UnitParentUnitID": "Unidad matriz de esta", + "UnitPurchasedDate": "Fecha de compra", + "UnitPurchaseFromID": "Adquirido a", + "UnitReceipt": "Recibo número", + "UnitReplacedByUnitID": "Reemplazado por unidad", + "UnitSerial": "Número de serie", + "UnitText1": "Text1", + "UnitText2": "Text2", + "UnitText3": "Text3", + "UnitText4": "Text4", + "UnitUnitHasOwnAddress": "La unidad tiene dirección propia", + "UnitWarrantyInfo": "Información de garantía", + "UpdateAvailable": "Actualización disponible. ¿Instalarlo ahora?", + "Upload": "Cargar", + "User": "Usuario", + "UserColor": "Color del usuario", + "UserCountExceeded": "Servidor cerrado debido a que se superó la licencia para los usuarios del tipo de servicio activo", + "UserCustom1": "Campo personalizado 1", + "UserCustom10": "Campo personalizado 10", + "UserCustom11": "Campo personalizado 11", + "UserCustom12": "Campo personalizado 12", + "UserCustom13": "Campo personalizado 13", + "UserCustom14": "Campo personalizado 14", + "UserCustom15": "Campo personalizado 15", + "UserCustom16": "Campo personalizado 16", + "UserCustom2": "Campo personalizado 2", + "UserCustom3": "Campo personalizado 3", + "UserCustom4": "Campo personalizado 4", + "UserCustom5": "Campo personalizado 5", + "UserCustom6": "Campo personalizado 6", + "UserCustom7": "Campo personalizado 7", + "UserCustom8": "Campo personalizado 8", + "UserCustom9": "Campo personalizado 9", + "UserEmailAddress": "Dirección e-mail del usuario", + "UserEmployeeNumber": "Número de empleado", + "UserInterfaceSettings": "Interfaz de usuario", + "UserList": "Usuarios", + "UserLogin": "Nombre de acceso", + "UserNotes": "Notas", + "UserOptions": "Opciones de usuario", + "UserPageAddress": "Dirección buscapersonas", + "UserPhone1": "Teléfono1", + "UserPhone2": "Teléfono 2", + "UserPreferences": "Preferencias del usuario", + "UserSettings": "Configuración de usuario", + "UserTimeZoneOffset": "Diferencia horaria con UTC", + "UserType": "Tipo de usuario", + "UserTypeCustomer": "Usuario cliente", + "UserTypeHeadOffice": "Usuario de la oficina central", + "UserTypeNotService": "No es usuario de tipo de servicio", + "UserTypeService": "Usuario de tipo de servicio", + "UserTypeServiceContractor": "Subcontratista", + "Vendor": "Proveedor", + "VendorAccountNumber": "Número de cuenta", + "VendorAlertNotes": "Notas emergentes", + "VendorContact": "Contact", + "VendorContactNotes": "Other contacts", + "VendorCustom1": "Campo personalizado 1", + "VendorCustom10": "Campo personalizado 10", + "VendorCustom11": "Campo personalizado 11", + "VendorCustom12": "Campo personalizado 12", + "VendorCustom13": "Campo personalizado 13", + "VendorCustom14": "Campo personalizado 14", + "VendorCustom15": "Campo personalizado 15", + "VendorCustom16": "Campo personalizado 16", + "VendorCustom2": "Campo personalizado 2", + "VendorCustom3": "Campo personalizado 3", + "VendorCustom4": "Campo personalizado 4", + "VendorCustom5": "Campo personalizado 5", + "VendorCustom6": "Campo personalizado 6", + "VendorCustom7": "Campo personalizado 7", + "VendorCustom8": "Campo personalizado 8", + "VendorCustom9": "Campo personalizado 9", + "VendorEmail": "Email", + "VendorList": "Proveedores", + "VendorName": "Nombre del proveedor", + "VendorNotes": "Notas", + "VendorPhone1": "Teléfono", + "VendorPhone2": "Fax", + "VendorPhone3": "Teléfono de casa", + "VendorPhone4": "Móvil", + "VendorPhone5": "Buscapersonas", + "Version": "versión", + "ViewEULA": "Ver acuerdo de licencia", + "ViewServerConfiguration": "Información del servidor", + "Warranty": "Garantía", + "WarrantyExpires": "Válido hasta", + "WebAddress": "Dirección web", + "Welcome": "Bienvenido a Sockeye", + "WhoWillBeNotified":"¿Quién será notificado?", + "WorkOrder": "Orden de trabajo", + "WorkOrderAge": "Age", + "WorkOrderCloseByDate": "Fecha para completar", + "WorkOrderContactCustomerHeadOfficeTaggedWith": "Permitir solo orden de trabajo, contactar, cliente u oficina central con cualquiera de estas etiquetas", + "WorkOrderConvertAllScheduledUsersToLabor": "Convertir todo en mano de obra", + "WorkOrderConvertScheduledUserToLabor": "Convertir usuario programado en mano de obra", + "WorkOrderCustom1": "Campo personalizado 1", + "WorkOrderCustom10": "Campo personalizado 10", + "WorkOrderCustom11": "Campo personalizado 11", + "WorkOrderCustom12": "Campo personalizado 12", + "WorkOrderCustom13": "Campo personalizado 13", + "WorkOrderCustom14": "Campo personalizado 14", + "WorkOrderCustom15": "Campo personalizado 15", + "WorkOrderCustom16": "Campo personalizado 16", + "WorkOrderCustom2": "Campo personalizado 2", + "WorkOrderCustom3": "Campo personalizado 3", + "WorkOrderCustom4": "Campo personalizado 4", + "WorkOrderCustom5": "Campo personalizado 5", + "WorkOrderCustom6": "Campo personalizado 6", + "WorkOrderCustom7": "Campo personalizado 7", + "WorkOrderCustom8": "Campo personalizado 8", + "WorkOrderCustom9": "Campo personalizado 9", + "WorkOrderCustomerContactName": "Contacto", + "WorkOrderCustomerReferenceNumber": "Referencia cliente", + "WorkOrderCustomerTaggedWith": "Orden de trabajo o cliente con cualquiera de estas etiquetas", + "WorkOrderErrorLocked": "La orden de trabajo está actualmente configurada en un estado bloqueado y no se puede cambiar", + "WorkOrderFromPMID": "M.P. origen", + "WorkOrderFromQuoteID": "Presupuesto origen", + "WorkOrderGenerateUnit": "Generate unit from selected part", + "WorkOrderInternalReferenceNumber": "Referencia interna", + "WorkOrderInvoiceNumber": "Número de factura", + "WorkOrderItem": "Elemento del pedido", + "WorkOrderItemCustom1": "Campo personalizado 1", + "WorkOrderItemCustom10": "Campo personalizado 10", + "WorkOrderItemCustom11": "Campo personalizado 11", + "WorkOrderItemCustom12": "Campo personalizado 12", + "WorkOrderItemCustom13": "Campo personalizado 13", + "WorkOrderItemCustom14": "Campo personalizado 14", + "WorkOrderItemCustom15": "Campo personalizado 15", + "WorkOrderItemCustom16": "Campo personalizado 16", + "WorkOrderItemCustom2": "Campo personalizado 2", + "WorkOrderItemCustom3": "Campo personalizado 3", + "WorkOrderItemCustom4": "Campo personalizado 4", + "WorkOrderItemCustom5": "Campo personalizado 5", + "WorkOrderItemCustom6": "Campo personalizado 6", + "WorkOrderItemCustom7": "Campo personalizado 7", + "WorkOrderItemCustom8": "Campo personalizado 8", + "WorkOrderItemCustom9": "Campo personalizado 9", + "WorkOrderItemExpense": "Gastos varios elemento pedido", + "WorkOrderItemExpenseChargeAmount": "Cantidad por cobrar", + "WorkOrderItemExpenseChargeTaxCodeID": "Código fiscal aplicable", + "WorkOrderItemExpenseChargeToCustomer": "Cobrar al cliente", + "WorkOrderItemExpenseDescription": "Descripción", + "WorkOrderItemExpenseList": "Elementos gastos varios", + "WorkOrderItemExpenseName": "Resumen gastos varios", + "WorkOrderItemExpenseReimburseUser": "Reembolsar al usuario", + "WorkOrderItemExpenseTaxPaid": "Impuesto pagado", + "WorkOrderItemExpenseTotalCost": "Coste total", + "WorkOrderItemExpenseUserID": "Usuario", + "WorkOrderItemLabor": "Mano de obra elemento pedido", + "WorkOrderItemLaborList": "Elementos mano de obra", + "WorkOrderItemLaborNoChargeQuantity": "Cantidad sin cargo", + "WorkOrderItemLaborPrice": "Precio neto", + "WorkOrderItemLaborServiceDetails": "Detalles del servicio", + "WorkOrderItemLaborServiceRateID": "Tarifa del servicio", + "WorkOrderItemLaborServiceRateQuantity": "Unidades tarifa de servicio", + "WorkOrderItemLaborServiceStartDate": "Fecha y hora inicio de servicio", + "WorkOrderItemLaborServiceStopDate": "Fecha y hora fin de servicio", + "WorkOrderItemLaborTaxRateSaleID": "Impuesto sobre la venta", + "WorkOrderItemLaborUserID": "Usuario", + "WorkOrderItemList": "Elementos", + "WorkOrderItemLoan": "Préstamo elemento de pedido", + "WorkOrderItemLoanDueDate": "Devolución prevista", + "WorkOrderItemLoanList": "Elementos préstamo", + "WorkOrderItemLoanNotes": "Notas", + "WorkOrderItemLoanOutDate": "En préstamo", + "WorkOrderItemLoanQuantity": "Rate quantity", + "WorkOrderItemLoanRate": "Rate", + "WorkOrderItemLoanReturnDate": "Devuelto", + "WorkOrderItemLoanTaxCodeID": "Impuesto sobre la venta", + "WorkOrderItemLoanUnit": "Elemento préstamo", + "WorkOrderItemOutsideService": "Servicio externo", + "WorkOrderItemOutsideServiceDateETA": "Llegada prevista", + "WorkOrderItemOutsideServiceDateReturned": "Devuelto fecha", + "WorkOrderItemOutsideServiceDateSent": "Fecha de envío", + "WorkOrderItemOutsideServiceList": "Lista de servicios externos", + "WorkOrderItemOutsideServiceNotes": "Notas", + "WorkOrderItemOutsideServiceRepairCost": "Coste reparación", + "WorkOrderItemOutsideServiceRepairPrice": "Precio reparación", + "WorkOrderItemOutsideServiceRMANumber": "Número RMA", + "WorkOrderItemOutsideServiceShippingCost": "Coste de envío", + "WorkOrderItemOutsideServiceShippingPrice": "Precio de envío", + "WorkOrderItemOutsideServiceTrackingNumber": "Núm. seguimiento", + "WorkOrderItemOutsideServiceVendorSentToID": "Enviado a", + "WorkOrderItemOutsideServiceVendorSentViaID": "Enviado por", + "WorkOrderItemPart": "Pieza elemento de pedido", + "WorkOrderItemPartDescription": "Descripción", + "WorkOrderItemPartList": "Elementos piezas", + "WorkOrderItemPartPartID": "Pieza", + "WorkOrderItemPartPartWarehouseID": "Almacén", + "WorkOrderItemPartQuantity": "Cantidad", + "WorkOrderItemPartRealizeSuggested": "Darse cuenta de las cantidades de piezas sugeridas", + "WorkOrderItemPartRequest": "Solicitud pieza elemento de pedido", + "WorkOrderItemPartRequestList": "Solicitudes de piezas", + "WorkOrderItemPartRequestMore": "Solicita {n} más", + "WorkOrderItemPartRequestOnOrder": "En pedido", + "WorkOrderItemPartRequestPartID": "Pieza", + "WorkOrderItemPartRequestPartWarehouseID": "Almacén", + "WorkOrderItemPartRequestQuantity": "Cantidad", + "WorkOrderItemPartRequestReceived": "Recibido", + "WorkOrderItemPartRequests": "Solicitudes de piezas", + "WorkOrderItemPartSuggestedQuantity": "Cantidad sugerida", + "WorkOrderItemPartTaxPartSaleID": "Impuesto sobre la venta", + "WorkOrderItemPartUsed": "En uso en el servicio", + "WorkOrderItemPriorityColor": "Color", + "WorkOrderItemPriorityID": "Prioridad", + "WorkOrderItemPriorityList": "Lista de prioridad de elementos de la orden de trabajo", + "WorkOrderItemPriorityName": "Nombre", + "WorkOrderItemRequestDate": "Fecha de solicitud", + "WorkOrderItemScheduledUser": "Usuario programado elemento de pedido", + "WorkOrderItemScheduledUserEstimatedQuantity": "Cantidad estimada", + "WorkOrderItemScheduledUserList": "Elementos usuarios programados", + "WorkOrderItemScheduledUserServiceRateID": "Tarifa sugerida", + "WorkOrderItemScheduledUserStartDate": "Fecha y hora de inicio", + "WorkOrderItemScheduledUserStopDate": "Fecha y hora de fin", + "WorkOrderItemScheduledUserUserID": "Usuario", + "WorkOrderItemStatusColor": "Color", + "WorkOrderItemStatusList": "Lista de estado del artículo", + "WorkOrderItemStatusName": "Nombre", + "WorkOrderItemStatusNotes": "Notas", + "WorkOrderItemSummary": "Resumen elemento", + "WorkOrderItemTags": "Etiquetas elemento del pedido", + "WorkOrderItemTask": "Tarea elemento de pedido", + "WorkOrderItemTaskCompletedDate": "Fecha completada", + "WorkOrderItemTaskCompletionTypeComplete": "Completado", + "WorkOrderItemTaskCompletionTypeFailed": "Falla", + "WorkOrderItemTaskCompletionTypeIncomplete": "Tareas pendientes", + "WorkOrderItemTaskCompletionTypeNotApplicable": "N/D", + "WorkOrderItemTasks": "Tareas", + "WorkOrderItemTaskTaskID": "Tarea", + "WorkOrderItemTaskUser": "Usuario", + "WorkOrderItemTaskWorkOrderItemTaskCompletionType": "Estado", + "WorkOrderItemTechNotes": "Notas del servicio", + "WorkOrderItemTravel": "Elemento de pedido desplazamiento", + "WorkOrderItemTravelDetails": "Detalles del desplazamiento", + "WorkOrderItemTravelDistance": "Distancia", + "WorkOrderItemTravelList": "Elementos desplazamiento", + "WorkOrderItemTravelNoChargeQuantity": "Cantidad sin cargo", + "WorkOrderItemTravelRateQuantity": "Cantidad", + "WorkOrderItemTravelServiceRateID": "Tarifa desplazamiento", + "WorkOrderItemTravelStartDate": "Fecha de inicio", + "WorkOrderItemTravelStopDate": "Fecha de fin", + "WorkOrderItemTravelTaxRateSaleID": "Impuesto sobre la venta", + "WorkOrderItemTravelUserID": "Usuario", + "WorkOrderItemUnit": "Unidad de artículo de orden de trabajo", + "WorkOrderItemUnitCustom1": "Campo personalizado 1", + "WorkOrderItemUnitCustom10": "Campo personalizado 10", + "WorkOrderItemUnitCustom11": "Campo personalizado 11", + "WorkOrderItemUnitCustom12": "Campo personalizado 12", + "WorkOrderItemUnitCustom13": "Campo personalizado 13", + "WorkOrderItemUnitCustom14": "Campo personalizado 14", + "WorkOrderItemUnitCustom15": "Campo personalizado 15", + "WorkOrderItemUnitCustom16": "Campo personalizado 16", + "WorkOrderItemUnitCustom2": "Campo personalizado 2", + "WorkOrderItemUnitCustom3": "Campo personalizado 3", + "WorkOrderItemUnitCustom4": "Campo personalizado 4", + "WorkOrderItemUnitCustom5": "Campo personalizado 5", + "WorkOrderItemUnitCustom6": "Campo personalizado 6", + "WorkOrderItemUnitCustom7": "Campo personalizado 7", + "WorkOrderItemUnitCustom8": "Campo personalizado 8", + "WorkOrderItemUnitCustom9": "Campo personalizado 9", + "WorkOrderItemUnitList": "Unidades", + "WorkOrderItemUnitNotes": "Notas", + "WorkOrderItemUnitTags": "Etiquetas artículo de pedido unitario", + "WorkOrderItemWarrantyService": "Servicio de garantía", + "WorkOrderItemWorkOrderStatusID": "Estado elemento pedido", + "WorkOrderList": "Pedidos de servicio", + "WorkOrderOnsite": "En el lugar", + "WorkOrderSerialNumber": "Número", + "WorkOrderServiceDate": "Fecha de servicio", + "WorkOrderStatus": "Estado del orden de trabajo", + "WorkOrderStatusList": "Estados del pedido", + "WorkOrderSummary": "Resumen" +} \ No newline at end of file diff --git a/server/resource/fr.json b/server/resource/fr.json new file mode 100644 index 0000000..43f94d1 --- /dev/null +++ b/server/resource/fr.json @@ -0,0 +1,1495 @@ +{ + "Accounting": "Comptabilité", + "Active": "Actif", + "Activity": "Activité", + "Add": "Ajouter", + "AddMultipleUnits": "Ajouter plusieurs unités", + "AddNewUnit": "Ajouter une nouvelle unité", + "Address": "Adresse", + "AddressCity": "Ville", + "AddressCopyToPhysical": "Copy to physical address", + "AddressCopyToPostal": "Copy to postal address", + "AddressCountry": "Pays", + "AddressDeliveryAddress": "Rue", + "AddressLatitude": "Latitude", + "AddressLongitude": "Longitude", + "AddressPostalCity": "Ville (courrier)", + "AddressPostalCountry": "Pays (courrier)", + "AddressPostalDeliveryAddress": "Adresse (courrier)", + "AddressPostalPostal": "Code postal (courrier)", + "AddressPostalStateProv": "Région / Province (courrier)", + "AddressStateProv": "Région ou province", + "AddressTypePhysical": "Adresse physique", + "AddressTypePostal": "Adresse postale", + "AdminEraseDatabase": "Effacer toute la base de données Sockeye", + "AdminEraseDatabaseLastWarning": "Attention : ceci est votre dernière chance d'annuler l'effacement définitif de toutes les données. Souhaitez-vous réellement effacer toutes les données ?", + "AdminEraseDatabaseWarning": "Attention : vous êtes sur le point d'effacer définitivement toutes les données d'Sockeye. Êtes-vous sûr ?", + "Administration": "Administration", + "AdministrationGlobalSettings": "Réglages généraux", + "AlertNotes": "Notes d'alerte", + "AllItemsInList": "Tous les éléments de la liste", + "AnyUser": "All users", + "AppendTasks": "Ajouter des tâches", + "ApplyUnitContract": "Appliquer le contrat '{0}' de cette unité à l'ordre de travail?", + "AreYouSureBackupNow": "Le serveur sera verrouillé pendant la sauvegarde. Êtes-vous sûr?", + "AreYouSureShutDown": "Êtes-vous certain de vouloir arrêter le serveur?", + "AreYouSureUnsavedChanges": "Voulez-vous vraiment quitter ? Vous perdrez toutes les modifications non enregistrées.", + "AttachFile": "Joindre fichier", + "AttachmentExists": "Le fichier existe", + "AttachmentFileName": "Nom du fichier", + "AttachmentNotes": "Remarques", + "Attachments": "Fichiers joints", + "AttachReport": "Joindre le rapport", + "AuthConnectAppManualEntry": "Vous rencontrez des difficultés pour scanner le code? Saisissez les informations suivantes manuellement dans votre application d'authentification:", + "AuthConnectAppSubTitle": "À l'aide d'une application d'authentification telle que Google Authenticator, Duo, Microsoft Authenticator ou Authy, scannez le code QR. Il affichera un code d'accès à 6 chiffres que vous devez saisir ci-dessous.", + "AuthConnectAppTitle": "Connectez votre application", + "AuthConnectCompleted": "L'authentification à deux facteurs est maintenant activée", + "AuthDisableTwoFactor": "Désactiver l'authentification à deux facteurs", + "AuthEnterPin": "Entrez le code à 6 chiffres", + "AuthorizationRoleAccounting": "Comptabilité", + "AuthorizationRoleAll": "Tous les rôles", + "AuthorizationRoleBizAdmin": "Administration des affaires", + "AuthorizationRoleBizAdminRestricted": "Administration des affaires - restreint", + "AuthorizationRoleCustomer": "Customer utilisateur", + "AuthorizationRoleCustomerRestricted": "Customer utilisateur - restreint", + "AuthorizationRoleInventory": "Inventaire", + "AuthorizationRoleInventoryRestricted": "Inventaire - restreint", + "AuthorizationRoleNoRole": "Pas de rôle", + "AuthorizationRoleOpsAdmin": "Opérations du système", + "AuthorizationRoleOpsAdminRestricted": "Opérations du système - restreint", + "AuthorizationRoles": "Rôles d'autorisation", + "AuthorizationRoleSales": "Ventes", + "AuthorizationRoleSalesRestricted": "Ventes - restreint", + "AuthorizationRoleService": "Superviseur des services", + "AuthorizationRoleServiceRestricted": "Superviseur des services - restreint", + "AuthorizationRoleSubContractor": "Sous-traitant", + "AuthorizationRoleSubContractorRestricted": "Sous-traitant - restreint", + "AuthorizationRoleTech": "Technicien de service", + "AuthorizationRoleTechRestricted": "Technicien de service - restreint", + "AuthPinInvalid": "Code non valide", + "AuthTwoFactor": "Authentification à deux facteurs", + "AuthTwoFactorDisabled": "L'authentification à deux facteurs est maintenant désactivée", + "AuthVerifyCode": "Vérifier le code", + "AvailableSpace": "Espace disponible", + "AyaFileFileTooLarge": "File size exceeds limit of {0}", + "SockeyeServerURL": "URL du serveur Sockeye", + "SockType": "Type", + "Backup": "Sauvegarder", + "BackupAttachments": "Pièces jointes de sauvegarde", + "BackupDeleteOld": "Suppression des anciens fichiers de sauvegarde", + "BackupFiles": "Liste des fichiers de sauvegarde", + "BackupLast": "Dernière heure de sauvegarde", + "BackupNow": "Lancer la sauvegarde maintenant", + "BackupSetsToKeep": "Nombre de sauvegardes à conserver", + "BackupSettings": "Paramètres de sauvegarde", + "BackupTime": "Temps de sauvegarde", + "Backward": "Recule", + "BatchDeleteJob": "Tâche de suppression par lots", + "BatchJob": "Travail par lots", + "BizMetrics": "Indicateurs de performance", + "Browser": "Navigateur", + "BusinessSettings": "Paramètres commerciaux", + "Cancel": "Annuler", + "CheckForLicense": "Installer la licence", + "Close": "Quitter", + "Columns": "Colonnes", + "CompanyEmail": "Email", + "CompanyInformation": "Informations sur la société", + "CompanyPhone1": "Phone 1", + "CompanyPhone2": "Phone 2", + "ConfirmPassword": "Confirmer le mot de passe", + "ConfirmUpdatePartCost": "Mettre à jour le coût de la pièce à partir du coût reçu?", + "ConnectionSecurity": "Sécurité de la connexion SMTP", + "Contact": "Contact", + "ContactCustomerHeadOfficeTaggedWith": "Autoriser uniquement à contacter, client ou siège social avec l'une de ces balises", + "ContactPhone": "Téléphone de contact", + "Contacts": "Contacts", + "ContactTitle": "Titre de contact", + "Contract": "Contrat", + "ContractAdjustment": "Ajustement de prix", + "ContractCustom1": "Champ personnalisé 1", + "ContractCustom10": "Champ personnalisé 10", + "ContractCustom11": "Champ personnalisé 11", + "ContractCustom12": "Champ personnalisé 12", + "ContractCustom13": "Champ personnalisé 13", + "ContractCustom14": "Champ personnalisé 14", + "ContractCustom15": "Champ personnalisé 15", + "ContractCustom16": "Champ personnalisé 16", + "ContractCustom2": "Champ personnalisé 2", + "ContractCustom3": "Champ personnalisé 3", + "ContractCustom4": "Champ personnalisé 4", + "ContractCustom5": "Champ personnalisé 5", + "ContractCustom6": "Champ personnalisé 6", + "ContractCustom7": "Champ personnalisé 7", + "ContractCustom8": "Champ personnalisé 8", + "ContractCustom9": "Champ personnalisé 9", + "ContractDefaultAdjustments": "Ajustements de prix par défaut", + "ContractDefaultResponseTime": "Temps de réponse", + "ContractDiscountParts": "Remise appliquée à toutes les pièces", + "ContractExpires": "Expiration du contrat", + "ContractList": "Contrats", + "ContractName": "Nom de contrat", + "ContractNotes": "Remarques", + "ContractOverrideType": "Type d'ajustement de prix", + "ContractOverrideTypeMarkup": "Coût plus pourcentage", + "ContractOverrideTypePriceDiscount": "Prix moins pourcentage", + "ContractRate": "Tarif de contrat", + "ContractRateList": "Tarifs de contrat", + "ContractServiceRatesOnly": "Autoriser uniquement ces tarifs de service", + "ContractTaggedAdjustments": "Ajustements de prix balisés", + "ContractTravelRatesOnly": "Autoriser uniquement ces tarifs de voyage", + "Copy": "Copier", + "CopyAttachments": "Copier les pièces jointes", + "CopyDbId": "Copier l'ID de la base de données", + "CopySupportInfo": "Copier les informations d'assistance", + "CopyToClipboard": "Copier dans le Presse-papiers", + "CopyToWorkOrder": "Copier sur le bon de travail", + "CopyWiki": "Copier le WIKI", + "Cost": "Coût", + "Created": "Enregistrement créé", + "CSRInfoText": "Message au client", + "CurrencyCode": "Code de devise (ISO 4217)", + "Customer": "Client", + "CustomerAccessSettings": "Paramètres d'accès client", + "CustomerAccessWorkOrderAttachments": "Ouvrir les pièces jointes d'en-tête d'ordre de travail", + "CustomerAccessWorkOrderReport": "Rapport de bon de travail client", + "CustomerAccessWorkOrderWiki": "Voir l'en-tête de l'ordre de travail WIKI", + "CustomerAccountNumber": "Numéro de compte", + "CustomerAlertNotes": "Notes contextuelles", + "CustomerAllowCreateUnit": "Autoriser le client à créer une unité", + "CustomerBillHeadOffice": "Siège social de facturation", + "CustomerCustom1": "Champ personnalisé 1", + "CustomerCustom10": "Champ personnalisé 10", + "CustomerCustom11": "Champ personnalisé 11", + "CustomerCustom12": "Champ personnalisé 12", + "CustomerCustom13": "Champ personnalisé 13", + "CustomerCustom14": "Champ personnalisé 14", + "CustomerCustom15": "Champ personnalisé 15", + "CustomerCustom16": "Champ personnalisé 16", + "CustomerCustom2": "Champ personnalisé 2", + "CustomerCustom3": "Champ personnalisé 3", + "CustomerCustom4": "Champ personnalisé 4", + "CustomerCustom5": "Champ personnalisé 5", + "CustomerCustom6": "Champ personnalisé 6", + "CustomerCustom7": "Champ personnalisé 7", + "CustomerCustom8": "Champ personnalisé 8", + "CustomerCustom9": "Champ personnalisé 9", + "CustomerEmail": "Email", + "CustomerList": "Les clients", + "CustomerName": "Nom de client", + "CustomerNote": "Note client", + "CustomerNoteList": "Notes de client", + "CustomerNoteNoteDate": "Date de note", + "CustomerNoteNotes": "Remarques", + "CustomerNotes": "Notes générales", + "CustomerNotifySubscription": "Abonnement aux notifications clients", + "CustomerNotifySubscriptionList": "Abonnements aux notifications client", + "CustomerPhone1": "Téléphone de travail", + "CustomerPhone2": "Fax", + "CustomerPhone3": "Téléphone fixe", + "CustomerPhone4": "Numéro de portable", + "CustomerPhone5": "Téléavertisseur", + "CustomerServiceRequest": "Demande de service client", + "CustomerServiceRequestAcceptToExisting": "Accept to existing work order", + "CustomerServiceRequestAcceptToNew": "Accept to new work order", + "CustomerServiceRequestCustom1": "Champ personnalisé 1", + "CustomerServiceRequestCustom10": "Champ personnalisé 10", + "CustomerServiceRequestCustom11": "Champ personnalisé 11", + "CustomerServiceRequestCustom12": "Champ personnalisé 12", + "CustomerServiceRequestCustom13": "Champ personnalisé 13", + "CustomerServiceRequestCustom14": "Champ personnalisé 14", + "CustomerServiceRequestCustom15": "Champ personnalisé 15", + "CustomerServiceRequestCustom16": "Champ personnalisé 16", + "CustomerServiceRequestCustom2": "Champ personnalisé 2", + "CustomerServiceRequestCustom3": "Champ personnalisé 3", + "CustomerServiceRequestCustom4": "Champ personnalisé 4", + "CustomerServiceRequestCustom5": "Champ personnalisé 5", + "CustomerServiceRequestCustom6": "Champ personnalisé 6", + "CustomerServiceRequestCustom7": "Champ personnalisé 7", + "CustomerServiceRequestCustom8": "Champ personnalisé 8", + "CustomerServiceRequestCustom9": "Champ personnalisé 9", + "CustomerServiceRequestCustomerReferenceNumber": "Numéro de référence", + "CustomerServiceRequestDetails": "Détails", + "CustomerServiceRequestItemUnitID": "Unité", + "CustomerServiceRequestList": "Demandes de service client", + "CustomerServiceRequestPriority": "Priorité", + "CustomerServiceRequestPriorityASAP": "Dès que possible", + "CustomerServiceRequestPriorityEmergency": "Urgence", + "CustomerServiceRequestPriorityNotUrgent": "Pas urgent", + "CustomerServiceRequestReject": "Rejeter la demande", + "CustomerServiceRequestRequestedBy": "Demandé par", + "CustomerServiceRequestStatus": "Statut", + "CustomerServiceRequestStatusAccepted": "Accepté", + "CustomerServiceRequestStatusDeclined": "Rejeté", + "CustomerServiceRequestStatusOpen": "Ouvert", + "CustomerServiceRequestTitle": "Demande", + "CustomerSignature": "Signature du client", + "CustomerTags": "Balises client", + "CustomerTechNotes": "Notes du technicien de maintenance", + "Customize": "Personnaliser....", + "DarkMode": "Mode Sombre", + "Dashboard": "Tableau de bord", + "DashboardNotAssigned": "Not assigned", + "DashboardNotScheduled": "Non planifié", + "DashboardOverdue": "En retard", + "DashboardOverdueAll": "En retard - tout", + "DashboardReminders": "Reminders", + "DashboardScheduled": "Scheduled", + "DashboardServiceRateQuantityAllUsers": "Quantité de service - tout", + "DashboardOpenCSR": "Demandes de service - ouvertes", + "DashboardCountWorkOrdersCreated": "Nombre de bons de travail créés", + "DashboardPctWorkOrderCompletedOnTime": "% des ordres de travail terminés à temps", + "DashboardWorkOrderByStatusList": "Liste des bons de travail par statut", + "DashboardWorkOrderStatusCount": "Nombre d'ordres de travail par statut", + "DashboardWorkOrderStatusPct": "% des bons de travail par statut", + "Database": "Base de données", + "DatabaseID": "ID base de données", + "DataListSavedFilter": "Filtre de liste", + "DateRange14DayWindow": "Fenêtre - 14 jours", + "DateRangeApril": "Avril", + "DateRangeAugust": "Août", + "DateRangeDecember": "Décembre", + "DateRangeFebruary": "Février", + "DateRangeFuture": "Futur", + "DateRangeInTheLastSixMonths": "Au cours des six derniers mois", + "DateRangeInTheLastThreeMonths": "Au cours des trois derniers mois", + "DateRangeJanuary": "Janvier", + "DateRangeJuly": "Juillet", + "DateRangeJune": "Juin", + "DateRangeLastMonth": "Mois - Précédent", + "DateRangeLastWeek": "Week - Previous", + "DateRangeLastYear": "Année - dernière", + "DateRangeMarch": "Mars", + "DateRangeMay": "Mai", + "DateRangeNextMonth": "Mois - Suivant", + "DateRangeNextWeek": "Semaine - Prochaine", + "DateRangeNovember": "Novembre", + "DateRangeOctober": "Octobre", + "DateRangePast": "Passé", + "DateRangePast24Hours": "24 dernières heures", + "DateRangePast30Days": "30 derniers jours", + "DateRangePast6Hours": "6 dernières heures", + "DateRangePast7Days": "7 derniers jours", + "DateRangePast90Days": "90 derniers jours", + "DateRangePastYear": "L'année dernière (365 jours)", + "DateRangePreviousYearLastMonth": "Année précédente - le mois dernier", + "DateRangePreviousYearNextMonth": "Année précédente - mois prochain", + "DateRangePreviousYearThisMonth": "Année précédente - ce mois-ci", + "DateRangeSeptember": "Septembre", + "DateRangeThisMonth": "Mois - Actuel", + "DateRangeThisWeek": "Semaine - Actuelle", + "DateRangeThisYear": "Année - en cours", + "DateRangeToday": "Aujourd'hui", + "DateRangeTomorrow": "Demain", + "DateRangeYesterday": "Hier", + "DayFriday": "Vendredi", + "DayMonday": "Lundi", + "DaySaturday": "Samedi", + "DaySunday": "Dimanche", + "DayThursday": "Jeudi", + "DayTuesday": "Mardi", + "DayWednesday": "Mercredi", + "DefaultLanguage": "Langue par défaut", + "DefaultReport": "Rapport par défaut", + "Delete": "Supprimer", + "DeletePrompt": "Souhaitez-vous réellement supprimer définitivement cet enregistrement ?", + "DeleteSelected": "Supprimer les éléments sélectionnés", + "DeliverAfter": "Livrer après", + "Description": "Description", + "DirectNotification": "Notification directe", + "Download": "Télécharger", + "DropFilesHere": "Déposez les fichiers ici", + "Duplicate": "Dupliquer", + "DuplicateToPM": "Dupliquer à la maintenance préventive", + "DuplicateToQuote": "Dupliquer pour devis", + "DuplicateToWorkOrder": "Dupliquer à l'ordre de travail", + "Duration": "Durée", + "EmailSubject": "Modèle d'objet d'e-mail", + "EmailTemplate": "Modèle de corps d'e-mail", + "EraseMultipleObjectsWarning": "Attention: vous êtes sur le point d'effacer définitivement plusieurs objets.\nÊtes-vous sûr?", + "ErrorAPI2000": "Le serveur est fermé", + "ErrorAPI2001": "Le serveur est fermé pour maintenance", + "ErrorAPI2002": "Erreur interne du serveur", + "ErrorAPI2003": "Échec de l’authentification", + "ErrorAPI2004": "Non autorisé", + "ErrorAPI2005": "L'objet a été récemment modifié par un autre utilisateur et ne peut pas être enregistré", + "ErrorAPI2006": "Le serveur est fermé pour migration", + "ErrorAPI2010": "Objet introuvable", + "ErrorAPI2020": "L'ID de route ne correspond pas à l'ID d'objet", + "ErrorAPI2030": "Opération non valide", + "ErrorAPI2040": "Pas assez d'inventaire", + "ErrorAPI2200": "Erreur de validation", + "ErrorAPI2201": "Champ obligatoire", + "ErrorAPI2202": "Longueur maximale dépassée", + "ErrorAPI2203": "Valeur non valide", + "ErrorAPI2204": "Champ obligatoire (personnalisé)", + "ErrorAPI2205": "Données attendues manquantes", + "ErrorAPI2206": "Doit être unique", + "ErrorAPI2207": "La date de début doit être antérieure à la date de fin", + "ErrorAPI2208": "Cet objet est lié à d'autres et ne peut pas être modifié de cette façon", + "ErrorAPI2209": "Cette valeur ne peut pas être changée", + "ErrorAPI2210": "Erreur d'objet enfant", + "ErrorAPI2212": "Une seule unité sous contrat autorisée par bon de travail", + "ErrorDBForeignKeyViolation": "Impossible de supprimer cet objet car il est relié à un ou plusieurs objets associés", + "ErrorFieldLengthExceeded": "{0} ne peut excéder {1} caractères", + "ErrorFieldValueNotDecimal": "La valeur doit être un nombre", + "ErrorFieldValueNotInteger": "La valeur doit être un entier", + "ErrorFieldValueNumberGreaterThanMax": "La valeur doit être inférieure à {0}", + "ErrorFieldValueNumberLessThanMin": "La valeur doit être supérieure à {0}", + "ErrorGenBeforeTooSmall": "Doit être inférieur à l'intervalle de répétition", + "ErrorNoMatch": "Les valeurs ne correspondent pas", + "ErrorPickListQueryInvalid": "Requête non valide - cliquez sur l'icône d'aide pour en savoir plus", + "ErrorRepeatIntervalTooSmall": "Au moins une heure", + "ErrorRequiredFieldEmpty": "{0} est un champ obligatoire. Veuillez saisir une valeur pour {0}", + "Errors": "les erreurs", + "ErrorSecurityAdministratorOnlyMessage": "Vous devez être connecté en tant que le compte SuperUser pour cette tâche", + "ErrorSecurityUserCapacity": "Le nombre de licences disponibles est insuffisant pour poursuivre cette opération", + "ErrorServerUnresponsive": "Le serveur ne répond pas (E17)", + "ErrorStartDateAfterEndDate": "La date de début doit être antérieure à la date de fin", + "ErrorUserNotAuthenticated": "Non authentifié (E16)", + "ErrorUserNotAuthorized": "Non autorisé", + "Evaluate": "Évaluer", + "EvaluateForceEmail": "Définissez tous les exemples d'adresses e-mail sur ceci (facultatif)", + "EvaluationGuide": "Guide d'évaluation", + "EvaluationRequestReceived": "Demande reçue, vérifiez votre e-mail pour terminer le processus de vérification", + "Event": "Un événement", + "EventAttachmentCreate": "Pièce jointe créée", + "EventAttachmentDelete": "Pièce jointe supprimée", + "EventAttachmentDownload": "Pièce jointe téléchargée", + "EventAttachmentModified": "Pièce jointe modifiée", + "EventCreated": "Créé", + "EventDeleted": "Supprimé", + "EventLicenseFetch": "Licence récupérée", + "EventLicenseTrialRequest": "Licence d'essai demandée", + "EventModified": "Modifié", + "EventResetSerial": "Réinitialisation du numéro de série", + "EventRetrieved": "Récupéré", + "EventSeedDatabase": "Base de données créée avec des exemples de données", + "EventServerStateChange": "L'état du serveur a changé", + "EventTextra": "Remarques", + "EventUtilityFileDownload": "Fichier d'opérations téléchargé", + "ExcludeDaysOfWeek": "Exclure les jours de la semaine", + "Export": "Exporter", + "Extensions": "Extensions", + "Failed": "Échec", + "False": "Faux", + "FileAttachment": "Pièce jointe", + "FileDate": "Date", + "FileName": "Nom", + "FileSize": "Taille", + "Filter": "Filtre", + "FilterUsers": "Filtrer les utilisateurs", + "Find": "Rechercher", + "FindAndReplace": "Rechercher et remplacer", + "First": "Première", + "FormCustom": "Personnalisation des formulaires", + "FormFieldEntryRequired": "Champ obligatoire", + "FormFieldVisible": "Visible", + "Forward": "Avance", + "GenerateBefore": "Générer au préalable", + "GenerateSampleData": "Générer des exemples de données", + "GeoCapture": "Défini sur l'emplacement actuel", + "GeoView": "Voir sur la carte", + "Global": "Général", + "GlobalAllowScheduleConflicts": "Autoriser les conflits de programme", + "GlobalCJKIndex": "Utiliser l'index CJC", + "GlobalCJKIndexDescription": "Ne réglez cette option sur VRAI que si des caractères chinois, japonais ou coréens doivent être saisis dans les champs et les étiquettes", + "GlobalFilterCaseSensitive": "Le filtrage est sensible à la casse", + "GlobalLaborSchedUserDfltTimeSpan": "Scheduled / Labor default minutes", + "GlobalLogo": "Logos d'entreprise", + "GlobalNextSeeds": "Définir le numéro suivant", + "GlobalOps": "Global ops", + "GlobalSignatureFooter": "Signature footer", + "GlobalSignatureHeader": "Signature header", + "GlobalSignatureTitle": "Signature title", + "GlobalTaxPartPurchaseID": "Taxe par défaut sur l'achat de pièces", + "GlobalTaxPartSaleID": "Taxe par défaut sur les ventes de pièces", + "GlobalTaxRateSaleID": "Taxes de service par défaut", + "GlobalTravelDfltTimeSpan": "Travel default minutes", + "GlobalUseInventory": "Utiliser le stock", + "GlobalWorkOrderCompleteByAge": "Âge d'achèvement des bons de travail par défaut", + "GridFilterDialogAndRadioText": "Conditions Et", + "GridFilterDialogOrRadioText": "Conditions Ou", + "GridFilterName": "Filter name", + "GridRowFilterDropDownBlanksItem": "(Aucune valeur)", + "GridRowFilterDropDownContains": "Contient", + "GridRowFilterDropDownDoesNotContain": "Ne contient pas", + "GridRowFilterDropDownEndsWith": "Se termine par", + "GridRowFilterDropDownEquals": "Égal à", + "GridRowFilterDropDownGreaterThan": "Supérieur à", + "GridRowFilterDropDownGreaterThanOrEqualTo": "Supérieur ou égal à", + "GridRowFilterDropDownLessThan": "Inférieur à", + "GridRowFilterDropDownLessThanOrEqualTo": "Inférieur ou égal à", + "GridRowFilterDropDownNonBlanksItem": "(A une valeur)", + "GridRowFilterDropDownNotEquals": "N'est pas égal à", + "GridRowFilterDropDownStartsWith": "Commence avec", + "HaveLicense": "Utiliser la licence existante", + "Heading": "Titre", + "HeadOffice": "Siège social", + "HeadOfficeAccountNumber": "Numéro de compte", + "HeadOfficeCustom1": "Champ personnalisé 1", + "HeadOfficeCustom10": "Champ personnalisé 10", + "HeadOfficeCustom11": "Champ personnalisé 11", + "HeadOfficeCustom12": "Champ personnalisé 12", + "HeadOfficeCustom13": "Champ personnalisé 13", + "HeadOfficeCustom14": "Champ personnalisé 14", + "HeadOfficeCustom15": "Champ personnalisé 15", + "HeadOfficeCustom16": "Champ personnalisé 16", + "HeadOfficeCustom2": "Champ personnalisé 2", + "HeadOfficeCustom3": "Champ personnalisé 3", + "HeadOfficeCustom4": "Champ personnalisé 4", + "HeadOfficeCustom5": "Champ personnalisé 5", + "HeadOfficeCustom6": "Champ personnalisé 6", + "HeadOfficeCustom7": "Champ personnalisé 7", + "HeadOfficeCustom8": "Champ personnalisé 8", + "HeadOfficeCustom9": "Champ personnalisé 9", + "HeadOfficeEmail": "Email", + "HeadOfficeList": "Sièges sociaux", + "HeadOfficeName": "Nom de siège social", + "HeadOfficeNotes": "Remarques", + "HeadOfficePhone1": "Téléphone de travail", + "HeadOfficePhone2": "Fax", + "HeadOfficePhone3": "Téléphone fixe", + "HeadOfficePhone4": "Numéro de portable", + "HeadOfficePhone5": "Téléavertisseur", + "HelpAboutSockeye": "À propos d'Sockeye", + "HelpCheckForUpdates": "Rechercher des mises à jour", + "HelpLicense": "License", + "HelpReleaseKey": "Réutiliser la licence existante", + "HelpRestore": "Comment restaurer les données", + "HelpTechSupport": "Assistance technique", + "History": "Historique", + "Home": "Accueil", + "Hour12": "Horloge 12 heures", + "ID": "Id", + "ImageDescription": "Description de l'image", + "ImageUrl": "URL d'image", + "Import": "Importer", + "Include": "Inclure", + "InsertImage": "Insérer une image", + "InsertLink": "Insérer un lien", + "Interval": "Intervalle", + "Inventory": "Stock", + "InventoryPurchaseOrders": "Bons de commande", + "InventoryRoleRequired": "L'utilisateur doit avoir un rôle d'inventaire pour cette opération", + "JobCompleted": "Travail terminé", + "JobCreated": "Travail serveur créé", + "JobExclusiveWarning": "AVERTISSEMENT: ce travail suspend temporairement tout accès utilisateur au serveur", + "JobFailed": "Le travail a échoué", + "KnownPasswordWarning": "DANGER: le mot de passe actuel n'est pas sécurisé et doit être changé immédiatement", + "LanguageCode": "Remplacer le code de langue par défaut", + "LargeLogo": "Logo de grande taille", + "Last": "Dernier", + "LastLogin": "Dernière connexion", + "LastServiceWorkOrder": "Dernier bon de travail", + "LastServiceWorkOrderServiceDate": "Dernière date de service", + "Leave": "Quitter", + "License": "Licence", + "LicenseCompanyName": "Nom de la compagnie", + "LicenseContactName": "Nom du contact", + "LicensedOptions": "Options de licence", + "LicenseEmail": "Adresse électronique", + "LicenseEmailVerficationHint": "(L'adresse sera vérifiée)", + "LicenseExpiration": "Accordée sous licence jusqu'au", + "LicenseSerial": "Numéro de série de licence", + "LineTotal": "Total de la ligne", + "LinkText": "Texte de lien", + "LinkUrl": "URL du lien", + "ListPrice": "Liste des prix", + "Loading": "Chargement...", + "LoanUnit": "Élément de prêt", + "LoanUnitCurrentWorkOrderItemLoan": "ID de prêt d'élément de bon de travail actuel", + "LoanUnitCustom1": "Champ personnalisé 1", + "LoanUnitCustom10": "Champ personnalisé 10", + "LoanUnitCustom11": "Champ personnalisé 11", + "LoanUnitCustom12": "Champ personnalisé 12", + "LoanUnitCustom13": "Champ personnalisé 13", + "LoanUnitCustom14": "Champ personnalisé 14", + "LoanUnitCustom15": "Champ personnalisé 15", + "LoanUnitCustom16": "Champ personnalisé 16", + "LoanUnitCustom2": "Champ personnalisé 2", + "LoanUnitCustom3": "Champ personnalisé 3", + "LoanUnitCustom4": "Champ personnalisé 4", + "LoanUnitCustom5": "Champ personnalisé 5", + "LoanUnitCustom6": "Champ personnalisé 6", + "LoanUnitCustom7": "Champ personnalisé 7", + "LoanUnitCustom8": "Champ personnalisé 8", + "LoanUnitCustom9": "Champ personnalisé 9", + "LoanUnitList": "Éléments de prêt", + "LoanUnitName": "Nom", + "LoanUnitNotes": "Remarques", + "LoanUnitRateDay": "Taux journalier", + "LoanUnitRateDayCost": "Coût journalier", + "LoanUnitRateHalfDay": "Tarif demi-journée", + "LoanUnitRateHalfDayCost": "Cout demi-journée", + "LoanUnitRateHour": "Taux horaire", + "LoanUnitRateHourCost": "Coût horaire", + "LoanUnitRateMonth": "Tarif mensuel", + "LoanUnitRateMonthCost": "Coût mensuel", + "LoanUnitRateNone": "-", + "LoanUnitRateWeek": "Tarif à la semaine", + "LoanUnitRateWeekCost": "Coût hebdomadaire", + "LoanUnitRateYear": "Tarif annuel", + "LoanUnitRateYearCost": "Coût annuel", + "LoanUnitSerial": "Numéro de série", + "LoanUnitShadowUnit": "Unité de l'ombre", + "Log": "Enregistrement", + "LogFile": "Fichier journal", + "Logout": "Fermer la session", + "MaintenanceExpired": "Maintenance expirée", + "MaintenanceExpiredNote": "L'abonnement au support et aux mises à jour a expiré\nSockeye ne peut pas être mis à jour et le support n'est plus disponible", + "MapUrlTemplate": "Modèle d'URL de carte", + "MediumLogo": "Logo de taille moyenne", + "Memo": "Mémo", + "MemoCustom1": "Champ personnalisé 1", + "MemoCustom10": "Champ personnalisé 10", + "MemoCustom11": "Champ personnalisé 11", + "MemoCustom12": "Champ personnalisé 12", + "MemoCustom13": "Champ personnalisé 13", + "MemoCustom14": "Champ personnalisé 14", + "MemoCustom15": "Champ personnalisé 15", + "MemoCustom16": "Champ personnalisé 16", + "MemoCustom2": "Champ personnalisé 2", + "MemoCustom3": "Champ personnalisé 3", + "MemoCustom4": "Champ personnalisé 4", + "MemoCustom5": "Champ personnalisé 5", + "MemoCustom6": "Champ personnalisé 6", + "MemoCustom7": "Champ personnalisé 7", + "MemoCustom8": "Champ personnalisé 8", + "MemoCustom9": "Champ personnalisé 9", + "MemoForward": "Faire suivre", + "MemoFromID": "De", + "MemoList": "Mémos", + "MemoMessage": "Message", + "MemoRe": "RE :", + "MemoReplied": "Répondu", + "MemoReply": "Répondre", + "MemoSent": "Envoyé", + "MemoSubject": "Objet", + "MemoToID": "À", + "MemoViewed": "Lu", + "MenuHelp": "Aide", + "MetricAllocatedMemory": "Allouée (MiB)", + "MetricAttachmentsCount": "Fichiers joints (compter)", + "MetricAttachmentsMB": "Fichiers joints (MiB)", + "MetricAvailableDiskSpace": "Espace disponible (MiB)", + "MetricBackupMB": "Fichiers de sauvegarde (MiB)", + "MetricCPUMemory": "CPU / Mémoire", + "MetricDBSize": "Taille de la base de données (MiB)", + "MetricFileStorage": "Stockage de fichiers", + "MetricPrivateBytes": "Octets privés (MiB)", + "MetricTopTablesSize": "Top tables de base de données (KiB)", + "MetricWorkingSet": "Plage de travail (MiB)", + "More": "Plus...", + "MoveSelected": "Déplacer les éléments sélectionnés", + "Name": "Nom", + "NativeDateTimeInput": "Utiliser les commandes de saisie de date et d'heure standard du navigateur", + "NetPrice": "Net", + "New": "Nouveau", + "NewLicenseInstalled": "Une nouvelle licence a été installée. Vous allez maintenant être déconnecté.", + "NewLicenseNotFound": "Aucune nouvelle licence n'est actuellement disponible", + "NewLogin": "Nouveau nom de connexion", + "NewPassword": "Nouveau mot de passe", + "NewStatus": "Nouveau statut", + "NextPMNumber": "Prochaine maintenance préventive", + "NextPONumber": "Prochaine commande d'achat", + "NextQuoteNumber": "Numéro de devis suivant", + "NextWorkorderNumber": "Bon de travail suivant", + "NoColor": "Sans couleur", + "NoData": "Aucune donnée", + "NoFeaturesAvailable": "Aucune fonctionnalité n'est activée pour votre compte", + "NoLicenseTitle": "Pas de licence", + "NoResults": "Aucun résultat", + "Notification": "Notification", + "Notifications": "Notifications", + "NotificationDeliveryLog":"Notifications utilisateur", + "NotificationCustomerDeliveryLog":"Notifications aux clients", + "NotifyDeliveryAddress": "Livrer à l'adresse", + "NotifyDeliveryMethod": "Méthode de remise des notifications", + "NotifyDeliveryMethodApp": "Livrer dans le programme", + "NotifyDeliveryMethodSMTP": "Livrer à l'adresse e-mail", + "NotifyEventBackupStatus": "Statut de sauvegarde", + "NotifyEventContractExpiring": "Contrat expirant", + "NotifyEventCSRAccepted": "Demande de service client acceptée", + "NotifyEventCSRRejected": "Demande de service client rejetée", + "NotifyEventCustomerServiceImminent": "Rendez-vous de service", + "NotifyEventCustomerServiceImminentMessage": "Vous avez un rendez-vous de service à venir dans {0}\nUtilisez le lien suivant pour afficher les détails:", + "NotifyEventGeneralNotification": "Notification générale", + "NotifyEventNotifyHealthCheck": "Vérification de l'état du système de notification", + "NotifyEventObjectAge": "Âge de l'objet depuis sa création", + "NotifyEventObjectCreated": "Objet créé", + "NotifyEventObjectDeleted": "Objet supprimé", + "NotifyEventObjectModified": "Objet changé", + "NotifyEventOutsideServiceOverdue": "Le service tiers est en retard", + "NotifyEventOutsideServiceReceived": "Service tiers terminé, unité reçue", + "NotifyEventPartRequestReceived": "Pièce demandée reçue", + "NotifyEventPMGenerationFailed": "Défaillance du système de maintenance préventive", + "NotifyEventPMInsufficientInventory": "Maintenance préventive inventaire insuffisant", + "NotifyEventPMStopGeneratingDateReached": "Date d'arrêt de la maintenance préventive atteinte", + "NotifyEventQuoteStatusAge": "Statut du devis inchangé pour la période de temps", + "NotifyEventQuoteStatusChange": "Statut du devis modifié", + "NotifyEventReminderImminent": "Rappel - imminent", + "NotifyEventReviewImminent": "Révision - imminente", + "NotifyEventScheduledOnWorkorder": "Planifié sur ordre de travail", + "NotifyEventScheduledOnWorkorderImminent": "Service planifié d'un ordre de travail imminent", + "NotifyEventServerOperationsProblem": "Problème de fonctionnement du serveur", + "NotifyEventType": "Événement de notification", + "NotifyEventUnitMeterReadingMultipleExceeded": "Lecture du compteur unitaire dépassée (multiple)", + "NotifyEventUnitWarrantyExpiry": "Expiration de la garantie de l'unité", + "NotifyEventWorkorderCompleted": "Ordre de travail terminé", + "NotifyEventWorkorderCompletedStatusOverdue": "Ordre de travail non terminé à temps", + "NotifyEventWorkorderCreatedForCustomer": "Bon de travail créé pour le client", + "NotifyEventWorkorderStatusAge": "Statut de l'ordre de travail inchangé pour la période de temps", + "NotifyEventWorkorderStatusChange": "État de l'ordre de travail défini", + "NotifyEventWorkorderTotalExceedsThreshold": "Le montant total de l'ordre de travail dépasse le seuil", + "NotifyFromAddress": "Notification SMTP envoyée depuis l'adresse", + "NotifyMailSecurityNone": "Aucun", + "NotifyMailSecuritySSLTLS": "SSL\\TLS", + "NotifyMailSecurityStartTls": "StartTLS", + "NotifyQueue": "Notifier file d'attente de remise des événements", + "NotifySubscription": "Abonnement aux notifications", + "NotifySubscriptionLinkText": "Modifier le paramètre de notification :", + "NotifySubscriptionList": "Abonnements aux notifications", + "NotifySubscriptionPendingSpan": "Notifier avant l'événement", + "NoType": "Aucun type", + "Now": "Maintenant", + "Object": "Objet", + "ObjectCustomFieldCustomGrid": "Champs personnalisés", + "OK": "OK", + "OldPassword": "Ancien mot de passe", + "Open": "Ouvrir", + "Operations": "Opérations de serveurs", + "OpsNotificationSettings": "Paramètres de notification", + "OpsTestJob": "Soumettre le travail de test", + "OutsideServiceList": "Liste des services extérieurs", + "PageOfPageText": "{0}-{1} sur {2}", + "Part": "Pièce", + "PartAlternativeWholesalerID": "Grossiste de remplacement", + "PartAlternativeWholesalerNumber": "Numéro de grossiste de remplacement", + "PartAssembly": "Assemblage de pièce", + "PartAssemblyCustom1": "Champ personnalisé 1", + "PartAssemblyCustom10": "Champ personnalisé 10", + "PartAssemblyCustom11": "Champ personnalisé 11", + "PartAssemblyCustom12": "Champ personnalisé 12", + "PartAssemblyCustom13": "Champ personnalisé 13", + "PartAssemblyCustom14": "Champ personnalisé 14", + "PartAssemblyCustom15": "Champ personnalisé 15", + "PartAssemblyCustom16": "Champ personnalisé 16", + "PartAssemblyCustom2": "Champ personnalisé 2", + "PartAssemblyCustom3": "Champ personnalisé 3", + "PartAssemblyCustom4": "Champ personnalisé 4", + "PartAssemblyCustom5": "Champ personnalisé 5", + "PartAssemblyCustom6": "Champ personnalisé 6", + "PartAssemblyCustom7": "Champ personnalisé 7", + "PartAssemblyCustom8": "Champ personnalisé 8", + "PartAssemblyCustom9": "Champ personnalisé 9", + "PartAssemblyList": "Assemblages de pièces", + "PartAssemblyName": "Nom d'assemblage de pièce", + "PartAssemblyNotes": "Remarques", + "PartByWarehouseInventoryList": "Stock de pièces", + "PartByWarehouseInventoryMinStockLevel": "Quantité minimum", + "PartByWarehouseInventoryQtyOnOrderCommitted": "Quantité en commande validée", + "PartByWarehouseInventoryQuantityOnOrder": "En commande", + "PartByWarehouseInventoryReorderQuantity": "Quantité de réapprovisionnement", + "PartCost": "Coût", + "PartCustom1": "Champ personnalisé 1", + "PartCustom10": "Champ personnalisé 10", + "PartCustom11": "Champ personnalisé 11", + "PartCustom12": "Champ personnalisé 12", + "PartCustom13": "Champ personnalisé 13", + "PartCustom14": "Champ personnalisé 14", + "PartCustom15": "Champ personnalisé 15", + "PartCustom16": "Champ personnalisé 16", + "PartCustom2": "Champ personnalisé 2", + "PartCustom3": "Champ personnalisé 3", + "PartCustom4": "Champ personnalisé 4", + "PartCustom5": "Champ personnalisé 5", + "PartCustom6": "Champ personnalisé 6", + "PartCustom7": "Champ personnalisé 7", + "PartCustom8": "Champ personnalisé 8", + "PartCustom9": "Champ personnalisé 9", + "PartDescription": "Description de la pièce", + "PartInventoryAdjustment": "Ajustement de stock de pièces", + "PartInventoryBalance": "Disponible", + "PartInventoryId": "ID d'inventaire", + "PartInventoryDataList": "Inventaire des pièces", + "PartInventoryTransaction": "Transaction d'inventaire", + "PartInventoryTransactionDescription": "La description", + "PartInventoryTransactionEntryDate": "Date", + "PartInventoryTransactionList": "Opérations d'inventaire", + "PartInventoryTransactionQuantity": "Quantité", + "PartInventoryTransactionSource": "Source de transaction", + "PartList": "Pièces", + "PartManufacturerID": "Fabricant", + "PartManufacturerNumber": "Numéro de fabricant", + "PartName": "Nom de la pièce", + "PartNotes": "Remarques", + "PartRestockRequiredByVendorList": "Réassortiment de pièces demandé par le fournisseur", + "PartRetail": "Détail", + "PartSerial": "Pièce numérotée", + "PartSerialNumbersAvailable": "Numéros de série disponibles", + "PartSerialWarehouseID": "Magasin de pièces", + "PartStockingLevels": "Niveaux de stockage des pièces", + "PartUPC": "CUP", + "PartWarehouse": "Magasin de pièces", + "PartWarehouseCustom1": "Champ personnalisé 1", + "PartWarehouseCustom10": "Champ personnalisé 10", + "PartWarehouseCustom11": "Champ personnalisé 11", + "PartWarehouseCustom12": "Champ personnalisé 12", + "PartWarehouseCustom13": "Champ personnalisé 13", + "PartWarehouseCustom14": "Champ personnalisé 14", + "PartWarehouseCustom15": "Champ personnalisé 15", + "PartWarehouseCustom16": "Champ personnalisé 16", + "PartWarehouseCustom2": "Champ personnalisé 2", + "PartWarehouseCustom3": "Champ personnalisé 3", + "PartWarehouseCustom4": "Champ personnalisé 4", + "PartWarehouseCustom5": "Champ personnalisé 5", + "PartWarehouseCustom6": "Champ personnalisé 6", + "PartWarehouseCustom7": "Champ personnalisé 7", + "PartWarehouseCustom8": "Champ personnalisé 8", + "PartWarehouseCustom9": "Champ personnalisé 9", + "PartWarehouseList": "Magasins de pièces", + "PartWarehouseName": "Nom de magasin de pièces", + "PartWarehouseNotes": "Remarques", + "PartWholesalerID": "Grossiste", + "PartWholesalerNumber": "Numéro de grossiste", + "PasswordResetMessageBody": "Bonjour {user_name},\n\nVotre compte de service en ligne est disponible une fois que vous avez défini votre mot de passe.\nVous pouvez utiliser le lien suivant pendant les 48 prochaines heures pour définir votre mot de passe.\n\nDéfinissez votre mot de passe: {action_link}\n\nSi vous n'avez pas demandé de réinitialisation de compte ou de mot de passe, veuillez ignorer cet e-mail.\n\nMerci,\n{registered_to}", + "PasswordResetMessageTitle": "Votre compte en ligne est prêt", + "PickListTemplate": "Modèle de liste de sélection", + "PickListTemplates": "Sélectionner des modèles de liste", + "PM": "Maintenance préventive", + "PMItem": "Maintenance préventive - article", + "PMItemExpense": "Maintenance préventive - dépenses", + "PMItemLabor": "Maintenance préventive - main-d'oeuvre", + "PMItemLoan": "Maintenance préventive - unité de prêt", + "PMItemOutsideService": "Maintenance préventive - service tiers", + "PMItemPart": "Maintenance préventive - pièces", + "PMItemScheduledUser": "Maintenance préventive - utilisateurs programmés", + "PMItemTravel": "Maintenance préventive - déplacements", + "PMItemTask": "Maintenance préventive - tâches", + "PMItemUnit": "Maintenance préventive - unités", + "PMList": "Liste de maintenance préventive", + "PMNextServiceDate": "Date de service suivant", + "PMNextWoGenerateDate": "Prochain événement de génération", + "PMSerialNumber": "Numéro", + "PMStopGeneratingDate": "Arrêter de générer des dates", + "Price": "Prix", + "PriceOverride": "Remplacement de prix", + "Print": "Imprimer", + "Processed":"Date de traitement", + "ProcessingJob": "Traitement du travail du serveur", + "Project": "Projet", + "ProjectAccountNumber": "Numéro de compte", + "ProjectCustom1": "Champ personnalisé 1", + "ProjectCustom10": "Champ personnalisé 10", + "ProjectCustom11": "Champ personnalisé 11", + "ProjectCustom12": "Champ personnalisé 12", + "ProjectCustom13": "Champ personnalisé 13", + "ProjectCustom14": "Champ personnalisé 14", + "ProjectCustom15": "Champ personnalisé 15", + "ProjectCustom16": "Champ personnalisé 16", + "ProjectCustom2": "Champ personnalisé 2", + "ProjectCustom3": "Champ personnalisé 3", + "ProjectCustom4": "Champ personnalisé 4", + "ProjectCustom5": "Champ personnalisé 5", + "ProjectCustom6": "Champ personnalisé 6", + "ProjectCustom7": "Champ personnalisé 7", + "ProjectCustom8": "Champ personnalisé 8", + "ProjectCustom9": "Champ personnalisé 9", + "ProjectDateCompleted": "Date de fin", + "ProjectDateStarted": "Date de début", + "ProjectList": "Projets", + "ProjectName": "Nom de projet", + "ProjectNotes": "Remarques", + "ProjectProjectOverseerID": "Responsable de projet", + "PurchaseLicense": "Acheter une licence", + "PurchaseOrder": "Bon de commande", + "PurchaseOrderCustom1": "Champ personnalisé 1", + "PurchaseOrderCustom10": "Champ personnalisé 10", + "PurchaseOrderCustom11": "Champ personnalisé 11", + "PurchaseOrderCustom12": "Champ personnalisé 12", + "PurchaseOrderCustom13": "Champ personnalisé 13", + "PurchaseOrderCustom14": "Champ personnalisé 14", + "PurchaseOrderCustom15": "Champ personnalisé 15", + "PurchaseOrderCustom16": "Champ personnalisé 16", + "PurchaseOrderCustom2": "Champ personnalisé 2", + "PurchaseOrderCustom3": "Champ personnalisé 3", + "PurchaseOrderCustom4": "Champ personnalisé 4", + "PurchaseOrderCustom5": "Champ personnalisé 5", + "PurchaseOrderCustom6": "Champ personnalisé 6", + "PurchaseOrderCustom7": "Champ personnalisé 7", + "PurchaseOrderCustom8": "Champ personnalisé 8", + "PurchaseOrderCustom9": "Champ personnalisé 9", + "PurchaseOrderDropShipToCustomerID": "Livraison directe au client", + "PurchaseOrderExpectedReceiveDate": "Réception prévue", + "PurchaseOrderItem": "Élément de bon de commande", + "PurchaseOrderItemLineTotal": "Total de la ligne", + "PurchaseOrderItemList": "Articles de commande d'achat", + "PurchaseOrderItemNetTotal": "Total net", + "PurchaseOrderItemPartName": "Nom de pièce", + "PurchaseOrderItemPartNumber": "Numéro de pièce", + "PurchaseOrderItemPartRequestedByID": "Demandé par", + "PurchaseOrderItemPurchaseOrderCost": "Coût de bon de commande", + "PurchaseOrderItemQuantityOrdered": "Quantité commandée", + "PurchaseOrderItemQuantityReceived": "Quantité réceptionnée", + "PurchaseOrderItemSerialNumbers": "Numéros de série", + "PurchaseOrderItemUIOrderedFrom": "Commandé chez", + "PurchaseOrderItemVendorPartNumber": "Fournisseur n°", + "PurchaseOrderItemWorkOrderNumber": "Bon de travail n°", + "PurchaseOrderNotes": "Remarques", + "PurchaseOrderOrderedDate": "Date de commande", + "PurchaseOrderPONumber": "Numéro de bon de commande", + "PurchaseOrderReceiptItemQuantityReceivedErrorInvalid": "La valeur doit être positive et ne pas dépasser le montant commandé", + "PurchaseOrderReceiptItemReceiptCost": "Coût reçu", + "PurchaseOrderReceiptReceivedDate": "Date de réception", + "PurchaseOrderReceiptText1": "Text1", + "PurchaseOrderReceiptText2": "Text2", + "PurchaseOrderReferenceNumber": "Numéro de référence", + "PurchaseOrderStatus": "État de bon de commande", + "PurchaseOrderStatusClosedFullReceived": "Fermé - totalement réceptionné", + "PurchaseOrderStatusClosedNoneReceived": "Fermé - non réceptionné", + "PurchaseOrderStatusClosedPartialReceived": "Fermé - partiellement réceptionné", + "PurchaseOrderStatusOpenNotYetOrdered": "Ouvert - pas encore commandé", + "PurchaseOrderStatusOpenOrdered": "Ouvert - en commande", + "PurchaseOrderStatusOpenPartialReceived": "Ouvert - partiellement réceptionné", + "PurchaseOrderUICopyToPurchaseOrder": "Copier dans le bon de commande", + "PurchaseOrderUIRestockList": "Liste de réassortiment", + "PurchaseOrderVendorMemo": "Mémo fournisseur", + "QuantityRequired": "Quantité Nécessaire", + "Quote": "Devis", + "QuoteDateApproved": "Approuvé", + "QuoteDateSubmitted": "Envoyé", + "QuoteIntroduction": "Texte d'introduction", + "QuoteItem": "Article de devis", + "QuoteItemExpense": "Devis - dépenses", + "QuoteItemLabor": "Devis - main-d'oeuvre", + "QuoteItemLoan": "Devis - unité de prêt", + "QuoteItemOutsideService": "Devis - service tiers", + "QuoteItemPart": "Devis - pièces", + "QuoteItemScheduledUser": "Devis - utilisateurs programmés", + "QuoteItemTravel": "Devis - déplacements", + "QuoteItemTask": "Devis - tâches", + "QuoteItemUnit": "Devis - unités", + "QuoteList": "Devis", + "QuotePreparedByID": "Préparé par l'utilisateur", + "QuoteQuoteRequestDate": "Demandé", + "QuoteQuoteStatusType": "État", + "QuoteSerialNumber": "Numéro", + "QuoteStatusList": "Liste des statuts de devis", + "QuoteValidUntilDate": "Date de validité", + "RateAccountNumber": "Numéro de compte", + "RateCharge": "Prix de détail", + "RateContractRate": "Tarif de contrat", + "RateUnitChargeDescriptionID": "Description de tarif unitaire", + "ReadOnly": "Lecture seule", + "ReceiveAll": "Recevoir tout", + "RecentWorkOrders": "Ordres de travail récents", + "RecordHistory": "Enregistrer l'historique", + "Refresh": "Actualiser...", + "Region": "Région", + "RegisteredUser": "Utilisateur inscrit", + "Reminder": "Rappel", + "ReminderColor": "Couleur", + "ReminderCustom1": "Champ personnalisé 1", + "ReminderCustom10": "Champ personnalisé 10", + "ReminderCustom11": "Champ personnalisé 11", + "ReminderCustom12": "Champ personnalisé 12", + "ReminderCustom13": "Champ personnalisé 13", + "ReminderCustom14": "Champ personnalisé 14", + "ReminderCustom15": "Champ personnalisé 15", + "ReminderCustom16": "Champ personnalisé 16", + "ReminderCustom2": "Champ personnalisé 2", + "ReminderCustom3": "Champ personnalisé 3", + "ReminderCustom4": "Champ personnalisé 4", + "ReminderCustom5": "Champ personnalisé 5", + "ReminderCustom6": "Champ personnalisé 6", + "ReminderCustom7": "Champ personnalisé 7", + "ReminderCustom8": "Champ personnalisé 8", + "ReminderCustom9": "Champ personnalisé 9", + "ReminderList": "Rappels", + "ReminderName": "Nom", + "ReminderNotes": "Remarques", + "ReminderStartDate": "Début", + "ReminderStopDate": "Fin", + "Remove": "Retirer", + "RemoveRoles": "Qui peut supprimer", + "RenderingReport": "Génération de rapport en cours", + "RepeatInterval": "Intervalle de répétition", + "Replace": "Remplacer", + "Report": "Rapport", + "ReportDesignReport": "Modifier le rapport", + "ReportDisplayHeaderFooter": "Afficher l'en-tête et le pied de page", + "ReportEditorData": "Exemple de données", + "ReportEditorMobileWarning": "Éditeur non pris en charge sur les appareils mobiles", + "ReportEditorProperties": "Propriétés", + "ReportFooterTemplate": "Modèle de pied de page", + "ReportHeaderTemplate": "Modèle d'en-tête", + "ReportIncludeAllWorkOrderItemDescendants": "Inclure tous les descendants de l'élément de l'ordre de travail", + "ReportLandscape": "Paysage", + "ReportList": "Modèles de rapport", + "ReportMarginOptionsBottom": "Marge inférieure", + "ReportMarginOptionsLeft": "Marge de gauche", + "ReportMarginOptionsRight": "Marge droite", + "ReportMarginOptionsTop": "Marge supérieure", + "ReportName": "Nom", + "ReportNotes": "Remarques", + "ReportPaperFormat": "Format papier", + "ReportPdfOptions": "Options PDF", + "ReportPreferCSSPageSize": "Préférez la taille de la page CSS", + "ReportPrintBackground": "Imprimer l'arrière-plan", + "ReportRenderTimeOut": "Le rapport prenait trop de minutes à traiter, la limite est définie sur {0}", + "ReportScale": "Échelle", + "ReportTemplate": "Modèle", + "RequestEvaluationLicense": "Demander une licence d'évaluation", + "ResetToDefault": "Rétablir la valeur par défaut", + "Review": "Évaluation", + "ReviewAssignedByUserId": "Assigné par", + "ReviewCompletedDate": "Date de fin", + "ReviewCompletionNotes": "Notes d'achèvement", + "ReviewCustom1": "Champ personnalisé 1", + "ReviewCustom10": "Champ personnalisé 10", + "ReviewCustom11": "Champ personnalisé 11", + "ReviewCustom12": "Champ personnalisé 12", + "ReviewCustom13": "Champ personnalisé 13", + "ReviewCustom14": "Champ personnalisé 14", + "ReviewCustom15": "Champ personnalisé 15", + "ReviewCustom16": "Champ personnalisé 16", + "ReviewCustom2": "Champ personnalisé 2", + "ReviewCustom3": "Champ personnalisé 3", + "ReviewCustom4": "Champ personnalisé 4", + "ReviewCustom5": "Champ personnalisé 5", + "ReviewCustom6": "Champ personnalisé 6", + "ReviewCustom7": "Champ personnalisé 7", + "ReviewCustom8": "Champ personnalisé 8", + "ReviewCustom9": "Champ personnalisé 9", + "ReviewDate": "Date de révision", + "ReviewList": "Liste d'évaluation", + "ReviewName": "Nom", + "ReviewNotes": "Remarques", + "ReviewOverDue": "Overdue", + "ReviewUserId": "Assigné à", + "RowsPerPage": "Lignes par page", + "Save": "Enregistrer", + "SaveACopy": "Enregistrer une copie", + "SaveRecordToProceed": "Enregistrer pour continuer", + "Schedule": "Programmer", + "Schedule4Day": "4 jours", + "ScheduleCategory": "Journée - équipe", + "ScheduleConflict": "Conflit d'horaire", + "ScheduleDay": "Jour", + "ScheduleDayView": "Affichage Journée unique", + "ScheduleEditReminder": "Modifier le rappel", + "ScheduleEditScheduleableUserGroup": "Modifier les groupes d'utilisateurs programmables", + "ScheduleEditWorkOrder": "Modifier le bon de travail sélectionné", + "ScheduleFirstHour": "Première heure à afficher dans la vue journalière", + "ScheduleMonth": "Mois", + "ScheduleOptions": "Paramètres de planification", + "ScheduleShowTypes": "Éléments à afficher", + "ScheduleWeek": "Semaine", + "ScheduleWOColorFrom": "Source de couleur de l'ordre de travail", + "SchemaVersion": "Version du schéma", + "Search": "Rechercher", + "SeedLevel": "Exemple de taille de données", + "SeedLevelHuge": "Énorme - très grand ensemble de données (environ 1 heure à traiter)", + "SeedLevelLarge": "Grande - multi-régions avec effectif complet (environ 10 minutes pour le traitement)", + "SeedLevelMedium": "Moyen - de nombreux techniciens de service et personnel d'assistance (environ 5 minutes pour le traitement)", + "SeedLevelSmall": "Petit atelier de service (environ 1 minute pour traiter)", + "SelectAlternateAddress": "Sélectionnez une autre adresse", + "SelectedItems": "Éléments sélectionnés", + "SelectItem": "Sélectionner", + "SelectRoles": "Qui peut sélectionner", + "SendEvaluationRequest": "Envoyer une demande", + "SendPasswordResetCode": "Envoyer un e-mail de réinitialisation du mot de passe", + "Sequence": "Séquence", + "Server": "Serveur", + "ServerAddress": "Adresse du serveur", + "ServerJob": "Travail serveur", + "ServerJobs": "File d'attente des travaux", + "ServerLog": "Journaux du serveur", + "ServerMetrics": "Mesure du serveur", + "ServerProfiler": "Profileur", + "ServerState": "État du serveur", + "ServerStateLoginRestricted": "Connexion restreinte en raison du paramètre d'état du serveur", + "ServerStateMigrateMode": "Mode de migration du serveur", + "ServerStateOpen": "Ouvrir", + "ServerStateOps": "Opérations système uniquement", + "ServerStateReason": "Objectif", + "ServerTime": "Heure du serveur", + "Service": "Service", + "ServiceHistory": "Historique de service", + "ServicePreventiveMaintenance": "EP", + "ServiceRate": "Taux de service", + "ServiceRateCustom1": "Champ personnalisé 1", + "ServiceRateCustom10": "Champ personnalisé 10", + "ServiceRateCustom11": "Champ personnalisé 11", + "ServiceRateCustom12": "Champ personnalisé 12", + "ServiceRateCustom13": "Champ personnalisé 13", + "ServiceRateCustom14": "Champ personnalisé 14", + "ServiceRateCustom15": "Champ personnalisé 15", + "ServiceRateCustom16": "Champ personnalisé 16", + "ServiceRateCustom2": "Champ personnalisé 2", + "ServiceRateCustom3": "Champ personnalisé 3", + "ServiceRateCustom4": "Champ personnalisé 4", + "ServiceRateCustom5": "Champ personnalisé 5", + "ServiceRateCustom6": "Champ personnalisé 6", + "ServiceRateCustom7": "Champ personnalisé 7", + "ServiceRateCustom8": "Champ personnalisé 8", + "ServiceRateCustom9": "Champ personnalisé 9", + "ServiceRateList": "Tarifs de service", + "ServiceRateNotes": "Remarques", + "SetLoginPassword": "Définir Nom d'utilisateur et Mot de passe", + "Settings": "Réglages", + "ShutDownServer": "Arrêter le serveur", + "SmallLogo": "Logo de petite taille", + "SmtpAccount": "Compte de serveur SMTP", + "SmtpDeliveryActive": "Notification SMTP active", + "SmtpPassword": "Mot de passe du serveur SMTP", + "SmtpServerAddress": "Adresse du serveur SMTP", + "SmtpServerPort": "Port du serveur SMTP", + "SoftDelete": "Marquer pour suppression", + "SoftDeleteAll": "Marquer * tout * pour suppression", + "Sort": "Trier", + "StartAttachmentMaintenanceJob": "Démarrer le travail de maintenance des pièces jointes", + "StartEvaluation": "Commencer l'évaluation de l'essai", + "StartJob": "Commencer le travail", + "Statistics": "Statistiques", + "Status": "Statut", + "StatusColor": "Couleur", + "StatusCompleted": "Est un statut terminé", + "StatusLocked": "Est un statut de verrouillage", + "StatusName": "Nom", + "StatusNotes": "Remarques", + "StopWords1": "quelle quelles quels qui sa sans ses seulement si sien son sont sous soyez sujet su ta tandis tellement tels tes ton tous tout top tès tu valeu voie voient vont vote vous vu ça étaient état étions été ête", + "StopWords2": "alos au aucuns aussi aute avant avec avoi bon ca ce cela ces ceux chaque ci comme comment dans des du dedans dehos depuis deux devait doit donc dos doite début elle elles en encoe essai est et eu fait faites fois", + "StopWords3": "font foce haut hos ici il ils je juste la le les leu là ma maintenant mais mes mine moins mon mot même ni nommés note nous nouveaux ou où pa pace paole pas pesonnes peut peu pièce plupat pou pouquoi quand que quel", + "StopWords4": "?", + "StopWords5": "?", + "StopWords6": "?", + "StopWords7": "?", + "SupportedUntil": "Support et mises à jour date d'expiration", + "Table": "Tableau", + "Tag": "Balise", + "TaggedWith": "Balisé avec", + "Tags": "Balises", + "Task": "Tâche", + "TaskGroup": "Groupe de tâches", + "TaskGroupList": "Groupes de tâches", + "TaskGroupName": "Nom de groupe de tâches", + "TaskGroupNotes": "Remarques", + "TaskList": "Tâches", + "Tax": "Impôt", + "TaxAAmt": "Montant de la taxe « A »", + "TaxBAmt": "Montant de la taxe « B »", + "TaxCode": "Code de taxe", + "TaxCodeCustom1": "Champ personnalisé 1", + "TaxCodeCustom10": "Champ personnalisé 10", + "TaxCodeCustom11": "Champ personnalisé 11", + "TaxCodeCustom12": "Champ personnalisé 12", + "TaxCodeCustom13": "Champ personnalisé 13", + "TaxCodeCustom14": "Champ personnalisé 14", + "TaxCodeCustom15": "Champ personnalisé 15", + "TaxCodeCustom16": "Champ personnalisé 16", + "TaxCodeCustom2": "Champ personnalisé 2", + "TaxCodeCustom3": "Champ personnalisé 3", + "TaxCodeCustom4": "Champ personnalisé 4", + "TaxCodeCustom5": "Champ personnalisé 5", + "TaxCodeCustom6": "Champ personnalisé 6", + "TaxCodeCustom7": "Champ personnalisé 7", + "TaxCodeCustom8": "Champ personnalisé 8", + "TaxCodeCustom9": "Champ personnalisé 9", + "TaxCodeDefault": "Erreur : impossible de supprimer ou de désactiver ce code de taxe s'il correspond à un réglage par défaut dans les Réglages généraux", + "TaxCodeList": "Codes de taxe", + "TaxCodeName": "Nom de code de taxe", + "TaxCodeNotes": "Remarques", + "TaxCodeTaxA": "Taxe “A”", + "TaxCodeTaxB": "Taxe “B”", + "TaxCodeTaxOnTax": "Taxe sur taxe", + "TechSignature": "Signature du technicien", + "TemplateTokens": "Jetons de modèle", + "TestSMTPSettings": "Envoyer un message de test", + "TestToAddress": "Envoyer à l'adresse", + "ThankYouForEvaluating": "Merci d'essayer Sockeye. Veuillez utiliser les liens suivants pour vous aider à explorer Sockeye et voir s'il correspond bien à votre organisation.", + "TimedOut": "Délai dépassé", + "TimeSpan": "Laps de temps", + "TimeSpanDays": "jours", + "TimeSpanHours": "heures", + "TimeSpanMinutes": "minutes", + "TimeSpanMonths": "mois", + "TimeSpanSeconds": "secondes", + "TimeSpanYears": "années", + "TimeStamp": "Horodatage", + "TimeToCompletion": "Durée jusqu'à l'état terminé", + "TimeZone": "Fuseau horaire par défaut de remplacement", + "TooManyResults": "Trop de résultats pour tout montrer", + "Total": "Total", + "Translation": "Traduction", + "TranslationDisplayText": "Texte standard", + "TranslationKey": "Clé", + "TranslationList": "Traductions", + "TravelRate": "Tarif voyage", + "TravelRateCustom1": "Champ personnalisé 1", + "TravelRateCustom10": "Champ personnalisé 10", + "TravelRateCustom11": "Champ personnalisé 11", + "TravelRateCustom12": "Champ personnalisé 12", + "TravelRateCustom13": "Champ personnalisé 13", + "TravelRateCustom14": "Champ personnalisé 14", + "TravelRateCustom15": "Champ personnalisé 15", + "TravelRateCustom16": "Champ personnalisé 16", + "TravelRateCustom2": "Champ personnalisé 2", + "TravelRateCustom3": "Champ personnalisé 3", + "TravelRateCustom4": "Champ personnalisé 4", + "TravelRateCustom5": "Champ personnalisé 5", + "TravelRateCustom6": "Champ personnalisé 6", + "TravelRateCustom7": "Champ personnalisé 7", + "TravelRateCustom8": "Champ personnalisé 8", + "TravelRateCustom9": "Champ personnalisé 9", + "TravelRateList": "Tarifs de voyage", + "TravelRateNotes": "Remarques", + "TrialSeeder": "Données sur les semences d'essai", + "True": "Vrai", + "TypeToSearchOrAdd": "Commencez à taper pour rechercher ou ajouter", + "UiFieldDataType": "Type de données de champ", + "UiFieldDataTypesCurrency": "Monnaie", + "UiFieldDataTypesDateOnly": "Date", + "UiFieldDataTypesDateTime": "Date et heure", + "UiFieldDataTypesDecimal": "Décimal", + "UiFieldDataTypesInteger": "Entier", + "UiFieldDataTypesText": "Texte", + "UiFieldDataTypesTimeOnly": "Heure", + "UiFieldDataTypesTrueFalse": "Vrai/Faux", + "Undelete": "Annuler la suppression", + "Unit": "Unité", + "UnitBoughtHere": "Acheté ici", + "UnitCustom1": "Champ personnalisé 1", + "UnitCustom10": "Champ personnalisé 10", + "UnitCustom11": "Champ personnalisé 11", + "UnitCustom12": "Champ personnalisé 12", + "UnitCustom13": "Champ personnalisé 13", + "UnitCustom14": "Champ personnalisé 14", + "UnitCustom15": "Champ personnalisé 15", + "UnitCustom16": "Champ personnalisé 16", + "UnitCustom2": "Champ personnalisé 2", + "UnitCustom3": "Champ personnalisé 3", + "UnitCustom4": "Champ personnalisé 4", + "UnitCustom5": "Champ personnalisé 5", + "UnitCustom6": "Champ personnalisé 6", + "UnitCustom7": "Champ personnalisé 7", + "UnitCustom8": "Champ personnalisé 8", + "UnitCustom9": "Champ personnalisé 9", + "UnitDescription": "Description", + "UnitList": "Unités client", + "UnitMetered": "Unité mesurée", + "UnitMeterReading": "Lecture de compteur d'unités", + "UnitMeterReadingDescription": "Description de compteur", + "UnitMeterReadingList": "Liste de lectures de compteur d'unités", + "UnitMeterReadingMeter": "Lecture de compteur d'unités", + "UnitMeterReadingMeterDate": "Date de lecture de compteur", + "UnitMeterReadingWorkOrderItemID": "Lecture de compteur sur bon de travail", + "UnitModel": "Modèle d'unité", + "UnitModelCustom1": "Champ personnalisé 1", + "UnitModelCustom10": "Champ personnalisé 10", + "UnitModelCustom11": "Champ personnalisé 11", + "UnitModelCustom12": "Champ personnalisé 12", + "UnitModelCustom13": "Champ personnalisé 13", + "UnitModelCustom14": "Champ personnalisé 14", + "UnitModelCustom15": "Champ personnalisé 15", + "UnitModelCustom16": "Champ personnalisé 16", + "UnitModelCustom2": "Champ personnalisé 2", + "UnitModelCustom3": "Champ personnalisé 3", + "UnitModelCustom4": "Champ personnalisé 4", + "UnitModelCustom5": "Champ personnalisé 5", + "UnitModelCustom6": "Champ personnalisé 6", + "UnitModelCustom7": "Champ personnalisé 7", + "UnitModelCustom8": "Champ personnalisé 8", + "UnitModelCustom9": "Champ personnalisé 9", + "UnitModelDiscontinued": "Abandonné", + "UnitModelDiscontinuedDate": "Date d'abandon", + "UnitModelIntroducedDate": "Date d'introduction", + "UnitModelLifeTimeWarranty": "Garantie à vie", + "UnitModelName": "Nom de modèle d'unité", + "UnitModelNotes": "Remarques", + "UnitModels": "Modèles d'unité", + "UnitModelUPC": "CUP", + "UnitModelVendorID": "Unit model vendor", + "UnitModelWarrantyLength": "Durée de garantie", + "UnitModelWarrantyTerms": "Conditions de garantie", + "UnitNotes": "Remarques", + "UnitOfMeasure": "Unité de mesure", + "UnitOverrideLength": "Ignorer la durée", + "UnitOverrideLifeTime": "Ignorer la garantie à vie", + "UnitOverrideWarranty": "Ignorer la garantie", + "UnitOverrideWarrantyTerms": "Ignorer les conditions de la garantie", + "UnitParentUnitID": "Unité parent de cette unité", + "UnitPurchasedDate": "Date d'achat", + "UnitPurchaseFromID": "Acheté chez", + "UnitReceipt": "Numéro de reçu", + "UnitReplacedByUnitID": "Remplacé par unité", + "UnitSerial": "Numéro de série", + "UnitText1": "Text1", + "UnitText2": "Text2", + "UnitText3": "Text3", + "UnitText4": "Text4", + "UnitUnitHasOwnAddress": "L'unité possède sa propre adresse", + "UnitWarrantyInfo": "Informations de garantie", + "UpdateAvailable": "La mise à jour est disponible. Installez-le maintenant?", + "Upload": "Télécharger", + "User": "Utilisateur", + "UserColor": "Couleur utilisateur", + "UserCountExceeded": "Serveur fermé en raison d'un dépassement de licence pour les utilisateurs de type de service actif", + "UserCustom1": "Champ personnalisé 1", + "UserCustom10": "Champ personnalisé 10", + "UserCustom11": "Champ personnalisé 11", + "UserCustom12": "Champ personnalisé 12", + "UserCustom13": "Champ personnalisé 13", + "UserCustom14": "Champ personnalisé 14", + "UserCustom15": "Champ personnalisé 15", + "UserCustom16": "Champ personnalisé 16", + "UserCustom2": "Champ personnalisé 2", + "UserCustom3": "Champ personnalisé 3", + "UserCustom4": "Champ personnalisé 4", + "UserCustom5": "Champ personnalisé 5", + "UserCustom6": "Champ personnalisé 6", + "UserCustom7": "Champ personnalisé 7", + "UserCustom8": "Champ personnalisé 8", + "UserCustom9": "Champ personnalisé 9", + "UserEmailAddress": "Adresse e-mail d'utilisateur", + "UserEmployeeNumber": "Numéro d'employé", + "UserInterfaceSettings": "Interface utilisateur", + "UserList": "Utilisateurs", + "UserLogin": "Nom d'utilisateur", + "UserNotes": "Remarques", + "UserOptions": "Options utilisateur", + "UserPageAddress": "Adresse messageur", + "UserPhone1": "Téléphone1", + "UserPhone2": "Téléphone 2", + "UserPreferences": "Préférences d'utilisateur", + "UserSettings": "Paramètres utilisateur", + "UserTimeZoneOffset": "Décalage UTC", + "UserType": "Type d'utilisateur", + "UserTypeCustomer": "Utilisateur client", + "UserTypeHeadOffice": "Utilisateur client de siège social", + "UserTypeNotService": "Pas utilisateur de type de service", + "UserTypeService": "Utilisateur de type de service", + "UserTypeServiceContractor": "Sous-traitant", + "Vendor": "Fournisseur", + "VendorAccountNumber": "Numéro de compte", + "VendorAlertNotes": "Notes contextuelles", + "VendorContact": "Contact", + "VendorContactNotes": "Other contacts", + "VendorCustom1": "Champ personnalisé 1", + "VendorCustom10": "Champ personnalisé 10", + "VendorCustom11": "Champ personnalisé 11", + "VendorCustom12": "Champ personnalisé 12", + "VendorCustom13": "Champ personnalisé 13", + "VendorCustom14": "Champ personnalisé 14", + "VendorCustom15": "Champ personnalisé 15", + "VendorCustom16": "Champ personnalisé 16", + "VendorCustom2": "Champ personnalisé 2", + "VendorCustom3": "Champ personnalisé 3", + "VendorCustom4": "Champ personnalisé 4", + "VendorCustom5": "Champ personnalisé 5", + "VendorCustom6": "Champ personnalisé 6", + "VendorCustom7": "Champ personnalisé 7", + "VendorCustom8": "Champ personnalisé 8", + "VendorCustom9": "Champ personnalisé 9", + "VendorEmail": "Email", + "VendorList": "Fournisseurs", + "VendorName": "Nom de fournisseur", + "VendorNotes": "Remarques", + "VendorPhone1": "Téléphone de travail", + "VendorPhone2": "Fax", + "VendorPhone3": "Téléphone fixe", + "VendorPhone4": "Numéro de portable", + "VendorPhone5": "Téléavertisseur", + "Version": "Version", + "ViewEULA": "Voir le contrat de licence", + "ViewServerConfiguration": "Informations sur le serveur", + "Warranty": "Garantie", + "WarrantyExpires": "Valable jusque", + "WebAddress": "Adresse Web", + "Welcome": "Bienvenue chez Sockeye", + "WhoWillBeNotified": "Qui sera notifié ?", + "WorkOrder": "Bon de travail", + "WorkOrderAge": "Age", + "WorkOrderCloseByDate": "Date à compléter", + "WorkOrderContactCustomerHeadOfficeTaggedWith": "Autoriser uniquement à bon de travail, contacter, client ou siège social avec l'une de ces balises", + "WorkOrderConvertAllScheduledUsersToLabor": "Tout convertir en travail", + "WorkOrderConvertScheduledUserToLabor": "Copier Utilisateur programmé dans Main d'oeuvre", + "WorkOrderCustom1": "Champ personnalisé 1", + "WorkOrderCustom10": "Champ personnalisé 10", + "WorkOrderCustom11": "Champ personnalisé 11", + "WorkOrderCustom12": "Champ personnalisé 12", + "WorkOrderCustom13": "Champ personnalisé 13", + "WorkOrderCustom14": "Champ personnalisé 14", + "WorkOrderCustom15": "Champ personnalisé 15", + "WorkOrderCustom16": "Champ personnalisé 16", + "WorkOrderCustom2": "Champ personnalisé 2", + "WorkOrderCustom3": "Champ personnalisé 3", + "WorkOrderCustom4": "Champ personnalisé 4", + "WorkOrderCustom5": "Champ personnalisé 5", + "WorkOrderCustom6": "Champ personnalisé 6", + "WorkOrderCustom7": "Champ personnalisé 7", + "WorkOrderCustom8": "Champ personnalisé 8", + "WorkOrderCustom9": "Champ personnalisé 9", + "WorkOrderCustomerContactName": "Contact", + "WorkOrderCustomerReferenceNumber": "Référence client n° ", + "WorkOrderCustomerTaggedWith": "Bon de travail ou Client avec l'une de ces balises", + "WorkOrderErrorLocked": "Le bon de travail est actuellement défini sur un statut verrouillé et ne peut pas être modifié", + "WorkOrderFromPMID": "Parent de l'E.P.", + "WorkOrderFromQuoteID": "Parent du devis", + "WorkOrderGenerateUnit": "Generate unit from selected part", + "WorkOrderInternalReferenceNumber": "Référence interne n° ", + "WorkOrderInvoiceNumber": "Numéro de facture", + "WorkOrderItem": "Élément de bon de travail", + "WorkOrderItemCustom1": "Champ personnalisé 1", + "WorkOrderItemCustom10": "Champ personnalisé 10", + "WorkOrderItemCustom11": "Champ personnalisé 11", + "WorkOrderItemCustom12": "Champ personnalisé 12", + "WorkOrderItemCustom13": "Champ personnalisé 13", + "WorkOrderItemCustom14": "Champ personnalisé 14", + "WorkOrderItemCustom15": "Champ personnalisé 15", + "WorkOrderItemCustom16": "Champ personnalisé 16", + "WorkOrderItemCustom2": "Champ personnalisé 2", + "WorkOrderItemCustom3": "Champ personnalisé 3", + "WorkOrderItemCustom4": "Champ personnalisé 4", + "WorkOrderItemCustom5": "Champ personnalisé 5", + "WorkOrderItemCustom6": "Champ personnalisé 6", + "WorkOrderItemCustom7": "Champ personnalisé 7", + "WorkOrderItemCustom8": "Champ personnalisé 8", + "WorkOrderItemCustom9": "Champ personnalisé 9", + "WorkOrderItemExpense": "Dépenses diverses d'élément de bon de travail", + "WorkOrderItemExpenseChargeAmount": "Quantité à facturer", + "WorkOrderItemExpenseChargeTaxCodeID": "Code de taxe à facturer", + "WorkOrderItemExpenseChargeToCustomer": "Facturer au client", + "WorkOrderItemExpenseDescription": "Description", + "WorkOrderItemExpenseList": "Éléments de dépenses diverses", + "WorkOrderItemExpenseName": "Résumé dépenses diverses", + "WorkOrderItemExpenseReimburseUser": "Rembourser l'utilisateur", + "WorkOrderItemExpenseTaxPaid": "Taxe payée", + "WorkOrderItemExpenseTotalCost": "Coût total", + "WorkOrderItemExpenseUserID": "Utilisateur", + "WorkOrderItemLabor": "Main d'oeuvre d'élément de bon de travail", + "WorkOrderItemLaborList": "Éléments de main d'oeuvre", + "WorkOrderItemLaborNoChargeQuantity": "Quantité non facturée", + "WorkOrderItemLaborPrice": "Prix net", + "WorkOrderItemLaborServiceDetails": "Détails de service", + "WorkOrderItemLaborServiceRateID": "Tarif de service", + "WorkOrderItemLaborServiceRateQuantity": "Quantité de tarif de service", + "WorkOrderItemLaborServiceStartDate": "Date et heure de début de service", + "WorkOrderItemLaborServiceStopDate": "Date et heure de fin de service", + "WorkOrderItemLaborTaxRateSaleID": "Taxe sur les ventes", + "WorkOrderItemLaborUserID": "Utilisateur", + "WorkOrderItemList": "Éléments", + "WorkOrderItemLoan": "Prêt d'élément de bon de travail", + "WorkOrderItemLoanDueDate": "Date de retour", + "WorkOrderItemLoanList": "Éléments de prêt", + "WorkOrderItemLoanNotes": "Remarques", + "WorkOrderItemLoanOutDate": "Prêté", + "WorkOrderItemLoanQuantity": "Rate quantity", + "WorkOrderItemLoanRate": "Rate", + "WorkOrderItemLoanReturnDate": "Rendu", + "WorkOrderItemLoanTaxCodeID": "Taxe sur les ventes", + "WorkOrderItemLoanUnit": "Élément de prêt", + "WorkOrderItemOutsideService": "Service extérieur", + "WorkOrderItemOutsideServiceDateETA": "Date probable d'arrivée", + "WorkOrderItemOutsideServiceDateReturned": "Date de retour", + "WorkOrderItemOutsideServiceDateSent": "Date d'envoi", + "WorkOrderItemOutsideServiceList": "Liste des services extérieurs", + "WorkOrderItemOutsideServiceNotes": "Remarques", + "WorkOrderItemOutsideServiceRepairCost": "Coût de réparation", + "WorkOrderItemOutsideServiceRepairPrice": "Prix de réparation", + "WorkOrderItemOutsideServiceRMANumber": "Numéro RMA", + "WorkOrderItemOutsideServiceShippingCost": "Coût d'expédition", + "WorkOrderItemOutsideServiceShippingPrice": "Prix d'expédition", + "WorkOrderItemOutsideServiceTrackingNumber": "Numéro de suivi", + "WorkOrderItemOutsideServiceVendorSentToID": "Envoyé à", + "WorkOrderItemOutsideServiceVendorSentViaID": "Envoyé via", + "WorkOrderItemPart": "Pièce d'élément de bon de travail", + "WorkOrderItemPartDescription": "Description", + "WorkOrderItemPartList": "Éléments de pièce", + "WorkOrderItemPartPartID": "Pièce", + "WorkOrderItemPartPartWarehouseID": "Magasin", + "WorkOrderItemPartQuantity": "Quantité", + "WorkOrderItemPartRealizeSuggested": "Réaliser les quantités de pièces suggérées", + "WorkOrderItemPartRequest": "Demande de pièce d'élément de bon de travail", + "WorkOrderItemPartRequestList": "Demandes de pièces", + "WorkOrderItemPartRequestMore": "Demander {n} de plus", + "WorkOrderItemPartRequestOnOrder": "En commande", + "WorkOrderItemPartRequestPartID": "Pièce", + "WorkOrderItemPartRequestPartWarehouseID": "Magasin", + "WorkOrderItemPartRequestQuantity": "Quantité", + "WorkOrderItemPartRequestReceived": "Réceptionné", + "WorkOrderItemPartRequests": "Demandes de pièces", + "WorkOrderItemPartSuggestedQuantity": "Quantité suggérée", + "WorkOrderItemPartTaxPartSaleID": "Taxe sur les ventes", + "WorkOrderItemPartUsed": "Utilisé pour le service", + "WorkOrderItemPriorityColor": "Couleur", + "WorkOrderItemPriorityID": "Priorité", + "WorkOrderItemPriorityList": "Liste de priorité des éléments de l'ordre de travail", + "WorkOrderItemPriorityName": "Nom", + "WorkOrderItemRequestDate": "Date de demande", + "WorkOrderItemScheduledUser": "Utilisateur programmé d'élément de bon de travail", + "WorkOrderItemScheduledUserEstimatedQuantity": "Quantité estimée", + "WorkOrderItemScheduledUserList": "Éléments d'utilisateur programmé", + "WorkOrderItemScheduledUserServiceRateID": "Tarif conseillé", + "WorkOrderItemScheduledUserStartDate": "Date et heure de début", + "WorkOrderItemScheduledUserStopDate": "Date et heure de fin", + "WorkOrderItemScheduledUserUserID": "Utilisateur", + "WorkOrderItemStatusColor": "Couleur", + "WorkOrderItemStatusList": "Liste des statuts des articles", + "WorkOrderItemStatusName": "Nom", + "WorkOrderItemStatusNotes": "Remarques", + "WorkOrderItemSummary": "Résumé d'élément", + "WorkOrderItemTags": "Balises Élément de bon de travail", + "WorkOrderItemTask": "Tâche d'élément de bon de travail", + "WorkOrderItemTaskCompletedDate": "Date de fin", + "WorkOrderItemTaskCompletionTypeComplete": "Terminé", + "WorkOrderItemTaskCompletionTypeFailed": "Échec", + "WorkOrderItemTaskCompletionTypeIncomplete": "À faire", + "WorkOrderItemTaskCompletionTypeNotApplicable": "N/D", + "WorkOrderItemTasks": "Tâches", + "WorkOrderItemTaskTaskID": "Tâche", + "WorkOrderItemTaskUser": "Utilisateur", + "WorkOrderItemTaskWorkOrderItemTaskCompletionType": "État", + "WorkOrderItemTechNotes": "Notes de service", + "WorkOrderItemTravel": "Déplacement d'élément de bon de travail", + "WorkOrderItemTravelDetails": "Détails du déplacement", + "WorkOrderItemTravelDistance": "Distance", + "WorkOrderItemTravelList": "Éléments de déplacement", + "WorkOrderItemTravelNoChargeQuantity": "Quantité non facturée", + "WorkOrderItemTravelRateQuantity": "Quantité", + "WorkOrderItemTravelServiceRateID": "Tarif de déplacement", + "WorkOrderItemTravelStartDate": "Date de début", + "WorkOrderItemTravelStopDate": "Date de fin", + "WorkOrderItemTravelTaxRateSaleID": "Taxe sur les ventes", + "WorkOrderItemTravelUserID": "Utilisateur", + "WorkOrderItemUnit": "Article de l'ordre de travail - unité", + "WorkOrderItemUnitCustom1": "Champ personnalisé 1", + "WorkOrderItemUnitCustom10": "Champ personnalisé 10", + "WorkOrderItemUnitCustom11": "Champ personnalisé 11", + "WorkOrderItemUnitCustom12": "Champ personnalisé 12", + "WorkOrderItemUnitCustom13": "Champ personnalisé 13", + "WorkOrderItemUnitCustom14": "Champ personnalisé 14", + "WorkOrderItemUnitCustom15": "Champ personnalisé 15", + "WorkOrderItemUnitCustom16": "Champ personnalisé 16", + "WorkOrderItemUnitCustom2": "Champ personnalisé 2", + "WorkOrderItemUnitCustom3": "Champ personnalisé 3", + "WorkOrderItemUnitCustom4": "Champ personnalisé 4", + "WorkOrderItemUnitCustom5": "Champ personnalisé 5", + "WorkOrderItemUnitCustom6": "Champ personnalisé 6", + "WorkOrderItemUnitCustom7": "Champ personnalisé 7", + "WorkOrderItemUnitCustom8": "Champ personnalisé 8", + "WorkOrderItemUnitCustom9": "Champ personnalisé 9", + "WorkOrderItemUnitList": "Unités", + "WorkOrderItemUnitNotes": "Remarques", + "WorkOrderItemUnitTags": "Balises Article de l'ordre de travail - unité", + "WorkOrderItemWarrantyService": "Service de garantie", + "WorkOrderItemWorkOrderStatusID": "État d'élément de bon de travail", + "WorkOrderList": "Bons de travail de service", + "WorkOrderOnsite": "Sur site", + "WorkOrderSerialNumber": "Numéro", + "WorkOrderServiceDate": "Date de service", + "WorkOrderStatus": "État de bon de travail", + "WorkOrderStatusList": "États de bon de travail", + "WorkOrderSummary": "Résumé" +} \ No newline at end of file diff --git a/server/resource/rpt/ay-bc.js b/server/resource/rpt/ay-bc.js new file mode 100644 index 0000000..fb3f47b --- /dev/null +++ b/server/resource/rpt/ay-bc.js @@ -0,0 +1,73 @@ +// This file is part of the bwip-js project available at: +// +// http://metafloor.github.io/bwip-js +// +// Copyright (c) 2011-2021 Mark Warren +// +// This file contains code automatically generated from: +// Barcode Writer in Pure PostScript - Version 2021-02-06 +// Copyright (c) 2004-2021 Terry Burton +// +// The MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define([], factory); + } else if (typeof module === 'object' && module.exports) { + module.exports = factory(); + } else { + root.bwipjs = factory(); + } +}(typeof self !== 'undefined' ? self : this, function () { +var $$=null;var $j=0;var $k=[];var $0={$error:new Map};function $a(a){if(!arguments.length){for(var i=$j-1;i>=0&&$k[i]!==Infinity;i--);if(i<0){throw new Error("array-marker-not-found")}a=$k.splice(i+1,$j-1-i);$j=i}else if(!(a instanceof Array)){a=new Array(+arguments[0]);for(var i=0,l=a.length;i=0&&$k[mark]!==Infinity;mark-=2){if($k[mark-1]===Infinity){throw new Error("dict-malformed-stack")}}if(mark<0){throw"dict-marker-not-found"}var d=new Map;for(var i=mark+1;i<$j;i+=2){var k=$k[i];var t=typeof k;if(t=="number"||t=="string"){d.set(k,$k[i+1])}else if(k instanceof Uint8Array){d.set($z(k),$k[i+1])}else{throw"dict-not-a-valid-key("+k+")"}}$j=mark;return d}function $s(v){var t=typeof v;if(t==="number"){return new Uint8Array(v)}if(t!=="string"){v=""+v}var s=new Uint8Array(v.length);for(var i=0;i=0;i--){d[o+i]=s[i]}}}else if(d instanceof Array){var darr=d.b;var doff=o+d.o;var sarr=s.b;var soff=s.o;for(var i=0,l=s.length;i0&&$k[--$j]!==Infinity);}function $m(){for(var i=$j-1;i>=0&&$k[i]!==Infinity;i--);return $j-i-1}function $q(a){for(var i=0,l=a.length,b=a.b,o=a.o;ib}function $ge(a,b){if(a instanceof Uint8Array){a=$z(a)}if(b instanceof Uint8Array){b=$z(b)}return a>=b}function $an(a,b){return typeof a==="boolean"?a&&b:a&b}function $or(a,b){return typeof a==="boolean"?a||b:a|b}function $xo(a,b){return typeof a==="boolean"?!a&&b||a&&!b:a^b}function $nt(a){return typeof a=="boolean"?!a:~a}var $f=function(fa){return function(v){return Number.isInteger(v)?v:(fa[0]=v,fa[0])}}(new Float32Array(1));function $stack(){console.log("[[[");for(var i=$j-1;i>=0;i--){console.log(tostring($k[i]))}console.log("]]]");function tostring(v){if(v===null){return"null"}else if(v===undefined){return""}else if(v instanceof Array){var s="[";for(var j=v.o,a=v.b,l=v.length+v.o;j>"}else if(typeof v==="string"){return'"'+v+'"'}else{return""+v}}}function bwipp_raiseerror(){$p($0.$error,"errorinfo",$k[--$j]);$p($0.$error,"errorname",$k[--$j]);$p($0.$error,"command",null);$p($0.$error,"newerror",true);throw new Error($z($0.$error.get("errorname"))+": "+$z($0.$error.get("errorinfo")))}function bwipp_parseinput(){var $1={};$1.fncvals=$k[--$j];$1.barcode=$k[--$j];var _2="parse";$1[_2]=$g($1.fncvals,_2);delete $1.fncvals[_2];var _6="parsefnc";$1[_6]=$g($1.fncvals,_6);delete $1.fncvals[_6];var _A="parseonly";var _C=$g($1.fncvals,_A)!==undefined;$1[_A]=_C;delete $1.fncvals[_A];var _E="eci";var _G=$g($1.fncvals,_E)!==undefined;$1[_E]=_G;delete $1.fncvals[_E];$1.msg=$a($1.barcode.length);$1.j=0;$k[$j++]=$1.barcode;for(;;){$x($k[--$j],"^");var _M=$k[--$j];var _N=$k[--$j];$k[$j++]=_M;$k[$j++]=_N.length;$k[$j++]=$1.msg;$k[$j++]=$1.j;$k[$j++]=_N;$k[$j++]=Infinity;var _Q=$k[--$j];var _R=$k[--$j];$k[$j++]=_Q;$F(_R);var _S=$a();var _T=$k[--$j];$P($k[--$j],_T,_S);$1.j=$f($k[--$j]+$1.j);if($k[--$j]){$j--;for(var _Y=0,_Z=1;_Y<_Z;_Y++){if($an($nt($1.parse),$nt($1.parsefnc))){$p($1.msg,$1.j,94);$1.j=$f($1.j+1);break}$p($1.msg,$1.j,94);$1.j=$f($1.j+1);if($1.parse){var _j=$k[--$j];$k[$j++]=_j;if(_j.length>=3){var _k=$k[--$j];var _l=$G(_k,0,3);$k[$j++]=_k;$k[$j++]=true;for(var _m=0,_n=_l.length;_m<_n;_m++){var _o=$g(_l,_m);if(_o<48||_o>57){$j--;$k[$j++]=false}}if($k[--$j]){var _q=$k[--$j];var _r=$G(_q,0,3);var _s=~~$z(_r);$k[$j++]=_q;$k[$j++]=_s;if(_s>255){$j-=2;$k[$j++]="bwipp.invalidOrdinal";$k[$j++]="Ordinal must be 000 to 255";bwipp_raiseerror()}$1.j=$f($1.j-1);$p($1.msg,$1.j,$k[--$j]);$1.j=$f($1.j+1);var _y=$k[--$j];$k[$j++]=$G(_y,3,_y.length-3)}}}if($or($1.parseonly,$nt($1.parsefnc))||$g($1.msg,$f($1.j-1))!=94){break}$1.j=$f($1.j-1);var _16=$k[--$j];$k[$j++]=_16;if(_16.length<3){$j--;$k[$j++]="bwipp.truncatedFNC";$k[$j++]="Function character truncated";bwipp_raiseerror()}var _17=$k[--$j];$k[$j++]=_17;if($g(_17,0)==94){$p($1.msg,$1.j,94);$1.j=$f($1.j+1);var _1C=$k[--$j];$k[$j++]=$G(_1C,1,_1C.length-1);break}var _1E=$k[--$j];$k[$j++]=_1E;if($eq($G(_1E,0,3),"ECI")&&$1.eci){var _1H=$k[--$j];$k[$j++]=_1H;if(_1H.length<9){$j--;$k[$j++]="bwipp.truncatedECI";$k[$j++]="ECI truncated";bwipp_raiseerror()}var _1I=$k[--$j];var _1J=$G(_1I,3,6);$k[$j++]=_1I;$k[$j++]=_1J;for(var _1K=0,_1L=_1J.length;_1K<_1L;_1K++){var _1M=$g(_1J,_1K);if(_1M<48||_1M>57){$j-=2;$k[$j++]="bwipp.invalidECI";$k[$j++]="ECI must be 000000 to 999999";bwipp_raiseerror()}}var _1N=$k[--$j];$k[$j++]=0;$F(_1N,function(){var _1O=$k[--$j];var _1P=$k[--$j];$k[$j++]=$f(_1P-$f(_1O-48))*10});$p($1.msg,$1.j,~~($k[--$j]/10)-1e6);$1.j=$f($1.j+1);var _1U=$k[--$j];$k[$j++]=$G(_1U,9,_1U.length-9);break}var _1W=$k[--$j];$k[$j++]=_1W;if(_1W.length<4){$j--;$k[$j++]="bwipp.truncatedFNC";$k[$j++]="Function character truncated";bwipp_raiseerror()}var _1X=$k[--$j];var _1Y=$G(_1X,0,4);var _1a=$g($1.fncvals,_1Y)!==undefined;$k[$j++]=_1X;$k[$j++]=_1Y;if(!_1a){var _1b=$k[--$j];var _1c=$s(_1b.length+28);$P(_1c,28,_1b);$P(_1c,0,"Unknown function character: ");var _1d=$k[--$j];$k[$j++]=_1c;$k[$j++]=_1d;$j--;var _1e=$k[--$j];$k[$j++]="bwipp.unknownFNC";$k[$j++]=_1e;bwipp_raiseerror()}$p($1.msg,$1.j,$g($1.fncvals,$k[--$j]));$1.j=$f($1.j+1);var _1l=$k[--$j];$k[$j++]=$G(_1l,4,_1l.length-4);break}}else{break}}if($nt($1.parseonly)){$k[$j++]=$G($1.msg,0,$1.j)}else{$k[$j++]=$s($1.j);for(var _1v=0,_1u=$f($1.j-1);_1v<=_1u;_1v+=1){var _1w=$k[--$j];$p(_1w,_1v,$g($1.msg,_1v));$k[$j++]=_1w}}}function bwipp_gs1lint(){var $1={};$1.vals=$k[--$j];$1.ais=$k[--$j];$1.lintnumeric=function(){var _2=$k[--$j];$k[$j++]=true;$F(_2,function(){var _3=$k[--$j];if(_3<48||_3>57){$j--;$k[$j++]=false;return true}});if($nt($k[--$j])){$j--;$k[$j++]="bwipp.GS1notNumeric";$k[$j++]="Not numeric";$k[$j++]=false;return true}};$1.lintcset82=function(){var _5=$k[--$j];$k[$j++]=true;$F(_5,function(){var _6=$s(1);$p(_6,0,$k[--$j]);$x("!\"%&'()*+,-./0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz",_6);if($nt($k[--$j])){$j-=2;$k[$j++]=false;return true}$j-=3});if($nt($k[--$j])){$j--;$k[$j++]="bwipp.GS1badCSET82character";$k[$j++]="Invalid CSET 82 character";$k[$j++]=false;return true}};$1.lintcset39=function(){var _A=$k[--$j];$k[$j++]=true;$F(_A,function(){var _B=$s(1);$p(_B,0,$k[--$j]);$x("#-/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",_B);if($nt($k[--$j])){$j-=2;$k[$j++]=false;return true}$j-=3});if($nt($k[--$j])){$j--;$k[$j++]="bwipp.GS1badCSET39character";$k[$j++]="Invalid CSET 39 character";$k[$j++]=false;return true}};$1.lintkey=function(){var _F=$k[--$j];$k[$j++]=_F;if(_F.length<2){$j-=2;$k[$j++]="bwipp.GS1keyTooShort";$k[$j++]="Key is too short";$k[$j++]=false;return true}var _G=$k[--$j];var _H=$g(_G,0);var _I=$g(_G,1);if(_H<48||_H>57||(_I<48||_I>57)){$j--;$k[$j++]="bwipp.GS1badGCP";$k[$j++]="Non-numeric company prefix";$k[$j++]=false;return true}};$1.lintimporteridx=function(){$x("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz",$k[--$j]);if($nt($k[--$j])){$j-=2;$k[$j++]="bwipp.GS1badImporterIndex";$k[$j++]="Invalid importer index";$k[$j++]=false;return true}$j-=3};$1.lintcsum=function(){$k[$j++]=Infinity;var _L=$k[--$j];var _M=$k[--$j];var _N=_M.length%2==0?3:1;$k[$j++]=_L;$k[$j++]=_N;$F(_M,function(){var _O=$k[--$j];var _P=$k[--$j];$k[$j++]=$f(_O-48)*_P;$k[$j++]=$f(4-_P)});$j--;var _Q=$m()+1;$k[$j++]=0;for(var _R=0,_S=_Q-1;_R<_S;_R++){var _T=$k[--$j];var _U=$k[--$j];$k[$j++]=$f(_U+_T)}var _V=$k[--$j];var _W=$k[--$j];$k[$j++]=_V;$k[$j++]=_W;$j--;if($k[--$j]%10!=0){$j--;$k[$j++]="bwipp.GS1badChecksum";$k[$j++]="Bad checksum";$k[$j++]=false;return true}};$k[$j++]=Infinity;$k[$j++]=0;for(var _Y=0,_Z="!\"%&'()*+,-./0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".length;_Y<_Z;_Y++){var _b=$k[--$j];$k[$j++]=$g("!\"%&'()*+,-./0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz",_Y);$k[$j++]=_b;$k[$j++]=$f(_b+1)}$j--;$1.cset82=$d();$k[$j++]=Infinity;$k[$j++]=0;for(var _d=0,_e="23456789ABCDEFGHJKLMNPQRSTUVWXYZ".length;_d<_e;_d++){var _g=$k[--$j];$k[$j++]=$g("23456789ABCDEFGHJKLMNPQRSTUVWXYZ",_d);$k[$j++]=_g;$k[$j++]=$f(_g+1)}$j--;$1.cset32=$d();$1.lintcsumalpha=function(){var _i=$k[--$j];$k[$j++]=_i;if(_i.length<2){$j-=2;$k[$j++]="bwipp.GS1alphaTooShort";$k[$j++]="Alphanumeric string is too short to check";$k[$j++]=false;return true}var _j=$k[--$j];$k[$j++]=_j;$k[$j++]=_j.length-2;$k[$j++]=$G(_j,0,_j.length-2);$k[$j++]=Infinity;var _l=$k[--$j];var _m=$k[--$j];$k[$j++]=_l;$F(_m,function(){var _n=$k[--$j];var _p=$g($1.cset82,_n)!==undefined;$k[$j++]=_n;if(_p){var _s=$g($1.cset82,$k[--$j]);$k[$j++]=_s}else{$k[$j++]=-1;return true}});var _t=$k[--$j];$k[$j++]=_t;if(_t==-1){$l();$j-=3;$k[$j++]="bwipp.GS1UnknownCSET82Character";$k[$j++]="Unknown CSET 82 character";$k[$j++]=false;return true}$r($a($m()));var _w=$k[--$j];var _x=$k[--$j];$k[$j++]=_w;$k[$j++]=_x;$j--;var _y=$k[--$j];var _z=$k[--$j];var _11=$G($k[--$j],_z,2);$k[$j++]=_y;$k[$j++]=_11;$k[$j++]=Infinity;var _12=$k[--$j];var _13=$k[--$j];$k[$j++]=_12;$F(_13,function(){var _14=$k[--$j];var _16=$g($1.cset32,_14)!==undefined;$k[$j++]=_14;if(_16){var _19=$g($1.cset32,$k[--$j]);$k[$j++]=_19}else{$k[$j++]=-1;return true}});var _1A=$k[--$j];$k[$j++]=_1A;if(_1A==-1){$l();$j-=2;$k[$j++]="bwipp.GS1UnknownCSET32Character";$k[$j++]="Unknown CSET 32 character";$k[$j++]=false;return true}$r($a($m()));var _1D=$k[--$j];var _1E=$k[--$j];$k[$j++]=_1D;$k[$j++]=_1E;$j--;var _1F=$k[--$j];var _1I=$k[--$j];var _1J=$a([2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83]);var _1K=_1I.length;$k[$j++]=$f(($g(_1F,0)<<5)+$g(_1F,1));$k[$j++]=_1I;$k[$j++]=_1J;$k[$j++]=_1K;if(_1K>_1J.length){$j-=5;$k[$j++]="bwipp.GS1alphaTooLong";$k[$j++]="Alphanumeric string is too long to check";$k[$j++]=false;return true}var _1L=$k[--$j];var _1N=$G($k[--$j],0,_1L);for(var _1O=0,_1P=_1N.length;_1O<_1P;_1O++){var _1R=$k[--$j];$k[$j++]=$g(_1N,_1O);$k[$j++]=_1R}var _1S=$k[--$j];$k[$j++]=0;$F(_1S,function(){var _1T=$k[--$j];var _1U=$k[--$j];var _1V=$k[--$j];$k[$j++]=$f(_1U+_1V*_1T)});var _1W=$k[--$j];if($k[--$j]!=_1W%1021){$j--;$k[$j++]="bwipp.GS1badAlphaCheckCharacters";$k[$j++]="Bad alphanumeric check characters";$k[$j++]=false;return true}};$k[$j++]=Infinity;var _1Y=$a(["004","008","010","012","016","020","024","028","031","032","036","040","044","048","050","051","052","056","060","064","068","070","072","074","076","084","086","090","092","096","100","104","108","112","116","120","124","132","136","140","144","148","152","156","158","162","166","170","174","175","178","180","184","188","191","192","196","203","204","208","212","214","218","222","226","231","232","233","234","238","239","242","246","248","250","254","258","260","262","266","268","270","275","276","288","292","296","300","304","308","312","316","320","324","328","332","334","336","340","344","348","352","356","360","364","368","372","376","380","384","388","392","398","400","404","408","410","414","417","418","422","426","428","430","434","438","440","442","446","450","454","458","462","466","470","474","478","480","484","492","496","498","499","500","504","508","512","516","520","524","528","531","533","534","535","540","548","554","558","562","566","570","574","578","580","581","583","584","585","586","591","598","600","604","608","612","616","620","624","626","630","634","638","642","643","646","652","654","659","660","662","663","666","670","674","678","682","686","688","690","694","702","703","704","705","706","710","716","724","728","729","732","740","744","748","752","756","760","762","764","768","772","776","780","784","788","792","795","796","798","800","804","807","818","826","831","832","833","834","840","850","854","858","860","862","876","882","887","894"]);for(var _1Z=0,_1a=_1Y.length;_1Z<_1a;_1Z++){var _1b=$g(_1Y,_1Z);$k[$j++]=_1b;$k[$j++]=_1b}$1.iso3166=$d();$1.lintiso3166=function(){var _1f=$g($1.iso3166,$k[--$j])!==undefined;if(!_1f){$j--;$k[$j++]="bwipp.GS1UnknownCountry";$k[$j++]="Unknown country code";$k[$j++]=false;return true}};$1.lintiso3166999=function(){var _1g=$k[--$j];$k[$j++]=_1g;if($ne(_1g,"999")){var _1j=$g($1.iso3166,$k[--$j])!==undefined;if(!_1j){$j--;$k[$j++]="bwipp.GS1UnknownCountryOr999";$k[$j++]="Unknown country code or not 999";$k[$j++]=false;return true}}else{$j--}};$1.lintiso3166list=function(){var _1k=$k[--$j];$k[$j++]=_1k;if(_1k.length%3!=0){$j-=2;$k[$j++]="bwipp.GS1BadCountryListLength";$k[$j++]="Not a group of three-digit country codes";$k[$j++]=false;return true}var _1l=$k[--$j];$k[$j++]=_1l;$k[$j++]=true;for(var _1n=0,_1m=_1l.length-1;_1n<=_1m;_1n+=3){var _1o=$k[--$j];var _1p=$k[--$j];var _1s=$g($1.iso3166,$G(_1p,_1n,3))!==undefined;$k[$j++]=_1p;$k[$j++]=_1o;if(!_1s){$j-=2;$k[$j++]=false;break}}if($nt($k[--$j])){$j--;$k[$j++]="bwipp.GS1UnknownCountry";$k[$j++]="Unknown country code";$k[$j++]=false;return true}$j--};$k[$j++]=Infinity;var _1u=$a(["AD","AE","AF","AG","AI","AL","AM","AO","AQ","AR","AS","AT","AU","AW","AX","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ","BL","BM","BN","BO","BQ","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF","CG","CH","CI","CK","CL","CM","CN","CO","CR","CU","CV","CW","CX","CY","CZ","DE","DJ","DK","DM","DO","DZ","EC","EE","EG","EH","ER","ES","ET","FI","FJ","FK","FM","FO","FR","GA","GB","GD","GE","GF","GG","GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR","HT","HU","ID","IE","IL","IM","IN","IO","IQ","IR","IS","IT","JE","JM","JO","JP","KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","ME","MF","MG","MH","MK","ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY","QA","RE","RO","RS","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO","SR","SS","ST","SV","SX","SY","SZ","TC","TD","TF","TG","TH","TJ","TK","TL","TM","TN","TO","TR","TT","TV","TW","TZ","UA","UG","UM","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","YE","YT","ZA","ZM","ZW"]);for(var _1v=0,_1w=_1u.length;_1v<_1w;_1v++){var _1x=$g(_1u,_1v);$k[$j++]=_1x;$k[$j++]=_1x}$1.iso3166alpha2=$d();$1.lintiso3166alpha2=function(){var _21=$g($1.iso3166alpha2,$k[--$j])!==undefined;if(!_21){$j--;$k[$j++]="bwipp.GS1UnknownCountryAlpha";$k[$j++]="Unknown country alpha code";$k[$j++]=false;return true}};$k[$j++]=Infinity;var _22=$a(["008","012","032","036","044","048","050","051","052","060","064","068","072","084","090","096","104","108","116","124","132","136","144","152","156","170","174","188","191","192","203","208","214","222","230","232","238","242","262","270","292","320","324","328","332","340","344","348","352","356","360","364","368","376","388","392","398","400","404","408","410","414","417","418","422","426","430","434","446","454","458","462","480","484","496","498","504","512","516","524","532","533","548","554","558","566","578","586","590","598","600","604","608","634","643","646","654","682","690","694","702","704","706","710","728","748","752","756","760","764","776","780","784","788","800","807","818","826","834","840","858","860","882","886","901","927","928","929","930","931","932","933","934","936","938","940","941","943","944","946","947","948","949","950","951","952","953","955","956","957","958","959","960","961","962","963","964","965","967","968","969","970","971","972","973","975","976","977","978","979","980","981","984","985","986","990","994","997","999"]);for(var _23=0,_24=_22.length;_23<_24;_23++){var _25=$g(_22,_23);$k[$j++]=_25;$k[$j++]=_25}$1.iso4217=$d();$1.lintiso4217=function(){var _29=$g($1.iso4217,$k[--$j])!==undefined;if(!_29){$j--;$k[$j++]="bwipp.GS1UnknownCurrency";$k[$j++]="Unknown currency code";$k[$j++]=false;return true}};$1.lintiban=function(){var _2A=$k[--$j];$k[$j++]=_2A;if(_2A.length<4){$j-=2;$k[$j++]="bwipp.GS1tooShort";$k[$j++]="IBAN too short";$k[$j++]=false;return true}var _2B=$k[--$j];$k[$j++]=_2B;$k[$j++]=true;$F(_2B,function(){var _2C=$s(1);$p(_2C,0,$k[--$j]);$x("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",_2C);if($nt($k[--$j])){$j-=2;$k[$j++]=false;return true}$j-=3});if($nt($k[--$j])){$j--;$k[$j++]="bwipp.GS1badIBANcharacter";$k[$j++]="Invalid IBAN character";$k[$j++]=false;return true}var _2G=$k[--$j];$k[$j++]=_2G;$k[$j++]=_2G;$k[$j++]=Infinity;var _2H=$k[--$j];var _2I=$k[--$j];$k[$j++]=_2H;$k[$j++]=_2I;$k[$j++]=Infinity;var _2J=$k[--$j];var _2K=$k[--$j];$k[$j++]=_2J;$F(_2K);$r($m(),-4);$r($a($m()));var _2O=$k[--$j];var _2P=$k[--$j];$k[$j++]=_2O;$k[$j++]=_2P;$j--;$F($k[--$j],function(){var _2S=$f($k[--$j]-48);$k[$j++]=_2S;if(_2S>9){var _2U=$f($k[--$j]-7);$k[$j++]=~~(_2U/10);$k[$j++]=_2U%10}});$r($a($m()));var _2X=$k[--$j];var _2Y=$k[--$j];$k[$j++]=_2X;$k[$j++]=_2Y;$j--;var _2Z=$k[--$j];$k[$j++]=0;$F(_2Z,function(){var _2a=$k[--$j];var _2b=$k[--$j];$k[$j++]=$f(_2a+_2b*10)%97});if($k[--$j]!=1){$j-=2;$k[$j++]="bwipp.GS1badIBANchecksum";$k[$j++]="IBAN checksum incorrect";$k[$j++]=false;return true}var _2e=$G($k[--$j],0,2);$k[$j++]=_2e;$1.lintiso3166alpha2()};$1.lintzero=function(){if($ne($k[--$j],"0")){$j--;$k[$j++]="bwipp.GS1zeroRequired";$k[$j++]="Zero is required";$k[$j++]=false;return true}};$1.lintnonzero=function(){var _2g=$k[--$j];$k[$j++]=false;$F(_2g,function(){if($k[--$j]!=48){$j--;$k[$j++]=true}});if($nt($k[--$j])){$j--;$k[$j++]="bwipp.GS1zeroNotPermitted";$k[$j++]="Zero not permitted";$k[$j++]=false;return true}};$1.lintnozeroprefix=function(){var _2j=$k[--$j];if(_2j.length>1&&$g(_2j,0)==48){$j--;$k[$j++]="bwipp.GS1badZeroPrefix";$k[$j++]="Zero prefix is not permitted";$k[$j++]=false;return true}};$1.lintyymmd0=function(){var _2l=$k[--$j];var _2n=~~$z($G(_2l,2,2));$k[$j++]=_2l;if(_2n<1||_2n>12){$j--;$k[$j++]="bwipp.GS1badMonth";$k[$j++]="Invalid month";$k[$j++]=false;return true}var _2o=$k[--$j];var _2q=~~$z($G(_2o,0,2));var _2r=_2q-21;$k[$j++]=_2o;$k[$j++]=_2q;$k[$j++]=_2r;if(_2r>=51){$j--;var _2s=$k[--$j];$k[$j++]=$f(_2s+1900)}else{if($k[--$j]<=-50){var _2u=$k[--$j];$k[$j++]=$f(_2u+2100)}else{var _2v=$k[--$j];$k[$j++]=$f(_2v+2e3)}}var _2w=$k[--$j];$k[$j++]=_2w%400==0||_2w%4==0&&_2w%100!=0;$k[$j++]=Infinity;var _2x=$k[--$j];var _2z=$k[--$j]?29:28;$k[$j++]=_2x;$k[$j++]=31;$k[$j++]=_2z;$k[$j++]=31;$k[$j++]=30;$k[$j++]=31;$k[$j++]=30;$k[$j++]=31;$k[$j++]=31;$k[$j++]=30;$k[$j++]=31;$k[$j++]=30;$k[$j++]=31;var _30=$a();var _31=$k[--$j];if($g(_30,~~$z($G(_31,2,2))-1)<~~$z($G(_31,4,2))){$j--;$k[$j++]="bwipp.GS1badDay";$k[$j++]="Invalid day of month";$k[$j++]=false;return true}};$1.lintyymmdd=function(){var _35=$k[--$j];$k[$j++]=_35;if(_35.length!=6){$j--;$k[$j++]="bwipp.GS1badDateLength";$k[$j++]="Invalid length for date";$k[$j++]=false;return true}var _36=$k[--$j];$k[$j++]=_36;if(~~$z($G(_36,4,2))<1){$j--;$k[$j++]="bwipp.GS1badDay";$k[$j++]="Invalid day of month";$k[$j++]=false;return true}$1.lintyymmd0()};$1.lintyymmddhh=function(){var _38=$k[--$j];$k[$j++]=_38;if(_38.length!=8){$j--;$k[$j++]="bwipp.GS1badYYMMDDHHLength";$k[$j++]="Invalid length for date with hour";$k[$j++]=false;return true}var _39=$k[--$j];$k[$j++]=_39;if(~~$z($G(_39,6,2))>23){$j-=2;$k[$j++]="bwipp.GS1badHour";$k[$j++]="Invalid hour of day";$k[$j++]=false;return true}var _3C=$G($k[--$j],0,6);$k[$j++]=_3C;$1.lintyymmdd()};$1.linthhmm=function(){var _3D=$k[--$j];$k[$j++]=_3D;if(~~$z($G(_3D,0,2))>23){$j-=2;$k[$j++]="bwipp.GS1badHour";$k[$j++]="Invalid hour of day";$k[$j++]=false;return true}if(~~$z($G($k[--$j],2,2))>59){$j--;$k[$j++]="bwipp.GS1badMinute";$k[$j++]="Invalid minute in the hour";$k[$j++]=false;return true}};$1.lintmmoptss=function(){var _3H=$k[--$j];var _3I=_3H.length;$k[$j++]=_3H;if(_3I!=2&&_3I!=4){$j--;$k[$j++]="bwipp.GS1badTimeLength";$k[$j++]="Invalid length for optional minutes and seconds";$k[$j++]=false;return true}var _3J=$k[--$j];$k[$j++]=_3J;if(~~$z($G(_3J,0,2))>59){$j-=2;$k[$j++]="bwipp.GS1badMinute";$k[$j++]="Invalid minute in the hour";$k[$j++]=false;return true}var _3L=$k[--$j];$k[$j++]=_3L;if(_3L.length>=4){var _3M=$k[--$j];$k[$j++]=_3M;if(~~$z($G(_3M,2,2))>59){$j-=2;$k[$j++]="bwipp.GS1badSecond";$k[$j++]="Invalid second in the minute";$k[$j++]=false;return true}}$j--};$1.lintyesno=function(){var _3O=$k[--$j];if($ne(_3O,"0")&&$ne(_3O,"1")){$j--;$k[$j++]="bwipp.GS1badBoolean";$k[$j++]="Neither 0 nor 1 for yes or no";$k[$j++]=false;return true}};$1.lintwinding=function(){var _3P=$k[--$j];if($ne(_3P,"0")&&($ne(_3P,"1")&&$ne(_3P,"9"))){$j--;$k[$j++]="bwipp.GS1badWinding";$k[$j++]="Invalid winding direction";$k[$j++]=false;return true}};$1.lintpieceoftotal=function(){var _3Q=$k[--$j];$k[$j++]=_3Q;if(_3Q.length%2!=0){$j-=2;$k[$j++]="bwipp.GS1badPieceTotalLength";$k[$j++]="Invalid piece/total length";$k[$j++]=false;return true}var _3R=$k[--$j];var _3T=~~$z($G(_3R,0,~~(_3R.length/2)));$k[$j++]=_3R;$k[$j++]=_3T;if(_3T==0){$j-=3;$k[$j++]="bwipp.GS1badPieceNumber";$k[$j++]="Invalid piece number";$k[$j++]=false;return true}var _3U=$k[--$j];var _3V=$k[--$j];var _3W=~~(_3V.length/2);var _3Y=~~$z($G(_3V,_3W,_3W));$k[$j++]=_3U;$k[$j++]=_3Y;if(_3Y==0){$j-=3;$k[$j++]="bwipp.GS1badPieceTotal";$k[$j++]="Invalid total number";$k[$j++]=false;return true}var _3Z=$k[--$j];if($gt($k[--$j],_3Z)){$j--;$k[$j++]="bwipp.GS1pieceExceedsTotal";$k[$j++]="Piece number exceeds total";$k[$j++]=false;return true}};$1.lintpcenc=function(){for(;;){$x($k[--$j],"%");if($nt($k[--$j])){$j--;break}$j-=2;var _3d=$k[--$j];$k[$j++]=_3d;if(_3d.length<2){$j-=2;$k[$j++]="bwipp.GS1badPercentEscape";$k[$j++]="Invalid % escape";$k[$j++]=false;break}var _3e=$k[--$j];var _3f=$G(_3e,0,2);$k[$j++]=_3e;$k[$j++]=true;for(var _3g=0,_3h=_3f.length;_3g<_3h;_3g++){var _3j=$s(1);$p(_3j,0,$g(_3f,_3g));$x("0123456789ABCDEFabcdef",_3j);if($nt($k[--$j])){$j-=2;$k[$j++]=false;return true}$j-=3}if($nt($k[--$j])){$j-=2;$k[$j++]="bwipp.GS1badPercentChars";$k[$j++]="Invalid characters for percent encoding";$k[$j++]=false;break}}};$1.lintcouponcode=function(){var _3m=$k[--$j];$k[$j++]=_3m;$k[$j++]=true;$F(_3m,function(){var _3n=$k[--$j];if(_3n<48||_3n>57){$j--;$k[$j++]=false;return true}});if($nt($k[--$j])){$j-=2;$k[$j++]="bwipp.GS1couponNotNumeric";$k[$j++]="Coupon not numeric";$k[$j++]=false;return true}var _3p=$k[--$j];$k[$j++]=_3p;if(_3p.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShortGCPVLI";$k[$j++]="Coupon too short: Missing GCP VLI";$k[$j++]=false;return true}var _3q=$k[--$j];var _3s=~~$z($G(_3q,0,1));$k[$j++]=_3q;$k[$j++]=_3s;if(_3s>6){$j-=2;$k[$j++]="bwipp.GS1couponBadGCPVLI";$k[$j++]="Coupon GCP length indicator must be 0-6";$k[$j++]=false;return true}var _3t=$k[--$j];var _3u=$k[--$j];$k[$j++]=_3u;$k[$j++]=$f($f(_3t+6)+1);if($f($f(_3t+6)+1)>_3u.length){$j-=3;$k[$j++]="bwipp.GS1couponTooShortGCP";$k[$j++]="Coupon too short: GCP truncated";$k[$j++]=false;return true}var _3v=$k[--$j];var _3w=$k[--$j];var _3x=$G(_3w,_3v,$f(_3w.length-_3v));$k[$j++]=_3x;if(_3x.length<6){$j-=2;$k[$j++]="bwipp.GS1couponTooShortOfferCode";$k[$j++]="Coupon too short: Offer Code truncated";$k[$j++]=false;return true}var _3y=$k[--$j];var _3z=$G(_3y,6,_3y.length-6);$k[$j++]=_3z;if(_3z.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShortSaveValueVLI";$k[$j++]="Coupon too short: Missing Save Value VLI";$k[$j++]=false;return true}var _40=$k[--$j];var _42=~~$z($G(_40,0,1));$k[$j++]=_40;$k[$j++]=_42;if(_42<1||_42>5){$j-=2;$k[$j++]="bwipp.GS1couponBadSaveValueVLI";$k[$j++]="Coupon Save Value length indicator must be 1-5";$k[$j++]=false;return true}var _43=$k[--$j];var _44=$k[--$j];$k[$j++]=_44;$k[$j++]=$f(_43+1);if($f(_43+1)>_44.length){$j-=3;$k[$j++]="bwipp.GS1couponTooShortSaveValue";$k[$j++]="Coupon too short: Save Value truncated";$k[$j++]=false;return true}var _45=$k[--$j];var _46=$k[--$j];var _47=$G(_46,_45,$f(_46.length-_45));$k[$j++]=_47;if(_47.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShort1stPurchaseRequirementVLI";$k[$j++]="Coupon too short: Missing 1st Purchase Requirement VLI";$k[$j++]=false;return true}var _48=$k[--$j];var _4A=~~$z($G(_48,0,1));$k[$j++]=_48;$k[$j++]=_4A;if(_4A<1||_4A>5){$j-=2;$k[$j++]="bwipp.GS1couponBad1stPurchaseRequirementVLI";$k[$j++]="Coupon 1st Purchase Requirement length indicator must be 1-5";$k[$j++]=false;return true}var _4B=$k[--$j];var _4C=$k[--$j];$k[$j++]=_4C;$k[$j++]=$f(_4B+1);if($f(_4B+1)>_4C.length){$j-=3;$k[$j++]="bwipp.GS1couponTooShort1stPurchaseRequirement";$k[$j++]="Coupon too short: 1st Purchase Requirement truncated";$k[$j++]=false;return true}var _4D=$k[--$j];var _4E=$k[--$j];var _4F=$G(_4E,_4D,$f(_4E.length-_4D));$k[$j++]=_4F;if(_4F.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShort1stPurchaseRequirementCode";$k[$j++]="Coupon too short: Missing 1st Purchase Requirement Code";$k[$j++]=false;return true}var _4G=$k[--$j];var _4I=~~$z($G(_4G,0,1));$k[$j++]=_4G;if(_4I>4&&_4I!=9){$j-=2;$k[$j++]="bwipp.GS1couponBad1stPurchaseRequirementCode";$k[$j++]="Coupon 1st Purchase Requirement Code must be 0-4 or 9";$k[$j++]=false;return true}var _4J=$k[--$j];var _4K=$G(_4J,1,_4J.length-1);$k[$j++]=_4K;if(_4K.length<3){$j-=2;$k[$j++]="bwipp.GS1couponTooShort1stPurchaseFamilyCode";$k[$j++]="Coupon too short: 1st Purchase Family Code truncated";$k[$j++]=false;return true}var _4L=$k[--$j];var _4M=$G(_4L,3,_4L.length-3);$k[$j++]=_4M;if(_4M.length>=1){var _4N=$k[--$j];$k[$j++]=_4N;if(~~$z($G(_4N,0,1))==1){var _4P=$k[--$j];var _4Q=$G(_4P,1,_4P.length-1);$k[$j++]=_4Q;if(_4Q.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShortAdditionalPurchaseRulesCode";$k[$j++]="Coupon too short: Missing Additional Purchase Rules Code";$k[$j++]=false;return true}var _4R=$k[--$j];$k[$j++]=_4R;if(~~$z($G(_4R,0,1))>3){$j-=2;$k[$j++]="bwipp.GS1couponBadAdditionalPurchaseRulesCode";$k[$j++]="Coupon Additional Purchase Rules Code must be 0-3";$k[$j++]=false;return true}var _4T=$k[--$j];var _4U=$G(_4T,1,_4T.length-1);$k[$j++]=_4U;if(_4U.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShort2ndPurchaseRequirementVLI";$k[$j++]="Coupon too short: Missing 2nd Purchase Requirement VLI";$k[$j++]=false;return true}var _4V=$k[--$j];var _4X=~~$z($G(_4V,0,1));$k[$j++]=_4V;$k[$j++]=_4X;if(_4X<1||_4X>5){$j-=2;$k[$j++]="bwipp.GS1couponBad2ndPurchaseRequirementVLI";$k[$j++]="Coupon 2nd Purchase Requirement length indicator must be 1-5";$k[$j++]=false;return true}var _4Y=$k[--$j];var _4Z=$k[--$j];$k[$j++]=_4Z;$k[$j++]=$f(_4Y+1);if($f(_4Y+1)>_4Z.length){$j-=3;$k[$j++]="bwipp.GS1couponTooShort2ndPurchaseRequirement";$k[$j++]="Coupon too short: 2nd Purchase Requirement truncated";$k[$j++]=false;return true}var _4a=$k[--$j];var _4b=$k[--$j];var _4c=$G(_4b,_4a,$f(_4b.length-_4a));$k[$j++]=_4c;if(_4c.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShort2ndPurchaseRequirementCode";$k[$j++]="Coupon too short: Missing 2nd Purchase Requirement Code";$k[$j++]=false;return true}var _4d=$k[--$j];var _4f=~~$z($G(_4d,0,1));$k[$j++]=_4d;if(_4f>4&&_4f!=9){$j-=2;$k[$j++]="bwipp.GS1couponBad2ndPurchaseRequirementCode";$k[$j++]="Coupon 2nd Purchase Requirement Code must be 0-4 or 9";$k[$j++]=false;return true}var _4g=$k[--$j];var _4h=$G(_4g,1,_4g.length-1);$k[$j++]=_4h;if(_4h.length<3){$j-=2;$k[$j++]="bwipp.GS1couponTooShort2ndPurchaseFamilyCode";$k[$j++]="Coupon too short: 2nd Purchase Family Code truncated";$k[$j++]=false;return true}var _4i=$k[--$j];var _4j=$G(_4i,3,_4i.length-3);$k[$j++]=_4j;if(_4j.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShort2ndPurchaseGCPVLI";$k[$j++]="Coupon too short: Missing 2nd Purchase GCP VLI";$k[$j++]=false;return true}var _4k=$k[--$j];var _4m=~~$z($G(_4k,0,1));$k[$j++]=_4k;$k[$j++]=_4m;if(_4m>6&&_4m!=9){$j-=2;$k[$j++]="bwipp.GS1couponBad2ndPurchaseGCPVLI";$k[$j++]="Coupon 2nd Purchase GCP length indicator must be 0-6 or 9";$k[$j++]=false;return true}var _4n=$k[--$j];$k[$j++]=_4n;if(_4n!=9){var _4o=$k[--$j];$k[$j++]=$f(_4o+6)}else{$j--;$k[$j++]=0}var _4p=$k[--$j];var _4q=$k[--$j];$k[$j++]=_4q;$k[$j++]=$f(_4p+1);if($f(_4p+1)>_4q.length){$j-=3;$k[$j++]="bwipp.GS1couponTooShort2ndPurchaseGCP";$k[$j++]="Coupon too short: 2nd Purchase GCP truncated";$k[$j++]=false;return true}var _4r=$k[--$j];var _4s=$k[--$j];$k[$j++]=$G(_4s,_4r,$f(_4s.length-_4r))}}var _4u=$k[--$j];$k[$j++]=_4u;if(_4u.length>=1){var _4v=$k[--$j];$k[$j++]=_4v;if(~~$z($G(_4v,0,1))==2){var _4x=$k[--$j];var _4y=$G(_4x,1,_4x.length-1);$k[$j++]=_4y;if(_4y.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShort3rdPurchaseRequirementVLI";$k[$j++]="Coupon too short: Missing 3rd Purchase Requirement VLI";$k[$j++]=false;return true}var _4z=$k[--$j];var _51=~~$z($G(_4z,0,1));$k[$j++]=_4z;$k[$j++]=_51;if(_51<1||_51>5){$j-=2;$k[$j++]="bwipp.GS1couponBad3rdPurchaseRequirementVLI";$k[$j++]="Coupon 3rd Purchase Requirement length indicator must be 1-5";$k[$j++]=false;return true}var _52=$k[--$j];var _53=$k[--$j];$k[$j++]=_53;$k[$j++]=$f(_52+1);if($f(_52+1)>_53.length){$j-=3;$k[$j++]="bwipp.GS1couponTooShort3rdPurchaseRequirement";$k[$j++]="Coupon too short: 3rd Purchase Requirement truncated";$k[$j++]=false;return true}var _54=$k[--$j];var _55=$k[--$j];var _56=$G(_55,_54,$f(_55.length-_54));$k[$j++]=_56;if(_56.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShort3rdPurchaseRequirementCode";$k[$j++]="Coupon too short: Missing 3rd Purchase Requirement Code";$k[$j++]=false;return true}var _57=$k[--$j];var _59=~~$z($G(_57,0,1));$k[$j++]=_57;if(_59>4&&_59!=9){$j-=2;$k[$j++]="bwipp.GS1couponBad3rdPurchaseRequirementCode";$k[$j++]="Coupon 3rd Purchase Requirement Code must be 0-4 or 9";$k[$j++]=false;return true}var _5A=$k[--$j];var _5B=$G(_5A,1,_5A.length-1);$k[$j++]=_5B;if(_5B.length<3){$j-=2;$k[$j++]="bwipp.GS1couponTooShort3rdPurchaseFamilyCode";$k[$j++]="Coupon too short: 3rd Purchase Family Code truncated";$k[$j++]=false;return true}var _5C=$k[--$j];var _5D=$G(_5C,3,_5C.length-3);$k[$j++]=_5D;if(_5D.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShort3rdPurchaseGCPVLI";$k[$j++]="Coupon too short: Missing 3rd Purchase GCP VLI";$k[$j++]=false;return true}var _5E=$k[--$j];var _5G=~~$z($G(_5E,0,1));$k[$j++]=_5E;$k[$j++]=_5G;if(_5G>6&&_5G!=9){$j-=2;$k[$j++]="bwipp.GS1couponBad3rdPurchaseGCPVLI";$k[$j++]="Coupon 3rd Purchase GCP length indicator must be 0-6 or 9";$k[$j++]=false;return true}var _5H=$k[--$j];$k[$j++]=_5H;if(_5H!=9){var _5I=$k[--$j];$k[$j++]=$f(_5I+6)}else{$j--;$k[$j++]=0}var _5J=$k[--$j];var _5K=$k[--$j];$k[$j++]=_5K;$k[$j++]=$f(_5J+1);if($f(_5J+1)>_5K.length){$j-=3;$k[$j++]="bwipp.GS1couponTooShort3rdPurchaseGCP";$k[$j++]="Coupon too short: 3rd Purchase GCP truncated";$k[$j++]=false;return true}var _5L=$k[--$j];var _5M=$k[--$j];$k[$j++]=$G(_5M,_5L,$f(_5M.length-_5L))}}$1.couponexpire=-1;var _5O=$k[--$j];$k[$j++]=_5O;if(_5O.length>=1){var _5P=$k[--$j];$k[$j++]=_5P;if(~~$z($G(_5P,0,1))==3){var _5R=$k[--$j];var _5S=$G(_5R,1,_5R.length-1);$k[$j++]=_5S;if(_5S.length<6){$j-=2;$k[$j++]="bwipp.GS1couponTooShortExpirationDate";$k[$j++]="Coupon too short: Expiration date";$k[$j++]=false;return true}var _5T=$k[--$j];var _5V=~~$z($G(_5T,2,2));$k[$j++]=_5T;if(_5V<1||_5V>12){$j-=2;$k[$j++]="bwipp.GS1couponExpirationDateBadMonth";$k[$j++]="Invalid month in expiration date";$k[$j++]=false;return true}var _5W=$k[--$j];var _5Y=~~$z($G(_5W,0,2));var _5Z=_5Y-21;$k[$j++]=_5W;$k[$j++]=_5Y;$k[$j++]=_5Z;if(_5Z>=51){$j--;var _5a=$k[--$j];$k[$j++]=$f(_5a+1900)}else{if($k[--$j]<=-50){var _5c=$k[--$j];$k[$j++]=$f(_5c+2100)}else{var _5d=$k[--$j];$k[$j++]=$f(_5d+2e3)}}var _5e=$k[--$j];$k[$j++]=_5e%400==0||_5e%4==0&&_5e%100!=0;$k[$j++]=Infinity;var _5f=$k[--$j];var _5h=$k[--$j]?29:28;$k[$j++]=_5f;$k[$j++]=31;$k[$j++]=_5h;$k[$j++]=31;$k[$j++]=30;$k[$j++]=31;$k[$j++]=30;$k[$j++]=31;$k[$j++]=31;$k[$j++]=30;$k[$j++]=31;$k[$j++]=30;$k[$j++]=31;var _5i=$a();var _5j=$k[--$j];var _5n=~~$z($G(_5j,4,2));$k[$j++]=_5j;if($g(_5i,~~$z($G(_5j,2,2))-1)<_5n||_5n<1){$j-=2;$k[$j++]="bwipp.GS1couponExpirationDateBadDay";$k[$j++]="Invalid day of month in expiration date";$k[$j++]=false;return true}var _5o=$k[--$j];$1.couponexpire=~~$z($G(_5o,0,6));$k[$j++]=$G(_5o,6,_5o.length-6)}}var _5r=$k[--$j];$k[$j++]=_5r;if(_5r.length>=1){var _5s=$k[--$j];$k[$j++]=_5s;if(~~$z($G(_5s,0,1))==4){var _5u=$k[--$j];var _5v=$G(_5u,1,_5u.length-1);$k[$j++]=_5v;if(_5v.length<6){$j-=2;$k[$j++]="bwipp.GS1couponTooShortStartDate";$k[$j++]="Coupon too short: Start date";$k[$j++]=false;return true}var _5w=$k[--$j];var _5y=~~$z($G(_5w,2,2));$k[$j++]=_5w;if(_5y<1||_5y>12){$j-=2;$k[$j++]="bwipp.GS1couponStartDateBadMonth";$k[$j++]="Invalid month in start date";$k[$j++]=false;return true}var _5z=$k[--$j];var _61=~~$z($G(_5z,0,2));var _62=_61-21;$k[$j++]=_5z;$k[$j++]=_61;$k[$j++]=_62;if(_62>=51){$j--;var _63=$k[--$j];$k[$j++]=$f(_63+1900)}else{if($k[--$j]<=-50){var _65=$k[--$j];$k[$j++]=$f(_65+2100)}else{var _66=$k[--$j];$k[$j++]=$f(_66+2e3)}}var _67=$k[--$j];$k[$j++]=_67%400==0||_67%4==0&&_67%100!=0;$k[$j++]=Infinity;var _68=$k[--$j];var _6A=$k[--$j]?29:28;$k[$j++]=_68;$k[$j++]=31;$k[$j++]=_6A;$k[$j++]=31;$k[$j++]=30;$k[$j++]=31;$k[$j++]=30;$k[$j++]=31;$k[$j++]=31;$k[$j++]=30;$k[$j++]=31;$k[$j++]=30;$k[$j++]=31;var _6B=$a();var _6C=$k[--$j];var _6G=~~$z($G(_6C,4,2));$k[$j++]=_6C;if($g(_6B,~~$z($G(_6C,2,2))-1)<_6G||_6G<1){$j-=2;$k[$j++]="bwipp.GS1couponStartDateBadDay";$k[$j++]="Invalid day of month in start date";$k[$j++]=false;return true}var _6H=$k[--$j];$1.couponstart=~~$z($G(_6H,0,6));$k[$j++]=_6H;if($1.couponexpire!=-1&&$1.couponexpire<$1.couponstart){$j-=2;$k[$j++]="bwipp.GS1couponExpireDateBeforeStartDate";$k[$j++]="Coupon expires before it starts";$k[$j++]=false;return true}var _6M=$k[--$j];$k[$j++]=$G(_6M,6,_6M.length-6)}}var _6O=$k[--$j];$k[$j++]=_6O;if(_6O.length>=1){var _6P=$k[--$j];$k[$j++]=_6P;if(~~$z($G(_6P,0,1))==5){var _6R=$k[--$j];var _6S=$G(_6R,1,_6R.length-1);$k[$j++]=_6S;if(_6S.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShortSerialNumberVLI";$k[$j++]="Coupon too short: Missing Serial Number VLI";$k[$j++]=false;return true}var _6T=$k[--$j];var _6U=$G(_6T,0,1);$k[$j++]=_6T;$k[$j++]=~~$z(_6U)+6+1;if(~~$z(_6U)+6+1>_6T.length){$j-=3;$k[$j++]="bwipp.GS1couponTooShortSerialNumber";$k[$j++]="Coupon too short: Serial Number truncated";$k[$j++]=false;return true}var _6V=$k[--$j];var _6W=$k[--$j];$k[$j++]=$G(_6W,_6V,$f(_6W.length-_6V))}}var _6Y=$k[--$j];$k[$j++]=_6Y;if(_6Y.length>=1){var _6Z=$k[--$j];$k[$j++]=_6Z;if(~~$z($G(_6Z,0,1))==6){var _6b=$k[--$j];var _6c=$G(_6b,1,_6b.length-1);$k[$j++]=_6c;if(_6c.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShortRetailerGCPGLNVLI";$k[$j++]="Coupon too short: Missing Retailer GCP/GLN VLI";$k[$j++]=false;return true}var _6d=$k[--$j];var _6f=~~$z($G(_6d,0,1));$k[$j++]=_6d;$k[$j++]=_6f;if(_6f<1||_6f>7){$j-=2;$k[$j++]="bwipp.GS1couponBadRetailerGCPGLNVLI";$k[$j++]="Coupon Retailer GCP/GLN length indicator must be 1-7";$k[$j++]=false;return true}var _6g=$k[--$j];var _6h=$k[--$j];$k[$j++]=_6h;$k[$j++]=$f($f(_6g+6)+1);if($f($f(_6g+6)+1)>_6h.length){$j-=3;$k[$j++]="bwipp.GS1couponTooShortRetailerGCPGLN";$k[$j++]="Coupon too short: Retailer GCP/GLN truncated";$k[$j++]=false;return true}var _6i=$k[--$j];var _6j=$k[--$j];$k[$j++]=$G(_6j,_6i,$f(_6j.length-_6i))}}var _6l=$k[--$j];$k[$j++]=_6l;if(_6l.length>=1){var _6m=$k[--$j];$k[$j++]=_6m;if(~~$z($G(_6m,0,1))==9){var _6o=$k[--$j];var _6p=$G(_6o,1,_6o.length-1);$k[$j++]=_6p;if(_6p.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShortSaveValueCode";$k[$j++]="Coupon too short: Missing Save Value Code";$k[$j++]=false;return true}var _6q=$k[--$j];var _6s=~~$z($G(_6q,0,1));$k[$j++]=_6q;if(_6s>6||(_6s==3||_6s==4)){$j-=2;$k[$j++]="bwipp.GS1couponBadSaveValueCode";$k[$j++]="Coupon Save Value Code must be 0,1,2,5 or 6";$k[$j++]=false;return true}var _6t=$k[--$j];var _6u=$G(_6t,1,_6t.length-1);$k[$j++]=_6u;if(_6u.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShortSaveValueAppliesToItem";$k[$j++]="Coupon too short: Missing Save Value Applies to Item";$k[$j++]=false;return true}var _6v=$k[--$j];$k[$j++]=_6v;if(~~$z($G(_6v,0,1))>2){$j-=2;$k[$j++]="bwipp.GS1couponBadSaveValueAppliesToItem";$k[$j++]="Coupon Save Value Applies to Item must be 0-2";$k[$j++]=false;return true}var _6x=$k[--$j];var _6y=$G(_6x,1,_6x.length-1);$k[$j++]=_6y;if(_6y.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShortStoreCouponFlag";$k[$j++]="Coupon too short: Missing Store Coupon Flag";$k[$j++]=false;return true}var _6z=$k[--$j];var _70=$G(_6z,1,_6z.length-1);$k[$j++]=_70;if(_70.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShortDontMultiplyFlag";$k[$j++]="Coupon too short: Missing Don't Multiply Flag";$k[$j++]=false;return true}var _71=$k[--$j];$k[$j++]=_71;if(~~$z($G(_71,0,1))>1){$j-=2;$k[$j++]="bwipp.GS1couponBadDontMultiplyFlag";$k[$j++]="Don't Multiply Flag must be 0 or 1";$k[$j++]=false;return true}var _73=$k[--$j];$k[$j++]=$G(_73,1,_73.length-1)}}var _75=$k[--$j];$k[$j++]=_75;if(_75.length!=0){$j-=2;$k[$j++]="bwipp.GS1couponUnrecognisedOptionalField";$k[$j++]="Coupon fields must be 1,2,3,4,5,6 or 9, increasing order";$k[$j++]=false;return true}$j--};$1.lintcouponposoffer=function(){var _76=$k[--$j];$k[$j++]=_76;$k[$j++]=true;$F(_76,function(){var _77=$k[--$j];if(_77<48||_77>57){$j--;$k[$j++]=false;return true}});if($nt($k[--$j])){$j-=2;$k[$j++]="bwipp.GS1couponNotNumeric";$k[$j++]="Coupon not numeric";$k[$j++]=false;return true}var _79=$k[--$j];$k[$j++]=_79;if(_79.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShortFormatCode";$k[$j++]="Coupon too short: Missing Format Code";$k[$j++]=false;return true}var _7A=$k[--$j];var _7B=$G(_7A,0,1);$k[$j++]=_7A;if($ne(_7B,"0")&&$ne(_7B,"1")){$j-=2;$k[$j++]="bwipp.GS1couponBadFormatCode";$k[$j++]="Coupon format must be 0 or 1";$k[$j++]=false;return true}var _7C=$k[--$j];var _7D=$G(_7C,1,_7C.length-1);$k[$j++]=_7D;if(_7D.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShortFunderVLI";$k[$j++]="Coupon too short: Missing Funder VLI";$k[$j++]=false;return true}var _7E=$k[--$j];var _7G=~~$z($G(_7E,0,1));$k[$j++]=_7E;$k[$j++]=_7G;if(_7G>6){$j-=3;$k[$j++]="bwipp.GS1couponBadFunderVLI";$k[$j++]="Coupon Funder length indicator must be 0-6";$k[$j++]=false;return true}var _7H=$k[--$j];var _7I=$k[--$j];$k[$j++]=_7I;$k[$j++]=$f($f(_7H+6)+1);if($f($f(_7H+6)+1)>_7I.length){$j-=3;$k[$j++]="bwipp.GS1couponTooShortFunder";$k[$j++]="Coupon too short: Truncated Funder ID";$k[$j++]=false;return true}var _7J=$k[--$j];var _7K=$k[--$j];var _7L=$G(_7K,_7J,$f(_7K.length-_7J));$k[$j++]=_7L;if(_7L.length<6){$j-=2;$k[$j++]="bwipp.GS1couponTooShortOfferCode";$k[$j++]="Coupon too short: Truncated Offer Code";$k[$j++]=false;return true}var _7M=$k[--$j];var _7N=$G(_7M,6,_7M.length-6);$k[$j++]=_7N;if(_7N.length<1){$j-=2;$k[$j++]="bwipp.GS1couponTooShortSnVLI";$k[$j++]="Coupon too short: Missing SN VLI";$k[$j++]=false;return true}var _7O=$k[--$j];var _7P=$G(_7O,0,1);$k[$j++]=_7O;$k[$j++]=~~$z(_7P)+6+1;if(~~$z(_7P)+6+1>_7O.length){$j-=3;$k[$j++]="bwipp.GS1couponTooShortSn";$k[$j++]="Coupon too short: Truncated SN";$k[$j++]=false;return true}var _7Q=$k[--$j];var _7R=$k[--$j];var _7S=$G(_7R,_7Q,$f(_7R.length-_7Q));$k[$j++]=_7S;if(_7S.length!=0){$j-=2;$k[$j++]="bwipp.GS1couponTooLong";$k[$j++]="Coupon too long";$k[$j++]=false;return true}$j--};var _7U=new Map([["cset","N"],["min",18],["max",18],["check",$a(["lintcsum","lintkey"])]]);var _7X=new Map([["cset","N"],["min",14],["max",14],["check",$a(["lintcsum","lintkey"])]]);var _7Y=$a([_7X]);var _7a=new Map([["cset","X"],["min",1],["max",20],["check",$a([])]]);var _7d=new Map([["cset","N"],["min",6],["max",6],["check",$a(["lintyymmd0"])]]);var _7e=$a([_7d]);var _7g=new Map([["cset","N"],["min",2],["max",2],["check",$a([])]]);var _7j=new Map([["cset","X"],["min",1],["max",20],["check",$a([])]]);var _7k=$a([_7j]);var _7m=new Map([["cset","X"],["min",1],["max",28],["check",$a([])]]);var _7p=new Map([["cset","X"],["min",1],["max",30],["check",$a([])]]);var _7q=$a([_7p]);var _7s=new Map([["cset","N"],["min",1],["max",6],["check",$a([])]]);var _7v=new Map([["cset","X"],["min",1],["max",20],["check",$a([])]]);var _7y=new Map([["cset","X"],["min",1],["max",30],["check",$a([])]]);var _7z=$a([_7y]);var _81=new Map([["cset","N"],["min",13],["max",13],["check",$a(["lintcsum","lintkey"])]]);var _83=new Map([["cset","X"],["min",0],["max",17],["check",$a([])]]);var _86=new Map([["cset","X"],["min",1],["max",20],["check",$a([])]]);var _89=new Map([["cset","N"],["min",13],["max",13],["check",$a(["lintcsum","lintkey"])]]);var _8B=new Map([["cset","N"],["min",0],["max",12],["check",$a([])]]);var _8E=new Map([["cset","N"],["min",1],["max",8],["check",$a([])]]);var _8H=new Map([["cset","N"],["min",6],["max",6],["check",$a([])]]);var _8I=$a([_8H]);var _8K=new Map([["cset","N"],["min",1],["max",8],["check",$a([])]]);var _8N=new Map([["cset","N"],["min",1],["max",15],["check",$a([])]]);var _8O=$a([_8N]);var _8Q=new Map([["cset","N"],["min",3],["max",3],["check",$a(["lintiso4217"])]]);var _8S=new Map([["cset","N"],["min",1],["max",15],["check",$a([])]]);var _8T=$a([_8Q,_8S]);var _8V=new Map([["cset","N"],["min",1],["max",15],["check",$a([])]]);var _8W=$a([_8V]);var _8Y=new Map([["cset","N"],["min",3],["max",3],["check",$a(["lintiso4217"])]]);var _8a=new Map([["cset","N"],["min",1],["max",15],["check",$a([])]]);var _8b=$a([_8Y,_8a]);var _8d=new Map([["cset","N"],["min",4],["max",4],["check",$a([])]]);var _8e=$a([_8d]);var _8g=new Map([["cset","N"],["min",6],["max",6],["check",$a([])]]);var _8h=$a([_8g]);var _8j=new Map([["cset","X"],["min",1],["max",30],["check",$a([])]]);var _8m=new Map([["cset","X"],["min",1],["max",30],["check",$a(["lintkey"])]]);var _8p=new Map([["cset","N"],["min",17],["max",17],["check",$a(["lintcsum","lintkey"])]]);var _8s=new Map([["cset","X"],["min",1],["max",30],["check",$a([])]]);var _8v=new Map([["cset","N"],["min",13],["max",13],["check",$a(["lintcsum","lintkey"])]]);var _8w=$a([_8v]);var _8y=new Map([["cset","X"],["min",1],["max",20],["check",$a([])]]);var _91=new Map([["cset","N"],["min",3],["max",3],["check",$a(["lintiso3166"])]]);var _93=new Map([["cset","X"],["min",1],["max",9],["check",$a([])]]);var _96=new Map([["cset","N"],["min",3],["max",3],["check",$a(["lintiso3166"])]]);var _99=new Map([["cset","N"],["min",1],["max",15],["check",$a(["lintiso3166list"])]]);var _9C=new Map([["cset","N"],["min",3],["max",3],["check",$a(["lintiso3166"])]]);var _9F=new Map([["cset","N"],["min",1],["max",15],["check",$a(["lintiso3166list"])]]);var _9I=new Map([["cset","N"],["min",3],["max",3],["check",$a(["lintiso3166"])]]);var _9L=new Map([["cset","X"],["min",1],["max",3],["check",$a([])]]);var _9O=new Map([["cset","X"],["min",1],["max",35],["check",$a(["lintpcenc"])]]);var _9P=$a([_9O]);var _9R=new Map([["cset","X"],["min",1],["max",70],["check",$a(["lintpcenc"])]]);var _9S=$a([_9R]);var _9U=new Map([["cset","X"],["min",2],["max",2],["check",$a(["lintiso3166alpha2"])]]);var _9X=new Map([["cset","X"],["min",1],["max",30],["check",$a([])]]);var _9a=new Map([["cset","X"],["min",1],["max",35],["check",$a(["lintpcenc"])]]);var _9b=$a([_9a]);var _9d=new Map([["cset","X"],["min",1],["max",70],["check",$a(["lintpcenc"])]]);var _9e=$a([_9d]);var _9g=new Map([["cset","X"],["min",2],["max",2],["check",$a(["lintiso3166alpha2"])]]);var _9j=new Map([["cset","X"],["min",1],["max",20],["check",$a([])]]);var _9m=new Map([["cset","X"],["min",1],["max",30],["check",$a([])]]);var _9p=new Map([["cset","X"],["min",1],["max",35],["check",$a(["lintpcenc"])]]);var _9s=new Map([["cset","N"],["min",1],["max",1],["check",$a(["lintyesno"])]]);var _9t=$a([_9s]);var _9v=new Map([["cset","N"],["min",6],["max",6],["check",$a(["lintyymmd0"])]]);var _9x=new Map([["cset","N"],["min",4],["max",4],["check",$a(["linthhmm"])]]);var _9y=$a([_9v,_9x]);var _A0=new Map([["cset","N"],["min",6],["max",6],["check",$a(["lintyymmdd"])]]);var _A3=new Map([["cset","N"],["min",13],["max",13],["check",$a([])]]);var _A6=new Map([["cset","X"],["min",1],["max",30],["check",$a([])]]);var _A9=new Map([["cset","N"],["min",6],["max",6],["check",$a(["lintyymmdd"])]]);var _AB=new Map([["cset","N"],["min",4],["max",4],["check",$a(["linthhmm"])]]);var _AE=new Map([["cset","N"],["min",1],["max",4],["check",$a([])]]);var _AH=new Map([["cset","X"],["min",1],["max",12],["check",$a([])]]);var _AK=new Map([["cset","N"],["min",6],["max",6],["check",$a(["lintyymmdd"])]]);var _AN=new Map([["cset","N"],["min",6],["max",6],["check",$a(["lintyymmdd"])]]);var _AP=new Map([["cset","N"],["min",0],["max",6],["check",$a(["lintyymmdd"])]]);var _AS=new Map([["cset","X"],["min",1],["max",3],["check",$a([])]]);var _AV=new Map([["cset","X"],["min",1],["max",10],["check",$a([])]]);var _AY=new Map([["cset","X"],["min",1],["max",2],["check",$a([])]]);var _Ab=new Map([["cset","X"],["min",1],["max",20],["check",$a([])]]);var _Ac=$a([_Ab]);var _Ae=new Map([["cset","X"],["min",1],["max",30],["check",$a(["lintkey"])]]);var _Ah=new Map([["cset","N"],["min",3],["max",3],["check",$a(["lintiso3166999"])]]);var _Aj=new Map([["cset","X"],["min",1],["max",27],["check",$a([])]]);var _Ak=$a([_Ah,_Aj]);var _Am=new Map([["cset","N"],["min",1],["max",1],["check",$a([])]]);var _Ao=new Map([["cset","X"],["min",1],["max",1],["check",$a([])]]);var _Aq=new Map([["cset","X"],["min",1],["max",1],["check",$a([])]]);var _As=new Map([["cset","X"],["min",1],["max",1],["check",$a(["lintimporteridx"])]]);var _Av=new Map([["cset","X"],["min",1],["max",20],["check",$a([])]]);var _Aw=$a([_Av]);var _Ay=new Map([["cset","X"],["min",2],["max",2],["check",$a([])]]);var _B0=new Map([["cset","X"],["min",1],["max",28],["check",$a([])]]);var _B1=$a([_Ay,_B0]);var _B3=new Map([["cset","X"],["min",1],["max",20],["check",$a([])]]);var _B6=new Map([["cset","N"],["min",4],["max",4],["check",$a(["lintnonzero"])]]);var _B8=new Map([["cset","N"],["min",5],["max",5],["check",$a(["lintnonzero"])]]);var _BA=new Map([["cset","N"],["min",3],["max",3],["check",$a(["lintnonzero"])]]);var _BC=new Map([["cset","N"],["min",1],["max",1],["check",$a(["lintwinding"])]]);var _BE=new Map([["cset","N"],["min",1],["max",1],["check",$a([])]]);var _BH=new Map([["cset","X"],["min",1],["max",20],["check",$a([])]]);var _BK=new Map([["cset","N"],["min",1],["max",1],["check",$a(["lintzero"])]]);var _BM=new Map([["cset","N"],["min",13],["max",13],["check",$a(["lintcsum","lintkey"])]]);var _BO=new Map([["cset","X"],["min",0],["max",16],["check",$a([])]]);var _BR=new Map([["cset","X"],["min",1],["max",30],["check",$a(["lintkey"])]]);var _BU=new Map([["cset","N"],["min",6],["max",6],["check",$a([])]]);var _BX=new Map([["cset","N"],["min",14],["max",14],["check",$a(["lintcsum"])]]);var _BZ=new Map([["cset","N"],["min",4],["max",4],["check",$a(["lintpieceoftotal"])]]);var _Bc=new Map([["cset","X"],["min",1],["max",34],["check",$a(["lintiban"])]]);var _Bf=new Map([["cset","N"],["min",8],["max",8],["check",$a(["lintyymmddhh"])]]);var _Bh=new Map([["cset","N"],["min",0],["max",4],["check",$a(["lintmmoptss"])]]);var _Bk=new Map([["cset","X"],["min",1],["max",50],["check",$a([])]]);var _Bn=new Map([["cset","C"],["min",1],["max",30],["check",$a(["lintkey"])]]);var _Bq=new Map([["cset","N"],["min",1],["max",12],["check",$a(["lintnozeroprefix"])]]);var _Bt=new Map([["cset","X"],["min",1],["max",20],["check",$a([])]]);var _Bw=new Map([["cset","X"],["min",1],["max",25],["check",$a(["lintcsumalpha","lintkey"])]]);var _Bz=new Map([["cset","N"],["min",18],["max",18],["check",$a(["lintcsum"])]]);var _C0=$a([_Bz]);var _C2=new Map([["cset","N"],["min",1],["max",10],["check",$a([])]]);var _C5=new Map([["cset","X"],["min",1],["max",25],["check",$a([])]]);var _C8=new Map([["cset","N"],["min",14],["max",14],["check",$a(["lintcsum"])]]);var _CA=new Map([["cset","N"],["min",4],["max",4],["check",$a(["lintpieceoftotal"])]]);var _CD=new Map([["cset","X"],["min",1],["max",70],["check",$a(["lintcouponcode"])]]);var _CG=new Map([["cset","N"],["min",4],["max",4],["check",$a([])]]);var _CJ=new Map([["cset","X"],["min",1],["max",70],["check",$a(["lintcouponposoffer"])]]);var _CM=new Map([["cset","X"],["min",1],["max",70],["check",$a([])]]);var _CP=new Map([["cset","X"],["min",1],["max",30],["check",$a([])]]);var _CS=new Map([["cset","X"],["min",1],["max",90],["check",$a([])]]);var _CT=$a([_CS]);var _CU=new Map([["00",$a([_7U])],["01",_7Y],["02",_7Y],["10",$a([_7a])],["11",_7e],["12",_7e],["13",_7e],["14",_7e],["15",_7e],["16",_7e],["17",_7e],["20",$a([_7g])],["21",_7k],["22",_7k],["235",$a([_7m])],["240",_7q],["241",_7q],["242",$a([_7s])],["243",$a([_7v])],["250",_7z],["251",_7z],["253",$a([_81,_83])],["254",$a([_86])],["255",$a([_89,_8B])],["30",$a([_8E])],["3100",_8I],["3101",_8I],["3102",_8I],["3103",_8I],["3104",_8I],["3105",_8I],["3110",_8I],["3111",_8I],["3112",_8I],["3113",_8I],["3114",_8I],["3115",_8I],["3120",_8I],["3121",_8I],["3122",_8I],["3123",_8I],["3124",_8I],["3125",_8I],["3130",_8I],["3131",_8I],["3132",_8I],["3133",_8I],["3134",_8I],["3135",_8I],["3140",_8I],["3141",_8I],["3142",_8I],["3143",_8I],["3144",_8I],["3145",_8I],["3150",_8I],["3151",_8I],["3152",_8I],["3153",_8I],["3154",_8I],["3155",_8I],["3160",_8I],["3161",_8I],["3162",_8I],["3163",_8I],["3164",_8I],["3165",_8I],["3200",_8I],["3201",_8I],["3202",_8I],["3203",_8I],["3204",_8I],["3205",_8I],["3210",_8I],["3211",_8I],["3212",_8I],["3213",_8I],["3214",_8I],["3215",_8I],["3220",_8I],["3221",_8I],["3222",_8I],["3223",_8I],["3224",_8I],["3225",_8I],["3230",_8I],["3231",_8I],["3232",_8I],["3233",_8I],["3234",_8I],["3235",_8I],["3240",_8I],["3241",_8I],["3242",_8I],["3243",_8I],["3244",_8I],["3245",_8I],["3250",_8I],["3251",_8I],["3252",_8I],["3253",_8I],["3254",_8I],["3255",_8I],["3260",_8I],["3261",_8I],["3262",_8I],["3263",_8I],["3264",_8I],["3265",_8I],["3270",_8I],["3271",_8I],["3272",_8I],["3273",_8I],["3274",_8I],["3275",_8I],["3280",_8I],["3281",_8I],["3282",_8I],["3283",_8I],["3284",_8I],["3285",_8I],["3290",_8I],["3291",_8I],["3292",_8I],["3293",_8I],["3294",_8I],["3295",_8I],["3300",_8I],["3301",_8I],["3302",_8I],["3303",_8I],["3304",_8I],["3305",_8I],["3310",_8I],["3311",_8I],["3312",_8I],["3313",_8I],["3314",_8I],["3315",_8I],["3320",_8I],["3321",_8I],["3322",_8I],["3323",_8I],["3324",_8I],["3325",_8I],["3330",_8I],["3331",_8I],["3332",_8I],["3333",_8I],["3334",_8I],["3335",_8I],["3340",_8I],["3341",_8I],["3342",_8I],["3343",_8I],["3344",_8I],["3345",_8I],["3350",_8I],["3351",_8I],["3352",_8I],["3353",_8I],["3354",_8I],["3355",_8I],["3360",_8I],["3361",_8I],["3362",_8I],["3363",_8I],["3364",_8I],["3365",_8I],["3370",_8I],["3371",_8I],["3372",_8I],["3373",_8I],["3374",_8I],["3375",_8I],["3400",_8I],["3401",_8I],["3402",_8I],["3403",_8I],["3404",_8I],["3405",_8I],["3410",_8I],["3411",_8I],["3412",_8I],["3413",_8I],["3414",_8I],["3415",_8I],["3420",_8I],["3421",_8I],["3422",_8I],["3423",_8I],["3424",_8I],["3425",_8I],["3430",_8I],["3431",_8I],["3432",_8I],["3433",_8I],["3434",_8I],["3435",_8I],["3440",_8I],["3441",_8I],["3442",_8I],["3443",_8I],["3444",_8I],["3445",_8I],["3450",_8I],["3451",_8I],["3452",_8I],["3453",_8I],["3454",_8I],["3455",_8I],["3460",_8I],["3461",_8I],["3462",_8I],["3463",_8I],["3464",_8I],["3465",_8I],["3470",_8I],["3471",_8I],["3472",_8I],["3473",_8I],["3474",_8I],["3475",_8I],["3480",_8I],["3481",_8I],["3482",_8I],["3483",_8I],["3484",_8I],["3485",_8I],["3490",_8I],["3491",_8I],["3492",_8I],["3493",_8I],["3494",_8I],["3495",_8I],["3500",_8I],["3501",_8I],["3502",_8I],["3503",_8I],["3504",_8I],["3505",_8I],["3510",_8I],["3511",_8I],["3512",_8I],["3513",_8I],["3514",_8I],["3515",_8I],["3520",_8I],["3521",_8I],["3522",_8I],["3523",_8I],["3524",_8I],["3525",_8I],["3530",_8I],["3531",_8I],["3532",_8I],["3533",_8I],["3534",_8I],["3535",_8I],["3540",_8I],["3541",_8I],["3542",_8I],["3543",_8I],["3544",_8I],["3545",_8I],["3550",_8I],["3551",_8I],["3552",_8I],["3553",_8I],["3554",_8I],["3555",_8I],["3560",_8I],["3561",_8I],["3562",_8I],["3563",_8I],["3564",_8I],["3565",_8I],["3570",_8I],["3571",_8I],["3572",_8I],["3573",_8I],["3574",_8I],["3575",_8I],["3600",_8I],["3601",_8I],["3602",_8I],["3603",_8I],["3604",_8I],["3605",_8I],["3610",_8I],["3611",_8I],["3612",_8I],["3613",_8I],["3614",_8I],["3615",_8I],["3620",_8I],["3621",_8I],["3622",_8I],["3623",_8I],["3624",_8I],["3625",_8I],["3630",_8I],["3631",_8I],["3632",_8I],["3633",_8I],["3634",_8I],["3635",_8I],["3640",_8I],["3641",_8I],["3642",_8I],["3643",_8I],["3644",_8I],["3645",_8I],["3650",_8I],["3651",_8I],["3652",_8I],["3653",_8I],["3654",_8I],["3655",_8I],["3660",_8I],["3661",_8I],["3662",_8I],["3663",_8I],["3664",_8I],["3665",_8I],["3670",_8I],["3671",_8I],["3672",_8I],["3673",_8I],["3674",_8I],["3675",_8I],["3680",_8I],["3681",_8I],["3682",_8I],["3683",_8I],["3684",_8I],["3685",_8I],["3690",_8I],["3691",_8I],["3692",_8I],["3693",_8I],["3694",_8I],["3695",_8I],["37",$a([_8K])],["3900",_8O],["3901",_8O],["3902",_8O],["3903",_8O],["3904",_8O],["3905",_8O],["3906",_8O],["3907",_8O],["3908",_8O],["3909",_8O],["3910",_8T],["3911",_8T],["3912",_8T],["3913",_8T],["3914",_8T],["3915",_8T],["3916",_8T],["3917",_8T],["3918",_8T],["3919",_8T],["3920",_8W],["3921",_8W],["3922",_8W],["3923",_8W],["3924",_8W],["3925",_8W],["3926",_8W],["3927",_8W],["3928",_8W],["3929",_8W],["3930",_8b],["3931",_8b],["3932",_8b],["3933",_8b],["3934",_8b],["3935",_8b],["3936",_8b],["3937",_8b],["3938",_8b],["3939",_8b],["3940",_8e],["3941",_8e],["3942",_8e],["3943",_8e],["3950",_8h],["3951",_8h],["3952",_8h],["3953",_8h],["3954",_8h],["3955",_8h],["400",$a([_8j])],["401",$a([_8m])],["402",$a([_8p])],["403",$a([_8s])],["410",_8w],["411",_8w],["412",_8w],["413",_8w],["414",_8w],["415",_8w],["416",_8w],["417",_8w],["420",$a([_8y])],["421",$a([_91,_93])],["422",$a([_96])],["423",$a([_99])],["424",$a([_9C])],["425",$a([_9F])],["426",$a([_9I])],["427",$a([_9L])],["4300",_9P],["4301",_9P],["4302",_9S],["4303",_9S],["4304",_9S],["4305",_9S],["4306",_9S],["4307",$a([_9U])],["4308",$a([_9X])],["4310",_9b],["4311",_9b],["4312",_9e],["4313",_9e],["4314",_9e],["4315",_9e],["4316",_9e],["4317",$a([_9g])],["4318",$a([_9j])],["4319",$a([_9m])],["4320",$a([_9p])],["4321",_9t],["4322",_9t],["4323",_9t],["4324",_9y],["4325",_9y],["4326",$a([_A0])],["7001",$a([_A3])],["7002",$a([_A6])],["7003",$a([_A9,_AB])],["7004",$a([_AE])],["7005",$a([_AH])],["7006",$a([_AK])],["7007",$a([_AN,_AP])],["7008",$a([_AS])],["7009",$a([_AV])],["7010",$a([_AY])],["7020",_Ac],["7021",_Ac],["7022",_Ac],["7023",$a([_Ae])],["7030",_Ak],["7031",_Ak],["7032",_Ak],["7033",_Ak],["7034",_Ak],["7035",_Ak],["7036",_Ak],["7037",_Ak],["7038",_Ak],["7039",_Ak],["7040",$a([_Am,_Ao,_Aq,_As])],["710",_Aw],["711",_Aw],["712",_Aw],["713",_Aw],["714",_Aw],["7230",_B1],["7231",_B1],["7232",_B1],["7233",_B1],["7234",_B1],["7235",_B1],["7236",_B1],["7237",_B1],["7238",_B1],["7239",_B1],["7240",$a([_B3])],["8001",$a([_B6,_B8,_BA,_BC,_BE])],["8002",$a([_BH])],["8003",$a([_BK,_BM,_BO])],["8004",$a([_BR])],["8005",$a([_BU])],["8006",$a([_BX,_BZ])],["8007",$a([_Bc])],["8008",$a([_Bf,_Bh])],["8009",$a([_Bk])],["8010",$a([_Bn])],["8011",$a([_Bq])],["8012",$a([_Bt])],["8013",$a([_Bw])],["8017",_C0],["8018",_C0],["8019",$a([_C2])],["8020",$a([_C5])],["8026",$a([_C8,_CA])],["8110",$a([_CD])],["8111",$a([_CG])],["8112",$a([_CJ])],["8200",$a([_CM])],["90",$a([_CP])],["91",_CT],["92",_CT],["93",_CT],["94",_CT],["95",_CT],["96",_CT],["97",_CT],["98",_CT],["99",_CT]]);$1.gs1syntax=_CU;$k[$j++]=true;for(var _CX=0,_CW=$1.vals.length-1;_CX<=_CW;_CX+=1){$1.ai=$g($1.ais,_CX);$1.val=$g($1.vals,_CX);var _Ce=$g($1.gs1syntax,$1.ai)!==undefined;if(_Ce){$F($g($1.gs1syntax,$1.ai),function(){$1.props=$k[--$j];var _Ck=$g($1.props,"max");var _Cl=$1.val;var _Cm=_Cl.length;if(_Ck>_Cl.length){var _=_Cm;_Cm=_Ck;_Ck=_}$1.eval=$G($1.val,0,_Ck);var _Cr=$1.eval.length;$1.val=$G($1.val,_Cr,$1.val.length-_Cr);if($1.eval.length<$g($1.props,"min")){$j--;$k[$j++]="bwipp.GS1valueTooShort";$k[$j++]="Too short";$k[$j++]=false;return true}var _Cx=new Map([["N","lintnumeric"],["X","lintcset82"],["C","lintcset39"]]);$k[$j++]=$1.eval;if($1[$g(_Cx,$g($1.props,"cset"))]()===true){return true}if($1.eval.length>0){$F($g($1.props,"check"),function(){var _D8=$1[$k[--$j]];$k[$j++]=$1.eval;if(_D8()===true){return true}})}});var _D9=$k[--$j];$k[$j++]=_D9;if($nt(_D9)){break}if($1.val.length!=0){$j--;$k[$j++]="bwipp.GS1valueTooLong";$k[$j++]="Too long";$k[$j++]=false;break}}else{$j--;$k[$j++]="bwipp.GS1unknownAI";$k[$j++]="Unrecognised AI";$k[$j++]=false;break}}if($nt($k[--$j])){var _DC=$k[--$j];var _DE=$s(_DC.length+$1.ai.length+5);$P(_DE,0,"AI ");$P(_DE,3,$1.ai);$P(_DE,3+$1.ai.length,": ");$P(_DE,5+$1.ai.length,_DC);$k[$j++]=_DE;bwipp_raiseerror()}$k[$j++]=true}function bwipp_renmatrix(){if($0.bwipjs_dontdraw){return}var $1={};$1.args=$k[--$j];$1.width=1;$1.height=1;$1.barcolor="unset";$1.backgroundcolor="unset";$1.colormap="unset";$1.dotty=false;$1.inkspread=0;$1.inkspreadh=0;$1.inkspreadv=0;$1.includetext=false;$1.txt=$a([]);$1.textcolor="unset";$1.textxalign="unset";$1.textyalign="unset";$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=0;$1.textgaps=0;$1.alttext="";$F($1.args,function(){var _4=$k[--$j];$1[$k[--$j]]=_4});var _6=$1.opt;for(var _B=_6.size,_A=_6.keys(),_9=0;_9<_B;_9++){var _7=_A.next().value;$1[_7]=_6.get(_7)}$1.width=+$1.width;$1.height=+$1.height;$1.barcolor=""+$1.barcolor;$1.backgroundcolor=""+$1.backgroundcolor;$1.inkspread=+$1.inkspread;$1.inkspreadh=+$1.inkspreadh;$1.inkspreadv=+$1.inkspreadv;$1.textcolor=""+$1.textcolor;$1.textxalign=""+$1.textxalign;$1.textyalign=""+$1.textyalign;$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.textgaps=+$1.textgaps;$1.alttext=""+$1.alttext;if($1.inkspread!=0){$1.inkspreadh=$1.inkspread}if($1.inkspread!=0){$1.inkspreadv=$1.inkspread}$1.xyget=function(){var _X=$k[--$j];var _a=$g($1.pixs,$f($k[--$j]+_X*$1.pixx));$k[$j++]=_a};$1.cget=function(){var _c=$k[--$j];var _f=$g($1.cache,$f($k[--$j]+_c*$1.pixx));var _g=$k[--$j];$k[$j++]=$an(_g,_f)};$1.cput=function(){var _h=$k[--$j];$k[$j++]=_h;if(_h%4==0){var _i=$k[--$j];var _j=$k[--$j];var _k=$k[--$j];var _l=$1.pixx;var _m=$1.cache;$p(_m,$f(_k+_j*_l),$or($g(_m,$f(_k+_j*_l)),_i))}else{$j-=3}};$1.abcd=function(){$k[$j++]=$s(4);$k[$j++]=0;$k[$j++]=Infinity;var _p=$k[--$j];var _q=$k[--$j];var _r=$k[--$j];var _s=$k[--$j];var _v=$f($k[--$j]+_s*$1.pixx);$k[$j++]=_r;$k[$j++]=_q;$k[$j++]=_p;$k[$j++]=_v;$q($G($1.pixs,_v,2));var _y=$k[--$j];var _z=$k[--$j];var _13=$G($1.pixs,$f($k[--$j]+$1.pixx),2);$k[$j++]=_z;$k[$j++]=_y;$q(_13);var _14=$a();for(var _15=0,_16=_14.length;_15<_16;_15++){var _18=$k[--$j];var _19=$k[--$j];$p(_19,_18,$f($g(_14,_15)+48));$k[$j++]=_19;$k[$j++]=$f(_18+1)}$j--};$1.right=function(){if($1.dir!=1){$k[$j++]=$1.x;$k[$j++]=$1.y;$k[$j++]=$1.dir;$1.cput();$k[$j++]=$a([$1.x,$1.y])}$1.x=$1.x+1;$1.dir=1};$1.down=function(){if($1.dir!=2){$k[$j++]=$1.x;$k[$j++]=$1.y;$k[$j++]=$1.dir;$1.cput();$k[$j++]=$a([$1.x,$1.y])}$1.y=$1.y+1;$1.dir=2};$1.left=function(){if($1.dir!=4){$k[$j++]=$1.x;$k[$j++]=$1.y;$k[$j++]=$1.dir;$1.cput();$k[$j++]=$a([$1.x,$1.y])}$1.x=$1.x-1;$1.dir=4};$1.up=function(){if($1.dir!=8){$k[$j++]=$1.x;$k[$j++]=$1.y;$k[$j++]=$1.dir;$1.cput();$k[$j++]=$a([$1.x,$1.y])}$1.y=$1.y-1;$1.dir=8};$1.trace=function(){$1.y=$k[--$j];$1.x=$k[--$j];$k[$j++]="dir";$k[$j++]=$f($1.x+1);$k[$j++]=$f($1.y+1);$1.xyget();var _1l=$k[--$j]==1?8:4;$1[$k[--$j]]=_1l;$1.sx=$1.x;$1.sy=$1.y;$1.sdir=$1.dir;$k[$j++]=Infinity;for(;;){$k[$j++]=$1.x;$k[$j++]=$1.y;$1.abcd();for(var _1s=0,_1t=1;_1s<_1t;_1s++){var _1u=$k[--$j];$k[$j++]=_1u;if($eq(_1u,"0001")||($eq(_1u,"0011")||$eq(_1u,"1011"))){$j--;$1.right();break}var _1v=$k[--$j];$k[$j++]=_1v;if($eq(_1v,"0010")||($eq(_1v,"1010")||$eq(_1v,"1110"))){$j--;$1.down();break}var _1w=$k[--$j];$k[$j++]=_1w;if($eq(_1w,"1000")||($eq(_1w,"1100")||$eq(_1w,"1101"))){$j--;$1.left();break}var _1x=$k[--$j];$k[$j++]=_1x;if($eq(_1x,"0100")||($eq(_1x,"0101")||$eq(_1x,"0111"))){$j--;$1.up();break}var _1y=$k[--$j];$k[$j++]=_1y;if($eq(_1y,"1001")){if($1.dir==2){$j--;$1.left();break}else{$j--;$1.right();break}}else{if($1.dir==1){$j--;$1.down();break}else{$j--;$1.up();break}}}if($eq($1.x,$1.sx)&&$eq($1.y,$1.sy)&&$1.dir==$1.sdir){break}}$r($a($m()));var _29=$k[--$j];var _2A=$k[--$j];$k[$j++]=_29;$k[$j++]=_2A;$j--};$1.drawlayer=function(){$1.pixsorig=$1.pixs;$1.pixs=$k[--$j];$k[$j++]=Infinity;for(var _2E=0,_2F=$1.pixx+2;_2E<_2F;_2E++){$k[$j++]=0}for(var _2J=0,_2K=$1.pixx,_2I=$1.pixs.length-1;_2K<0?_2J>=_2I:_2J<=_2I;_2J+=_2K){$k[$j++]=0;$q($G($1.pixs,_2J,$1.pixx));$k[$j++]=0}for(var _2P=0,_2Q=$1.pixx+2;_2P<_2Q;_2P++){$k[$j++]=0}$1.pixs=$a();$1.pixx=$1.pixx+2;$1.pixy=$1.pixy+2;$k[$j++]=Infinity;for(var _2V=0,_2W=$1.pixs.length;_2V<_2W;_2V++){$k[$j++]=0}$1.cache=$a();$k[$j++]=Infinity;for(var _2a=0,_2Z=$1.pixy-2;_2a<=_2Z;_2a+=1){$1.j=_2a;for(var _2d=0,_2c=$1.pixx-2;_2d<=_2c;_2d+=1){$1.i=_2d;$k[$j++]="k";$k[$j++]=$1.i;$k[$j++]=$1.j;$1.abcd();var _2g=$k[--$j];$1[$k[--$j]]=_2g;if($eq($1.k,"0001")||$eq($1.k,"1001")){$k[$j++]=8;$k[$j++]=$1.i;$k[$j++]=$1.j;$1.cget();if($k[--$j]==0){$k[$j++]=$1.i;$k[$j++]=$1.j;$1.trace()}}if($eq($1.k,"1110")){$k[$j++]=4;$k[$j++]=$1.i;$k[$j++]=$1.j;$1.cget();if($k[--$j]==0){$k[$j++]=$1.i;$k[$j++]=$1.j;$1.trace()}}}}$1.paths=$a();$1.pixx=$1.pixx-2;$1.pixy=$1.pixy-2;$$.newpath();var _2y=$1.paths;for(var _2z=0,_30=_2y.length;_2z<_30;_2z++){$1.p=$g(_2y,_2z);$1.len=$1.p.length;$q($g($1.p,$1.len-1));$q($g($1.p,0));for(var _3A=0,_39=$1.len-1;_3A<=_39;_3A+=1){$1.i=_3A;$q($g($1.p,($1.i+1)%$1.len));var _3F=$k[--$j];var _3G=$k[--$j];var _3H=$k[--$j];var _3I=$k[--$j];var _3J=$k[--$j];var _3K=$k[--$j];$k[$j++]=_3I;$k[$j++]=_3H;$k[$j++]=_3G;$k[$j++]=_3F;$k[$j++]=_3K;$k[$j++]=_3I;$k[$j++]=$1.inkspreadh;if($lt(_3F,_3J)){var _3M=$k[--$j];var _3N=$k[--$j];$k[$j++]=$f(_3N+_3M)}else{var _3O=$k[--$j];var _3P=$k[--$j];$k[$j++]=$f(_3P-_3O)}var _3Q=$k[--$j];var _3R=$k[--$j];var _3S=$k[--$j];var _3T=$k[--$j];var _3U=$k[--$j];$k[$j++]=_3U;$k[$j++]=_3Q;$k[$j++]=_3T;$k[$j++]=_3S;$k[$j++]=_3U;$k[$j++]=$1.inkspreadv;if($gt(_3T,_3R)){var _3W=$k[--$j];var _3X=$k[--$j];$k[$j++]=$f(_3X+_3W)}else{var _3Y=$k[--$j];var _3Z=$k[--$j];$k[$j++]=$f(_3Z-_3Y)}var _3a=$k[--$j];var _3b=$k[--$j];var _3c=$k[--$j];var _3d=$k[--$j];$k[$j++]=_3c;$k[$j++]=_3b;$k[$j++]=_3d;$k[$j++]=$f($1.pixy-_3a);if($1.i==0){var _3g=$k[--$j];$$.moveto($k[--$j],_3g)}else{var _3i=$k[--$j];$$.lineto($k[--$j],_3i)}}$$.closepath();$j-=4}$$.fill();$1.pixs=$1.pixsorig};$1.drawlayerdots=function(){$1.pixsorig=$1.pixs;$1.pixs=$k[--$j];$$.newpath();for(var _3p=0,_3o=$1.pixs.length-1;_3p<=_3o;_3p+=1){$1.x=_3p%$1.pixx;$1.y=~~(_3p/$1.pixx);$k[$j++]=$1.x;$k[$j++]=$1.y;$1.xyget();if($k[--$j]==1){$$.moveto($f($1.x+.5),$f($1.pixy-$1.y-.5));$$.arc($f($1.x+.5),$f($1.pixy-$1.y-.5),$f(.5-$1.inkspread),0,360,1)}}$$.fill();$1.pixs=$1.pixsorig};$$.save();$1.inkspread=$1.inkspread/2;$1.inkspreadh=$1.inkspreadh/2;$1.inkspreadv=$1.inkspreadv/2;var _46=$$.currpos();$$.translate(_46.x,_46.y);$$.scale($1.width/$1.pixx*72,$1.height/$1.pixy*72);$$.moveto(0,0);$$.lineto($1.pixx,0);$$.lineto($1.pixx,$1.pixy);$$.lineto(0,$1.pixy);$$.closepath();if($eq($1.colormap,"unset")){var _4H=new Map([[1,$1.barcolor]]);$1.colormap=_4H}var _4I=$1.colormap;for(var _4N=_4I.size,_4M=_4I.keys(),_4L=0;_4L<_4N;_4L++){var _4J=_4M.next().value;$$.setcolor(_4I.get(_4J));$1.key=_4J;$k[$j++]=Infinity;var _4O=$1.pixs;for(var _4P=0,_4Q=_4O.length;_4P<_4Q;_4P++){var _4T=$eq($g(_4O,_4P),$1.key)?1:0;$k[$j++]=_4T}var _4U=$a();$k[$j++]=_4U;if($1.dotty){$1.drawlayerdots()}else{$1.drawlayer()}}if($ne($1.textcolor,"unset")){$$.setcolor($1.textcolor)}if($1.includetext){if($eq($1.textxalign,"unset")&&$eq($1.textyalign,"unset")&&$eq($1.alttext,"")){$1.s=0;$1.fn="";var _4c=$1.txt;for(var _4d=0,_4e=_4c.length;_4d<_4e;_4d++){$F($g(_4c,_4d));var _4g=$k[--$j];var _4h=$k[--$j];$k[$j++]=_4h;$k[$j++]=_4g;if(_4g!=$1.s||$ne(_4h,$1.fn)){var _4k=$k[--$j];var _4l=$k[--$j];$1.s=_4k;$1.fn=_4l;$$.selectfont(_4l,_4k)}else{$j-=2}var _4m=$k[--$j];$$.moveto($k[--$j],_4m);$$.show($k[--$j],0,0)}}else{$$.selectfont($1.textfont,$1.textsize);if($eq($1.alttext,"")){$k[$j++]=Infinity;var _4s=$1.txt;for(var _4t=0,_4u=_4s.length;_4t<_4u;_4t++){$F($g($g(_4s,_4t),0))}$1.txt=$a();$1.tstr=$s($1.txt.length);for(var _52=0,_51=$1.txt.length-1;_52<=_51;_52+=1){$p($1.tstr,_52,$g($1.txt,_52))}}else{$1.tstr=$1.alttext}if($1.tstr.length==0){$k[$j++]=0}else{$$.save();$$.newpath();$$.moveto(0,0);$$.charpath("0",false);var _58=$$.pathbbox();$$.restore();$k[$j++]=_58.ury}$1.textascent=$k[--$j];var _5B=$$.stringwidth($1.tstr);$1.textwidth=$f(_5B.w+($1.tstr.length-1)*$1.textgaps);$1.textxpos=$f($1.textxoffset+$f($1.x-$1.textwidth)/2);if($eq($1.textxalign,"left")){$1.textxpos=$1.textxoffset}if($eq($1.textxalign,"right")){$1.textxpos=$f($f($1.x-$1.textxoffset)-$1.textwidth)}if($eq($1.textxalign,"offleft")){$1.textxpos=-$f($1.textwidth+$1.textxoffset)}if($eq($1.textxalign,"offright")){$1.textxpos=$f($1.x+$1.textxoffset)}if($eq($1.textxalign,"justify")&&$1.textwidth<$1.x){$1.textxpos=0;$1.textgaps=$f($1.x-$1.textwidth)/($1.tstr.length-1)}$1.textypos=-$f($f($1.textyoffset+$1.textascent)+1);if($eq($1.textyalign,"above")){$1.textypos=$f($f($1.textyoffset+$1.pixy)+1)}if($eq($1.textyalign,"center")){$1.textypos=$f($1.textyoffset+$f($1.pixy-$1.textascent)/2)}$$.moveto($1.textxpos,$1.textypos);$$.show($1.tstr,$1.textgaps,0)}}$$.restore()}function bwipp_ean5(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=12;$1.textxoffset=0;$1.textyoffset="unset";$1.height=.7;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.height=+$1.height;$1.textxoffset=+$1.textxoffset;if($eq($1.textyoffset,"unset")){$1.textyoffset=$f($1.height*72+1)}else{$1.textyoffset=+$1.textyoffset}if($1.barcode.length!=5){$k[$j++]="bwipp.ean5badLength";$k[$j++]="EAN-5 add-on must be 5 digits";bwipp_raiseerror()}$F($1.barcode,function(){var _E=$k[--$j];if(_E<48||_E>57){$k[$j++]="bwipp.ean5badCharacter";$k[$j++]="EAN-5 add-on must contain only digits";bwipp_raiseerror()}});$1.encs=$a(["3211","2221","2122","1411","1132","1231","1114","1312","1213","3112","112","11"]);$1.barchars="0123456789";$1.mirrormaps=$a(["11000","10100","10010","10001","01100","00110","00011","01010","01001","00101"]);$1.checksum=0;for(var _H=0;_H<=4;_H+=1){$1.i=_H;$1.barchar=$f($g($1.barcode,$1.i)-48);if($1.i%2==0){$1.checksum=$f($1.barchar*3+$1.checksum)}else{$1.checksum=$f($1.barchar*9+$1.checksum)}}$1.checksum=$1.checksum%10;$1.mirrormap=$g($1.mirrormaps,$1.checksum);$1.sbs=$s(31);$1.txt=$a(5);for(var _W=0;_W<=4;_W+=1){$1.i=_W;if($1.i==0){$P($1.sbs,0,$g($1.encs,10))}else{$P($1.sbs,($1.i-1)*6+7,$g($1.encs,11))}$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enc=$g($1.encs,$1.indx);if($g($1.mirrormap,$1.i)==49){$1.enclen=$1.enc.length;$1.revenc=$s($1.enclen);for(var _v=0,_u=$1.enclen-1;_v<=_u;_v+=1){$1.j=_v;$1.char=$g($1.enc,$1.j);$p($1.revenc,$1.enclen-$1.j-1,$1.char)}$1.enc=$1.revenc}$P($1.sbs,$1.i*6+3,$1.enc);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$f(($1.i-1)*9+13+$1.textxoffset),$1.textyoffset,$1.textfont,$1.textsize]))}$k[$j++]=Infinity;$k[$j++]=Infinity;var _1I=$1.sbs;for(var _1J=0,_1K=_1I.length;_1J<_1K;_1J++){$k[$j++]=$g(_1I,_1J)-48}var _1M=$a();$k[$j++]=Infinity;for(var _1N=0,_1O=16;_1N<_1O;_1N++){$k[$j++]=$1.height}var _1Q=$a();$k[$j++]=Infinity;for(var _1R=0,_1S=16;_1R<_1S;_1R++){$k[$j++]=0}var _1T=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_1M;$k[$j++]="bhs";$k[$j++]=_1Q;$k[$j++]="bbs";$k[$j++]=_1T;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;$k[$j++]="guardrightpos";$k[$j++]=10;$k[$j++]="guardrightypos";$k[$j++]=$f($1.textyoffset+4);$k[$j++]="bordertop";$k[$j++]=10;var _1Y=$d();$k[$j++]=_1Y;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_ean2(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=12;$1.textxoffset=0;$1.textyoffset="unset";$1.height=.7;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.height=+$1.height;$1.textxoffset=+$1.textxoffset;if($eq($1.textyoffset,"unset")){$1.textyoffset=$f($1.height*72+1)}else{$1.textyoffset=+$1.textyoffset}if($1.barcode.length!=2){$k[$j++]="bwipp.ean2badLength";$k[$j++]="EAN-2 add-on must be 2 digits";bwipp_raiseerror()}$F($1.barcode,function(){var _E=$k[--$j];if(_E<48||_E>57){$k[$j++]="bwipp.ean2badCharacter";$k[$j++]="EAN-2 add-on must contain only digits";bwipp_raiseerror()}});$1.encs=$a(["3211","2221","2122","1411","1132","1231","1114","1312","1213","3112","112","11"]);$1.barchars="0123456789";$1.mirrormap=$g($a(["00","01","10","11"]),~~$z($G($1.barcode,0,2))%4);$1.sbs=$s(13);$1.txt=$a(2);for(var _M=0;_M<=1;_M+=1){$1.i=_M;if($1.i==0){$P($1.sbs,0,$g($1.encs,10))}else{$P($1.sbs,($1.i-1)*6+7,$g($1.encs,11))}$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enc=$g($1.encs,$1.indx);if($g($1.mirrormap,$1.i)==49){$1.enclen=$1.enc.length;$1.revenc=$s($1.enclen);for(var _l=0,_k=$1.enclen-1;_l<=_k;_l+=1){$1.j=_l;$1.char=$g($1.enc,$1.j);$p($1.revenc,$1.enclen-$1.j-1,$1.char)}$1.enc=$1.revenc}$P($1.sbs,$1.i*6+3,$1.enc);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$f(($1.i-1)*9+13+$1.textxoffset),$1.textyoffset,$1.textfont,$1.textsize]))}$k[$j++]=Infinity;$k[$j++]=Infinity;var _18=$1.sbs;for(var _19=0,_1A=_18.length;_19<_1A;_19++){$k[$j++]=$g(_18,_19)-48}var _1C=$a();$k[$j++]=Infinity;for(var _1D=0,_1E=12;_1D<_1E;_1D++){$k[$j++]=$1.height}var _1G=$a();$k[$j++]=Infinity;for(var _1H=0,_1I=12;_1H<_1I;_1H++){$k[$j++]=0}var _1J=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_1C;$k[$j++]="bhs";$k[$j++]=_1G;$k[$j++]="bbs";$k[$j++]=_1J;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;$k[$j++]="guardrightpos";$k[$j++]=10;$k[$j++]="guardrightypos";$k[$j++]=$f($1.textyoffset+4);$k[$j++]="bordertop";$k[$j++]=10;var _1O=$d();$k[$j++]=_1O;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_ean13(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=12;$1.textxoffset=-10;$1.textyoffset=-4;$1.height=1;$1.addongap=12;$1.addontextfont="unset";$1.addontextsize="unset";$1.addontextxoffset="unset";$1.addontextyoffset="unset";$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.addongap=+$1.addongap;if($ne($1.addontextfont,"unset")){$1.addontextfont=""+$1.addontextfont}if($ne($1.addontextsize,"unset")){$1.addontextsize=+$1.addontextsize}if($ne($1.addontextxoffset,"unset")){$1.addontextxoffset=+$1.addontextxoffset}if($ne($1.addontextyoffset,"unset")){$1.addontextyoffset=+$1.addontextyoffset}$x($1.barcode," ");if($k[--$j]){$1.barcode=$k[--$j];$j--;$1.addon=$k[--$j]}else{$j--;$1.addon=""}if($1.barcode.length!=12&&$1.barcode.length!=13){$k[$j++]="bwipp.ean13badLength";$k[$j++]="EAN-13 must be 12 or 13 digits";bwipp_raiseerror()}$F($1.barcode,function(){var _Q=$k[--$j];if(_Q<48||_Q>57){$k[$j++]="bwipp.ean13badCharacter";$k[$j++]="EAN-13 must contain only digits";bwipp_raiseerror()}});if($1.addon.length!=0&&$1.addon.length!=2&&$1.addon.length!=5){$k[$j++]="bwipp.ean13badAddOnLength";$k[$j++]="Add-on for EAN-13 must be 2 or 5 digits";bwipp_raiseerror()}$1.pad=$s(13);$1.checksum=0;for(var _V=0;_V<=11;_V+=1){$1.i=_V;$1.barchar=$f($g($1.barcode,$1.i)-48);if($1.i%2==0){$1.checksum=$f($1.barchar+$1.checksum)}else{$1.checksum=$f($1.barchar*3+$1.checksum)}}$1.checksum=$f(10-$1.checksum%10)%10;if($1.barcode.length==13){if($g($1.barcode,12)!=$f($1.checksum+48)){$k[$j++]="bwipp.ean13badCheckDigit";$k[$j++]="Incorrect EAN-13 check digit provided";bwipp_raiseerror()}}$P($1.pad,0,$1.barcode);$p($1.pad,12,$f($1.checksum+48));$1.barcode=$1.pad;$1.encs=$a(["3211","2221","2122","1411","1132","1231","1114","1312","1213","3112","111","11111","111"]);$1.barchars="0123456789";$1.mirrormaps=$a(["000000","001011","001101","001110","010011","011001","011100","010101","010110","011010"]);$1.sbs=$s(59);$1.txt=$a(13);$P($1.sbs,0,$g($1.encs,10));$1.mirrormap=$g($1.mirrormaps,$g($1.barcode,0)-48);$p($1.txt,0,$a([$G($1.barcode,0,1),$1.textxoffset,$1.textyoffset,$1.textfont,$1.textsize]));for(var _17=1;_17<=6;_17+=1){$1.i=_17;$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enc=$g($1.encs,$1.indx);if($g($1.mirrormap,$1.i-1)==49){$1.enclen=$1.enc.length;$1.revenc=$s($1.enclen);for(var _1O=0,_1N=$1.enclen-1;_1O<=_1N;_1O+=1){$1.j=_1O;$1.char=$g($1.enc,$1.j);$p($1.revenc,$1.enclen-$1.j-1,$1.char)}$1.enc=$1.revenc}$P($1.sbs,($1.i-1)*4+3,$1.enc);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$f(($1.i-1)*7+14+$1.textxoffset),$1.textyoffset,$1.textfont,$1.textsize]))}$P($1.sbs,(7-1)*4+3,$g($1.encs,11));for(var _1o=7;_1o<=12;_1o+=1){$1.i=_1o;$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enc=$g($1.encs,$1.indx);$P($1.sbs,($1.i-1)*4+8,$1.enc);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$f(($1.i-1)*7+18+$1.textxoffset),$1.textyoffset,$1.textfont,$1.textsize]))}$P($1.sbs,56,$g($1.encs,12));$k[$j++]=Infinity;var _2E=$1.sbs;for(var _2F=0,_2G=_2E.length;_2F<_2G;_2F++){$k[$j++]=$g(_2E,_2F)-48}$1.sbs=$a();if($1.includetext){$k[$j++]=Infinity;$k[$j++]=$1.height;$k[$j++]=$1.height;for(var _2M=0,_2N=12;_2M<_2N;_2M++){$k[$j++]=$f($1.height-.075)}$k[$j++]=$1.height;$k[$j++]=$1.height;for(var _2R=0,_2S=12;_2R<_2S;_2R++){$k[$j++]=$f($1.height-.075)}$k[$j++]=$1.height;$k[$j++]=$1.height;$1.bhs=$a();$k[$j++]=Infinity;$k[$j++]=0;$k[$j++]=0;for(var _2X=0,_2Y=12;_2X<_2Y;_2X++){$k[$j++]=.075}$k[$j++]=0;$k[$j++]=0;for(var _2Z=0,_2a=12;_2Z<_2a;_2Z++){$k[$j++]=.075}$k[$j++]=0;$k[$j++]=0;$1.bbs=$a()}else{$k[$j++]=Infinity;for(var _2c=0,_2d=30;_2c<_2d;_2c++){$k[$j++]=$1.height}$1.bhs=$a();$k[$j++]=Infinity;for(var _2g=0,_2h=30;_2g<_2h;_2g++){$k[$j++]=0}$1.bbs=$a();$1.txt=$a([])}$1.guardrightypos=0;if($ne($1.addon,"")){$k[$j++]=Infinity;$k[$j++]="dontdraw";$k[$j++]=true;$k[$j++]="includetext";$k[$j++]=true;$k[$j++]="height";$k[$j++]=$f($1.height-.15);$k[$j++]="textxoffset";if($1.addontextxoffset!="unset"){$k[$j++]=$1.addontextxoffset}else{$k[$j++]=$f(95+$1.addongap)}if($1.addontextyoffset!="unset"){$k[$j++]="textyoffset";$k[$j++]=$1.addontextyoffset}var _2s=$1.addontextsize!="unset"?$1.addontextsize:$1.textsize;var _2u=$ne($1.addontextfont,"unset")?$1.addontextfont:$1.textfont;$k[$j++]="textsize";$k[$j++]=_2s;$k[$j++]="textfont";$k[$j++]=_2u;$1.addopts=$d();if($1.addon.length==2){$k[$j++]=$1.addon;$k[$j++]=$1.addopts;bwipp_ean2()}if($1.addon.length==5){$k[$j++]=$1.addon;$k[$j++]=$1.addopts;bwipp_ean5()}$1.addcode=$k[--$j];$k[$j++]=Infinity;$q($1.sbs);$k[$j++]=$1.addongap;$q($g($1.addcode,"sbs"));$1.sbs=$a();$k[$j++]=Infinity;$q($1.bhs);$F($g($1.addcode,"bhs"),function(){var _3B=$k[--$j];$k[$j++]=$f(_3B-.075)});$1.bhs=$a();$k[$j++]=Infinity;$q($1.bbs);$F($g($1.addcode,"bbs"),function(){var _3G=$k[--$j];$k[$j++]=$f(_3G+.075)});$1.bbs=$a();$k[$j++]=Infinity;$q($1.txt);$q($g($1.addcode,"txt"));$1.txt=$a();$1.guardrightypos=$f($1.height*72-6)}var _3T=new Map([["ren",bwipp_renlinear],["sbs",$1.sbs],["bhs",$1.bhs],["bbs",$1.bbs],["txt",$1.txt],["opt",$1.options],["guardrightpos",10],["guardrightypos",$1.guardrightypos],["borderbottom",5]]);$k[$j++]=_3T;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_ean8(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=12;$1.textxoffset=4;$1.textyoffset=-4;$1.height=1;$1.addongap=12;$1.addontextfont="unset";$1.addontextsize="unset";$1.addontextxoffset="unset";$1.addontextyoffset="unset";$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.addongap=+$1.addongap;if($ne($1.addontextfont,"unset")){$1.addontextfont=""+$1.addontextfont}if($ne($1.addontextsize,"unset")){$1.addontextsize=+$1.addontextsize}if($ne($1.addontextxoffset,"unset")){$1.addontextxoffset=+$1.addontextxoffset}if($ne($1.addontextyoffset,"unset")){$1.addontextyoffset=+$1.addontextyoffset}$x($1.barcode," ");if($k[--$j]){$1.barcode=$k[--$j];$j--;$1.addon=$k[--$j]}else{$j--;$1.addon=""}if($1.barcode.length!=7&&$1.barcode.length!=8){$k[$j++]="bwipp.ean8badLength";$k[$j++]="EAN-8 must be 7 or 8 digits";bwipp_raiseerror()}$F($1.barcode,function(){var _Q=$k[--$j];if(_Q<48||_Q>57){$k[$j++]="bwipp.ean8badCharacter";$k[$j++]="EAN-8 must contain only digits";bwipp_raiseerror()}});if($1.addon.length!=0&&$1.addon.length!=2&&$1.addon.length!=5){$k[$j++]="bwipp.ean8badAddOnLength";$k[$j++]="Add-on for EAN-8 must be 2 or 5 digits";bwipp_raiseerror()}$1.pad=$s(8);$1.checksum=0;for(var _V=0;_V<=6;_V+=1){$1.i=_V;$1.barchar=$f($g($1.barcode,$1.i)-48);if($1.i%2!=0){$1.checksum=$f($1.barchar+$1.checksum)}else{$1.checksum=$f($1.barchar*3+$1.checksum)}}$1.checksum=$f(10-$1.checksum%10)%10;if($1.barcode.length==8){if($g($1.barcode,7)!=$f($1.checksum+48)){$k[$j++]="bwipp.ean8badCheckDigit";$k[$j++]="Incorrect EAN-8 check digit provided";bwipp_raiseerror()}}$P($1.pad,0,$1.barcode);$p($1.pad,7,$f($1.checksum+48));$1.barcode=$1.pad;$1.encs=$a(["3211","2221","2122","1411","1132","1231","1114","1312","1213","3112","111","11111","111"]);$1.barchars="0123456789";$1.sbs=$s(43);$1.txt=$a(8);$P($1.sbs,0,$g($1.encs,10));for(var _u=0;_u<=3;_u+=1){$1.i=_u;$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enc=$g($1.encs,$1.indx);$P($1.sbs,$1.i*4+3,$1.enc);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$f($1.i*7+$1.textxoffset),$1.textyoffset,$1.textfont,$1.textsize]))}$P($1.sbs,4*4+3,$g($1.encs,11));for(var _1K=4;_1K<=7;_1K+=1){$1.i=_1K;$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enc=$g($1.encs,$1.indx);$P($1.sbs,$1.i*4+8,$1.enc);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$f($f($1.i*7+$1.textxoffset)+4),$1.textyoffset,$1.textfont,$1.textsize]))}$P($1.sbs,40,$g($1.encs,12));$k[$j++]=Infinity;var _1k=$1.sbs;for(var _1l=0,_1m=_1k.length;_1l<_1m;_1l++){$k[$j++]=$g(_1k,_1l)-48}$1.sbs=$a();if($1.includetext){$k[$j++]=Infinity;$k[$j++]=$1.height;$k[$j++]=$1.height;for(var _1s=0,_1t=8;_1s<_1t;_1s++){$k[$j++]=$f($1.height-.075)}$k[$j++]=$1.height;$k[$j++]=$1.height;for(var _1x=0,_1y=8;_1x<_1y;_1x++){$k[$j++]=$f($1.height-.075)}$k[$j++]=$1.height;$k[$j++]=$1.height;$1.bhs=$a();$k[$j++]=Infinity;$k[$j++]=0;$k[$j++]=0;for(var _23=0,_24=8;_23<_24;_23++){$k[$j++]=.075}$k[$j++]=0;$k[$j++]=0;for(var _25=0,_26=8;_25<_26;_25++){$k[$j++]=.075}$k[$j++]=0;$k[$j++]=0;$1.bbs=$a()}else{$k[$j++]=Infinity;for(var _28=0,_29=22;_28<_29;_28++){$k[$j++]=$1.height}$1.bhs=$a();$k[$j++]=Infinity;for(var _2C=0,_2D=22;_2C<_2D;_2C++){$k[$j++]=0}$1.bbs=$a();$1.txt=$a([])}$1.guardrightypos=0;if($ne($1.addon,"")){$k[$j++]=Infinity;$k[$j++]="dontdraw";$k[$j++]=true;$k[$j++]="includetext";$k[$j++]=true;$k[$j++]="height";$k[$j++]=$f($1.height-.15);$k[$j++]="textxoffset";if($1.addontextxoffset!="unset"){$k[$j++]=$1.addontextxoffset}else{$k[$j++]=$f(67+$1.addongap)}if($1.addontextyoffset!="unset"){$k[$j++]="textyoffset";$k[$j++]=$1.addontextyoffset}var _2O=$1.addontextsize!="unset"?$1.addontextsize:$1.textsize;var _2Q=$ne($1.addontextfont,"unset")?$1.addontextfont:$1.textfont;$k[$j++]="textsize";$k[$j++]=_2O;$k[$j++]="textfont";$k[$j++]=_2Q;$1.addopts=$d();if($1.addon.length==2){$k[$j++]=$1.addon;$k[$j++]=$1.addopts;bwipp_ean2()}if($1.addon.length==5){$k[$j++]=$1.addon;$k[$j++]=$1.addopts;bwipp_ean5()}$1.addcode=$k[--$j];$k[$j++]=Infinity;$q($1.sbs);$k[$j++]=$1.addongap;$q($g($1.addcode,"sbs"));$1.sbs=$a();$k[$j++]=Infinity;$q($1.bhs);$F($g($1.addcode,"bhs"),function(){var _2h=$k[--$j];$k[$j++]=$f(_2h-.075)});$1.bhs=$a();$k[$j++]=Infinity;$q($1.bbs);$F($g($1.addcode,"bbs"),function(){var _2m=$k[--$j];$k[$j++]=$f(_2m+.075)});$1.bbs=$a();$k[$j++]=Infinity;$q($1.txt);$q($g($1.addcode,"txt"));$1.txt=$a();$1.guardrightypos=$f($1.height*72-6)}var _2z=new Map([["ren",bwipp_renlinear],["sbs",$1.sbs],["bhs",$1.bhs],["bbs",$1.bbs],["txt",$1.txt],["opt",$1.options],["guardleftpos",10],["guardrightpos",10],["guardrightypos",$1.guardrightypos],["borderbottom",5]]);$k[$j++]=_2z;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_upca(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=12;$1.textxoffset=-7;$1.textyoffset=-4;$1.height=1;$1.addongap=12;$1.addontextfont="unset";$1.addontextsize="unset";$1.addontextxoffset="unset";$1.addontextyoffset="unset";$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.addongap=+$1.addongap;if($ne($1.addontextfont,"unset")){$1.addontextfont=""+$1.addontextfont}if($ne($1.addontextsize,"unset")){$1.addontextsize=+$1.addontextsize}if($ne($1.addontextxoffset,"unset")){$1.addontextxoffset=+$1.addontextxoffset}if($ne($1.addontextyoffset,"unset")){$1.addontextyoffset=+$1.addontextyoffset}$x($1.barcode," ");if($k[--$j]){$1.barcode=$k[--$j];$j--;$1.addon=$k[--$j]}else{$j--;$1.addon=""}if($1.barcode.length==7||$1.barcode.length==8){$F($1.barcode,function(){var _Q=$k[--$j];if(_Q<48||_Q>57){$k[$j++]="bwipp.upcAupcEbadCharacter";$k[$j++]="UPC-E must contain only digits";bwipp_raiseerror()}});var _S=$g($1.barcode,0);if(_S!=48&&_S!=49){$k[$j++]="bwipp.upcAupcEbadNumberSystem";$k[$j++]="UPC-E must have number system 0 or 1";bwipp_raiseerror()}for(var _T=0,_U=1;_T<_U;_T++){var _W=$1.barcode.length==8?12:11;$1.upcacode=$s(_W);if($f($g($1.barcode,6)-48)<=2){$P($1.upcacode,0,$G($1.barcode,0,3));$P($1.upcacode,3,$G($1.barcode,6,1));$P($1.upcacode,4,"0000");$P($1.upcacode,8,$G($1.barcode,3,3));break}if($f($g($1.barcode,6)-48)==3){$P($1.upcacode,0,$G($1.barcode,0,4));$P($1.upcacode,4,"00000");$P($1.upcacode,9,$G($1.barcode,4,2));break}if($f($g($1.barcode,6)-48)==4){$P($1.upcacode,0,$G($1.barcode,0,5));$P($1.upcacode,5,"00000");$P($1.upcacode,10,$G($1.barcode,5,1));break}if($f($g($1.barcode,6)-48)>=5){$P($1.upcacode,0,$G($1.barcode,0,6));$P($1.upcacode,6,"0000");$P($1.upcacode,10,$G($1.barcode,6,1));break}}if($1.barcode.length==8){$P($1.upcacode,11,$G($1.barcode,7,1))}$1.barcode=$1.upcacode}if($1.barcode.length!=11&&$1.barcode.length!=12){$k[$j++]="bwipp.upcAbadLength";$k[$j++]="UPC-A must be 11 or 12 digits";bwipp_raiseerror()}var _1I=$1.barcode;for(var _1J=0,_1K=_1I.length;_1J<_1K;_1J++){var _1L=$g(_1I,_1J);if(_1L<48||_1L>57){$k[$j++]="bwipp.upcAbadCharacter";$k[$j++]="UPC-A must contain only digits";bwipp_raiseerror()}}if($1.addon.length!=0&&$1.addon.length!=2&&$1.addon.length!=5){$k[$j++]="bwipp.upcAbadAddOnLength";$k[$j++]="Add-on for UPC-A must be 2 or 5 digits";bwipp_raiseerror()}$1.pad=$s(12);$1.checksum=0;for(var _1Q=0;_1Q<=10;_1Q+=1){$1.i=_1Q;$1.barchar=$g($1.barcode,$1.i)-48;if($1.i%2!=0){$1.checksum=$1.checksum+$1.barchar}else{$1.checksum=$1.checksum+$1.barchar*3}}$1.checksum=(10-$1.checksum%10)%10;if($1.barcode.length==12){if($g($1.barcode,11)!=$1.checksum+48){$k[$j++]="bwipp.upcAbadCheckDigit";$k[$j++]="Incorrect UPC check digit provided";bwipp_raiseerror()}}$P($1.pad,0,$1.barcode);$p($1.pad,11,$1.checksum+48);$1.barcode=$1.pad;$1.encs=$a(["3211","2221","2122","1411","1132","1231","1114","1312","1213","3112","111","11111","111"]);$1.barchars="0123456789";$1.sbs=$s(59);$1.txt=$a(12);$P($1.sbs,0,$g($1.encs,10));for(var _1p=0;_1p<=5;_1p+=1){$1.i=_1p;$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enc=$g($1.encs,$1.indx);$P($1.sbs,$1.i*4+3,$1.enc);if($1.i==0){$p($1.txt,0,$a([$G($1.barcode,0,1),$1.textxoffset,$1.textyoffset,$1.textfont,$f($1.textsize-2)]))}else{$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$f($1.i*7+11+$1.textxoffset),$1.textyoffset,$1.textfont,$1.textsize]))}}$P($1.sbs,6*4+3,$g($1.encs,11));for(var _2O=6;_2O<=11;_2O+=1){$1.i=_2O;$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enc=$g($1.encs,$1.indx);$P($1.sbs,$1.i*4+8,$1.enc);if($1.i==11){$p($1.txt,11,$a([$G($1.barcode,11,1),$f($1.textxoffset+104),$1.textyoffset,$1.textfont,$f($1.textsize-2)]))}else{$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$f($f($1.i*7+$1.textxoffset)+15),$1.textyoffset,$1.textfont,$1.textsize]))}}$P($1.sbs,56,$g($1.encs,12));$k[$j++]=Infinity;var _2x=$1.sbs;for(var _2y=0,_2z=_2x.length;_2y<_2z;_2y++){$k[$j++]=$g(_2x,_2y)-48}$1.sbs=$a();if($1.includetext){$k[$j++]=Infinity;for(var _33=0,_34=4;_33<_34;_33++){$k[$j++]=$1.height}for(var _36=0,_37=10;_36<_37;_36++){$k[$j++]=$f($1.height-.075)}$k[$j++]=$1.height;$k[$j++]=$1.height;for(var _3B=0,_3C=10;_3B<_3C;_3B++){$k[$j++]=$f($1.height-.075)}for(var _3E=0,_3F=4;_3E<_3F;_3E++){$k[$j++]=$1.height}$1.bhs=$a();$k[$j++]=Infinity;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;for(var _3I=0,_3J=10;_3I<_3J;_3I++){$k[$j++]=.075}$k[$j++]=0;$k[$j++]=0;for(var _3K=0,_3L=10;_3K<_3L;_3K++){$k[$j++]=.075}$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$1.bbs=$a()}else{$k[$j++]=Infinity;for(var _3N=0,_3O=30;_3N<_3O;_3N++){$k[$j++]=$1.height}$1.bhs=$a();$k[$j++]=Infinity;for(var _3R=0,_3S=30;_3R<_3S;_3R++){$k[$j++]=0}$1.bbs=$a();$1.txt=$a([])}$1.guardrightypos=0;if($ne($1.addon,"")){$k[$j++]=Infinity;$k[$j++]="dontdraw";$k[$j++]=true;$k[$j++]="includetext";$k[$j++]=true;$k[$j++]="height";$k[$j++]=$f($1.height-.15);$k[$j++]="textxoffset";if($1.addontextxoffset!="unset"){$k[$j++]=$1.addontextxoffset}else{$k[$j++]=$f(95+$1.addongap)}if($1.addontextyoffset!="unset"){$k[$j++]="textyoffset";$k[$j++]=$1.addontextyoffset}var _3d=$1.addontextsize!="unset"?$1.addontextsize:$1.textsize;var _3f=$ne($1.addontextfont,"unset")?$1.addontextfont:$1.textfont;$k[$j++]="textsize";$k[$j++]=_3d;$k[$j++]="textfont";$k[$j++]=_3f;$1.addopts=$d();if($1.addon.length==2){$k[$j++]=$1.addon;$k[$j++]=$1.addopts;bwipp_ean2()}if($1.addon.length==5){$k[$j++]=$1.addon;$k[$j++]=$1.addopts;bwipp_ean5()}$1.addcode=$k[--$j];$k[$j++]=Infinity;$q($1.sbs);$k[$j++]=$1.addongap;$q($g($1.addcode,"sbs"));$1.sbs=$a();$k[$j++]=Infinity;$q($1.bhs);$F($g($1.addcode,"bhs"),function(){var _3w=$k[--$j];$k[$j++]=$f(_3w-.075)});$1.bhs=$a();$k[$j++]=Infinity;$q($1.bbs);$F($g($1.addcode,"bbs"),function(){var _41=$k[--$j];$k[$j++]=$f(_41+.075)});$1.bbs=$a();$k[$j++]=Infinity;$q($1.txt);$q($g($1.addcode,"txt"));$1.txt=$a();$1.guardrightypos=$f($1.height*72-6)}var _4E=new Map([["ren",bwipp_renlinear],["sbs",$1.sbs],["bhs",$1.bhs],["bbs",$1.bbs],["txt",$1.txt],["opt",$1.options],["guardrightpos",10],["guardrightypos",$1.guardrightypos],["borderbottom",5]]);$k[$j++]=_4E;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_upce(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=12;$1.textxoffset=-7;$1.textyoffset=-4;$1.height=1;$1.addongap=12;$1.addontextfont="unset";$1.addontextsize="unset";$1.addontextxoffset="unset";$1.addontextyoffset="unset";$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.addongap=+$1.addongap;if($ne($1.addontextfont,"unset")){$1.addontextfont=""+$1.addontextfont}if($ne($1.addontextsize,"unset")){$1.addontextsize=+$1.addontextsize}if($ne($1.addontextxoffset,"unset")){$1.addontextxoffset=+$1.addontextxoffset}if($ne($1.addontextyoffset,"unset")){$1.addontextyoffset=+$1.addontextyoffset}$x($1.barcode," ");if($k[--$j]){$1.barcode=$k[--$j];$j--;$1.addon=$k[--$j]}else{$j--;$1.addon=""}if($1.barcode.length==11||$1.barcode.length==12){$F($1.barcode,function(){var _Q=$k[--$j];if(_Q<48||_Q>57){$k[$j++]="bwipp.upcEupcAbadCharacter";$k[$j++]="UPC-A must contain only digits";bwipp_raiseerror()}});for(var _R=0,_S=1;_R<_S;_R++){var _U=$1.barcode.length==12?8:7;$1.upcecode=$s(_U);if($f($g($1.barcode,3)-48)<=2&&$eq($G($1.barcode,4,4),"0000")){$P($1.upcecode,0,$G($1.barcode,0,3));$P($1.upcecode,3,$G($1.barcode,8,3));$P($1.upcecode,6,$G($1.barcode,3,1));break}if($eq($G($1.barcode,4,5),"00000")){$P($1.upcecode,0,$G($1.barcode,0,4));$P($1.upcecode,4,$G($1.barcode,9,2));$P($1.upcecode,6,"3");break}if($eq($G($1.barcode,5,5),"00000")){$P($1.upcecode,0,$G($1.barcode,0,5));$P($1.upcecode,5,$G($1.barcode,10,1));$P($1.upcecode,6,"4");break}if($f($g($1.barcode,10)-48)>=5&&$eq($G($1.barcode,6,4),"0000")){$P($1.upcecode,0,$G($1.barcode,0,6));$P($1.upcecode,6,$G($1.barcode,10,1));break}$k[$j++]="bwipp.upcEupcAnotCompressible";$k[$j++]="UPC-A cannot be converted to a UPC-E";bwipp_raiseerror()}if($1.barcode.length==12){$P($1.upcecode,7,$G($1.barcode,11,1))}$1.barcode=$1.upcecode}if($1.barcode.length!=7&&$1.barcode.length!=8){$k[$j++]="bwipp.upcEbadLength";$k[$j++]="UPC-E must be 7 or 8 digits";bwipp_raiseerror()}var _1I=$1.barcode;for(var _1J=0,_1K=_1I.length;_1J<_1K;_1J++){var _1L=$g(_1I,_1J);if(_1L<48||_1L>57){$k[$j++]="bwipp.upcEbadCharacter";$k[$j++]="UPC-E must contain only digits";bwipp_raiseerror()}}if($1.addon.length!=0&&$1.addon.length!=2&&$1.addon.length!=5){$k[$j++]="bwipp.upcEbadAddOnLength";$k[$j++]="Add-on for UPC-E must be 2 or 5 digits";bwipp_raiseerror()}var _1Q=$g($1.barcode,0);if(_1Q!=48&&_1Q!=49){$k[$j++]="bwipp.upcEbadNumberSystem";$k[$j++]="UPC-E must have number system 0 or 1";bwipp_raiseerror()}$1.encs=$a(["3211","2221","2122","1411","1132","1231","1114","1312","1213","3112","111","111111"]);$1.barchars="0123456789";$1.mirrormaps=$a(["000111","001011","001101","001110","010011","011001","011100","010101","010110","011010"]);for(var _1T=0,_1U=1;_1T<_1U;_1T++){$1.upcacode=$s(11);if($g($1.barcode,6)-48<=2){$P($1.upcacode,0,$G($1.barcode,0,3));$P($1.upcacode,3,$G($1.barcode,6,1));$P($1.upcacode,4,"0000");$P($1.upcacode,8,$G($1.barcode,3,3));break}if($g($1.barcode,6)-48==3){$P($1.upcacode,0,$G($1.barcode,0,4));$P($1.upcacode,4,"00000");$P($1.upcacode,9,$G($1.barcode,4,2));break}if($g($1.barcode,6)-48==4){$P($1.upcacode,0,$G($1.barcode,0,5));$P($1.upcacode,5,"00000");$P($1.upcacode,10,$G($1.barcode,5,1));break}if($g($1.barcode,6)-48>=5){$P($1.upcacode,0,$G($1.barcode,0,6));$P($1.upcacode,6,"0000");$P($1.upcacode,10,$G($1.barcode,6,1));break}}$1.checksum=0;for(var _29=0;_29<=10;_29+=1){$1.i=_29;$1.barchar=$g($1.upcacode,$1.i)-48;if($1.i%2!=0){$1.checksum=$1.checksum+$1.barchar}else{$1.checksum=$1.checksum+$1.barchar*3}}$1.checksum=(10-$1.checksum%10)%10;if($1.barcode.length==8){if($g($1.barcode,7)!=$1.checksum+48){$k[$j++]="bwipp.upcEbadCheckDigit";$k[$j++]="Incorrect UPC check digit provided";bwipp_raiseerror()}}$1.pad=$s(8);$P($1.pad,0,$1.barcode);$p($1.pad,7,$1.checksum+48);$1.barcode=$1.pad;$1.txt=$a(8);$p($1.txt,0,$a([$G($1.barcode,0,1),$1.textxoffset,$1.textyoffset,$1.textfont,$f($1.textsize-2)]));$1.mirrormap=$g($1.mirrormaps,$1.checksum);if($g($1.barcode,0)==48){$1.invt=$s($1.mirrormap.length);for(var _2l=0,_2k=$1.mirrormap.length-1;_2l<=_2k;_2l+=1){$1.i=_2l;if($g($1.mirrormap,$1.i)==48){$p($1.invt,$1.i,49)}else{$p($1.invt,$1.i,48)}}$1.mirrormap=$1.invt}$1.sbs=$s(33);$P($1.sbs,0,$g($1.encs,10));for(var _2y=1;_2y<=6;_2y+=1){$1.i=_2y;$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enc=$g($1.encs,$1.indx);if($g($1.mirrormap,$1.i-1)==49){$1.enclen=$1.enc.length;$1.revenc=$s($1.enclen);for(var _3F=0,_3E=$1.enclen-1;_3F<=_3E;_3F+=1){$1.j=_3F;$1.char=$g($1.enc,$1.j);$p($1.revenc,$1.enclen-$1.j-1,$1.char)}$1.enc=$1.revenc}$P($1.sbs,($1.i-1)*4+3,$1.enc);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$f(($1.i-1)*7+11+$1.textxoffset),$1.textyoffset,$1.textfont,$1.textsize]))}$p($1.txt,7,$a([$G($1.barcode,7,1),$f(6*7+18+$1.textxoffset),$1.textyoffset,$1.textfont,$f($1.textsize-2)]));$P($1.sbs,27,$g($1.encs,11));$k[$j++]=Infinity;var _3n=$1.sbs;for(var _3o=0,_3p=_3n.length;_3o<_3p;_3o++){$k[$j++]=$g(_3n,_3o)-48}$1.sbs=$a();if($1.includetext){$k[$j++]=Infinity;$k[$j++]=$1.height;$k[$j++]=$1.height;for(var _3v=0,_3w=12;_3v<_3w;_3v++){$k[$j++]=$f($1.height-.075)}$k[$j++]=$1.height;$k[$j++]=$1.height;$k[$j++]=$1.height;$1.bhs=$a();$k[$j++]=Infinity;$k[$j++]=0;$k[$j++]=0;for(var _42=0,_43=12;_42<_43;_42++){$k[$j++]=.075}$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$1.bbs=$a()}else{$k[$j++]=Infinity;for(var _45=0,_46=17;_45<_46;_45++){$k[$j++]=$1.height}$1.bhs=$a();$k[$j++]=Infinity;for(var _49=0,_4A=17;_49<_4A;_49++){$k[$j++]=0}$1.bbs=$a();$1.txt=$a([])}$1.guardrightypos=0;if($ne($1.addon,"")){$k[$j++]=Infinity;$k[$j++]="dontdraw";$k[$j++]=true;$k[$j++]="includetext";$k[$j++]=true;$k[$j++]="height";$k[$j++]=$f($1.height-.15);$k[$j++]="textxoffset";if($1.addontextxoffset!="unset"){$k[$j++]=$1.addontextxoffset}else{$k[$j++]=$f(51+$1.addongap)}if($1.addontextyoffset!="unset"){$k[$j++]="textyoffset";$k[$j++]=$1.addontextyoffset}var _4L=$1.addontextsize!="unset"?$1.addontextsize:$1.textsize;var _4N=$ne($1.addontextfont,"unset")?$1.addontextfont:$1.textfont;$k[$j++]="textsize";$k[$j++]=_4L;$k[$j++]="textfont";$k[$j++]=_4N;$1.addopts=$d();if($1.addon.length==2){$k[$j++]=$1.addon;$k[$j++]=$1.addopts;bwipp_ean2()}if($1.addon.length==5){$k[$j++]=$1.addon;$k[$j++]=$1.addopts;bwipp_ean5()}$1.addcode=$k[--$j];$k[$j++]=Infinity;$q($1.sbs);$k[$j++]=$1.addongap;$q($g($1.addcode,"sbs"));$1.sbs=$a();$k[$j++]=Infinity;$q($1.bhs);$F($g($1.addcode,"bhs"),function(){var _4e=$k[--$j];$k[$j++]=$f(_4e-.075)});$1.bhs=$a();$k[$j++]=Infinity;$q($1.bbs);$F($g($1.addcode,"bbs"),function(){var _4j=$k[--$j];$k[$j++]=$f(_4j+.075)});$1.bbs=$a();$k[$j++]=Infinity;$q($1.txt);$q($g($1.addcode,"txt"));$1.txt=$a();$1.guardrightypos=$f($1.height*72-6)}var _4w=new Map([["ren",bwipp_renlinear],["sbs",$1.sbs],["bhs",$1.bhs],["bbs",$1.bbs],["txt",$1.txt],["opt",$1.options],["guardrightpos",10],["guardrightypos",$1.guardrightypos],["borderbottom",5]]);$k[$j++]=_4w;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_isbn(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.isbntextfont="OCR-A";$1.isbntextsize=8;$1.isbntextxoffset="unset";$1.isbntextyoffset="unset";$1.height=1;$1.addongap=12;$1.legacy=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.isbntextfont=""+$1.isbntextfont;$1.isbntextsize=+$1.isbntextsize;if($ne($1.isbntextxoffset,"unset")){$1.isbntextxoffset=+$1.isbntextxoffset}if($ne($1.isbntextyoffset,"unset")){$1.isbntextyoffset=+$1.isbntextyoffset}$1.height=+$1.height;$1.addongap=+$1.addongap;$x($1.barcode," ");if($k[--$j]){$1.barcode=$k[--$j];$j--;$1.addon=$k[--$j]}else{$j--;$1.addon=""}if($1.barcode.length!=15&&$1.barcode.length!=17&&($1.barcode.length!=11&&$1.barcode.length!=13)){$k[$j++]="bwipp.isbnBadLength";$k[$j++]="ISBN-13 must be 15 or 17 characters including dashes. ISBN-10 must be 11 or 13 characters including dashes";bwipp_raiseerror()}if($1.addon.length!=0&&$1.addon.length!=2&&$1.addon.length!=5){$k[$j++]="bwipp.isbnBadAddOnLength";$k[$j++]="Add-on for ISBN must be 2 or 5 digits";bwipp_raiseerror()}if($1.barcode.length>=15){var _Q=$G($1.barcode,0,4);if($ne(_Q,"978-")&&$ne(_Q,"979-")){$k[$j++]="bwipp.isbn13badPrefix";$k[$j++]="ISBN-13 prefix must be 978- or 979-";bwipp_raiseerror()}$1.wasdash=false;$1.numdash=0;$1.numdigit=0;var _S=$G($1.barcode,5,9);for(var _T=0,_U=_S.length;_T<_U;_T++){var _V=$g(_S,_T);$k[$j++]=_V;if(_V==45){if($1.wasdash){$k[$j++]="bwipp.isbn13adjacentDashes";$k[$j++]="ISBN-13 does not permit adjacent dashes";bwipp_raiseerror()}$1.wasdash=true;$1.numdash=$1.numdash+1}var _Y=$k[--$j];if(_Y>=48&&_Y<=57){$1.wasdash=false;$1.numdigit=$1.numdigit+1}}if($1.numdash!=2||$1.numdigit!=7){$k[$j++]="bwipp.isbn13numDashesDigits";$k[$j++]="Incorrect number of dashes and digits for ISBN-13";bwipp_raiseerror()}var _d=$g($1.barcode,14);if(_d<48||_d>57){$k[$j++]="bwipp.isbn13character15";$k[$j++]="ISBN-13 character 15 must be a digit";bwipp_raiseerror()}if($1.barcode.length==17){if($ne($G($1.barcode,15,1),"-")){$k[$j++]="bwipp.isbn13character16";$k[$j++]="ISBN-13 penultimate character must be a dash";bwipp_raiseerror()}var _i=$g($1.barcode,16);if(_i<48||_i>57){$k[$j++]="bwipp.isbn13character17";$k[$j++]="ISBN-13 final character must be a digit";bwipp_raiseerror()}}}else{var _k=$g($1.barcode,0);if(_k<48||_k>57){$k[$j++]="bwipp.isbn10FirstDigit";$k[$j++]="ISBN-10 first character must be a digit";bwipp_raiseerror()}$1.wasdash=false;$1.numdash=0;$1.numdigit=0;var _m=$G($1.barcode,1,9);for(var _n=0,_o=_m.length;_n<_o;_n++){var _p=$g(_m,_n);$k[$j++]=_p;if(_p==45){if($1.wasdash){$k[$j++]="bwipp.isbn10adjacentDashes";$k[$j++]="ISBN-10 does not permit adjacent dashes";bwipp_raiseerror()}$1.wasdash=true;$1.numdash=$1.numdash+1}var _s=$k[--$j];if(_s>=48&&_s<=57){$1.wasdash=false;$1.numdigit=$1.numdigit+1}}if($1.numdash!=2||$1.numdigit!=7){$k[$j++]="bwipp.isbn10numDashesDigits";$k[$j++]="Incorrect number of dashes and digits for ISBN-10";bwipp_raiseerror()}var _x=$g($1.barcode,10);if(_x<48||_x>57){$k[$j++]="bwipp.isbn10character11";$k[$j++]="ISBN-10 character 11 must be a digit";bwipp_raiseerror()}if($1.barcode.length==13){if($ne($G($1.barcode,11,1),"-")){$k[$j++]="bwipp.isbn10character12";$k[$j++]="ISBN-10 penultimate character must be a dash";bwipp_raiseerror()}var _12=$g($1.barcode,12);if((_12<48||_12>57)&&_12!=88){$k[$j++]="bwipp.isbn10character13";$k[$j++]="ISBN-10 final character must be a digit or X";bwipp_raiseerror()}}}$1.isbntxt=$1.barcode;if($1.isbntxt.length<=13){$1.isbn=$s(10);$1.checksum=0;$1.i=0;$1.n=0;for(;;){$1.isbnchar=$f($g($1.isbntxt,$1.i)-48);if($1.isbnchar!=-3){$p($1.isbn,$1.n,$f($1.isbnchar+48));if($1.n<9){$1.checksum=$f($1.checksum+(10-$1.n)*$1.isbnchar)}$1.n=$1.n+1}$1.i=$1.i+1;if($1.i==$1.isbntxt.length){break}}var _1M=$f(11-$1.checksum%11)%11;$k[$j++]="checksum";$k[$j++]=_1M;if(_1M==10){$j--;$k[$j++]=40}var _1N=$k[--$j];$1[$k[--$j]]=$f(_1N+48);$1.isbn=$G($1.isbn,0,9);if($1.isbntxt.length==13){if($g($1.isbntxt,12)!=$1.checksum){$k[$j++]="bwipp.isbn10badCheckDigit";$k[$j++]="Incorrect ISBN-10 check digit provided";bwipp_raiseerror()}}}if(!$1.legacy||$1.isbntxt.length>=15){if($1.isbntxt.length<=13){$1.pad=$s(15);$P($1.pad,0,"978-");$P($1.pad,4,$G($1.isbntxt,0,11));$1.isbntxt=$1.pad}$1.isbn=$s(13);$1.checksum=0;$1.i=0;$1.n=0;for(;;){$1.isbnchar=$g($1.isbntxt,$1.i)-48;if($1.isbnchar!=-3){$p($1.isbn,$1.n,$1.isbnchar+48);if($1.n<12){$k[$j++]="checksum";$k[$j++]=$1.isbnchar;if($1.n%2!=0){var _1p=$k[--$j];$k[$j++]=_1p*3}var _1r=$k[--$j];$1[$k[--$j]]=$f(_1r+$1.checksum)}$1.n=$1.n+1}$1.i=$1.i+1;if($1.i==$1.isbntxt.length){break}}$1.checksum=(10-$1.checksum%10)%10+48;$1.isbn=$G($1.isbn,0,12);if($1.isbntxt.length==17){if($g($1.isbntxt,16)!=$1.checksum){$k[$j++]="bwipp.isbn13badCheckDigit";$k[$j++]="Incorrect ISBN-13 check digit provided";bwipp_raiseerror()}}}var _25=$1.isbn.length==12?22:18;$1.pad=$s(_25);$P($1.pad,0,"ISBN ");$P($1.pad,5,$1.isbntxt);$p($1.pad,$1.pad.length-2,45);$p($1.pad,$1.pad.length-1,$1.checksum);$1.isbntxt=$1.pad;$1.barcode=$s(12);if($1.isbn.length==9){$P($1.barcode,0,"978");$P($1.barcode,3,$1.isbn)}else{$P($1.barcode,0,$1.isbn)}if($ne($1.addon,"")){var _2P=$s(12+$1.addon.length+1);$P(_2P,0,$1.barcode);$P(_2P,12," ");$P(_2P,13,$1.addon);$1.barcode=_2P}$p($1.options,"dontdraw",true);$p($1.options,"addongap",$1.addongap);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_ean13();var _2X=$k[--$j];$1[$k[--$j]]=_2X;if($1.includetext){if($1.isbntextxoffset=="unset"){$k[$j++]="isbntextxoffset";if($1.isbn.length==9){$k[$j++]=-1}else{$k[$j++]=-12}var _2c=$k[--$j];$1[$k[--$j]]=_2c}if($1.isbntextyoffset=="unset"){$1.isbntextyoffset=$f($1.height*72+3)}var _2h=$g($1.args,"txt")!==undefined;if(_2h){$1.txt=$g($1.args,"txt");$1.newtxt=$a($1.txt.length+1);$P($1.newtxt,0,$1.txt);$p($1.newtxt,$1.newtxt.length-1,$a([$1.isbntxt,$1.isbntextxoffset,$1.isbntextyoffset,$1.isbntextfont,$1.isbntextsize]));$p($1.args,"txt",$1.newtxt)}else{$p($1.args,"txt",$a([$a([$1.isbntxt,$1.isbntextxoffset,$1.isbntextyoffset,$1.isbntextfont,$1.isbntextsize])]))}}$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_ismn(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.ismntextfont="OCR-A";$1.ismntextsize=8;$1.ismntextxoffset="unset";$1.ismntextyoffset="unset";$1.height=1;$1.addongap=12;$1.legacy=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.ismntextfont=""+$1.ismntextfont;$1.ismntextsize=+$1.ismntextsize;if($ne($1.ismntextxoffset,"unset")){$1.ismntextxoffset=+$1.ismntextxoffset}if($ne($1.ismntextyoffset,"unset")){$1.ismntextyoffset=+$1.ismntextyoffset}$1.height=+$1.height;$1.addongap=+$1.addongap;$x($1.barcode," ");if($k[--$j]){$1.barcode=$k[--$j];$j--;$1.addon=$k[--$j]}else{$j--;$1.addon=""}if($1.barcode.length!=15&&$1.barcode.length!=17&&($1.barcode.length!=11&&$1.barcode.length!=13)){$k[$j++]="bwipp.ismnBadLength";$k[$j++]="ISMN-13 must be 15 or 17 characters including dashes. ISMN-10 must be 11 or 13 characters including dashes";bwipp_raiseerror()}if($1.addon.length!=0&&$1.addon.length!=2&&$1.addon.length!=5){$k[$j++]="bwipp.ismnBadAddOnLength";$k[$j++]="Add-on for ISMN must be 2 or 5 digits";bwipp_raiseerror()}if($1.barcode.length>=15){if($ne($G($1.barcode,0,4),"979-")){$k[$j++]="bwipp.ismn13badPrefix";$k[$j++]="ISMN-13 prefix must be 979-";bwipp_raiseerror()}$1.wasdash=false;$1.numdash=0;$1.numdigit=0;var _S=$G($1.barcode,5,9);for(var _T=0,_U=_S.length;_T<_U;_T++){var _V=$g(_S,_T);$k[$j++]=_V;if(_V==45){if($1.wasdash){$k[$j++]="bwipp.ismn13adjacentDashes";$k[$j++]="ISMN-13 does not permit adjacent dashes";bwipp_raiseerror()}$1.wasdash=true;$1.numdash=$1.numdash+1}var _Y=$k[--$j];if(_Y>=48&&_Y<=57){$1.wasdash=false;$1.numdigit=$1.numdigit+1}}if($1.numdash!=2||$1.numdigit!=7){$k[$j++]="bwipp.ismn13numDashesDigits";$k[$j++]="Incorrect number of dashes and digits for ISMN-13";bwipp_raiseerror()}var _d=$g($1.barcode,14);if(_d<48||_d>57){$k[$j++]="bwipp.ismn13character15";$k[$j++]="ISMN-13 character 15 must be a digit";bwipp_raiseerror()}if($1.barcode.length==17){if($ne($G($1.barcode,15,1),"-")){$k[$j++]="bwipp.ismn13character16";$k[$j++]="ISMN-13 penultimate character must be a dash";bwipp_raiseerror()}var _i=$g($1.barcode,16);if(_i<48||_i>57){$k[$j++]="bwipp.ismn13character17";$k[$j++]="ISMN-13 final character must be a digit";bwipp_raiseerror()}}}else{if($ne($G($1.barcode,0,2),"M-")){$k[$j++]="bwipp.ismn10badPrefix";$k[$j++]="ISMN-10 prefix must be M-";bwipp_raiseerror()}var _m=$g($1.barcode,2);if(_m<48||_m>57){$k[$j++]="bwipp.ismn10character3";$k[$j++]="ISMN-10 character 3 must be a digit";bwipp_raiseerror()}$1.wasdash=false;$1.numdash=0;$1.numdigit=0;var _o=$G($1.barcode,3,7);for(var _p=0,_q=_o.length;_p<_q;_p++){var _r=$g(_o,_p);$k[$j++]=_r;if(_r==45){if($1.wasdash){$k[$j++]="bwipp.ismn10adjacentDashes";$k[$j++]="ISMN-10 does not permit adjacent dashes";bwipp_raiseerror()}$1.wasdash=true;$1.numdash=$1.numdash+1}var _u=$k[--$j];if(_u>=48&&_u<=57){$1.wasdash=false;$1.numdigit=$1.numdigit+1}}if($1.numdash!=1||$1.numdigit!=6){$k[$j++]="bwipp.ismn10numDashesDigits";$k[$j++]="Incorrect number of dashes and digits for ISMN-10";bwipp_raiseerror()}var _z=$g($1.barcode,10);if(_z<48||_z>57){$k[$j++]="bwipp.ismn10character11";$k[$j++]="ISMN-10 character 11 must be a digit";bwipp_raiseerror()}if($1.barcode.length==13){if($ne($G($1.barcode,11,1),"-")){$k[$j++]="bwipp.ismn10character12";$k[$j++]="ISMN-10 penultimate character must be a dash";bwipp_raiseerror()}var _14=$g($1.barcode,12);if((_14<48||_14>57)&&_14!=88){$k[$j++]="bwipp.ismn10character13";$k[$j++]="ISMN-10 final character must be a digit or X";bwipp_raiseerror()}}}$1.ismntxt=$1.barcode;$1.legacytxt="";if($1.ismntxt.length<=13){$1.legacytxt=$1.ismntxt;$1.pad=$s($1.ismntxt.length+4);$P($1.pad,0,"979-0-");$P($1.pad,6,$G($1.ismntxt,2,$1.ismntxt.length-2));$1.ismntxt=$1.pad}$1.ismn=$s(13);$1.checksum=0;$1.i=0;$1.n=0;for(;;){$1.ismnchar=$g($1.ismntxt,$1.i)-48;if($1.ismnchar!=-3){$p($1.ismn,$1.n,$1.ismnchar+48);if($1.n<12){if($1.n%2==0){$1.checksum=$1.ismnchar+$1.checksum}else{$1.checksum=$1.ismnchar*3+$1.checksum}}$1.n=$1.n+1}$1.i=$1.i+1;if($1.i==$1.ismntxt.length){break}}$1.checksum=(10-$1.checksum%10)%10+48;if($1.barcode.length==13||$1.barcode.length==17){var _1b=$1.barcode;if($g(_1b,_1b.length-1)!=$1.checksum){$k[$j++]="bwipp.ismnBadCheckDigit";$k[$j++]="Incorrect ISMN check digit provided";bwipp_raiseerror()}}if($1.legacy&&$ne($1.legacytxt,"")){$1.ismntxt=$1.legacytxt;$1.pad=$s(18)}else{$1.pad=$s(22)}$P($1.pad,0,"ISMN ");$P($1.pad,5,$1.ismntxt);$p($1.pad,$1.pad.length-2,45);$p($1.pad,$1.pad.length-1,$1.checksum);$1.ismntxt=$1.pad;$1.barcode=$G($1.ismn,0,12);if($ne($1.addon,"")){var _1w=$s(12+$1.addon.length+1);$P(_1w,0,$1.barcode);$P(_1w,12," ");$P(_1w,13,$1.addon);$1.barcode=_1w}$p($1.options,"dontdraw",true);$p($1.options,"addongap",$1.addongap);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_ean13();var _24=$k[--$j];$1[$k[--$j]]=_24;if($1.includetext){if($1.ismntextxoffset=="unset"){$k[$j++]="ismntextxoffset";if($1.ismntxt.length==18){$k[$j++]=-1}else{$k[$j++]=-12}var _29=$k[--$j];$1[$k[--$j]]=_29}if($1.ismntextyoffset=="unset"){$1.ismntextyoffset=$f($1.height*72+3)}var _2E=$g($1.args,"txt")!==undefined;if(_2E){$1.txt=$g($1.args,"txt");$1.newtxt=$a($1.txt.length+1);$P($1.newtxt,0,$1.txt);$p($1.newtxt,$1.newtxt.length-1,$a([$1.ismntxt,$1.ismntextxoffset,$1.ismntextyoffset,$1.ismntextfont,$1.ismntextsize]));$p($1.args,"txt",$1.newtxt)}else{$p($1.args,"txt",$a([$a([$1.ismntxt,$1.ismntextxoffset,$1.ismntextyoffset,$1.ismntextfont,$1.ismntextsize])]))}}$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_issn(){var $1={};$1.options=$k[--$j];$1.issntxt=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.issntextfont="OCR-A";$1.issntextsize=8;$1.issntextxoffset="unset";$1.issntextyoffset="unset";$1.height=1;$1.addongap=12;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.issntextfont=""+$1.issntextfont;$1.issntextsize=+$1.issntextsize;if($ne($1.issntextxoffset,"unset")){$1.issntextxoffset=+$1.issntextxoffset}if($ne($1.issntextyoffset,"unset")){$1.issntextyoffset=+$1.issntextyoffset}$1.height=+$1.height;$1.addongap=+$1.addongap;$x($1.issntxt," ");if($k[--$j]){$1.issntxt=$k[--$j];$j--;$1.seqvar=$k[--$j]}else{$j--;$1.seqvar="00"}$x($1.seqvar," ");if($k[--$j]){$1.seqvar=$k[--$j];$j--;$1.addon=$k[--$j]}else{$j--;$1.addon=""}if($1.issntxt.length!=8&&$1.issntxt.length!=9){$k[$j++]="bwipp.issnBadLength";$k[$j++]="ISSN must be 8 or 9 characters including dash, in the format XXXX-XXXX";bwipp_raiseerror()}var _O=$G($1.issntxt,0,4);for(var _P=0,_Q=_O.length;_P<_Q;_P++){var _R=$g(_O,_P);if(_R<48||_R>57){$k[$j++]="bwipp.issnFirstThroughFourthNotNumeric";$k[$j++]="ISSN first four characters must be numeral characters";bwipp_raiseerror()}}if($ne($G($1.issntxt,4,1),"-")){$k[$j++]="bwipp.issnNeedsDash";$k[$j++]="ISSN fifth character must be a dash";bwipp_raiseerror()}var _V=$G($1.issntxt,5,3);for(var _W=0,_X=_V.length;_W<_X;_W++){var _Y=$g(_V,_W);if(_Y<48||_Y>57){$k[$j++]="bwipp.issnSixthThroughEighthNotNumeric";$k[$j++]="ISSN sixth through eighth characters must be numerals";bwipp_raiseerror()}}if($1.issntxt.length==9){var _b=$g($1.issntxt,8);if((_b<48||_b>57)&&_b!=88){$k[$j++]="bwipp.issnNinthCharacterBadFormat";$k[$j++]="ISSN ninth character must be a number or the character X";bwipp_raiseerror()}}if($1.seqvar.length!=2){$k[$j++]="bwipp.issnBadSequenceVariantLength";$k[$j++]="Sequence variant for ISSN must be 2 digits";bwipp_raiseerror()}$F($1.seqvar,function(){var _e=$k[--$j];if(_e<48||_e>57){$k[$j++]="bwipp.issnSequenceVariantBadCharacter";$k[$j++]="Sequence variant for ISSN must contain only digits";bwipp_raiseerror()}});if($1.addon.length!=0&&$1.addon.length!=2&&$1.addon.length!=5){$k[$j++]="bwipp.issnBadAddOnLength";$k[$j++]="Add-on for ISSN must be 2 or 5 digits";bwipp_raiseerror()}$1.issn=$s(8);$1.checksum=0;$1.i=0;$1.n=0;for(;;){$1.issnchar=$f($g($1.issntxt,$1.i)-48);if($1.issnchar!=-3){$p($1.issn,$1.n,$f($1.issnchar+48));if($1.n<7){$1.checksum=$f($1.checksum+$1.issnchar*(8-$1.n))}$1.n=$1.n+1}$1.i=$1.i+1;if($1.i==$1.issntxt.length){break}}$1.checksum=$f(11-$1.checksum%11)%11;var _10=$f($1.checksum+48);$k[$j++]="checksum";$k[$j++]=_10;if(_10==58){$j--;$k[$j++]=88}var _11=$k[--$j];$1[$k[--$j]]=_11;if($1.issntxt.length==9){if($g($1.issntxt,8)!=$1.checksum){$k[$j++]="bwipp.issnBadCheckDigit";$k[$j++]="Incorrect ISSN check digit provided";bwipp_raiseerror()}}$1.pad=$s(14);$P($1.pad,0,"ISSN ");$P($1.pad,5,$1.issntxt);$p($1.pad,13,$1.checksum);$1.issntxt=$1.pad;$1.barcode=$G($1.issn,0,7);$1.barcode=$s(12);$P($1.barcode,0,"977");$P($1.barcode,3,$1.issn);$P($1.barcode,10,$1.seqvar);if($ne($1.addon,"")){var _1O=$s(12+$1.addon.length+1);$P(_1O,0,$1.barcode);$P(_1O,12," ");$P(_1O,13,$1.addon);$1.barcode=_1O}$p($1.options,"dontdraw",true);$p($1.options,"addongap",$1.addongap);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_ean13();var _1W=$k[--$j];$1[$k[--$j]]=_1W;if($1.includetext){if($1.issntextxoffset=="unset"){$1.issntextxoffset=10}if($1.issntextyoffset=="unset"){$1.issntextyoffset=$f($1.height*72+3)}var _1d=$g($1.args,"txt")!==undefined;if(_1d){$1.txt=$g($1.args,"txt");$1.newtxt=$a($1.txt.length+1);$P($1.newtxt,0,$1.txt);$p($1.newtxt,$1.newtxt.length-1,$a([$1.issntxt,$1.issntextxoffset,$1.issntextyoffset,$1.issntextfont,$1.issntextsize]));$p($1.args,"txt",$1.newtxt)}else{$p($1.args,"txt",$a([$a([$1.issntxt,$1.issntextxoffset,$1.issntextyoffset,$1.issntextfont,$1.issntextsize])]))}}$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_code128(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=-8;$1.height=1;$1.encoding="auto";$1.raw=false;$1.parse=false;$1.parsefnc=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.sta=-1;$1.stb=-2;$1.stc=-3;$1.swa=-4;$1.swb=-5;$1.swc=-6;$1.fn1=-7;$1.fn2=-8;$1.fn3=-9;$1.fn4=-10;$1.sft=-11;$1.stp=-12;$1.lka=-13;$1.lkc=-14;var _I=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["FNC1",$1.fn1],["FNC1",$1.fn1],["FNC2",$1.fn2],["FNC3",$1.fn3],["LNKA",$1.lka],["LNKC",$1.lkc]]);$1.fncvals=_I;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _L=$k[--$j];$1[$k[--$j]]=_L;$1.msglen=$1.msg.length;$1.charmaps=$a([$a([32,32,"00"]),$a(["!","!","01"]),$a(['"','"',"02"]),$a(["#","#","03"]),$a(["$","$","04"]),$a(["%","%","05"]),$a(["&","&","06"]),$a(["'","'","07"]),$a([40,40,"08"]),$a([41,41,"09"]),$a(["*","*","10"]),$a(["+","+","11"]),$a([",",",","12"]),$a(["-","-","13"]),$a([".",".","14"]),$a(["/","/","15"]),$a(["0","0","16"]),$a(["1","1","17"]),$a(["2","2","18"]),$a(["3","3","19"]),$a(["4","4","20"]),$a(["5","5","21"]),$a(["6","6","22"]),$a(["7","7","23"]),$a(["8","8","24"]),$a(["9","9","25"]),$a([":",":","26"]),$a([";",";","27"]),$a(["<","<","28"]),$a(["=","=","29"]),$a([">",">","30"]),$a(["?","?","31"]),$a(["@","@","32"]),$a(["A","A","33"]),$a(["B","B","34"]),$a(["C","C","35"]),$a(["D","D","36"]),$a(["E","E","37"]),$a(["F","F","38"]),$a(["G","G","39"]),$a(["H","H","40"]),$a(["I","I","41"]),$a(["J","J","42"]),$a(["K","K","43"]),$a(["L","L","44"]),$a(["M","M","45"]),$a(["N","N","46"]),$a(["O","O","47"]),$a(["P","P","48"]),$a(["Q","Q","49"]),$a(["R","R","50"]),$a(["S","S","51"]),$a(["T","T","52"]),$a(["U","U","53"]),$a(["V","V","54"]),$a(["W","W","55"]),$a(["X","X","56"]),$a(["Y","Y","57"]),$a(["Z","Z","58"]),$a(["[","[","59"]),$a([92,92,"60"]),$a(["]","]","61"]),$a(["^","^","62"]),$a(["_","_","63"]),$a([0,"`","64"]),$a([1,"a","65"]),$a([2,"b","66"]),$a([3,"c","67"]),$a([4,"d","68"]),$a([5,"e","69"]),$a([6,"f","70"]),$a([7,"g","71"]),$a([8,"h","72"]),$a([9,"i","73"]),$a([10,"j","74"]),$a([11,"k","75"]),$a([12,"l","76"]),$a([13,"m","77"]),$a([14,"n","78"]),$a([15,"o","79"]),$a([16,"p","80"]),$a([17,"q","81"]),$a([18,"r","82"]),$a([19,"s","83"]),$a([20,"t","84"]),$a([21,"u","85"]),$a([22,"v","86"]),$a([23,"w","87"]),$a([24,"x","88"]),$a([25,"y","89"]),$a([26,"z","90"]),$a([27,"{","91"]),$a([28,"|","92"]),$a([29,"}","93"]),$a([30,"~","94"]),$a([31,127,"95"]),$a([$1.fn3,$1.fn3,"96"]),$a([$1.fn2,$1.fn2,"97"]),$a([$1.sft,$1.sft,"98"]),$a([$1.swc,$1.swc,"99"]),$a([$1.swb,$1.fn4,$1.swb]),$a([$1.fn4,$1.swa,$1.swa]),$a([$1.fn1,$1.fn1,$1.fn1]),$a([$1.sta,$1.sta,$1.sta]),$a([$1.stb,$1.stb,$1.stb]),$a([$1.stc,$1.stc,$1.stc]),$a([$1.stp,$1.stp,$1.stp])]);$1.charvals=$a([new Map,new Map,new Map]);for(var _2e=0,_2d=$1.charmaps.length-1;_2e<=_2d;_2e+=1){$1.i=_2e;$1.encs=$g($1.charmaps,$1.i);for(var _2i=0;_2i<=2;_2i+=1){$1.j=_2i;var _2l=$g($1.encs,$1.j);$k[$j++]=_2l;if($eq($t(_2l),"stringtype")){var _2o=$g($k[--$j],0);$k[$j++]=_2o}$p($g($1.charvals,$1.j),$k[--$j],$1.i)}}$1.seta=$g($1.charvals,0);$1.setb=$g($1.charvals,1);$1.setc=$g($1.charvals,2);$p($1.seta,$1.lka,$g($1.seta,$1.swb));$p($1.seta,$1.lkc,$g($1.seta,$1.swc));$p($1.setb,$1.lka,$g($1.setb,$1.swc));$p($1.setb,$1.lkc,$g($1.setb,$1.swa));$p($1.setc,$1.lka,$g($1.setc,$1.swa));$p($1.setc,$1.lkc,$g($1.setc,$1.swb));if($1.raw){$1.encoding="raw"}if($eq($1.encoding,"raw")){$1.cws=$a($1.barcode.length);$1.i=0;$1.j=0;for(;;){if($1.i==$1.barcode.length){break}$1.cw=~~$z($G($1.barcode,$1.i+1,3));$p($1.cws,$1.j,$1.cw);$1.i=$1.i+4;$1.j=$1.j+1}$1.cws=$G($1.cws,0,$1.j);$1.text=""}if($eq($1.encoding,"auto")){$1.text=$s($1.msglen);for(var _3q=0,_3p=$1.msglen-1;_3q<=_3p;_3q+=1){$1.i=_3q;var _3v=$g($1.msg,$1.i);$k[$j++]=$1.text;$k[$j++]=$1.i;$k[$j++]=_3v;if(_3v<0){$j--;$k[$j++]=32}var _3w=$k[--$j];var _3x=$k[--$j];$p($k[--$j],_3x,_3w)}$k[$j++]=Infinity;for(var _40=0,_41=$1.msglen;_40<_41;_40++){$k[$j++]=0}$k[$j++]=0;$1.numSA=$a();$k[$j++]=Infinity;for(var _44=0,_45=$1.msglen;_44<_45;_44++){$k[$j++]=0}$k[$j++]=0;$1.numEA=$a();for(var _48=$1.msglen-1;_48>=0;_48-=1){$1.i=_48;if($g($1.msg,$1.i)>=0){if($g($1.msg,$1.i)>=128){$p($1.numEA,$1.i,$f($g($1.numEA,$1.i+1)+1))}else{$p($1.numSA,$1.i,$f($g($1.numSA,$1.i+1)+1))}}}$1.ea=false;$1.msgtmp=$a([]);for(var _4S=0,_4R=$1.msglen-1;_4S<=_4R;_4S+=1){$1.i=_4S;$1.c=$g($1.msg,$1.i);if(!$xo($1.ea,$1.c<128)&&$1.c>=0){if($1.ea){$k[$j++]=$1.numSA}else{$k[$j++]=$1.numEA}var _4e=$g($k[--$j],$1.i);var _4h=$f(_4e+$1.i)==$1.msglen?3:5;if(_4e<_4h){$k[$j++]=Infinity;$q($1.msgtmp);$k[$j++]=$1.fn4;$1.msgtmp=$a()}else{$k[$j++]=Infinity;$q($1.msgtmp);$k[$j++]=$1.fn4;$k[$j++]=$1.fn4;$1.msgtmp=$a();$1.ea=!$1.ea}}$k[$j++]=Infinity;$q($1.msgtmp);if($1.c>=0){$k[$j++]=$1.c&127}else{$k[$j++]=$1.c}$1.msgtmp=$a()}$1.msg=$1.msgtmp;$1.msglen=$1.msg.length;$1.numsscr=function(){$1.n=0;$1.s=0;$1.p=$k[--$j];for(;;){if($1.p>=$1.msglen){break}var _52=$g($1.msg,$1.p);var _54=$g($1.setc,_52)!==undefined;$k[$j++]=_52;if(!_54){$j--;break}var _55=$k[--$j];$k[$j++]=_55;if(_55<=-1){var _57=$k[--$j];if(_57==$1.fn1&&$1.s%2==0){$1.s=$1.s+1}else{break}}else{$j--}$1.n=$1.n+1;$1.s=$1.s+1;$1.p=$f($1.p+1)}$k[$j++]=$1.n;$k[$j++]=$1.s};$1.enca=function(){$p($1.cws,$1.j,$g($1.seta,$k[--$j]));$1.j=$1.j+1};$1.encb=function(){$p($1.cws,$1.j,$g($1.setb,$k[--$j]));$1.j=$1.j+1};$1.encc=function(){var _5R=$k[--$j];$k[$j++]=_5R;if($ne($t(_5R),"arraytype")){var _5V=$g($1.setc,$k[--$j]);$k[$j++]=_5V}else{$q($k[--$j]);var _5X=$k[--$j];var _5Y=$k[--$j];$k[$j++]=$f($f(_5X-48)+$f(_5Y-48)*10)}$p($1.cws,$1.j,$k[--$j]);$1.j=$1.j+1};$1.anotb=function(){var _5d=$k[--$j];var _5f=$g($1.seta,_5d)!==undefined;var _5h=$g($1.setb,_5d)!==undefined;$k[$j++]=_5f&&!_5h};$1.bnota=function(){var _5i=$k[--$j];var _5k=$g($1.setb,_5i)!==undefined;var _5m=$g($1.seta,_5i)!==undefined;$k[$j++]=_5k&&!_5m};$k[$j++]=Infinity;for(var _5o=0,_5p=$1.msg.length;_5o<_5p;_5o++){$k[$j++]=0}$k[$j++]=9999;$1.nextanotb=$a();$k[$j++]=Infinity;for(var _5s=0,_5t=$1.msg.length;_5s<_5t;_5s++){$k[$j++]=0}$k[$j++]=9999;$1.nextbnota=$a();for(var _5w=$1.msg.length-1;_5w>=0;_5w-=1){$1.i=_5w;$k[$j++]=$g($1.msg,$1.i);$1.anotb();if($k[--$j]){$p($1.nextanotb,$1.i,0)}else{$p($1.nextanotb,$1.i,$f($g($1.nextanotb,$1.i+1)+1))}$k[$j++]=$g($1.msg,$1.i);$1.bnota();if($k[--$j]){$p($1.nextbnota,$1.i,0)}else{$p($1.nextbnota,$1.i,$f($g($1.nextbnota,$1.i+1)+1))}}$1.abeforeb=function(){var _6J=$k[--$j];$k[$j++]=$lt($g($1.nextanotb,_6J),$g($1.nextbnota,_6J))};$1.bbeforea=function(){var _6O=$k[--$j];$k[$j++]=$lt($g($1.nextbnota,_6O),$g($1.nextanotb,_6O))};$1.cws=$a($1.barcode.length*2+3);$1.j=0;if($1.msglen>0){$k[$j++]=0;$1.numsscr()}else{$k[$j++]=-1;$k[$j++]=-1}$1.nums=$k[--$j];$1.nchars=$k[--$j];for(;;){if($1.msglen==0){$k[$j++]=$1.stb;$1.enca();$1.cset="setb";break}if($1.msglen==2&&$1.nums==2){$k[$j++]=$1.stc;$1.enca();$1.cset="setc";break}if($1.nums>=4){$k[$j++]=$1.stc;$1.enca();$1.cset="setc";break}$k[$j++]=0;$1.abeforeb();if($k[--$j]){$k[$j++]=$1.sta;$1.enca();$1.cset="seta";break}$k[$j++]=$1.stb;$1.enca();$1.cset="setb";break}$1.i=0;for(;;){if($1.i==$1.msglen){break}$k[$j++]=$1.i;$1.numsscr();$1.nums=$k[--$j];$1.nchars=$k[--$j];for(;;){if(($eq($1.cset,"seta")||$eq($1.cset,"setb"))&&$1.nums>=4&&$g($1.msg,$1.i)!=$1.fn1){if($1.nums%2==0){$k[$j++]=$1.swc;if($eq($1.cset,"seta")){$1.enca()}else{$1.encb()}$1.cset="setc";break}else{$k[$j++]=$g($1.msg,$1.i);if($eq($1.cset,"seta")){$1.enca()}else{$1.encb()}$1.i=$1.i+1;$k[$j++]=$1.swc;if($eq($1.cset,"seta")){$1.enca()}else{$1.encb()}$1.cset="setc";break}}$k[$j++]=$eq($1.cset,"setb");$k[$j++]=$g($1.msg,$1.i);$1.anotb();var _78=$k[--$j];var _79=$k[--$j];if(_79&&_78){if($1.i<$1.msglen-1){$k[$j++]=$1.i+1;$1.bbeforea();if($k[--$j]){$k[$j++]=$1.sft;$1.encb();$k[$j++]=$g($1.msg,$1.i);$1.enca();$1.i=$1.i+1;break}}$k[$j++]=$1.swa;$1.encb();$1.cset="seta";break}$k[$j++]=$eq($1.cset,"seta");$k[$j++]=$g($1.msg,$1.i);$1.bnota();var _7O=$k[--$j];var _7P=$k[--$j];if(_7P&&_7O){if($1.i<$1.msglen-1){$k[$j++]=$1.i+1;$1.abeforeb();if($k[--$j]){$k[$j++]=$1.sft;$1.enca();$k[$j++]=$g($1.msg,$1.i);$1.encb();$1.i=$1.i+1;break}}$k[$j++]=$1.swb;$1.enca();$1.cset="setb";break}if($eq($1.cset,"setc")&&$1.nums<2&&$g($1.msg,$1.i)>-1){$k[$j++]=$1.i;$1.abeforeb();if($k[--$j]){$k[$j++]=$1.swa;$1.encc();$1.cset="seta";break}$k[$j++]=$1.swb;$1.encc();$1.cset="setb";break}if($eq($1.cset,"seta")){$k[$j++]=$g($1.msg,$1.i);$1.enca();$1.i=$1.i+1;break}if($eq($1.cset,"setb")){$k[$j++]=$g($1.msg,$1.i);$1.encb();$1.i=$1.i+1;break}if($eq($1.cset,"setc")){if($g($1.msg,$1.i)<=-1){$k[$j++]=$g($1.msg,$1.i);$1.encc();$1.i=$1.i+1}else{$k[$j++]=$G($1.msg,$1.i,2);$1.encc();$1.i=$1.i+2}break}break}}$1.cws=$G($1.cws,0,$1.j)}var _89=$a($1.j+2);$P(_89,0,$1.cws);$1.cws=_89;$1.csum=$g($1.cws,0);for(var _8F=1,_8E=$1.j-1;_8F<=_8E;_8F+=1){$1.i=_8F;$1.csum=$f($1.csum+$g($1.cws,$1.i)*$1.i)}$1.csum=$1.csum%103;$p($1.cws,$1.j,$1.csum);$p($1.cws,$1.j+1,$g($1.seta,$1.stp));$1.encs=$a(["212222","222122","222221","121223","121322","131222","122213","122312","132212","221213","221312","231212","112232","122132","122231","113222","123122","123221","223211","221132","221231","213212","223112","312131","311222","321122","321221","312212","322112","322211","212123","212321","232121","111323","131123","131321","112313","132113","132311","211313","231113","231311","112133","112331","132131","113123","113321","133121","313121","211331","231131","213113","213311","213131","311123","311321","331121","312113","312311","332111","314111","221411","431111","111224","111422","121124","121421","141122","141221","112214","112412","122114","122411","142112","142211","241211","221114","413111","241112","134111","111242","121142","121241","114212","124112","124211","411212","421112","421211","212141","214121","412121","111143","111341","131141","114113","114311","411113","411311","113141","114131","311141","411131","211412","211214","211232","2331112"]);$1.sbs=$s($1.cws.length*6+1);for(var _8Z=0,_8Y=$1.cws.length-1;_8Z<=_8Y;_8Z+=1){$1.i=_8Z;$P($1.sbs,$1.i*6,$g($1.encs,$g($1.cws,$1.i)))}$k[$j++]=Infinity;$k[$j++]=Infinity;var _8h=$1.sbs;for(var _8i=0,_8j=_8h.length;_8i<_8j;_8i++){$k[$j++]=$g(_8h,_8i)-48}var _8l=$a();$k[$j++]=Infinity;for(var _8n=0,_8o=~~(($1.sbs.length+1)/2);_8n<_8o;_8n++){$k[$j++]=$1.height}var _8q=$a();$k[$j++]=Infinity;for(var _8s=0,_8t=~~(($1.sbs.length+1)/2);_8s<_8t;_8s++){$k[$j++]=0}var _8u=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_8l;$k[$j++]="bhs";$k[$j++]=_8q;$k[$j++]="bbs";$k[$j++]=_8u;$k[$j++]="txt";$k[$j++]=$a([$a([$1.text,$1.textxoffset,$1.textyoffset,$1.textfont,$1.textsize])]);$k[$j++]="textxalign";$k[$j++]="center";$k[$j++]="opt";$k[$j++]=$1.options;var _93=$d();$k[$j++]=_93;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_gs1_128(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=-8;$1.height=.5;$1.linkagea=false;$1.linkagec=false;$1.parse=false;$1.dontlint=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.text=$1.barcode;$1.expand=function(){var _C=new Map([["parse",$1.parse],["parseonly",true],["parsefnc",false]]);$1.fncvals=_C;$k[$j++]=$1.fncvals;bwipp_parseinput()};$1.ais=$a([]);$1.vals=$a([]);var _G=$1.barcode;$k[$j++]=$G(_G,1,_G.length-1);for(;;){var _I=$k[--$j];$k[$j++]=_I;if($eq(_I,"")){break}$x($k[--$j],")");$j--;var _K=$k[--$j];var _L=$k[--$j];$k[$j++]=_K;$k[$j++]=_L;$j--;var _M=$k[--$j];var _N=$k[--$j];$k[$j++]=_M;$x(_N,"(");if($k[--$j]){var _P=$k[--$j];var _Q=$k[--$j];$k[$j++]=_P;$k[$j++]=_Q;$j--;var _R=$k[--$j];var _S=$k[--$j];var _T=$k[--$j];$k[$j++]=_S;$k[$j++]=_T;$k[$j++]=_R}else{var _U=$k[--$j];var _V=$k[--$j];$k[$j++]="";$k[$j++]=_V;$k[$j++]=_U}$k[$j++]=Infinity;$q($1.ais);var _Y=$k[$j-1-($m()+2)];$k[$j++]=_Y;$1.ais=$a();$k[$j++]=Infinity;$q($1.vals);var _c=$k[$j-1-($m()+1)];$k[$j++]=_c;$1.expand();$1.vals=$a();$j-=2}$j--;if(!$1.dontlint){var _g=$1.vals;$k[$j++]=$1.ais;$k[$j++]=_g;bwipp_gs1lint();$j--}$1.aifixed=new Map;$k[$j++]=Infinity;for(var _h=0;_h<=4;_h+=1){$k[$j++]=_h}var _i=$a();for(var _j=0,_k=_i.length;_j<_k;_j++){var _n=$Z($s(2),"00");$p(_n,1,$f($g(_i,_j)+48));$p($1.aifixed,_n,_n)}$k[$j++]=Infinity;for(var _p=11;_p<=20;_p+=1){$k[$j++]=_p}$k[$j++]=23;for(var _q=31;_q<=36;_q+=1){$k[$j++]=_q}$k[$j++]=41;var _r=$a();for(var _s=0,_t=_r.length;_s<_t;_s++){var _w=$R($s(2),$g(_r,_s),10);$p($1.aifixed,_w,_w)}$1.fnc1=-1;$1.c128=$a([$1.fnc1]);for(var _12=0,_11=$1.ais.length-1;_12<=_11;_12+=1){$1.i=_12;$1.ai=$g($1.ais,$1.i);$1.val=$g($1.vals,$1.i);var _1C=$a($1.c128.length+$1.ai.length+$1.val.length);$P(_1C,0,$1.c128);$k[$j++]=_1C;$k[$j++]=_1C;$k[$j++]=$1.c128.length;$k[$j++]=$1.ai;$k[$j++]=Infinity;var _1G=$k[--$j];var _1H=$k[--$j];$k[$j++]=_1G;$F(_1H);var _1I=$a();var _1J=$k[--$j];$P($k[--$j],_1J,_1I);var _1L=$k[--$j];$k[$j++]=_1L;$k[$j++]=_1L;$k[$j++]=$1.c128.length+$1.ai.length;$k[$j++]=$1.val;$k[$j++]=Infinity;var _1P=$k[--$j];var _1Q=$k[--$j];$k[$j++]=_1P;$F(_1Q);var _1R=$a();var _1S=$k[--$j];$P($k[--$j],_1S,_1R);$1.c128=$k[--$j];var _1a=$g($1.aifixed,$G($1.ai,0,2))!==undefined;if($1.i!=$1.ais.length-1&&!_1a){var _1c=$a($1.c128.length+1);$P(_1c,0,$1.c128);$p(_1c,$1.c128.length,$1.fnc1);$1.c128=_1c}}$1.barcode=$s(($1.c128.length+1)*5);$1.i=0;$1.j=0;for(;;){if($1.i==$1.c128.length){break}var _1m=$g($1.c128,$1.i);$k[$j++]=_1m;if(_1m==$1.fnc1){$j--;$P($1.barcode,$1.j,"^FNC1");$1.j=$1.j+4}else{$p($1.barcode,$1.j,$k[--$j])}$1.i=$1.i+1;$1.j=$1.j+1}if($1.linkagea||$1.linkagec){$k[$j++]=$1.barcode;$k[$j++]=$1.j;if($1.linkagea){$k[$j++]="^LNKA"}else{$k[$j++]="^LNKC"}var _21=$k[--$j];var _22=$k[--$j];$P($k[--$j],_22,_21);$1.j=$1.j+5}$1.barcode=$G($1.barcode,0,$1.j);delete $1.options["parse"];$p($1.options,"height",$1.height);$p($1.options,"dontdraw",true);$p($1.options,"parsefnc",true);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_code128();var _2F=$k[--$j];$1[$k[--$j]]=_2F;$p($1.args,"txt",$a([$a([$1.text,$1.textxoffset,$1.textyoffset,$1.textfont,$1.textsize])]));$p($1.args,"textxalign","center");$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_ean14(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.text=$1.barcode;var _B=$1.barcode;$k[$j++]=$s(_B.length);$k[$j++]=0;$F(_B,function(){var _D=$k[--$j];$k[$j++]=_D;if(_D!=32){var _E=$k[--$j];var _F=$k[--$j];var _G=$k[--$j];$p(_G,_F,_E);$k[$j++]=_G;$k[$j++]=$f(_F+1)}else{$j--}});var _H=$k[--$j];$1.barcode=$G($k[--$j],0,_H);$1.hasspace=$1.text.length!=$1.barcode.length;if($ne($G($1.barcode,0,4),"(01)")){$k[$j++]="bwipp.ean14badAI";$k[$j++]="GS1-14 must begin with (01) application identifier";bwipp_raiseerror()}if($1.barcode.length!=17&&$1.barcode.length!=18){$k[$j++]="bwipp.ean14badLength";$k[$j++]="GS1-14 must be 13 or 14 digits";bwipp_raiseerror()}var _S=$G($1.barcode,4,$1.barcode.length-4);for(var _T=0,_U=_S.length;_T<_U;_T++){var _V=$g(_S,_T);if(_V<48||_V>57){$k[$j++]="bwipp.ean14badCharacter";$k[$j++]="GS1-14 must contain only digits";bwipp_raiseerror()}}$1.checksum=0;for(var _W=0;_W<=12;_W+=1){$1.i=_W;$k[$j++]="checksum";$k[$j++]=$1.checksum;$k[$j++]=$f($g($1.barcode,$1.i+4)-48);if($1.i%2==0){var _c=$k[--$j];$k[$j++]=_c*3}var _d=$k[--$j];var _e=$k[--$j];$1[$k[--$j]]=$f(_e+_d)}$1.checksum=(10-$1.checksum%10)%10;if($1.barcode.length==18){if($g($1.barcode,17)!=$1.checksum+48){$k[$j++]="bwipp.ean14badCheckDigit";$k[$j++]="Incorrect GS1-14 check digit provided";bwipp_raiseerror()}}else{var _l=$s(18);$P(_l,0,$1.barcode);$p(_l,17,$1.checksum+48);$1.barcode=_l;var _q=$1.hasspace?2:1;var _r=$s($1.text.length+_q);$P(_r,_r.length-2," ");$p(_r,_r.length-1,$1.checksum+48);$P(_r,0,$1.text);$1.text=_r}$1.gtin=$G($1.barcode,4,14);$1.barcode=$s(21);$P($1.barcode,0,"^FNC101");$P($1.barcode,7,$1.gtin);$p($1.options,"dontdraw",true);$p($1.options,"parsefnc",true);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_code128();var _14=$k[--$j];$1[$k[--$j]]=_14;$p($1.args,"txt",$a([$a([$1.text,$1.textxoffset,$1.textyoffset,$1.textfont,$1.textsize])]));$p($1.args,"textxalign","center");$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_sscc18(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.text=$1.barcode;var _B=$1.barcode;$k[$j++]=$s(_B.length);$k[$j++]=0;$F(_B,function(){var _D=$k[--$j];$k[$j++]=_D;if(_D!=32){var _E=$k[--$j];var _F=$k[--$j];var _G=$k[--$j];$p(_G,_F,_E);$k[$j++]=_G;$k[$j++]=$f(_F+1)}else{$j--}});var _H=$k[--$j];$1.barcode=$G($k[--$j],0,_H);$1.hasspace=$1.text.length!=$1.barcode.length;if($ne($G($1.barcode,0,4),"(00)")){$k[$j++]="bwipp.sscc18badAI";$k[$j++]="SSCC-18 must begin with (00) application identifier";bwipp_raiseerror()}if($1.barcode.length!=21&&$1.barcode.length!=22){$k[$j++]="bwipp.sscc18badLength";$k[$j++]="SSCC-18 must be 17 or 18 digits";bwipp_raiseerror()}var _S=$G($1.barcode,4,$1.barcode.length-4);for(var _T=0,_U=_S.length;_T<_U;_T++){var _V=$g(_S,_T);if(_V<48||_V>57){$k[$j++]="bwipp.sscc18badCharacter";$k[$j++]="SSCC-18 must contain only digits";bwipp_raiseerror()}}$1.checksum=0;for(var _W=0;_W<=16;_W+=1){$1.i=_W;$k[$j++]="checksum";$k[$j++]=$1.checksum;$k[$j++]=$f($g($1.barcode,$1.i+4)-48);if($1.i%2==0){var _c=$k[--$j];$k[$j++]=_c*3}var _d=$k[--$j];var _e=$k[--$j];$1[$k[--$j]]=$f(_e+_d)}$1.checksum=(10-$1.checksum%10)%10;if($1.barcode.length==22){if($g($1.barcode,21)!=$1.checksum+48){$k[$j++]="bwipp.sscc18badCheckDigit";$k[$j++]="Incorrect SSCC-18 check digit provided";bwipp_raiseerror()}}else{var _l=$s(22);$P(_l,0,$1.barcode);$p(_l,21,$1.checksum+48);$1.barcode=_l;var _q=$1.hasspace?2:1;var _r=$s($1.text.length+_q);$P(_r,_r.length-2," ");$p(_r,_r.length-1,$1.checksum+48);$P(_r,0,$1.text);$1.text=_r}$1.sscc=$G($1.barcode,4,18);$1.barcode=$s(25);$P($1.barcode,0,"^FNC100");$P($1.barcode,7,$1.sscc);$p($1.options,"dontdraw",true);$p($1.options,"parsefnc",true);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_code128();var _14=$k[--$j];$1[$k[--$j]]=_14;$p($1.args,"txt",$a([$a([$1.text,$1.textxoffset,$1.textyoffset,$1.textfont,$1.textsize])]));$p($1.args,"textxalign","center");$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_code39(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includecheck=false;$1.validatecheck=false;$1.includetext=false;$1.includecheckintext=false;$1.hidestars=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.barchars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";$1.charvals=new Map;for(var _9=0;_9<=42;_9+=1){$p($1.charvals,$G($1.barchars,_9,1),_9)}for(var _F=0,_E=$1.barcode.length-1;_F<=_E;_F+=1){var _J=$g($1.charvals,$G($1.barcode,_F,1))!==undefined;if(!_J){$k[$j++]="bwipp.code39badCharacter";$k[$j++]="Code 39 must contain only digits, capital letters, spaces and the symbols -.$/+%";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _M=$k[--$j];$k[$j++]=$f(_M-1)}var _N=$k[--$j];$1[$k[--$j]]=_N;$1.checksum=0;for(var _R=0,_Q=$f($1.barlen-1);_R<=_Q;_R+=1){$1.checksum=$f($g($1.charvals,$G($1.barcode,_R,1))+$1.checksum)}$1.checksum=$1.checksum%43;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.code39badCheckDigit";$k[$j++]="Incorrect Code 39 check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen);$1.includecheck=true}$1.encs=$a(["1113313111","3113111131","1133111131","3133111111","1113311131","3113311111","1133311111","1113113131","3113113111","1133113111","3111131131","1131131131","3131131111","1111331131","3111331111","1131331111","1111133131","3111133111","1131133111","1111333111","3111111331","1131111331","3131111311","1111311331","3111311311","1131311311","1111113331","3111113311","1131113311","1111313311","3311111131","1331111131","3331111111","1311311131","3311311111","1331311111","1311113131","3311113111","1331113111","1313131111","1313111311","1311131311","1113131311","1311313111"]);var _l=$1.includecheck?3:2;$1.sbs=$s($f($1.barlen+_l)*10);var _p=$1.includecheck?3:2;$1.txt=$a($f($1.barlen+_p));$P($1.sbs,0,$g($1.encs,43));if(!$1.hidestars){$p($1.txt,0,$a(["*",0,$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,0,$a(["",0,$1.textyoffset,$1.textfont,$1.textsize]))}for(var _17=0,_16=$f($1.barlen-1);_17<=_16;_17+=1){$1.i=_17;$1.indx=$g($1.charvals,$G($1.barcode,$1.i,1));$P($1.sbs,$1.i*10+10,$g($1.encs,$1.indx));$p($1.txt,$1.i+1,$a([$G($1.barcode,$1.i,1),($1.i+1)*16,$1.textyoffset,$1.textfont,$1.textsize]))}if($1.includecheck){$P($1.sbs,$f($1.barlen*10+10),$g($1.encs,$1.checksum));if($1.includecheckintext){$p($1.txt,$f($1.barlen+1),$a([$G($1.barchars,$1.checksum,1),$f($1.barlen+1)*16,$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$f($1.barlen+1),$a(["",$f($1.barlen+1)*16,$1.textyoffset,$1.textfont,$1.textsize]))}$P($1.sbs,$f($1.barlen*10+20),$g($1.encs,43));if(!$1.hidestars){$p($1.txt,$f($1.barlen+2),$a(["*",$f($1.barlen+2)*16,$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$f($1.barlen+2),$a(["",$f($1.barlen+2)*16,$1.textyoffset,$1.textfont,$1.textsize]))}}else{$P($1.sbs,$f($1.barlen*10+10),$g($1.encs,43));if(!$1.hidestars){$p($1.txt,$f($1.barlen+1),$a(["*",$f($1.barlen+1)*16,$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$f($1.barlen+1),$a(["",$f($1.barlen+1)*16,$1.textyoffset,$1.textfont,$1.textsize]))}}$k[$j++]=Infinity;$k[$j++]=Infinity;var _2S=$1.sbs;for(var _2T=0,_2U=_2S.length;_2T<_2U;_2T++){$k[$j++]=$g(_2S,_2T)-48}var _2W=$a();$k[$j++]=Infinity;for(var _2Y=0,_2Z=~~(($1.sbs.length+1)/2);_2Y<_2Z;_2Y++){$k[$j++]=$1.height}var _2b=$a();$k[$j++]=Infinity;for(var _2d=0,_2e=~~(($1.sbs.length+1)/2);_2d<_2e;_2d++){$k[$j++]=0}var _2f=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_2W;$k[$j++]="bhs";$k[$j++]=_2b;$k[$j++]="bbs";$k[$j++]=_2f;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _2j=$d();$k[$j++]=_2j;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_code39ext(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.parse=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});var _6=new Map([["parse",$1.parse],["parseonly",true],["parsefnc",false]]);$1.fncvals=_6;$k[$j++]="barcode";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _9=$k[--$j];$1[$k[--$j]]=_9;$1.barlen=$1.barcode.length;delete $1.options["parse"];$F($1.barcode,function(){if($k[--$j]>=128){$k[$j++]="bwipp.code39extBadCharacter";$k[$j++]="Code 39 Extended must contain only ASCII characters";bwipp_raiseerror()}});$1.extencs=$a(["%U","$A","$B","$C","$D","$E","$F","$G","$H","$I","$J","$K","$L","$M","$N","$O","$P","$Q","$R","$S","$T","$U","$V","$W","$X","$Y","$Z","%A","%B","%C","%D","%E"," ","/A","/B","/C","/D","/E","/F","/G","/H","/I","/J","/K","/L","-",".","/O","0","1","2","3","4","5","6","7","8","9","/Z","%F","%G","%H","%I","%J","%V","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","%K","%L","%M","%N","%O","%W","+A","+B","+C","+D","+E","+F","+G","+H","+I","+J","+K","+L","+M","+N","+O","+P","+Q","+R","+S","+T","+U","+V","+W","+X","+Y","+Z","%P","%Q","%R","%S","%T"]);$1.newcode=$s($1.barlen*2);$1.newtext=$s($1.barlen*2);$1.j=0;for(var _M=0,_L=$1.barlen-1;_M<=_L;_M+=1){$1.i=_M;$1.extchar=$g($1.extencs,$g($1.barcode,$1.i));$P($1.newcode,$1.j,$1.extchar);$p($1.newtext,$1.j,$g($1.barcode,$1.i));if($1.extchar.length!=1){$P($1.newtext,$1.j+1," ")}$1.j=$1.j+$1.extchar.length}$1.newcode=$G($1.newcode,0,$1.j);$1.newtext=$G($1.newtext,0,$1.j);$p($1.options,"dontdraw",true);$k[$j++]="args";$k[$j++]=$1.newcode;$k[$j++]=$1.options;bwipp_code39();var _o=$k[--$j];$1[$k[--$j]]=_o;if($1.includetext){$1.txt=$g($1.args,"txt");for(var _v=0,_u=$1.newtext.length-1;_v<=_u;_v+=1){$1.i=_v;$1.txtentry=$g($1.txt,$1.i+1);$p($1.txtentry,0,$G($1.newtext,$1.i,1));$p($1.txt,$1.i+1,$1.txtentry)}$p($1.args,"txt",$1.txt)}$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_code32(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.textxoffset=0;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;if($1.barcode.length!=8&&$1.barcode.length!=9){$k[$j++]="bwipp.code32badLength";$k[$j++]="Italian Pharmacode must be 8 or 9 digits";bwipp_raiseerror()}$F($1.barcode,function(){var _C=$k[--$j];if(_C<48||_C>57){$k[$j++]="bwipp.code32badCharacter";$k[$j++]="Italian Pharmacode must contain only digits";bwipp_raiseerror()}});$1.checksum=0;for(var _D=0;_D<=7;_D+=1){$1.i=_D;$k[$j++]=$f($g($1.barcode,$1.i)-48);if($1.i%2!=0){var _I=$k[--$j];$k[$j++]=_I*2}var _J=$k[--$j];$k[$j++]=_J;if(_J>9){var _K=$k[--$j];$k[$j++]=$f(_K-9)}$1.checksum=$f($k[--$j]+$1.checksum)}$1.checksum=$1.checksum%10;if($1.barcode.length==9){if($g($1.barcode,8)!=$f($1.checksum+48)){$k[$j++]="bwipp.code32badCheckDigit";$k[$j++]="Incorrect Italian Pharmacode check digit provided";bwipp_raiseerror()}}var _S=$s(10);$P(_S,0,$1.barcode);$p(_S,8,$f($1.checksum+48));$1.text=_S;$1.val=$R($s(6),~~$z($1.text),32);$1.barcode=$s(6);for(var _Z=0;_Z<=5;_Z+=1){$p($1.barcode,_Z,48)}$P($1.barcode,6-$1.val.length,$1.val);for(var _e=0;_e<=5;_e+=1){var _g=$g($1.barcode,_e);$k[$j++]=_e;$k[$j++]=_g;$k[$j++]=_g;for(var _h=0,_i="AEIO".length;_h<_i;_h++){if($k[--$j]>=$g("AEIO",_h)){var _l=$k[--$j];$k[$j++]=$f(_l+1)}var _m=$k[--$j];$k[$j++]=_m;$k[$j++]=_m}$j--;var _o=$k[--$j];$p($1.barcode,$k[--$j],_o)}var _r=$1.text;$P($1.text,1,$G(_r,0,9));$p($1.text,0,65);$p($1.options,"dontdraw",true);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_code39();var _x=$k[--$j];$1[$k[--$j]]=_x;$p($1.args,"txt",$a([$a([$1.text,$1.textxoffset,$1.textyoffset,$1.textfont,$1.textsize])]));$p($1.args,"textxalign","center");$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_pzn(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.textxoffset=0;$1.height=1;$1.pzn8=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.textxoffset=+$1.textxoffset;$1.height=+$1.height;if($1.pzn8){if($1.barcode.length!=7&&$1.barcode.length!=8){$k[$j++]="bwipp.pzn8badLength";$k[$j++]="PZN8 must be 7 or 8 digits";bwipp_raiseerror()}}else{if($1.barcode.length!=6&&$1.barcode.length!=7){$k[$j++]="bwipp.pzn7badLength";$k[$j++]="PZN7 must be 6 or 7 digits";bwipp_raiseerror()}}$F($1.barcode,function(){var _G=$k[--$j];if(_G<48||_G>57){$k[$j++]="bwipp.pznBadCharacter";$k[$j++]="PZN must contain only digits";bwipp_raiseerror()}});var _I=$1.pzn8?9:8;$1.msglen=_I;$1.checksum=0;for(var _L=0,_K=$1.msglen-3;_L<=_K;_L+=1){$1.i=_L;var _R=$1.pzn8?1:2;$1.checksum=$f($f($g($1.barcode,$1.i)-48)*($1.i+_R)+$1.checksum)}$1.checksum=$1.checksum%11;if($1.checksum==10){$k[$j++]="bwipp.pznBadInputSequence";$k[$j++]="Incorrect PZN input sequence provided";bwipp_raiseerror()}if($1.barcode.length==$1.msglen-1){if($g($1.barcode,$1.msglen-2)!=$f($1.checksum+48)){$k[$j++]="bwipp.pznBadCheckDigit";$k[$j++]="Incorrect PZN check digit provided";bwipp_raiseerror()}}var _c=$s($1.msglen);$p(_c,0,45);$P(_c,1,$1.barcode);$p(_c,$1.msglen-1,$f($1.checksum+48));$1.msg=_c;$p($1.options,"dontdraw",true);$k[$j++]="args";$k[$j++]=$1.msg;$k[$j++]=$1.options;bwipp_code39();var _j=$k[--$j];$1[$k[--$j]]=_j;$1.text=$s($1.msglen+5);$P($1.text,0,"PZN - ");var _r=$G($1.msg,1,$1.msglen-1);$P($1.text,6,_r);$p($1.args,"txt",$a([$a([$1.text,$1.textxoffset,$1.textyoffset,$1.textfont,$1.textsize])]));$p($1.args,"textxalign","center");$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_code93(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includecheck=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=1;$1.parse=false;$1.parsefnc=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.encs=$a(["131112","111213","111312","111411","121113","121212","121311","111114","131211","141111","211113","211212","211311","221112","221211","231111","112113","112212","112311","122112","132111","111123","111222","111321","121122","131121","212112","212211","211122","211221","221121","222111","112122","112221","122121","123111","121131","311112","311211","321111","112131","113121","211131","121221","312111","311121","122211","111141","1111411"]);$1.barchars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";$1.sft1=-1;$1.sft2=-2;$1.sft3=-3;$1.sft4=-4;var _G=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["SFT$",$1.sft1],["SFT%",$1.sft2],["SFT/",$1.sft3],["SFT+",$1.sft4]]);$1.fncvals=_G;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _J=$k[--$j];$1[$k[--$j]]=_J;$1.msglen=$1.msg.length;if($1.includecheck){$1.sbs=$s($1.msglen*6+25)}else{$1.sbs=$s($1.msglen*6+13)}$1.txt=$a($1.msglen);$P($1.sbs,0,$g($1.encs,47));$1.checksum1=0;$1.checksum2=0;for(var _Y=0,_X=$1.msglen-1;_Y<=_X;_Y+=1){$1.i=_Y;var _b=$g($1.msg,$1.i);$k[$j++]=_b;if(_b<0){$1.indx=$f(42-$k[--$j]);$1.char=" "}else{var _d=$s(1);$p(_d,0,$k[--$j]);$1.char=_d;$x($1.barchars,$1.char);$j--;$1.indx=$k[--$j].length;$j-=2}$1.enc=$g($1.encs,$1.indx);$P($1.sbs,$1.i*6+6,$1.enc);$p($1.txt,$1.i,$a([$1.char,$1.i*9+9,$1.textyoffset,$1.textfont,$1.textsize]));$1.checksum1=$1.checksum1+(($1.msglen-$1.i-1)%20+1)*$1.indx;$1.checksum2=$1.checksum2+(($1.msglen-$1.i)%15+1)*$1.indx}if($1.includecheck){$1.checksum1=$1.checksum1%47;$1.checksum2=($1.checksum2+$1.checksum1)%47;$P($1.sbs,$1.msglen*6+6,$g($1.encs,$1.checksum1));$P($1.sbs,$1.msglen*6+12,$g($1.encs,$1.checksum2));$P($1.sbs,$1.msglen*6+18,$g($1.encs,48))}else{$P($1.sbs,$1.msglen*6+6,$g($1.encs,48))}$k[$j++]=Infinity;$k[$j++]=Infinity;var _1Q=$1.sbs;for(var _1R=0,_1S=_1Q.length;_1R<_1S;_1R++){$k[$j++]=$g(_1Q,_1R)-48}var _1U=$a();$k[$j++]=Infinity;for(var _1W=0,_1X=~~(($1.sbs.length+1)/2);_1W<_1X;_1W++){$k[$j++]=$1.height}var _1Z=$a();$k[$j++]=Infinity;for(var _1b=0,_1c=~~(($1.sbs.length+1)/2);_1b<_1c;_1b++){$k[$j++]=0}var _1d=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_1U;$k[$j++]="bhs";$k[$j++]=_1Z;$k[$j++]="bbs";$k[$j++]=_1d;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _1h=$d();$k[$j++]=_1h;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_code93ext(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.parse=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});var _6=new Map([["parse",$1.parse],["parseonly",true],["parsefnc",false]]);$1.fncvals=_6;$k[$j++]="barcode";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _9=$k[--$j];$1[$k[--$j]]=_9;$1.barlen=$1.barcode.length;delete $1.options["parse"];$1.extencs=$a(["^SFT%U","^SFT$A","^SFT$B","^SFT$C","^SFT$D","^SFT$E","^SFT$F","^SFT$G","^SFT$H","^SFT$I","^SFT$J","^SFT$K","^SFT$L","^SFT$M","^SFT$N","^SFT$O","^SFT$P","^SFT$Q","^SFT$R","^SFT$S","^SFT$T","^SFT$U","^SFT$V","^SFT$W","^SFT$X","^SFT$Y","^SFT$Z","^SFT%A","^SFT%B","^SFT%C","^SFT%D","^SFT%E"," ","^SFT/A","^SFT/B","^SFT/C","^SFT/D","^SFT/E","^SFT/F","^SFT/G","^SFT/H","^SFT/I","^SFT/J","^SFT/K","^SFT/L","-",".","^SFT/O","0","1","2","3","4","5","6","7","8","9","^SFT/Z","^SFT%F","^SFT%G","^SFT%H","^SFT%I","^SFT%J","^SFT%V","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","^SFT%K","^SFT%L","^SFT%M","^SFT%N","^SFT%O","^SFT%W","^SFT+A","^SFT+B","^SFT+C","^SFT+D","^SFT+E","^SFT+F","^SFT+G","^SFT+H","^SFT+I","^SFT+J","^SFT+K","^SFT+L","^SFT+M","^SFT+N","^SFT+O","^SFT+P","^SFT+Q","^SFT+R","^SFT+S","^SFT+T","^SFT+U","^SFT+V","^SFT+W","^SFT+X","^SFT+Y","^SFT+Z","^SFT%P","^SFT%Q","^SFT%R","^SFT%S","^SFT%T"]);$1.newcode=$s($1.barlen*6);$1.newtext=$s($1.barlen*6);$1.j=0;$1.k=0;for(var _K=0,_J=$1.barlen-1;_K<=_J;_K+=1){$1.i=_K;$1.extchar=$g($1.extencs,$g($1.barcode,$1.i));$1.extlen=$1.extchar.length;$P($1.newcode,$1.j,$1.extchar);$p($1.newtext,$1.k,$g($1.barcode,$1.i));if($1.extlen!=1){$P($1.newtext,$1.k+1," ")}$1.j=$1.j+$1.extlen;var _g=$1.extlen==1?1:2;$1.k=$1.k+_g}$1.newcode=$G($1.newcode,0,$1.j);$1.newtext=$G($1.newtext,0,$1.k);$p($1.options,"dontdraw",true);$p($1.options,"parsefnc",true);$k[$j++]="args";$k[$j++]=$1.newcode;$k[$j++]=$1.options;bwipp_code93();var _r=$k[--$j];$1[$k[--$j]]=_r;if($1.includetext){$1.txt=$g($1.args,"txt");for(var _y=0,_x=$1.newtext.length-1;_y<=_x;_y+=1){$1.i=_y;$1.txtentry=$g($1.txt,$1.i);$p($1.txtentry,0,$G($1.newtext,$1.i,1));$p($1.txt,$1.i,$1.txtentry)}$p($1.args,"txt",$1.txt)}$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_interleaved2of5(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includecheck=false;$1.includetext=false;$1.includecheckintext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$F($1.barcode,function(){var _A=$k[--$j];if(_A<48||_A>57){$k[$j++]="bwipp.interleaved2of5badCharacter";$k[$j++]="Interleaved 2 of 5 must contain only digits";bwipp_raiseerror()}});$1.barlen=$1.barcode.length;if($1.barlen%2==0&&$1.includecheck||$1.barlen%2!=0&&!$1.includecheck){$1.pad=$s($1.barlen+1);$p($1.pad,0,48);$P($1.pad,1,$1.barcode);$1.barcode=$1.pad;$1.barlen=$1.barlen+1}if($1.includecheck){$1.checksum=0;for(var _Q=0,_P=$1.barlen-1;_Q<=_P;_Q+=1){$1.i=_Q;if($1.i%2==0){$1.checksum=$1.checksum+($g($1.barcode,$1.i)-48)*3}else{$1.checksum=$1.checksum+($g($1.barcode,$1.i)-48)}}$1.checksum=(10-$1.checksum%10)%10;$1.pad=$s($1.barlen+1);$P($1.pad,0,$1.barcode);$p($1.pad,$1.barlen,$1.checksum+48);$1.barcode=$1.pad;$1.barlen=$1.barlen+1}$1.encs=$a(["11221","21112","12112","22111","11212","21211","12211","11122","21121","12121","1111","2111"]);$1.barchars="0123456789";$1.sbs=$s($1.barlen*5+8);$1.txt=$a($1.barlen);$P($1.sbs,0,$g($1.encs,10));for(var _u=0,_t=$1.barlen-1;_u<=_t;_u+=2){$1.i=_u;$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enca=$g($1.encs,$1.indx);$x($1.barchars,$G($1.barcode,$1.i+1,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.encb=$g($1.encs,$1.indx);$1.intl=$s($1.enca.length*2);for(var _1F=0,_1E=$1.enca.length-1;_1F<=_1E;_1F+=1){$1.j=_1F;$1.achar=$g($1.enca,$1.j);$1.bchar=$g($1.encb,$1.j);$p($1.intl,$1.j*2,$1.achar);$p($1.intl,$1.j*2+1,$1.bchar)}$P($1.sbs,$1.i*5+4,$1.intl);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$1.i*9+4,$1.textyoffset,$1.textfont,$1.textsize]));if($1.includecheck&&!$1.includecheckintext&&$1.barlen-2==$1.i){$p($1.txt,$1.i+1,$a([" ",($1.i+1)*9+4,$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$1.i+1,$a([$G($1.barcode,$1.i+1,1),($1.i+1)*9+4,$1.textyoffset,$1.textfont,$1.textsize]))}}$P($1.sbs,$1.barlen*5+4,$g($1.encs,11));$k[$j++]=Infinity;$k[$j++]=Infinity;var _24=$1.sbs;for(var _25=0,_26=_24.length;_25<_26;_25++){$k[$j++]=$g(_24,_25)-48}var _28=$a();$k[$j++]=Infinity;for(var _2A=0,_2B=~~(($1.sbs.length+1)/2);_2A<_2B;_2A++){$k[$j++]=$1.height}var _2D=$a();$k[$j++]=Infinity;for(var _2F=0,_2G=~~(($1.sbs.length+1)/2);_2F<_2G;_2F++){$k[$j++]=0}var _2H=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_28;$k[$j++]="bhs";$k[$j++]=_2D;$k[$j++]="bbs";$k[$j++]=_2H;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="barratio";$k[$j++]=2;$k[$j++]="spaceratio";$k[$j++]=2;$k[$j++]="opt";$k[$j++]=$1.options;var _2L=$d();$k[$j++]=_2L;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_itf14(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.includecheckintext=true;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=3;$1.height=.5;$1.showborder=true;$1.borderwidth=4;$1.borderleft=15;$1.borderright=15;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.borderwidth=+$1.borderwidth;$1.borderleft=+$1.borderleft;$1.borderright=+$1.borderright;$1.text=$1.barcode;var _E=$1.barcode;$k[$j++]=$s(_E.length);$k[$j++]=0;$F(_E,function(){var _G=$k[--$j];$k[$j++]=_G;if(_G!=32){var _H=$k[--$j];var _I=$k[--$j];var _J=$k[--$j];$p(_J,_I,_H);$k[$j++]=_J;$k[$j++]=$f(_I+1)}else{$j--}});var _K=$k[--$j];$1.barcode=$G($k[--$j],0,_K);$1.hasspace=$1.text.length!=$1.barcode.length;if($1.barcode.length!=13&&$1.barcode.length!=14){$k[$j++]="bwipp.itf14badLength";$k[$j++]="ITF-14 must be 13 or 14 digits";bwipp_raiseerror()}var _R=$1.barcode;for(var _S=0,_T=_R.length;_S<_T;_S++){var _U=$g(_R,_S);if(_U<48||_U>57){$k[$j++]="bwipp.itf14badCharacter";$k[$j++]="ITF-14 must contain only digits";bwipp_raiseerror()}}$1.checksum=0;for(var _V=0;_V<=12;_V+=1){$1.i=_V;$k[$j++]="checksum";$k[$j++]=$1.checksum;$k[$j++]=$f($g($1.barcode,$1.i)-48);if($1.i%2==0){var _b=$k[--$j];$k[$j++]=_b*3}var _c=$k[--$j];var _d=$k[--$j];$1[$k[--$j]]=$f(_d+_c)}$1.checksum=(10-$1.checksum%10)%10;if($1.barcode.length==14){if($g($1.barcode,13)!=$1.checksum+48){$k[$j++]="bwipp.itf14badCheckDigit";$k[$j++]="Incorrect ITF-14 check digit provided";bwipp_raiseerror()}}else{var _k=$s(14);$P(_k,0,$1.barcode);$p(_k,13,$1.checksum+48);$1.barcode=_k;var _p=$1.hasspace?2:1;var _q=$s($1.text.length+_p);$P(_q,_q.length-2," ");$p(_q,_q.length-1,$1.checksum+48);$P(_q,0,$1.text);$1.text=_q}$p($1.options,"dontdraw",true);$p($1.options,"showborder",$1.showborder);$p($1.options,"borderwidth",$1.borderwidth);$p($1.options,"borderleft",$1.borderleft);$p($1.options,"borderright",$1.borderright);$p($1.options,"height",$1.height);$p($1.options,"textyoffset",$1.textyoffset);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_interleaved2of5();var _18=$k[--$j];$1[$k[--$j]]=_18;$p($1.args,"txt",$a([$a([$1.text,$1.textxoffset,$1.textyoffset,$1.textfont,$1.textsize])]));$p($1.args,"textxalign","center");$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_identcode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;if($1.barcode.length!=11&&$1.barcode.length!=12){$k[$j++]="bwipp.identcodeBadLength";$k[$j++]="Deutsche Post Identcode must be 11 or 12 digits";bwipp_raiseerror()}$F($1.barcode,function(){var _D=$k[--$j];if(_D<48||_D>57){$k[$j++]="bwipp.identcodeBadCharacter";$k[$j++]="Deutsche Post Identcode must contain only digits";bwipp_raiseerror()}});$1.checksum=0;for(var _E=0;_E<=10;_E+=1){$1.i=_E;$k[$j++]="checksum";$k[$j++]=$1.checksum;$k[$j++]=$f($g($1.barcode,$1.i)-48);if($1.i%2==0){var _K=$k[--$j];$k[$j++]=_K*4}else{var _L=$k[--$j];$k[$j++]=_L*9}var _M=$k[--$j];var _N=$k[--$j];$1[$k[--$j]]=$f(_N+_M)}$1.checksum=(10-$1.checksum%10)%10;if($1.barcode.length==12){if($g($1.barcode,11)!=$1.checksum+48){$k[$j++]="bwipp.identcodeBadCheckDigit";$k[$j++]="Incorrect Deutsche Post Identcode check digit provided";bwipp_raiseerror()}}var _U=$s(12);$P(_U,0,$1.barcode);$p(_U,11,$1.checksum+48);$1.barcode=_U;$1.text=$Z($s(16)," . . ");$P($1.text,0,$G($1.barcode,0,2));$P($1.text,3,$G($1.barcode,2,3));$P($1.text,7,$G($1.barcode,5,3));var _i=$1.text;$P(_i,11,$G($1.barcode,8,3));$P($1.text,15,$G($1.barcode,11,1));$p($1.options,"dontdraw",true);$p($1.options,"includecheck",false);var _r=$1.options;$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=_r;bwipp_interleaved2of5();var _s=$k[--$j];$1[$k[--$j]]=_s;$p($1.args,"txt",$a([$a([$1.text,$1.textxoffset,$1.textyoffset,$1.textfont,$1.textsize])]));$p($1.args,"textxalign","center");$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_leitcode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;if($1.barcode.length!=13&&$1.barcode.length!=14){$k[$j++]="bwipp.leitcodeBadLength";$k[$j++]="Deutsche Post Leitcode must be 13 or 14 digits";bwipp_raiseerror()}$F($1.barcode,function(){var _D=$k[--$j];if(_D<48||_D>57){$k[$j++]="bwipp.leitcodeBadCharacter";$k[$j++]="Deutsche Post Leitcode must contain only digits";bwipp_raiseerror()}});$1.checksum=0;for(var _E=0;_E<=12;_E+=1){$1.i=_E;$k[$j++]="checksum";$k[$j++]=$1.checksum;$k[$j++]=$f($g($1.barcode,$1.i)-48);if($1.i%2==0){var _K=$k[--$j];$k[$j++]=_K*4}else{var _L=$k[--$j];$k[$j++]=_L*9}var _M=$k[--$j];var _N=$k[--$j];$1[$k[--$j]]=$f(_N+_M)}$1.checksum=(10-$1.checksum%10)%10;if($1.barcode.length==14){if($g($1.barcode,13)!=$1.checksum+48){$k[$j++]="bwipp.leitcodeBadCheckDigit";$k[$j++]="Incorrect Deutsche Post Leitcode check digit provided";bwipp_raiseerror()}}var _U=$s(14);$P(_U,0,$1.barcode);$p(_U,13,$1.checksum+48);$1.barcode=_U;$1.text=$Z($s(18)," . . . ");$P($1.text,0,$G($1.barcode,0,5));$P($1.text,6,$G($1.barcode,5,3));$P($1.text,10,$G($1.barcode,8,3));var _i=$1.text;$P(_i,14,$G($1.barcode,11,2));$P($1.text,17,$G($1.barcode,13,1));$p($1.options,"dontdraw",true);$p($1.options,"includecheck",false);var _r=$1.options;$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=_r;bwipp_interleaved2of5();var _s=$k[--$j];$1[$k[--$j]]=_s;$p($1.args,"txt",$a([$a([$1.text,$1.textxoffset,$1.textyoffset,$1.textfont,$1.textsize])]));$p($1.args,"textxalign","center");$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_databaromni(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.height=33/72;$1.linkage=false;$1.format="omni";$1.barxmult=33;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});if($eq($1.format,"truncated")){$1.height=13/72}$1.height=+$1.height;$1.barxmult=~~$1.barxmult;if($ne($G($1.barcode,0,4),"(01)")){$k[$j++]="bwipp.databaromniBadAI";$k[$j++]="GS1 DataBar Omnidirectional must begin with (01) application identifier";bwipp_raiseerror()}if($1.barcode.length!=17&&$1.barcode.length!=18){$k[$j++]="bwipp.databaromniBadLength";$k[$j++]="GS1 DataBar Omnidirectional must be 13 or 14 digits";bwipp_raiseerror()}var _E=$G($1.barcode,4,$1.barcode.length-4);for(var _F=0,_G=_E.length;_F<_G;_F++){var _H=$g(_E,_F);if(_H<48||_H>57){$k[$j++]="bwipp.databaromniBadCharacter";$k[$j++]="GS1 DataBar Omnidirectional must contain only digits";bwipp_raiseerror()}}$1.checksum=0;for(var _I=0;_I<=12;_I+=1){$1.i=_I;$k[$j++]="checksum";$k[$j++]=$1.checksum;$k[$j++]=$f($g($1.barcode,$1.i+4)-48);if($1.i%2==0){var _O=$k[--$j];$k[$j++]=_O*3}var _P=$k[--$j];var _Q=$k[--$j];$1[$k[--$j]]=$f(_Q+_P)}$1.checksum=(10-$1.checksum%10)%10;if($1.barcode.length==18){if($g($1.barcode,17)!=$1.checksum+48){$k[$j++]="bwipp.databaromniBadCheckDigit";$k[$j++]="Incorrect GS1 DataBar Omnidirectional check digit provided";bwipp_raiseerror()}}var _X=$s(18);$P(_X,0,$1.barcode);$p(_X,17,$1.checksum+48);$1.barcode=_X;$1.txt=$a($1.barcode.length);for(var _e=0,_d=$1.barcode.length-1;_e<=_d;_e+=1){$1.i=_e;$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),0,0,"",0]))}$1.ncr=function(){var _l=$k[--$j];var _m=$k[--$j];var _n=$f(_m-_l);if(_l<$f(_m-_l)){var _=_n;_n=_l;_l=_}$k[$j++]=_n;$k[$j++]=1;$k[$j++]=1;for(var _p=_m,_o=$f(_l+1);_p>=_o;_p-=1){var _q=$k[--$j];var _r=$k[--$j];var _s=$k[--$j];$k[$j++]=_s;$k[$j++]=_r;$k[$j++]=_q*_p;if($le(_r,_s)){var _t=$k[--$j];var _u=$k[--$j];$k[$j++]=$f(_u+1);$k[$j++]=~~(_t/_u)}}for(;;){var _v=$k[--$j];var _w=$k[--$j];var _x=$k[--$j];$k[$j++]=_x;$k[$j++]=_w;$k[$j++]=_v;if($gt(_w,_x)){break}var _y=$k[--$j];var _z=$k[--$j];$k[$j++]=$f(_z+1);$k[$j++]=~~(_y/_z)}var _10=$k[--$j];var _11=$k[--$j];$k[$j++]=_10;$k[$j++]=_11;$j--;var _12=$k[--$j];var _13=$k[--$j];$k[$j++]=_12;$k[$j++]=_13;$j--};$1.getRSSwidths=function(){$1.oe=$k[--$j];$1.el=$k[--$j];$1.mw=$k[--$j];$1.nm=$k[--$j];$1.val=$k[--$j];$1.out=$a($1.el);$1.mask=0;for(var _1D=0,_1C=$f($1.el-2);_1D<=_1C;_1D+=1){$1.bar=_1D;$1.ew=1;var _1F=$1.bar;$1.mask=$1.mask|(_1F<0?1>>>-_1F:1<<_1F);for(;;){$k[$j++]="sval";$k[$j++]=$f($f($1.nm-$1.ew)-1);$k[$j++]=$f($f($1.el-$1.bar)-2);$1.ncr();var _1K=$k[--$j];$1[$k[--$j]]=_1K;if($1.oe&&$1.mask==0&&$f($f($f($1.nm-$1.ew)-$1.el*2)+$1.bar*2)>=-2){$k[$j++]="sval";$k[$j++]=$1.sval;$k[$j++]=$f($f($f($1.nm-$1.ew)-$1.el)+$1.bar);$k[$j++]=$f($f($1.el-$1.bar)-2);$1.ncr();var _1Z=$k[--$j];var _1a=$k[--$j];$1[$k[--$j]]=$f(_1a-_1Z)}if($f($1.el-$1.bar)>2){$1.lval=0;for(var _1k=$f($f($f($f($1.nm-$1.ew)-$1.el)+$1.bar)+2),_1j=$f($1.mw+1);_1k>=_1j;_1k-=1){$k[$j++]=$f($f($f($1.nm-_1k)-$1.ew)-1);$k[$j++]=$f($f($1.el-$1.bar)-3);$1.ncr();$1.lval=$f($k[--$j]+$1.lval)}$1.sval=$f($1.sval-$1.lval*$f($f($1.el-$1.bar)-1))}else{if($f($1.nm-$1.ew)>$1.mw){$1.sval=$f($1.sval-1)}}$1.val=$f($1.val-$1.sval);if($1.val<0){break}$1.ew=$1.ew+1;var _24=$1.bar;$1.mask=$1.mask&~(_24<0?1>>>-_24:1<<_24)}$1.val=$f($1.val+$1.sval);$1.nm=$f($1.nm-$1.ew);$p($1.out,$1.bar,$1.ew)}$p($1.out,$f($1.el-1),$1.nm);$k[$j++]=$1.out};$k[$j++]=Infinity;var _2H=$1.linkage?1:0;var _2J=$G($1.barcode,4,13);$k[$j++]=_2H;for(var _2K=0,_2L=_2J.length;_2K<_2L;_2K++){$k[$j++]=$f($g(_2J,_2K)-48)}$1.binval=$a();for(var _2O=0;_2O<=12;_2O+=1){$1.i=_2O;var _2P=$1.binval;var _2Q=$1.i;$p(_2P,_2Q+1,$f($g(_2P,_2Q+1)+$g($1.binval,$1.i)%4537077*10));$p($1.binval,$1.i,~~($g($1.binval,$1.i)/4537077))}$1.right=$g($1.binval,13)%4537077;var _2c=$1.binval;$p(_2c,13,~~($g(_2c,13)/4537077));$1.left=0;$1.i=true;for(var _2e=0;_2e<=13;_2e+=1){$1.j=_2e;var _2h=$g($1.binval,$1.j);$k[$j++]=_2h;if(_2h==0&&$1.i){$j--}else{$1.i=false;$1.left=$f($1.left+$k[--$j]*~~Math.pow(10,13-$1.j))}}$1.d1=~~($1.left/1597);$1.d2=$1.left%1597;$1.d3=~~($1.right/1597);$1.d4=$1.right%1597;$1.tab164=$a([160,0,12,4,8,1,161,1,960,161,10,6,6,3,80,10,2014,961,8,8,4,5,31,34,2714,2015,6,10,3,6,10,70,2840,2715,4,12,1,8,1,126]);$1.tab154=$a([335,0,5,10,2,7,4,84,1035,336,7,8,4,5,20,35,1515,1036,9,6,6,3,48,10,1596,1516,11,4,8,1,81,1]);$1.i=0;for(;;){if($1.d1<=$g($1.tab164,$1.i)){var _2y=$G($1.tab164,$1.i+1,7);for(var _2z=0,_30=_2y.length;_2z<_30;_2z++){$k[$j++]=$g(_2y,_2z)}$1.d1te=$k[--$j];$1.d1to=$k[--$j];$1.d1mwe=$k[--$j];$1.d1mwo=$k[--$j];$1.d1ele=$k[--$j];$1.d1elo=$k[--$j];$1.d1gs=$k[--$j];break}$1.i=$1.i+8}$1.i=0;for(;;){if($1.d2<=$g($1.tab154,$1.i)){var _3G=$G($1.tab154,$1.i+1,7);for(var _3H=0,_3I=_3G.length;_3H<_3I;_3H++){$k[$j++]=$g(_3G,_3H)}$1.d2te=$k[--$j];$1.d2to=$k[--$j];$1.d2mwe=$k[--$j];$1.d2mwo=$k[--$j];$1.d2ele=$k[--$j];$1.d2elo=$k[--$j];$1.d2gs=$k[--$j];break}$1.i=$1.i+8}$1.i=0;for(;;){if($1.d3<=$g($1.tab164,$1.i)){var _3Y=$G($1.tab164,$1.i+1,7);for(var _3Z=0,_3a=_3Y.length;_3Z<_3a;_3Z++){$k[$j++]=$g(_3Y,_3Z)}$1.d3te=$k[--$j];$1.d3to=$k[--$j];$1.d3mwe=$k[--$j];$1.d3mwo=$k[--$j];$1.d3ele=$k[--$j];$1.d3elo=$k[--$j];$1.d3gs=$k[--$j];break}$1.i=$1.i+8}$1.i=0;for(;;){if($1.d4<=$g($1.tab154,$1.i)){var _3q=$G($1.tab154,$1.i+1,7);for(var _3r=0,_3s=_3q.length;_3r<_3s;_3r++){$k[$j++]=$g(_3q,_3r)}$1.d4te=$k[--$j];$1.d4to=$k[--$j];$1.d4mwe=$k[--$j];$1.d4mwo=$k[--$j];$1.d4ele=$k[--$j];$1.d4elo=$k[--$j];$1.d4gs=$k[--$j];break}$1.i=$1.i+8}$k[$j++]="d1wo";$k[$j++]=~~($f($1.d1-$1.d1gs)/$1.d1te);$k[$j++]=$1.d1elo;$k[$j++]=$1.d1mwo;$k[$j++]=4;$k[$j++]=false;$1.getRSSwidths();var _47=$k[--$j];$1[$k[--$j]]=_47;$k[$j++]="d1we";$k[$j++]=$f($1.d1-$1.d1gs)%$1.d1te;$k[$j++]=$1.d1ele;$k[$j++]=$1.d1mwe;$k[$j++]=4;$k[$j++]=true;$1.getRSSwidths();var _4E=$k[--$j];$1[$k[--$j]]=_4E;$k[$j++]="d2wo";$k[$j++]=$f($1.d2-$1.d2gs)%$1.d2to;$k[$j++]=$1.d2elo;$k[$j++]=$1.d2mwo;$k[$j++]=4;$k[$j++]=true;$1.getRSSwidths();var _4L=$k[--$j];$1[$k[--$j]]=_4L;$k[$j++]="d2we";$k[$j++]=~~($f($1.d2-$1.d2gs)/$1.d2to);$k[$j++]=$1.d2ele;$k[$j++]=$1.d2mwe;$k[$j++]=4;$k[$j++]=false;$1.getRSSwidths();var _4S=$k[--$j];$1[$k[--$j]]=_4S;$k[$j++]="d3wo";$k[$j++]=~~($f($1.d3-$1.d3gs)/$1.d3te);$k[$j++]=$1.d3elo;$k[$j++]=$1.d3mwo;$k[$j++]=4;$k[$j++]=false;$1.getRSSwidths();var _4Z=$k[--$j];$1[$k[--$j]]=_4Z;$k[$j++]="d3we";$k[$j++]=$f($1.d3-$1.d3gs)%$1.d3te;$k[$j++]=$1.d3ele;$k[$j++]=$1.d3mwe;$k[$j++]=4;$k[$j++]=true;$1.getRSSwidths();var _4g=$k[--$j];$1[$k[--$j]]=_4g;$k[$j++]="d4wo";$k[$j++]=$f($1.d4-$1.d4gs)%$1.d4to;$k[$j++]=$1.d4elo;$k[$j++]=$1.d4mwo;$k[$j++]=4;$k[$j++]=true;$1.getRSSwidths();var _4n=$k[--$j];$1[$k[--$j]]=_4n;$k[$j++]="d4we";$k[$j++]=~~($f($1.d4-$1.d4gs)/$1.d4to);$k[$j++]=$1.d4ele;$k[$j++]=$1.d4mwe;$k[$j++]=4;$k[$j++]=false;$1.getRSSwidths();var _4u=$k[--$j];$1[$k[--$j]]=_4u;$1.d1w=$a(8);for(var _4x=0;_4x<=3;_4x+=1){$1.i=_4x;$p($1.d1w,$1.i*2,$g($1.d1wo,$1.i));$p($1.d1w,$1.i*2+1,$g($1.d1we,$1.i))}$1.d2w=$a(8);for(var _59=0;_59<=3;_59+=1){$1.i=_59;$p($1.d2w,7-$1.i*2,$g($1.d2wo,$1.i));$p($1.d2w,6-$1.i*2,$g($1.d2we,$1.i))}$1.d3w=$a(8);for(var _5L=0;_5L<=3;_5L+=1){$1.i=_5L;$p($1.d3w,7-$1.i*2,$g($1.d3wo,$1.i));$p($1.d3w,6-$1.i*2,$g($1.d3we,$1.i))}$1.d4w=$a(8);for(var _5X=0;_5X<=3;_5X+=1){$1.i=_5X;$p($1.d4w,$1.i*2,$g($1.d4wo,$1.i));$p($1.d4w,$1.i*2+1,$g($1.d4we,$1.i))}$k[$j++]=Infinity;var _5i=$1.d1w;for(var _5j=0,_5k=_5i.length;_5j<_5k;_5j++){$k[$j++]=$g(_5i,_5j)}var _5m=$1.d2w;for(var _5n=0,_5o=_5m.length;_5n<_5o;_5n++){$k[$j++]=$g(_5m,_5n)}var _5q=$1.d3w;for(var _5r=0,_5s=_5q.length;_5r<_5s;_5r++){$k[$j++]=$g(_5q,_5r)}var _5u=$1.d4w;for(var _5v=0,_5w=_5u.length;_5v<_5w;_5v++){$k[$j++]=$g(_5u,_5v)}$1.widths=$a();$1.checkweights=$a([1,3,9,27,2,6,18,54,58,72,24,8,29,36,12,4,74,51,17,32,37,65,48,16,64,34,23,69,49,68,46,59]);$1.checkwidths=$a([3,8,2,1,1,3,5,5,1,1,3,3,7,1,1,3,1,9,1,1,2,7,4,1,1,2,5,6,1,1,2,3,8,1,1,1,5,7,1,1,1,3,9,1,1]);$1.checksum=0;for(var _61=0;_61<=31;_61+=1){$1.i=_61;$1.checksum=$f($1.checksum+$g($1.widths,$1.i)*$g($1.checkweights,$1.i))}$1.checksum=$1.checksum%79;if($1.checksum>=8){$1.checksum=$f($1.checksum+1)}if($1.checksum>=72){$1.checksum=$f($1.checksum+1)}$1.checklt=$G($1.checkwidths,~~($1.checksum/9)*5,5);$1.checkrtrev=$G($1.checkwidths,$1.checksum%9*5,5);$1.checkrt=$a(5);for(var _6L=0;_6L<=4;_6L+=1){$1.i=_6L;$p($1.checkrt,$1.i,$g($1.checkrtrev,4-$1.i))}if($eq($1.format,"omni")||$eq($1.format,"truncated")){$k[$j++]=Infinity;var _6T=$1.d1w;$k[$j++]=1;for(var _6U=0,_6V=_6T.length;_6U<_6V;_6U++){$k[$j++]=$g(_6T,_6U)}var _6X=$1.checklt;for(var _6Y=0,_6Z=_6X.length;_6Y<_6Z;_6Y++){$k[$j++]=$g(_6X,_6Y)}var _6b=$1.d2w;for(var _6c=0,_6d=_6b.length;_6c<_6d;_6c++){$k[$j++]=$g(_6b,_6c)}var _6f=$1.d4w;for(var _6g=0,_6h=_6f.length;_6g<_6h;_6g++){$k[$j++]=$g(_6f,_6g)}var _6j=$1.checkrt;for(var _6k=0,_6l=_6j.length;_6k<_6l;_6k++){$k[$j++]=$g(_6j,_6k)}var _6n=$1.d3w;for(var _6o=0,_6p=_6n.length;_6o<_6p;_6o++){$k[$j++]=$g(_6n,_6o)}$k[$j++]=1;$k[$j++]=1;$1.sbs=$a();$k[$j++]=Infinity;var _6s=$1.sbs;$k[$j++]=Infinity;for(var _6u=0,_6v=~~(($1.sbs.length+1)/2);_6u<_6v;_6u++){$k[$j++]=$1.height}var _6x=$a();$k[$j++]=Infinity;for(var _6z=0,_70=~~(($1.sbs.length+1)/2);_6z<_70;_6z++){$k[$j++]=0}var _71=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_6s;$k[$j++]="bhs";$k[$j++]=_6x;$k[$j++]="bbs";$k[$j++]=_71;$k[$j++]="txt";$k[$j++]=$1.txt;$k[$j++]="textxalign";$k[$j++]="center";$k[$j++]="opt";$k[$j++]=$1.options;var _74=$d();$k[$j++]=_74;if(!$1.dontdraw){bwipp_renlinear()}}else{$k[$j++]=Infinity;var _76=$1.d1w;$k[$j++]=1;$k[$j++]=1;for(var _77=0,_78=_76.length;_77<_78;_77++){$k[$j++]=$g(_76,_77)}var _7A=$1.checklt;for(var _7B=0,_7C=_7A.length;_7B<_7C;_7B++){$k[$j++]=$g(_7A,_7B)}var _7E=$1.d2w;for(var _7F=0,_7G=_7E.length;_7F<_7G;_7F++){$k[$j++]=$g(_7E,_7F)}$k[$j++]=1;$k[$j++]=1;$k[$j++]=0;$1.top=$a();$k[$j++]=Infinity;var _7J=$1.d4w;$k[$j++]=1;$k[$j++]=1;for(var _7K=0,_7L=_7J.length;_7K<_7L;_7K++){$k[$j++]=$g(_7J,_7K)}var _7N=$1.checkrt;for(var _7O=0,_7P=_7N.length;_7O<_7P;_7O++){$k[$j++]=$g(_7N,_7O)}var _7R=$1.d3w;for(var _7S=0,_7T=_7R.length;_7S<_7T;_7S++){$k[$j++]=$g(_7R,_7S)}$k[$j++]=1;$k[$j++]=1;$k[$j++]=0;$1.bot=$a();for(var _7W=0;_7W<=24;_7W+=2){$1.i=_7W;for(var _7a=0,_7b=$g($1.top,$1.i);_7a<_7b;_7a++){$k[$j++]=0}for(var _7f=0,_7g=$g($1.top,$1.i+1);_7f<_7g;_7f++){$k[$j++]=1}}$r($a(50));$1.top=$k[--$j];for(var _7j=0;_7j<=24;_7j+=2){$1.i=_7j;for(var _7n=0,_7o=$g($1.bot,$1.i);_7n<_7o;_7n++){$k[$j++]=1}for(var _7s=0,_7t=$g($1.bot,$1.i+1);_7s<_7t;_7s++){$k[$j++]=0}}$r($a(50));$1.bot=$k[--$j];if($eq($1.format,"stacked")){$1.sep=$a(50);$p($1.sep,0,0);for(var _7z=1;_7z<=49;_7z+=1){$1.i=_7z;if($eq($g($1.top,$1.i),$g($1.bot,$1.i))){$p($1.sep,$1.i,$f(1-$g($1.top,$1.i)))}else{$p($1.sep,$1.i,$f(1-$g($1.sep,$1.i-1)))}}$P($1.sep,0,$a([0,0,0,0]));$P($1.sep,46,$a([0,0,0,0]));$k[$j++]=Infinity;for(var _8K=0,_8L=5;_8K<_8L;_8K++){$q($1.top)}$q($1.sep);for(var _8O=0,_8P=7;_8O<_8P;_8O++){$q($1.bot)}$1.pixs=$a();$1.pixy=~~($1.pixs.length/50)}if($eq($1.format,"stackedomni")){$k[$j++]=Infinity;$F($1.top,function(){var _8V=$k[--$j];$k[$j++]=$f(1-_8V)});$1.sep1=$a();$P($1.sep1,0,$a([0,0,0,0]));$P($1.sep1,46,$a([0,0,0,0]));for(var _8b=18;_8b<=30;_8b+=1){$1.i=_8b;if($g($1.top,$1.i)==0){if($g($1.top,$1.i-1)==1){$k[$j++]=1}else{var _8l=$g($1.sep1,$1.i-1)==0?1:0;$k[$j++]=_8l}}else{$k[$j++]=0}$p($1.sep1,$1.i,$k[--$j])}$k[$j++]=Infinity;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;for(var _8p=0,_8q=21;_8p<_8q;_8p++){$k[$j++]=0;$k[$j++]=1}$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$1.sep2=$a();$k[$j++]=Infinity;$F($1.bot,function(){var _8t=$k[--$j];$k[$j++]=$f(1-_8t)});$1.sep3=$a();$P($1.sep3,0,$a([0,0,0,0]));$P($1.sep3,46,$a([0,0,0,0]));for(var _8z=19;_8z<=31;_8z+=1){$1.i=_8z;if($g($1.bot,$1.i)==0){if($g($1.bot,$1.i-1)==1){$k[$j++]=1}else{var _99=$g($1.sep3,$1.i-1)==0?1:0;$k[$j++]=_99}}else{$k[$j++]=0}$p($1.sep3,$1.i,$k[--$j])}$1.f3=$a([1,1,1,1,1,1,1,1,1,0,1,1,1]);$k[$j++]=true;for(var _9E=0;_9E<=12;_9E+=1){var _9J=$k[--$j];$k[$j++]=_9J&&$eq($g($1.bot,_9E+19),$g($1.f3,_9E))}if($k[--$j]){$P($1.sep3,19,$a([0,0,0,0,0,0,0,0,0,0,1,0,0]))}$k[$j++]=Infinity;for(var _9O=0,_9P=$1.barxmult;_9O<_9P;_9O++){$q($1.top)}$q($1.sep1);$q($1.sep2);$q($1.sep3);for(var _9V=0,_9W=$1.barxmult;_9V<_9W;_9V++){$q($1.bot)}$1.pixs=$a();$1.pixy=~~($1.pixs.length/50)}var _9e=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",50],["pixy",$1.pixy],["height",$1.pixy/72],["width",50/72],["opt",$1.options]]);$k[$j++]=_9e;if(!$1.dontdraw){bwipp_renmatrix()}}}function bwipp_databarstacked(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});if($ne($G($1.barcode,0,4),"(01)")){$k[$j++]="bwipp.databarstackedBadAI";$k[$j++]="GS1 DataBar Stacked must begin with (01) application identifier";bwipp_raiseerror()}if($1.barcode.length!=17&&$1.barcode.length!=18){$k[$j++]="bwipp.databarstackedBadLength";$k[$j++]="GS1 DataBar Stacked must be 13 or 14 digits";bwipp_raiseerror()}var _B=$G($1.barcode,4,$1.barcode.length-4);for(var _C=0,_D=_B.length;_C<_D;_C++){var _E=$g(_B,_C);if(_E<48||_E>57){$k[$j++]="bwipp.databarstackedBadCharacter";$k[$j++]="GS1 DataBar Stacked must contain only digits";bwipp_raiseerror()}}$1.checksum=0;for(var _F=0;_F<=12;_F+=1){$1.i=_F;$k[$j++]="checksum";$k[$j++]=$1.checksum;$k[$j++]=$f($g($1.barcode,$1.i+4)-48);if($1.i%2==0){var _L=$k[--$j];$k[$j++]=_L*3}var _M=$k[--$j];var _N=$k[--$j];$1[$k[--$j]]=$f(_N+_M)}$1.checksum=(10-$1.checksum%10)%10;if($1.barcode.length==18){if($g($1.barcode,17)!=$1.checksum+48){$k[$j++]="bwipp.databarstackedBadCheckDigit";$k[$j++]="Incorrect GS1 DataBar Stacked check digit provided";bwipp_raiseerror()}}var _U=$s(18);$P(_U,0,$1.barcode);$p(_U,17,$1.checksum+48);$1.barcode=_U;$p($1.options,"dontdraw",true);$p($1.options,"format","stacked");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_databaromni();var _b=$k[--$j];$1[$k[--$j]]=_b;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_databarstackedomni(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});if($ne($G($1.barcode,0,4),"(01)")){$k[$j++]="bwipp.databarstackedomniBadAI";$k[$j++]="GS1 DataBar Stacked Omnidirectional must begin with (01) application identifier";bwipp_raiseerror()}if($1.barcode.length!=17&&$1.barcode.length!=18){$k[$j++]="bwipp.databarstackedomniBadLength";$k[$j++]="GS1 DataBar Stacked Omnidirectional must be 13 or 14 digits";bwipp_raiseerror()}var _B=$G($1.barcode,4,$1.barcode.length-4);for(var _C=0,_D=_B.length;_C<_D;_C++){var _E=$g(_B,_C);if(_E<48||_E>57){$k[$j++]="bwipp.databarstackedomniBadCharacter";$k[$j++]="GS1 DataBar Stacked Omnidirectional must contain only digits";bwipp_raiseerror()}}$1.checksum=0;for(var _F=0;_F<=12;_F+=1){$1.i=_F;$k[$j++]="checksum";$k[$j++]=$1.checksum;$k[$j++]=$f($g($1.barcode,$1.i+4)-48);if($1.i%2==0){var _L=$k[--$j];$k[$j++]=_L*3}var _M=$k[--$j];var _N=$k[--$j];$1[$k[--$j]]=$f(_N+_M)}$1.checksum=(10-$1.checksum%10)%10;if($1.barcode.length==18){if($g($1.barcode,17)!=$1.checksum+48){$k[$j++]="bwipp.databarstackedomniBadCheckDigit";$k[$j++]="Incorrect GS1 DataBar Stacked Omnidirectional check digit provided";bwipp_raiseerror()}}var _U=$s(18);$P(_U,0,$1.barcode);$p(_U,17,$1.checksum+48);$1.barcode=_U;$p($1.options,"dontdraw",true);$p($1.options,"format","stackedomni");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_databaromni();var _b=$k[--$j];$1[$k[--$j]]=_b;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_databartruncated(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});if($ne($G($1.barcode,0,4),"(01)")){$k[$j++]="bwipp.databartruncatedBadAI";$k[$j++]="GS1 DataBar Truncated must begin with (01) application identifier";bwipp_raiseerror()}if($1.barcode.length!=17&&$1.barcode.length!=18){$k[$j++]="bwipp.databartruncatedBadLength";$k[$j++]="GS1 DataBar Truncated must be 13 or 14 digits";bwipp_raiseerror()}var _B=$G($1.barcode,4,$1.barcode.length-4);for(var _C=0,_D=_B.length;_C<_D;_C++){var _E=$g(_B,_C);if(_E<48||_E>57){$k[$j++]="bwipp.databartruncatedBadCharacter";$k[$j++]="GS1 DataBar Truncated must contain only digits";bwipp_raiseerror()}}$1.checksum=0;for(var _F=0;_F<=12;_F+=1){$1.i=_F;$k[$j++]="checksum";$k[$j++]=$1.checksum;$k[$j++]=$f($g($1.barcode,$1.i+4)-48);if($1.i%2==0){var _L=$k[--$j];$k[$j++]=_L*3}var _M=$k[--$j];var _N=$k[--$j];$1[$k[--$j]]=$f(_N+_M)}$1.checksum=(10-$1.checksum%10)%10;if($1.barcode.length==18){if($g($1.barcode,17)!=$1.checksum+48){$k[$j++]="bwipp.databartruncatedBadCheckDigit";$k[$j++]="Incorrect GS1 DataBar Truncated check digit provided";bwipp_raiseerror()}}var _U=$s(18);$P(_U,0,$1.barcode);$p(_U,17,$1.checksum+48);$1.barcode=_U;$p($1.options,"dontdraw",true);$p($1.options,"format","truncated");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_databaromni();var _b=$k[--$j];$1[$k[--$j]]=_b;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_databarlimited(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.height=10/72;$1.linkage=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.height=+$1.height;if($ne($G($1.barcode,0,4),"(01)")){$k[$j++]="bwipp.databarlimitedBadAI";$k[$j++]="GS1 DataBar Limited must begin with (01) application identifier";bwipp_raiseerror()}if($1.barcode.length!=17&&$1.barcode.length!=18){$k[$j++]="bwipp.databarlimitedBadLength";$k[$j++]="GS1 DataBar Limited must be 13 or 14 digits";bwipp_raiseerror()}var _B=$g($1.barcode,4);if(_B<48||_B>49){$k[$j++]="bwipp.databarlimitedBadStartDigit";$k[$j++]="GS1 DataBar Limited must begin with 0 or 1";bwipp_raiseerror()}var _E=$G($1.barcode,5,$1.barcode.length-5);for(var _F=0,_G=_E.length;_F<_G;_F++){var _H=$g(_E,_F);if(_H<48||_H>57){$k[$j++]="bwipp.databarlimitedBadCharacter";$k[$j++]="GS1 DataBar Limited must contain only digits";bwipp_raiseerror()}}$1.checksum=0;for(var _I=0;_I<=12;_I+=1){$1.i=_I;$k[$j++]="checksum";$k[$j++]=$1.checksum;$k[$j++]=$f($g($1.barcode,$1.i+4)-48);if($1.i%2==0){var _O=$k[--$j];$k[$j++]=_O*3}var _P=$k[--$j];var _Q=$k[--$j];$1[$k[--$j]]=$f(_Q+_P)}$1.checksum=(10-$1.checksum%10)%10;if($1.barcode.length==18){if($g($1.barcode,17)!=$1.checksum+48){$k[$j++]="bwipp.databarlimitedBadCheckDigit";$k[$j++]="Incorrect GS1 DataBar Limited check digit provided";bwipp_raiseerror()}}var _X=$s(18);$P(_X,0,$1.barcode);$p(_X,17,$1.checksum+48);$1.barcode=_X;$k[$j++]=Infinity;var _b=$G($1.barcode,4,13);for(var _c=0,_d=_b.length;_c<_d;_c++){$k[$j++]=$f($g(_b,_c)-48)}$1.binval=$a();if($1.linkage){$1.linkval=$a([2,0,1,5,1,3,3,5,3,1,0,9,6]);for(var _k=0,_j=$1.binval.length-1;_k<=_j;_k+=1){$1.i=_k;$p($1.binval,$1.i,$f($g($1.binval,$1.i)+$g($1.linkval,$1.i)))}}$1.txt=$a($1.barcode.length);for(var _x=0,_w=$1.barcode.length-1;_x<=_w;_x+=1){$1.i=_x;$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),0,0,"",0]))}$1.ncr=function(){var _14=$k[--$j];var _15=$k[--$j];var _16=$f(_15-_14);if(_14<$f(_15-_14)){var _=_16;_16=_14;_14=_}$k[$j++]=_16;$k[$j++]=1;$k[$j++]=1;for(var _18=_15,_17=$f(_14+1);_18>=_17;_18-=1){var _19=$k[--$j];var _1A=$k[--$j];var _1B=$k[--$j];$k[$j++]=_1B;$k[$j++]=_1A;$k[$j++]=_19*_18;if($le(_1A,_1B)){var _1C=$k[--$j];var _1D=$k[--$j];$k[$j++]=$f(_1D+1);$k[$j++]=~~(_1C/_1D)}}for(;;){var _1E=$k[--$j];var _1F=$k[--$j];var _1G=$k[--$j];$k[$j++]=_1G;$k[$j++]=_1F;$k[$j++]=_1E;if($gt(_1F,_1G)){break}var _1H=$k[--$j];var _1I=$k[--$j];$k[$j++]=$f(_1I+1);$k[$j++]=~~(_1H/_1I)}var _1J=$k[--$j];var _1K=$k[--$j];$k[$j++]=_1J;$k[$j++]=_1K;$j--;var _1L=$k[--$j];var _1M=$k[--$j];$k[$j++]=_1L;$k[$j++]=_1M;$j--};$1.getRSSwidths=function(){$1.oe=$k[--$j];$1.el=$k[--$j];$1.mw=$k[--$j];$1.nm=$k[--$j];$1.val=$k[--$j];$1.out=$a($1.el);$1.mask=0;for(var _1W=0,_1V=$f($1.el-2);_1W<=_1V;_1W+=1){$1.bar=_1W;$1.ew=1;var _1Y=$1.bar;$1.mask=$1.mask|(_1Y<0?1>>>-_1Y:1<<_1Y);for(;;){$k[$j++]="sval";$k[$j++]=$f($f($1.nm-$1.ew)-1);$k[$j++]=$f($f($1.el-$1.bar)-2);$1.ncr();var _1d=$k[--$j];$1[$k[--$j]]=_1d;if($1.oe&&$1.mask==0&&$f($f($f($1.nm-$1.ew)-$1.el*2)+$1.bar*2)>=-2){$k[$j++]="sval";$k[$j++]=$1.sval;$k[$j++]=$f($f($f($1.nm-$1.ew)-$1.el)+$1.bar);$k[$j++]=$f($f($1.el-$1.bar)-2);$1.ncr();var _1s=$k[--$j];var _1t=$k[--$j];$1[$k[--$j]]=$f(_1t-_1s)}if($f($1.el-$1.bar)>2){$1.lval=0;for(var _23=$f($f($f($f($1.nm-$1.ew)-$1.el)+$1.bar)+2),_22=$f($1.mw+1);_23>=_22;_23-=1){$k[$j++]=$f($f($f($1.nm-_23)-$1.ew)-1);$k[$j++]=$f($f($1.el-$1.bar)-3);$1.ncr();$1.lval=$f($k[--$j]+$1.lval)}$1.sval=$f($1.sval-$1.lval*$f($f($1.el-$1.bar)-1))}else{if($f($1.nm-$1.ew)>$1.mw){$1.sval=$f($1.sval-1)}}$1.val=$f($1.val-$1.sval);if($1.val<0){break}$1.ew=$1.ew+1;var _2N=$1.bar;$1.mask=$1.mask&~(_2N<0?1>>>-_2N:1<<_2N)}$1.val=$f($1.val+$1.sval);$1.nm=$f($1.nm-$1.ew);$p($1.out,$1.bar,$1.ew)}$p($1.out,$f($1.el-1),$1.nm);$k[$j++]=$1.out};for(var _2Z=0;_2Z<=11;_2Z+=1){$1.i=_2Z;var _2a=$1.binval;var _2b=$1.i;$p(_2a,_2b+1,$f($g(_2a,_2b+1)+$g($1.binval,$1.i)%2013571*10));$p($1.binval,$1.i,~~($g($1.binval,$1.i)/2013571))}$1.d2=$g($1.binval,12)%2013571;var _2n=$1.binval;$p(_2n,12,~~($g(_2n,12)/2013571));$1.d1=0;$1.i=true;for(var _2p=0;_2p<=12;_2p+=1){$1.j=_2p;var _2s=$g($1.binval,$1.j);$k[$j++]=_2s;if(_2s==0&&$1.i){$j--}else{$1.i=false;$1.d1=$f($1.d1+$k[--$j]*~~Math.pow(10,12-$1.j))}}$1.tab267=$a([183063,0,17,9,6,3,6538,28,820063,183064,13,13,5,4,875,728,1000775,820064,9,17,3,6,28,6454,1491020,1000776,15,11,5,4,2415,203,1979844,1491021,11,15,4,5,203,2408,1996938,1979845,19,7,8,1,17094,1,2013570,1996939,7,19,1,8,1,16632]);$1.i=0;for(;;){if($1.d1<=$g($1.tab267,$1.i)){var _34=$G($1.tab267,$1.i+1,7);for(var _35=0,_36=_34.length;_35<_36;_35++){$k[$j++]=$g(_34,_35)}$1.d1te=$k[--$j];$1.d1to=$k[--$j];$1.d1mwe=$k[--$j];$1.d1mwo=$k[--$j];$1.d1ele=$k[--$j];$1.d1elo=$k[--$j];$1.d1gs=$k[--$j];break}$1.i=$1.i+8}$1.i=0;for(;;){if($1.d2<=$g($1.tab267,$1.i)){var _3M=$G($1.tab267,$1.i+1,7);for(var _3N=0,_3O=_3M.length;_3N<_3O;_3N++){$k[$j++]=$g(_3M,_3N)}$1.d2te=$k[--$j];$1.d2to=$k[--$j];$1.d2mwe=$k[--$j];$1.d2mwo=$k[--$j];$1.d2ele=$k[--$j];$1.d2elo=$k[--$j];$1.d2gs=$k[--$j];break}$1.i=$1.i+8}$k[$j++]="d1wo";$k[$j++]=~~($f($1.d1-$1.d1gs)/$1.d1te);$k[$j++]=$1.d1elo;$k[$j++]=$1.d1mwo;$k[$j++]=7;$k[$j++]=false;$1.getRSSwidths();var _3d=$k[--$j];$1[$k[--$j]]=_3d;$k[$j++]="d1we";$k[$j++]=$f($1.d1-$1.d1gs)%$1.d1te;$k[$j++]=$1.d1ele;$k[$j++]=$1.d1mwe;$k[$j++]=7;$k[$j++]=true;$1.getRSSwidths();var _3k=$k[--$j];$1[$k[--$j]]=_3k;$k[$j++]="d2wo";$k[$j++]=~~($f($1.d2-$1.d2gs)/$1.d2te);$k[$j++]=$1.d2elo;$k[$j++]=$1.d2mwo;$k[$j++]=7;$k[$j++]=false;$1.getRSSwidths();var _3r=$k[--$j];$1[$k[--$j]]=_3r;$k[$j++]="d2we";$k[$j++]=$f($1.d2-$1.d2gs)%$1.d2te;$k[$j++]=$1.d2ele;$k[$j++]=$1.d2mwe;$k[$j++]=7;$k[$j++]=true;$1.getRSSwidths();var _3y=$k[--$j];$1[$k[--$j]]=_3y;$1.d1w=$a(14);for(var _41=0;_41<=6;_41+=1){$1.i=_41;$p($1.d1w,$1.i*2,$g($1.d1wo,$1.i));$p($1.d1w,$1.i*2+1,$g($1.d1we,$1.i))}$1.d2w=$a(14);for(var _4D=0;_4D<=6;_4D+=1){$1.i=_4D;$p($1.d2w,$1.i*2,$g($1.d2wo,$1.i));$p($1.d2w,$1.i*2+1,$g($1.d2we,$1.i))}$k[$j++]=Infinity;var _4O=$1.d1w;for(var _4P=0,_4Q=_4O.length;_4P<_4Q;_4P++){$k[$j++]=$g(_4O,_4P)}var _4S=$1.d2w;for(var _4T=0,_4U=_4S.length;_4T<_4U;_4T++){$k[$j++]=$g(_4S,_4T)}$1.widths=$a();$1.checkweights=$a([1,3,9,27,81,65,17,51,64,14,42,37,22,66,20,60,2,6,18,54,73,41,34,13,39,28,84,74]);$k[$j++]=Infinity;for(var _4Y=0;_4Y<=43;_4Y+=1){$k[$j++]=_4Y}$k[$j++]=45;$k[$j++]=52;$k[$j++]=57;for(var _4Z=63;_4Z<=66;_4Z+=1){$k[$j++]=_4Z}for(var _4a=73;_4a<=79;_4a+=1){$k[$j++]=_4a}$k[$j++]=82;for(var _4b=126;_4b<=130;_4b+=1){$k[$j++]=_4b}$k[$j++]=132;for(var _4c=141;_4c<=146;_4c+=1){$k[$j++]=_4c}for(var _4d=210;_4d<=217;_4d+=1){$k[$j++]=_4d}$k[$j++]=220;for(var _4e=316;_4e<=320;_4e+=1){$k[$j++]=_4e}$k[$j++]=322;$k[$j++]=323;$k[$j++]=326;$k[$j++]=337;$1.checkseq=$a();$1.checksum=0;for(var _4g=0;_4g<=27;_4g+=1){$1.i=_4g;$1.checksum=$f($1.checksum+$g($1.widths,$1.i)*$g($1.checkweights,$1.i))}$1.checksum=$1.checksum%89;$1.seq=$g($1.checkseq,$1.checksum);$k[$j++]="swidths";$k[$j++]=~~($1.seq/21);$k[$j++]=8;$k[$j++]=3;$k[$j++]=6;$k[$j++]=false;$1.getRSSwidths();var _4t=$k[--$j];$1[$k[--$j]]=_4t;$k[$j++]="bwidths";$k[$j++]=$1.seq%21;$k[$j++]=8;$k[$j++]=3;$k[$j++]=6;$k[$j++]=false;$1.getRSSwidths();var _4w=$k[--$j];$1[$k[--$j]]=_4w;$1.checkwidths=$a([0,0,0,0,0,0,0,0,0,0,0,0,1,1]);for(var _4z=0;_4z<=5;_4z+=1){$1.i=_4z;$p($1.checkwidths,$1.i*2,$g($1.swidths,$1.i));$p($1.checkwidths,$1.i*2+1,$g($1.bwidths,$1.i))}$k[$j++]=Infinity;var _5A=$1.d1w;$k[$j++]=1;for(var _5B=0,_5C=_5A.length;_5B<_5C;_5B++){$k[$j++]=$g(_5A,_5B)}var _5E=$1.checkwidths;for(var _5F=0,_5G=_5E.length;_5F<_5G;_5F++){$k[$j++]=$g(_5E,_5F)}var _5I=$1.d2w;for(var _5J=0,_5K=_5I.length;_5J<_5K;_5J++){$k[$j++]=$g(_5I,_5J)}$k[$j++]=1;$k[$j++]=1;$k[$j++]=5;$1.sbs=$a();$k[$j++]=Infinity;var _5N=$1.sbs;$k[$j++]=Infinity;for(var _5P=0,_5Q=~~(($1.sbs.length+1)/2);_5P<_5Q;_5P++){$k[$j++]=$1.height}var _5S=$a();$k[$j++]=Infinity;for(var _5U=0,_5V=~~(($1.sbs.length+1)/2);_5U<_5V;_5U++){$k[$j++]=0}var _5W=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_5N;$k[$j++]="bhs";$k[$j++]=_5S;$k[$j++]="bbs";$k[$j++]=_5W;$k[$j++]="txt";$k[$j++]=$1.txt;$k[$j++]="textxalign";$k[$j++]="center";$k[$j++]="opt";$k[$j++]=$1.options;var _5Z=$d();$k[$j++]=_5Z;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_databarexpanded(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.parse=false;$1.dontlint=false;$1.dontdraw=false;$1.height=34/72;$1.format="expanded";$1.segments=-1;$1.linkage=false;$1.barxmult=34;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.height=+$1.height;$1.segments=~~$1.segments;$1.barxmult=~~$1.barxmult;if($1.segments==-1){var _A=$eq($1.format,"expandedstacked")?4:22;$1.segments=_A}else{if($1.segments<2||$1.segments>22||$1.segments%2!=0){$k[$j++]="bwipp.gs1databarexpandedBadSegments";$k[$j++]="The number of segments must be even from 2 to 22";bwipp_raiseerror()}}$1.expand=function(){var _F=new Map([["parse",$1.parse],["parseonly",true],["parsefnc",false]]);$1.fncvals=_F;$k[$j++]=$1.fncvals;bwipp_parseinput()};$1.ais=$a([]);$1.vals=$a([]);var _J=$1.barcode;$k[$j++]=$G(_J,1,_J.length-1);for(;;){var _L=$k[--$j];$k[$j++]=_L;if($eq(_L,"")){break}$x($k[--$j],")");$j--;var _N=$k[--$j];var _O=$k[--$j];$k[$j++]=_N;$k[$j++]=_O;$j--;var _P=$k[--$j];var _Q=$k[--$j];$k[$j++]=_P;$x(_Q,"(");if($k[--$j]){var _S=$k[--$j];var _T=$k[--$j];$k[$j++]=_S;$k[$j++]=_T;$j--;var _U=$k[--$j];var _V=$k[--$j];var _W=$k[--$j];$k[$j++]=_V;$k[$j++]=_W;$k[$j++]=_U}else{var _X=$k[--$j];var _Y=$k[--$j];$k[$j++]="";$k[$j++]=_Y;$k[$j++]=_X}$k[$j++]=Infinity;$q($1.ais);var _b=$k[$j-1-($m()+2)];$k[$j++]=_b;$1.ais=$a();$k[$j++]=Infinity;$q($1.vals);var _f=$k[$j-1-($m()+1)];$k[$j++]=_f;$1.expand();$1.vals=$a();$j-=2}$j--;if(!$1.dontlint){$k[$j++]=$1.ais;$k[$j++]=$1.vals;bwipp_gs1lint();$j--}for(;;){if($1.ais.length==2){if($eq($g($1.ais,0),"01")&&$eq($g($1.ais,1),"3103")){if($eq($G($g($1.vals,0),0,1),"9")&&~~$z($g($1.vals,1))<=32767){$k[$j++]="0100";$k[$j++]=false;break}}}if($1.ais.length==2){if($eq($g($1.ais,0),"01")&&$eq($g($1.ais,1),"3202")){if($eq($G($g($1.vals,0),0,1),"9")&&~~$z($g($1.vals,1))<=9999){$k[$j++]="0101";$k[$j++]=false;break}}}if($1.ais.length==2){if($eq($g($1.ais,0),"01")&&$eq($g($1.ais,1),"3203")){if($eq($G($g($1.vals,0),0,1),"9")&&~~$z($g($1.vals,1))<=22767){$k[$j++]="0101";$k[$j++]=false;break}}}var _1F=$1.ais.length;if(_1F==2||_1F==3){$k[$j++]="ai310x";$k[$j++]=false;for(var _1G=3100;_1G<=3109;_1G+=1){var _1L=$k[--$j];$k[$j++]=_1L||$eq($R($s(4),_1G,10),$g($1.ais,1))}var _1M=$k[--$j];$1[$k[--$j]]=_1M;$k[$j++]="ai320x";$k[$j++]=false;for(var _1O=3200;_1O<=3209;_1O+=1){var _1T=$k[--$j];$k[$j++]=_1T||$eq($R($s(4),_1O,10),$g($1.ais,1))}var _1U=$k[--$j];$1[$k[--$j]]=_1U;if($1.ais.length==3){var _1X=$a(["11","13","15","17"]);$k[$j++]="aibad";$k[$j++]=true;for(var _1Y=0,_1Z=_1X.length;_1Y<_1Z;_1Y++){var _1d=$k[--$j];$k[$j++]=_1d&&$ne($g(_1X,_1Y),$g($1.ais,2))}var _1e=$k[--$j];$1[$k[--$j]]=_1e}else{$1.aibad=false}if($eq($g($1.ais,0),"01")&&($1.ai310x||$1.ai320x)&&!$1.aibad){if($1.ais.length==3){var _1u=~~$z($G($g($1.vals,2),2,2));var _1y=~~$z($G($g($1.vals,2),4,2));if($eq($G($g($1.vals,0),0,1),"9")&&~~$z($g($1.vals,1))<=99999&&(_1u>=1&&_1u<=12)&&(_1y>=0&&_1y<=31)){if($1.ai310x&&$eq($g($1.ais,2),"11")){$k[$j++]="0111000";$k[$j++]=false;break}if($1.ai320x&&$eq($g($1.ais,2),"11")){$k[$j++]="0111001";$k[$j++]=false;break}if($1.ai310x&&$eq($g($1.ais,2),"13")){$k[$j++]="0111010";$k[$j++]=false;break}if($1.ai320x&&$eq($g($1.ais,2),"13")){$k[$j++]="0111011";$k[$j++]=false;break}if($1.ai310x&&$eq($g($1.ais,2),"15")){$k[$j++]="0111100";$k[$j++]=false;break}if($1.ai320x&&$eq($g($1.ais,2),"15")){$k[$j++]="0111101";$k[$j++]=false;break}if($1.ai310x&&$eq($g($1.ais,2),"17")){$k[$j++]="0111110";$k[$j++]=false;break}if($1.ai320x&&$eq($g($1.ais,2),"17")){$k[$j++]="0111111";$k[$j++]=false;break}}}else{if($eq($G($g($1.vals,0),0,1),"9")&&~~$z($g($1.vals,1))<=99999){if($1.ai310x){$k[$j++]="0111000";$k[$j++]=false;break}if($1.ai320x){$k[$j++]="0111001";$k[$j++]=false;break}}}}}if($1.ais.length>=2){$k[$j++]="ai392x";$k[$j++]=false;for(var _2V=3920;_2V<=3923;_2V+=1){var _2a=$k[--$j];$k[$j++]=_2a||$eq($R($s(4),_2V,10),$g($1.ais,1))}var _2b=$k[--$j];$1[$k[--$j]]=_2b;if($eq($g($1.ais,0),"01")&&$1.ai392x){if($eq($G($g($1.vals,0),0,1),"9")){$k[$j++]="01100";$k[$j++]=true;break}}}if($1.ais.length>=2){$k[$j++]="ai393x";$k[$j++]=false;for(var _2k=3930;_2k<=3933;_2k+=1){var _2p=$k[--$j];$k[$j++]=_2p||$eq($R($s(4),_2k,10),$g($1.ais,1))}var _2q=$k[--$j];$1[$k[--$j]]=_2q;if($eq($g($1.ais,0),"01")&&$1.ai393x){if($eq($G($g($1.vals,0),0,1),"9")){$k[$j++]="01101";$k[$j++]=true;break}}}if($eq($g($1.ais,0),"01")){$k[$j++]="1";$k[$j++]=true;break}$k[$j++]="00";$k[$j++]=true;break}$1.gpfallow=$k[--$j];$1.method=$k[--$j];$1.conv12to40=function(){var _33=$Z($s(40),"0000000000000000000000000000000000000000");var _34=$k[--$j];var _38=$R($s(10),~~$z($G(_34,0,3)),2);$P($G(_33,0,10),10-_38.length,_38);var _3C=$R($s(10),~~$z($G(_34,3,3)),2);$P($G(_33,10,10),10-_3C.length,_3C);var _3G=$R($s(10),~~$z($G(_34,6,3)),2);$P($G(_33,20,10),10-_3G.length,_3G);var _3K=$R($s(10),~~$z($G(_34,9,3)),2);$P($G(_33,30,10),10-_3K.length,_3K);$k[$j++]=_33;$k[$j++]=_34;$j--};$1.conv13to44=function(){var _3M=$Z($s(44),"00000000000000000000000000000000000000000000");var _3N=$k[--$j];var _3R=$R($s(4),~~$z($G(_3N,0,1)),2);$P($G(_3M,0,4),4-_3R.length,_3R);$k[$j++]=_3M;$k[$j++]=_3M;$k[$j++]=$G(_3N,1,12);$1.conv12to40();var _3T=$k[--$j];$P($k[--$j],4,_3T)};$1.tobin=function(){var _3W=$s($k[--$j]);$k[$j++]=_3W;for(var _3Y=0,_3X=_3W.length-1;_3Y<=_3X;_3Y+=1){var _3Z=$k[--$j];$p(_3Z,_3Y,48);$k[$j++]=_3Z}var _3a=$k[--$j];var _3d=$R($s(_3a.length),$k[--$j],2);$P(_3a,_3a.length-_3d.length,_3d);$k[$j++]=_3a};$1.fnc1=-1;$1.lnumeric=-2;$1.lalphanumeric=-3;$1.liso646=-4;if($eq($1.method,"00")){$1.cdf=$a([]);$1.gpf=$a([])}if($eq($1.method,"1")){$k[$j++]="cdf";$k[$j++]=$G($g($1.vals,0),0,13);$1.conv13to44();var _3l=$k[--$j];$1[$k[--$j]]=_3l;$k[$j++]=Infinity;var _3n=$1.cdf;for(var _3o=0,_3p=_3n.length;_3o<_3p;_3o++){$k[$j++]=$f($g(_3n,_3o)-48)}$1.cdf=$a();$1.gpf=$a([]);$1.ais=$G($1.ais,1,$1.ais.length-1);$1.vals=$G($1.vals,1,$1.vals.length-1)}if($eq($1.method,"0100")){$1.cdf=$s(55);$k[$j++]=$1.cdf;$k[$j++]=0;$k[$j++]=$G($g($1.vals,0),1,12);$1.conv12to40();var _45=$k[--$j];var _46=$k[--$j];$P($k[--$j],_46,_45);$k[$j++]=$1.cdf;$k[$j++]=40;$k[$j++]=~~$z($g($1.vals,1));$k[$j++]=15;$1.tobin();var _4B=$k[--$j];var _4C=$k[--$j];$P($k[--$j],_4C,_4B);$k[$j++]=Infinity;var _4E=$1.cdf;for(var _4F=0,_4G=_4E.length;_4F<_4G;_4F++){$k[$j++]=$g(_4E,_4F)-48}$1.cdf=$a();$1.gpf=$a([]);$1.ais=$a([]);$1.vals=$a([])}if($eq($1.method,"0101")){$1.cdf=$s(55);$k[$j++]=$1.cdf;$k[$j++]=0;$k[$j++]=$G($g($1.vals,0),1,12);$1.conv12to40();var _4S=$k[--$j];var _4T=$k[--$j];$P($k[--$j],_4T,_4S);if($eq($g($1.ais,1),"3202")){$k[$j++]=~~$z($g($1.vals,1));$k[$j++]=15;$1.tobin()}else{$k[$j++]=~~$z($g($1.vals,1))+1e4;$k[$j++]=15;$1.tobin()}$P($1.cdf,40,$k[--$j]);$k[$j++]=Infinity;var _4d=$1.cdf;for(var _4e=0,_4f=_4d.length;_4e<_4f;_4e++){$k[$j++]=$g(_4d,_4e)-48}$1.cdf=$a();$1.gpf=$a([]);$1.ais=$a([]);$1.vals=$a([])}if($1.method.length==7){$1.cdf=$s(76);$k[$j++]=$1.cdf;$k[$j++]=0;$k[$j++]=$G($g($1.vals,0),1,12);$1.conv12to40();var _4r=$k[--$j];var _4s=$k[--$j];$P($k[--$j],_4s,_4r);var _4u=$s(6);$P(_4u,0,$G($g($1.ais,1),3,1));$P(_4u,1,$G($g($1.vals,1),1,5));$k[$j++]=~~$z(_4u);$k[$j++]=20;$1.tobin();$P($1.cdf,40,$k[--$j]);if($1.ais.length==3){var _55=$g($1.vals,2);$k[$j++]=~~$z($G(_55,0,2))*384+((~~$z($G(_55,2,2))-1)*32+~~$z($G(_55,4,2)))}else{$k[$j++]=38400}$k[$j++]=16;$1.tobin();$P($1.cdf,60,$k[--$j]);$k[$j++]=Infinity;var _5B=$1.cdf;for(var _5C=0,_5D=_5B.length;_5C<_5D;_5C++){$k[$j++]=$g(_5B,_5C)-48}$1.cdf=$a();$1.gpf=$a([]);$1.ais=$a([]);$1.vals=$a([])}if($eq($1.method,"01100")){$1.cdf=$s(42);$k[$j++]=$1.cdf;$k[$j++]=0;$k[$j++]=$G($g($1.vals,0),1,12);$1.conv12to40();var _5P=$k[--$j];var _5Q=$k[--$j];$P($k[--$j],_5Q,_5P);$k[$j++]=$1.cdf;$k[$j++]=40;$k[$j++]=~~$z($G($g($1.ais,1),3,1));$k[$j++]=2;$1.tobin();var _5W=$k[--$j];var _5X=$k[--$j];$P($k[--$j],_5X,_5W);$k[$j++]=Infinity;var _5Z=$1.cdf;for(var _5a=0,_5b=_5Z.length;_5a<_5b;_5a++){$k[$j++]=$g(_5Z,_5a)-48}$1.cdf=$a();$k[$j++]=Infinity;$F($g($1.vals,1));if($1.ais.length>2){$k[$j++]=$1.fnc1}$1.gpf=$a();$1.ais=$G($1.ais,2,$1.ais.length-2);$1.vals=$G($1.vals,2,$1.vals.length-2)}if($eq($1.method,"01101")){$1.cdf=$s(52);$k[$j++]=$1.cdf;$k[$j++]=0;$k[$j++]=$G($g($1.vals,0),1,12);$1.conv12to40();var _5v=$k[--$j];var _5w=$k[--$j];$P($k[--$j],_5w,_5v);$k[$j++]=$1.cdf;$k[$j++]=40;$k[$j++]=~~$z($G($g($1.ais,1),3,1));$k[$j++]=2;$1.tobin();var _62=$k[--$j];var _63=$k[--$j];$P($k[--$j],_63,_62);$k[$j++]=$1.cdf;$k[$j++]=42;$k[$j++]=~~$z($G($g($1.vals,1),0,3));$k[$j++]=10;$1.tobin();var _69=$k[--$j];var _6A=$k[--$j];$P($k[--$j],_6A,_69);$k[$j++]=Infinity;var _6C=$1.cdf;for(var _6D=0,_6E=_6C.length;_6D<_6E;_6D++){$k[$j++]=$g(_6C,_6D)-48}$1.cdf=$a();$k[$j++]=Infinity;var _6I=$g($1.vals,1);var _6J=$G(_6I,3,_6I.length-3);for(var _6K=0,_6L=_6J.length;_6K<_6L;_6K++){$k[$j++]=$g(_6J,_6K)}if($1.ais.length>2){$k[$j++]=$1.fnc1}$1.gpf=$a();$1.ais=$G($1.ais,2,$1.ais.length-2);$1.vals=$G($1.vals,2,$1.vals.length-2)}if($1.gpfallow){$1.vlf=$a(2)}else{$1.vlf=$a([])}$1.aifixed=new Map;$k[$j++]=Infinity;for(var _6Z=0;_6Z<=4;_6Z+=1){$k[$j++]=_6Z}var _6a=$a();for(var _6b=0,_6c=_6a.length;_6b<_6c;_6b++){var _6f=$Z($s(2),"00");$p(_6f,1,$f($g(_6a,_6b)+48));$p($1.aifixed,_6f,_6f)}$k[$j++]=Infinity;for(var _6h=11;_6h<=20;_6h+=1){$k[$j++]=_6h}$k[$j++]=23;for(var _6i=31;_6i<=36;_6i+=1){$k[$j++]=_6i}$k[$j++]=41;var _6j=$a();for(var _6k=0,_6l=_6j.length;_6k<_6l;_6k++){var _6o=$R($s(2),$g(_6j,_6k),10);$p($1.aifixed,_6o,_6o)}$k[$j++]=Infinity;for(var _6q=0;_6q<=119;_6q+=1){var _6s=$Z($s(2),"00");var _6u=$R($s(2),_6q,11);$P(_6s,2-_6u.length,_6u);$k[$j++]=_6q;$k[$j++]=_6s;if($g(_6s,0)==65){var _6w=$k[--$j];$p(_6w,0,94);$k[$j++]=_6w}var _6x=$k[--$j];$k[$j++]=_6x;if($g(_6x,1)==65){var _6z=$k[--$j];$p(_6z,1,94);$k[$j++]=_6z}var _70=$k[--$j];var _73=$Z($s(7),"0000000");var _75=$R($s(7),$f($k[--$j]+8),2);$P(_73,7-_75.length,_75);$k[$j++]=_70;$k[$j++]=_73}$k[$j++]=$1.lalphanumeric;$k[$j++]="0000";$1.numeric=$d();$k[$j++]=Infinity;for(var _78=48;_78<=57;_78+=1){$k[$j++]=_78;$k[$j++]=_78-43;$k[$j++]=5;$1.tobin()}$k[$j++]=$1.fnc1;$k[$j++]="01111";for(var _7A=65;_7A<=90;_7A+=1){$k[$j++]=_7A;$k[$j++]=_7A-33;$k[$j++]=6;$1.tobin()}$k[$j++]=42;$k[$j++]="111010";for(var _7B=44;_7B<=47;_7B+=1){$k[$j++]=_7B;$k[$j++]=_7B+15;$k[$j++]=6;$1.tobin()}$k[$j++]=$1.lnumeric;$k[$j++]="000";$k[$j++]=$1.liso646;$k[$j++]="00100";$1.alphanumeric=$d();$k[$j++]=Infinity;for(var _7F=48;_7F<=57;_7F+=1){$k[$j++]=_7F;$k[$j++]=_7F-43;$k[$j++]=5;$1.tobin()}$k[$j++]=$1.fnc1;$k[$j++]="01111";for(var _7H=65;_7H<=90;_7H+=1){$k[$j++]=_7H;$k[$j++]=_7H-1;$k[$j++]=7;$1.tobin()}for(var _7I=97;_7I<=122;_7I+=1){$k[$j++]=_7I;$k[$j++]=_7I-7;$k[$j++]=7;$1.tobin()}$k[$j++]=33;$k[$j++]="11101000";$k[$j++]=34;$k[$j++]="11101001";for(var _7J=37;_7J<=47;_7J+=1){$k[$j++]=_7J;$k[$j++]=_7J+197;$k[$j++]=8;$1.tobin()}for(var _7K=58;_7K<=63;_7K+=1){$k[$j++]=_7K;$k[$j++]=_7K+187;$k[$j++]=8;$1.tobin()}$k[$j++]=95;$k[$j++]="11111011";$k[$j++]=32;$k[$j++]="11111100";$k[$j++]=$1.lnumeric;$k[$j++]="000";$k[$j++]=$1.lalphanumeric;$k[$j++]="00100";$1.iso646=$d();for(var _7Q=0,_7P=$1.ais.length-1;_7Q<=_7P;_7Q+=1){$1.i=_7Q;$1.ai=$g($1.ais,$1.i);$1.val=$g($1.vals,$1.i);var _7a=$a($1.gpf.length+$1.ai.length+$1.val.length);$P(_7a,0,$1.gpf);$k[$j++]=_7a;$k[$j++]=_7a;$k[$j++]=$1.gpf.length;$k[$j++]=$1.ai;$k[$j++]=Infinity;var _7e=$k[--$j];var _7f=$k[--$j];$k[$j++]=_7e;$F(_7f);var _7g=$a();var _7h=$k[--$j];$P($k[--$j],_7h,_7g);var _7j=$k[--$j];$k[$j++]=_7j;$k[$j++]=_7j;$k[$j++]=$1.gpf.length+$1.ai.length;$k[$j++]=$1.val;$k[$j++]=Infinity;var _7n=$k[--$j];var _7o=$k[--$j];$k[$j++]=_7n;$F(_7o);var _7p=$a();var _7q=$k[--$j];$P($k[--$j],_7q,_7p);$1.gpf=$k[--$j];var _7y=$g($1.aifixed,$G($1.ai,0,2))!==undefined;if($1.i!=$1.ais.length-1&&!_7y){var _80=$a($1.gpf.length+1);$P(_80,0,$1.gpf);$p(_80,$1.gpf.length,$1.fnc1);$1.gpf=_80}}$1.rembits=function(){var _84=$k[--$j];var _85=48;var _86=~~Math.ceil(_84/12)*12;if(~~Math.ceil(_84/12)*12<48){var _=_85;_85=_86;_86=_}var _87=~~(_86/12);$k[$j++]=_84;$k[$j++]=_86;$k[$j++]=_87;if(_87%$1.segments==1){var _89=$k[--$j];var _8A=$k[--$j];$k[$j++]=$f(_89+1)*12;$k[$j++]=_8A;$j--}else{$j--}var _8B=$k[--$j];var _8C=$k[--$j];$k[$j++]=$f(_8B-_8C)};$1.encode=function(){var _8D=$k[--$j];$k[$j++]=_8D;if($ne(_8D,"raw")){var _8E=$k[--$j];var _8G=$g(_8E,$k[--$j]);$k[$j++]=_8G}else{$j--}$k[$j++]=Infinity;var _8H=$k[--$j];var _8I=$k[--$j];$k[$j++]=_8H;$F(_8I,function(){var _8J=$k[--$j];$k[$j++]=$f(_8J-48)});var _8K=$a();$P($1.gpfenc,$1.j,_8K);$1.j=_8K.length+$1.j};$k[$j++]=Infinity;for(var _8P=0,_8Q=$1.gpf.length;_8P<_8Q;_8P++){$k[$j++]=0}$k[$j++]=0;$k[$j++]=-1;$1.numericruns=$a();$k[$j++]=Infinity;for(var _8T=0,_8U=$1.gpf.length;_8T<_8U;_8T++){$k[$j++]=0}$k[$j++]=0;$1.alphanumericruns=$a();$k[$j++]=Infinity;for(var _8X=0,_8Y=$1.gpf.length;_8X<_8Y;_8X++){$k[$j++]=0}$k[$j++]=9999;$1.nextiso646only=$a();for(var _8b=$1.gpf.length-1;_8b>=0;_8b-=1){$1.i=_8b;var _8g=$Z($s(2),"00");var _8j=$g($1.gpf,$1.i);$k[$j++]=$g($1.gpf,$1.i);$k[$j++]=_8g;$k[$j++]=_8g;$k[$j++]=0;$k[$j++]=_8j;if(_8j==$1.fnc1){$j--;$k[$j++]=94}var _8l=$k[--$j];var _8m=$k[--$j];$p($k[--$j],_8m,_8l);if($1.i<$1.gpf.length-1){var _8q=$k[--$j];var _8t=$g($1.gpf,$1.i+1);$k[$j++]=_8q;$k[$j++]=_8q;$k[$j++]=1;$k[$j++]=_8t;if(_8t==$1.fnc1){$j--;$k[$j++]=94}var _8v=$k[--$j];var _8w=$k[--$j];$p($k[--$j],_8w,_8v)}var _90=$g($1.numeric,$k[--$j])!==undefined;if(_90){$p($1.numericruns,$1.i,$f($g($1.numericruns,$1.i+2)+2))}else{$p($1.numericruns,$1.i,0)}var _98=$k[--$j];var _9A=$g($1.alphanumeric,_98)!==undefined;$k[$j++]=_98;if(_9A){$p($1.alphanumericruns,$1.i,$f($g($1.alphanumericruns,$1.i+1)+1))}else{$p($1.alphanumericruns,$1.i,0)}var _9I=$k[--$j];var _9K=$g($1.iso646,_9I)!==undefined;var _9M=$g($1.alphanumeric,_9I)!==undefined;if(_9K&&!_9M){$p($1.nextiso646only,$1.i,0)}else{$p($1.nextiso646only,$1.i,$f($g($1.nextiso646only,$1.i+1)+1))}}$1.gpfenc=$a(252);$1.i=0;$1.j=0;$1.mode="numeric";for(;;){if($1.i==$1.gpf.length){break}for(;;){if($eq($1.mode,"numeric")){if($1.i<=$1.gpf.length-2){var _9a=$s(2);var _9d=$g($1.gpf,$1.i);$k[$j++]=_9a;$k[$j++]=_9a;$k[$j++]=0;$k[$j++]=_9d;if(_9d==$1.fnc1){$j--;$k[$j++]=94}var _9f=$k[--$j];var _9g=$k[--$j];$p($k[--$j],_9g,_9f);var _9i=$k[--$j];var _9l=$g($1.gpf,$1.i+1);$k[$j++]=_9i;$k[$j++]=_9i;$k[$j++]=1;$k[$j++]=_9l;if(_9l==$1.fnc1){$j--;$k[$j++]=94}var _9n=$k[--$j];var _9o=$k[--$j];$p($k[--$j],_9o,_9n);var _9q=$k[--$j];var _9s=$g($1.numeric,_9q)!==undefined;$k[$j++]=_9q;if(_9s){$k[$j++]=$1.numeric;$1.encode();$1.i=$1.i+2;break}$j--;$k[$j++]=$1.lalphanumeric;$k[$j++]=$1.numeric;$1.encode();$1.mode="alphanumeric";break}else{var _9z=$g($1.gpf,$1.i);if(_9z<48||_9z>57){$k[$j++]=$1.lalphanumeric;$k[$j++]=$1.numeric;$1.encode();$1.mode="alphanumeric";break}$k[$j++]="rem";$k[$j++]=12+1+$1.method.length+$1.vlf.length+$1.cdf.length+$1.j;$1.rembits();var _A6=$k[--$j];$1[$k[--$j]]=_A6;if($1.rem>=4&&$1.rem<=6){var _AD=$G($Z($s(6),"000000"),0,$1.rem);var _AI=$R($s(4),$f($g($1.gpf,$1.i)-47),2);$P(_AD,4-_AI.length,_AI);$k[$j++]=_AD;$k[$j++]="raw";$1.encode();$1.i=$1.i+1;break}else{var _AK=$s(2);$p(_AK,0,$g($1.gpf,$1.i));$p(_AK,1,94);$k[$j++]=_AK;$k[$j++]=$1.numeric;$1.encode();$1.i=$1.i+1;break}}}if($eq($1.mode,"alphanumeric")){if($g($1.gpf,$1.i)==$1.fnc1){$k[$j++]=$1.fnc1;$k[$j++]=$1.alphanumeric;$1.encode();$1.mode="numeric";$1.i=$1.i+1;break}var _Aa=$g($1.gpf,$1.i);var _Ac=$g($1.iso646,_Aa)!==undefined;var _Ae=$g($1.alphanumeric,_Aa)!==undefined;if(_Ac&&!_Ae){$k[$j++]=$1.liso646;$k[$j++]=$1.alphanumeric;$1.encode();$1.mode="iso646";break}if($g($1.numericruns,$1.i)>=6){$k[$j++]=$1.lnumeric;$k[$j++]=$1.alphanumeric;$1.encode();$1.mode="numeric";break}var _Ao=$g($1.numericruns,$1.i);if(_Ao>=4&&$f(_Ao+$1.i)==$1.gpf.length){$k[$j++]=$1.lnumeric;$k[$j++]=$1.alphanumeric;$1.encode();$1.mode="numeric";break}$k[$j++]=$g($1.gpf,$1.i);$k[$j++]=$1.alphanumeric;$1.encode();$1.i=$1.i+1;break}if($eq($1.mode,"iso646")){if($g($1.gpf,$1.i)==$1.fnc1){$k[$j++]=$1.fnc1;$k[$j++]=$1.iso646;$1.encode();$1.mode="numeric";$1.i=$1.i+1;break}if($g($1.numericruns,$1.i)>=4&&$g($1.nextiso646only,$1.i)>=10){$k[$j++]=$1.lnumeric;$k[$j++]=$1.iso646;$1.encode();$1.mode="numeric";break}if($g($1.alphanumericruns,$1.i)>=5&&$g($1.nextiso646only,$1.i)>=10){$k[$j++]=$1.lalphanumeric;$k[$j++]=$1.iso646;$1.encode();$1.mode="alphanumeric";break}$k[$j++]=$g($1.gpf,$1.i);$k[$j++]=$1.iso646;$1.encode();$1.i=$1.i+1;break}}}$1.gpf=$G($1.gpfenc,0,$1.j);var _BY=1+12+$1.method.length+$1.vlf.length+$1.cdf.length+$1.gpf.length;$k[$j++]=_BY;$k[$j++]=_BY;$1.rembits();var _BZ=$k[--$j];$1.pad=$a(_BZ);$k[$j++]=_BZ;if($1.vlf.length!=0){var _Bc=$k[--$j];var _Be=~~($f($k[--$j]+_Bc)/12);$p($1.vlf,0,_Be%2);var _Bg=_Be<=14?0:1;$p($1.vlf,1,_Bg)}else{$j-=2}if($1.pad.length>0){for(var _Bl=0,_Bk=$1.pad.length-1;_Bl<=_Bk;_Bl+=5){$1.i=_Bl;var _Bm=$1.pad;var _Bn=$1.i;var _Bo=$a([0,0,1,0,0]);var _Bp=$1.pad;var _Bq=$1.i;var _Br=5;var _Bs=_Bp.length-_Bq;if(_Bp.length-_Bq>5){var _=_Br;_Br=_Bs;_Bs=_}$P(_Bm,_Bn,$G(_Bo,0,_Bs))}if($eq($1.mode,"numeric")){$k[$j++]=Infinity;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$q($1.pad);$1.pad=$G($a(),0,$1.pad.length)}}$k[$j++]=Infinity;var _C0=$1.linkage?1:0;$k[$j++]=_C0;$F($1.method,function(){var _C2=$k[--$j];$k[$j++]=$f(_C2-48)});$q($1.vlf);$q($1.cdf);$q($1.gpf);$q($1.pad);$1.binval=$a();$1.datalen=~~($1.binval.length/12);$1.ncr=function(){var _C9=$k[--$j];var _CA=$k[--$j];var _CB=$f(_CA-_C9);if(_C9<$f(_CA-_C9)){var _=_CB;_CB=_C9;_C9=_}$k[$j++]=_CB;$k[$j++]=1;$k[$j++]=1;for(var _CD=_CA,_CC=$f(_C9+1);_CD>=_CC;_CD-=1){var _CE=$k[--$j];var _CF=$k[--$j];var _CG=$k[--$j];$k[$j++]=_CG;$k[$j++]=_CF;$k[$j++]=_CE*_CD;if($le(_CF,_CG)){var _CH=$k[--$j];var _CI=$k[--$j];$k[$j++]=$f(_CI+1);$k[$j++]=~~(_CH/_CI)}}for(;;){var _CJ=$k[--$j];var _CK=$k[--$j];var _CL=$k[--$j];$k[$j++]=_CL;$k[$j++]=_CK;$k[$j++]=_CJ;if($gt(_CK,_CL)){break}var _CM=$k[--$j];var _CN=$k[--$j];$k[$j++]=$f(_CN+1);$k[$j++]=~~(_CM/_CN)}var _CO=$k[--$j];var _CP=$k[--$j];$k[$j++]=_CO;$k[$j++]=_CP;$j--;var _CQ=$k[--$j];var _CR=$k[--$j];$k[$j++]=_CQ;$k[$j++]=_CR;$j--};$1.getRSSwidths=function(){$1.oe=$k[--$j];$1.el=$k[--$j];$1.mw=$k[--$j];$1.nm=$k[--$j];$1.val=$k[--$j];$1.out=$a($1.el);$1.mask=0;for(var _Cb=0,_Ca=$f($1.el-2);_Cb<=_Ca;_Cb+=1){$1.bar=_Cb;$1.ew=1;var _Cd=$1.bar;$1.mask=$1.mask|(_Cd<0?1>>>-_Cd:1<<_Cd);for(;;){$k[$j++]="sval";$k[$j++]=$f($f($1.nm-$1.ew)-1);$k[$j++]=$f($f($1.el-$1.bar)-2);$1.ncr();var _Ci=$k[--$j];$1[$k[--$j]]=_Ci;if($1.oe&&$1.mask==0&&$f($f($f($1.nm-$1.ew)-$1.el*2)+$1.bar*2)>=-2){$k[$j++]="sval";$k[$j++]=$1.sval;$k[$j++]=$f($f($f($1.nm-$1.ew)-$1.el)+$1.bar);$k[$j++]=$f($f($1.el-$1.bar)-2);$1.ncr();var _Cx=$k[--$j];var _Cy=$k[--$j];$1[$k[--$j]]=$f(_Cy-_Cx)}if($f($1.el-$1.bar)>2){$1.lval=0;for(var _D8=$f($f($f($f($1.nm-$1.ew)-$1.el)+$1.bar)+2),_D7=$f($1.mw+1);_D8>=_D7;_D8-=1){$k[$j++]=$f($f($f($1.nm-_D8)-$1.ew)-1);$k[$j++]=$f($f($1.el-$1.bar)-3);$1.ncr();$1.lval=$f($k[--$j]+$1.lval)}$1.sval=$f($1.sval-$1.lval*$f($f($1.el-$1.bar)-1))}else{if($f($1.nm-$1.ew)>$1.mw){$1.sval=$f($1.sval-1)}}$1.val=$f($1.val-$1.sval);if($1.val<0){break}$1.ew=$1.ew+1;var _DS=$1.bar;$1.mask=$1.mask&~(_DS<0?1>>>-_DS:1<<_DS)}$1.val=$f($1.val+$1.sval);$1.nm=$f($1.nm-$1.ew);$p($1.out,$1.bar,$1.ew)}$p($1.out,$f($1.el-1),$1.nm);$k[$j++]=$1.out};$1.tab174=$a([347,0,12,5,7,2,87,4,1387,348,10,7,5,4,52,20,2947,1388,8,9,4,5,30,52,3987,2948,6,11,3,6,10,104,4191,3988,4,13,1,8,1,204]);$1.dxw=$a($1.datalen);for(var _Dj=0,_Di=$1.datalen-1;_Dj<=_Di;_Dj+=1){$1.x=_Dj;$1.d=$G($1.binval,$1.x*12,12);$k[$j++]="d";$k[$j++]=0;for(var _Dn=0;_Dn<=11;_Dn+=1){$1.j=_Dn;var _Ds=$k[--$j];$k[$j++]=$f(_Ds+~~Math.pow(2,11-$1.j)*$g($1.d,$1.j))}var _Dt=$k[--$j];$1[$k[--$j]]=_Dt;$1.j=0;for(;;){if($le($1.d,$g($1.tab174,$1.j))){var _E1=$G($1.tab174,$1.j+1,7);for(var _E2=0,_E3=_E1.length;_E2<_E3;_E2++){$k[$j++]=$g(_E1,_E2)}$1.dte=$k[--$j];$1.dto=$k[--$j];$1.dmwe=$k[--$j];$1.dmwo=$k[--$j];$1.dele=$k[--$j];$1.delo=$k[--$j];$1.dgs=$k[--$j];break}$1.j=$1.j+8}$k[$j++]="dwo";$k[$j++]=~~($f($1.d-$1.dgs)/$1.dte);$k[$j++]=$1.delo;$k[$j++]=$1.dmwo;$k[$j++]=4;$k[$j++]=true;$1.getRSSwidths();var _EI=$k[--$j];$1[$k[--$j]]=_EI;$k[$j++]="dwe";$k[$j++]=$f($1.d-$1.dgs)%$1.dte;$k[$j++]=$1.dele;$k[$j++]=$1.dmwe;$k[$j++]=4;$k[$j++]=false;$1.getRSSwidths();var _EP=$k[--$j];$1[$k[--$j]]=_EP;$1.dw=$a(8);if($1.x%2==0){for(var _ET=0;_ET<=3;_ET+=1){$1.j=_ET;$p($1.dw,7-$1.j*2,$g($1.dwo,$1.j));$p($1.dw,6-$1.j*2,$g($1.dwe,$1.j))}}else{for(var _Ee=0;_Ee<=3;_Ee+=1){$1.j=_Ee;$p($1.dw,$1.j*2,$g($1.dwo,$1.j));$p($1.dw,$1.j*2+1,$g($1.dwe,$1.j))}}$p($1.dxw,$1.x,$1.dw)}$1.finderwidths=$a([1,8,4,1,1,1,1,4,8,1,3,6,4,1,1,1,1,4,6,3,3,4,6,1,1,1,1,6,4,3,3,2,8,1,1,1,1,8,2,3,2,6,5,1,1,1,1,5,6,2,2,2,9,1,1,1,1,9,2,2]);$1.finderseq=$a([$a([0,1]),$a([0,3,2]),$a([0,5,2,7]),$a([0,9,2,7,4]),$a([0,9,2,7,6,11]),$a([0,9,2,7,8,11,10]),$a([0,1,2,3,4,5,6,7]),$a([0,1,2,3,4,5,6,9,8]),$a([0,1,2,3,4,5,6,9,10,11]),$a([0,1,2,3,4,7,6,9,8,11,10])]);$1.seq=$g($1.finderseq,~~(($1.datalen-2)/2));$1.fxw=$a($1.seq.length);for(var _FB=0,_FA=$1.seq.length-1;_FB<=_FA;_FB+=1){$1.x=_FB;$p($1.fxw,$1.x,$G($1.finderwidths,$g($1.seq,$1.x)*5,5))}$1.checkweights=$a([-1,-1,-1,-1,-1,-1,-1,-1,77,96,32,81,27,9,3,1,20,60,180,118,143,7,21,63,205,209,140,117,39,13,145,189,193,157,49,147,19,57,171,91,132,44,85,169,197,136,186,62,185,133,188,142,4,12,36,108,50,87,29,80,97,173,128,113,150,28,84,41,123,158,52,156,166,196,206,139,187,203,138,46,76,17,51,153,37,111,122,155,146,119,110,107,106,176,129,43,16,48,144,10,30,90,59,177,164,125,112,178,200,137,116,109,70,210,208,202,184,130,179,115,190,204,68,93,31,151,191,134,148,22,66,198,172,94,71,2,40,154,192,64,162,54,18,6,120,149,25,75,14,42,126,167,175,199,207,69,23,78,26,79,103,98,83,38,114,131,182,124,159,53,88,170,127,183,61,161,55,165,73,8,24,72,5,15,89,100,174,58,160,194,135,45]);$k[$j++]=Infinity;$F($1.seq,function(){$q($G($1.checkweights,$k[--$j]*16,16))});var _FO=$a();$1.checkweightseq=$G(_FO,8,_FO.length-8);$k[$j++]=Infinity;var _FQ=$1.dxw;for(var _FR=0,_FS=_FQ.length;_FR<_FS;_FR++){$F($g(_FQ,_FR))}$1.widths=$a();$1.checksum=0;for(var _FX=0,_FW=$1.widths.length-1;_FX<=_FW;_FX+=1){$1.i=_FX;$1.checksum=$f($1.checksum+$g($1.widths,$1.i)*$g($1.checkweightseq,$1.i))}$1.checksum=$f($1.checksum%211+($1.datalen-3)*211);$1.i=0;for(;;){if($1.checksum<=$g($1.tab174,$1.i)){var _Fn=$G($1.tab174,$1.i+1,7);for(var _Fo=0,_Fp=_Fn.length;_Fo<_Fp;_Fo++){$k[$j++]=$g(_Fn,_Fo)}$1.cte=$k[--$j];$1.cto=$k[--$j];$1.cmwe=$k[--$j];$1.cmwo=$k[--$j];$1.cele=$k[--$j];$1.celo=$k[--$j];$1.cgs=$k[--$j];break}$1.i=$1.i+8}$k[$j++]="cwo";$k[$j++]=~~($f($1.checksum-$1.cgs)/$1.cte);$k[$j++]=$1.celo;$k[$j++]=$1.cmwo;$k[$j++]=4;$k[$j++]=true;$1.getRSSwidths();var _G4=$k[--$j];$1[$k[--$j]]=_G4;$k[$j++]="cwe";$k[$j++]=$f($1.checksum-$1.cgs)%$1.cte;$k[$j++]=$1.cele;$k[$j++]=$1.cmwe;$k[$j++]=4;$k[$j++]=false;$1.getRSSwidths();var _GB=$k[--$j];$1[$k[--$j]]=_GB;$1.cw=$a(8);for(var _GE=0;_GE<=3;_GE+=1){$1.i=_GE;$p($1.cw,$1.i*2,$g($1.cwo,$1.i));$p($1.cw,$1.i*2+1,$g($1.cwe,$1.i))}var _GP=$a(22);$p(_GP,0,$1.cw);$P(_GP,1,$1.dxw);$1.dxw=$G(_GP,0,$1.datalen+1);$1.datalen=$1.dxw.length;$1.rows=$a(~~Math.ceil($1.datalen/$1.segments));$1.numrows=$1.rows.length;for(var _Gb=0,_Ga=$1.numrows-1;_Gb<=_Ga;_Gb+=1){$1.r=_Gb;$k[$j++]=Infinity;if($1.segments%4!=0&&$1.r%2==1){$k[$j++]=0}$k[$j++]=1;$k[$j++]=1;for(var _Gg=0,_Gf=$1.segments-1;_Gg<=_Gf;_Gg+=1){$1.pos=_Gg+$1.r*$1.segments;if($1.pos<$1.datalen){$F($g($1.dxw,$1.pos));if($1.pos%2==0){$F($g($1.fxw,~~($1.pos/2)))}}}var _Gs=$m()+2;$k[$j++]=1;$k[$j++]=1;$r($a(_Gs));$p($1.rows,$1.r,$k[--$j]);$j--}if($ne($1.format,"expandedstacked")){var _Gz=$g($1.rows,0);$1.sbs=$G(_Gz,1,_Gz.length-1);$k[$j++]=Infinity;$k[$j++]=1;for(var _H3=0,_H2=$1.datalen-1;_H3<=_H2;_H3+=1){$1.i=_H3;$F($g($1.dxw,$1.i));if($1.i%2==0){$F($g($1.fxw,~~($1.i/2)))}}$k[$j++]=1;$k[$j++]=1;$1.sbs=$a();delete $1.options["parse"];$k[$j++]=Infinity;var _HD=$1.sbs;$k[$j++]=Infinity;for(var _HF=0,_HG=~~(($1.sbs.length+1)/2);_HF<_HG;_HF++){$k[$j++]=$1.height}var _HI=$a();$k[$j++]=Infinity;for(var _HK=0,_HL=~~(($1.sbs.length+1)/2);_HK<_HL;_HK++){$k[$j++]=0}var _HM=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_HD;$k[$j++]="bhs";$k[$j++]=_HI;$k[$j++]="bbs";$k[$j++]=_HM;$k[$j++]="opt";$k[$j++]=$1.options;var _HO=$d();$k[$j++]=_HO;if(!$1.dontdraw){bwipp_renlinear()}}else{$1.seps=$a($1.numrows);for(var _HU=0,_HT=$1.numrows-1;_HU<=_HT;_HU+=1){$1.r=_HU;$1.row=$g($1.rows,$1.r);$k[$j++]=Infinity;for(var _Ha=0,_HZ=$1.row.length-1;_Ha<=_HZ;_Ha+=2){$1.i=_Ha;for(var _He=0,_Hf=$g($1.row,$1.i);_He<_Hf;_He++){$k[$j++]=0}if($1.i<$1.row.length-1){for(var _Hl=0,_Hm=$g($1.row,$1.i+1);_Hl<_Hm;_Hl++){$k[$j++]=1}}}$r($a($m()));$1.row=$k[--$j];$j--;$k[$j++]=Infinity;$F($1.row,function(){var _Hr=$k[--$j];$k[$j++]=$f(1-_Hr)});$1.sep=$a();$k[$j++]=Infinity;for(var _Hv=19,_Hu=$1.row.length-13;_Hv<=_Hu;_Hv+=98){$k[$j++]=_Hv}for(var _Hy=68,_Hx=$1.row.length-13;_Hy<=_Hx;_Hy+=98){$k[$j++]=_Hy}$1.finderpos=$a();var _I0=$1.finderpos;for(var _I1=0,_I2=_I0.length;_I1<_I2;_I1++){var _I3=$g(_I0,_I1);for(var _I5=_I3,_I4=$f(_I3+14);_I5<=_I4;_I5+=1){$1.i=_I5;if($g($1.row,$1.i)==0){if($g($1.row,$1.i-1)==1){$k[$j++]=1}else{var _IF=$g($1.sep,$1.i-1)==0?1:0;$k[$j++]=_IF}}else{$k[$j++]=0}$p($1.sep,$1.i,$k[--$j])}}$P($1.sep,0,$a([0,0,0,0]));$P($1.sep,$1.row.length-4,$a([0,0,0,0]));if($1.segments%4==0&&$1.r%2==1){if($g($1.rows,$1.r).length!=$g($1.rows,0).length&&$1.finderpos.length%2==1){$k[$j++]=Infinity;$k[$j++]=0;$q($1.row);$1.row=$a();$k[$j++]=Infinity;$k[$j++]=0;$q($1.sep);$1.sep=$a()}else{for(var _Ib=$1.row.length-1;_Ib>=0;_Ib-=1){$k[$j++]=$g($1.row,_Ib)}$r($1.row);$j--;for(var _Ig=$1.sep.length-1;_Ig>=0;_Ig-=1){$k[$j++]=$g($1.sep,_Ig)}$r($1.sep);$j--}}$p($1.rows,$1.r,$1.row);$p($1.seps,$1.r,$1.sep)}$1.pixx=$g($1.rows,0).length;$k[$j++]=Infinity;for(var _It=0,_Iu=$1.pixx;_It<_Iu;_It++){$k[$j++]=0}var _Iv=$a();$P(_Iv,0,$g($1.rows,$1.numrows-1));$p($1.rows,$1.numrows-1,_Iv);$k[$j++]=Infinity;for(var _J2=0,_J3=$1.pixx;_J2<_J3;_J2++){$k[$j++]=0}var _J4=$a();$P(_J4,0,$g($1.seps,$1.numrows-1));$p($1.seps,$1.numrows-1,_J4);$k[$j++]=Infinity;for(var _JB=0,_JC=~~($1.pixx/2)+1;_JB<_JC;_JB++){$k[$j++]=0;$k[$j++]=1}$1.sep=$G($a(),0,$1.pixx);$P($1.sep,0,$a([0,0,0,0]));$P($1.sep,$1.pixx-4,$a([0,0,0,0]));$k[$j++]=Infinity;for(var _JN=0,_JM=$1.numrows-1;_JN<=_JM;_JN+=1){$1.r=_JN;if($1.r!=0){$q($g($1.seps,$1.r))}for(var _JT=0,_JU=$1.barxmult;_JT<_JU;_JT++){$q($g($1.rows,$1.r))}if($1.r!=$1.numrows-1){$q($g($1.seps,$1.r));$q($1.sep)}}$1.pixs=$a();delete $1.options["parse"];var _Jo=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",$1.pixx],["pixy",~~($1.pixs.length/$1.pixx)],["height",~~($1.pixs.length/$1.pixx)/72],["width",$1.pixx/72],["opt",$1.options]]);$k[$j++]=_Jo;if(!$1.dontdraw){bwipp_renmatrix()}}}function bwipp_databarexpandedstacked(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$p($1.options,"dontdraw",true);$p($1.options,"format","expandedstacked");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_databarexpanded();var _9=$k[--$j];$1[$k[--$j]]=_9;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_gs1northamericancoupon(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.coupontextfont="OCR-B";$1.coupontextsize=9;$1.coupontextxoffset="unset";$1.coupontextyoffset="unset";$1.parse=false;$1.dontlint=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.coupontextfont=""+$1.coupontextfont;$1.coupontextsize=+$1.coupontextsize;if($ne($1.coupontextxoffset,"unset")){$1.coupontextxoffset=+$1.coupontextxoffset}if($ne($1.coupontextyoffset,"unset")){$1.coupontextyoffset=+$1.coupontextyoffset}$1.expand=function(){var _C=new Map([["parse",$1.parse],["parseonly",true],["parsefnc",false]]);$1.fncvals=_C;$k[$j++]=$1.fncvals;bwipp_parseinput()};$1.ais=$a([]);$1.vals=$a([]);var _G=$1.barcode;$k[$j++]=$G(_G,1,_G.length-1);for(;;){var _I=$k[--$j];$k[$j++]=_I;if($eq(_I,"")){break}$x($k[--$j],")");$j--;var _K=$k[--$j];var _L=$k[--$j];$k[$j++]=_K;$k[$j++]=_L;$j--;var _M=$k[--$j];var _N=$k[--$j];$k[$j++]=_M;$x(_N,"(");if($k[--$j]){var _P=$k[--$j];var _Q=$k[--$j];$k[$j++]=_P;$k[$j++]=_Q;$j--;var _R=$k[--$j];var _S=$k[--$j];var _T=$k[--$j];$k[$j++]=_S;$k[$j++]=_T;$k[$j++]=_R}else{var _U=$k[--$j];var _V=$k[--$j];$k[$j++]="";$k[$j++]=_V;$k[$j++]=_U}$k[$j++]=Infinity;$q($1.ais);var _Y=$k[$j-1-($m()+2)];$k[$j++]=_Y;$1.ais=$a();$k[$j++]=Infinity;$q($1.vals);var _c=$k[$j-1-($m()+1)];$k[$j++]=_c;$1.expand();$1.vals=$a();$j-=2}$j--;if($1.ais.length!=1){$k[$j++]="bwipp.gs1northamericancouponBadAIStructure";$k[$j++]="A GS1 North American Coupon should consist of a single AI (8110)";bwipp_raiseerror()}if($ne($g($1.ais,0),"8110")){$k[$j++]="bwipp.gs1northamericancouponBadAIStructure";$k[$j++]="A GS1 North American Coupon should consist of a single AI (8110)";bwipp_raiseerror()}if(!$1.dontlint){$k[$j++]=$1.ais;$k[$j++]=$1.vals;bwipp_gs1lint();$j--}$1.val=$g($1.vals,0);$1.vli=$f($g($1.val,0)-48);if($1.vli<0||$1.vli>6){$k[$j++]="bwipp.gs1northamericancouponBadVLI";$k[$j++]="The AI (8110) data should start with a Company Prefix length indicator in the range 0 to 6";bwipp_raiseerror()}$1.gcp=$G($1.val,1,$f($1.vli+6));$1.cod=$G($1.val,$f($1.vli+7),6);$1.coupontext=$s($1.gcp.length+7);$P($1.coupontext,0,$1.gcp);$P($1.coupontext,$1.gcp.length,"-");$P($1.coupontext,$1.gcp.length+1,$1.cod);delete $1.options["parse"];$p($1.options,"dontdraw",true);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_databarexpandedstacked();var _19=$k[--$j];$1[$k[--$j]]=_19;if($1.includetext){if($1.coupontextxoffset=="unset"){$1.coupontextxoffset=0}if($1.coupontextyoffset=="unset"){$1.coupontextyoffset=$f($g($1.args,"height")*72+3)}var _1H=$g($1.args,"txt")!==undefined;if(_1H){$1.txt=$g($1.args,"txt");$1.newtxt=$a($1.txt.length+1);$P($1.newtxt,0,$1.txt);$p($1.newtxt,$1.newtxt.length-1,$a([$1.coupontext,$1.coupontextxoffset,$1.coupontextyoffset,$1.coupontextfont,$1.coupontextsize]));$p($1.args,"txt",$1.newtxt)}else{$p($1.args,"txt",$a([$a([$1.coupontext,$1.coupontextxoffset,$1.coupontextyoffset,$1.coupontextfont,$1.coupontextsize])]))}}$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_pharmacode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.height=8*2.835/72;$1.nwidth=.5*2.835;$1.wwidth=1.5*2.835;$1.swidth=1*2.835;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.height=+$1.height;$1.nwidth=+$1.nwidth;$1.wwidth=+$1.wwidth;$1.swidth=+$1.swidth;if($1.barcode.length<1||$1.barcode.length>6){$k[$j++]="bwipp.pharmacodeBadLength";$k[$j++]="Pharmacode must be 1 to 6 digits";bwipp_raiseerror()}$F($1.barcode,function(){var _C=$k[--$j];if(_C<48||_C>57){$k[$j++]="bwipp.pharmacodeBadCharacter";$k[$j++]="Pharmacode must contain only digits";bwipp_raiseerror()}});var _E=~~$z($1.barcode);if(_E<3||_E>131070){$k[$j++]="bwipp.pharmacodeBadValue";$k[$j++]="Pharmacode value must be between 3 and 131070";bwipp_raiseerror()}$1.txt=$a($1.barcode.length);for(var _J=0,_I=$1.barcode.length-1;_J<=_I;_J+=1){$1.i=_J;$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),0,0,"",0]))}$1.barcode=$R($s(17),~~$z($1.barcode)+1,2);$1.barcode=$G($1.barcode,1,$1.barcode.length-1);$1.barlen=$1.barcode.length;$1.sbs=$a($1.barlen*2);for(var _b=0,_a=$1.barlen-1;_b<=_a;_b+=1){$1.i=_b;$1.enc=$G($1.barcode,$1.i,1);if($eq($1.enc,"0")){$p($1.sbs,$1.i*2,$1.nwidth)}else{$p($1.sbs,$1.i*2,$1.wwidth)}$p($1.sbs,$1.i*2+1,$1.swidth)}$k[$j++]=Infinity;var _p=$1.sbs;$k[$j++]=Infinity;for(var _r=0,_s=~~(($1.sbs.length+1)/2);_r<_s;_r++){$k[$j++]=$1.height}var _u=$a();$k[$j++]=Infinity;for(var _w=0,_x=~~(($1.sbs.length+1)/2);_w<_x;_w++){$k[$j++]=0}var _y=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_p;$k[$j++]="bhs";$k[$j++]=_u;$k[$j++]="bbs";$k[$j++]=_y;$k[$j++]="txt";$k[$j++]=$1.txt;$k[$j++]="textxalign";$k[$j++]="center";$k[$j++]="opt";$k[$j++]=$1.options;var _11=$d();$k[$j++]=_11;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_pharmacode2(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.height=4;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.height=+$1.height;if($1.barcode.length<1||$1.barcode.length>8){$k[$j++]="bwipp.pharmacode2BadLength";$k[$j++]="Two-track Pharmacode must be 1 to 6 digits";bwipp_raiseerror()}$F($1.barcode,function(){var _9=$k[--$j];if(_9<48||_9>57){$k[$j++]="bwipp.pharmacode2badCharacter";$k[$j++]="Two-track Pharmacode must contain only digits";bwipp_raiseerror()}});var _B=~~$z($1.barcode);if(_B<4||_B>64570080){$k[$j++]="bwipp.pharmacode2badValue";$k[$j++]="Two-track Pharmacode value must be between 4 and 64570080";bwipp_raiseerror()}$1.txt=$a($1.barcode.length);for(var _G=0,_F=$1.barcode.length-1;_G<=_F;_G+=1){$1.i=_G;$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),0,0,"",0]))}$1.encstr=$s(16);$1.sbs=$a(32);$1.bar=1/25.4*$1.height;$1.spc=1/25.4*72;$1.i=15;$1.val=~~$z($1.barcode);for(;;){if($1.val==0){break}var _T=$1.val%3;$1.val=~~($f($1.val-$g($a([3,1,2]),_T))/3);$p($1.encstr,$1.i,$g($a([2,0,1]),_T));$1.i=$1.i-1}$1.encstr=$G($1.encstr,$1.i+1,15-$1.i);$1.bhs=$a($1.encstr.length);$1.bbs=$a($1.encstr.length);for(var _m=0,_l=$1.encstr.length-1;_m<=_l;_m+=1){$1.i=_m;var _p=$g($1.encstr,$1.i);$p($1.bhs,$1.i,$1.bar*$g($a([1,1,2]),_p));$p($1.bbs,$1.i,$g($a([0,$1.bar,0]),_p))}$k[$j++]=Infinity;var _10=$1.bhs;var _11=$1.bbs;$k[$j++]=Infinity;for(var _13=0,_14=$1.encstr.length*2;_13<_14;_13++){$k[$j++]=$1.spc}var _16=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="bhs";$k[$j++]=_10;$k[$j++]="bbs";$k[$j++]=_11;$k[$j++]="sbs";$k[$j++]=_16;$k[$j++]="txt";$k[$j++]=$1.txt;$k[$j++]="textxalign";$k[$j++]="center";$k[$j++]="textyoffset";$k[$j++]=4;$k[$j++]="opt";$k[$j++]=$1.options;var _19=$d();$k[$j++]=_19;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_code2of5(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includecheck=false;$1.validatecheck=false;$1.includetext=false;$1.includecheckintext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=1;$1.version="industrial";$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$F($1.barcode,function(){var _A=$k[--$j];if(_A<48||_A>57){$k[$j++]="bwipp.code2of5badCharacter";$k[$j++]="Code 25 must contain only digits";bwipp_raiseerror()}});$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _D=$k[--$j];$k[$j++]=$f(_D-1)}var _E=$k[--$j];$1[$k[--$j]]=_E;$1.checksum=0;for(var _I=0,_H=$f($1.barlen-1);_I<=_H;_I+=1){$1.i=_I;$k[$j++]="checksum";$k[$j++]=$1.checksum;$k[$j++]=$f($g($1.barcode,$1.i)-48);if($f($1.barlen-$1.i)%2!=0){var _P=$k[--$j];$k[$j++]=_P*3}var _Q=$k[--$j];var _R=$k[--$j];$1[$k[--$j]]=$f(_R+_Q)}$1.checksum=(10-$1.checksum%10)%10;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$1.checksum+48){$k[$j++]="bwipp.code2of5badCheckDigit";$k[$j++]="Incorrect Code 25 check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen);$1.includecheck=true}var _h=new Map([["industrial",$a(["1111313111","3111111131","1131111131","3131111111","1111311131","3111311111","1131311111","1111113131","3111113111","1131113111","313111","31113"])],["iata",$a(["1111313111","3111111131","1131111131","3131111111","1111311131","3111311111","1131311111","1111113131","3111113111","1131113111","1111","311"])],["matrix",$a(["113311","311131","131131","331111","113131","313111","133111","111331","311311","131311","311111","31111"])],["coop",$a(["331111","111331","113131","113311","131131","131311","133111","311131","311311","313111","3131","133"])],["datalogic",$a(["113311","311131","131131","331111","113131","313111","133111","111331","311311","131311","1111","311"])]]);$1.versions=_h;var _k=$g($1.versions,$1.version)!==undefined;if(!_k){$k[$j++]="bwipp.code2of5badVersion";$k[$j++]="Unrecognised Code 25 version";bwipp_raiseerror()}$1.encs=$g($1.versions,$1.version);$1.cs=$g($1.encs,0).length;$k[$j++]="cw";$k[$j++]=0;$F($g($1.encs,0),function(){var _s=$k[--$j];var _t=$k[--$j];$k[$j++]=$f(_t+$f(_s-48))});var _u=$k[--$j];$1[$k[--$j]]=_u;$1.ss=$g($1.encs,10).length;$k[$j++]="sw";$k[$j++]=0;$F($g($1.encs,10),function(){var _10=$k[--$j];var _11=$k[--$j];$k[$j++]=$f(_11+$f(_10-48))});var _12=$k[--$j];$1[$k[--$j]]=_12;$1.es=$g($1.encs,11).length;$1.barchars="0123456789";$k[$j++]="sbs";$k[$j++]=$1.barlen;if($1.includecheck){var _18=$k[--$j];$k[$j++]=$f(_18+1)}var _1D=$s($f($f($k[--$j]*$1.cs+$1.ss)+$1.es));$1[$k[--$j]]=_1D;$k[$j++]="txt";$k[$j++]=$1.barlen;if($1.includecheck){var _1H=$k[--$j];$k[$j++]=$f(_1H+1)}var _1J=$a($k[--$j]);$1[$k[--$j]]=_1J;$P($1.sbs,0,$g($1.encs,10));for(var _1Q=0,_1P=$f($1.barlen-1);_1Q<=_1P;_1Q+=1){$1.i=_1Q;$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enc=$g($1.encs,$1.indx);$P($1.sbs,$1.i*$1.cs+$1.ss,$1.enc);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$f($1.i*$1.cw+$1.sw),$1.textyoffset,$1.textfont,$1.textsize]))}if($1.includecheck){$P($1.sbs,$f($1.barlen*$1.cs+$1.ss),$g($1.encs,$1.checksum));$P($1.sbs,$f($f($1.barlen*$1.cs+$1.cs)+$1.ss),$g($1.encs,11));if($1.includecheckintext){$p($1.txt,$1.barlen,$a([$G($1.barchars,$1.checksum,1),$f($1.barlen*$1.cw+$1.sw),$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$1.barlen,$a([" ",$f($1.barlen*$1.cw+$1.sw),$1.textyoffset,$1.textfont,$1.textsize]))}}else{$P($1.sbs,$f($1.barlen*$1.cs+$1.ss),$g($1.encs,11))}$k[$j++]=Infinity;$k[$j++]=Infinity;$F($1.sbs,function(){var _2Y=$k[--$j];$k[$j++]=$f(_2Y-48)});var _2Z=$a();$k[$j++]=Infinity;for(var _2b=0,_2c=~~(($1.sbs.length+1)/2);_2b<_2c;_2b++){$k[$j++]=$1.height}var _2e=$a();$k[$j++]=Infinity;for(var _2g=0,_2h=~~(($1.sbs.length+1)/2);_2g<_2h;_2g++){$k[$j++]=0}var _2i=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_2Z;$k[$j++]="bhs";$k[$j++]=_2e;$k[$j++]="bbs";$k[$j++]=_2i;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _2m=$d();$k[$j++]=_2m;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_industrial2of5(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$p($1.options,"dontdraw",true);$p($1.options,"version","industrial");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_code2of5();var _E=$k[--$j];$1[$k[--$j]]=_E;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_iata2of5(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$p($1.options,"dontdraw",true);$p($1.options,"version","iata");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_code2of5();var _E=$k[--$j];$1[$k[--$j]]=_E;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_matrix2of5(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$p($1.options,"dontdraw",true);$p($1.options,"version","matrix");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_code2of5();var _E=$k[--$j];$1[$k[--$j]]=_E;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_coop2of5(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$p($1.options,"dontdraw",true);$p($1.options,"version","coop");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_code2of5();var _E=$k[--$j];$1[$k[--$j]]=_E;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_datalogic2of5(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$p($1.options,"dontdraw",true);$p($1.options,"version","datalogic");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_code2of5();var _E=$k[--$j];$1[$k[--$j]]=_E;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_code11(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includecheck=false;$1.validatecheck=false;$1.includetext=false;$1.includecheckintext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.encs=$a(["111131","311131","131131","331111","113131","313111","133111","111331","311311","311111","113111","113311"]);$1.barchars="0123456789-";$1.charvals=new Map;for(var _A=0;_A<=10;_A+=1){$p($1.charvals,$G($1.barchars,_A,1),_A)}for(var _G=0,_F=$1.barcode.length-1;_G<=_F;_G+=1){var _K=$g($1.charvals,$G($1.barcode,_G,1))!==undefined;if(!_K){$k[$j++]="bwipp.code11badCharacter";$k[$j++]="Code 11 must contain only digits and dashes";bwipp_raiseerror()}}$1.barlen=$1.barcode.length;if($1.validatecheck){if($1.barlen==11){$k[$j++]="bwipp.code11badLength";$k[$j++]="Code 11 cannot be 11 characters using check digits";bwipp_raiseerror()}var _Q=$1.barlen<=10?1:2;$1.barlen=$1.barlen-_Q}$k[$j++]="numchecks";if($1.includecheck||$1.validatecheck){var _U=$1.barlen>=10?2:1;$k[$j++]=_U}else{$k[$j++]=0}var _V=$k[--$j];$1[$k[--$j]]=_V;$1.checksum1=0;$1.checksum2=0;for(var _Z=0,_Y=$1.barlen-1;_Z<=_Y;_Z+=1){$1.i=_Z;$1.indx=$g($1.charvals,$G($1.barcode,$1.i,1));$1.checksum1=$f($1.checksum1+(($1.barlen-$1.i-1)%10+1)*$1.indx);$1.checksum2=$f($1.checksum2+(($1.barlen-$1.i)%9+1)*$1.indx)}$1.checksum1=$1.checksum1%11;$1.checksum2=$f($1.checksum2+$1.checksum1)%11;if($1.validatecheck){var _r=$1.numchecks;if(_r==1){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum1)){$k[$j++]="bwipp.code11badCheckDigit";$k[$j++]="Incorrect Code 11 check digit provided";bwipp_raiseerror()}}else{if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum1)||$g($1.barcode,$1.barlen+1)!=$g($1.barchars,$1.checksum2)){$k[$j++]="bwipp.code11badCheckDigits";$k[$j++]="Incorrect Code 11 check digits provided";bwipp_raiseerror()}}$1.barcode=$G($1.barcode,0,$1.barlen);$1.includecheck=true}$1.sbs=$s($f($f($1.barlen+$1.numchecks)*6+12));$1.txt=$a($f($1.barlen+$1.numchecks));$P($1.sbs,0,$g($1.encs,11));$1.xpos=8;for(var _1O=0,_1N=$1.barlen-1;_1O<=_1N;_1O+=1){$1.i=_1O;$1.indx=$g($1.charvals,$G($1.barcode,$1.i,1));$1.enc=$g($1.encs,$1.indx);$P($1.sbs,$1.i*6+6,$1.enc);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$1.xpos,$1.textyoffset,$1.textfont,$1.textsize]));for(var _1k=0;_1k<=5;_1k+=1){$1.xpos=$f($f($g($1.enc,_1k)-48)+$1.xpos)}}if($1.includecheck){if($1.barlen>=10){$P($1.sbs,$1.barlen*6+6,$g($1.encs,$1.checksum1));$P($1.sbs,$1.barlen*6+12,$g($1.encs,$1.checksum2));if($1.includecheckintext){$p($1.txt,$1.barlen,$a([$G($1.barchars,$1.checksum1,1),$1.xpos,$1.textyoffset,$1.textfont,$1.textsize]));$1.enc=$g($1.encs,$1.checksum1);for(var _2E=0;_2E<=5;_2E+=1){$1.xpos=$f($f($g($1.enc,_2E)-48)+$1.xpos)}$p($1.txt,$1.barlen+1,$a([$G($1.barchars,$1.checksum2,1),$1.xpos,$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$1.barlen,$a(["",$1.xpos,$1.textyoffset,$1.textfont,$1.textsize]));$p($1.txt,$1.barlen+1,$a(["",$1.xpos,$1.textyoffset,$1.textfont,$1.textsize]))}$P($1.sbs,$1.barlen*6+18,$g($1.encs,11))}else{$P($1.sbs,$1.barlen*6+6,$g($1.encs,$1.checksum1));if($1.includecheckintext){$p($1.txt,$1.barlen,$a([$G($1.barchars,$1.checksum1,1),$1.xpos,$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$1.barlen,$a(["",$1.xpos,$1.textyoffset,$1.textfont,$1.textsize]))}$P($1.sbs,$1.barlen*6+12,$g($1.encs,11))}}else{$P($1.sbs,$1.barlen*6+6,$g($1.encs,11))}$k[$j++]=Infinity;$k[$j++]=Infinity;var _3F=$1.sbs;for(var _3G=0,_3H=_3F.length;_3G<_3H;_3G++){$k[$j++]=$g(_3F,_3G)-48}var _3J=$a();$k[$j++]=Infinity;for(var _3L=0,_3M=~~(($1.sbs.length+1)/2);_3L<_3M;_3L++){$k[$j++]=$1.height}var _3O=$a();$k[$j++]=Infinity;for(var _3Q=0,_3R=~~(($1.sbs.length+1)/2);_3Q<_3R;_3Q++){$k[$j++]=0}var _3S=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_3J;$k[$j++]="bhs";$k[$j++]=_3O;$k[$j++]="bbs";$k[$j++]=_3S;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _3W=$d();$k[$j++]=_3W;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_bc412(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includecheck=false;$1.validatecheck=false;$1.includetext=false;$1.includecheckintext=false;$1.includestartstop=false;$1.semi=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;if($1.semi){$1.includecheck=true;$1.includestartstop=true}$1.barchars="0R9GLVHA8EZ4NTS1J2Q6C7DYKBUIX3FWP5M";$1.charvals=new Map;for(var _A=0;_A<=34;_A+=1){$p($1.charvals,$G($1.barchars,_A,1),_A)}for(var _G=0,_F=$1.barcode.length-1;_G<=_F;_G+=1){var _K=$g($1.charvals,$G($1.barcode,_G,1))!==undefined;if(!_K){$k[$j++]="bwipp.bc412badCharacter";$k[$j++]="BC412 must contain only digits and capital letters except O";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _N=$k[--$j];$k[$j++]=$f(_N-1)}var _O=$k[--$j];$1[$k[--$j]]=_O;$1.checksum=0;for(var _S=0,_R=$f($1.barlen-1);_S<=_R;_S+=1){$1.checksum=$f($g($1.charvals,$G($1.barcode,_S,1))+$1.checksum)}$1.checksum=$1.checksum%35;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.bc412badCheckDigit";$k[$j++]="Incorrect BC412 check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen);$1.includecheck=true}$1.encs=$a(["11111115","13111212","11131113","12111213","12121311","13131111","12111312","11131212","11121411","11151111","15111111","11111511","12131211","13121112","13111212","11111214","12121113","11111313","13111113","11121213","11141112","11121312","11141211","14121111","12121212","11131311","13121211","12111411","14111211","11111412","12111114","14111112","12141111","11121114","12131112","12","111"]);$1.sbs=$s($f($f($1.barlen+1)*8+5));if($1.includecheck){$1.txt=$a($f($1.barlen+1))}else{$1.txt=$a($1.barlen)}if($1.includestartstop){$P($1.sbs,0,$g($1.encs,35));$1.pos=2;$1.txtpos=3}else{$1.pos=0;$1.txtpos=0}for(var _x=0,_w=$f($1.barlen-1);_x<=_w;_x+=1){$1.i=_x;$1.indx=$g($1.charvals,$G($1.barcode,$1.i,1));$P($1.sbs,$1.pos,$g($1.encs,$1.indx));$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$1.i*12+$1.txtpos,$1.textyoffset,$1.textfont,$1.textsize]));$1.pos=$1.pos+8}if($1.includecheck){$P($1.sbs,$1.pos,$g($1.encs,$1.checksum));if($1.includecheckintext){$p($1.txt,$1.barlen,$a([$G($1.barchars,$1.checksum,1),$f($1.barlen*12+$1.txtpos),$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$1.barlen,$a(["",$f($1.barlen*12+$1.txtpos),$1.textyoffset,$1.textfont,$1.textsize]))}$1.pos=$1.pos+8}if($1.includestartstop){$P($1.sbs,$1.pos,$g($1.encs,36));$1.pos=$1.pos+2}$1.sbs=$G($1.sbs,0,$1.pos);$k[$j++]=Infinity;$k[$j++]=Infinity;var _1u=$1.sbs;for(var _1v=0,_1w=_1u.length;_1v<_1w;_1v++){$k[$j++]=$f($g(_1u,_1v)-48)}var _1y=$a();$k[$j++]=Infinity;for(var _20=0,_21=~~(($1.sbs.length+1)/2);_20<_21;_20++){$k[$j++]=$1.height}var _23=$a();$k[$j++]=Infinity;for(var _25=0,_26=~~(($1.sbs.length+1)/2);_25<_26;_25++){$k[$j++]=0}var _27=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_1y;$k[$j++]="bhs";$k[$j++]=_23;$k[$j++]="bbs";$k[$j++]=_27;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _2B=$d();$k[$j++]=_2B;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_rationalizedCodabar(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.altstartstop=false;$1.includecheck=false;$1.validatecheck=false;$1.includetext=false;$1.includecheckintext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.encs=$a(["11111331","11113311","11131131","33111111","11311311","31111311","13111131","13113111","13311111","31131111","11133111","11331111","31113131","31311131","31313111","11313131","11331311","13131131","11131331","11133311"]);if($1.altstartstop){$1.barchars="0123456789-$:/.+TN*E"}else{$1.barchars="0123456789-$:/.+ABCD"}$1.charvals=new Map;for(var _B=0;_B<=19;_B+=1){$p($1.charvals,$G($1.barchars,_B,1),_B)}$1.bodyvals=new Map;for(var _F=0;_F<=15;_F+=1){$p($1.bodyvals,$G($1.barchars,_F,1),_F)}$1.ssvals=new Map;for(var _J=16;_J<=19;_J+=1){$p($1.ssvals,$G($1.barchars,_J,1),_J)}var _Q=$g($1.ssvals,$G($1.barcode,0,1))!==undefined;var _V=$g($1.ssvals,$G($1.barcode,$1.barcode.length-1,1))!==undefined;if(!_Q||!_V){if($1.altstartstop){$k[$j++]="bwipp.rationalizedCodabarBadAltStartStop";$k[$j++]="Codabar start and stop characters must be one of E N T or *";bwipp_raiseerror()}else{$k[$j++]="bwipp.rationalizedCodabarBadStartStop";$k[$j++]="Codabar start and stop characters must be one of A B C or D";bwipp_raiseerror()}}for(var _Z=1,_Y=$1.barcode.length-2;_Z<=_Y;_Z+=1){var _d=$g($1.bodyvals,$G($1.barcode,_Z,1))!==undefined;if(!_d){$k[$j++]="bwipp.rationalizedCodabarBadCharacter";$k[$j++]="Codabar body must contain only digits and symbols - $ : / . +";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _g=$k[--$j];$k[$j++]=$f(_g-1)}var _h=$k[--$j];$1[$k[--$j]]=_h;$1.checksum=0;for(var _l=0,_k=$f($1.barlen-2);_l<=_k;_l+=1){$1.checksum=$f($g($1.charvals,$G($1.barcode,_l,1))+$1.checksum)}var _r=$1.barcode;$1.checksum=$f($g($1.charvals,$G(_r,$1.barcode.length-1,1))+$1.checksum);$1.checksum=$f(16-$1.checksum%16)%16;if($1.validatecheck){if($g($1.barcode,$f($1.barlen-1))!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.rationalizedCodabarBadCheckDigit";$k[$j++]="Incorrect Codabar check digit provided";bwipp_raiseerror()}var _16=$s($1.barlen);$P(_16,0,$G($1.barcode,0,$f($1.barlen-1)));$P(_16,$f($1.barlen-1),$G($1.barcode,$1.barlen,1));$1.barcode=_16;$1.includecheck=true}$k[$j++]="sbs";$k[$j++]=$1.barlen;if($1.includecheck){var _1G=$k[--$j];$k[$j++]=$f(_1G+1)}var _1I=$s($k[--$j]*8);$1[$k[--$j]]=_1I;$k[$j++]="txt";$k[$j++]=$1.barlen;if($1.includecheck){var _1M=$k[--$j];$k[$j++]=$f(_1M+1)}var _1O=$a($k[--$j]);$1[$k[--$j]]=_1O;$1.xpos=0;for(var _1S=0,_1R=$f($1.barlen-2);_1S<=_1R;_1S+=1){$1.i=_1S;$1.indx=$g($1.charvals,$G($1.barcode,$1.i,1));$1.enc=$g($1.encs,$1.indx);$P($1.sbs,$1.i*8,$1.enc);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$1.xpos,$1.textyoffset,$1.textfont,$1.textsize]));for(var _1o=0;_1o<=7;_1o+=1){$1.xpos=$f($f($g($1.enc,_1o)-48)+$1.xpos)}}if($1.includecheck){$P($1.sbs,$f($1.barlen*8-8),$g($1.encs,$1.checksum));if($1.includecheckintext){$p($1.txt,$f($1.barlen-1),$a([$G($1.barchars,$1.checksum,1),$1.xpos,$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$f($1.barlen-1),$a([" ",$1.xpos,$1.textyoffset,$1.textfont,$1.textsize]))}for(var _2G=0;_2G<=7;_2G+=1){$1.xpos=$f($f($g($g($1.encs,$1.checksum),_2G)-48)+$1.xpos)}$1.indx=$g($1.charvals,$G($1.barcode,$f($1.barlen-1),1));$1.enc=$g($1.encs,$1.indx);$P($1.sbs,$1.barlen*8,$1.enc);$p($1.txt,$1.barlen,$a([$G($1.barcode,$f($1.barlen-1),1),$1.xpos,$1.textyoffset,$1.textfont,$1.textsize]))}else{$1.indx=$g($1.charvals,$G($1.barcode,$f($1.barlen-1),1));$1.enc=$g($1.encs,$1.indx);$P($1.sbs,$f($1.barlen*8-8),$1.enc);$p($1.txt,$f($1.barlen-1),$a([$G($1.barcode,$f($1.barlen-1),1),$1.xpos,$1.textyoffset,$1.textfont,$1.textsize]))}$k[$j++]=Infinity;$k[$j++]=Infinity;$F($1.sbs,function(){var _33=$k[--$j];$k[$j++]=$f(_33-48)});var _34=$a();$k[$j++]=Infinity;for(var _36=0,_37=~~(($1.sbs.length+1)/2);_36<_37;_36++){$k[$j++]=$1.height}var _39=$a();$k[$j++]=Infinity;for(var _3B=0,_3C=~~(($1.sbs.length+1)/2);_3B<_3C;_3B++){$k[$j++]=0}var _3D=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_34;$k[$j++]="bhs";$k[$j++]=_39;$k[$j++]="bbs";$k[$j++]=_3D;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _3H=$d();$k[$j++]=_3H;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_onecode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.height=.15;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.height=+$1.height;$1.barlen=$1.barcode.length;$k[$j++]=Infinity;var _9=$eq($G($1.barcode,5,1),"9")?14:11;$k[$j++]=2;$k[$j++]=2;$k[$j++]=5;$k[$j++]=5;$k[$j++]=_9;$k[$j++]=_9;$k[$j++]=20;$k[$j++]=20;$k[$j++]=25;$k[$j++]=25;$k[$j++]=29;$k[$j++]=29;$1.txtdict=$d();$1.txt=$a($1.barlen+6);$1.spacecnt=0;for(var _F=0,_E=$1.barlen-1;_F<=_E;_F+=1){$1.i=_F;var _I=$g($1.txtdict,$1.i)!==undefined;if(_I){$p($1.txt,$1.i+$1.spacecnt,$a([" ",0,0,"",0]));$1.spacecnt=$1.spacecnt+1}$p($1.txt,$1.i+$1.spacecnt,$a([$G($1.barcode,$1.i,1),0,0,"",0]))}$1.txt=$G($1.txt,0,$1.barcode.length+$1.spacecnt);$1.normalize=function(){$1.base=$k[--$j];$1.num=$k[--$j];for(var _c=$1.num.length-1;_c>=1;_c-=1){$1.i=_c;var _d=$1.num;var _e=$1.i;$p(_d,_e-1,$f($g(_d,_e-1)+~~($g($1.num,$1.i)/$1.base)));$p($1.num,$1.i,$g($1.num,$1.i)%$1.base)}for(;;){if($lt($g($1.num,0),$1.base)){break}$k[$j++]=Infinity;$k[$j++]=0;$F($1.num);$1.num=$a();$p($1.num,0,$f($g($1.num,0)+~~($g($1.num,1)/$1.base)));$p($1.num,1,$g($1.num,1)%$1.base)}$k[$j++]=Infinity;$1.i=true;var _15=$1.num;for(var _16=0,_17=_15.length;_16<_17;_16++){var _18=$g(_15,_16);$k[$j++]=_18;if(_18==0&&$1.i){$j--}else{$1.i=false}}$1.num=$a();if($1.num.length==0){$1.num=$a([0])}$k[$j++]=$1.num};$1.bigadd=function(){var _1E=$k[--$j];var _1F=$k[--$j];$1.offset=Math.abs(_1E.length-_1F.length);if(_1E.length<_1F.length){var _=_1E;_1E=_1F;_1F=_}$1.a=_1E;$1.b=_1F;for(var _1I=0,_1H=$1.b.length-1;_1I<=_1H;_1I+=1){var _1J=$1.a;var _1K=$1.offset;$p(_1J,_1I+_1K,$f($g(_1J,_1I+_1K)+$g($1.b,_1I)))}$k[$j++]=$1.a};if($1.barlen==20){$k[$j++]=$a([0])}if($1.barlen==25){$k[$j++]=$a([1])}if($1.barlen==29){$k[$j++]=$a([1,0,0,0,0,1])}if($1.barlen==31){$k[$j++]=$a([1,0,0,0,1,0,0,0,0,1])}var _1X=$k[--$j];$k[$j++]="binval";$k[$j++]=_1X;$k[$j++]=Infinity;var _1a=$G($1.barcode,20,$1.barlen-20);for(var _1b=0,_1c=_1a.length;_1b<_1c;_1b++){$k[$j++]=$f($g(_1a,_1b)-48)}var _1e=$a();$k[$j++]=_1e;$1.bigadd();var _1f=$k[--$j];$1[$k[--$j]]=_1f;$k[$j++]=Infinity;var _1h=$1.binval;for(var _1i=0,_1j=_1h.length;_1i<_1j;_1i++){$k[$j++]=$g(_1h,_1i)}$k[$j++]=$f($g($1.barcode,0)-48);$1.binval=$a();$k[$j++]=Infinity;var _1o=$1.binval;for(var _1p=0,_1q=_1o.length;_1p<_1q;_1p++){$k[$j++]=$g(_1o,_1p)*5}var _1s=$a();$k[$j++]="binval";$k[$j++]=_1s;$k[$j++]=$a([$f($g($1.barcode,1)-48)]);$1.bigadd();$k[$j++]=10;$1.normalize();var _1w=$k[--$j];$1[$k[--$j]]=_1w;$k[$j++]=Infinity;var _1y=$1.binval;for(var _1z=0,_20=_1y.length;_1z<_20;_1z++){$k[$j++]=$g(_1y,_1z)}var _23=$G($1.barcode,2,18);for(var _24=0,_25=_23.length;_24<_25;_24++){$k[$j++]=$f($g(_23,_24)-48)}$1.binval=$a();$1.bytes=$a(13);$k[$j++]=Infinity;var _29=$1.binval;for(var _2A=0,_2B=_29.length;_2A<_2B;_2A++){$k[$j++]=$g(_29,_2A)}$1.bintmp=$a();for(var _2E=12;_2E>=0;_2E-=1){$1.i=_2E;for(var _2H=0,_2G=$1.bintmp.length-2;_2H<=_2G;_2H+=1){$1.j=_2H;var _2I=$1.bintmp;var _2J=$1.j;$p(_2I,_2J+1,$f($g(_2I,_2J+1)+$g($1.bintmp,$1.j)%256*10));$p($1.bintmp,$1.j,~~($g($1.bintmp,$1.j)/256))}$p($1.bytes,$1.i,$g($1.bintmp,$1.bintmp.length-1)%256);var _2Y=$1.bintmp;var _2Z=$1.bintmp;$p(_2Y,_2Z.length-1,~~($g(_2Y,_2Z.length-1)/256))}$1.fcs=2047;$1.dat=$g($1.bytes,0)<<5;for(var _2d=0,_2e=6;_2d<_2e;_2d++){if((($1.fcs^$1.dat)&1024)!=0){$1.fcs=$1.fcs<<1^3893}else{$1.fcs=$1.fcs<<1}$1.fcs=$1.fcs&2047;$1.dat=$1.dat<<1}for(var _2l=1;_2l<=12;_2l+=1){$1.dat=$g($1.bytes,_2l)<<3;for(var _2o=0,_2p=8;_2o<_2p;_2o++){if((($1.fcs^$1.dat)&1024)!=0){$1.fcs=$1.fcs<<1^3893}else{$1.fcs=$1.fcs<<1}$1.fcs=$1.fcs&2047;$1.dat=$1.dat<<1}}$1.codewords=$a(10);for(var _2x=9;_2x>=0;_2x-=1){$1.i=_2x;if($1.i==9){$1.b=636}else{$1.b=1365}for(var _31=0,_30=$1.binval.length-2;_31<=_30;_31+=1){$1.j=_31;var _32=$1.binval;var _33=$1.j;$p(_32,_33+1,$f($g(_32,_33+1)+$g($1.binval,$1.j)%$1.b*10));$p($1.binval,$1.j,~~($g($1.binval,$1.j)/$1.b))}$p($1.codewords,$1.i,$g($1.binval,$1.binval.length-1)%$1.b);var _3L=$1.binval;var _3M=$1.binval;$p(_3L,_3M.length-1,~~($g(_3L,_3M.length-1)/$1.b))}$p($1.codewords,9,$g($1.codewords,9)*2);if(($1.fcs&1024)!=0){$p($1.codewords,0,$f($g($1.codewords,0)+659))}$1.tab513=$a([31,7936,47,7808,55,7552,59,7040,61,6016,62,3968,79,7744,87,7488,91,6976,93,5952,94,3904,103,7360,107,6848,109,5824,110,3776,115,6592,117,5568,118,3520,121,5056,122,3008,124,1984,143,7712,151,7456,155,6944,157,5920,158,3872,167,7328,171,6816,173,5792,174,3744,179,6560,181,5536,182,3488,185,5024,186,2976,188,1952,199,7264,203,6752,205,5728,206,3680,211,6496,213,5472,214,3424,217,4960,218,2912,220,1888,227,6368,229,5344,230,3296,233,4832,234,2784,236,1760,241,4576,242,2528,244,1504,248,992,271,7696,279,7440,283,6928,285,5904,286,3856,295,7312,299,6800,301,5776,302,3728,307,6544,309,5520,310,3472,313,5008,314,2960,316,1936,327,7248,331,6736,333,5712,334,3664,339,6480,341,5456,342,3408,345,4944,346,2896,348,1872,355,6352,357,5328,358,3280,361,4816,362,2768,364,1744,369,4560,370,2512,372,1488,376,976,391,7216,395,6704,397,5680,398,3632,403,6448,405,5424,406,3376,409,4912,410,2864,412,1840,419,6320,421,5296,422,3248,425,4784,426,2736,428,1712,433,4528,434,2480,436,1456,440,944,451,6256,453,5232,454,3184,457,4720,458,2672,460,1648,465,4464,466,2416,468,1392,472,880,481,4336,482,2288,484,1264,488,752,527,7688,535,7432,539,6920,541,5896,542,3848,551,7304,555,6792,557,5768,558,3720,563,6536,565,5512,566,3464,569,5e3,570,2952,572,1928,583,7240,587,6728,589,5704,590,3656,595,6472,597,5448,598,3400,601,4936,602,2888,604,1864,611,6344,613,5320,614,3272,617,4808,618,2760,620,1736,625,4552,626,2504,628,1480,632,968,647,7208,651,6696,653,5672,654,3624,659,6440,661,5416,662,3368,665,4904,666,2856,668,1832,675,6312,677,5288,678,3240,681,4776,682,2728,684,1704,689,4520,690,2472,692,1448,696,936,707,6248,709,5224,710,3176,713,4712,714,2664,716,1640,721,4456,722,2408,724,1384,728,872,737,4328,738,2280,740,1256,775,7192,779,6680,781,5656,782,3608,787,6424,789,5400,790,3352,793,4888,794,2840,796,1816,803,6296,805,5272,806,3224,809,4760,810,2712,812,1688,817,4504,818,2456,820,1432,824,920,835,6232,837,5208,838,3160,841,4696,842,2648,844,1624,849,4440,850,2392,852,1368,865,4312,866,2264,868,1240,899,6200,901,5176,902,3128,905,4664,906,2616,908,1592,913,4408,914,2360,916,1336,929,4280,930,2232,932,1208,961,4216,962,2168,964,1144,1039,7684,1047,7428,1051,6916,1053,5892,1054,3844,1063,7300,1067,6788,1069,5764,1070,3716,1075,6532,1077,5508,1078,3460,1081,4996,1082,2948,1084,1924,1095,7236,1099,6724,1101,5700,1102,3652,1107,6468,1109,5444,1110,3396,1113,4932,1114,2884,1116,1860,1123,6340,1125,5316,1126,3268,1129,4804,1130,2756,1132,1732,1137,4548,1138,2500,1140,1476,1159,7204,1163,6692,1165,5668,1166,3620,1171,6436,1173,5412,1174,3364,1177,4900,1178,2852,1180,1828,1187,6308,1189,5284,1190,3236,1193,4772,1194,2724,1196,1700,1201,4516,1202,2468,1204,1444,1219,6244,1221,5220,1222,3172,1225,4708,1226,2660,1228,1636,1233,4452,1234,2404,1236,1380,1249,4324,1250,2276,1287,7188,1291,6676,1293,5652,1294,3604,1299,6420,1301,5396,1302,3348,1305,4884,1306,2836,1308,1812,1315,6292,1317,5268,1318,3220,1321,4756,1322,2708,1324,1684,1329,4500,1330,2452,1332,1428,1347,6228,1349,5204,1350,3156,1353,4692,1354,2644,1356,1620,1361,4436,1362,2388,1377,4308,1378,2260,1411,6196,1413,5172,1414,3124,1417,4660,1418,2612,1420,1588,1425,4404,1426,2356,1441,4276,1442,2228,1473,4212,1474,2164,1543,7180,1547,6668,1549,5644,1550,3596,1555,6412,1557,5388,1558,3340,1561,4876,1562,2828,1564,1804,1571,6284,1573,5260,1574,3212,1577,4748,1578,2700,1580,1676,1585,4492,1586,2444,1603,6220,1605,5196,1606,3148,1609,4684,1610,2636,1617,4428,1618,2380,1633,4300,1634,2252,1667,6188,1669,5164,1670,3116,1673,4652,1674,2604,1681,4396,1682,2348,1697,4268,1698,2220,1729,4204,1730,2156,1795,6172,1797,5148,1798,3100,1801,4636,1802,2588,1809,4380,1810,2332,1825,4252,1826,2204,1857,4188,1858,2140,1921,4156,1922,2108,2063,7682,2071,7426,2075,6914,2077,5890,2078,3842,2087,7298,2091,6786,2093,5762,2094,3714,2099,6530,2101,5506,2102,3458,2105,4994,2106,2946,2119,7234,2123,6722,2125,5698,2126,3650,2131,6466,2133,5442,2134,3394,2137,4930,2138,2882,2147,6338,2149,5314,2150,3266,2153,4802,2154,2754,2161,4546,2162,2498,2183,7202,2187,6690,2189,5666,2190,3618,2195,6434,2197,5410,2198,3362,2201,4898,2202,2850,2211,6306,2213,5282,2214,3234,2217,4770,2218,2722,2225,4514,2226,2466,2243,6242,2245,5218,2246,3170,2249,4706,2250,2658,2257,4450,2258,2402,2273,4322,2311,7186,2315,6674,2317,5650,2318,3602,2323,6418,2325,5394,2326,3346,2329,4882,2330,2834,2339,6290,2341,5266,2342,3218,2345,4754,2346,2706,2353,4498,2354,2450,2371,6226,2373,5202,2374,3154,2377,4690,2378,2642,2385,4434,2401,4306,2435,6194,2437,5170,2438,3122,2441,4658,2442,2610,2449,4402,2465,4274,2497,4210,2567,7178,2571,6666,2573,5642,2574,3594,2579,6410,2581,5386,2582,3338,2585,4874,2586,2826,2595,6282,2597,5258,2598,3210,2601,4746,2602,2698,2609,4490,2627,6218,2629,5194,2630,3146,2633,4682,2641,4426,2657,4298,2691,6186,2693,5162,2694,3114,2697,4650,2705,4394,2721,4266,2753,4202,2819,6170,2821,5146,2822,3098,2825,4634,2833,4378,2849,4250,2881,4186,2945,4154,3079,7174,3083,6662,3085,5638,3086,3590,3091,6406,3093,5382,3094,3334,3097,4870,3107,6278,3109,5254,3110,3206,3113,4742,3121,4486,3139,6214,3141,5190,3145,4678,3153,4422,3169,4294,3203,6182,3205,5158,3209,4646,3217,4390,3233,4262,3265,4198,3331,6166,3333,5142,3337,4630,3345,4374,3361,4246,3393,4182,3457,4150,3587,6158,3589,5134,3593,4622,3601,4366,3617,4238,3649,4174,3713,4142,3841,4126,4111,7681,4119,7425,4123,6913,4125,5889,4135,7297,4139,6785,4141,5761,4147,6529,4149,5505,4153,4993,4167,7233,4171,6721,4173,5697,4179,6465,4181,5441,4185,4929,4195,6337,4197,5313,4201,4801,4209,4545,4231,7201,4235,6689,4237,5665,4243,6433,4245,5409,4249,4897,4259,6305,4261,5281,4265,4769,4273,4513,4291,6241,4293,5217,4297,4705,4305,4449,4359,7185,4363,6673,4365,5649,4371,6417,4373,5393,4377,4881,4387,6289,4389,5265,4393,4753,4401,4497,4419,6225,4421,5201,4425,4689,4483,6193,4485,5169,4489,4657,4615,7177,4619,6665,4621,5641,4627,6409,4629,5385,4633,4873,4643,6281,4645,5257,4649,4745,4675,6217,4677,5193,4739,6185,4741,5161,4867,6169,4869,5145,5127,7173,5131,6661,5133,5637,5139,6405,5141,5381,5155,6277,5157,5253,5187,6213,5251,6181,5379,6165,5635,6157,6151,7171,6155,6659,6163,6403,6179,6275,6211,5189,4681,4433,4321,3142,2634,2386,2274,1612,1364,1252,856,744,496]);$1.tab213=$a([3,6144,5,5120,6,3072,9,4608,10,2560,12,1536,17,4352,18,2304,20,1280,24,768,33,4224,34,2176,36,1152,40,640,48,384,65,4160,66,2112,68,1088,72,576,80,320,96,192,129,4128,130,2080,132,1056,136,544,144,288,257,4112,258,2064,260,1040,264,528,513,4104,514,2056,516,1032,1025,4100,1026,2052,2049,4098,4097,2050,1028,520,272,160]);$1.chars=$a(10);for(var _3Z=0;_3Z<=9;_3Z+=1){$1.i=_3Z;var _3c=$g($1.codewords,$1.i);$k[$j++]=_3c;if(_3c<=1286){var _3f=$g($1.tab513,$k[--$j]);$k[$j++]=_3f}else{var _3i=$g($1.tab213,$f($k[--$j]-1287));$k[$j++]=_3i}$p($1.chars,$1.i,$k[--$j])}for(var _3m=9;_3m>=0;_3m-=1){$1.i=_3m;if((~~Math.pow(2,$1.i)&$1.fcs)!=0){$p($1.chars,$1.i,$g($1.chars,$1.i)^8191)}}$1.barmap=$a([7,2,4,3,1,10,0,0,9,12,2,8,5,5,6,11,8,9,3,1,0,1,5,12,2,5,1,8,4,4,9,11,6,3,8,10,3,9,7,6,5,11,1,4,8,5,2,12,9,10,0,2,7,1,6,7,3,6,4,9,0,3,8,6,6,4,2,7,1,1,9,9,7,10,5,2,4,0,3,8,6,2,0,4,8,11,1,0,9,8,3,12,2,6,7,7,5,1,4,10,1,12,6,9,7,3,8,0,5,8,9,7,4,6,2,10,3,4,0,5,8,4,5,7,7,11,1,9,6,0,9,6,0,6,4,8,2,1,3,2,5,9,8,12,4,11,6,1,9,5,7,4,3,3,1,2,0,7,2,0,1,3,4,1,6,10,3,5,8,7,9,4,2,11,5,6,0,8,7,12,4,2,8,1,5,10,3,0,9,3,0,9,6,5,2,4,7,8,1,7,5,0,4,5,2,3,0,10,6,12,9,2,3,11,1,6,8,8,7,9,5,4,0,11,1,5,2,2,9,1,4,12,8,3,6,6,7,0,3,7,4,7,7,5,0,12,1,11,2,9,9,0,6,8,5,3,3,10,8,2]);$1.bbs=$a(65);$1.bhs=$a(65);for(var _3x=0;_3x<=64;_3x+=1){$1.i=_3x;$1.dec=($g($1.chars,$g($1.barmap,$1.i*4))&~~Math.pow(2,$g($1.barmap,$1.i*4+1)))!=0;$1.asc=($g($1.chars,$g($1.barmap,$1.i*4+2))&~~Math.pow(2,$g($1.barmap,$1.i*4+3)))!=0;if(!$1.dec&&!$1.asc){$p($1.bbs,$1.i,3*$1.height/8);$p($1.bhs,$1.i,2*$1.height/8)}if(!$1.dec&&$1.asc){$p($1.bbs,$1.i,3*$1.height/8);$p($1.bhs,$1.i,5*$1.height/8)}if($1.dec&&!$1.asc){$p($1.bbs,$1.i,0*$1.height/8);$p($1.bhs,$1.i,5*$1.height/8)}if($1.dec&&$1.asc){$p($1.bbs,$1.i,0*$1.height/8);$p($1.bhs,$1.i,8*$1.height/8)}}$k[$j++]=Infinity;var _4k=$1.bbs;var _4l=$1.bhs;$k[$j++]=Infinity;for(var _4n=0,_4o=$1.bhs.length-1;_4n<_4o;_4n++){$k[$j++]=1.44;$k[$j++]=1.872}$k[$j++]=1.44;var _4p=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="bbs";$k[$j++]=_4k;$k[$j++]="bhs";$k[$j++]=_4l;$k[$j++]="sbs";$k[$j++]=_4p;$k[$j++]="txt";$k[$j++]=$1.txt;$k[$j++]="textxalign";$k[$j++]="left";$k[$j++]="textfont";$k[$j++]="OCR-B";$k[$j++]="textyoffset";$k[$j++]=1;$k[$j++]="textxoffset";$k[$j++]=-.3;$k[$j++]="opt";$k[$j++]=$1.options;var _4s=$d();$k[$j++]=_4s;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_postnet(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.validatecheck=false;$1.includecheckintext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=.125;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _B=$k[--$j];$k[$j++]=$f(_B-1)}var _C=$k[--$j];$1[$k[--$j]]=_C;if($1.barlen!=5&&$1.barlen!=9&&$1.barlen!=11){$k[$j++]="bwipp.postnetBadLength";$k[$j++]="USPS POSTNET must be 5, 9 or 11 digits excluding check digit";bwipp_raiseerror()}$F($1.barcode,function(){var _I=$k[--$j];if(_I<48||_I>57){$k[$j++]="bwipp.postnetBadCharacter";$k[$j++]="USPS POSTNET must contain only digits";bwipp_raiseerror()}});$1.barchars="0123456789";$1.checksum=0;for(var _L=0,_K=$f($1.barlen-1);_L<=_K;_L+=1){$1.i=_L;$1.checksum=$f($1.checksum+$f($g($1.barcode,$1.i)-48))}$1.checksum=$f(10-$1.checksum%10)%10;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.postnetBadCheckDigit";$k[$j++]="Incorrect USPS POSTNET check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen)}$1.encs=$a(["55222","22255","22525","22552","25225","25252","25522","52225","52252","52522","5","5"]);$1.bhs=$a($f($1.barlen*5+7));$1.txt=$a($f($1.barlen+1));$1.enc=$g($1.encs,10);$1.heights=$a($1.enc.length);for(var _m=0,_l=$1.enc.length-1;_m<=_l;_m+=1){$1.j=_m;$p($1.heights,$1.j,~~$z($G($1.enc,$1.j,1))*$1.height/5)}$P($1.bhs,0,$1.heights);for(var _x=0,_w=$f($1.barlen-1);_x<=_w;_x+=1){$1.i=_x;$1.enc=$g($1.encs,$f($g($1.barcode,$1.i)-48));$1.heights=$a($1.enc.length);for(var _17=0,_16=$1.enc.length-1;_17<=_16;_17+=1){$1.j=_17;$p($1.heights,$1.j,~~$z($G($1.enc,$1.j,1))*$1.height/5)}$P($1.bhs,$1.i*5+1,$1.heights);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),($1.i*5+1)*3.312,$1.textyoffset,$1.textfont,$1.textsize]))}$1.enc=$g($1.encs,$1.checksum);$1.heights=$a($1.enc.length);for(var _1Y=0,_1X=$1.enc.length-1;_1Y<=_1X;_1Y+=1){$1.j=_1Y;$p($1.heights,$1.j,~~$z($G($1.enc,$1.j,1))*$1.height/5)}$P($1.bhs,$f($1.barlen*5+1),$1.heights);if($1.includecheckintext){$p($1.txt,$1.barlen,$a([$G($1.barchars,$1.checksum,1),$f($1.barlen*5+1)*3.312,$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$1.barlen,$a([" ",$f($1.barlen*5+1)*72/25,$1.textyoffset,$1.textfont,$1.textsize]))}$1.enc=$g($1.encs,11);$1.heights=$a($1.enc.length);for(var _26=0,_25=$1.enc.length-1;_26<=_25;_26+=1){$1.j=_26;$p($1.heights,$1.j,~~$z($G($1.enc,$1.j,1))*$1.height/5)}$P($1.bhs,$f($1.barlen*5+6),$1.heights);$k[$j++]=Infinity;var _2G=$1.bhs;$k[$j++]=Infinity;for(var _2I=0,_2J=$1.bhs.length;_2I<_2J;_2I++){$k[$j++]=0}var _2K=$a();$k[$j++]=Infinity;for(var _2M=0,_2N=$1.bhs.length-1;_2M<_2N;_2M++){$k[$j++]=1.44;$k[$j++]=1.872}$k[$j++]=1.44;var _2O=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="bhs";$k[$j++]=_2G;$k[$j++]="bbs";$k[$j++]=_2K;$k[$j++]="sbs";$k[$j++]=_2O;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _2S=$d();$k[$j++]=_2S;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_planet(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.validatecheck=false;$1.includecheckintext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=.125;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _B=$k[--$j];$k[$j++]=$f(_B-1)}var _C=$k[--$j];$1[$k[--$j]]=_C;if($1.barlen!=11&&$1.barlen!=13){$k[$j++]="bwipp.planetBadLength";$k[$j++]="USPS PLANET must be 11 or 13 digits excluding check digit";bwipp_raiseerror()}$F($1.barcode,function(){var _H=$k[--$j];if(_H<48||_H>57){$k[$j++]="bwipp.planetBadCharacter";$k[$j++]="USPS PLANET must contain only digits";bwipp_raiseerror()}});$1.barchars="0123456789";$1.checksum=0;for(var _K=0,_J=$f($1.barlen-1);_K<=_J;_K+=1){$1.i=_K;$1.checksum=$f($1.checksum+$f($g($1.barcode,$1.i)-48))}$1.checksum=$f(10-$1.checksum%10)%10;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.planetBadCheckDigit";$k[$j++]="Incorrect USPS PLANET check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen)}$1.encs=$a(["22555","55522","55252","55225","52552","52525","52255","25552","25525","25255","5","5"]);$1.bhs=$a($f($1.barlen*5+7));$1.txt=$a($f($1.barlen+1));$1.enc=$g($1.encs,10);$1.heights=$a($1.enc.length);for(var _l=0,_k=$1.enc.length-1;_l<=_k;_l+=1){$1.j=_l;$p($1.heights,$1.j,~~$z($G($1.enc,$1.j,1))*$1.height/5)}$P($1.bhs,0,$1.heights);for(var _w=0,_v=$f($1.barlen-1);_w<=_v;_w+=1){$1.i=_w;$1.enc=$g($1.encs,$f($g($1.barcode,$1.i)-48));$1.heights=$a($1.enc.length);for(var _16=0,_15=$1.enc.length-1;_16<=_15;_16+=1){$1.j=_16;$p($1.heights,$1.j,~~$z($G($1.enc,$1.j,1))*$1.height/5)}$P($1.bhs,$1.i*5+1,$1.heights);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),($1.i*5+1)*3.312,$1.textyoffset,$1.textfont,$1.textsize]))}$1.enc=$g($1.encs,$1.checksum);$1.heights=$a($1.enc.length);for(var _1X=0,_1W=$1.enc.length-1;_1X<=_1W;_1X+=1){$1.j=_1X;$p($1.heights,$1.j,~~$z($G($1.enc,$1.j,1))*$1.height/5)}$P($1.bhs,$f($1.barlen*5+1),$1.heights);if($1.includecheckintext){$p($1.txt,$1.barlen,$a([$G($1.barchars,$1.checksum,1),$f($1.barlen*5+1)*3.312,$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$1.barlen,$a([" ",$f($1.barlen*5+1)*72/25,$1.textyoffset,$1.textfont,$1.textsize]))}$1.enc=$g($1.encs,11);$1.heights=$a($1.enc.length);for(var _25=0,_24=$1.enc.length-1;_25<=_24;_25+=1){$1.j=_25;$p($1.heights,$1.j,~~$z($G($1.enc,$1.j,1))*$1.height/5)}$P($1.bhs,$f($1.barlen*5+6),$1.heights);$k[$j++]=Infinity;var _2F=$1.bhs;$k[$j++]=Infinity;for(var _2H=0,_2I=$1.bhs.length;_2H<_2I;_2H++){$k[$j++]=0}var _2J=$a();$k[$j++]=Infinity;for(var _2L=0,_2M=$1.bhs.length-1;_2L<_2M;_2L++){$k[$j++]=1.44;$k[$j++]=1.872}$k[$j++]=1.44;var _2N=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="bhs";$k[$j++]=_2F;$k[$j++]="bbs";$k[$j++]=_2J;$k[$j++]="sbs";$k[$j++]=_2N;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _2R=$d();$k[$j++]=_2R;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_royalmail(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.validatecheck=false;$1.includecheckintext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=.175;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.barchars="ZUVWXY501234B6789AHCDEFGNIJKLMTOPQRS";$1.charvals=new Map;for(var _9=0;_9<=35;_9+=1){$p($1.charvals,$G($1.barchars,_9,1),_9)}for(var _F=0,_E=$1.barcode.length-1;_F<=_E;_F+=1){var _J=$g($1.charvals,$G($1.barcode,_F,1))!==undefined;if(!_J){$k[$j++]="bwipp.royalmailBadCharacter";$k[$j++]="RM4SCC must contain only capital letters and digits";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _M=$k[--$j];$k[$j++]=$f(_M-1)}var _N=$k[--$j];$1[$k[--$j]]=_N;$1.checksumrow=0;$1.checksumcol=0;for(var _R=0,_Q=$f($1.barlen-1);_R<=_Q;_R+=1){$1.i=_R;$1.indx=$g($1.charvals,$G($1.barcode,$1.i,1));$1.checksumrow=$1.checksumrow+~~($1.indx/6);$1.checksumcol=$f($1.checksumcol+$1.indx%6)}$1.checksum=$f($1.checksumrow%6*6+$1.checksumcol%6);if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.royalmailBadCheckDigit";$k[$j++]="Incorrect RM4SCC check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen)}$1.encs=$a(["3300","2211","2301","2310","3201","3210","1122","0033","0123","0132","1023","1032","1302","0213","0303","0312","1203","1212","1320","0231","0321","0330","1221","1230","3102","2013","2103","2112","3003","3012","3120","2031","2121","2130","3021","3030","2","3"]);$1.encstr=$s($f($1.barlen*4+6));$1.txt=$a($f($1.barlen+1));$P($1.encstr,0,$g($1.encs,36));for(var _x=0,_w=$f($1.barlen-1);_x<=_w;_x+=1){$1.i=_x;$1.indx=$g($1.charvals,$G($1.barcode,$1.i,1));$P($1.encstr,$1.i*4+1,$g($1.encs,$1.indx));$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),($1.i*4+1)*3.312,$1.textyoffset,$1.textfont,$1.textsize]))}$P($1.encstr,$f($1.barlen*4+1),$g($1.encs,$1.checksum));if($1.includecheckintext){$p($1.txt,$1.barlen,$a([$G($1.barchars,$1.checksum,1),$f($1.barlen*4+1)*3.312,$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$1.barlen,$a([" ",$f($1.barlen*4+1)*3.312,$1.textyoffset,$1.textfont,$1.textsize]))}$P($1.encstr,$f($1.barlen*4+5),$g($1.encs,37));$1.bbs=$a($1.encstr.length);$1.bhs=$a($1.encstr.length);for(var _1p=0,_1o=$1.encstr.length-1;_1p<=_1o;_1p+=1){$1.i=_1p;$1.enc=$G($1.encstr,$1.i,1);if($eq($1.enc,"0")){$p($1.bbs,$1.i,3*$1.height/8);$p($1.bhs,$1.i,2*$1.height/8)}if($eq($1.enc,"1")){$p($1.bbs,$1.i,0*$1.height/8);$p($1.bhs,$1.i,5*$1.height/8)}if($eq($1.enc,"2")){$p($1.bbs,$1.i,3*$1.height/8);$p($1.bhs,$1.i,5*$1.height/8)}if($eq($1.enc,"3")){$p($1.bbs,$1.i,0*$1.height/8);$p($1.bhs,$1.i,8*$1.height/8)}}$k[$j++]=Infinity;var _2L=$1.bbs;var _2M=$1.bhs;$k[$j++]=Infinity;for(var _2O=0,_2P=$1.bhs.length-1;_2O<_2P;_2O++){$k[$j++]=1.44;$k[$j++]=1.872}$k[$j++]=1.44;var _2Q=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="bbs";$k[$j++]=_2L;$k[$j++]="bhs";$k[$j++]=_2M;$k[$j++]="sbs";$k[$j++]=_2Q;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _2U=$d();$k[$j++]=_2U;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_auspost(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=.175;$1.custinfoenc="character";$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.encs=$a(["000","001","002","010","011","012","020","021","022","100","101","102","110","111","112","120","121","122","200","201","202","210","211","212","220","221","222","300","301","302","310","311","312","320","321","322","023","030","031","032","033","103","113","123","130","131","132","133","203","213","223","230","231","232","233","303","313","323","330","331","332","333","003","013","00","01","02","10","11","12","20","21","22","30","13","3"]);$1.barchars="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz #";$1.barlen=$1.barcode.length;if($eq($G($1.barcode,0,2),"11")){$k[$j++]=37}if($eq($G($1.barcode,0,2),"45")){$k[$j++]=37}if($eq($G($1.barcode,0,2),"59")){$k[$j++]=52}if($eq($G($1.barcode,0,2),"62")){$k[$j++]=67}$1.encstr=$s($k[--$j]);$1.txt=$a($1.barlen-2);$P($1.encstr,0,$g($1.encs,74));for(var _Q=0;_Q<=1;_Q+=1){$1.i=_Q;$P($1.encstr,$1.i*2+2,$g($1.encs,~~$z($G($1.barcode,$1.i,1))+64))}for(var _Y=2;_Y<=9;_Y+=1){$1.i=_Y;$P($1.encstr,$1.i*2+2,$g($1.encs,~~$z($G($1.barcode,$1.i,1))+64));$p($1.txt,$1.i-2,$a([$G($1.barcode,$1.i,1),(($1.i-2)*2+6)*3.312,$1.textyoffset,$1.textfont,$1.textsize]))}if($eq($1.custinfoenc,"numeric")){for(var _t=0,_s=$1.barlen-11;_t<=_s;_t+=1){$1.i=_t;$P($1.encstr,$1.i*2+22,$g($1.encs,~~$z($G($1.barcode,$1.i+10,1))+64));$p($1.txt,$1.i+8,$a([$G($1.barcode,$1.i+10,1),($1.i*2+22)*3.312,$1.textyoffset,$1.textfont,$1.textsize]))}$1.ciflen=($1.barlen-10)*2}else{for(var _1E=0,_1D=$1.barlen-11;_1E<=_1D;_1E+=1){$1.i=_1E;$x($1.barchars,$G($1.barcode,$1.i+10,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enc=$g($1.encs,$1.indx);$P($1.encstr,$1.i*3+22,$1.enc);$p($1.txt,$1.i+8,$a([$G($1.barcode,$1.i+10,1),($1.i*3+22)*3.312,$1.textyoffset,$1.textfont,$1.textsize]))}$1.ciflen=($1.barlen-10)*3}for(var _1e=22+$1.ciflen,_1d=$1.encstr.length-14;_1e<=_1d;_1e+=1){$P($1.encstr,_1e,$g($1.encs,75))}$1.rstable=$a(64*64);$k[$j++]=$1.rstable;$k[$j++]=0;$k[$j++]=Infinity;for(var _1k=0,_1l=64;_1k<_1l;_1k++){$k[$j++]=0}var _1m=$a();var _1n=$k[--$j];$P($k[--$j],_1n,_1m);$k[$j++]=$1.rstable;$k[$j++]=64;$k[$j++]=Infinity;for(var _1q=0;_1q<=63;_1q+=1){$k[$j++]=_1q}var _1r=$a();var _1s=$k[--$j];$P($k[--$j],_1s,_1r);$1.prev=1;for(var _1u=0,_1v=64;_1u<_1v;_1u++){$1.next=$1.prev<<1;if(($1.next&64)!=0){$1.next=$1.next^67}for(var _1z=0;_1z<=63;_1z+=1){$1.j=_1z;$1.nextcell=function(){$k[$j++]=$1.rstable;$k[$j++]=64*$1.next+$1.j};$1.nextcell();var _27=$k[--$j];$p($k[--$j],_27,$g($1.rstable,64*$1.prev+$1.j)<<1);$1.nextcell();var _29=$k[--$j];if(($g($k[--$j],_29)&64)!=0){$1.nextcell();$1.nextcell();var _2C=$k[--$j];var _2E=$g($k[--$j],_2C);var _2F=$k[--$j];$p($k[--$j],_2F,_2E^67)}}$1.prev=$1.next}$1.rscodes=$a(~~(($1.encstr.length-16)/3)+4);$k[$j++]=$1.rscodes;$k[$j++]=0;$k[$j++]=Infinity;for(var _2L=0,_2M=4;_2L<_2M;_2L++){$k[$j++]=0}var _2N=$a();var _2O=$k[--$j];$P($k[--$j],_2O,_2N);for(var _2S=2,_2R=$1.encstr.length-16;_2S<=_2R;_2S+=3){$1.i=_2S;$p($1.rscodes,$1.rscodes.length-~~(($1.i-2)/3)-1,~~$z($G($1.encstr,$1.i,1))*16+~~$z($G($1.encstr,$1.i+1,1))*4+~~$z($G($1.encstr,$1.i+2,1)))}for(var _2g=$1.rscodes.length-5;_2g>=0;_2g-=1){$1.i=_2g;for(var _2h=0;_2h<=4;_2h+=1){$1.j=_2h;$p($1.rscodes,$1.i+$1.j,$xo($g($1.rscodes,$1.i+$1.j),$g($1.rstable,$f(64*$g($a([48,17,29,30,1]),$1.j)+$g($1.rscodes,$1.i+4)))))}}$1.checkcode=$Z($s(12),"000000000000");for(var _2z=0;_2z<=3;_2z+=1){$1.i=_2z;$1.enc=$R($s(3),$g($1.rscodes,3-$1.i),4);$P($1.checkcode,$1.i*3+(3-$1.enc.length),$1.enc)}$P($1.encstr,$1.encstr.length-14,$1.checkcode);$P($1.encstr,$1.encstr.length-2,$g($1.encs,74));$1.bbs=$a($1.encstr.length);$1.bhs=$a($1.encstr.length);for(var _3M=0,_3L=$1.encstr.length-1;_3M<=_3L;_3M+=1){$1.i=_3M;$1.enc=$G($1.encstr,$1.i,1);if($eq($1.enc,"0")){$p($1.bbs,$1.i,0*$1.height/8);$p($1.bhs,$1.i,8*$1.height/8)}if($eq($1.enc,"1")){$p($1.bbs,$1.i,3*$1.height/8);$p($1.bhs,$1.i,5*$1.height/8)}if($eq($1.enc,"2")){$p($1.bbs,$1.i,0*$1.height/8);$p($1.bhs,$1.i,5*$1.height/8)}if($eq($1.enc,"3")){$p($1.bbs,$1.i,3*$1.height/8);$p($1.bhs,$1.i,2*$1.height/8)}}$k[$j++]=Infinity;var _3s=$1.bbs;var _3t=$1.bhs;$k[$j++]=Infinity;for(var _3v=0,_3w=$1.bhs.length-1;_3v<_3w;_3v++){$k[$j++]=1.44;$k[$j++]=1.872}$k[$j++]=1.44;var _3x=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="bbs";$k[$j++]=_3s;$k[$j++]="bhs";$k[$j++]=_3t;$k[$j++]="sbs";$k[$j++]=_3x;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _41=$d();$k[$j++]=_41;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_kix(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=.175;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.encs=$a(["0033","0123","0132","1023","1032","1122","0213","0303","0312","1203","1212","1302","0231","0321","0330","1221","1230","1320","2013","2103","2112","3003","3012","3102","2031","2121","2130","3021","3030","3120","2211","2301","2310","3201","3210","3300"]);$1.barchars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";$1.charvals=new Map;for(var _A=0;_A<=35;_A+=1){$p($1.charvals,$G($1.barchars,_A,1),_A)}for(var _G=0,_F=$1.barcode.length-1;_G<=_F;_G+=1){var _K=$g($1.charvals,$G($1.barcode,_G,1))!==undefined;if(!_K){$k[$j++]="bwipp.kixBadCharacter";$k[$j++]="KIX must contain only capital letters and digits";bwipp_raiseerror()}}$1.barlen=$1.barcode.length;$1.encstr=$s($1.barlen*4);$1.txt=$a($1.barlen);for(var _S=0,_R=$1.barlen-1;_S<=_R;_S+=1){$1.i=_S;$1.indx=$g($1.charvals,$G($1.barcode,$1.i,1));$P($1.encstr,$1.i*4,$g($1.encs,$1.indx));$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$1.i*4*3.312,$1.textyoffset,$1.textfont,$1.textsize]))}$1.bbs=$a($1.encstr.length);$1.bhs=$a($1.encstr.length);for(var _t=0,_s=$1.encstr.length-1;_t<=_s;_t+=1){$1.i=_t;$1.enc=$G($1.encstr,$1.i,1);if($eq($1.enc,"0")){$p($1.bbs,$1.i,3*$1.height/8);$p($1.bhs,$1.i,2*$1.height/8)}if($eq($1.enc,"1")){$p($1.bbs,$1.i,0*$1.height/8);$p($1.bhs,$1.i,5*$1.height/8)}if($eq($1.enc,"2")){$p($1.bbs,$1.i,3*$1.height/8);$p($1.bhs,$1.i,5*$1.height/8)}if($eq($1.enc,"3")){$p($1.bbs,$1.i,0*$1.height/8);$p($1.bhs,$1.i,8*$1.height/8)}}$k[$j++]=Infinity;var _1P=$1.bbs;var _1Q=$1.bhs;$k[$j++]=Infinity;for(var _1S=0,_1T=$1.bhs.length-1;_1S<_1T;_1S++){$k[$j++]=1.44;$k[$j++]=1.872}$k[$j++]=1.44;var _1U=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="bbs";$k[$j++]=_1P;$k[$j++]="bhs";$k[$j++]=_1Q;$k[$j++]="sbs";$k[$j++]=_1U;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _1Y=$d();$k[$j++]=_1Y;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_japanpost(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.includecheckintext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=.175;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.encs=$a(["300","330","312","132","321","303","123","231","213","033","030","120","102","210","012","201","021","003","333","31","13"]);$1.barchars="0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";$1.barlen=$1.barcode.length;$1.encstr=$s((20+1)*3+4);$1.digits=$a(20);$1.txt=$a($1.barlen+1);$P($1.encstr,0,$g($1.encs,19));$1.checksum=0;$1.j=0;$1.i=0;for(var _K=0,_J=$1.barlen-1;_K<=_J;_K+=1){$1.i=_K;$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;if($1.indx>=11&&$1.indx<37){if($1.j>18){break}$1.digit=~~(($1.indx-1)/10)+10;$P($1.encstr,$1.j*3+2,$g($1.encs,$1.digit));$1.checksum=$1.checksum+$1.digit;$p($1.digits,$1.j,$1.digit);$1.j=$1.j+1}if($1.j>19){break}$1.digit=0;if($1.indx==37){$1.digit=14}if($1.indx>=11&&$1.indx<37){$1.digit=($1.indx-1)%10}if($1.indx<11){$1.digit=$1.indx}$P($1.encstr,$1.j*3+2,$g($1.encs,$1.digit));$1.checksum=$1.checksum+$1.digit;$p($1.digits,$1.j,$1.digit);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),($1.j*3+2)*3.312,$1.textyoffset,$1.textfont,$1.textsize]));$1.j=$1.j+1}for(var _19=$1.j,_18=20-1;_19<=_18;_19+=1){$1.k=_19;$P($1.encstr,$1.k*3+2,$g($1.encs,14));$1.checksum=$1.checksum+14;$p($1.digits,$1.k,14)}$1.checksum=19-$1.checksum%19;$P($1.encstr,2+20*3,$g($1.encs,$1.checksum));$P($1.encstr,2+21*3,$g($1.encs,20));$1.checkdigit=" ";if($1.includecheckintext){$1.checkdigit=$G($1.barchars,$1.checksum,1)}$p($1.txt,$1.i+1,$a([$1.checkdigit,(20*3+2)*3.312,$1.textyoffset,$1.textfont,$1.textsize]));$P($1.encstr,(20+1)*3+2,$g($1.encs,20));$1.bbs=$a(21*3+4);$1.bhs=$a($1.bbs.length);for(var _1i=0,_1h=$1.bbs.length-1;_1i<=_1h;_1i+=1){$1.i=_1i;$1.enc=$G($1.encstr,$1.i,1);$1.bunit=0;$1.hunit=0;if($eq($1.enc,"0")){$1.bunit=3;$1.hunit=2}if($eq($1.enc,"1")){$1.bunit=0;$1.hunit=5}if($eq($1.enc,"2")){$1.bunit=3;$1.hunit=5}if($eq($1.enc,"3")){$1.bunit=0;$1.hunit=8}$p($1.bbs,$1.i,$1.bunit*$1.height/8);$p($1.bhs,$1.i,$1.hunit*$1.height/8)}$k[$j++]=Infinity;var _1y=$1.bbs;var _1z=$1.bhs;$k[$j++]=Infinity;for(var _21=0,_22=$1.bhs.length-1;_21<_22;_21++){$k[$j++]=1.44;$k[$j++]=1.872}$k[$j++]=1.44;var _23=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="bbs";$k[$j++]=_1y;$k[$j++]="bhs";$k[$j++]=_1z;$k[$j++]="sbs";$k[$j++]=_23;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _27=$d();$k[$j++]=_27;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_msi(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includecheck=false;$1.includetext=false;$1.includecheckintext=false;$1.checktype="mod10";$1.badmod11=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.encs=$a(["12121212","12121221","12122112","12122121","12211212","12211221","12212112","12212121","21121212","21121221","21","121"]);$1.barchars="0123456789";$1.barlen=$1.barcode.length;$1.txtlen=$1.barlen;$1.mod10=function(){$1.code=$k[--$j];$k[$j++]=0;$k[$j++]=0;$F($1.code,function(){var _E=$k[--$j];var _F=$k[--$j];var _G=$k[--$j];$k[$j++]=$f($f(_E-48)+_F*10);$k[$j++]=_G});$j--;var _H=$k[--$j];$k[$j++]=_H*2;$k[$j++]=0;for(;;){var _I=$k[--$j];var _J=$k[--$j];var _K=~~(_J/10);$k[$j++]=$f(_I+_J%10);$k[$j++]=_K;if(_K==0){$j--;break}var _L=$k[--$j];var _M=$k[--$j];$k[$j++]=_L;$k[$j++]=_M}$k[$j++]=0;$k[$j++]=0;$F($1.code,function(){var _O=$k[--$j];var _P=$k[--$j];var _Q=$k[--$j];$k[$j++]=$f(_P+$f(_O-48));$k[$j++]=_Q});var _R=$k[--$j];var _S=$k[--$j];$k[$j++]=_R;$k[$j++]=_S;$j--;var _T=$k[--$j];var _W=$s($1.code.length+1);$P(_W,0,$1.code);$p(_W,$1.code.length,$f($f(10-$f($k[--$j]+_T)%10)%10+48));$k[$j++]=_W};$1.mod11=function(){$1.code=$k[--$j];$k[$j++]=$1.code.length-1;$F($1.code,function(){var _c=$k[--$j];var _d=$k[--$j];$k[$j++]=$f(_c-48);$k[$j++]=$f(_d%6+2);$k[$j++]=$f(_d-1)});$j--;$k[$j++]=0;for(var _f=0,_g=$1.code.length;_f<_g;_f++){var _h=$k[--$j];var _i=$k[--$j];var _j=$k[--$j];$k[$j++]=$f(_h+_j*_i)}var _l=$f(11-$k[--$j]%11)%11;$k[$j++]=_l;if(_l==10&&$1.badmod11){$j--;var _o=$s($1.code.length+2);$P(_o,0,$1.code);$P(_o,$1.code.length,"10");$k[$j++]=_o}else{var _s=$s($1.code.length+1);$P(_s,0,$1.code);$p(_s,$1.code.length,$f($k[--$j]+48));$k[$j++]=_s}};$1.ncrmod11=function(){$1.code=$k[--$j];$k[$j++]=$1.code.length-1;$F($1.code,function(){var _z=$k[--$j];var _10=$k[--$j];$k[$j++]=$f(_z-48);$k[$j++]=$f(_10%8+2);$k[$j++]=$f(_10-1)});$j--;$k[$j++]=0;for(var _12=0,_13=$1.code.length;_12<_13;_12++){var _14=$k[--$j];var _15=$k[--$j];var _16=$k[--$j];$k[$j++]=$f(_14+_16*_15)}var _18=$f(11-$k[--$j]%11)%11;$k[$j++]=_18;if(_18==10&&$1.badmod11){$j--;var _1B=$s($1.code.length+2);$P(_1B,0,$1.code);$P(_1B,$1.code.length,"10");$k[$j++]=_1B}else{var _1F=$s($1.code.length+1);$P(_1F,0,$1.code);$p(_1F,$1.code.length,$f($k[--$j]+48));$k[$j++]=_1F}};if($1.includecheck){if($eq($1.checktype,"mod10")){$k[$j++]=$1.barcode;$1.mod10()}if($eq($1.checktype,"mod1010")){$k[$j++]=$1.barcode;$1.mod10();$1.mod10()}if($eq($1.checktype,"mod11")){$k[$j++]=$1.barcode;$1.mod11()}if($eq($1.checktype,"ncrmod11")){$k[$j++]=$1.barcode;$1.ncrmod11()}if($eq($1.checktype,"mod1110")){$k[$j++]=$1.barcode;$1.mod11();$1.mod10()}if($eq($1.checktype,"ncrmod1110")){$k[$j++]=$1.barcode;$1.ncrmod11();$1.mod10()}$1.barcode=$k[--$j];$1.barlen=$1.barcode.length;if($1.includecheckintext){$1.txtlen=$1.barlen}}$1.sbs=$s($1.barlen*8+5);$1.txt=$a($1.barlen);$P($1.sbs,0,$g($1.encs,10));for(var _1j=0,_1i=$1.barlen-1;_1j<=_1i;_1j+=1){$1.i=_1j;$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enc=$g($1.encs,$1.indx);$P($1.sbs,$1.i*8+2,$1.enc);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$1.i*12+3,$1.textyoffset,$1.textfont,$1.textsize]))}$P($1.sbs,$1.barlen*8+2,$g($1.encs,11));$k[$j++]=Infinity;$k[$j++]=Infinity;var _29=$1.sbs;for(var _2A=0,_2B=_29.length;_2A<_2B;_2A++){$k[$j++]=$g(_29,_2A)-48}var _2D=$a();$k[$j++]=Infinity;for(var _2F=0,_2G=~~(($1.sbs.length+1)/2);_2F<_2G;_2F++){$k[$j++]=$1.height}var _2I=$a();$k[$j++]=Infinity;for(var _2K=0,_2L=~~(($1.sbs.length+1)/2);_2K<_2L;_2K++){$k[$j++]=0}var _2M=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_2D;$k[$j++]="bhs";$k[$j++]=_2I;$k[$j++]="bbs";$k[$j++]=_2M;if($1.includetext){$k[$j++]="txt";$k[$j++]=$G($1.txt,0,$1.txtlen)}$k[$j++]="opt";$k[$j++]=$1.options;var _2S=$d();$k[$j++]=_2S;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_plessey(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.validatecheck=false;$1.includecheckintext=false;$1.unidirectional=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$1.barchars="0123456789ABCDEF";$1.charvals=new Map;for(var _9=0;_9<=15;_9+=1){$p($1.charvals,$G($1.barchars,_9,1),_9)}for(var _F=0,_E=$1.barcode.length-1;_F<=_E;_F+=1){var _J=$g($1.charvals,$G($1.barcode,_F,1))!==undefined;if(!_J){$k[$j++]="bwipp.plesseyBadCharacter";$k[$j++]="Plessey must contain only digits and letters A B C D E F";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _M=$k[--$j];$k[$j++]=$f(_M-2)}var _N=$k[--$j];$1[$k[--$j]]=_N;$1.checkbits=$a($f($1.barlen*4+8));$P($1.checkbits,$1.barlen*4,$a([0,0,0,0,0,0,0,0]));for(var _W=0,_V=$f($1.barlen-1);_W<=_V;_W+=1){$1.i=_W;$1.indx=$g($1.charvals,$G($1.barcode,$1.i,1));$P($1.checkbits,$1.i*4,$a([$1.indx&1,$1.indx>>>1&1,$1.indx>>>2&1,$1.indx>>>3]))}$1.checksalt=$a([1,1,1,1,0,1,0,0,1]);for(var _m=0,_l=$f($1.barlen*4-1);_m<=_l;_m+=1){$1.i=_m;if($g($1.checkbits,$1.i)==1){for(var _q=0;_q<=8;_q+=1){$1.j=_q;$p($1.checkbits,$1.i+$1.j,$xo($g($1.checkbits,$1.i+$1.j),$g($1.checksalt,$1.j)))}}}$1.checkval=0;for(var _11=0;_11<=7;_11+=1){$1.i=_11;$1.checkval=$f($1.checkval+~~Math.pow(2,$1.i)*$g($1.checkbits,$f($1.barlen*4+$1.i)))}$1.checksum1=$1.checkval&15;$1.checksum2=$1.checkval>>>4;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum1)||$g($1.barcode,$f($1.barlen+1))!=$g($1.barchars,$1.checksum2)){$k[$j++]="bwipp.plesseyBadCheckDigits";$k[$j++]="Incorrect Plessey check digits provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen)}$1.encs=$a(["14141414","32141414","14321414","32321414","14143214","32143214","14323214","32323214","14141432","32141432","14321432","32321432","14143232","32143232","14323232","32323232","32321432","541412323","323"]);var _1T=$1.unidirectional?27:33;$1.sbs=$s($f($1.barlen*8+_1T));$1.txt=$a($f($1.barlen+2));$P($1.sbs,0,$g($1.encs,16));for(var _1c=0,_1b=$f($1.barlen-1);_1c<=_1b;_1c+=1){$1.i=_1c;$1.indx=$g($1.charvals,$G($1.barcode,$1.i,1));$P($1.sbs,$1.i*8+8,$g($1.encs,$1.indx));$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$1.i*20+20,$1.textyoffset,$1.textfont,$1.textsize]))}$P($1.sbs,$f($1.barlen*8+8),$g($1.encs,$1.checksum1));$P($1.sbs,$f($1.barlen*8+16),$g($1.encs,$1.checksum2));if($1.includecheckintext){$p($1.txt,$1.barlen,$a([$G($1.barchars,$1.checksum1,1),$f($1.barlen*20+20),$1.textyoffset,$1.textfont,$1.textsize]));$p($1.txt,$f($1.barlen+1),$a([$G($1.barchars,$1.checksum2,1),$f($f($1.barlen+1)*20+20),$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$1.barlen,$a([" ",$f($1.barlen*20+20),$1.textyoffset,$1.textfont,$1.textsize]));$p($1.txt,$f($1.barlen+1),$a([" ",$f($f($1.barlen+1)*20+20),$1.textyoffset,$1.textfont,$1.textsize]))}var _2k=$1.unidirectional?18:17;$P($1.sbs,$f($1.barlen*8+24),$g($1.encs,_2k));$k[$j++]=Infinity;$k[$j++]=Infinity;var _2m=$1.sbs;for(var _2n=0,_2o=_2m.length;_2n<_2o;_2n++){$k[$j++]=$g(_2m,_2n)-48}var _2q=$a();$k[$j++]=Infinity;for(var _2s=0,_2t=~~(($1.sbs.length+1)/2);_2s<_2t;_2s++){$k[$j++]=$1.height}var _2v=$a();$k[$j++]=Infinity;for(var _2x=0,_2y=~~(($1.sbs.length+1)/2);_2x<_2y;_2x++){$k[$j++]=0}var _2z=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_2q;$k[$j++]="bhs";$k[$j++]=_2v;$k[$j++]="bbs";$k[$j++]=_2z;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _33=$d();$k[$j++]=_33;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_telepen(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.numeric=false;$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=1;$1.parse=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;var _A=new Map([["parse",$1.parse],["parseonly",true],["parsefnc",false]]);$1.fncvals=_A;$k[$j++]="barcode";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _D=$k[--$j];$1[$k[--$j]]=_D;$1.barlen=$1.barcode.length;delete $1.options["parse"];$1.encs=$a(["31313131","1131313111","33313111","1111313131","3111313111","11333131","13133131","111111313111","31333111","1131113131","33113131","1111333111","3111113131","1113133111","1311133111","111111113131","3131113111","11313331","333331","111131113111","31113331","1133113111","1313113111","1111113331","31131331","113111113111","3311113111","1111131331","311111113111","1113111331","1311111331","11111111113111","31313311","1131311131","33311131","1111313311","3111311131","11333311","13133311","111111311131","31331131","1131113311","33113311","1111331131","3111113311","1113131131","1311131131","111111113311","3131111131","1131131311","33131311","111131111131","3111131311","1133111131","1313111131","111111131311","3113111311","113111111131","3311111131","111113111311","311111111131","111311111311","131111111311","11111111111131","3131311111","11313133","333133","111131311111","31113133","1133311111","1313311111","1111113133","313333","113111311111","3311311111","11113333","311111311111","11131333","13111333","11111111311111","31311133","1131331111","33331111","1111311133","3111331111","11331133","13131133","111111331111","3113131111","1131111133","33111133","111113131111","3111111133","111311131111","131111131111","111111111133","31311313","113131111111","3331111111","1111311313","311131111111","11331313","13131313","11111131111111","3133111111","1131111313","33111313","111133111111","3111111313","111313111111","131113111111","111111111313","313111111111","1131131113","33131113","11113111111111","3111131113","113311111111","131311111111","111111131113","3113111113","11311111111111","331111111111","111113111113","31111111111111","111311111113","131111111113","1111111111111111"]);$1.barlen=$1.barcode.length;$1.sbs=$s($1.barlen*16+48);$1.txt=$a($1.barlen);$1.enc=$g($1.encs,95);$P($1.sbs,0,$1.enc);$1.l=$1.enc.length;$1.checksum=0;$1.i=0;$1.j=0;for(;;){if($1.i==$1.barlen){break}if($1.numeric){if($g($1.barcode,$1.i)>16){$1.np=$G($1.barcode,$1.i,2);if($eq($G($1.np,1,1),"X")){$1.indx=~~$z($G($1.np,0,1))+17}else{$1.indx=~~$z($1.np)+27}$p($1.txt,$1.j,$a([$1.np,$1.j*16+16,$1.textyoffset,$1.textfont,$1.textsize]));$1.i=$1.i+2}else{$1.indx=$g($1.barcode,$1.i);$p($1.txt,$1.j,$a([" ",$1.j*16+16,$1.textyoffset,$1.textfont,$1.textsize]));$1.i=$1.i+1}}else{$1.indx=$g($1.barcode,$1.i);if($1.indx>=32&&$1.indx<=126){$p($1.txt,$1.j,$a([$G($1.barcode,$1.i,1),$1.j*16+16,$1.textyoffset,$1.textfont,$1.textsize]))}else{$p($1.txt,$1.j,$a([" ",$1.j*16+16,$1.textyoffset,$1.textfont,$1.textsize]))}$1.i=$1.i+1}$1.checksum=$f($1.checksum+$1.indx);$1.enc=$g($1.encs,$1.indx);$P($1.sbs,$1.l,$1.enc);$1.l=$1.l+$1.enc.length;$1.j=$1.j+1}$1.checksum=$f(127-$1.checksum%127)%127;$1.enc=$g($1.encs,$1.checksum);$P($1.sbs,$1.l,$1.enc);$1.l=$1.l+$1.enc.length;$1.enc=$g($1.encs,122);$P($1.sbs,$1.l,$1.enc);$1.l=$1.l+$1.enc.length;$1.sbs=$G($1.sbs,0,$1.l);$1.txt=$G($1.txt,0,$1.j);$k[$j++]=Infinity;$k[$j++]=Infinity;var _1u=$1.sbs;for(var _1v=0,_1w=_1u.length;_1v<_1w;_1v++){$k[$j++]=$f($g(_1u,_1v)-48)}var _1y=$a();$k[$j++]=Infinity;for(var _20=0,_21=~~(($1.sbs.length+1)/2);_20<_21;_20++){$k[$j++]=$1.height}var _23=$a();$k[$j++]=Infinity;for(var _25=0,_26=~~(($1.sbs.length+1)/2);_25<_26;_25++){$k[$j++]=0}var _27=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_1y;$k[$j++]="bhs";$k[$j++]=_23;$k[$j++]="bbs";$k[$j++]=_27;if($1.includetext){$k[$j++]="txt";$k[$j++]=$1.txt}$k[$j++]="opt";$k[$j++]=$1.options;var _2B=$d();$k[$j++]=_2B;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_telepennumeric(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=-8;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$p($1.options,"dontdraw",true);$p($1.options,"numeric",true);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_telepen();var _E=$k[--$j];$1[$k[--$j]]=_E;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_posicode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=-8;$1.height=1;$1.encoding="auto";$1.version="a";$1.checkoffset=0;$1.raw=false;$1.parse=false;$1.parsefnc=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.encoding=""+$1.encoding;$1.version=""+$1.version;$1.checkoffset=~~$1.checkoffset;$1.height=+$1.height;$1.la0=-1;$1.la1=-2;$1.la2=-3;$1.sf0=-4;$1.sf1=-5;$1.sf2=-6;$1.fn1=-7;$1.fn2=-8;$1.fn3=-9;$1.fn4=-10;var _1l=new Map([["normal",$a([$a(["0","^","'"]),$a(["1",";",27]),$a(["2","<",28]),$a(["3","=",29]),$a(["4",">",30]),$a(["5","?",31]),$a(["6","@","!"]),$a(["7","[",'"']),$a(["8",92,"#"]),$a(["9","]","&"]),$a(["A","a",1]),$a(["B","b",2]),$a(["C","c",3]),$a(["D","d",4]),$a(["E","e",5]),$a(["F","f",6]),$a(["G","g",7]),$a(["H","h",8]),$a(["I","i",9]),$a(["J","j",10]),$a(["K","k",11]),$a(["L","l",12]),$a(["M","m",13]),$a(["N","n",14]),$a(["O","o",15]),$a(["P","p",16]),$a(["Q","q",17]),$a(["R","r",18]),$a(["S","s",19]),$a(["T","t",20]),$a(["U","u",21]),$a(["V","v",22]),$a(["W","w",23]),$a(["X","x",24]),$a(["Y","y",25]),$a(["Z","z",26]),$a(["-","_",40]),$a([".","`",41]),$a([" ",127,0]),$a(["$","{","*"]),$a(["/","|",","]),$a(["+","}",":"]),$a(["%","~",$1.fn1]),$a([$1.la1,$1.la0,$1.fn2]),$a([$1.sf1,$1.sf0,$1.fn3]),$a([$1.sf2,$1.sf2,$1.fn4])])],["limited",$a([$a(["0",-98,-98]),$a(["1",-98,-98]),$a(["2",-98,-98]),$a(["3",-98,-98]),$a(["4",-98,-98]),$a(["5",-98,-98]),$a(["6",-98,-98]),$a(["7",-98,-98]),$a(["8",-98,-98]),$a(["9",-98,-98]),$a(["A",-98,-98]),$a(["B",-98,-98]),$a(["C",-98,-98]),$a(["D",-98,-98]),$a(["E",-98,-98]),$a(["F",-98,-98]),$a(["G",-98,-98]),$a(["H",-98,-98]),$a(["I",-98,-98]),$a(["J",-98,-98]),$a(["K",-98,-98]),$a(["L",-98,-98]),$a(["M",-98,-98]),$a(["N",-98,-98]),$a(["O",-98,-98]),$a(["P",-98,-98]),$a(["Q",-98,-98]),$a(["R",-98,-98]),$a(["S",-98,-98]),$a(["T",-98,-98]),$a(["U",-98,-98]),$a(["V",-98,-98]),$a(["W",-98,-98]),$a(["X",-98,-98]),$a(["Y",-98,-98]),$a(["Z",-98,-98]),$a(["-",-98,-98]),$a([".",-98,-98])])]]);$k[$j++]=_1l;if($eq($1.version,"a")||$eq($1.version,"b")){$k[$j++]="normal"}else{$k[$j++]="limited"}var _1o=$k[--$j];$1.charmaps=$g($k[--$j],_1o);var _1r=$1.charmaps;var _1s=$1.charmaps;var _1t=$1.charmaps;$1.charvals=$a([new Map,new Map,new Map]);for(var _1x=0,_1w=$1.charmaps.length-1;_1x<=_1w;_1x+=1){$1.i=_1x;$1.encs=$g($1.charmaps,$1.i);for(var _21=0;_21<=2;_21+=1){$1.j=_21;var _24=$g($1.encs,$1.j);$k[$j++]=_24;if($eq($t(_24),"stringtype")){var _27=$g($k[--$j],0);$k[$j++]=_27}$p($g($1.charvals,$1.j),$k[--$j],$1.i)}}$1.set0=$g($1.charvals,0);$1.set1=$g($1.charvals,1);$1.set2=$g($1.charvals,2);if($1.raw){$1.encoding="raw"}if($eq($1.encoding,"raw")){$1.cws=$a($1.barcode.length);$1.i=0;$1.j=0;for(;;){if($1.i==$1.barcode.length){break}$1.cw=~~$z($G($1.barcode,$1.i+1,3));$p($1.cws,$1.j,$1.cw);$1.i=$1.i+4;$1.j=$1.j+1}$1.cws=$G($1.cws,0,$1.j);$1.text=""}if($eq($1.encoding,"auto")){var _2g=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["FNC1",$1.fn1],["FNC2",$1.fn2],["FNC3",$1.fn3]]);$1.fncvals=_2g;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _2j=$k[--$j];$1[$k[--$j]]=_2j;$1.msglen=$1.msg.length;$1.text=$s($1.msglen);for(var _2q=0,_2p=$1.msglen-1;_2q<=_2p;_2q+=1){$1.i=_2q;var _2v=$g($1.msg,$1.i);$k[$j++]=$1.text;$k[$j++]=$1.i;$k[$j++]=_2v;if(_2v<0){$j--;$k[$j++]=32}var _2w=$k[--$j];var _2x=$k[--$j];$p($k[--$j],_2x,_2w)}$k[$j++]=Infinity;for(var _30=0,_31=$1.msglen;_30<_31;_30++){$k[$j++]=0}$k[$j++]=0;$1.numSA=$a();$k[$j++]=Infinity;for(var _34=0,_35=$1.msglen;_34<_35;_34++){$k[$j++]=0}$k[$j++]=0;$1.numEA=$a();for(var _38=$1.msglen-1;_38>=0;_38-=1){$1.i=_38;if($g($1.msg,$1.i)>=0){if($g($1.msg,$1.i)>=128){$p($1.numEA,$1.i,$f($g($1.numEA,$1.i+1)+1))}else{$p($1.numSA,$1.i,$f($g($1.numSA,$1.i+1)+1))}}}$1.ea=false;$1.msgtmp=$a([]);for(var _3S=0,_3R=$1.msglen-1;_3S<=_3R;_3S+=1){$1.i=_3S;$1.c=$g($1.msg,$1.i);if(!$xo($1.ea,$1.c<128)&&$1.c>=0){if($1.ea){$k[$j++]=$1.numSA}else{$k[$j++]=$1.numEA}var _3e=$g($k[--$j],$1.i);var _3h=$f(_3e+$1.i)==$1.msglen?3:5;if(_3e<_3h){$k[$j++]=Infinity;$q($1.msgtmp);$k[$j++]=$1.fn4;$1.msgtmp=$a()}else{$k[$j++]=Infinity;$q($1.msgtmp);$k[$j++]=$1.fn4;$k[$j++]=$1.fn4;$1.msgtmp=$a();$1.ea=!$1.ea}}$k[$j++]=Infinity;$q($1.msgtmp);if($1.c>=0){$k[$j++]=$1.c&127}else{$k[$j++]=$1.c}$1.msgtmp=$a()}$1.msg=$1.msgtmp;$1.msglen=$1.msg.length;$1.enc=function(){var _3x=$k[--$j];$p($1.cws,$1.j,$g(_3x,$k[--$j]));$1.j=$1.j+1};$1.cws=$a($1.msglen*2);$1.i=0;$1.j=0;$1.cset="set0";for(;;){if($1.i==$1.msglen){break}for(;;){$1.char1=$g($1.msg,$1.i);$k[$j++]="char2";if($1.i+1<$1.msglen){$k[$j++]=$g($1.msg,$1.i+1)}else{$k[$j++]=-99}var _4F=$k[--$j];$1[$k[--$j]]=_4F;var _4K=$g($1[$1.cset],$1.char1)!==undefined;if(_4K){$k[$j++]=$1.char1;$k[$j++]=$1[$1.cset];$1.enc();$1.i=$1.i+1;break}var _4R=$g($1.set2,$1.char1)!==undefined;if(_4R){$k[$j++]=$1.sf2;$k[$j++]=$1[$1.cset];$1.enc();$k[$j++]=$1.char1;$k[$j++]=$1.set2;$1.enc();$1.i=$1.i+1;break}var _4b=$g($1[$1.cset],$1.char2)!==undefined;if(!_4b){if($eq($1.cset,"set0")){$k[$j++]=$1.la1;$k[$j++]=$1[$1.cset];$1.enc();$1.cset="set1"}else{$k[$j++]=$1.la0;$k[$j++]=$1[$1.cset];$1.enc();$1.cset="set0"}break}else{if($eq($1.cset,"set0")){$k[$j++]=$1.sf1;$k[$j++]=$1[$1.cset];$1.enc();$k[$j++]=$1.char1;$k[$j++]=$1.set1;$1.enc()}else{$k[$j++]=$1.sf0;$k[$j++]=$1[$1.cset];$1.enc();$k[$j++]=$1.char1;$k[$j++]=$1.set0;$1.enc()}$1.i=$1.i+1;break}break}}$1.cws=$G($1.cws,0,$1.j)}var _52=new Map([["a",$a(["141112","131212","121312","111412","131113","121213","111313","121114","111214","111115","181111","171211","161311","151411","141511","131611","121711","111811","171112","161212","151312","141412","131512","121612","111712","161113","151213","141313","131413","121513","111613","151114","141214","131314","121414","111514","141115","131215","121315","111415","131116","121216","111316","121117","111217","111118","1<111112","111111111;1"])],["b",$a(["151213","141313","131413","121513","141214","131314","121414","131215","121315","121216","191212","181312","171412","161512","151612","141712","131812","121912","181213","171313","161413","151513","141613","131713","121813","171214","161314","151414","141514","131614","121714","161215","151315","141415","131515","121615","151216","141316","131416","121516","141217","131317","121417","131218","121318","121219","1<121312","121212121<1"])],["limiteda",$a(["111411","111312","111213","111114","121311","121212","121113","141111","131211","131112","171111","161211","151311","141411","131511","121611","111711","161112","151212","141312","131412","121512","111612","151113","141213","131313","121413","111513","141114","131214","121314","111414","131115","121215","111315","121116","111216","111117","151111","1"])],["limitedb",$a(["121512","121413","121314","121215","131412","131313","131214","151212","141312","141213","181212","171312","161412","151512","141612","131712","121812","171213","161313","151413","141513","131613","121713","161214","151314","141414","131514","121614","151215","141315","131415","121515","141216","131316","121416","131217","121317","121218","141212","1"])]]);$1.encs=$g(_52,$1.version);$1.c2w=$a([$a([495,330,210,126,70,35,15,5]),$a([165,120,84,56,35,20,10,4]),$a([45,36,28,21,15,10,6,3]),$a([9,8,7,6,5,4,3,2]),$a([1,1,1,1,1,1,1,1])]);$1.v=0;var _5B=$1.cws;for(var _5C=0,_5D=_5B.length;_5C<_5D;_5C++){$1.cw=$g(_5B,_5C);for(var _5F=0,_5G=6;_5F<_5G;_5F++){if((($1.cw^$1.v)&1)!=0){$1.v=$1.v^7682}$1.v=$1.v>>>1;$1.cw=$1.cw>>>1}}$1.v=$1.v+$1.checkoffset;if($eq($1.version,"limiteda")||$eq($1.version,"limitedb")){$1.v=$1.v&1023;if($1.v>824&&$1.v<853){$1.v=$1.v+292}}else{$1.v=($1.v&1023)+45}$1.d=$a([2,2,2,2,2,2]);$1.r=0;$1.c=0;$1.w=0;$1.sum=0;for(;;){if($1.sum==$1.v){break}$1.t=$f($1.sum+$g($g($1.c2w,$1.r),$1.c));if($1.t==$1.v){$1.w=$1.w+1;$p($1.d,$1.r,$1.w+2);$1.sum=$1.t}if($1.t>$1.v){$p($1.d,$1.r,$1.w+2);$1.r=$1.r+1;$1.w=0}if($1.t<$1.v){$1.c=$1.c+1;$1.w=$1.w+1;$1.sum=$1.t}}$k[$j++]=20;for(var _5w=0;_5w<=4;_5w+=1){var _5z=$k[--$j];$k[$j++]=$f(_5z-$g($1.d,_5w))}$p($1.d,5,$k[--$j]);if($eq($1.version,"b")||$eq($1.version,"limitedb")){$k[$j++]=Infinity;var _64=$1.d;for(var _65=0,_66=_64.length;_65<_66;_65++){$k[$j++]=$f($g(_64,_65)+1)}$1.d=$a()}$1.cbs=$Z($s(12),"111111111111");for(var _6B=5;_6B>=0;_6B-=1){$1.i=_6B;$p($1.cbs,(5-$1.i)*2+1,$f($g($1.d,$1.i)+47))}$1.sbs=$s($1.cws.length*6+31);var _6L=$g($1.encs,$1.encs.length-2);$P($1.sbs,0,_6L);$1.j=_6L.length;for(var _6P=0,_6O=$1.cws.length-1;_6P<=_6O;_6P+=1){$1.i=_6P;$P($1.sbs,$1.j,$g($1.encs,$g($1.cws,$1.i)));$1.j=$1.j+6}$P($1.sbs,$1.j,$1.cbs);$1.j=$1.j+12;var _6e=$g($1.encs,$1.encs.length-1);$P($1.sbs,$1.j,_6e);$1.j=_6e.length+$1.j;$1.sbs=$G($1.sbs,0,$1.j);$k[$j++]=Infinity;$k[$j++]=Infinity;var _6l=$1.sbs;for(var _6m=0,_6n=_6l.length;_6m<_6n;_6m++){$k[$j++]=$f($g(_6l,_6m)-48)}var _6p=$a();$k[$j++]=Infinity;for(var _6r=0,_6s=~~(($1.sbs.length+1)/2);_6r<_6s;_6r++){$k[$j++]=$1.height}var _6u=$a();$k[$j++]=Infinity;for(var _6w=0,_6x=~~(($1.sbs.length+1)/2);_6w<_6x;_6w++){$k[$j++]=0}var _6y=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_6p;$k[$j++]="bhs";$k[$j++]=_6u;$k[$j++]="bbs";$k[$j++]=_6y;$k[$j++]="txt";$k[$j++]=$a([$a([$1.text,$1.textxoffset,$1.textyoffset,$1.textfont,$1.textsize])]);$k[$j++]="textxalign";$k[$j++]="center";$k[$j++]="opt";$k[$j++]=$1.options;var _77=$d();$k[$j++]=_77;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_codablockf(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.rows=-1;$1.columns=8;$1.rowheight=10;$1.sepheight=1;$1.parse=false;$1.parsefnc=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.rows=~~$1.rows;$1.columns=~~$1.columns;$1.rowheight=~~$1.rowheight;$1.sepheight=~~$1.sepheight;$k[$j++]="c";if($1.columns>=4&&$1.columns<=62){$k[$j++]=$1.columns}else{$k[$j++]=8}var _C=$k[--$j];$1[$k[--$j]]=_C;$k[$j++]="rows";if($1.rows>=2&&$1.rows<=44){$k[$j++]=$1.rows}else{$k[$j++]=-1}var _H=$k[--$j];$1[$k[--$j]]=_H;$1.swa=-1;$1.swb=-2;$1.swc=-3;$1.sft=-4;$1.fn1=-5;$1.fn2=-6;$1.fn3=-7;$1.fn4=-8;$1.sta=-9;$1.stp=-10;var _N=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["FNC1",$1.fn1],["FNC3",$1.fn3]]);$1.fncvals=_N;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _Q=$k[--$j];$1[$k[--$j]]=_Q;$1.msglen=$1.msg.length;$1.msgtmp=$a([]);var _U=$1.msg;for(var _V=0,_W=_U.length;_V<_W;_V++){$1.char=$g(_U,_V);$k[$j++]=Infinity;$q($1.msgtmp);if($1.char<128){$k[$j++]=$1.char}else{$k[$j++]=$1.fn4;$k[$j++]=$1.char&127}$1.msgtmp=$a()}$1.msg=$1.msgtmp;$1.msglen=$1.msg.length;$1.charmaps=$a([$a([32,32,"00"]),$a(["!","!","01"]),$a(['"','"',"02"]),$a(["#","#","03"]),$a(["$","$","04"]),$a(["%","%","05"]),$a(["&","&","06"]),$a(["'","'","07"]),$a([40,40,"08"]),$a([41,41,"09"]),$a(["*","*","10"]),$a(["+","+","11"]),$a([",",",","12"]),$a(["-","-","13"]),$a([".",".","14"]),$a(["/","/","15"]),$a(["0","0","16"]),$a(["1","1","17"]),$a(["2","2","18"]),$a(["3","3","19"]),$a(["4","4","20"]),$a(["5","5","21"]),$a(["6","6","22"]),$a(["7","7","23"]),$a(["8","8","24"]),$a(["9","9","25"]),$a([":",":","26"]),$a([";",";","27"]),$a(["<","<","28"]),$a(["=","=","29"]),$a([">",">","30"]),$a(["?","?","31"]),$a(["@","@","32"]),$a(["A","A","33"]),$a(["B","B","34"]),$a(["C","C","35"]),$a(["D","D","36"]),$a(["E","E","37"]),$a(["F","F","38"]),$a(["G","G","39"]),$a(["H","H","40"]),$a(["I","I","41"]),$a(["J","J","42"]),$a(["K","K","43"]),$a(["L","L","44"]),$a(["M","M","45"]),$a(["N","N","46"]),$a(["O","O","47"]),$a(["P","P","48"]),$a(["Q","Q","49"]),$a(["R","R","50"]),$a(["S","S","51"]),$a(["T","T","52"]),$a(["U","U","53"]),$a(["V","V","54"]),$a(["W","W","55"]),$a(["X","X","56"]),$a(["Y","Y","57"]),$a(["Z","Z","58"]),$a(["[","[","59"]),$a([92,92,"60"]),$a(["]","]","61"]),$a(["^","^","62"]),$a(["_","_","63"]),$a([0,"`","64"]),$a([1,"a","65"]),$a([2,"b","66"]),$a([3,"c","67"]),$a([4,"d","68"]),$a([5,"e","69"]),$a([6,"f","70"]),$a([7,"g","71"]),$a([8,"h","72"]),$a([9,"i","73"]),$a([10,"j","74"]),$a([11,"k","75"]),$a([12,"l","76"]),$a([13,"m","77"]),$a([14,"n","78"]),$a([15,"o","79"]),$a([16,"p","80"]),$a([17,"q","81"]),$a([18,"r","82"]),$a([19,"s","83"]),$a([20,"t","84"]),$a([21,"u","85"]),$a([22,"v","86"]),$a([23,"w","87"]),$a([24,"x","88"]),$a([25,"y","89"]),$a([26,"z","90"]),$a([27,"{","91"]),$a([28,"|","92"]),$a([29,"}","93"]),$a([30,"~","94"]),$a([31,127,"95"]),$a([$1.fn3,$1.fn3,"96"]),$a([$1.fn2,$1.fn2,"97"]),$a([$1.sft,$1.sft,"98"]),$a([$1.swc,$1.swc,"99"]),$a([$1.swb,$1.fn4,$1.swb]),$a([$1.fn4,$1.swa,$1.swa]),$a([$1.fn1,$1.fn1,$1.fn1]),$a([$1.sta,$1.sta,$1.sta]),$a([$1.stp,$1.stp,$1.stp])]);$1.charvals=$a([new Map,new Map,new Map]);for(var _2o=0,_2n=$1.charmaps.length-1;_2o<=_2n;_2o+=1){$1.i=_2o;$1.encs=$g($1.charmaps,$1.i);for(var _2s=0;_2s<=2;_2s+=1){$1.j=_2s;var _2v=$g($1.encs,$1.j);$k[$j++]=_2v;if($eq($t(_2v),"stringtype")){var _2y=$g($k[--$j],0);$k[$j++]=_2y}$p($g($1.charvals,$1.j),$k[--$j],$1.i)}}$1.seta=$g($1.charvals,0);$1.setb=$g($1.charvals,1);$1.setc=$g($1.charvals,2);$1.numsscr=function(){$1.n=0;$1.s=0;$1.p=$k[--$j];for(;;){if($1.p>=$1.msglen){break}var _3F=$g($1.msg,$1.p);var _3H=$g($1.setc,_3F)!==undefined;$k[$j++]=_3F;if(!_3H){$j--;break}if($k[--$j]==$1.fn1){if($1.s%2==0){$1.s=$1.s+1}else{break}}$1.n=$1.n+1;$1.s=$1.s+1;$1.p=$f($1.p+1)}$k[$j++]=$1.n;$k[$j++]=$1.s};$1.enca=function(){$p($1.cws,$1.j,$g($1.seta,$k[--$j]));$1.j=$1.j+1};$1.encb=function(){$p($1.cws,$1.j,$g($1.setb,$k[--$j]));$1.j=$1.j+1};$1.encc=function(){var _3d=$k[--$j];$k[$j++]=_3d;if($ne($t(_3d),"arraytype")){var _3h=$g($1.setc,$k[--$j]);$k[$j++]=_3h}else{$q($k[--$j]);var _3j=$k[--$j];var _3k=$k[--$j];$k[$j++]=$f($f(_3j-48)+$f(_3k-48)*10)}$p($1.cws,$1.j,$k[--$j]);$1.j=$1.j+1};$1.anotb=function(){var _3p=$k[--$j];var _3r=$g($1.seta,_3p)!==undefined;var _3t=$g($1.setb,_3p)!==undefined;$k[$j++]=_3r&&!_3t};$1.bnota=function(){var _3u=$k[--$j];var _3w=$g($1.setb,_3u)!==undefined;var _3y=$g($1.seta,_3u)!==undefined;$k[$j++]=_3w&&!_3y};$k[$j++]=Infinity;for(var _40=0,_41=$1.msg.length;_40<_41;_40++){$k[$j++]=0}$k[$j++]=9999;$1.nextanotb=$a();$k[$j++]=Infinity;for(var _44=0,_45=$1.msg.length;_44<_45;_44++){$k[$j++]=0}$k[$j++]=9999;$1.nextbnota=$a();for(var _48=$1.msg.length-1;_48>=0;_48-=1){$1.i=_48;$k[$j++]=$g($1.msg,$1.i);$1.anotb();if($k[--$j]){$p($1.nextanotb,$1.i,0)}else{$p($1.nextanotb,$1.i,$f($g($1.nextanotb,$1.i+1)+1))}$k[$j++]=$g($1.msg,$1.i);$1.bnota();if($k[--$j]){$p($1.nextbnota,$1.i,0)}else{$p($1.nextbnota,$1.i,$f($g($1.nextbnota,$1.i+1)+1))}}$1.abeforeb=function(){var _4V=$k[--$j];$k[$j++]=$lt($g($1.nextanotb,_4V),$g($1.nextbnota,_4V))};$1.bbeforea=function(){var _4a=$k[--$j];$k[$j++]=$lt($g($1.nextbnota,_4a),$g($1.nextanotb,_4a))};$1.padrow=function(){for(var _4g=0,_4h=$k[--$j];_4g<_4h;_4g++){for(;;){if($eq($1.cset,"seta")){$k[$j++]=$1.swc;$1.enca();$1.cset="setc";break}if($eq($1.cset,"setb")){$k[$j++]=$1.swc;$1.encb();$1.cset="setc";break}if($eq($1.cset,"setc")){$k[$j++]=$1.swb;$1.encc();$1.cset="setb";break}}}};$1.cws=$a($f($1.c+5)*44);$1.i=0;$1.j=0;$1.r=1;$1.lastrow=false;for(;;){if($1.lastrow){break}$k[$j++]=$1.sta;$1.enca();if($1.i<$1.msglen){$k[$j++]=$1.i;$1.numsscr()}else{$k[$j++]=-1;$k[$j++]=-1}$1.nums=$k[--$j];$1.nchars=$k[--$j];for(;;){if($1.msglen==0){$k[$j++]=$1.swb;$1.enca();$1.cset="setb";break}if($1.nums>=2){$k[$j++]=$1.swc;$1.enca();$1.cset="setc";break}$k[$j++]=$1.i;$1.abeforeb();if($k[--$j]){$k[$j++]=$1.sft;$1.enca();$1.cset="seta";break}$k[$j++]=$1.swb;$1.enca();$1.cset="setb";break}$1.j=$1.j+1;$1.endofrow=false;for(;;){$1.rem=$f($f($1.c+3)-$1.j%$f($1.c+5));if($1.i==$1.msglen||$1.endofrow){break}$k[$j++]=$1.i;$1.numsscr();$1.nums=$k[--$j];$1.nchars=$k[--$j];$k[$j++]="remnums";if($1.nums>$1.rem*2){$k[$j++]=$1.rem*2}else{$k[$j++]=$1.nums}var _5J=$k[--$j];$1[$k[--$j]]=_5J;for(;;){if(($eq($1.cset,"seta")||$eq($1.cset,"setb"))&&$1.remnums>=4&&$g($1.msg,$1.i)!=$1.fn1){if($1.remnums%2==0&&$1.rem>=3){$k[$j++]=$1.swc;if($eq($1.cset,"seta")){$1.enca()}else{$1.encb()}$1.cset="setc";for(var _5W=0,_5X=2;_5W<_5X;_5W++){if($g($1.msg,$1.i)==$1.fn1){$k[$j++]=$1.fn1;$1.encc();$1.i=$1.i+1}else{$k[$j++]=$G($1.msg,$1.i,2);$1.encc();$1.i=$1.i+2}}break}if($1.remnums%2!=0&&$1.rem>=4){$k[$j++]=$g($1.msg,$1.i);if($eq($1.cset,"seta")){$1.enca()}else{$1.encb()}$1.i=$1.i+1;$k[$j++]=$1.swc;if($eq($1.cset,"seta")){$1.enca()}else{$1.encb()}$1.cset="setc";for(var _5r=0,_5s=2;_5r<_5s;_5r++){if($g($1.msg,$1.i)==$1.fn1){$k[$j++]=$1.fn1;$1.encc();$1.i=$1.i+1}else{$k[$j++]=$G($1.msg,$1.i,2);$1.encc();$1.i=$1.i+2}}break}}$k[$j++]=$eq($1.cset,"setb");$k[$j++]=$g($1.msg,$1.i);$1.anotb();var _67=$k[--$j];var _68=$k[--$j];if(_68&&_67&&$1.rem>=2){if($1.i<$1.msglen-1){$k[$j++]=$1.i+1;$1.bbeforea();if($k[--$j]){$k[$j++]=$1.sft;$1.encb();$k[$j++]=$g($1.msg,$1.i);$1.enca();$1.i=$1.i+1;break}}$k[$j++]=$1.swa;$1.encb();$1.cset="seta";$k[$j++]=$g($1.msg,$1.i);$1.enca();$1.i=$1.i+1;break}$k[$j++]=$eq($1.cset,"seta");$k[$j++]=$g($1.msg,$1.i);$1.bnota();var _6S=$k[--$j];var _6T=$k[--$j];if(_6T&&_6S&&$1.rem>=2){if($1.i<$1.msglen-1){$k[$j++]=$1.i+1;$1.abeforeb();if($k[--$j]){$k[$j++]=$1.sft;$1.enca();$k[$j++]=$g($1.msg,$1.i);$1.encb();$1.i=$1.i+1;break}}$k[$j++]=$1.swb;$1.enca();$1.cset="setb";$k[$j++]=$g($1.msg,$1.i);$1.encb();$1.i=$1.i+1;break}if($eq($1.cset,"setc")&&$1.remnums<2&&$1.rem>=2){$k[$j++]=$1.i;$1.abeforeb();if($k[--$j]){$k[$j++]=$1.swa;$1.encc();$1.cset="seta";$k[$j++]=$g($1.msg,$1.i);$1.enca();$1.i=$1.i+1;break}$k[$j++]=$1.swb;$1.encc();$1.cset="setb";$k[$j++]=$g($1.msg,$1.i);$1.encb();$1.i=$1.i+1;break}var _73=$g($1.seta,$g($1.msg,$1.i))!==undefined;if($eq($1.cset,"seta")&&_73&&$1.rem>=1){$k[$j++]=$g($1.msg,$1.i);$1.enca();$1.i=$1.i+1;break}var _7E=$g($1.setb,$g($1.msg,$1.i))!==undefined;if($eq($1.cset,"setb")&&_7E&&$1.rem>=1){$k[$j++]=$g($1.msg,$1.i);$1.encb();$1.i=$1.i+1;break}if($eq($1.cset,"setc")&&$1.remnums>=2&&$1.rem>=1){if($g($1.msg,$1.i)==$1.fn1){$k[$j++]=$1.fn1;$1.encc();$1.i=$1.i+1}else{$k[$j++]=$G($1.msg,$1.i,2);$1.encc();$1.i=$1.i+2}break}$1.endofrow=true;break}}if(($1.r>=$1.rows||$1.rows==-1)&&$1.r>1&&$1.i==$1.msglen&&$1.rem>=2){$k[$j++]=$f($1.rem-2);$1.padrow();$1.j=$1.j+3;$k[$j++]=$1.stp;$1.enca();$1.lastrow=true}else{$k[$j++]=$1.rem;$1.padrow();$1.j=$1.j+1;$k[$j++]=$1.stp;$1.enca();$1.r=$1.r+1}}$1.cws=$G($1.cws,0,$1.j);$k[$j++]=Infinity;for(var _7o=64;_7o<=95;_7o+=1){$k[$j++]=_7o}for(var _7p=0;_7p<=15;_7p+=1){$k[$j++]=_7p}for(var _7q=26;_7q<=63;_7q+=1){$k[$j++]=_7q}$1.abmap=$a();$k[$j++]=Infinity;for(var _7s=0;_7s<=85;_7s+=1){$k[$j++]=_7s}$1.cmap=$a();$1.chkmsg=$a($1.msglen);$1.j=0;for(var _7y=0,_7x=$1.msglen-1;_7y<=_7x;_7y+=1){$1.i=_7y;$1.char=$g($1.msg,$1.i);if($1.char>=0){$p($1.chkmsg,$1.j,$1.char);$1.j=$1.j+1}if($1.char==$1.fn1&&$1.i!=0){$p($1.chkmsg,$1.j,29);$1.j=$1.j+1}}$1.t1=0;$1.t2=0;$1.k1=0;$1.k2=0;for(var _8F=0,_8E=$1.j-1;_8F<=_8E;_8F+=1){$1.i=_8F;$1.t1=$g($1.chkmsg,$1.i)*$1.i%86;$1.t2=$f($1.t1+$g($1.chkmsg,$1.i))%86;$1.k1=$f($1.k1+$1.t2)%86;$1.k2=$f($1.k2+$1.t1)%86}$k[$j++]=$1.cws;$k[$j++]=$1.cws.length-4;if($ne($1.cset,"setc")){$k[$j++]=$1.abmap}else{$k[$j++]=$1.cmap}var _8Z=$g($k[--$j],$1.k1);var _8a=$k[--$j];$p($k[--$j],_8a,_8Z);$k[$j++]=$1.cws;$k[$j++]=$1.cws.length-3;if($ne($1.cset,"setc")){$k[$j++]=$1.abmap}else{$k[$j++]=$1.cmap}var _8j=$g($k[--$j],$1.k2);var _8k=$k[--$j];$p($k[--$j],_8k,_8j);var _8m=$1.cws;$k[$j++]=_8m;$k[$j++]=2;if($g(_8m,2-1)!=99){$k[$j++]=$1.abmap}else{$k[$j++]=$1.cmap}var _8s=$g($k[--$j],$1.r-2);var _8t=$k[--$j];$p($k[--$j],_8t,_8s);for(var _8x=1,_8w=$1.r-1;_8x<=_8w;_8x+=1){$1.i=_8x;var _8y=$1.cws;var _8z=$1.i;var _90=$1.c;$k[$j++]=_8y;$k[$j++]=$f(_8z*$f(_90+5)+2);if($g(_8y,$f($f(_8z*$f(_90+5)+2)-1))!=99){$k[$j++]=$1.abmap}else{$k[$j++]=$1.cmap}var _96=$g($k[--$j],$1.i+42);var _97=$k[--$j];$p($k[--$j],_97,_96)}for(var _9B=0,_9A=$1.r-1;_9B<=_9A;_9B+=1){$1.rcws=$G($1.cws,_9B*$f($1.c+5),$f($1.c+4));$1.csum=$g($1.rcws,0);for(var _9K=1,_9J=$1.rcws.length-2;_9K<=_9J;_9K+=1){$1.i=_9K;$1.csum=$f($1.csum+$g($1.rcws,$1.i)*$1.i)}$p($1.rcws,$1.rcws.length-1,$1.csum%103)}$1.encs=$a(["212222","222122","222221","121223","121322","131222","122213","122312","132212","221213","221312","231212","112232","122132","122231","113222","123122","123221","223211","221132","221231","213212","223112","312131","311222","321122","321221","312212","322112","322211","212123","212321","232121","111323","131123","131321","112313","132113","132311","211313","231113","231311","112133","112331","132131","113123","113321","133121","313121","211331","231131","213113","213311","213131","311123","311321","331121","312113","312311","332111","314111","221411","431111","111224","111422","121124","121421","141122","141221","112214","112412","122114","122411","142112","142211","241211","221114","413111","241112","134111","111242","121142","121241","114212","124112","124211","411212","421112","421211","212141","214121","412121","111143","111341","131141","114113","114311","411113","411311","113141","114131","311141","411131","211412","2331112"]);$1.rowbits=$a($1.r);for(var _9Y=0,_9X=$1.r-1;_9Y<=_9X;_9Y+=1){$1.i=_9Y;$k[$j++]=Infinity;var _9d=$G($1.cws,$1.i*$f($1.c+5),$f($1.c+5));for(var _9e=0,_9f=_9d.length;_9e<_9f;_9e++){$F($g($1.encs,$g(_9d,_9e)),function(){var _9j=$k[--$j];$k[$j++]=$f(_9j-48)})}$1.sbs=$a();$k[$j++]=Infinity;var _9l=$1.sbs;$k[$j++]=0;for(var _9m=0,_9n=_9l.length;_9m<_9n;_9m++){var _9p=$k[--$j];var _9q=_9p==1?0:1;$k[$j++]=_9p;for(var _9r=0,_9s=$g(_9l,_9m);_9r<_9s;_9r++){$k[$j++]=_9q}}$r($a($m()-1));var _9v=$k[--$j];var _9w=$k[--$j];$k[$j++]=_9v;$k[$j++]=_9w;$j--;var _9x=$k[--$j];var _9y=$k[--$j];$k[$j++]=_9x;$k[$j++]=_9y;$j--;$p($1.rowbits,$1.i,$k[--$j])}$1.symwid=$f($1.c*11+57);$k[$j++]=Infinity;for(var _A5=0,_A6=$1.symwid*$1.sepheight;_A5<_A6;_A5++){$k[$j++]=1}for(var _A9=0,_A8=$1.r-2;_A9<=_A8;_A9+=1){$1.i=_A9;for(var _AB=0,_AC=$1.rowheight;_AB<_AC;_AB++){$q($g($1.rowbits,$1.i))}for(var _AH=0,_AI=$1.sepheight;_AH<_AI;_AH++){$k[$j++]=1;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=0;for(var _AK=0,_AL=$f($1.symwid-24);_AK<_AL;_AK++){$k[$j++]=1}$k[$j++]=1;$k[$j++]=1;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=1}}for(var _AN=0,_AO=$1.rowheight;_AN<_AO;_AN++){$q($g($1.rowbits,$1.r-1))}for(var _AU=0,_AV=$1.symwid*$1.sepheight;_AU<_AV;_AU++){$k[$j++]=1}$1.pixs=$a();var _Af=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",$1.symwid],["pixy",~~($1.pixs.length/$1.symwid)],["height",~~($1.pixs.length/$1.symwid)/72],["width",$1.symwid/72],["opt",$1.options]]);$k[$j++]=_Af;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_code16k(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.mode=-1;$1.pos=-1;$1.rows=0;$1.rowheight=8;$1.sepheight=1;$1.encoding="auto";$1.raw=false;$1.parse=false;$1.parsefnc=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.mode=~~$1.mode;$1.pos=~~$1.pos;$1.rows=~~$1.rows;$1.rowheight=~~$1.rowheight;$1.sepheight=~~$1.sepheight;if($1.pos!=-1){$1.rows=16}$1.swa=-1;$1.swb=-2;$1.swc=-3;$1.sa1=-4;$1.sb1=-5;$1.sc1=-6;$1.sa2=-7;$1.sb2=-8;$1.sc2=-9;$1.pad=-10;$1.sb3=-11;$1.sc3=-12;$1.fn1=-13;$1.fn2=-14;$1.fn3=-15;$1.fn4=-16;$1.charmaps=$a([$a([32,32,"00"]),$a(["!","!","01"]),$a(['"','"',"02"]),$a(["#","#","03"]),$a(["$","$","04"]),$a(["%","%","05"]),$a(["&","&","06"]),$a(["'","'","07"]),$a([40,40,"08"]),$a([41,41,"09"]),$a(["*","*","10"]),$a(["+","+","11"]),$a([",",",","12"]),$a(["-","-","13"]),$a([".",".","14"]),$a(["/","/","15"]),$a(["0","0","16"]),$a(["1","1","17"]),$a(["2","2","18"]),$a(["3","3","19"]),$a(["4","4","20"]),$a(["5","5","21"]),$a(["6","6","22"]),$a(["7","7","23"]),$a(["8","8","24"]),$a(["9","9","25"]),$a([":",":","26"]),$a([";",";","27"]),$a(["<","<","28"]),$a(["=","=","29"]),$a([">",">","30"]),$a(["?","?","31"]),$a(["@","@","32"]),$a(["A","A","33"]),$a(["B","B","34"]),$a(["C","C","35"]),$a(["D","D","36"]),$a(["E","E","37"]),$a(["F","F","38"]),$a(["G","G","39"]),$a(["H","H","40"]),$a(["I","I","41"]),$a(["J","J","42"]),$a(["K","K","43"]),$a(["L","L","44"]),$a(["M","M","45"]),$a(["N","N","46"]),$a(["O","O","47"]),$a(["P","P","48"]),$a(["Q","Q","49"]),$a(["R","R","50"]),$a(["S","S","51"]),$a(["T","T","52"]),$a(["U","U","53"]),$a(["V","V","54"]),$a(["W","W","55"]),$a(["X","X","56"]),$a(["Y","Y","57"]),$a(["Z","Z","58"]),$a(["[","[","59"]),$a([92,92,"60"]),$a(["]","]","61"]),$a(["^","^","62"]),$a(["_","_","63"]),$a([0,"`","64"]),$a([1,"a","65"]),$a([2,"b","66"]),$a([3,"c","67"]),$a([4,"d","68"]),$a([5,"e","69"]),$a([6,"f","70"]),$a([7,"g","71"]),$a([8,"h","72"]),$a([9,"i","73"]),$a([10,"j","74"]),$a([11,"k","75"]),$a([12,"l","76"]),$a([13,"m","77"]),$a([14,"n","78"]),$a([15,"o","79"]),$a([16,"p","80"]),$a([17,"q","81"]),$a([18,"r","82"]),$a([19,"s","83"]),$a([20,"t","84"]),$a([21,"u","85"]),$a([22,"v","86"]),$a([23,"w","87"]),$a([24,"x","88"]),$a([25,"y","89"]),$a([26,"z","90"]),$a([27,"{","91"]),$a([28,"|","92"]),$a([29,"}","93"]),$a([30,"~","94"]),$a([31,127,"95"]),$a([$1.fn3,$1.fn3,"96"]),$a([$1.fn2,$1.fn2,"97"]),$a([$1.sb1,$1.sa1,"98"]),$a([$1.swc,$1.swc,"99"]),$a([$1.swb,$1.fn4,$1.swb]),$a([$1.fn4,$1.swa,$1.swa]),$a([$1.fn1,$1.fn1,$1.fn1]),$a([$1.pad,$1.pad,$1.pad]),$a([$1.sb2,$1.sa2,$1.sb1]),$a([$1.sc2,$1.sc2,$1.sb2]),$a([$1.sc3,$1.sc3,$1.sb3])]);$1.charvals=$a([new Map,new Map,new Map]);for(var _2R=0,_2Q=$1.charmaps.length-1;_2R<=_2Q;_2R+=1){$1.i=_2R;$1.encs=$g($1.charmaps,$1.i);for(var _2V=0;_2V<=2;_2V+=1){$1.j=_2V;var _2Y=$g($1.encs,$1.j);$k[$j++]=_2Y;if($eq($t(_2Y),"stringtype")){var _2b=$g($k[--$j],0);$k[$j++]=_2b}$p($g($1.charvals,$1.j),$k[--$j],$1.i)}}$1.seta=$g($1.charvals,0);$1.setb=$g($1.charvals,1);$1.setc=$g($1.charvals,2);if($1.raw){$1.encoding="raw"}if($eq($1.encoding,"raw")){$1.cws=$a($1.barcode.length);$1.i=0;$1.j=0;for(;;){if($1.i==$1.barcode.length){break}$1.cw=~~$z($G($1.barcode,$1.i+1,3));$p($1.cws,$1.j,$1.cw);$1.i=$1.i+4;$1.j=$1.j+1}$1.cws=$G($1.cws,0,$1.j)}if($eq($1.encoding,"auto")){var _3A=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["eci",true],["FNC1",$1.fn1],["FNC2",$1.fn2],["FNC3",$1.fn3]]);$1.fncvals=_3A;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _3D=$k[--$j];$1[$k[--$j]]=_3D;$1.msglen=$1.msg.length;$k[$j++]=Infinity;for(var _3H=0,_3I=$1.msglen;_3H<_3I;_3H++){$k[$j++]=0}$k[$j++]=0;$1.numSA=$a();$k[$j++]=Infinity;for(var _3L=0,_3M=$1.msglen;_3L<_3M;_3L++){$k[$j++]=0}$k[$j++]=0;$1.numEA=$a();for(var _3P=$1.msglen-1;_3P>=0;_3P-=1){$1.i=_3P;if($g($1.msg,$1.i)>=0){if($g($1.msg,$1.i)>=128){$p($1.numEA,$1.i,$f($g($1.numEA,$1.i+1)+1))}else{$p($1.numSA,$1.i,$f($g($1.numSA,$1.i+1)+1))}}}$1.ea=false;$1.msgtmp=$a([]);for(var _3j=0,_3i=$1.msglen-1;_3j<=_3i;_3j+=1){$1.i=_3j;$1.c=$g($1.msg,$1.i);if(!$xo($1.ea,$1.c<128)&&$1.c>=0){if($1.ea){$k[$j++]=$1.numSA}else{$k[$j++]=$1.numEA}var _3v=$g($k[--$j],$1.i);var _3y=$f(_3v+$1.i)==$1.msglen?3:5;if(_3v<_3y){$k[$j++]=Infinity;$q($1.msgtmp);$k[$j++]=$1.fn4;$1.msgtmp=$a()}else{$k[$j++]=Infinity;$q($1.msgtmp);$k[$j++]=$1.fn4;$k[$j++]=$1.fn4;$1.msgtmp=$a();$1.ea=!$1.ea}}$k[$j++]=Infinity;$q($1.msgtmp);if($1.c>=0){$k[$j++]=$1.c&127}else{$k[$j++]=$1.c}$1.msgtmp=$a()}$1.msg=$1.msgtmp;$1.msglen=$1.msg.length;$1.numsscr=function(){$1.n=0;$1.s=0;$1.p=$k[--$j];for(;;){if($1.p>=$1.msglen){break}var _4J=$g($1.msg,$1.p);var _4L=$g($1.setc,_4J)!==undefined;$k[$j++]=_4J;if(!_4L){$j--;break}if($k[--$j]==$1.fn1){if($1.s%2==0){$1.s=$1.s+1}else{break}}$1.n=$1.n+1;$1.s=$1.s+1;$1.p=$f($1.p+1)}$k[$j++]=$1.n;$k[$j++]=$1.s};$1.enca=function(){$p($1.cws,$1.j,$g($1.seta,$k[--$j]));$1.j=$1.j+1};$1.encb=function(){$p($1.cws,$1.j,$g($1.setb,$k[--$j]));$1.j=$1.j+1};$1.encc=function(){var _4h=$k[--$j];$k[$j++]=_4h;if($ne($t(_4h),"arraytype")){var _4l=$g($1.setc,$k[--$j]);$k[$j++]=_4l}else{$q($k[--$j]);var _4n=$k[--$j];var _4o=$k[--$j];$k[$j++]=$f($f(_4n-48)+$f(_4o-48)*10)}$p($1.cws,$1.j,$k[--$j]);$1.j=$1.j+1};$1.anotb=function(){var _4t=$k[--$j];var _4v=$g($1.seta,_4t)!==undefined;var _4x=$g($1.setb,_4t)!==undefined;$k[$j++]=_4v&&!_4x};$1.bnota=function(){var _4y=$k[--$j];var _50=$g($1.setb,_4y)!==undefined;var _52=$g($1.seta,_4y)!==undefined;$k[$j++]=_50&&!_52};$k[$j++]=Infinity;for(var _54=0,_55=$1.msg.length;_54<_55;_54++){$k[$j++]=0}$k[$j++]=9999;$1.nextanotb=$a();$k[$j++]=Infinity;for(var _58=0,_59=$1.msg.length;_58<_59;_58++){$k[$j++]=0}$k[$j++]=9999;$1.nextbnota=$a();for(var _5C=$1.msg.length-1;_5C>=0;_5C-=1){$1.i=_5C;$k[$j++]=$g($1.msg,$1.i);$1.anotb();if($k[--$j]){$p($1.nextanotb,$1.i,0)}else{$p($1.nextanotb,$1.i,$f($g($1.nextanotb,$1.i+1)+1))}$k[$j++]=$g($1.msg,$1.i);$1.bnota();if($k[--$j]){$p($1.nextbnota,$1.i,0)}else{$p($1.nextbnota,$1.i,$f($g($1.nextbnota,$1.i+1)+1))}}$1.abeforeb=function(){var _5Z=$k[--$j];$k[$j++]=$lt($g($1.nextanotb,_5Z),$g($1.nextbnota,_5Z))};$1.bbeforea=function(){var _5e=$k[--$j];$k[$j++]=$lt($g($1.nextbnota,_5e),$g($1.nextanotb,_5e))};$1.cws=$a($1.barcode.length*2+3);$1.i=0;$1.j=0;for(;;){if($1.pos!=-1){$p($1.cws,0,~~($1.pos/10)-1);$p($1.cws,1,$1.pos%10-1);$1.j=2;$1.cset="setb";$1.mode=7;break}if($1.msglen==0){$1.cset="setb";$1.mode=1;break}if($1.msglen>=2){$k[$j++]=$g($1.msg,0)==$1.fn1;$k[$j++]=1;$1.numsscr();var _5v=$k[--$j];var _5w=$k[--$j];$k[$j++]=_5v;$k[$j++]=_5w;$j--;var _5x=$k[--$j];var _5y=$k[--$j];if(_5y&&_5x>=2){$1.cset="setc";$1.mode=4;$1.i=1;break}}if($g($1.msg,0)==$1.fn1){$1.cset="setb";$1.mode=3;$1.i=1;break}if($1.msglen>=2){$k[$j++]=0;$1.numsscr();var _63=$k[--$j];var _64=$k[--$j];$k[$j++]=_63;$k[$j++]=_64;$j--;var _65=$k[--$j];if(_65>=2&&_65%2==0){$1.cset="setc";$1.mode=2;break}$k[$j++]=0;$1.numsscr();var _66=$k[--$j];var _67=$k[--$j];$k[$j++]=_66;$k[$j++]=_67;$j--;var _68=$k[--$j];if(_68>=3&&_68%2==1){$k[$j++]=$g($1.msg,0);$1.encb();$1.cset="setc";$1.mode=5;$1.i=1;break}var _6E=$g($1.setb,$g($1.msg,0))!==undefined;$k[$j++]=_6E;$k[$j++]=1;$1.numsscr();var _6F=$k[--$j];var _6G=$k[--$j];$k[$j++]=_6F;$k[$j++]=_6G;$j--;var _6H=$k[--$j];var _6I=$k[--$j];if(_6I&&(_6H>=2&&_6H%2==0)){$k[$j++]=$g($1.msg,0);$1.encb();$1.cset="setc";$1.mode=5;$1.i=1;break}var _6O=$g($1.setb,$g($1.msg,0))!==undefined;$k[$j++]=_6O;$k[$j++]=1;$1.numsscr();var _6P=$k[--$j];var _6Q=$k[--$j];$k[$j++]=_6P;$k[$j++]=_6Q;$j--;var _6R=$k[--$j];var _6S=$k[--$j];if(_6S&&(_6R>=3&&_6R%2==1)){$k[$j++]=$g($1.msg,0);$1.encb();$k[$j++]=$g($1.msg,1);$1.encb();$1.cset="setc";$1.mode=6;$1.i=2;break}var _6a=$g($1.setb,$g($1.msg,0))!==undefined;var _6e=$g($1.setb,$g($1.msg,1))!==undefined;$k[$j++]=_6a&&_6e;$k[$j++]=2;$1.numsscr();var _6f=$k[--$j];var _6g=$k[--$j];$k[$j++]=_6f;$k[$j++]=_6g;$j--;var _6h=$k[--$j];var _6i=$k[--$j];if(_6i&&(_6h>=2&&_6h%2==0)){$k[$j++]=$g($1.msg,0);$1.encb();$k[$j++]=$g($1.msg,1);$1.encb();$1.cset="setc";$1.mode=6;$1.i=2;break}}$k[$j++]=0;$1.abeforeb();if($k[--$j]){$1.cset="seta";$1.mode=0;break}$1.cset="setb";$1.mode=1;break}for(;;){if($1.i==$1.msglen){break}$k[$j++]=$1.i;$1.numsscr();$1.nums=$k[--$j];$1.nchars=$k[--$j];for(;;){if($eq($1.cset,"seta")){if($1.i<$1.msglen-1){$k[$j++]=$g($1.msg,$1.i);$1.bnota();$k[$j++]=$1.i+1;$1.abeforeb();var _70=$k[--$j];var _71=$k[--$j];if(_71&&_70){$k[$j++]=$1.sb1;$1.enca();$k[$j++]=$g($1.msg,$1.i);$1.encb();$1.i=$1.i+1;break}}if($1.i<$1.msglen-2){$k[$j++]=$g($1.msg,$1.i);$1.bnota();$k[$j++]=$g($1.msg,$1.i+1);$1.bnota();var _7F=$k[--$j];var _7G=$k[--$j];$k[$j++]=$an(_7G,_7F);$k[$j++]=$1.i+2;$1.abeforeb();var _7I=$k[--$j];var _7J=$k[--$j];if(_7J&&_7I){$k[$j++]=$1.sb2;$1.enca();$k[$j++]=$g($1.msg,$1.i);$1.encb();$k[$j++]=$g($1.msg,$1.i+1);$1.encb();$1.i=$1.i+2;break}}$k[$j++]=$g($1.msg,$1.i);$1.bnota();if($k[--$j]){$k[$j++]=$1.swb;$1.enca();$1.cset="setb";break}if($1.i<$1.msglen-4){var _7e=$g($1.seta,$g($1.msg,$1.i+4))!==undefined;if($1.nums==4&&_7e){$k[$j++]=$1.sc2;$1.enca();for(var _7g=0,_7h=2;_7g<_7h;_7g++){if($g($1.msg,$1.i)==$1.fn1){$k[$j++]=$1.fn1;$1.encc();$1.i=$1.i+1}else{$k[$j++]=$G($1.msg,$1.i,2);$1.encc();$1.i=$1.i+2}}break}}if($1.i<$1.msglen-6){var _7z=$g($1.seta,$g($1.msg,$1.i+6))!==undefined;if($1.nums==6&&_7z){$k[$j++]=$1.sc3;$1.enca();for(var _81=0,_82=3;_81<_82;_81++){if($g($1.msg,$1.i)==$1.fn1){$k[$j++]=$1.fn1;$1.encc();$1.i=$1.i+1}else{$k[$j++]=$G($1.msg,$1.i,2);$1.encc();$1.i=$1.i+2}}break}}if($1.nums>=4&&$1.nums%2==0){$k[$j++]=$1.swc;$1.enca();$1.cset="setc";break}$k[$j++]=$g($1.msg,$1.i);$1.enca();$1.i=$1.i+1;break}if($eq($1.cset,"setb")){if($1.i<$1.msglen-1){$k[$j++]=$g($1.msg,$1.i);$1.anotb();$k[$j++]=$1.i+1;$1.bbeforea();var _8R=$k[--$j];var _8S=$k[--$j];if(_8S&&_8R){$k[$j++]=$1.sa1;$1.encb();$k[$j++]=$g($1.msg,$1.i);$1.enca();$1.i=$1.i+1;break}}if($1.i<$1.msglen-2){$k[$j++]=$g($1.msg,$1.i);$1.anotb();$k[$j++]=$g($1.msg,$1.i+1);$1.anotb();var _8g=$k[--$j];var _8h=$k[--$j];$k[$j++]=$an(_8h,_8g);$k[$j++]=$1.i+2;$1.bbeforea();var _8j=$k[--$j];var _8k=$k[--$j];if(_8k&&_8j){$k[$j++]=$1.sa2;$1.encb();$k[$j++]=$g($1.msg,$1.i);$1.enca();$k[$j++]=$g($1.msg,$1.i+1);$1.enca();$1.i=$1.i+2;break}}$k[$j++]=$g($1.msg,$1.i);$1.anotb();if($k[--$j]){$k[$j++]=$1.swa;$1.encb();$1.cset="seta";break}if($1.i<$1.msglen-4){var _95=$g($1.setb,$g($1.msg,$1.i+4))!==undefined;if($1.nums==4&&_95){$k[$j++]=$1.sc2;$1.encb();for(var _97=0,_98=2;_97<_98;_97++){if($g($1.msg,$1.i)==$1.fn1){$k[$j++]=$1.fn1;$1.encc();$1.i=$1.i+1}else{$k[$j++]=$G($1.msg,$1.i,2);$1.encc();$1.i=$1.i+2}}break}}if($1.i<$1.msglen-6){var _9Q=$g($1.setb,$g($1.msg,$1.i+6))!==undefined;if($1.nums==6&&_9Q){$k[$j++]=$1.sc3;$1.encb();for(var _9S=0,_9T=3;_9S<_9T;_9S++){if($g($1.msg,$1.i)==$1.fn1){$k[$j++]=$1.fn1;$1.encc();$1.i=$1.i+1}else{$k[$j++]=$G($1.msg,$1.i,2);$1.encc();$1.i=$1.i+2}}break}}if($1.nums>=4&&$1.nums%2==0){$k[$j++]=$1.swc;$1.encb();$1.cset="setc";break}$k[$j++]=$g($1.msg,$1.i);$1.encb();$1.i=$1.i+1;break}if($eq($1.cset,"setc")){if($1.nums>=2){if($g($1.msg,$1.i)==$1.fn1){$k[$j++]=$1.fn1;$1.encc();$1.i=$1.i+1}else{$k[$j++]=$G($1.msg,$1.i,2);$1.encc();$1.i=$1.i+2}break}if($1.i<$1.msglen-1){var _A3=$g($1.setb,$g($1.msg,$1.i))!==undefined;$k[$j++]=_A3;$k[$j++]=$1.i+1;$1.numsscr();var _A5=$k[--$j];var _A6=$k[--$j];$k[$j++]=_A5;$k[$j++]=_A6;$j--;var _A7=$k[--$j];var _A8=$k[--$j];if(_A8&&(_A7>=2&&_A7%2==0)){$k[$j++]=$1.sb1;$1.encc();$k[$j++]=$g($1.msg,$1.i);$1.encb();$1.i=$1.i+1;break}}if($1.i<$1.msglen-1){var _AK=$g($1.setb,$g($1.msg,$1.i))!==undefined;$k[$j++]=_AK;$k[$j++]=$1.i+1;$1.numsscr();var _AM=$k[--$j];var _AN=$k[--$j];$k[$j++]=_AM;$k[$j++]=_AN;$j--;var _AO=$k[--$j];var _AP=$k[--$j];if(_AP&&(_AO>=3&&_AO%2==1)){$k[$j++]=$1.sb2;$1.encc();$k[$j++]=$g($1.msg,$1.i);$1.encb();$k[$j++]=$g($1.msg,$1.i+1);$1.encb();$1.i=$1.i+2;break}}if($1.i<$1.msglen-2){var _Ae=$g($1.setb,$g($1.msg,$1.i))!==undefined;var _Aj=$g($1.setb,$g($1.msg,$1.i+1))!==undefined;$k[$j++]=_Ae&&_Aj;$k[$j++]=$1.i+2;$1.numsscr();var _Al=$k[--$j];var _Am=$k[--$j];$k[$j++]=_Al;$k[$j++]=_Am;$j--;var _An=$k[--$j];var _Ao=$k[--$j];if(_Ao&&(_An>=2&&_An%2==0)){$k[$j++]=$1.sb2;$1.encc();$k[$j++]=$g($1.msg,$1.i);$1.encb();$k[$j++]=$g($1.msg,$1.i+1);$1.encb();$1.i=$1.i+2;break}}if($1.i<$1.msglen-3){var _B3=$g($1.setb,$g($1.msg,$1.i))!==undefined;var _B8=$g($1.setb,$g($1.msg,$1.i+1))!==undefined;$k[$j++]=_B3&&_B8;$k[$j++]=$1.i+2;$1.numsscr();var _BA=$k[--$j];var _BB=$k[--$j];$k[$j++]=_BA;$k[$j++]=_BB;$j--;var _BC=$k[--$j];var _BD=$k[--$j];if(_BD&&(_BC>=3&&_BC%2==1)){$k[$j++]=$1.sb3;$1.encc();$k[$j++]=$g($1.msg,$1.i);$1.encb();$k[$j++]=$g($1.msg,$1.i+1);$1.encb();$k[$j++]=$g($1.msg,$1.i+2);$1.encb();$1.i=$1.i+3;break}}if($1.i<$1.msglen-3){var _BV=$g($1.setb,$g($1.msg,$1.i))!==undefined;var _Ba=$g($1.setb,$g($1.msg,$1.i+1))!==undefined;var _Bf=$g($1.setb,$g($1.msg,$1.i+2))!==undefined;$k[$j++]=_BV&&_Ba&&_Bf;$k[$j++]=$1.i+3;$1.numsscr();var _Bh=$k[--$j];var _Bi=$k[--$j];$k[$j++]=_Bh;$k[$j++]=_Bi;$j--;var _Bj=$k[--$j];var _Bk=$k[--$j];if(_Bk&&(_Bj>=2&&_Bj%2==0)){$k[$j++]=$1.sb3;$1.encc();$k[$j++]=$g($1.msg,$1.i);$1.encb();$k[$j++]=$g($1.msg,$1.i+1);$1.encb();$k[$j++]=$g($1.msg,$1.i+2);$1.encb();$1.i=$1.i+3;break}}$k[$j++]=$1.i;$1.abeforeb();if($k[--$j]){$k[$j++]=$1.swa;$1.encc();$1.cset="seta";break}$k[$j++]=$1.swb;$1.encc();$1.cset="setb";break}break}}$1.cws=$G($1.cws,0,$1.j)}$1.metrics=$a([$a([2,7]),$a([3,12]),$a([4,17]),$a([5,22]),$a([6,27]),$a([7,32]),$a([8,37]),$a([9,42]),$a([10,47]),$a([11,52]),$a([12,57]),$a([13,62]),$a([14,67]),$a([15,72]),$a([16,77])]);$1.urows=$1.rows;$1.i=0;for(;;){$1.m=$g($1.metrics,$1.i);$1.r=$g($1.m,0);$1.dcws=$g($1.m,1);$1.okay=true;if($1.urows!=0&&$1.urows!=$1.r){$1.okay=false}if($1.cws.length>$1.dcws){$1.okay=false}if($1.okay){break}$1.i=$1.i+1}$k[$j++]=Infinity;$q($1.cws);for(var _Cb=0,_Cc=$f($1.dcws-$1.cws.length);_Cb<_Cc;_Cb++){$k[$j++]=103}$1.cws=$a();$k[$j++]=Infinity;$k[$j++]=$f($f($1.r-2)*7+$1.mode);$q($1.cws);$1.cws=$a();$k[$j++]=0;for(var _Ck=0,_Cj=$1.dcws;_Ck<=_Cj;_Ck+=1){var _Cn=$k[--$j];$k[$j++]=$f(_Cn+(_Ck+2)*$g($1.cws,_Ck))}$1.c1=$k[--$j]%107;$k[$j++]=0;for(var _Cr=0,_Cq=$1.dcws;_Cr<=_Cq;_Cr+=1){var _Cu=$k[--$j];$k[$j++]=$f(_Cu+(_Cr+1)*$g($1.cws,_Cr))}$1.c2=$f($k[--$j]+$1.c1*$f($1.dcws+2))%107;$k[$j++]=Infinity;$q($1.cws);$k[$j++]=$1.c1;$k[$j++]=$1.c2;$1.cws=$a();$1.encs=$a(["212222","222122","222221","121223","121322","131222","122213","122312","132212","221213","221312","231212","112232","122132","122231","113222","123122","123221","223211","221132","221231","213212","223112","312131","311222","321122","321221","312212","322112","322211","212123","212321","232121","111323","131123","131321","112313","132113","132311","211313","231113","231311","112133","112331","132131","113123","113321","133121","313121","211331","231131","213113","213311","213131","311123","311321","331121","312113","312311","332111","314111","221411","431111","111224","111422","121124","121421","141122","141221","112214","112412","122114","122411","142112","142211","241211","221114","413111","241112","134111","111242","121142","121241","114212","124112","124211","411212","421112","421211","212141","214121","412121","111143","111341","131141","114113","114311","411113","411311","113141","114131","311141","411131","211412","211214","211232","211133"]);$1.startencs=$a(["3211","2221","2122","1411","1132","1231","1114","3112","3211","2221","2122","1411","1132","1231","1114","3112"]);$1.stopencsodd=$a(["3211","2221","2122","1411","1132","1231","1114","3112","1132","1231","1114","3112","3211","2221","2122","1411"]);$1.stopencseven=$a(["2122","1411","1132","1231","1114","3112","1132","1231","1114","3112","3211","2221","2122","1411","3211","2221"]);if($1.pos==-1||~~($1.pos/10)%2==1){$1.stopencs=$1.stopencsodd}else{$1.stopencs=$1.stopencseven}$1.rowbits=$a($1.r);for(var _DE=0,_DD=$f($1.r-1);_DE<=_DD;_DE+=1){$1.i=_DE;$k[$j++]=Infinity;$k[$j++]=10;$F($g($1.startencs,$1.i),function(){var _DI=$k[--$j];$k[$j++]=$f(_DI-48)});var _DL=$G($1.cws,$1.i*5,5);$k[$j++]=1;for(var _DM=0,_DN=_DL.length;_DM<_DN;_DM++){$F($g($1.encs,$g(_DL,_DM)),function(){var _DR=$k[--$j];$k[$j++]=$f(_DR-48)})}$F($g($1.stopencs,$1.i),function(){var _DV=$k[--$j];$k[$j++]=$f(_DV-48)});$k[$j++]=1;$1.sbs=$a();$k[$j++]=Infinity;var _DX=$1.sbs;$k[$j++]=1;for(var _DY=0,_DZ=_DX.length;_DY<_DZ;_DY++){var _Db=$k[--$j];var _Dc=_Db==0?1:0;$k[$j++]=_Db;for(var _Dd=0,_De=$g(_DX,_DY);_Dd<_De;_Dd++){$k[$j++]=_Dc}}$r($a($m()-1));var _Dh=$k[--$j];var _Di=$k[--$j];$k[$j++]=_Dh;$k[$j++]=_Di;$j--;var _Dj=$k[--$j];var _Dk=$k[--$j];$k[$j++]=_Dj;$k[$j++]=_Dk;$j--;$p($1.rowbits,$1.i,$k[--$j])}$k[$j++]=Infinity;for(var _Dp=0,_Dq=81*$1.sepheight;_Dp<_Dq;_Dp++){$k[$j++]=1}for(var _Dt=0,_Ds=$f($1.r-2);_Dt<=_Ds;_Dt+=1){$1.i=_Dt;for(var _Dv=0,_Dw=$1.rowheight;_Dv<_Dw;_Dv++){$q($g($1.rowbits,$1.i))}for(var _E1=0,_E2=$1.sepheight;_E1<_E2;_E1++){for(var _E3=0,_E4=10;_E3<_E4;_E3++){$k[$j++]=0}for(var _E5=0,_E6=70;_E5<_E6;_E5++){$k[$j++]=1}$k[$j++]=0}}for(var _E8=0,_E9=$1.rowheight;_E8<_E9;_E8++){$q($g($1.rowbits,$f($1.r-1)))}for(var _EE=0,_EF=81*$1.sepheight;_EE<_EF;_EE++){$k[$j++]=1}$1.pixs=$a();var _EL=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",81],["pixy",~~($1.pixs.length/81)],["height",~~($1.pixs.length/81)/72],["width",81/72],["opt",$1.options]]);$k[$j++]=_EL;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_code49(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.mode=-1;$1.pos=-1;$1.rows=0;$1.rowheight=8;$1.sepheight=1;$1.parse=false;$1.parsefnc=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.mode=~~$1.mode;$1.pos=~~$1.pos;$1.rows=~~$1.rows;$1.rowheight=~~$1.rowheight;$1.sepheight=~~$1.sepheight;$1.s1=-1;$1.s2=-2;$1.fn1=-3;$1.fn2=-4;$1.fn3=-5;$1.ns=-6;var _F=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["FNC1",$1.fn1],["FNC2",$1.fn2],["FNC3",$1.fn3]]);$1.fncvals=_F;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _I=$k[--$j];$1[$k[--$j]]=_I;$1.msglen=$1.msg.length;$1.charmap=$a(["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","-","."," ","$","/","+","%",$1.s1,$1.s2,$1.fn1,$1.fn2,$1.fn3,$1.ns]);$1.charvals=new Map;for(var _S=0;_S<=48;_S+=1){$1.i=_S;var _V=$g($1.charmap,$1.i);$k[$j++]=_V;if($eq($t(_V),"stringtype")){var _Y=$g($k[--$j],0);$k[$j++]=_Y}$p($1.charvals,$k[--$j],$1.i)}$1.combos=$a(["1 ","1A","1B","1C","1D","1E","1F","1G","1H","1I","1J","1K","1L","1M","1N","1O","1P","1Q","1R","1S","1T","1U","1V","1W","1X","1Y","1Z","11","12","13","14","15"," ","16","17","18"," $"," %","19","10","1-","1.","1$"," +","1/"," -"," ."," /"," 0"," 1"," 2"," 3"," 4"," 5"," 6"," 7"," 8"," 9","1+","21","22","23","24","25","26"," A"," B"," C"," D"," E"," F"," G"," H"," I"," J"," K"," L"," M"," N"," O"," P"," Q"," R"," S"," T"," U"," V"," W"," X"," Y"," Z","27","28","29","20","2-","2.","2A","2B","2C","2D","2E","2F","2G","2H","2I","2J","2K","2L","2M","2N","2O","2P","2Q","2R","2S","2T","2U","2V","2W","2X","2Y","2Z","2$","2/","2+","2%","2 "]);for(var _d=0;_d<=127;_d+=1){$1.i=_d;var _g=$g($1.combos,$1.i);$1.c1=$g(_g,0);$1.c2=$g(_g,1);if($1.c1==49){$p($1.charvals,$1.i,$a([$g($1.charvals,$1.s1),$g($1.charvals,$1.c2)]))}if($1.c1==50){$p($1.charvals,$1.i,$a([$g($1.charvals,$1.s2),$g($1.charvals,$1.c2)]))}}$1.encodealpha=function(){var _15=$g($1.charvals,$k[--$j]);$k[$j++]=_15;if($ne($t(_15),"arraytype")){$r($a(1))}var _18=$k[--$j];$P($1.cws,$1.j,_18);$1.j=_18.length+$1.j};$1.base48=function(){var _1C=$k[--$j];$k[$j++]=0;$F(_1C,function(){var _1D=$k[--$j];var _1E=$k[--$j];$k[$j++]=$f($f(_1D-48)+_1E*10)});$k[$j++]=Infinity;var _1F=$k[--$j];var _1G=$k[--$j];var _1H=$k[--$j];$k[$j++]=_1F;$k[$j++]=_1G;for(var _1I=0,_1J=$f(_1H-1);_1I<_1J;_1I++){var _1K=$k[--$j];$k[$j++]=_1K%48;$k[$j++]=~~(_1K/48)}var _1L=$a();$k[$j++]=_1L;$k[$j++]=Infinity;var _1M=$k[--$j];var _1N=$k[--$j];$k[$j++]=_1M;$k[$j++]=_1N;for(var _1O=_1N.length-1;_1O>=0;_1O-=1){var _1P=$k[--$j];$k[$j++]=$g(_1P,_1O);$k[$j++]=_1P}$j--;var _1R=$a();$P($1.cws,$1.j,_1R);$1.j=_1R.length+$1.j};$1.encodenumeric=function(){$1.nums=$k[--$j];var _1X=$1.nums.length;var _1Y=_1X%5;$k[$j++]="pre";$k[$j++]=_1X;$k[$j++]=_1Y;if(_1Y!=2){var _1Z=$k[--$j];var _1a=$k[--$j];$k[$j++]=$f(_1a-_1Z)}else{var _1b=$k[--$j];var _1c=$k[--$j];$k[$j++]=$f($f(_1c-_1b)-5)}var _1d=$k[--$j];$1[$k[--$j]]=_1d;for(var _1h=0,_1g=$f($1.pre-1);_1h<=_1g;_1h+=5){$k[$j++]=3;$k[$j++]=$G($1.nums,_1h,5);$1.base48()}$1.nums=$G($1.nums,$1.pre,$f($1.nums.length-$1.pre));var _1q=$1.nums.length;$k[$j++]=_1q;if(_1q==1){$k[$j++]=$g($1.nums,$1.i);$1.encodealpha()}var _1u=$k[--$j];$k[$j++]=_1u;if(_1u==3){$k[$j++]=2;$k[$j++]=$1.nums;$1.base48()}var _1w=$k[--$j];$k[$j++]=_1w;if(_1w==4){$k[$j++]=3;$k[$j++]=Infinity;$k[$j++]=49;$k[$j++]=48;$q($1.nums);var _1y=$a();$k[$j++]=_1y;$1.base48()}if($k[--$j]==7){$k[$j++]=3;$k[$j++]=Infinity;$k[$j++]=49;$k[$j++]=48;$q($G($1.nums,0,4));var _22=$a();$k[$j++]=_22;$1.base48();$k[$j++]=2;$k[$j++]=$G($1.nums,4,3);$1.base48()}};$k[$j++]=Infinity;for(var _26=0,_27=$1.msglen;_26<_27;_26++){$k[$j++]=0}$k[$j++]=0;$1.numericruns=$a();for(var _2A=$1.msglen-1;_2A>=0;_2A-=1){$1.i=_2A;var _2D=$g($1.msg,$1.i);if(_2D>=48&&_2D<=57){$p($1.numericruns,$1.i,$f($g($1.numericruns,$1.i+1)+1))}else{$p($1.numericruns,$1.i,0)}}if($1.mode==-1){for(;;){if($1.pos!=-1){$1.mode=3;break}if($g($1.numericruns,0)>=5){$1.mode=2;break}var _2S=$g($1.charvals,$g($1.msg,0));$k[$j++]=_2S;if($ne($t(_2S),"arraytype")){$j--;$1.mode=0;break}var _2W=$g($k[--$j],0)==43?4:5;$1.mode=_2W;break}}$1.cws=$a($1.msglen*2+1);$1.method="alpha";$1.i=-1;$1.j=-1;if($1.mode==0||$1.mode==1){$1.method="alpha";$1.i=0;$1.j=0}if($1.mode==2){$1.method="numeric";$1.i=0;$1.j=0}if($1.mode==3){$1.posval=$a([12,22,13,23,33,14,24,34,44,15,25,35,45,55,16,26,36,46,56,66,17,27,37,47,57,67,77,18,28,38,48,58,68,78,88,19,29,39,49,59,69,79,89,99]);$k[$j++]=$1.cws;$k[$j++]=0;for(var _2f=0;_2f<=43;_2f+=1){$k[$j++]=_2f;if($g($1.posval,_2f)!=$1.pos){$j--}}var _2j=$k[--$j];var _2k=$k[--$j];$p($k[--$j],_2k,$f(_2j+1));$1.method="alpha";$1.i=0;$1.j=1}if($1.mode==4||$1.mode==5){$p($1.cws,0,$g($g($1.charvals,$g($1.msg,0)),1));$1.method="alpha";$1.i=1;$1.j=1}for(;;){if($1.i==$1.msglen){break}for(;;){if($eq($1.method,"alpha")){if($g($1.numericruns,$1.i)>=5){$k[$j++]=$1.ns;$1.encodealpha();$1.method="numeric";break}$k[$j++]=$g($1.msg,$1.i);$1.encodealpha();$1.i=$1.i+1;break}if($eq($1.method,"numeric")){if($g($1.numericruns,$1.i)<5){$k[$j++]=$1.ns;$1.encodealpha();$1.method="alpha";break}$k[$j++]=$G($1.msg,$1.i,$g($1.numericruns,$1.i));$1.encodenumeric();$1.i=$f($1.i+$g($1.numericruns,$1.i));break}}}$1.cws=$G($1.cws,0,$1.j);$1.metrics=$a([$a([2,9]),$a([3,16]),$a([4,23]),$a([5,30]),$a([6,37]),$a([7,42]),$a([8,49])]);$1.urows=$1.rows;$1.i=0;for(;;){$1.m=$g($1.metrics,$1.i);$1.r=$g($1.m,0);$1.dcws=$g($1.m,1);$1.okay=true;if($1.urows!=0&&$1.urows!=$1.r){$1.okay=false}if($1.cws.length>$1.dcws){$1.okay=false}if($1.okay){break}$1.i=$1.i+1}$k[$j++]=Infinity;$q($1.cws);for(var _3n=0,_3o=$f($1.dcws-$1.cws.length);_3n<_3o;_3n++){$k[$j++]=48}$1.cws=$a();$1.ccs=$a($1.r*8);$1.j=0;for(var _3u=0,_3t=$f($1.r-2);_3u<=_3t;_3u+=1){$1.i=_3u;$1.cc=$G($1.cws,$1.j,7);$P($1.ccs,$1.i*8,$1.cc);var _43=$1.cc;$k[$j++]=$1.ccs;$k[$j++]=$1.i*8+7;$k[$j++]=0;for(var _44=0,_45=_43.length;_44<_45;_44++){var _47=$k[--$j];$k[$j++]=$f(_47+$g(_43,_44))}var _48=$k[--$j];var _49=$k[--$j];$p($k[--$j],_49,_48%49);$1.j=$1.j+7}if($1.j<$1.dcws){$P($1.ccs,$1.ccs.length-8,$G($1.cws,$1.j,$f($1.dcws-$1.j)))}$1.cr7=$f($f($1.r-2)*7+$1.mode);$p($1.ccs,$1.ccs.length-2,$1.cr7);var _4Q=$a([1,9,31,26,2,12,17,23,37,18,22,6,27,44,15,43,39,11,13,5,41,33,36,8,4,32,3,19,40,25,29,10,24,30]);$k[$j++]=_4Q;$k[$j++]=_4Q;$k[$j++]=Infinity;var _4R=$k[--$j];var _4T=$G($k[--$j],0,32);$k[$j++]=_4R;$k[$j++]=20;$q(_4T);$1.weightx=$a();var _4V=$k[--$j];$k[$j++]=_4V;$k[$j++]=_4V;$k[$j++]=Infinity;var _4W=$k[--$j];var _4Y=$G($k[--$j],1,32);$k[$j++]=_4W;$k[$j++]=16;$q(_4Y);$1.weighty=$a();$k[$j++]=Infinity;var _4a=$k[--$j];var _4c=$G($k[--$j],2,32);$k[$j++]=_4a;$k[$j++]=38;$q(_4c);$1.weightz=$a();$1.calccheck=function(){$1.weights=$k[--$j];$1.score=0;for(var _4h=0,_4g=~~($f($1.r-1)*8/2)-1;_4h<=_4g;_4h+=1){$1.i=_4h;$1.score=$f($f($g($1.ccs,$1.i*2)*49+$g($1.ccs,$1.i*2+1))*$g($1.weights,$1.i+1)+$1.score)}$k[$j++]=$1.score};$1.lastrow=$G($1.ccs,$1.ccs.length-8,8);if($1.r>=7){$k[$j++]=$1.cr7*$g($1.weightz,0);$k[$j++]=$1.weightz;$1.calccheck();var _51=$k[--$j];var _53=$f($k[--$j]+_51)%2401;$k[$j++]=~~(_53/49);$k[$j++]=_53%49;$r($a(2));$P($1.lastrow,0,$k[--$j])}$1.wr1=$f($g($1.lastrow,0)*49+$g($1.lastrow,1));$k[$j++]=$1.cr7*$g($1.weighty,0);$k[$j++]=$1.weighty;$1.calccheck();var _5F=$k[--$j];var _5L=$f($f($k[--$j]+_5F)+$1.wr1*$g($1.weighty,$f($1.r*4-3)))%2401;$1.wr2=_5L;$k[$j++]=~~(_5L/49);$k[$j++]=_5L%49;$r($a(2));$P($1.lastrow,2,$k[--$j]);$k[$j++]=$1.cr7*$g($1.weightx,0);$k[$j++]=$1.weightx;$1.calccheck();var _5T=$k[--$j];var _5d=$f($f($f($k[--$j]+_5T)+$1.wr1*$g($1.weightx,$f($1.r*4-3)))+$1.wr2*$g($1.weightx,$f($1.r*4-2)))%2401;$k[$j++]=~~(_5d/49);$k[$j++]=_5d%49;$r($a(2));$P($1.lastrow,4,$k[--$j]);var _5j=$G($1.ccs,$1.ccs.length-8,7);$k[$j++]=0;for(var _5k=0,_5l=_5j.length;_5k<_5l;_5k++){var _5n=$k[--$j];$k[$j++]=$f(_5n+$g(_5j,_5k))}$p($1.ccs,$1.ccs.length-1,$k[--$j]%49);$1.patterns=$a([$a(["11521132","25112131","14212132","25121221","14221222","12412132","23321221","12421222","21521221","15112222","15121312","13312222","24221311","13321312","11512222","22421311","11521312","25112311","14212312","23312311","12412312","21512311","16121131","14321131","12521131","15212131","15221221","13412131","13421221","11612131","16112221","16121311","14312221","14321311","12512221","12521311","15212311","13412311","11612311","11131135","31131133","51131131","21122134","41122132","21131224","41131222","11113135","31113133","51113131","11122225","31122223","51122221","11131315","31131313","51131311","21113224","41113222","21122314","41122312","11113315","31113313","51113311","12131134","32131132","21231133","41231131","22122133","42122131","11222134","22131223","42131221","11231224","31231222","12113134","32113132","12122224","32122222","12131314","32131312","21231313","41231311","22113223","42113221","11213224","22122313","42122311","11222314","31222312","12113314","32113312","21213313","41213311","13131133","33131131","22231132","11331133","31331131","23122132","12222133","23131222","12231223","32231221","21331222","13113133","33113131","13122223","33122221","11313133","13131313","33131311","11322223","22231312","11331313","31331311","23113222","12213223","23122312","12222313","32222311","21322312","13113313","33113311","22213312","11313313","31313311","14131132","23231131","12331132","21431131","24122131","13222132","24131221","13231222","11422132","22331221","11431222","14113132","14122222","12313132","14131312","12322222","23231311","12331312","21431311","24113221","13213222","24122311","13222312","11413222","22322311","11422312","14113312","23213311","12313312","21413311","15131131","13331131","14222131","14231221","12422131","12431221","15113131","15122221","13313131","15131311","13322221","11513131","13331311","11522221","14213221","14222311","12413221","12422311","15113311","13313311","11513311","11141134","31141132","21132133","41132131","21141223","41141221","11123134","31123132","11132224","31132222","11141314","31141312","21114133","41114131","21123223","41123221","21132313","41132311","11114224","31114222","11123314","31123312","21114313","41114311","12141133","32141131","21241132","22132132","11232133","22141222","11241223","31241221","12123133","32123131","12132223","32132221","12141313","32141311","21241312","22114132","11214133","22123222","11223223","22132312","11232313","31232311","12114223","32114221","12123313","32123311","21223312","22114312","11214313","31214311","13141132","22241131","11341132","23132131","12232132","23141221","12241222","21341221","13123132","13132222","11323132","13141312","11332222","22241311","11341312","23114131","12214132","23123221","12223222","23132311","12232312","21332311","13114222","13123312","11314222","22223311","11323312","23114311","12214312","21314311","14141131","12341131","13232131","13241221","11432131","14123131","14132221","12323131","14141311","12332221","12341311","13214131","13223221","11414131","13232311","11423221","11432311","14114221","14123311","12314221","12323311","13214311","11414311","11151133","31151131","21142132","21151222","11133133","31133131","11142223","31142221","11151313","31151311","21124132","21133222","21142312","11115133","31115131","11124223","31124221","11133313","31133311","21115222","21124312","12151132","21251131","22142131","11242132","22151221","11251222","12133132","12142222","12151312","21251311","22124131","11224132","22133221","11233222","22142311","11242312","12115132","12124222","12133312","21233311","22115221","11215222","22124311","11224312","13151131","12242131","12251221","13133131","13142221","11333131","13151311","11342221","12224131","12233221","12242311","13115131","13124221","11315131","13133311","11324221","11333311","12215221","12224311","11161132","21152131","21161221","11143132","11152222","11161312","21134131","21143221","21152311","11125132","11134222","11143312","21116131","21125221","21134311","12161131","11252131","12143131","12152221","12161311","11234131","11243221","11252311","12125131","12134221","12143311","11216131","11225221","11234311","11111236","31111234","51111232","21111325","41111323","61111321","11111416","31111414","51111412","31211143","51211141","12111235","32111233","52111231","21211234","41211232","22111324","42111322","11211325","31211323","51211321","12111415","32111413","52111411","21211414","41211412","12211144","32211142","21311143","41311141","13111234","33111232","22211233","42211231","11311234","31311232","23111323","43111321","12211324","32211322","21311323","41311321","13111414","33111412","22211413","42211411","11311414","31311412","13211143","33211141","22311142","11411143","31411141","14111233","34111231","23211232","12311233","32311231","21411232","24111322","13211323","33211321","22311322","11411323","31411321","14111413","34111411","23211412","12311413","32311411","21411412","14211142","23311141","12411142","21511141","15111232","24211231","13311232","22411231","11511232","25111321","14211322","23311321","12411322","21511321","15111412","24211411","13311412","22411411","11511412","15211141","13411141","11611141","16111231","14311231","12511231","15211321","13411321","11611321","16111411","14311411","12511411","21121144","41121142","11112145","31112143","51112141","11121235","31121233","51121231","21112234","41112232","21121324","41121322","11112325","31112323","51112321","11121415","31121413","51121411","21112414","41112412","22121143","42121141","11221144","31221142","12112144","32112142","12121234","32121232","21221233","41221231","22112233","42112231","11212234","22121323","42121321","11221324","31221322","12112324","32112322","12121414","32121412","21221413","41221411","22112413","42112411","11212414","31212412","23121142","12221143","32221141","21321142","13112143","33112141","13121233","33121231","11312143","22221232","11321233","31321231","23112232","12212233","23121322","12221323","32221321","21321322","13112323","33112321","13121413","33121411","11312323","22221412","11321413","31321411","23112412","12212413","32212411","21312412","24121141","13221142","22321141","11421142","14112142","14121232","12312142","23221231","12321232","21421231","24112231","13212232","24121321","13221322","11412232","22321321","11421322","14112322","14121412","12312322","23221411","12321412","21421411","24112411","13212412","22312411","11412412","14221141","12421141","15112141","15121231","13312141","13321231","11512141","11521231","14212231","14221321","12412231","12421321","15112321","15121411","13312321","13321411","11512321","11521411","14212411","12412411","21131143","41131141","11122144","31122142","11131234","31131232","21113143","41113141","21122233","41122231","21131323","41131321","11113234","31113232","11122324","31122322","11131414","31131412","21113323","41113321","21122413","41122411","11113414","31113412","22131142","11231143","31231141","12122143","32122141","12131233","32131231","21231232","22113142","11213143","22122232","11222233","22131322","11231323","31231321","12113233","32113231","12122323","32122321","12131413","32131411","21231412","22113322","11213323","22122412","11222413","31222411","12113413","32113411","21213412","23131141","12231142","21331141","13122142","13131232","11322142","22231231","11331232","23113141","12213142","23122231","12222232","23131321","12231322","21331321","13113232","13122322","11313232","13131412","11322322","22231411","11331412","23113321","12213322","23122411","12222412","21322411","13113412","22213411","11313412","13231141","11431141","14122141","14131231","12322141","12331231","13213141","13222231","11413141","13231321","11422231","11431321","14113231","14122321","12313231","14131411","12322321","12331411","13213321","13222411","11413321","11422411","14113411","12313411","21141142","11132143","31132141","11141233","31141231","21123142","21132232","21141322","11114143","31114141","11123233","31123231","11132323","31132321","11141413","31141411","21114232","21123322","21132412","11114323","31114321","11123413","31123411","22141141","11241142","12132142","12141232","21241231","22123141","11223142","22132231","11232232","22141321","11241322","12114142","12123232","12132322","12141412","21241411","22114231","11214232","22123321","11223322","22132411","11232412","12114322","12123412","21223411","12241141","13132141","13141231","11332141","11341231","12223141","12232231","12241321","13114141","13123231","11314141","13132321","11323231","13141411","11332321","11341411","12214231","12223321","12232411","13114321","13123411","11314321","11323411","21151141","11142142","11151232","21133141","21142231","21151321","11124142","11133232","11142322","11151412","21115141","21124231","21133321","21142411","11115232","11124322","11133412","11251141","12142141","12151231","11233141","11242231","11251321","12124141","12133231","12142321","12151411","11215141","11224231","11233321","11242411","12115231","12124321","12133411","11152141","11161231","11134141","11143231","11152321","11161411","11116141","11125231","11134321","11143411","21111244","41111242","11111335","31111333","51111331","21111424","41111422","11111515","31111513","51111511","21211153","41211151","22111243","42111241","11211244","31211242","12111334","32111332","21211333","41211331","22111423","42111421","11211424","31211422","12111514","32111512","21211513","41211511","22211152","11311153","31311151","23111242","12211243","32211241","21311242","13111333","33111331","22211332","11311333","31311331","23111422","12211423","32211421","21311422","13111513","33111511","22211512","11311513","31311511","23211151","12311152","21411151","24111241","13211242","22311241","11411242","14111332","23211331","12311332","21411331","24111421","13211422","22311421","11411422","14111512","23211511","12311512","21411511","13311151","11511151","14211241","12411241","15111331","13311331","11511331","14211421","12411421","15111511","13311511","11511511","31121152","21112153","41112151","21121243","41121241","11112244","31112242","11121334","31121332","21112333","41112331","21121423","41121421","11112424","31112422","11121514","31121512","21112513","41112511","12121153","32121151","21221152","22112152","11212153","22121242","11221243","31221241","12112243","32112241","12121333","32121331","21221332","22112332","11212333","22121422","11221423","31221421","12112423","32112421","12121513","32121511","21221512","22112512","11212513","31212511","13121152","22221151","11321152","23112151","12212152","23121241","12221242","21321241","13112242","13121332","11312242","22221331","11321332","23112331","12212332","23121421","12221422","21321421","13112422","13121512","11312422","22221511","11321512","23112511","12212512","21312511","14121151","12321151","13212151","13221241","11412151","11421241","14112241","14121331","12312241","12321331","13212331","13221421","11412331","11421421","14112421","14121511","12312421","12321511","13212511","11412511","11131153","31131151","21122152","21131242","11113153","31113151","11122243","31122241","11131333","31131331","21113242","21122332","21131422","11113333","31113331","11122423","31122421","11131513","31131511","21113422","21122512","12131152","21231151","22122151","11222152","22131241","11231242","12113152","12122242","12131332","21231331","22113241","11213242","22122331","11222332","22131421","11231422","12113332","12122422","12131512","21231511","22113421","11213422","22122511","11222512","13131151","11331151","12222151","12231241","13113151","13122241","11313151","13131331","11322241","11331331","12213241","12222331","12231421","13113331","13122421","11313331","13131511","11322421","11331511","12213421","12222511","11141152","21132151","21141241","11123152","11132242","11141332","21114151","21123241","21132331","21141421","11114242","11123332","11132422","11141512","21114331","21123421","21132511","12141151","11232151","11241241","12123151","12132241","12141331","11214151","11223241","11232331","11241421","12114241","12123331","12132421","12141511","11214331","11223421","11232511","11151151","11133151","11142241","11151331","11115151","11124241","11133331","11142421","11151511","11111254","31111252","21111343","41111341","11111434","31111432","21111523","41111521","11111614","31111612","31211161","12111253","32111251","21211252","22111342","11211343","31211341","12111433","32111431","21211432","22111522","11211523","31211521","12111613","32111611","21211612","12211162","21311161","13111252","22211251","11311252","23111341","12211342","21311341","13111432","22211431","11311432","23111521","12211522","21311521","13111612","22211611","11311612","13211161","11411161","14111251","12311251","13211341","11411341","14111431","12311431","13211521","11411521","14111611","12311611","21121162","11112163","31112161","11121253","31121251","21112252","21121342","11112343","31112341","11121433","31121431","21112432","21121522","11112523","31112521","11121613","31121611","22121161","11221162","12112162","12121252","21221251","22112251","11212252","22121341","11221342","12112342","12121432","21221431","22112431","11212432","22121521","11221522","12112522","12121612","21221611","12221161","13112161","13121251","11312161","11321251","32121115","52121113","21221116","41221114","61221112","22112116","42112114","31212115","51212113","13121116","33121114","22221115","42221113","11321116","31321114","51321112","23112115","43112113","12212116","32212114","52212112","21312115","41312113","61312111","14121115","34121113","23221114","43221112","12321115","32321113","52321111","21421114","41421112","24112114","13212115","33212113","22312114","42312112","11412115","31412113","51412111","15121114","24221113","13321114","33321112","22421113","42421111","11521114","31521112","25112113","14212114","34212112","23312113","43312111","12412114","32412112","21512113","41512111","16121113","25221112","14321113","34321111","23421112","12521113","32521111","15212113","24312112","13412113","33412111","22512112","11612113","31612111","31131115","51131113","21122116","41122114","61122112","31113115","51113113","12131116","32131114","52131112","21231115","41231113","61231111","22122115","42122113","11222116","31222114","51222112","12113116","32113114","52113112","21213115","41213113","61213111","13131115","33131113","22231114","42231112","11331115","31331113","51331111","23122114","43122112","12222115","32222113","52222111","21322114","41322112","13113115","33113113","22213114","42213112","11313115","31313113","51313111","14131114","34131112","23231113","43231111","12331114","32331112","21431113","41431111","24122113","13222114","33222112","22322113","42322111","11422114","31422112","14113114","34113112","23213113","43213111","12313114","32313112","21413113","41413111","15131113","24231112","13331113","33331111","22431112","25122112","14222113","34222111","23322112","12422113","32422111","21522112","15113113","24213112","13313113","33313111","22413112","11513113","31513111","16131112","25231111","14331112","23431111","15222112","24322111","13422112","22522111","16113112","25213111","14313112","23413111","12513112","21613111","11141116","31141114","51141112","21132115","41132113","61132111","11123116","31123114","51123112","21114115","41114113","61114111","12141115","32141113","52141111","21241114","41241112","22132114","42132112","11232115","31232113","51232111","12123115","32123113","52123111","21223114","41223112","22114114","42114112","11214115","31214113","51214111","13141114","33141112","22241113","42241111","11341114","31341112","23132113","43132111","12232114","32232112","21332113","41332111","13123114","33123112","22223113","42223111","11323114","31323112","23114113","43114111","12214114","32214112","21314113","41314111","14141113","34141111","23241112","12341113","32341111","24132112","13232113","33232111","22332112","11432113","31432111","14123113","34123111","23223112","12323113","32323111","21423112","24114112","13214113","33214111","22314112","11414113","31414111","15141112","24241111","13341112","25132111","14232112","23332111","12432112","15123112","24223111","13323112","22423111","11523112","25114111","14214112","23314111","12414112","21514111","16141111","14341111","15232111","13432111","16123111","14323111","12523111","15214111","13414111","11614111","11151115","31151113","51151111","21142114","41142112","11133115","31133113","51133111","21124114","41124112","11115115","31115113","51115111","12151114","32151112","21251113","41251111","22142113","42142111","11242114","31242112","12133114","32133112","21233113","41233111","22124113","42124111","11224114","31224112","12115114","32115112","21215113","41215111","13151113","33151111","22251112","23142112","12242113","32242111","21342112","13133113","33133111","22233112","11333113","31333111","23124112","12224113","32224111","21324112","13115113","33115111","22215112","11315113","31315111","14151112","23251111","24142111","13242112","22342111","14133112","23233111","12333112","21433111","24124111","13224112","22324111","11424112","14115112","23215111","12315112","21415111","15151111","14242111","15133111","13333111","14224111","12424111","15115111","13315111","11515111","11161114","31161112","21152113","41152111","11143114","31143112","21134113","41134111","11125114","31125112","21116113","41116111","12161113","32161111","22152112","11252113","31252111","12143113","32143111","21243112","22134112","11234113","31234111","12125113","32125111","21225112","22116112","11216113","31216111","13161112","23152111","12252112","13143112","22243111","11343112","23134111","12234112","21334111","13125112","22225111","11325112","23116111","12216112","21316111","14161111","13252111","14143111","12343111","13234111","11434111","14125111","12325111","13216111","11416111","31111216","51111214","31211125","51211123","32111215","52111213","21211216","41211214","61211212","12211126","32211124","52211122","21311125","41311123","61311121","13111216","33111214","22211215","42211213","11311216","31311214","51311212","13211125","33211123","22311124","42311122","11411125","31411123","51411121","14111215","34111213","23211214","43211212","12311215","32311213","52311211","21411214","41411212","14211124","34211122","23311123","43311121","12411124","32411122","21511123","41511121","15111214","24211213","13311214","33311212","22411213","42411211","11511214","31511212","15211123","24311122","13411123","33411121","22511122","11611123","31611121","16111213","25211212","14311213","34311211","23411212","12511213","32511211","21611212","21121126","41121124","61121122","31112125","51112123","31121215","51121213","21112216","41112214","61112212","22121125","42121123","11221126","31221124","51221122","12112126","32112124","52112122","12121216","32121214","52121212","21221215","41221213","61221211","22112215","42112213","11212216","31212214","51212212","23121124","43121122","12221125","32221123","52221121","21321124","41321122","13112125","33112123","13121215","33121213","11312125","22221214","42221212","11321215","31321213","51321211","23112214","43112212","12212215","32212213","52212211","21312214","41312212","24121123","13221124","33221122","22321123","42321121","11421124","31421122","14112124","34112122","14121214","34121212","12312124","23221213","43221211","12321214","32321212","21421213","41421211","24112213","13212214","33212212","22312213","42312211","11412214","31412212","25121122","14221123","34221121","23321122","12421123","32421121","21521122","15112123","15121213","13312123","24221212","13321213","33321211","11512123","22421212","11521213","31521211","25112212","14212213","34212211","23312212","12412213","32412211","21512212","15221122","24321121","13421122","22521121","16112122","16121212","14312122","25221211","14321212","12512122","23421211","12521212","15212212","24312211","13412212","22512211","11612212","21131125","41131123","61131121","11122126","31122124","51122122","11131216","31131214","51131212","21113125","41113123","61113121","21122215","41122213","61122211","11113216","31113214","51113212","22131124","42131122","11231125","31231123","51231121","12122125","32122123","52122121","12131215","32131213","52131211","21231214","41231212","22113124","42113122","11213125","22122214","42122212","11222215","31222213","51222211","12113215","32113213","52113211","21213214","41213212","23131123","43131121","12231124","32231122","21331123","41331121","13122124","33122122","13131214","33131212","11322124","22231213","42231211","11331214","31331212","23113123","43113121","12213124","23122213","43122211","12222214","32222212","21322213","41322211","13113214","33113212","22213213","42213211","11313214","31313212","24131122","13231123","33231121","22331122","11431123","31431121","14122123","34122121","14131213","34131211","12322123","23231212","12331213","32331211","21431212","24113122","13213123","24122212","13222213","33222211","11413123","22322212","11422213","31422211","14113213","34113211","23213212","12313213","32313211","21413212","25131121","14231122","23331121","12431122","15122122","15131212","13322122","24231211","13331212","11522122","22431211","25113121","14213122","25122211","14222212","12413122","23322211","12422212","21522211","15113212","24213211","13313212","22413211","11513212","15231121","13431121","16122121","16131211","14322121","14331211","12522121","15213121","15222211","13413121","13422211","11613121","16113211","14313211","12513211","21141124","41141122","11132125","31132123","51132121","11141215","31141213","51141211","21123124","41123122","21132214","41132212","11114125","31114123","51114121","11123215","31123213","51123211","21114214","41114212","22141123","42141121","11241124","31241122","12132124","32132122","12141214","32141212","21241213","41241211","22123123","42123121","11223124","22132213","42132211","11232214","31232212","12114124","32114122","12123214","32123212","21223213","41223211","22114213","42114211","11214214","31214212","23141122","12241123","32241121","21341122","13132123","33132121","13141213","33141211","11332123","22241212","11341213","31341211","23123122","12223123","23132212","12232213","32232211","21332212","13114123","33114121","13123213","33123211","11314123","22223212","11323213","31323211","23114212","12214213","32214211","21314212","24141121","13241122","22341121","14132122","14141212","12332122","23241211","12341212","24123121","13223122","24132211","13232212","11423122","22332211","11432212","14114122","14123212","12314122","23223211","12323212","21423211","24114211","13214212","22314211","11414212","14241121","15132121","15141211","13332121","13341211","14223121","14232211","12423121","12432211","15114121","15123211","13314121","13323211","11514121","11523211","14214211","12414211","21151123","41151121","11142124","31142122","11151214","31151212","21133123","41133121","21142213","41142211","11124124","31124122","11133214","31133212","21115123","41115121","21124213","41124211","11115214","31115212","22151122","11251123","31251121","12142123","32142121","12151213","32151211","21251212","22133122","11233123","22142212","11242213","31242211","12124123","32124121","12133213","32133211","21233212","22115122","11215123","22124212","11224213","31224211","12115213","32115211","21215212","23151121","12251122","13142122","13151212","11342122","22251211","23133121","12233122","23142211","12242212","21342211","13124122","13133212","11324122","22233211","11333212","23115121","12215122","23124211","12224212","21324211","13115212","22215211","11315212","13251121","14142121","14151211","12342121","13233121","13242211","11433121","14124121","14133211","12324121","12333211","13215121","13224211","11415121","11424211","14115211","12315211","21161122","11152123","31152121","11161213","31161211","21143122","21152212","11134123","31134121","11143213","31143211","21125122","21134212","11116123","31116121","11125213","31125211","22161121","12152122","12161212","22143121","11243122","22152211","11252212","12134122","12143212","21243211","22125121","11225122","22134211","11234212","12116122","12125212","21225211","13152121","13161211","12243121","12252211","13134121","13143211","11334121","11343211","12225121","12234211","13116121","13125211","11316121","11325211","21111226","41111224","61111222","31111315","51111313","21211135","41211133","61211131","22111225","42111223","11211226","31211224","51211222","12111316","32111314","52111312","21211315","41211313","61211311","22211134","42211132","11311135","31311133","51311131","23111224","43111222","12211225","32211223","52211221","21311224","41311222","13111315","33111313","22211314","42211312","11311315","31311313","51311311","23211133","43211131","12311134","32311132","21411133","41411131","24111223","13211224","33211222","22311223","42311221","11411224","31411222","14111314","34111312","23211313","43211311","12311314","32311312","21411313","41411311","24211132","13311133","33311131","22411132","11511133","31511131","25111222","14211223","34211221","23311222","12411223","32411221","21511222","15111313","24211312","13311313","33311311","22411312","11511313","31511311","25211131","14311132","23411131","12511132","21611131","15211222","24311221","13411222","22511221","11611222","16111312","25211311","14311312","23411311","12511312","21611311","31121134","51121132","21112135","41112133","61112131","21121225","41121223","61121221","11112226","31112224","51112222","11121316","31121314","51121312","21112315","41112313","61112311","12121135","32121133","52121131","21221134","41221132","22112134","42112132","11212135","22121224","42121222","11221225","31221223","51221221","12112225","32112223","52112221","12121315","32121313","52121311","21221314","41221312","22112314","42112312","11212315","31212313","51212311","13121134","33121132","22221133","42221131","11321134","31321132","23112133","43112131","12212134","23121223","43121221","12221224","32221222","21321223","41321221","13112224","33112222","13121314","33121312","11312224","22221313","42221311","11321314","31321312","23112313","43112311","12212314","32212312","21312313","41312311","14121133","34121131","23221132","12321133","32321131","21421132","24112132","13212133","24121222","13221223","33221221","11412133","22321222","11421223","31421221","14112223","34112221","14121313","34121311","12312223","23221312","12321313","32321311","21421312","24112312","13212313","33212311","22312312","11412313","31412311","15121132","24221131","13321132","22421131"]),$a(["22121116","42121114","31221115","51221113","32112115","52112113","21212116","41212114","61212112","23121115","43121113","12221116","32221114","52221112","21321115","41321113","61321111","13112116","33112114","22212115","42212113","11312116","31312114","51312112","24121114","13221115","33221113","22321114","42321112","11421115","31421113","51421111","14112115","34112113","23212114","43212112","12312115","32312113","52312111","21412114","41412112","25121113","14221114","34221112","23321113","43321111","12421114","32421112","21521113","41521111","15112114","24212113","13312114","33312112","22412113","42412111","11512114","31512112","15221113","24321112","13421113","33421111","22521112","16112113","25212112","14312113","34312111","23412112","12512113","32512111","21612112","21131116","41131114","61131112","31122115","51122113","21113116","41113114","61113112","22131115","42131113","11231116","31231114","51231112","12122116","32122114","52122112","21222115","41222113","61222111","22113115","42113113","11213116","31213114","51213112","23131114","43131112","12231115","32231113","52231111","21331114","41331112","13122115","33122113","22222114","42222112","11322115","31322113","51322111","23113114","43113112","12213115","32213113","52213111","21313114","41313112","24131113","13231114","33231112","22331113","42331111","11431114","31431112","14122114","34122112","23222113","43222111","12322114","32322112","21422113","41422111","24113113","13213114","33213112","22313113","42313111","11413114","31413112","25131112","14231113","34231111","23331112","12431113","32431111","15122113","24222112","13322113","33322111","22422112","11522113","31522111","25113112","14213113","34213111","23313112","12413113","32413111","21513112","15231112","24331111","13431112","16122112","25222111","14322112","23422111","12522112","15213112","24313111","13413112","22513111","11613112","21141115","41141113","61141111","11132116","31132114","51132112","21123115","41123113","61123111","11114116","31114114","51114112","22141114","42141112","11241115","31241113","51241111","12132115","32132113","52132111","21232114","41232112","22123114","42123112","11223115","31223113","51223111","12114115","32114113","52114111","21214114","41214112","23141113","43141111","12241114","32241112","21341113","41341111","13132114","33132112","22232113","42232111","11332114","31332112","23123113","43123111","12223114","32223112","21323113","41323111","13114114","33114112","22214113","42214111","11314114","31314112","24141112","13241113","33241111","22341112","14132113","34132111","23232112","12332113","32332111","21432112","24123112","13223113","33223111","22323112","11423113","31423111","14114113","34114111","23214112","12314113","32314111","21414112","25141111","14241112","23341111","15132112","24232111","13332112","22432111","25123111","14223112","23323111","12423112","21523111","15114112","24214111","13314112","22414111","11514112","15241111","16132111","14332111","15223111","13423111","16114111","14314111","12514111","21151114","41151112","11142115","31142113","51142111","21133114","41133112","11124115","31124113","51124111","21115114","41115112","22151113","42151111","11251114","31251112","12142114","32142112","21242113","41242111","22133113","42133111","11233114","31233112","12124114","32124112","21224113","41224111","22115113","42115111","11215114","31215112","23151112","12251113","32251111","13142113","33142111","22242112","11342113","31342111","23133112","12233113","32233111","21333112","13124113","33124111","22224112","11324113","31324111","23115112","12215113","32215111","21315112","24151111","13251112","14142112","23242111","12342112","24133111","13233112","22333111","11433112","14124112","23224111","12324112","21424111","24115111","13215112","22315111","11415112","14251111","15142111","13342111","14233111","12433111","15124111","13324111","11524111","14215111","12415111","21161113","41161111","11152114","31152112","21143113","41143111","11134114","31134112","21125113","41125111","11116114","31116112","22161112","12152113","32152111","21252112","22143112","11243113","31243111","12134113","32134111","21234112","22125112","11225113","31225111","12116113","32116111","21216112","23161111","13152112","22252111","23143111","12243112","21343111","13134112","22234111","11334112","23125111","12225112","21325111","13116112","22216111","11316112","14152111","13243111","14134111","12334111","13225111","11425111","14116111","12316111","41111215","61111213","21211126","41211124","61211122","22111216","42111214","31211215","51211213","22211125","42211123","11311126","31311124","51311122","23111215","43111213","12211216","32211214","52211212","21311215","41311213","61311211","23211124","43211122","12311125","32311123","52311121","21411124","41411122","24111214","13211215","33211213","22311214","42311212","11411215","31411213","51411211","24211123","13311124","33311122","22411123","42411121","11511124","31511122","25111213","14211214","34211212","23311213","43311211","12411214","32411212","21511213","41511211","25211122","14311123","34311121","23411122","12511123","32511121","21611122","15211213","24311212","13411213","33411211","22511212","11611213","31611211","31121125","51121123","21112126","41112124","61112122","21121216","41121214","61121212","31112215","51112213","12121126","32121124","52121122","21221125","41221123","61221121","22112125","42112123","11212126","22121215","42121213","11221216","31221214","51221212","12112216","32112214","52112212","21212215","41212213","61212211","13121125","33121123","22221124","42221122","11321125","31321123","51321121","23112124","43112122","12212125","23121214","43121212","12221215","32221213","52221211","21321214","41321212","13112215","33112213","22212214","42212212","11312215","31312213","51312211","14121124","34121122","23221123","43221121","12321124","32321122","21421123","41421121","24112123","13212124","24121213","13221214","33221212","11412124","22321213","42321211","11421214","31421212","14112214","34112212","23212213","43212211","12312214","32312212","21412213","41412211","15121123","24221122","13321123","33321121","22421122","11521123","31521121","25112122","14212123","25121212","14221213","34221211","12412123","23321212","12421213","32421211","21521212","15112213","24212212","13312213","33312211","22412212","11512213","31512211","16121122","25221121","14321122","23421121","12521122","15212122","15221212","13412122","24321211","13421212","11612122","22521211","16112212","25212211","14312212","23412211","12512212","21612211","11131126","31131124","51131122","21122125","41122123","61122121","21131215","41131213","61131211","11113126","31113124","51113122","11122216","31122214","51122212","21113215","41113213","61113211","12131125","32131123","52131121","21231124","41231122","22122124","42122122","11222125","22131214","42131212","11231215","31231213","51231211","12113125","32113123","52113121","12122215","32122213","52122211","21222214","41222212","22113214","42113212","11213215","31213213","51213211","13131124","33131122","22231123","42231121","11331124","31331122","23122123","43122121","12222124","23131213","43131211","12231214","32231212","21331213","41331211","13113124","33113122","13122214","33122212","11313124","22222213","42222211","11322214","31322212","23113213","43113211","12213214","32213212","21313213","41313211","14131123","34131121","23231122","12331123","32331121","21431122","24122122","13222123","24131212","13231213","33231211","11422123","22331212","11431213","31431211","14113123","34113121","14122213","34122211","12313123","23222212","12322213","32322211","21422212","24113212","13213213","33213211","22313212","11413213","31413211","15131122","24231121","13331122","22431121","25122121","14222122","25131211","14231212","12422122","23331211","12431212","15113122","15122212","13313122","24222211","13322212","11513122","22422211","11522212","25113211","14213212","23313211","12413212","21513211","16131121","14331121","15222121","15231211","13422121","13431211","16113121","16122211","14313121","14322211","12513121","12522211","15213211","13413211","11613211","11141125","31141123","51141121","21132124","41132122","21141214","41141212","11123125","31123123","51123121","11132215","31132213","51132211","21114124","41114122","21123214","41123212","11114215","31114213","51114211","12141124","32141122","21241123","41241121","22132123","42132121","11232124","22141213","42141211","11241214","31241212","12123124","32123122","12132214","32132212","21232213","41232211","22114123","42114121","11214124","22123213","42123211","11223214","31223212","12114214","32114212","21214213","41214211","13141123","33141121","22241122","11341123","31341121","23132122","12232123","23141212","12241213","32241211","21341212","13123123","33123121","13132213","33132211","11323123","22232212","11332213","31332211","23114122","12214123","23123212","12223213","32223211","21323212","13114213","33114211","22214212","11314213","31314211","14141122","23241121","12341122","24132121","13232122","24141211","13241212","11432122","22341211","14123122","14132212","12323122","23232211","12332212","21432211","24114121","13214122","24123211","13223212","11414122","22323211","11423212","14114212","23214211","12314212","21414211","15141121","13341121","14232121","14241211","12432121","15123121","15132211","13323121","13332211","11523121","14214121","14223211","12414121","12423211","15114211","13314211","11514211","11151124","31151122","21142123","41142121","21151213","41151211","11133124","31133122","11142214","31142212","21124123","41124121","21133213","41133211","11115124","31115122","11124214","31124212","21115213","41115211","12151123","32151121","21251122","22142122","11242123","22151212","11251213","31251211","12133123","32133121","12142213","32142211","21242212","22124122","11224123","22133212","11233213","31233211","12115123","32115121","12124213","32124211","21224212","22115212","11215213","31215211","13151122","22251121","23142121","12242122","23151211","12251212","13133122","13142212","11333122","22242211","11342212","23124121","12224122","23133211","12233212","21333211","13115122","13124212","11315122","22224211","11324212","23115211","12215212","21315211","14151121","13242121","13251211","14133121","14142211","12333121","12342211","13224121","13233211","11424121","11433211","14115121","14124211","12315121","12324211","13215211","11415211","11161123","31161121","21152122","21161212","11143123","31143121","11152213","31152211","21134122","21143212","11125123","31125121","11134213","31134211","21116122","21125212","12161122","22152121","11252122","22161211","12143122","12152212","21252211","22134121","11234122","22143211","11243212","12125122","12134212","21234211","22116121","11216122","22125211","11225212","13161121","12252121","13143121","13152211","11343121","12234121","12243211","13125121","13134211","11325121","11334211","12216121","12225211","31111225","51111223","21111316","41111314","61111312","31211134","51211132","12111226","32111224","52111222","21211225","41211223","61211221","22111315","42111313","11211316","31211314","51211312","12211135","32211133","52211131","21311134","41311132","13111225","33111223","22211224","42211222","11311225","31311223","51311221","23111314","43111312","12211315","32211313","52211311","21311314","41311312","13211134","33211132","22311133","42311131","11411134","31411132","14111224","34111222","23211223","43211221","12311224","32311222","21411223","41411221","24111313","13211314","33211312","22311313","42311311","11411314","31411312","14211133","34211131","23311132","12411133","32411131","21511132","15111223","24211222","13311223","33311221","22411222","11511223","31511221","25111312","14211313","34211311","23311312","12411313","32411311","21511312","15211132","24311131","13411132","22511131","11611132","16111222","25211221","14311222","23411221","12511222","21611221","15211312","24311311","13411312","22511311","11611312","21121135","41121133","61121131","11112136","31112134","51112132","11121226","31121224","51121222","21112225","41112223","61112221","21121315","41121313","61121311","11112316","31112314","51112312","22121134","42121132","11221135","31221133","51221131","12112135","32112133","52112131","12121225","32121223","52121221","21221224","41221222","22112224","42112222","11212225","22121314","42121312","11221315","31221313","51221311","12112315","32112313","52112311","21212314","41212312","23121133","43121131","12221134","32221132","21321133","41321131","13112134","33112132","13121224","33121222","11312134","22221223","42221221","11321224","31321222","23112223","43112221","12212224","23121313","43121311","12221314","32221312","21321313","41321311","13112314","33112312","22212313","42212311","11312314","31312312","24121132","13221133","33221131","22321132","11421133","31421131","14112133","34112131","14121223","34121221","12312133","23221222","12321223","32321221","21421222","24112222","13212223","24121312","13221313","33221311","11412223","22321312","11421313","31421311","14112313","34112311","23212312","12312313","32312311","21412312","25121131","14221132","23321131","12421132","21521131","15112132","15121222","13312132","24221221","13321222","11512132","22421221","11521222","25112221","14212222","25121311","14221312","12412222","23321311","12421312","21521311","15112312","24212311","13312312","22412311","11512312","15221131","13421131","16112131","16121221","14312131","14321221","12512131","12521221","15212221","15221311","13412221","13421311","11612221","16112311","14312311","12512311","21131134","41131132","11122135","31122133","51122131","11131225","31131223","51131221","21113134","41113132","21122224","41122222","21131314","41131312","11113225","31113223","51113221","11122315","31122313","51122311","21113314","41113312","22131133","42131131","11231134","31231132","12122134","32122132","12131224","32131222","21231223","41231221","22113133","42113131","11213134","22122223","42122221","11222224","22131313","42131311","11231314","31231312","12113224","32113222","12122314","32122312","21222313","41222311","22113313","42113311","11213314","31213312","23131132","12231133","32231131","21331132","13122133","33122131","13131223","33131221","11322133","22231222","11331223","31331221","23113132","12213133","23122222","12222223","23131312","12231313","32231311","21331312","13113223","33113221","13122313","33122311","11313223","22222312","11322313","31322311","23113312","12213313","32213311","21313312","24131131","13231132","22331131","11431132","14122132","14131222","12322132","23231221","12331222","21431221","24113131","13213132","24122221","13222222","24131311","11413132","13231312","11422222","22331311","11431312","14113222","14122312","12313222","23222311","12322312","21422311","24113311","13213312","22313311","11413312","14231131","12431131","15122131","15131221","13322131","13331221","11522131","14213131","14222221","12413131","14231311","12422221","12431311","15113221","15122311","13313221","13322311","11513221","11522311","14213311","12413311","21141133","41141131","11132134","31132132","11141224","31141222","21123133","41123131","21132223","41132221","21141313","41141311","11114134","31114132","11123224","31123222","11132314","31132312","21114223","41114221","21123313","41123311","11114314","31114312","22141132","11241133","31241131","12132133","32132131","12141223","32141221","21241222","22123132","11223133","22132222","11232223","22141312","11241313","31241311","12114133","32114131","12123223","32123221","12132313","32132311","21232312","22114222","11214223","22123312","11223313","31223311","12114313","32114311","21214312","23141131","12241132","21341131","13132132","13141222","11332132","22241221","11341222","23123131","12223132","23132221","12232222","23141311","12241312","21341311","13114132","13123222","11314132","13132312","11323222","22232311","11332312","23114221","12214222","23123311","12223312","21323311","13114312","22214311","11314312","13241131","14132131","14141221","12332131","12341221","13223131","13232221","11423131","13241311","11432221","14114131","14123221","12314131","14132311","12323221","12332311","13214221","13223311","11414221","11423311","14114311","12314311","21151132","11142133","31142131","11151223","31151221","21133132","21142222","21151312","11124133","31124131","11133223","31133221","11142313","31142311","21115132","21124222","21133312","11115223","31115221","11124313","31124311","22151131","11251132","12142132","12151222","21251221","22133131","11233132","22142221","11242222","22151311","11251312","12124132","12133222","12142312","21242311","22115131","11215132","22124221","11224222","22133311","11233312","12115222","12124312","21224311","12251131","13142131","13151221","11342131","12233131","12242221","12251311","13124131","13133221","11324131","13142311","11333221","11342311","12215131","12224221","12233311","13115221","13124311","11315221","11324311","21161131","11152132","11161222","21143131","21152221","21161311","11134132","11143222","11152312","21125131","21134221","21143311","11116132","11125222","11134312","12152131","12161221","11243131","11252221","12134131","12143221","12152311","11225131","11234221","11243311","12116131","12125221","12134311","21111235","41111233","61111231","11111326","31111324","51111322","21111415","41111413","61111411","21211144","41211142","22111234","42111232","11211235","31211233","51211231","12111325","32111323","52111321","21211324","41211322","22111414","42111412","11211415","31211413","51211411","22211143","42211141","11311144","31311142","23111233","43111231","12211234","32211232","21311233","41311231","13111324","33111322","22211323","42211321","11311324","31311322","23111413","43111411","12211414","32211412","21311413","41311411","23211142","12311143","32311141","21411142","24111232","13211233","33211231","22311232","11411233","31411231","14111323","34111321","23211322","12311323","32311321","21411322","24111412","13211413","33211411","22311412","11411413","31411411","24211141","13311142","22411141","11511142","25111231","14211232","23311231","12411232","21511231","15111322","24211321","13311322","22411321","11511322","25111411","14211412","23311411","12411412","21511411","14311141","12511141","15211231","13411231","11611231","16111321","14311321","12511321","15211411","13411411","11611411","31121143","51121141","21112144","41112142","21121234","41121232","11112235","31112233","51112231","11121325","31121323","51121321","21112324","41112322","21121414","41121412","11112415","31112413","51112411","12121144","32121142","21221143","41221141","22112143","42112141","11212144","22121233","42121231","11221234","31221232","12112234","32112232","12121324","32121322","21221323","41221321","22112323","42112321","11212324","22121413","42121411","11221414","31221412","12112414","32112412","21212413","41212411","13121143","33121141","22221142","11321143","31321141","23112142","12212143","23121232","12221233","32221231","21321232","13112233","33112231","13121323","33121321","11312233","22221322","11321323","31321321","23112322","12212323","23121412","12221413","32221411","21321412","13112413","33112411","22212412","11312413","31312411","14121142","23221141","12321142","21421141","24112141","13212142","24121231","13221232","11412142","22321231","11421232","14112232","14121322","12312232","23221321","12321322","21421321","24112321","13212322","24121411","13221412","11412322","22321411","11421412","14112412","23212411","12312412","21412411","15121141","13321141","11521141","14212141","14221231","12412141","12421231","15112231","15121321","13312231","13321321","11512231","11521321","14212321","14221411","12412321","12421411","15112411","13312411","11512411","11131144","31131142","21122143","41122141","21131233","41131231","11113144","31113142","11122234","31122232","11131324","31131322","21113233","41113231","21122323","41122321","21131413","41131411","11113324","31113322","11122414","31122412","21113413","41113411","12131143","32131141","21231142","22122142","11222143","22131232","11231233","31231231","12113143","32113141","12122233","32122231","12131323","32131321","21231322","22113232","11213233","22122322","11222323","22131412","11231413","31231411","12113323","32113321","12122413","32122411","21222412","22113412","11213413","31213411","13131142","22231141","11331142","23122141","12222142","23131231","12231232","21331231","13113142","13122232","11313142","13131322","11322232","22231321","11331322","23113231","12213232","23122321","12222322","23131411","12231412","21331411","13113322","13122412","11313322","22222411","11322412","23113411","12213412","21313411","14131141","12331141","13222141","13231231","11422141","11431231","14113141","14122231","12313141","14131321","12322231","12331321","13213231","13222321","11413231","13231411","11422321","11431411","14113321","14122411","12313321","12322411","13213411","11413411","11141143","31141141","21132142","21141232","11123143","31123141","11132233","31132231","11141323","31141321","21114142","21123232","21132322","21141412","11114233","31114231","11123323","31123321","11132413","31132411","21114322","21123412","12141142","21241141","22132141","11232142","22141231","11241232","12123142","12132232","12141322","21241321","22114141","11214142","22123231","11223232","22132321","11232322","22141411","11241412","12114232","12123322","12132412","21232411","22114321","11214322","22123411","11223412","13141141","11341141","12232141","12241231","13123141","13132231","11323141","13141321","11332231","11341321","12214141","12223231","12232321","12241411","13114231","13123321","11314231","13132411","11323321","11332411","12214321","12223411","11151142","21142141","21151231","11133142","11142232","11151322","21124141","21133231","21142321","21151411","11115142","11124232","11133322","11142412","21115231","21124321","21133411","12151141","11242141","11251231","12133141","12142231","12151321","11224141","11233231","11242321","11251411","12115141","12124231","12133321","12142411","11215231","11224321","11233411","11161141","11143141","11152231","11161321","11125141","11134231","11143321","11152411","11111245","31111243","51111241","21111334","41111332","11111425","31111423","51111421","21111514","41111512","31211152","12111244","32111242","21211243","41211241","22111333","42111331","11211334","31211332","12111424","32111422","21211423","41211421","22111513","42111511","11211514","31211512","12211153","32211151","21311152","13111243","33111241","22211242","11311243","31311241","23111332","12211333","32211331","21311332","13111423","33111421","22211422","11311423","31311421","23111512","12211513","32211511","21311512","13211152","22311151","11411152","14111242","23211241","12311242","21411241","24111331","13211332","22311331","11411332","14111422","23211421","12311422","21411421","24111511","13211512","22311511","11411512","14211151","12411151","15111241","13311241","11511241","14211331","12411331","15111421","13311421","11511421","14211511","12411511","21121153","41121151","11112154","31112152","11121244","31121242","21112243","41112241","21121333","41121331","11112334","31112332","11121424","31121422","21112423","41112421","21121513","41121511","11112514","31112512","22121152","11221153","31221151","12112153","32112151","12121243","32121241","21221242","22112242","11212243","22121332","11221333","31221331","12112333","32112331","12121423","32121421","21221422","22112422","11212423","22121512","11221513","31221511","12112513","32112511","21212512","23121151","12221152","21321151","13112152","13121242","11312152","22221241","11321242","23112241","12212242","23121331","12221332","21321331","13112332","13121422","11312332","22221421","11321422","23112421","12212422","23121511","12221512","21321511","13112512","22212511","11312512","13221151","11421151","14112151","14121241","12312151","12321241","13212241","13221331","11412241","11421331","14112331","14121421","12312331","12321421","13212421","13221511","11412421","11421511","14112511","12312511","21131152","11122153","31122151","11131243","31131241","21113152","21122242","21131332","11113243","31113241","11122333","31122331","11131423","31131421","21113332","21122422","21131512","11113423","31113421","11122513","31122511","22131151","11231152","12122152","12131242","21231241","22113151","11213152","22122241","11222242","22131331","11231332","12113242","12122332","12131422","21231421","22113331","11213332","22122421","11222422","22131511","11231512","12113422","12122512","21222511","12231151","13122151","13131241","11322151","11331241","12213151","12222241","12231331","13113241","13122331","11313241","13131421","11322331","11331421","12213331","12222421","12231511","13113421","13122511","11313421","11322511","21141151","11132152","11141242","21123151","21132241","21141331","11114152","11123242","11132332","11141422","21114241","21123331","21132421","21141511","11114332","11123422","11132512","11241151","12132151","12141241","11223151","11232241","11241331","12114151","12123241","12132331","12141421","11214241","11223331","11232421","11241511","12114331","12123421","12132511","11142151","11151241","11124151","11133241","11142331","11151421","11115241","11124331","11133421","11142511","21111253","41111251","11111344","31111342","21111433","41111431","11111524","31111522","21111613","41111611","21211162","22111252","11211253","31211251","12111343","32111341","21211342","22111432","11211433","31211431","12111523","32111521","21211522","22111612","11211613","31211611","22211161","11311162","23111251","12211252","21311251","13111342","22211341","11311342","23111431","12211432","21311431","13111522","22211521","11311522","23111611","12211612","21311611","12311161","13211251","11411251","14111341","12311341","13211431","11411431","14111521","12311521","13211611","11411611","31121161","21112162","21121252","11112253","31112251","11121343","31121341","21112342","21121432","11112433","31112431","11121523","31121521","21112522","21121612","12121162","21221161","22112161","11212162","22121251","11221252","12112252","12121342","21221341","22112341","11212342","22121431","11221432","12112432","12121522","21221521","22112521","11212522","22121611","11221612","13121161","11321161","12212161","12221251","13112251","13121341","11312251","11321341","12212341","12221431","13112431","13121521","11312431","11321521","12212521","12221611","11131162","21122161","21131251","11113162"])]);$1.parity=$a(["1001","0101","1100","0011","1010","0110","1111","0000"]);$1.rowbits=$a($1.r);for(var _5z=0,_5y=$f($1.r-1);_5z<=_5y;_5z+=1){$1.i=_5z;$k[$j++]="p";if($1.i!=$f($1.r-1)){$k[$j++]=$g($1.parity,$1.i)}else{$k[$j++]="0000"}var _65=$k[--$j];$1[$k[--$j]]=_65;$1.ccrow=$G($1.ccs,$1.i*8,8);$k[$j++]=Infinity;for(var _6A=0;_6A<=7;_6A+=2){$q($G($1.ccrow,_6A,2));var _6D=$k[--$j];var _6E=$k[--$j];$k[$j++]=$f(_6D+_6E*49)}$1.scrow=$a();$k[$j++]=Infinity;$k[$j++]=10;$k[$j++]=1;$k[$j++]=1;for(var _6G=0;_6G<=3;_6G+=1){$1.j=_6G;$F($g($g($1.patterns,$f($g($1.p,$1.j)-48)),$g($1.scrow,$1.j)),function(){var _6Q=$k[--$j];$k[$j++]=$f(_6Q-48)})}$k[$j++]=4;$k[$j++]=1;$1.sbs=$a();$k[$j++]=Infinity;var _6S=$1.sbs;$k[$j++]=1;for(var _6T=0,_6U=_6S.length;_6T<_6U;_6T++){var _6W=$k[--$j];var _6X=_6W==0?1:0;$k[$j++]=_6W;for(var _6Y=0,_6Z=$g(_6S,_6T);_6Y<_6Z;_6Y++){$k[$j++]=_6X}}$r($a($m()-1));var _6c=$k[--$j];var _6d=$k[--$j];$k[$j++]=_6c;$k[$j++]=_6d;$j--;var _6e=$k[--$j];var _6f=$k[--$j];$k[$j++]=_6e;$k[$j++]=_6f;$j--;$p($1.rowbits,$1.i,$k[--$j])}$k[$j++]=Infinity;for(var _6k=0,_6l=81*$1.sepheight;_6k<_6l;_6k++){$k[$j++]=1}for(var _6o=0,_6n=$f($1.r-2);_6o<=_6n;_6o+=1){$1.i=_6o;for(var _6q=0,_6r=$1.rowheight;_6q<_6r;_6q++){$q($g($1.rowbits,$1.i))}for(var _6w=0,_6x=$1.sepheight;_6w<_6x;_6w++){for(var _6y=0,_6z=10;_6y<_6z;_6y++){$k[$j++]=0}for(var _70=0,_71=70;_70<_71;_70++){$k[$j++]=1}$k[$j++]=0}}for(var _73=0,_74=$1.rowheight;_73<_74;_73++){$q($g($1.rowbits,$f($1.r-1)))}for(var _79=0,_7A=81*$1.sepheight;_79<_7A;_79++){$k[$j++]=1}$1.pixs=$a();var _7G=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",81],["pixy",~~($1.pixs.length/81)],["height",~~($1.pixs.length/81)/72],["width",81/72],["opt",$1.options]]);$k[$j++]=_7G;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_flattermarken(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.includetext=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.height=.3;$F($1.barcode,function(){var _3=$k[--$j];if(_3<48||_3>57){$k[$j++]="bwipp.flattermarkenBadCharacter";$k[$j++]="Flattermarken must contain only digits";bwipp_raiseerror()}});$F($1.options,function(){var _5=$k[--$j];$1[$k[--$j]]=_5});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.height=+$1.height;$F($1.barcode,function(){var _C=$k[--$j];if(_C<48||_C>57){$k[$j++]="bwipp.flattermarkenBadCharacter";$k[$j++]="Flattermarken must contain only digits";bwipp_raiseerror()}});$1.encs=$a(["0018","0117","0216","0315","0414","0513","0612","0711","0810","0900"]);$1.barchars="1234567890";$1.barlen=$1.barcode.length;$1.sbs=$s($1.barlen*4);$1.txt=$a($1.barlen);for(var _L=0,_K=$1.barlen-1;_L<=_K;_L+=1){$1.i=_L;$x($1.barchars,$G($1.barcode,$1.i,1));$j--;$1.indx=$k[--$j].length;$j-=2;$1.enc=$g($1.encs,$1.indx);$P($1.sbs,$1.i*4,$1.enc);$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),$1.i*9,$1.textyoffset,$1.textfont,$1.textsize]))}$k[$j++]=Infinity;$k[$j++]=Infinity;var _h=$1.sbs;for(var _i=0,_j=_h.length;_i<_j;_i++){$k[$j++]=$g(_h,_i)-48}var _l=$a();$k[$j++]=Infinity;for(var _n=0,_o=~~(($1.sbs.length+1)/2);_n<_o;_n++){$k[$j++]=$1.height}var _q=$a();$k[$j++]=Infinity;for(var _s=0,_t=~~(($1.sbs.length+1)/2);_s<_t;_s++){$k[$j++]=0}var _u=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_l;$k[$j++]="bhs";$k[$j++]=_q;$k[$j++]="bbs";$k[$j++]=_u;$k[$j++]="txt";$k[$j++]=$1.txt;$k[$j++]="opt";$k[$j++]=$1.options;var _x=$d();$k[$j++]=_x;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_raw(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.height=+$1.height;$F($1.barcode,function(){var _7=$k[--$j];if(_7<49||_7>57){$k[$j++]="bwipp.rawBadCharacter";$k[$j++]="Raw must contain only digits 1 to 9";bwipp_raiseerror()}});$k[$j++]=Infinity;$k[$j++]=Infinity;$F($1.barcode,function(){var _9=$k[--$j];$k[$j++]=$f(_9-48)});var _A=$a();$k[$j++]=Infinity;for(var _C=0,_D=~~(($1.barcode.length+1)/2);_C<_D;_C++){$k[$j++]=$1.height}var _F=$a();$k[$j++]=Infinity;for(var _H=0,_I=~~(($1.barcode.length+1)/2);_H<_I;_H++){$k[$j++]=0}var _J=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_A;$k[$j++]="bhs";$k[$j++]=_F;$k[$j++]="bbs";$k[$j++]=_J;$k[$j++]="opt";$k[$j++]=$1.options;var _L=$d();$k[$j++]=_L;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_daft(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.height=.175;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.height=+$1.height;$F($1.barcode,function(){var _7=$k[--$j];if(_7!=68&&(_7!=65&&(_7!=70&&_7!=84))){$k[$j++]="bwipp.daftBadCharacter";$k[$j++]="DAFT must contain only characters D, A, F and T";bwipp_raiseerror()}});$1.barlen=$1.barcode.length;$1.bbs=$a($1.barlen);$1.bhs=$a($1.barlen);for(var _F=0,_E=$1.barlen-1;_F<=_E;_F+=1){$1.i=_F;$1.enc=$G($1.barcode,$1.i,1);if($eq($1.enc,"D")){$p($1.bbs,$1.i,0*$1.height/8);$p($1.bhs,$1.i,5*$1.height/8)}if($eq($1.enc,"A")){$p($1.bbs,$1.i,3*$1.height/8);$p($1.bhs,$1.i,5*$1.height/8)}if($eq($1.enc,"F")){$p($1.bbs,$1.i,0*$1.height/8);$p($1.bhs,$1.i,8*$1.height/8)}if($eq($1.enc,"T")){$p($1.bbs,$1.i,3*$1.height/8);$p($1.bhs,$1.i,2*$1.height/8)}}$k[$j++]=Infinity;var _l=$1.bbs;var _m=$1.bhs;$k[$j++]=Infinity;for(var _o=0,_p=$1.bhs.length-1;_o<_p;_o++){$k[$j++]=1.44;$k[$j++]=1.872}$k[$j++]=1.44;var _q=$a();var _r=$1.options;$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="bbs";$k[$j++]=_l;$k[$j++]="bhs";$k[$j++]=_m;$k[$j++]="sbs";$k[$j++]=_q;$k[$j++]="opt";$k[$j++]=_r;var _s=$d();$k[$j++]=_s;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_symbol(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});var _H=new Map([["fima",function(){$1.sbs=$a([2.25,2.25,2.25,11.25,2.25,11.25,2.25,2.25,2.25]);$1.bhs=$a([.625,.625,.625,.625,.625]);$1.bbs=$a([0,0,0,0,0])}],["fimb",function(){$1.sbs=$a([2.25,6.75,2.25,2.25,2.25,6.25,2.25,2.25,2.25,6.75,2.25]);$1.bhs=$a([.625,.625,.625,.625,.625,.625]);$1.bbs=$a([0,0,0,0,0,0])}],["fimc",function(){$1.sbs=$a([2.25,2.25,2.25,6.75,2.25,6.75,2.25,6.75,2.25,2.25,2.25]);$1.bhs=$a([.625,.625,.625,.625,.625,.625]);$1.bbs=$a([0,0,0,0,0,0])}],["fimd",function(){$1.sbs=$a([2.25,2.25,2.25,2.25,2.25,6.75,2.25,6.75,2.25,2.25,2.25,2.25,2.25]);$1.bhs=$a([.625,.625,.625,.625,.625,.625,.625]);$1.bbs=$a([0,0,0,0,0,0,0])}]]);$1.symbols=_H;var _K=$g($1.symbols,$1.barcode)!==undefined;if(!_K){$k[$j++]="bwipp.symbolUnknownSymbol";$k[$j++]="Unknown symbol name provided";bwipp_raiseerror()}if($g($1.symbols,$1.barcode)()===true){return true}var _S=new Map([["ren",bwipp_renlinear],["sbs",$1.sbs],["bhs",$1.bhs],["bbs",$1.bbs],["opt",$1.options]]);$k[$j++]=_S;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_pdf417(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.compact=false;$1.eclevel=-1;$1.columns=0;$1.rows=0;$1.rowmult=3;$1.encoding="auto";$1.ccc=false;$1.raw=false;$1.parse=false;$1.parsefnc=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.eclevel=~~$1.eclevel;$1.columns=~~$1.columns;$1.rows=~~$1.rows;$1.rowmult=+$1.rowmult;if($1.raw){$1.encoding="raw"}if($eq($1.encoding,"raw")){$1.datcws=$a($1.barcode.length);$1.i=0;$1.j=0;for(;;){if($1.i==$1.barcode.length){break}$1.cw=~~$z($G($1.barcode,$1.i+1,3));$p($1.datcws,$1.j,$1.cw);$1.i=$1.i+4;$1.j=$1.j+1}$1.datcws=$G($1.datcws,0,$1.j)}if($1.ccc){$1.encoding="ccc"}$1.encb=function(){$1.in=$k[--$j];$1.inlen=$1.in.length;$1.out=$a(~~($1.inlen/6)*5+$1.inlen%6);for(var _Y=0,_X=~~($1.inlen/6)-1;_Y<=_X;_Y+=1){$1.k=_Y;$k[$j++]=Infinity;$q($G($1.in,$1.k*6,3));$1.msbs=$a();$k[$j++]=Infinity;$q($1.msbs);var _e=$k[--$j];var _f=$k[--$j];var _g=$k[--$j];$k[$j++]=$f($f(_e+_f*256)+_g*65536);for(var _h=0,_i=3;_h<_i;_h++){var _j=$k[--$j];$k[$j++]=_j%900;$k[$j++]=~~(_j/900)}$1.mscs=$a();$k[$j++]=Infinity;$q($G($1.in,$1.k*6+3,3));$1.lsbs=$a();$k[$j++]=Infinity;$q($1.lsbs);var _q=$k[--$j];var _r=$k[--$j];var _s=$k[--$j];$k[$j++]=$f($f(_q+_r*256)+_s*65536);for(var _t=0,_u=3;_t<_u;_t++){var _v=$k[--$j];$k[$j++]=_v%900;$k[$j++]=~~(_v/900)}$1.lscs=$a();var _y=$g($1.lscs,0);var _10=$g($1.mscs,0);$p($1.out,$1.k*5+4,$f(_y+_10*316)%900);var _14=$g($1.lscs,1);var _16=$g($1.mscs,0);var _18=$g($1.mscs,1);$p($1.out,$1.k*5+3,$f($f($f(~~($f(_y+_10*316)/900)+_14)+_16*641)+_18*316)%900);var _1C=$g($1.lscs,2);var _1E=$g($1.mscs,0);var _1G=$g($1.mscs,1);var _1I=$g($1.mscs,2);$p($1.out,$1.k*5+2,$f($f($f($f(~~($f($f($f(~~($f(_y+_10*316)/900)+_14)+_16*641)+_18*316)/900)+_1C)+_1E*20)+_1G*641)+_1I*316)%900);var _1M=$g($1.lscs,3);var _1O=$g($1.mscs,1);var _1Q=$g($1.mscs,2);$p($1.out,$1.k*5+1,$f($f($f(~~($f($f($f($f(~~($f($f($f(~~($f(_y+_10*316)/900)+_14)+_16*641)+_18*316)/900)+_1C)+_1E*20)+_1G*641)+_1I*316)/900)+_1M)+_1O*20)+_1Q*641)%900);$p($1.out,$1.k*5,$f(~~($f($f($f(~~($f($f($f($f(~~($f($f($f(~~($f(_y+_10*316)/900)+_14)+_16*641)+_18*316)/900)+_1C)+_1E*20)+_1G*641)+_1I*316)/900)+_1M)+_1O*20)+_1Q*641)/900)+$g($1.mscs,2)*20)%900)}$1.rem=$1.inlen%6;if($1.rem!=0){$k[$j++]=$1.out;$k[$j++]=$1.out.length-$1.rem;$k[$j++]=Infinity;$q($G($1.in,$1.inlen-$1.rem,$1.rem));var _1h=$a();var _1i=$k[--$j];$P($k[--$j],_1i,_1h)}$k[$j++]=$1.out};if($eq($1.encoding,"byte")||$eq($1.encoding,"ccc")){$1.barlen=$1.barcode.length;$1.datcws=$a(~~($1.barlen/6)*5+$1.barlen%6+1);var _1t=$1.barlen%6==0?924:901;$p($1.datcws,0,_1t);$k[$j++]=$1.datcws;$k[$j++]=1;$k[$j++]=Infinity;$F($1.barcode);var _1w=$a();$k[$j++]=_1w;$1.encb();var _1x=$k[--$j];var _1y=$k[--$j];$P($k[--$j],_1y,_1x);if($eq($1.encoding,"ccc")){$k[$j++]=Infinity;$k[$j++]=920;$q($1.datcws);$1.datcws=$a()}}if($eq($1.encoding,"auto")){var _26=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["eci",true]]);$1.fncvals=_26;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _29=$k[--$j];$1[$k[--$j]]=_29;$1.msglen=$1.msg.length;$1.T=0;$1.N=1;$1.B=2;$1.A=0;$1.L=1;$1.M=2;$1.P=3;$1.tl=-1;$1.nl=-2;$1.bl=-3;$1.bl6=-4;$1.bs=-5;$1.al=-6;$1.ll=-7;$1.ml=-8;$1.pl=-9;$1.as=-10;$1.ps=-11;$1.charmaps=$a([$a(["A","a","0",";"]),$a(["B","b","1","<"]),$a(["C","c","2",">"]),$a(["D","d","3","@"]),$a(["E","e","4","["]),$a(["F","f","5",92]),$a(["G","g","6","]"]),$a(["H","h","7","_"]),$a(["I","i","8","`"]),$a(["J","j","9","~"]),$a(["K","k","&","!"]),$a(["L","l",13,13]),$a(["M","m",9,9]),$a(["N","n",",",","]),$a(["O","o",":",":"]),$a(["P","p","#",10]),$a(["Q","q","-","-"]),$a(["R","r",".","."]),$a(["S","s","$","$"]),$a(["T","t","/","/"]),$a(["U","u","+",'"']),$a(["V","v","%","|"]),$a(["W","w","*","*"]),$a(["X","x","=",40]),$a(["Y","y","^",41]),$a(["Z","z",$1.pl,"?"]),$a([" "," "," ","{"]),$a([$1.ll,$1.as,$1.ll,"}"]),$a([$1.ml,$1.ml,$1.al,"'"]),$a([$1.ps,$1.ps,$1.ps,$1.al])]);$1.charvals=$a([new Map,new Map,new Map,new Map]);$1.alltext=new Map;for(var _2v=0,_2u=$1.charmaps.length-1;_2v<=_2u;_2v+=1){$1.i=_2v;$1.encs=$g($1.charmaps,$1.i);for(var _2z=0;_2z<=3;_2z+=1){$1.j=_2z;var _32=$g($1.encs,$1.j);$k[$j++]=_32;if($eq($t(_32),"stringtype")){var _35=$g($k[--$j],0);$k[$j++]=_35}var _36=$k[--$j];$p($g($1.charvals,$1.j),_36,$1.i);$p($1.alltext,_36,-1)}}$1.e=1e4;$1.latlen=$a([$a([0,1,1,2]),$a([2,0,1,2]),$a([1,1,0,1]),$a([1,2,2,0])]);$1.latseq=$a([$a([$a([]),$a([$1.ll]),$a([$1.ml]),$a([$1.ml,$1.pl])]),$a([$a([$1.ml,$1.al]),$a([]),$a([$1.ml]),$a([$1.ml,$1.pl])]),$a([$a([$1.al]),$a([$1.ll]),$a([]),$a([$1.pl])]),$a([$a([$1.al]),$a([$1.al,$1.ll]),$a([$1.al,$1.ml]),$a([])])]);$1.shftlen=$a([$a([$1.e,$1.e,$1.e,1]),$a([1,$1.e,$1.e,1]),$a([$1.e,$1.e,$1.e,1]),$a([$1.e,$1.e,$1.e,$1.e])]);$k[$j++]=Infinity;for(var _4B=0,_4C=$1.msglen;_4B<_4C;_4B++){$k[$j++]=0}$k[$j++]=0;$1.numdigits=$a();$k[$j++]=Infinity;for(var _4F=0,_4G=$1.msglen;_4F<_4G;_4F++){$k[$j++]=0}$k[$j++]=0;$1.numtext=$a();$k[$j++]=Infinity;for(var _4J=0,_4K=$1.msglen;_4J<_4K;_4J++){$k[$j++]=0}$k[$j++]=0;$1.numbytes=$a();$k[$j++]=Infinity;for(var _4N=0,_4O=$1.msglen;_4N<_4O;_4N++){$k[$j++]=0}$k[$j++]=0;$1.iseci=$a();for(var _4R=$1.msglen-1;_4R>=0;_4R-=1){$1.i=_4R;var _4U=$g($1.msg,$1.i);if(_4U>=48&&_4U<=57){$p($1.numdigits,$1.i,$f($g($1.numdigits,$1.i+1)+1))}var _4e=$g($1.alltext,$g($1.msg,$1.i))!==undefined;if(_4e&&$g($1.numdigits,$1.i)<13){$p($1.numtext,$1.i,$f($g($1.numtext,$1.i+1)+1))}if($g($1.msg,$1.i)>=0&&$g($1.numtext,$1.i)<5&&$g($1.numdigits,$1.i)<13){$p($1.numbytes,$1.i,$f($g($1.numbytes,$1.i+1)+1))}$p($1.iseci,$1.i,$g($1.msg,$1.i)<=-1e6)}$1.numdigits=$G($1.numdigits,0,$1.msglen);$1.numtext=$G($1.numtext,0,$1.msglen);$1.numbytes=$G($1.numbytes,0,$1.msglen);$1.seq=$a([]);$1.seqlen=0;$1.state=$1.T;$1.p=0;for(;;){if($1.p==$1.msglen){break}if($g($1.iseci,$1.p)){$1.eci=$g($1.msg,$1.p);$k[$j++]=Infinity;$q($1.seq);$k[$j++]=$a([$1.eci]);$1.seq=$a();$1.p=$1.p+1;$k[$j++]="seqlen";$k[$j++]=$1.seqlen;if($1.eci<=-1810900){$k[$j++]=2}else{var _5X=$1.eci<=-1000900?3:2;$k[$j++]=_5X}var _5Y=$k[--$j];var _5Z=$k[--$j];$1[$k[--$j]]=$f(_5Z+_5Y)}else{$1.n=$g($1.numdigits,$1.p);if($1.n>=13){$k[$j++]=Infinity;$q($1.seq);$k[$j++]=$1.nl;$k[$j++]=Infinity;$q($G($1.msg,$1.p,$1.n));var _5l=$a();$k[$j++]=_5l;$1.seq=$a();$1.state=$1.N;$1.p=$f($1.p+$1.n);$1.seqlen=$f($1.seqlen+1+$1.n)}else{$1.t=$g($1.numtext,$1.p);if($1.t>=5){$k[$j++]=Infinity;$q($1.seq);if($1.state!=$1.T){$k[$j++]=$1.tl}$k[$j++]=Infinity;$q($G($1.msg,$1.p,$1.t));var _64=$a();$k[$j++]=_64;$1.seq=$a();$1.state=$1.T;$1.p=$f($1.p+$1.t);$1.seqlen=$f($f($1.seqlen+1)+$1.t)}else{$1.b=$g($1.numbytes,$1.p);if($1.b==1&&$1.state==$1.T){$k[$j++]=Infinity;$q($1.seq);$k[$j++]=$1.bs;$k[$j++]=$a([$g($1.msg,$1.p)]);$1.seq=$a();$1.p=$f($1.p+$1.b);$1.seqlen=$f($1.seqlen+2)}else{$k[$j++]=Infinity;$q($1.seq);var _6T=$1.b%6!=0?$1.bl:$1.bl6;$k[$j++]=_6T;$k[$j++]=Infinity;$q($G($1.msg,$1.p,$1.b));var _6Y=$a();$k[$j++]=_6Y;$1.seq=$a();$1.state=$1.B;$1.p=$f($1.p+$1.b);$1.seqlen=$f($f($1.seqlen+1)+$1.b)}}}}}$k[$j++]=Infinity;$k[$j++]=$1.tl;$k[$j++]=900;$k[$j++]=$1.bl;$k[$j++]=901;$k[$j++]=$1.bl6;$k[$j++]=924;$k[$j++]=$1.nl;$k[$j++]=902;$k[$j++]=$1.bs;$k[$j++]=913;$1.latchcws=$d();$1.enca=function(){var _6p=$g($g($1.charvals,$1.A),$k[--$j]);$k[$j++]=_6p};$1.encl=function(){var _6u=$g($g($1.charvals,$1.L),$k[--$j]);$k[$j++]=_6u};$1.encm=function(){var _6z=$g($g($1.charvals,$1.M),$k[--$j]);$k[$j++]=_6z};$1.encp=function(){var _74=$g($g($1.charvals,$1.P),$k[--$j]);$k[$j++]=_74};$1.textencfuncs=$a(["enca","encl","encm","encp"]);$1.addtotext=function(){$p($1.text,$1.l,$k[--$j]);$1.l=$1.l+1};$1.enct=function(){$1.in=$k[--$j];$1.curlen=$a([$1.e,$1.e,$1.e,$1.e]);$p($1.curlen,$1.submode,0);$1.curseq=$a([$a([]),$a([]),$a([]),$a([])]);$F($1.in,function(){$1.char=$k[--$j];for(;;){$1.imp=false;var _7T=$a([$1.A,$1.L,$1.M,$1.P]);for(var _7U=0,_7V=_7T.length;_7U<_7V;_7U++){$1.x=$g(_7T,_7U);var _7b=$a([$1.A,$1.L,$1.M,$1.P]);for(var _7c=0,_7d=_7b.length;_7c<_7d;_7c++){$1.y=$g(_7b,_7c);$1.cost=$f($g($1.curlen,$1.x)+$g($g($1.latlen,$1.x),$1.y));if($1.cost<$g($1.curlen,$1.y)){$p($1.curlen,$1.y,$1.cost);$k[$j++]=$1.curseq;$k[$j++]=$1.y;$k[$j++]=Infinity;$q($g($1.curseq,$1.x));$q($g($g($1.latseq,$1.x),$1.y));var _84=$a();var _85=$k[--$j];$p($k[--$j],_85,_84);$1.imp=true}}}if(!$1.imp){break}}$1.nxtlen=$a([$1.e,$1.e,$1.e,$1.e]);$1.nxtseq=$a(4);var _8I=$a([$1.A,$1.L,$1.M,$1.P]);for(var _8J=0,_8K=_8I.length;_8J<_8K;_8J++){$1.x=$g(_8I,_8J);for(;;){var _8Q=$g($g($1.charvals,$1.x),$1.char)!==undefined;if(!_8Q){break}$1.cost=$f($g($1.curlen,$1.x)+1);if($1.cost<$g($1.nxtlen,$1.x)){$p($1.nxtlen,$1.x,$1.cost);$k[$j++]=$1.nxtseq;$k[$j++]=$1.x;$k[$j++]=Infinity;$q($g($1.curseq,$1.x));$k[$j++]=$1.char;var _8h=$a();var _8i=$k[--$j];$p($k[--$j],_8i,_8h)}var _8o=$a([$1.A,$1.L,$1.M,$1.P]);for(var _8p=0,_8q=_8o.length;_8p<_8q;_8p++){$1.y=$g(_8o,_8p);if($ne($1.x,$1.y)){$1.cost=$f($f($g($1.curlen,$1.y)+$g($g($1.shftlen,$1.y),$1.x))+1);if($1.cost<$g($1.nxtlen,$1.y)){$p($1.nxtlen,$1.y,$1.cost);$k[$j++]=$1.nxtseq;$k[$j++]=$1.y;$k[$j++]=Infinity;$q($g($1.curseq,$1.y));var _9G=$1.x==$1.A?$1.as:$1.ps;$k[$j++]=_9G;$k[$j++]=$1.char;var _9I=$a();var _9J=$k[--$j];$p($k[--$j],_9J,_9I)}}}break}}$1.curlen=$1.nxtlen;$1.curseq=$1.nxtseq});$1.minseq=$1.e;var _9S=$a([$1.A,$1.L,$1.M,$1.P]);for(var _9T=0,_9U=_9S.length;_9T<_9U;_9T++){$1.k=$g(_9S,_9T);if($g($1.curlen,$1.k)<$1.minseq){$1.minseq=$g($1.curlen,$1.k);$1.txtseq=$g($1.curseq,$1.k)}}$1.text=$a($1.minseq);$1.k=0;$1.l=0;for(;;){if($1.k>=$1.txtseq.length){break}$1.char=$g($1.txtseq,$1.k);$k[$j++]=$1.char;if($1[$g($1.textencfuncs,$1.submode)]()===true){break}$1.addtotext();$1.k=$1.k+1;if($1.char==$1.as||$1.char==$1.ps){$k[$j++]=$g($1.txtseq,$1.k);if($1.char==$1.as){$1.enca()}else{$1.encp()}$1.addtotext();$1.k=$1.k+1}if($1.char==$1.al){$1.submode=$1.A}if($1.char==$1.ll){$1.submode=$1.L}if($1.char==$1.ml){$1.submode=$1.M}if($1.char==$1.pl){$1.submode=$1.P}}if($1.text.length%2==1){if($1.submode==$1.P){$k[$j++]="pad";$k[$j++]=$1.al;$1.encp();var _AJ=$k[--$j];$1[$k[--$j]]=_AJ;$1.submode=$1.A}else{$k[$j++]="pad";$k[$j++]=$1.ps;if($1[$g($1.textencfuncs,$1.submode)]()===true){return true}var _AR=$k[--$j];$1[$k[--$j]]=_AR}$k[$j++]=Infinity;$q($1.text);$k[$j++]=$1.pad;$1.text=$a()}$1.out=$a(~~($1.text.length/2));for(var _Aa=0,_AZ=$1.out.length-1;_Aa<=_AZ;_Aa+=1){$1.k=_Aa;$p($1.out,$1.k,$f($g($1.text,$1.k*2)*30+$g($1.text,$1.k*2+1)))}$k[$j++]=$1.out};$1.encn=function(){$1.in=$k[--$j];$1.out=$a([]);for(var _Ao=0,_An=$1.in.length-1;_Ao<=_An;_Ao+=44){$1.k=_Ao;$k[$j++]=Infinity;var _At=$1.in.length-$1.k;$k[$j++]=1;$k[$j++]=$1.in;$k[$j++]=$1.k;$k[$j++]=_At;if(_At>44){$j--;$k[$j++]=44}var _Au=$k[--$j];var _Av=$k[--$j];var _Ax=$G($k[--$j],_Av,_Au);for(var _Ay=0,_Az=_Ax.length;_Ay<_Az;_Ay++){$k[$j++]=$f($g(_Ax,_Ay)-48)}$1.gmod=$a();$1.cwn=$a([]);for(;;){$1.dv=900;$1.gmul=$a([]);$1.val=0;for(;;){if($1.gmod.length==0){break}$1.val=$f($1.val*10+$g($1.gmod,0));$1.gmod=$G($1.gmod,1,$1.gmod.length-1);if($1.val<$1.dv){if($1.gmul.length!=0){$k[$j++]=Infinity;$q($1.gmul);$k[$j++]=0;$1.gmul=$a()}}else{$k[$j++]=Infinity;$q($1.gmul);$k[$j++]=~~($1.val/$1.dv);$1.gmul=$a()}$1.val=$1.val%$1.dv}$1.dv=$1.val;$k[$j++]=Infinity;$k[$j++]=$1.dv;$q($1.cwn);$1.cwn=$a();$1.gmod=$1.gmul;if($1.gmul.length==0){break}}$k[$j++]=Infinity;$q($1.out);$q($1.cwn);$1.out=$a()}$k[$j++]=$1.out};$1.ence=function(){var _BY=$f(-$g($k[--$j],0)-1e6);$k[$j++]=_BY;if(_BY<=899){var _BZ=$k[--$j];$k[$j++]=927;$k[$j++]=_BZ;$r($a(2))}else{var _Bb=$k[--$j];$k[$j++]=_Bb;if(_Bb<=810899){var _Bc=$k[--$j];$k[$j++]=926;$k[$j++]=~~(_Bc/900)-1;$k[$j++]=_Bc%900;$r($a(3))}else{var _Be=$k[--$j];$k[$j++]=_Be;if(_Be<=811799){var _Bf=$k[--$j];$k[$j++]=925;$k[$j++]=$f(_Bf-810900);$r($a(2))}else{$k[$j++]="bwipp.pdf417badECI";$k[$j++]="PDF417 supports ECIs 000000 to 811799";bwipp_raiseerror()}}}};$1.encfuncs=$a(["enct","encn","encb"]);$1.addtocws=function(){var _Bi=$k[--$j];$P($1.datcws,$1.j,_Bi);$1.j=_Bi.length+$1.j};$1.state=$1.T;$1.submode=$1.A;$1.datcws=$a($1.seqlen);$1.i=0;$1.j=0;for(;;){if($1.i>=$1.seq.length){break}$1.chars=$g($1.seq,$1.i);if($eq($t($1.chars),"arraytype")){if($g($1.chars,0)<=-1e6){$k[$j++]=$1.chars;$1.ence();$1.addtocws()}else{$k[$j++]=$1.chars;if($1[$g($1.encfuncs,$1.state)]()===true){break}$1.addtocws()}}else{$k[$j++]=$a([$g($1.latchcws,$1.chars)]);$1.addtocws();if($1.chars==$1.tl){$1.state=$1.T;$1.submode=$1.A}if($1.chars==$1.nl){$1.state=$1.N}if($1.chars==$1.bl||$1.chars==$1.bl6){$1.state=$1.B}if($1.chars==$1.bs){$1.i=$1.i+1;$k[$j++]=$g($1.seq,$1.i);$1.encb();$1.addtocws()}}$1.i=$1.i+1}$1.datcws=$G($1.datcws,0,$1.j)}$1.m=$1.datcws.length;if($1.eclevel==-1){if($1.m<=40){$1.eclevel=2}if($1.m>=41&&$1.m<=160){$1.eclevel=3}if($1.m>=161&&$1.m<=320){$1.eclevel=4}if($1.m>=321){$1.eclevel=5}}$1.maxeclevel=~~(Math.log(928-1-$1.m)/Math.log(2))-1;if($1.eclevel>$1.maxeclevel){$1.eclevel=$1.maxeclevel}$1.k=~~Math.pow(2,$1.eclevel+1);if($1.columns==0){$1.columns=~~Math.round(Math.sqrt(($1.m+$1.k)/3))}$k[$j++]="c";if($1.columns>=1){$k[$j++]=$1.columns}else{$k[$j++]=1}var _Cn=$k[--$j];$1[$k[--$j]]=_Cn;$1.r=~~Math.ceil(($1.m+$1.k+1)/$1.columns);if($1.r<$1.rows&&$1.rows<=90){$1.r=$1.rows}if($1.r<3){$1.r=3}var _Cx=$1.c;var _Cy=$1.r;var _Cz=$1.m;var _D0=8;var _D1=~~(Math.log($f($f(_Cx*_Cy-1)-_Cz))/Math.log(2))-1;if(~~(Math.log($f($f(_Cx*_Cy-1)-_Cz))/Math.log(2))-1>8){var _=_D0;_D0=_D1;_D1=_}$1.maxeclevel=_D1;if($1.maxeclevel>$1.eclevel){$1.eclevel=$1.maxeclevel;$1.k=~~Math.pow(2,$1.eclevel+1)}$1.n=$f($1.c*$1.r-$1.k);$1.cws=$a($f($1.c*$1.r+1));$p($1.cws,0,$1.n);$P($1.cws,1,$1.datcws);$k[$j++]=$1.cws;$k[$j++]=$1.m+1;$k[$j++]=Infinity;for(var _DK=0,_DL=$f($f($1.n-$1.m)-1);_DK<_DL;_DK++){$k[$j++]=900}var _DM=$a();var _DN=$k[--$j];$P($k[--$j],_DN,_DM);$k[$j++]=$1.cws;$k[$j++]=$1.n;$k[$j++]=Infinity;for(var _DS=0,_DT=$1.k;_DS<_DT;_DS++){$k[$j++]=0}$k[$j++]=0;var _DU=$a();var _DV=$k[--$j];$P($k[--$j],_DV,_DU);$k[$j++]=Infinity;$k[$j++]=1;for(var _DX=0,_DY=928;_DX<_DY;_DX++){var _DZ=$k[--$j];$k[$j++]=_DZ;$k[$j++]=_DZ*3%929}$1.rsalog=$a();$1.rslog=$a(929);for(var _Dc=1;_Dc<=928;_Dc+=1){$p($1.rslog,$g($1.rsalog,_Dc),_Dc)}$1.rsprod=function(){var _Dg=$k[--$j];var _Dh=$k[--$j];$k[$j++]=_Dh;$k[$j++]=_Dg;if(_Dg!=0&&_Dh!=0){var _Dk=$g($1.rslog,$k[--$j]);var _Dp=$g($1.rsalog,$f(_Dk+$g($1.rslog,$k[--$j]))%928);$k[$j++]=_Dp}else{$j-=2;$k[$j++]=0}};$k[$j++]=Infinity;$k[$j++]=1;for(var _Dr=0,_Ds=$1.k;_Dr<_Ds;_Dr++){$k[$j++]=0}$1.coeffs=$a();for(var _Dw=1,_Dv=$1.k;_Dw<=_Dv;_Dw+=1){$1.i=_Dw;$p($1.coeffs,$1.i,$g($1.coeffs,$1.i-1));for(var _E3=$1.i-1;_E3>=1;_E3-=1){$1.j=_E3;$k[$j++]=$1.coeffs;$k[$j++]=$1.j;$k[$j++]=$g($1.coeffs,$1.j-1);$k[$j++]=$g($1.coeffs,$1.j);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _EF=$k[--$j];var _EG=$k[--$j];var _EH=$k[--$j];$p($k[--$j],_EH,$f(_EG+_EF)%929)}$k[$j++]=$1.coeffs;$k[$j++]=0;$k[$j++]=$g($1.coeffs,0);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _EP=$k[--$j];var _EQ=$k[--$j];$p($k[--$j],_EQ,_EP)}$1.coeffs=$G($1.coeffs,0,$1.coeffs.length-1);for(var _EW=$1.coeffs.length-1;_EW>=0;_EW-=2){var _EX=$1.coeffs;$p(_EX,_EW,$f(929-$g(_EX,_EW)))}for(var _Eb=0,_Ea=$f($1.n-1);_Eb<=_Ea;_Eb+=1){$1.t=$f($g($1.cws,_Eb)+$g($1.cws,$1.n))%929;for(var _Ej=0,_Ei=$1.k-1;_Ej<=_Ei;_Ej+=1){$1.j=_Ej;$p($1.cws,$f($1.n+$1.j),$f($g($1.cws,$f($f($1.n+$1.j)+1))+$f(929-$1.t*$g($1.coeffs,$1.k-$1.j-1)%929))%929)}}for(var _F0=$1.n,_Ez=$f($1.n+$1.k);_F0<=_Ez;_F0+=1){$p($1.cws,_F0,$f(929-$g($1.cws,_F0))%929)}$1.cws=$G($1.cws,0,$1.cws.length-1);$1.clusters=$a([$a([120256,125680,128380,120032,125560,128318,108736,119920,108640,86080,108592,86048,110016,120560,125820,109792,120440,125758,88256,109680,88160,89536,110320,120700,89312,110200,120638,89200,110140,89840,110460,89720,110398,89980,128506,119520,125304,128190,107712,119408,125244,107616,119352,84032,107568,119324,84e3,107544,83984,108256,119672,125374,85184,108144,119612,85088,108088,119582,85040,108060,85728,108408,119742,85616,108348,85560,108318,85880,108478,85820,85790,107200,119152,125116,107104,119096,125086,83008,107056,119068,82976,107032,82960,82952,83648,107376,119228,83552,107320,119198,83504,107292,83480,83468,83824,107452,83768,107422,83740,83900,106848,118968,125022,82496,106800,118940,82464,106776,118926,82448,106764,82440,106758,82784,106936,119006,82736,106908,82712,106894,82700,82694,106974,82830,82240,106672,118876,82208,106648,118862,82192,106636,82184,106630,82180,82352,82328,82316,82080,118830,106572,106566,82050,117472,124280,127678,103616,117360,124220,103520,117304,124190,75840,103472,75808,104160,117624,124350,76992,104048,117564,76896,103992,76848,76824,77536,104312,117694,77424,104252,77368,77340,77688,104382,77628,77758,121536,126320,128700,121440,126264,128670,111680,121392,126236,111648,121368,126222,111632,121356,103104,117104,124092,112320,103008,117048,124062,112224,121656,126366,93248,74784,102936,117006,93216,112152,93200,75456,103280,117180,93888,75360,103224,117150,93792,112440,121758,93744,75288,93720,75632,103356,94064,75576,103326,94008,112542,93980,75708,94140,75678,94110,121184,126136,128606,111168,121136,126108,111136,121112,126094,111120,121100,111112,111108,102752,116920,123998,111456,102704,116892,91712,74272,121244,116878,91680,74256,102668,91664,111372,102662,74244,74592,102840,116958,92e3,74544,102812,91952,111516,102798,91928,74508,74502,74680,102878,92088,74652,92060,74638,92046,92126,110912,121008,126044,110880,120984,126030,110864,120972,110856,120966,110852,110850,74048,102576,116828,90944,74016,102552,116814,90912,111e3,121038,90896,73992,102534,90888,110982,90884,74160,102620,91056,74136,102606,91032,111054,91020,74118,91014,91100,91086,110752,120920,125998,110736,120908,110728,120902,110724,110722,73888,102488,116782,90528,73872,102476,90512,110796,102470,90504,73860,90500,73858,73944,90584,90572,90566,120876,120870,110658,102444,73800,90312,90308,90306,101056,116080,123580,100960,116024,70720,100912,115996,70688,100888,70672,70664,71360,101232,116156,71264,101176,116126,71216,101148,71192,71180,71536,101308,71480,101278,71452,71612,71582,118112,124600,127838,105024,118064,124572,104992,118040,124558,104976,118028,104968,118022,100704,115896,123486,105312,100656,115868,79424,70176,118172,115854,79392,105240,100620,79376,70152,79368,70496,100792,115934,79712,70448,118238,79664,105372,100750,79640,70412,79628,70584,100830,79800,70556,79772,70542,70622,79838,122176,126640,128860,122144,126616,128846,122128,126604,122120,126598,122116,104768,117936,124508,113472,104736,126684,124494,113440,122264,126670,113424,104712,117894,113416,122246,104706,69952,100528,115804,78656,69920,100504,115790,96064,78624,104856,117966,96032,113560,122318,100486,96016,78600,104838,96008,69890,70064,100572,78768,70040,100558,96176,78744,104910,96152,113614,70022,78726,70108,78812,70094,96220,78798,122016,126552,128814,122e3,126540,121992,126534,121988,121986,104608,117848,124462,113056,104592,126574,113040,122060,117830,113032,104580,113028,104578,113026,69792,100440,115758,78240,69776,100428,95136,78224,104652,100422,95120,113100,69764,95112,78212,69762,78210,69848,100462,78296,69836,95192,78284,69830,95180,78278,69870,95214,121936,126508,121928,126502,121924,121922,104528,117804,112848,104520,117798,112840,121958,112836,104514,112834,69712,100396,78032,69704,100390,94672,78024,104550,94664,112870,69698,94660,78018,94658,78060,94700,94694,126486,121890,117782,104484,104482,69672,77928,94440,69666,77922,99680,68160,99632,68128,99608,115342,68112,99596,68104,99590,68448,99768,115422,68400,99740,68376,99726,68364,68358,68536,99806,68508,68494,68574,101696,116400,123740,101664,116376,101648,116364,101640,116358,101636,67904,99504,115292,72512,67872,116444,115278,72480,101784,116430,72464,67848,99462,72456,101766,67842,68016,99548,72624,67992,99534,72600,101838,72588,67974,68060,72668,68046,72654,118432,124760,127918,118416,124748,118408,124742,118404,118402,101536,116312,105888,101520,116300,105872,118476,116294,105864,101508,105860,101506,105858,67744,99416,72096,67728,116334,80800,72080,101580,99398,80784,105932,67716,80776,72068,67714,72066,67800,99438,72152,67788,80856,72140,67782,80844,72134,67822,72174,80878,126800,128940,126792,128934,126788,126786,118352,124716,122576,126828,124710,122568,126822,122564,118338,122562,101456,116268,105680,101448,116262,114128,105672,118374,114120,122598,101442,114116,105666,114114,67664,99372,71888,67656,99366,80336,71880,101478,97232,80328,105702,67650,97224,114150,71874,97220,67692,71916,67686,80364,71910,97260,80358,97254,126760,128918,126756,126754,118312,124694,122472,126774,122468,118306,122466,101416,116246,105576,101412,113896,105572,101410,113892,105570,113890,67624,99350,71784,101430,80104,71780,67618,96744,80100,71778,96740,80098,96738,71798,96758,126738,122420,122418,105524,113780,113778,71732,79988,96500,96498,66880,66848,98968,66832,66824,66820,66992,66968,66956,66950,67036,67022,1e5,99984,115532,99976,115526,99972,99970,66720,98904,69024,100056,98892,69008,100044,69e3,100038,68996,66690,68994,66776,98926,69080,100078,69068,66758,69062,66798,69102,116560,116552,116548,116546,99920,102096,116588,115494,102088,116582,102084,99906,102082,66640,68816,66632,98854,73168,68808,66628,73160,68804,66626,73156,68802,66668,68844,66662,73196,68838,73190,124840,124836,124834,116520,118632,124854,118628,116514,118626,99880,115478,101992,116534,106216,101988,99874,106212,101986,106210,66600,98838,68712,99894,72936,68708,66594,81384,72932,68706,81380,72930,66614,68726,72950,81398,128980,128978,124820,126900,124818,126898,116500,118580,116498,122740,118578,122738,99860,101940,99858,106100,101938,114420]),$a([128352,129720,125504,128304,129692,125472,128280,129678,125456,128268,125448,128262,125444,125792,128440,129758,120384,125744,128412,120352,125720,128398,120336,125708,120328,125702,120324,120672,125880,128478,110144,120624,125852,110112,120600,125838,110096,120588,110088,120582,110084,110432,120760,125918,89664,110384,120732,89632,110360,120718,89616,110348,89608,110342,89952,110520,120798,89904,110492,89880,110478,89868,90040,110558,90012,89998,125248,128176,129628,125216,128152,129614,125200,128140,125192,128134,125188,125186,119616,125360,128220,119584,125336,128206,119568,125324,119560,125318,119556,119554,108352,119728,125404,108320,119704,125390,108304,119692,108296,119686,108292,108290,85824,108464,119772,85792,108440,119758,85776,108428,85768,108422,85764,85936,108508,85912,108494,85900,85894,85980,85966,125088,128088,129582,125072,128076,125064,128070,125060,125058,119200,125144,128110,119184,125132,119176,125126,119172,119170,107424,119256,125166,107408,119244,107400,119238,107396,107394,83872,107480,119278,83856,107468,83848,107462,83844,83842,83928,107502,83916,83910,83950,125008,128044,125e3,128038,124996,124994,118992,125036,118984,125030,118980,118978,106960,119020,106952,119014,106948,106946,82896,106988,82888,106982,82884,82882,82924,82918,124968,128022,124964,124962,118888,124982,118884,118882,106728,118902,106724,106722,82408,106742,82404,82402,124948,124946,118836,118834,106612,106610,124224,127664,129372,124192,127640,129358,124176,127628,124168,127622,124164,124162,117568,124336,127708,117536,124312,127694,117520,124300,117512,124294,117508,117506,104256,117680,124380,104224,117656,124366,104208,117644,104200,117638,104196,104194,77632,104368,117724,77600,104344,117710,77584,104332,77576,104326,77572,77744,104412,77720,104398,77708,77702,77788,77774,128672,129880,93168,128656,129868,92664,128648,129862,92412,128644,128642,124064,127576,129326,126368,124048,129902,126352,128716,127558,126344,124036,126340,124034,126338,117152,124120,127598,121760,117136,124108,121744,126412,124102,121736,117124,121732,117122,121730,103328,117208,124142,112544,103312,117196,112528,121804,117190,112520,103300,112516,103298,112514,75680,103384,117230,94112,75664,103372,94096,112588,103366,94088,75652,94084,75650,75736,103406,94168,75724,94156,75718,94150,75758,128592,129836,91640,128584,129830,91388,128580,91262,128578,123984,127532,126160,123976,127526,126152,128614,126148,123970,126146,116944,124012,121296,116936,124006,121288,126182,121284,116930,121282,102864,116972,111568,102856,116966,111560,121318,111556,102850,111554,74704,102892,92112,74696,102886,92104,111590,92100,74690,92098,74732,92140,74726,92134,128552,129814,90876,128548,90750,128546,123944,127510,126056,128566,126052,123938,126050,116840,123958,121064,116836,121060,116834,121058,102632,116854,111080,121078,111076,102626,111074,74216,102646,91112,74212,91108,74210,91106,74230,91126,128532,90494,128530,123924,126004,123922,126002,116788,120948,116786,120946,102516,110836,102514,110834,73972,90612,73970,90610,128522,123914,125978,116762,120890,102458,110714,123552,127320,129198,123536,127308,123528,127302,123524,123522,116128,123608,127342,116112,123596,116104,123590,116100,116098,101280,116184,123630,101264,116172,101256,116166,101252,101250,71584,101336,116206,71568,101324,71560,101318,71556,71554,71640,101358,71628,71622,71662,127824,129452,79352,127816,129446,79100,127812,78974,127810,123472,127276,124624,123464,127270,124616,127846,124612,123458,124610,115920,123500,118224,115912,123494,118216,124646,118212,115906,118210,100816,115948,105424,100808,115942,105416,118246,105412,100802,105410,70608,100844,79824,70600,100838,79816,105446,79812,70594,79810,70636,79852,70630,79846,129960,95728,113404,129956,95480,113278,129954,95356,95294,127784,129430,78588,128872,129974,95996,78462,128868,127778,95870,128866,123432,127254,124520,123428,126696,128886,123426,126692,124514,126690,115816,123446,117992,115812,122344,117988,115810,122340,117986,122338,100584,115830,104936,100580,113640,104932,100578,113636,104930,113634,70120,100598,78824,70116,96232,78820,70114,96228,78818,96226,70134,78838,129940,94968,113022,129938,94844,94782,127764,78206,128820,127762,95102,128818,123412,124468,123410,126580,124466,126578,115764,117876,115762,122100,117874,122098,100468,104692,100466,113140,104690,113138,69876,78324,69874,95220,78322,95218,129930,94588,94526,127754,128794,123402,124442,126522,115738,117818,121978,100410,104570,112890,69754,78074,94714,94398,123216,127148,123208,127142,123204,123202,115408,123244,115400,123238,115396,115394,99792,115436,99784,115430,99780,99778,68560,99820,68552,99814,68548,68546,68588,68582,127400,129238,72444,127396,72318,127394,123176,127126,123752,123172,123748,123170,123746,115304,123190,116456,115300,116452,115298,116450,99560,115318,101864,99556,101860,99554,101858,68072,99574,72680,68068,72676,68066,72674,68086,72694,129492,80632,105854,129490,80508,80446,127380,72062,127924,127378,80766,127922,123156,123700,123154,124788,123698,124786,115252,116340,115250,118516,116338,118514,99444,101620,99442,105972,101618,105970,67828,72180,67826,80884,72178,80882,97008,114044,96888,113982,96828,96798,129482,80252,130010,97148,80190,97086,127370,127898,128954,123146,123674,124730,126842,115226,116282,118394,122618,99386,101498,105722,114170,67706,71930,80378,96632,113854,96572,96542,80062,96702,96444,96414,96350,123048,123044,123042,115048,123062,115044,115042,99048,115062,99044,99042,67048,99062,67044,67042,67062,127188,68990,127186,123028,123316,123026,123314,114996,115572,114994,115570,98932,100084,98930,100082,66804,69108,66802,69106,129258,73084,73022,127178,127450,123018,123290,123834,114970,115514,116602,98874,99962,102138,66682,68858,73210,81272,106174,81212,81182,72894,81342,97648,114364,97592,114334,97564,97550,81084,97724,81054,97694,97464,114270,97436,97422,80990,97502,97372,97358,97326,114868,114866,98676,98674,66292,66290,123098,114842,115130,98618,99194,66170,67322,69310,73404,73374,81592,106334,81564,81550,73310,81630,97968,114524,97944,114510,97932,97926,81500,98012,81486,97998,97880,114478,97868,97862,81454,97902,97836,97830,69470,73564,73550,81752,106414,81740,81734,73518,81774,81708,81702]),$a([109536,120312,86976,109040,120060,86496,108792,119934,86256,108668,86136,129744,89056,110072,129736,88560,109820,129732,88312,109694,129730,88188,128464,129772,89592,128456,129766,89340,128452,89214,128450,125904,128492,125896,128486,125892,125890,120784,125932,120776,125926,120772,120770,110544,120812,110536,120806,110532,84928,108016,119548,84448,107768,119422,84208,107644,84088,107582,84028,129640,85488,108284,129636,85240,108158,129634,85116,85054,128232,129654,85756,128228,85630,128226,125416,128246,125412,125410,119784,125430,119780,119778,108520,119798,108516,108514,83424,107256,119166,83184,107132,83064,107070,83004,82974,129588,83704,107390,129586,83580,83518,128116,83838,128114,125172,125170,119284,119282,107508,107506,82672,106876,82552,106814,82492,82462,129562,82812,82750,128058,125050,119034,82296,106686,82236,82206,82366,82108,82078,76736,103920,117500,76256,103672,117374,76016,103548,75896,103486,75836,129384,77296,104188,129380,77048,104062,129378,76924,76862,127720,129398,77564,127716,77438,127714,124392,127734,124388,124386,117736,124406,117732,117730,104424,117750,104420,104418,112096,121592,126334,92608,111856,121468,92384,111736,121406,92272,111676,92216,111646,92188,75232,103160,117118,93664,74992,103036,93424,112252,102974,93304,74812,93244,74782,93214,129332,75512,103294,129908,129330,93944,75388,129906,93820,75326,93758,127604,75646,128756,127602,94078,128754,124148,126452,124146,126450,117236,121844,117234,121842,103412,103410,91584,111344,121212,91360,111224,121150,91248,111164,91192,111134,91164,91150,74480,102780,91888,74360,102718,91768,111422,91708,74270,91678,129306,74620,129850,92028,74558,91966,127546,128634,124026,126202,116986,121338,102906,90848,110968,121022,90736,110908,90680,110878,90652,90638,74104,102590,91e3,74044,90940,74014,90910,74174,91070,90480,110780,90424,110750,90396,90382,73916,90556,73886,90526,90296,110686,90268,90254,73822,90334,90204,90190,71136,101112,116094,70896,100988,70776,100926,70716,70686,129204,71416,101246,129202,71292,71230,127348,71550,127346,123636,123634,116212,116210,101364,101362,79296,105200,118140,79072,105080,118078,78960,105020,78904,104990,78876,78862,70384,100732,79600,70264,100670,79480,105278,79420,70174,79390,129178,70524,129466,79740,70462,79678,127290,127866,123514,124666,115962,118266,100858,113376,122232,126654,95424,113264,122172,95328,113208,122142,95280,113180,95256,113166,95244,78560,104824,117950,95968,78448,104764,95856,113468,104734,95800,78364,95772,78350,95758,70008,100542,78712,69948,96120,78652,69918,96060,78622,96030,70078,78782,96190,94912,113008,122044,94816,112952,122014,94768,112924,94744,112910,94732,94726,78192,104636,95088,78136,104606,95032,113054,95004,78094,94990,69820,78268,69790,95164,78238,95134,94560,112824,121950,94512,112796,94488,112782,94476,94470,78008,104542,94648,77980,94620,77966,94606,69726,78046,94686,94384,112732,94360,112718,94348,94342,77916,94428,77902,94414,94296,112686,94284,94278,77870,94318,94252,94246,68336,99708,68216,99646,68156,68126,68476,68414,127162,123258,115450,99834,72416,101752,116414,72304,101692,72248,101662,72220,72206,67960,99518,72568,67900,72508,67870,72478,68030,72638,80576,105840,118460,80480,105784,118430,80432,105756,80408,105742,80396,80390,72048,101564,80752,71992,101534,80696,71964,80668,71950,80654,67772,72124,67742,80828,72094,80798,114016,122552,126814,96832,113968,122524,96800,113944,122510,96784,113932,96776,113926,96772,80224,105656,118366,97120,80176,105628,97072,114076,105614,97048,80140,97036,80134,97030,71864,101470,80312,71836,97208,80284,71822,97180,80270,97166,67678,71902,80350,97246,96576,113840,122460,96544,113816,122446,96528,113804,96520,113798,96516,96514,80048,105564,96688,80024,105550,96664,113870,96652,80006,96646,71772,80092,71758,96732,80078,96718,96416,113752,122414,96400,113740,96392,113734,96388,96386,79960,105518,96472,79948,96460,79942,96454,71726,79982,96494,96336,113708,96328,113702,96324,96322,79916,96364,79910,96358,96296,113686,96292,96290,79894,96310,66936,99006,66876,66846,67006,68976,100028,68920,99998,68892,68878,66748,69052,66718,69022,73056,102072,116574,73008,102044,72984,102030,72972,72966,68792,99934,73144,68764,73116,68750,73102,66654,68830,73182,81216,106160,118620,81184,106136,118606,81168,106124,81160,106118,81156,81154,72880,101980,81328,72856,101966,81304,106190,81292,72838,81286,68700,72924,68686,81372,72910,81358,114336,122712,126894,114320,122700,114312,122694,114308,114306,81056,106072,118574,97696,81040,106060,97680,114380,106054,97672,81028,97668,81026,97666,72792,101934,81112,72780,97752,81100,72774,97740,81094,97734,68654,72814,81134,97774,114256,122668,114248,122662,114244,114242,80976,106028,97488,80968,106022,97480,114278,97476,80962,97474,72748,81004,72742,97516,80998,97510,114216,122646,114212,114210,80936,106006,97384,80932,97380,80930,97378,72726,80950,97398,114196,114194,80916,97332,80914,97330,66236,66206,67256,99166,67228,67214,66142,67294,69296,100188,69272,100174,69260,69254,67164,69340,67150,69326,73376,102232,116654,73360,102220,73352,102214,73348,73346,69208,100142,73432,102254,73420,69190,73414,67118,69230,73454,106320,118700,106312,118694,106308,106306,73296,102188,81616,106348,102182,81608,73284,81604,73282,81602,69164,73324,69158,81644,73318,81638,122792,126934,122788,122786,106280,118678,114536,106276,114532,106274,114530,73256,102166,81512,73252,98024,81508,73250,98020,81506,98018,69142,73270,81526,98038,122772,122770,106260,114484,106258,114482,73236,81460,73234,97908,81458,97906,122762,106250,114458,73226,81434,97850,66396,66382,67416,99246,67404,67398,66350,67438,69456,100268,69448,100262,69444,69442,67372,69484,67366,69478,102312,116694,102308,102306,69416,100246,73576,102326,73572,69410,73570,67350,69430,73590,118740,118738,102292,106420,102290,106418,69396,73524,69394,81780,73522,81778,118730,102282,106394,69386,73498,81722,66476,66470,67496,99286,67492,67490,66454,67510,100308,100306,67476,69556,67474,69554,116714])]);$1.cwtobits=function(){var _FD=$g($1.clusters,$k[--$j]);$1.v=$g(_FD,$k[--$j]);$k[$j++]=Infinity;for(var _FG=0,_FH=17;_FG<_FH;_FG++){$k[$j++]=0}var _FK=$R($s(17),$1.v,2);for(var _FL=0,_FM=_FK.length;_FL<_FM;_FL++){$k[$j++]=$g(_FK,_FL)-48}var _FO=$a();$k[$j++]=$G(_FO,_FO.length-17,17)};if($1.compact){$1.rwid=$f($f($f(17*$1.c+17)+17)+1)}else{$1.rwid=$f($f($f($f(17*$1.c+17)+17)+17)+18)}$1.pixs=$a($1.rwid*$1.r);for(var _FY=0,_FX=$1.r-1;_FY<=_FX;_FY+=1){$1.i=_FY;if($1.i%3==0){$1.lcw=~~($1.i/3)*30+~~(($1.r-1)/3);$1.rcw=$f($f(~~($1.i/3)*30+$1.c)-1)}if($1.i%3==1){$1.lcw=~~($1.i/3)*30+$1.eclevel*3+($1.r-1)%3;$1.rcw=~~($1.i/3)*30+~~(($1.r-1)/3)}if($1.i%3==2){$1.lcw=$f($f(~~($1.i/3)*30+$1.c)-1);$1.rcw=~~($1.i/3)*30+$1.eclevel*3+($1.r-1)%3}$k[$j++]=$1.pixs;$k[$j++]=$1.rwid*$1.i;$k[$j++]=Infinity;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=$1.lcw;$k[$j++]=$1.i%3;$1.cwtobits();$F($k[--$j]);var _G0=$G($1.cws,$1.c*$1.i,$1.c);for(var _G1=0,_G2=_G0.length;_G1<_G2;_G1++){$k[$j++]=$g(_G0,_G1);$k[$j++]=$1.i%3;$1.cwtobits();$F($k[--$j])}if($1.compact){$k[$j++]=1}else{$k[$j++]=$1.rcw;$k[$j++]=$1.i%3;$1.cwtobits();$F($k[--$j]);$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=0;$k[$j++]=1}var _GA=$a();var _GB=$k[--$j];$P($k[--$j],_GB,_GA)}var _GK=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",$1.rwid],["pixy",$1.r],["height",$1.r/72*$1.rowmult],["width",$1.rwid/72],["opt",$1.options]]);$k[$j++]=_GK;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_pdf417compact(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$p($1.options,"dontdraw",true);$p($1.options,"compact",true);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_pdf417();var _9=$k[--$j];$1[$k[--$j]]=_9;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_micropdf417(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.version="unset";$1.columns=0;$1.rows=0;$1.rowmult=2;$1.encoding="auto";$1.cca=false;$1.ccb=false;$1.raw=false;$1.parse=false;$1.parsefnc=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});if($ne($1.version,"unset")){$x($1.version,"x");$j--;$1.columns=$k[--$j];$j--;$1.rows=$k[--$j]}$1.columns=~~$z($1.columns);$1.rows=~~$z($1.rows);$1.rowmult=+$1.rowmult;if($1.raw){$1.encoding="raw"}if($1.cca){$1.encoding="cca"}if($eq($1.encoding,"raw")||$eq($1.encoding,"cca")){$1.datcws=$a($1.barcode.length);$1.i=0;$1.j=0;for(;;){if($1.i==$1.barcode.length){break}$1.cw=~~$z($G($1.barcode,$1.i+1,3));$p($1.datcws,$1.j,$1.cw);$1.i=$1.i+4;$1.j=$1.j+1}$1.datcws=$G($1.datcws,0,$1.j)}if($1.ccb){$1.encoding="ccb"}$1.encb=function(){$1.in=$k[--$j];$1.inlen=$1.in.length;$1.out=$a(~~($1.inlen/6)*5+$1.inlen%6);for(var _d=0,_c=~~($1.inlen/6)-1;_d<=_c;_d+=1){$1.k=_d;$k[$j++]=Infinity;$q($G($1.in,$1.k*6,3));$1.msbs=$a();$k[$j++]=Infinity;$q($1.msbs);var _j=$k[--$j];var _k=$k[--$j];var _l=$k[--$j];$k[$j++]=$f($f(_j+_k*256)+_l*65536);for(var _m=0,_n=3;_m<_n;_m++){var _o=$k[--$j];$k[$j++]=_o%900;$k[$j++]=~~(_o/900)}$1.mscs=$a();$k[$j++]=Infinity;$q($G($1.in,$1.k*6+3,3));$1.lsbs=$a();$k[$j++]=Infinity;$q($1.lsbs);var _v=$k[--$j];var _w=$k[--$j];var _x=$k[--$j];$k[$j++]=$f($f(_v+_w*256)+_x*65536);for(var _y=0,_z=3;_y<_z;_y++){var _10=$k[--$j];$k[$j++]=_10%900;$k[$j++]=~~(_10/900)}$1.lscs=$a();var _13=$g($1.lscs,0);var _15=$g($1.mscs,0);$p($1.out,$1.k*5+4,$f(_13+_15*316)%900);var _19=$g($1.lscs,1);var _1B=$g($1.mscs,0);var _1D=$g($1.mscs,1);$p($1.out,$1.k*5+3,$f($f($f(~~($f(_13+_15*316)/900)+_19)+_1B*641)+_1D*316)%900);var _1H=$g($1.lscs,2);var _1J=$g($1.mscs,0);var _1L=$g($1.mscs,1);var _1N=$g($1.mscs,2);$p($1.out,$1.k*5+2,$f($f($f($f(~~($f($f($f(~~($f(_13+_15*316)/900)+_19)+_1B*641)+_1D*316)/900)+_1H)+_1J*20)+_1L*641)+_1N*316)%900);var _1R=$g($1.lscs,3);var _1T=$g($1.mscs,1);var _1V=$g($1.mscs,2);$p($1.out,$1.k*5+1,$f($f($f(~~($f($f($f($f(~~($f($f($f(~~($f(_13+_15*316)/900)+_19)+_1B*641)+_1D*316)/900)+_1H)+_1J*20)+_1L*641)+_1N*316)/900)+_1R)+_1T*20)+_1V*641)%900);$p($1.out,$1.k*5,$f(~~($f($f($f(~~($f($f($f($f(~~($f($f($f(~~($f(_13+_15*316)/900)+_19)+_1B*641)+_1D*316)/900)+_1H)+_1J*20)+_1L*641)+_1N*316)/900)+_1R)+_1T*20)+_1V*641)/900)+$g($1.mscs,2)*20)%900)}$1.rem=$1.inlen%6;if($1.rem!=0){$k[$j++]=$1.out;$k[$j++]=$1.out.length-$1.rem;$k[$j++]=Infinity;$q($G($1.in,$1.inlen-$1.rem,$1.rem));var _1m=$a();var _1n=$k[--$j];$P($k[--$j],_1n,_1m)}$k[$j++]=$1.out};if($eq($1.encoding,"byte")||$eq($1.encoding,"ccb")){$1.barlen=$1.barcode.length;$1.datcws=$a(~~($1.barlen/6)*5+$1.barlen%6+1);var _1y=$1.barlen%6==0?924:901;$p($1.datcws,0,_1y);$k[$j++]=$1.datcws;$k[$j++]=1;$k[$j++]=Infinity;$F($1.barcode);var _21=$a();$k[$j++]=_21;$1.encb();var _22=$k[--$j];var _23=$k[--$j];$P($k[--$j],_23,_22);if($eq($1.encoding,"ccb")){$k[$j++]=Infinity;$k[$j++]=920;$q($1.datcws);$1.datcws=$a()}}if($eq($1.encoding,"auto")){var _2B=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["eci",true]]);$1.fncvals=_2B;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _2E=$k[--$j];$1[$k[--$j]]=_2E;$1.msglen=$1.msg.length;$1.T=0;$1.N=1;$1.B=2;$1.A=0;$1.L=1;$1.M=2;$1.P=3;$1.tl=-1;$1.nl=-2;$1.bl=-3;$1.bl6=-4;$1.bs=-5;$1.al=-6;$1.ll=-7;$1.ml=-8;$1.pl=-9;$1.as=-10;$1.ps=-11;$1.charmaps=$a([$a(["A","a","0",";"]),$a(["B","b","1","<"]),$a(["C","c","2",">"]),$a(["D","d","3","@"]),$a(["E","e","4","["]),$a(["F","f","5",92]),$a(["G","g","6","]"]),$a(["H","h","7","_"]),$a(["I","i","8","`"]),$a(["J","j","9","~"]),$a(["K","k","&","!"]),$a(["L","l",13,13]),$a(["M","m",9,9]),$a(["N","n",",",","]),$a(["O","o",":",":"]),$a(["P","p","#",10]),$a(["Q","q","-","-"]),$a(["R","r",".","."]),$a(["S","s","$","$"]),$a(["T","t","/","/"]),$a(["U","u","+",'"']),$a(["V","v","%","|"]),$a(["W","w","*","*"]),$a(["X","x","=",40]),$a(["Y","y","^",41]),$a(["Z","z",$1.pl,"?"]),$a([" "," "," ","{"]),$a([$1.ll,$1.as,$1.ll,"}"]),$a([$1.ml,$1.ml,$1.al,"'"]),$a([$1.ps,$1.ps,$1.ps,$1.al])]);$1.charvals=$a([new Map,new Map,new Map,new Map]);$1.alltext=new Map;for(var _30=0,_2z=$1.charmaps.length-1;_30<=_2z;_30+=1){$1.i=_30;$1.encs=$g($1.charmaps,$1.i);for(var _34=0;_34<=3;_34+=1){$1.j=_34;var _37=$g($1.encs,$1.j);$k[$j++]=_37;if($eq($t(_37),"stringtype")){var _3A=$g($k[--$j],0);$k[$j++]=_3A}var _3B=$k[--$j];$p($g($1.charvals,$1.j),_3B,$1.i);$p($1.alltext,_3B,-1)}}$1.e=1e4;$1.latlen=$a([$a([0,1,1,2]),$a([2,0,1,2]),$a([1,1,0,1]),$a([1,2,2,0])]);$1.latseq=$a([$a([$a([]),$a([$1.ll]),$a([$1.ml]),$a([$1.ml,$1.pl])]),$a([$a([$1.ml,$1.al]),$a([]),$a([$1.ml]),$a([$1.ml,$1.pl])]),$a([$a([$1.al]),$a([$1.ll]),$a([]),$a([$1.pl])]),$a([$a([$1.al]),$a([$1.al,$1.ll]),$a([$1.al,$1.ml]),$a([])])]);$1.shftlen=$a([$a([$1.e,$1.e,$1.e,1]),$a([1,$1.e,$1.e,1]),$a([$1.e,$1.e,$1.e,1]),$a([$1.e,$1.e,$1.e,$1.e])]);$k[$j++]=Infinity;for(var _4G=0,_4H=$1.msglen;_4G<_4H;_4G++){$k[$j++]=0}$k[$j++]=0;$1.numdigits=$a();$k[$j++]=Infinity;for(var _4K=0,_4L=$1.msglen;_4K<_4L;_4K++){$k[$j++]=0}$k[$j++]=0;$1.numtext=$a();$k[$j++]=Infinity;for(var _4O=0,_4P=$1.msglen;_4O<_4P;_4O++){$k[$j++]=0}$k[$j++]=0;$1.numbytes=$a();$k[$j++]=Infinity;for(var _4S=0,_4T=$1.msglen;_4S<_4T;_4S++){$k[$j++]=0}$1.iseci=$a();for(var _4W=$1.msglen-1;_4W>=0;_4W-=1){$1.i=_4W;var _4Z=$g($1.msg,$1.i);if(_4Z>=48&&_4Z<=57){$p($1.numdigits,$1.i,$f($g($1.numdigits,$1.i+1)+1))}var _4j=$g($1.alltext,$g($1.msg,$1.i))!==undefined;if(_4j&&$g($1.numdigits,$1.i)<13){$p($1.numtext,$1.i,$f($g($1.numtext,$1.i+1)+1))}if($g($1.msg,$1.i)>=0&&$g($1.numtext,$1.i)<5&&$g($1.numdigits,$1.i)<13){$p($1.numbytes,$1.i,$f($g($1.numbytes,$1.i+1)+1))}$p($1.iseci,$1.i,$g($1.msg,$1.i)<=-1e6)}$1.numdigits=$G($1.numdigits,0,$1.msglen);$1.numtext=$G($1.numtext,0,$1.msglen);$1.numbytes=$G($1.numbytes,0,$1.msglen);$1.seq=$a([]);$1.seqlen=0;$1.state=$1.B;$1.p=0;for(;;){if($1.p==$1.msglen){break}if($g($1.iseci,$1.p)){$1.eci=$g($1.msg,$1.p);$k[$j++]=Infinity;$q($1.seq);$k[$j++]=$a([$1.eci]);$1.seq=$a();$1.p=$1.p+1;$k[$j++]="seqlen";$k[$j++]=$1.seqlen;if($1.eci<=-1810900){$k[$j++]=2}else{var _5c=$1.eci<=-1000900?3:2;$k[$j++]=_5c}var _5d=$k[--$j];var _5e=$k[--$j];$1[$k[--$j]]=$f(_5e+_5d)}else{$1.n=$g($1.numdigits,$1.p);if($1.n>=13){$k[$j++]=Infinity;$q($1.seq);$k[$j++]=$1.nl;$k[$j++]=Infinity;$q($G($1.msg,$1.p,$1.n));var _5q=$a();$k[$j++]=_5q;$1.seq=$a();$1.state=$1.N;$1.p=$f($1.p+$1.n);$1.seqlen=$f($1.seqlen+1+$1.n)}else{$1.t=$g($1.numtext,$1.p);if($1.t>=5){$k[$j++]=Infinity;$q($1.seq);if($1.state!=$1.T){$k[$j++]=$1.tl}$k[$j++]=Infinity;$q($G($1.msg,$1.p,$1.t));var _69=$a();$k[$j++]=_69;$1.seq=$a();$1.state=$1.T;$1.p=$f($1.p+$1.t);$1.seqlen=$f($f($1.seqlen+1)+$1.t)}else{$1.b=$g($1.numbytes,$1.p);if($1.b==1&&$1.state==$1.T){$k[$j++]=Infinity;$q($1.seq);$k[$j++]=$1.bs;$k[$j++]=$a([$g($1.msg,$1.p)]);$1.seq=$a();$1.p=$f($1.p+$1.b);$1.seqlen=$f($1.seqlen+2)}else{$k[$j++]=Infinity;$q($1.seq);var _6Y=$1.b%6!=0?$1.bl:$1.bl6;$k[$j++]=_6Y;$k[$j++]=Infinity;$q($G($1.msg,$1.p,$1.b));var _6d=$a();$k[$j++]=_6d;$1.seq=$a();$1.state=$1.B;$1.p=$f($1.p+$1.b);$1.seqlen=$f($f($1.seqlen+1)+$1.b)}}}}}$k[$j++]=Infinity;$k[$j++]=$1.tl;$k[$j++]=900;$k[$j++]=$1.bl;$k[$j++]=901;$k[$j++]=$1.bl6;$k[$j++]=924;$k[$j++]=$1.nl;$k[$j++]=902;$k[$j++]=$1.bs;$k[$j++]=913;$1.latchcws=$d();$1.enca=function(){var _6u=$g($g($1.charvals,$1.A),$k[--$j]);$k[$j++]=_6u};$1.encl=function(){var _6z=$g($g($1.charvals,$1.L),$k[--$j]);$k[$j++]=_6z};$1.encm=function(){var _74=$g($g($1.charvals,$1.M),$k[--$j]);$k[$j++]=_74};$1.encp=function(){var _79=$g($g($1.charvals,$1.P),$k[--$j]);$k[$j++]=_79};$1.textencfuncs=$a(["enca","encl","encm","encp"]);$1.addtotext=function(){$p($1.text,$1.l,$k[--$j]);$1.l=$1.l+1};$1.enct=function(){$1.in=$k[--$j];$1.curlen=$a([$1.e,$1.e,$1.e,$1.e]);$p($1.curlen,$1.submode,0);$1.curseq=$a([$a([]),$a([]),$a([]),$a([])]);$F($1.in,function(){$1.char=$k[--$j];for(;;){$1.imp=false;var _7Y=$a([$1.A,$1.L,$1.M,$1.P]);for(var _7Z=0,_7a=_7Y.length;_7Z<_7a;_7Z++){$1.x=$g(_7Y,_7Z);var _7g=$a([$1.A,$1.L,$1.M,$1.P]);for(var _7h=0,_7i=_7g.length;_7h<_7i;_7h++){$1.y=$g(_7g,_7h);$1.cost=$f($g($1.curlen,$1.x)+$g($g($1.latlen,$1.x),$1.y));if($1.cost<$g($1.curlen,$1.y)){$p($1.curlen,$1.y,$1.cost);$k[$j++]=$1.curseq;$k[$j++]=$1.y;$k[$j++]=Infinity;$q($g($1.curseq,$1.x));$q($g($g($1.latseq,$1.x),$1.y));var _89=$a();var _8A=$k[--$j];$p($k[--$j],_8A,_89);$1.imp=true}}}if(!$1.imp){break}}$1.nxtlen=$a([$1.e,$1.e,$1.e,$1.e]);$1.nxtseq=$a(4);var _8N=$a([$1.A,$1.L,$1.M,$1.P]);for(var _8O=0,_8P=_8N.length;_8O<_8P;_8O++){$1.x=$g(_8N,_8O);for(;;){var _8V=$g($g($1.charvals,$1.x),$1.char)!==undefined;if(!_8V){break}$1.cost=$f($g($1.curlen,$1.x)+1);if($1.cost<$g($1.nxtlen,$1.x)){$p($1.nxtlen,$1.x,$1.cost);$k[$j++]=$1.nxtseq;$k[$j++]=$1.x;$k[$j++]=Infinity;$q($g($1.curseq,$1.x));$k[$j++]=$1.char;var _8m=$a();var _8n=$k[--$j];$p($k[--$j],_8n,_8m)}var _8t=$a([$1.A,$1.L,$1.M,$1.P]);for(var _8u=0,_8v=_8t.length;_8u<_8v;_8u++){$1.y=$g(_8t,_8u);if($ne($1.x,$1.y)){$1.cost=$f($f($g($1.curlen,$1.y)+$g($g($1.shftlen,$1.y),$1.x))+1);if($1.cost<$g($1.nxtlen,$1.y)){$p($1.nxtlen,$1.y,$1.cost);$k[$j++]=$1.nxtseq;$k[$j++]=$1.y;$k[$j++]=Infinity;$q($g($1.curseq,$1.y));var _9L=$1.x==$1.A?$1.as:$1.ps;$k[$j++]=_9L;$k[$j++]=$1.char;var _9N=$a();var _9O=$k[--$j];$p($k[--$j],_9O,_9N)}}}break}}$1.curlen=$1.nxtlen;$1.curseq=$1.nxtseq});$1.minseq=$1.e;var _9X=$a([$1.A,$1.L,$1.M,$1.P]);for(var _9Y=0,_9Z=_9X.length;_9Y<_9Z;_9Y++){$1.k=$g(_9X,_9Y);if($g($1.curlen,$1.k)<$1.minseq){$1.minseq=$g($1.curlen,$1.k);$1.txtseq=$g($1.curseq,$1.k)}}$1.text=$a($1.minseq);$1.k=0;$1.l=0;for(;;){if($1.k>=$1.txtseq.length){break}$1.char=$g($1.txtseq,$1.k);$k[$j++]=$1.char;if($1[$g($1.textencfuncs,$1.submode)]()===true){break}$1.addtotext();$1.k=$1.k+1;if($1.char==$1.as||$1.char==$1.ps){$k[$j++]=$g($1.txtseq,$1.k);if($1.char==$1.as){$1.enca()}else{$1.encp()}$1.addtotext();$1.k=$1.k+1}if($1.char==$1.al){$1.submode=$1.A}if($1.char==$1.ll){$1.submode=$1.L}if($1.char==$1.ml){$1.submode=$1.M}if($1.char==$1.pl){$1.submode=$1.P}}if($1.text.length%2==1){if($1.submode==$1.P){$k[$j++]="pad";$k[$j++]=$1.al;$1.encp();var _AO=$k[--$j];$1[$k[--$j]]=_AO;$1.submode=$1.A}else{$k[$j++]="pad";$k[$j++]=$1.ps;if($1[$g($1.textencfuncs,$1.submode)]()===true){return true}var _AW=$k[--$j];$1[$k[--$j]]=_AW}$k[$j++]=Infinity;$q($1.text);$k[$j++]=$1.pad;$1.text=$a()}$1.out=$a(~~($1.text.length/2));for(var _Af=0,_Ae=$1.out.length-1;_Af<=_Ae;_Af+=1){$1.k=_Af;$p($1.out,$1.k,$f($g($1.text,$1.k*2)*30+$g($1.text,$1.k*2+1)))}$k[$j++]=$1.out};$1.encn=function(){$1.in=$k[--$j];$1.out=$a([]);for(var _At=0,_As=$1.in.length-1;_At<=_As;_At+=44){$1.k=_At;$k[$j++]=Infinity;var _Ay=$1.in.length-$1.k;$k[$j++]=1;$k[$j++]=$1.in;$k[$j++]=$1.k;$k[$j++]=_Ay;if(_Ay>44){$j--;$k[$j++]=44}var _Az=$k[--$j];var _B0=$k[--$j];var _B2=$G($k[--$j],_B0,_Az);for(var _B3=0,_B4=_B2.length;_B3<_B4;_B3++){$k[$j++]=$f($g(_B2,_B3)-48)}$1.gmod=$a();$1.cwn=$a([]);for(;;){$1.dv=900;$1.gmul=$a([]);$1.val=0;for(;;){if($1.gmod.length==0){break}$1.val=$f($1.val*10+$g($1.gmod,0));$1.gmod=$G($1.gmod,1,$1.gmod.length-1);if($1.val<$1.dv){if($1.gmul.length!=0){$k[$j++]=Infinity;$q($1.gmul);$k[$j++]=0;$1.gmul=$a()}}else{$k[$j++]=Infinity;$q($1.gmul);$k[$j++]=~~($1.val/$1.dv);$1.gmul=$a()}$1.val=$1.val%$1.dv}$1.dv=$1.val;$k[$j++]=Infinity;$k[$j++]=$1.dv;$q($1.cwn);$1.cwn=$a();$1.gmod=$1.gmul;if($1.gmul.length==0){break}}$k[$j++]=Infinity;$q($1.out);$q($1.cwn);$1.out=$a()}$k[$j++]=$1.out};$1.ence=function(){var _Bd=$f(-$g($k[--$j],0)-1e6);$k[$j++]=_Bd;if(_Bd<=899){var _Be=$k[--$j];$k[$j++]=927;$k[$j++]=_Be;$r($a(2))}else{var _Bg=$k[--$j];$k[$j++]=_Bg;if(_Bg<=810899){var _Bh=$k[--$j];$k[$j++]=926;$k[$j++]=~~(_Bh/900)-1;$k[$j++]=_Bh%900;$r($a(3))}else{var _Bj=$k[--$j];$k[$j++]=_Bj;if(_Bj<=811799){var _Bk=$k[--$j];$k[$j++]=925;$k[$j++]=$f(_Bk-810900);$r($a(2))}else{$k[$j++]="bwipp.pdf417badECI";$k[$j++]="PDF417 supports ECIs 000000 to 811799";bwipp_raiseerror()}}}};$1.encfuncs=$a(["enct","encn","encb"]);$1.addtocws=function(){var _Bn=$k[--$j];$P($1.datcws,$1.j,_Bn);$1.j=_Bn.length+$1.j};$1.datcws=$a($1.seqlen);$1.i=0;$1.j=0;for(;;){if($1.i>=$1.seq.length){break}$1.chars=$g($1.seq,$1.i);if($eq($t($1.chars),"arraytype")){if($g($1.chars,0)<=-1e6){$k[$j++]=$1.chars;$1.ence();$1.addtocws()}else{$k[$j++]=$1.chars;if($1[$g($1.encfuncs,$1.state)]()===true){break}$1.addtocws()}}else{$k[$j++]=$a([$g($1.latchcws,$1.chars)]);$1.addtocws();if($1.chars==$1.tl){$1.state=$1.T;$1.submode=$1.A}if($1.chars==$1.nl){$1.state=$1.N}if($1.chars==$1.bl||$1.chars==$1.bl6){$1.state=$1.B}if($1.chars==$1.bs){$1.i=$1.i+1;$k[$j++]=$g($1.seq,$1.i);$1.encb();$1.addtocws()}}$1.i=$1.i+1}$1.datcws=$G($1.datcws,0,$1.j)}$1.metrics=$a([$a([1,11,7,1,0,9]),$a([1,14,7,8,0,8]),$a([1,17,7,36,0,36]),$a([1,20,8,19,0,19]),$a([1,24,8,9,0,17]),$a([1,28,8,25,0,33]),$a([2,8,8,1,0,1]),$a([2,11,9,1,0,9]),$a([2,14,9,8,0,8]),$a([2,17,10,36,0,36]),$a([2,20,11,19,0,19]),$a([2,23,13,9,0,17]),$a([2,26,15,27,0,35]),$a([3,6,12,1,1,1]),$a([3,8,14,7,7,7]),$a([3,10,16,15,15,15]),$a([3,12,18,25,25,25]),$a([3,15,21,37,37,37]),$a([3,20,26,1,17,33]),$a([3,26,32,1,9,17]),$a([3,32,38,21,29,37]),$a([3,38,44,15,31,47]),$a([3,44,50,1,25,49]),$a([4,4,8,47,19,43]),$a([4,6,12,1,1,1]),$a([4,8,14,7,7,7]),$a([4,10,16,15,15,15]),$a([4,12,18,25,25,25]),$a([4,15,21,37,37,37]),$a([4,20,26,1,17,33]),$a([4,26,32,1,9,17]),$a([4,32,38,21,29,37]),$a([4,38,44,15,31,47]),$a([4,44,50,1,25,49])]);$1.ccametrics=$a([$a([2,5,4,39,0,19]),$a([2,6,4,1,0,33]),$a([2,7,5,32,0,12]),$a([2,8,5,8,0,40]),$a([2,9,6,14,0,46]),$a([2,10,6,43,0,23]),$a([2,12,7,20,0,52]),$a([3,4,4,11,43,23]),$a([3,5,5,1,33,13]),$a([3,6,6,5,37,17]),$a([3,7,7,15,47,27]),$a([3,8,7,21,1,33]),$a([4,3,4,40,20,52]),$a([4,4,5,43,23,3]),$a([4,5,6,46,26,6]),$a([4,6,7,34,14,46]),$a([4,7,8,29,9,41])]);if($1.cca){$1.metrics=$1.ccametrics}$1.urows=$1.rows;$1.ucols=$1.columns;$1.i=0;for(;;){$1.m=$g($1.metrics,$1.i);$1.c=$g($1.m,0);$1.r=$g($1.m,1);$1.k=$g($1.m,2);$1.rapl=$g($1.m,3);$1.rapc=$g($1.m,4);$1.rapr=$g($1.m,5);$1.ncws=$f($1.r*$1.c-$1.k);$1.okay=true;if($1.datcws.length>$1.ncws){$1.okay=false}if($1.urows!=0&&$1.urows!=$1.r){$1.okay=false}if($1.ucols!=0&&$1.ucols!=$1.c){$1.okay=false}if($1.okay){break}$1.i=$1.i+1}$1.m=$1.datcws.length;$1.n=$f($1.c*$1.r-$1.k);$1.cws=$a($f($1.c*$1.r+1));$P($1.cws,0,$1.datcws);$k[$j++]=$1.cws;$k[$j++]=$1.m;$k[$j++]=Infinity;for(var _E8=0,_E9=$f($1.n-$1.m);_E8<_E9;_E8++){$k[$j++]=900}var _EA=$a();var _EB=$k[--$j];$P($k[--$j],_EB,_EA);$k[$j++]=$1.cws;$k[$j++]=$1.n;$k[$j++]=Infinity;for(var _EG=0,_EH=$1.k;_EG<_EH;_EG++){$k[$j++]=0}$k[$j++]=0;var _EI=$a();var _EJ=$k[--$j];$P($k[--$j],_EJ,_EI);$k[$j++]=Infinity;$k[$j++]=1;for(var _EL=0,_EM=928;_EL<_EM;_EL++){var _EN=$k[--$j];$k[$j++]=_EN;$k[$j++]=_EN*3%929}$1.rsalog=$a();$1.rslog=$a(929);for(var _EQ=1;_EQ<=928;_EQ+=1){$p($1.rslog,$g($1.rsalog,_EQ),_EQ)}$1.rsprod=function(){var _EU=$k[--$j];var _EV=$k[--$j];$k[$j++]=_EV;$k[$j++]=_EU;if(_EU!=0&&_EV!=0){var _EY=$g($1.rslog,$k[--$j]);var _Ed=$g($1.rsalog,$f(_EY+$g($1.rslog,$k[--$j]))%928);$k[$j++]=_Ed}else{$j-=2;$k[$j++]=0}};$k[$j++]=Infinity;$k[$j++]=1;for(var _Ef=0,_Eg=$1.k;_Ef<_Eg;_Ef++){$k[$j++]=0}$1.coeffs=$a();for(var _Ek=1,_Ej=$1.k;_Ek<=_Ej;_Ek+=1){$1.i=_Ek;$p($1.coeffs,$1.i,$g($1.coeffs,$1.i-1));for(var _Er=$1.i-1;_Er>=1;_Er-=1){$1.j=_Er;$k[$j++]=$1.coeffs;$k[$j++]=$1.j;$k[$j++]=$g($1.coeffs,$1.j-1);$k[$j++]=$g($1.coeffs,$1.j);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _F3=$k[--$j];var _F4=$k[--$j];var _F5=$k[--$j];$p($k[--$j],_F5,$f(_F4+_F3)%929)}$k[$j++]=$1.coeffs;$k[$j++]=0;$k[$j++]=$g($1.coeffs,0);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _FD=$k[--$j];var _FE=$k[--$j];$p($k[--$j],_FE,_FD)}$1.coeffs=$G($1.coeffs,0,$1.coeffs.length-1);for(var _FK=$1.coeffs.length-1;_FK>=0;_FK-=2){var _FL=$1.coeffs;$p(_FL,_FK,$f(929-$g(_FL,_FK)))}for(var _FP=0,_FO=$f($1.n-1);_FP<=_FO;_FP+=1){$1.t=$f($g($1.cws,_FP)+$g($1.cws,$1.n))%929;for(var _FX=0,_FW=$f($1.k-1);_FX<=_FW;_FX+=1){$1.j=_FX;$p($1.cws,$f($1.n+$1.j),$f($g($1.cws,$f($f($1.n+$1.j)+1))+$f(929-$1.t*$g($1.coeffs,$f($f($1.k-$1.j)-1))%929))%929)}}for(var _Fo=$1.n,_Fn=$f($1.n+$1.k);_Fo<=_Fn;_Fo+=1){$p($1.cws,_Fo,$f(929-$g($1.cws,_Fo))%929)}$1.cws=$G($1.cws,0,$1.cws.length-1);$1.clusters=$a([$a([120256,125680,128380,120032,125560,128318,108736,119920,108640,86080,108592,86048,110016,120560,125820,109792,120440,125758,88256,109680,88160,89536,110320,120700,89312,110200,120638,89200,110140,89840,110460,89720,110398,89980,128506,119520,125304,128190,107712,119408,125244,107616,119352,84032,107568,119324,84e3,107544,83984,108256,119672,125374,85184,108144,119612,85088,108088,119582,85040,108060,85728,108408,119742,85616,108348,85560,108318,85880,108478,85820,85790,107200,119152,125116,107104,119096,125086,83008,107056,119068,82976,107032,82960,82952,83648,107376,119228,83552,107320,119198,83504,107292,83480,83468,83824,107452,83768,107422,83740,83900,106848,118968,125022,82496,106800,118940,82464,106776,118926,82448,106764,82440,106758,82784,106936,119006,82736,106908,82712,106894,82700,82694,106974,82830,82240,106672,118876,82208,106648,118862,82192,106636,82184,106630,82180,82352,82328,82316,82080,118830,106572,106566,82050,117472,124280,127678,103616,117360,124220,103520,117304,124190,75840,103472,75808,104160,117624,124350,76992,104048,117564,76896,103992,76848,76824,77536,104312,117694,77424,104252,77368,77340,77688,104382,77628,77758,121536,126320,128700,121440,126264,128670,111680,121392,126236,111648,121368,126222,111632,121356,103104,117104,124092,112320,103008,117048,124062,112224,121656,126366,93248,74784,102936,117006,93216,112152,93200,75456,103280,117180,93888,75360,103224,117150,93792,112440,121758,93744,75288,93720,75632,103356,94064,75576,103326,94008,112542,93980,75708,94140,75678,94110,121184,126136,128606,111168,121136,126108,111136,121112,126094,111120,121100,111112,111108,102752,116920,123998,111456,102704,116892,91712,74272,121244,116878,91680,74256,102668,91664,111372,102662,74244,74592,102840,116958,92e3,74544,102812,91952,111516,102798,91928,74508,74502,74680,102878,92088,74652,92060,74638,92046,92126,110912,121008,126044,110880,120984,126030,110864,120972,110856,120966,110852,110850,74048,102576,116828,90944,74016,102552,116814,90912,111e3,121038,90896,73992,102534,90888,110982,90884,74160,102620,91056,74136,102606,91032,111054,91020,74118,91014,91100,91086,110752,120920,125998,110736,120908,110728,120902,110724,110722,73888,102488,116782,90528,73872,102476,90512,110796,102470,90504,73860,90500,73858,73944,90584,90572,90566,120876,120870,110658,102444,73800,90312,90308,90306,101056,116080,123580,100960,116024,70720,100912,115996,70688,100888,70672,70664,71360,101232,116156,71264,101176,116126,71216,101148,71192,71180,71536,101308,71480,101278,71452,71612,71582,118112,124600,127838,105024,118064,124572,104992,118040,124558,104976,118028,104968,118022,100704,115896,123486,105312,100656,115868,79424,70176,118172,115854,79392,105240,100620,79376,70152,79368,70496,100792,115934,79712,70448,118238,79664,105372,100750,79640,70412,79628,70584,100830,79800,70556,79772,70542,70622,79838,122176,126640,128860,122144,126616,128846,122128,126604,122120,126598,122116,104768,117936,124508,113472,104736,126684,124494,113440,122264,126670,113424,104712,117894,113416,122246,104706,69952,100528,115804,78656,69920,100504,115790,96064,78624,104856,117966,96032,113560,122318,100486,96016,78600,104838,96008,69890,70064,100572,78768,70040,100558,96176,78744,104910,96152,113614,70022,78726,70108,78812,70094,96220,78798,122016,126552,128814,122e3,126540,121992,126534,121988,121986,104608,117848,124462,113056,104592,126574,113040,122060,117830,113032,104580,113028,104578,113026,69792,100440,115758,78240,69776,100428,95136,78224,104652,100422,95120,113100,69764,95112,78212,69762,78210,69848,100462,78296,69836,95192,78284,69830,95180,78278,69870,95214,121936,126508,121928,126502,121924,121922,104528,117804,112848,104520,117798,112840,121958,112836,104514,112834,69712,100396,78032,69704,100390,94672,78024,104550,94664,112870,69698,94660,78018,94658,78060,94700,94694,126486,121890,117782,104484,104482,69672,77928,94440,69666,77922,99680,68160,99632,68128,99608,115342,68112,99596,68104,99590,68448,99768,115422,68400,99740,68376,99726,68364,68358,68536,99806,68508,68494,68574,101696,116400,123740,101664,116376,101648,116364,101640,116358,101636,67904,99504,115292,72512,67872,116444,115278,72480,101784,116430,72464,67848,99462,72456,101766,67842,68016,99548,72624,67992,99534,72600,101838,72588,67974,68060,72668,68046,72654,118432,124760,127918,118416,124748,118408,124742,118404,118402,101536,116312,105888,101520,116300,105872,118476,116294,105864,101508,105860,101506,105858,67744,99416,72096,67728,116334,80800,72080,101580,99398,80784,105932,67716,80776,72068,67714,72066,67800,99438,72152,67788,80856,72140,67782,80844,72134,67822,72174,80878,126800,128940,126792,128934,126788,126786,118352,124716,122576,126828,124710,122568,126822,122564,118338,122562,101456,116268,105680,101448,116262,114128,105672,118374,114120,122598,101442,114116,105666,114114,67664,99372,71888,67656,99366,80336,71880,101478,97232,80328,105702,67650,97224,114150,71874,97220,67692,71916,67686,80364,71910,97260,80358,97254,126760,128918,126756,126754,118312,124694,122472,126774,122468,118306,122466,101416,116246,105576,101412,113896,105572,101410,113892,105570,113890,67624,99350,71784,101430,80104,71780,67618,96744,80100,71778,96740,80098,96738,71798,96758,126738,122420,122418,105524,113780,113778,71732,79988,96500,96498,66880,66848,98968,66832,66824,66820,66992,66968,66956,66950,67036,67022,1e5,99984,115532,99976,115526,99972,99970,66720,98904,69024,100056,98892,69008,100044,69e3,100038,68996,66690,68994,66776,98926,69080,100078,69068,66758,69062,66798,69102,116560,116552,116548,116546,99920,102096,116588,115494,102088,116582,102084,99906,102082,66640,68816,66632,98854,73168,68808,66628,73160,68804,66626,73156,68802,66668,68844,66662,73196,68838,73190,124840,124836,124834,116520,118632,124854,118628,116514,118626,99880,115478,101992,116534,106216,101988,99874,106212,101986,106210,66600,98838,68712,99894,72936,68708,66594,81384,72932,68706,81380,72930,66614,68726,72950,81398,128980,128978,124820,126900,124818,126898,116500,118580,116498,122740,118578,122738,99860,101940,99858,106100,101938,114420]),$a([128352,129720,125504,128304,129692,125472,128280,129678,125456,128268,125448,128262,125444,125792,128440,129758,120384,125744,128412,120352,125720,128398,120336,125708,120328,125702,120324,120672,125880,128478,110144,120624,125852,110112,120600,125838,110096,120588,110088,120582,110084,110432,120760,125918,89664,110384,120732,89632,110360,120718,89616,110348,89608,110342,89952,110520,120798,89904,110492,89880,110478,89868,90040,110558,90012,89998,125248,128176,129628,125216,128152,129614,125200,128140,125192,128134,125188,125186,119616,125360,128220,119584,125336,128206,119568,125324,119560,125318,119556,119554,108352,119728,125404,108320,119704,125390,108304,119692,108296,119686,108292,108290,85824,108464,119772,85792,108440,119758,85776,108428,85768,108422,85764,85936,108508,85912,108494,85900,85894,85980,85966,125088,128088,129582,125072,128076,125064,128070,125060,125058,119200,125144,128110,119184,125132,119176,125126,119172,119170,107424,119256,125166,107408,119244,107400,119238,107396,107394,83872,107480,119278,83856,107468,83848,107462,83844,83842,83928,107502,83916,83910,83950,125008,128044,125e3,128038,124996,124994,118992,125036,118984,125030,118980,118978,106960,119020,106952,119014,106948,106946,82896,106988,82888,106982,82884,82882,82924,82918,124968,128022,124964,124962,118888,124982,118884,118882,106728,118902,106724,106722,82408,106742,82404,82402,124948,124946,118836,118834,106612,106610,124224,127664,129372,124192,127640,129358,124176,127628,124168,127622,124164,124162,117568,124336,127708,117536,124312,127694,117520,124300,117512,124294,117508,117506,104256,117680,124380,104224,117656,124366,104208,117644,104200,117638,104196,104194,77632,104368,117724,77600,104344,117710,77584,104332,77576,104326,77572,77744,104412,77720,104398,77708,77702,77788,77774,128672,129880,93168,128656,129868,92664,128648,129862,92412,128644,128642,124064,127576,129326,126368,124048,129902,126352,128716,127558,126344,124036,126340,124034,126338,117152,124120,127598,121760,117136,124108,121744,126412,124102,121736,117124,121732,117122,121730,103328,117208,124142,112544,103312,117196,112528,121804,117190,112520,103300,112516,103298,112514,75680,103384,117230,94112,75664,103372,94096,112588,103366,94088,75652,94084,75650,75736,103406,94168,75724,94156,75718,94150,75758,128592,129836,91640,128584,129830,91388,128580,91262,128578,123984,127532,126160,123976,127526,126152,128614,126148,123970,126146,116944,124012,121296,116936,124006,121288,126182,121284,116930,121282,102864,116972,111568,102856,116966,111560,121318,111556,102850,111554,74704,102892,92112,74696,102886,92104,111590,92100,74690,92098,74732,92140,74726,92134,128552,129814,90876,128548,90750,128546,123944,127510,126056,128566,126052,123938,126050,116840,123958,121064,116836,121060,116834,121058,102632,116854,111080,121078,111076,102626,111074,74216,102646,91112,74212,91108,74210,91106,74230,91126,128532,90494,128530,123924,126004,123922,126002,116788,120948,116786,120946,102516,110836,102514,110834,73972,90612,73970,90610,128522,123914,125978,116762,120890,102458,110714,123552,127320,129198,123536,127308,123528,127302,123524,123522,116128,123608,127342,116112,123596,116104,123590,116100,116098,101280,116184,123630,101264,116172,101256,116166,101252,101250,71584,101336,116206,71568,101324,71560,101318,71556,71554,71640,101358,71628,71622,71662,127824,129452,79352,127816,129446,79100,127812,78974,127810,123472,127276,124624,123464,127270,124616,127846,124612,123458,124610,115920,123500,118224,115912,123494,118216,124646,118212,115906,118210,100816,115948,105424,100808,115942,105416,118246,105412,100802,105410,70608,100844,79824,70600,100838,79816,105446,79812,70594,79810,70636,79852,70630,79846,129960,95728,113404,129956,95480,113278,129954,95356,95294,127784,129430,78588,128872,129974,95996,78462,128868,127778,95870,128866,123432,127254,124520,123428,126696,128886,123426,126692,124514,126690,115816,123446,117992,115812,122344,117988,115810,122340,117986,122338,100584,115830,104936,100580,113640,104932,100578,113636,104930,113634,70120,100598,78824,70116,96232,78820,70114,96228,78818,96226,70134,78838,129940,94968,113022,129938,94844,94782,127764,78206,128820,127762,95102,128818,123412,124468,123410,126580,124466,126578,115764,117876,115762,122100,117874,122098,100468,104692,100466,113140,104690,113138,69876,78324,69874,95220,78322,95218,129930,94588,94526,127754,128794,123402,124442,126522,115738,117818,121978,100410,104570,112890,69754,78074,94714,94398,123216,127148,123208,127142,123204,123202,115408,123244,115400,123238,115396,115394,99792,115436,99784,115430,99780,99778,68560,99820,68552,99814,68548,68546,68588,68582,127400,129238,72444,127396,72318,127394,123176,127126,123752,123172,123748,123170,123746,115304,123190,116456,115300,116452,115298,116450,99560,115318,101864,99556,101860,99554,101858,68072,99574,72680,68068,72676,68066,72674,68086,72694,129492,80632,105854,129490,80508,80446,127380,72062,127924,127378,80766,127922,123156,123700,123154,124788,123698,124786,115252,116340,115250,118516,116338,118514,99444,101620,99442,105972,101618,105970,67828,72180,67826,80884,72178,80882,97008,114044,96888,113982,96828,96798,129482,80252,130010,97148,80190,97086,127370,127898,128954,123146,123674,124730,126842,115226,116282,118394,122618,99386,101498,105722,114170,67706,71930,80378,96632,113854,96572,96542,80062,96702,96444,96414,96350,123048,123044,123042,115048,123062,115044,115042,99048,115062,99044,99042,67048,99062,67044,67042,67062,127188,68990,127186,123028,123316,123026,123314,114996,115572,114994,115570,98932,100084,98930,100082,66804,69108,66802,69106,129258,73084,73022,127178,127450,123018,123290,123834,114970,115514,116602,98874,99962,102138,66682,68858,73210,81272,106174,81212,81182,72894,81342,97648,114364,97592,114334,97564,97550,81084,97724,81054,97694,97464,114270,97436,97422,80990,97502,97372,97358,97326,114868,114866,98676,98674,66292,66290,123098,114842,115130,98618,99194,66170,67322,69310,73404,73374,81592,106334,81564,81550,73310,81630,97968,114524,97944,114510,97932,97926,81500,98012,81486,97998,97880,114478,97868,97862,81454,97902,97836,97830,69470,73564,73550,81752,106414,81740,81734,73518,81774,81708,81702]),$a([109536,120312,86976,109040,120060,86496,108792,119934,86256,108668,86136,129744,89056,110072,129736,88560,109820,129732,88312,109694,129730,88188,128464,129772,89592,128456,129766,89340,128452,89214,128450,125904,128492,125896,128486,125892,125890,120784,125932,120776,125926,120772,120770,110544,120812,110536,120806,110532,84928,108016,119548,84448,107768,119422,84208,107644,84088,107582,84028,129640,85488,108284,129636,85240,108158,129634,85116,85054,128232,129654,85756,128228,85630,128226,125416,128246,125412,125410,119784,125430,119780,119778,108520,119798,108516,108514,83424,107256,119166,83184,107132,83064,107070,83004,82974,129588,83704,107390,129586,83580,83518,128116,83838,128114,125172,125170,119284,119282,107508,107506,82672,106876,82552,106814,82492,82462,129562,82812,82750,128058,125050,119034,82296,106686,82236,82206,82366,82108,82078,76736,103920,117500,76256,103672,117374,76016,103548,75896,103486,75836,129384,77296,104188,129380,77048,104062,129378,76924,76862,127720,129398,77564,127716,77438,127714,124392,127734,124388,124386,117736,124406,117732,117730,104424,117750,104420,104418,112096,121592,126334,92608,111856,121468,92384,111736,121406,92272,111676,92216,111646,92188,75232,103160,117118,93664,74992,103036,93424,112252,102974,93304,74812,93244,74782,93214,129332,75512,103294,129908,129330,93944,75388,129906,93820,75326,93758,127604,75646,128756,127602,94078,128754,124148,126452,124146,126450,117236,121844,117234,121842,103412,103410,91584,111344,121212,91360,111224,121150,91248,111164,91192,111134,91164,91150,74480,102780,91888,74360,102718,91768,111422,91708,74270,91678,129306,74620,129850,92028,74558,91966,127546,128634,124026,126202,116986,121338,102906,90848,110968,121022,90736,110908,90680,110878,90652,90638,74104,102590,91e3,74044,90940,74014,90910,74174,91070,90480,110780,90424,110750,90396,90382,73916,90556,73886,90526,90296,110686,90268,90254,73822,90334,90204,90190,71136,101112,116094,70896,100988,70776,100926,70716,70686,129204,71416,101246,129202,71292,71230,127348,71550,127346,123636,123634,116212,116210,101364,101362,79296,105200,118140,79072,105080,118078,78960,105020,78904,104990,78876,78862,70384,100732,79600,70264,100670,79480,105278,79420,70174,79390,129178,70524,129466,79740,70462,79678,127290,127866,123514,124666,115962,118266,100858,113376,122232,126654,95424,113264,122172,95328,113208,122142,95280,113180,95256,113166,95244,78560,104824,117950,95968,78448,104764,95856,113468,104734,95800,78364,95772,78350,95758,70008,100542,78712,69948,96120,78652,69918,96060,78622,96030,70078,78782,96190,94912,113008,122044,94816,112952,122014,94768,112924,94744,112910,94732,94726,78192,104636,95088,78136,104606,95032,113054,95004,78094,94990,69820,78268,69790,95164,78238,95134,94560,112824,121950,94512,112796,94488,112782,94476,94470,78008,104542,94648,77980,94620,77966,94606,69726,78046,94686,94384,112732,94360,112718,94348,94342,77916,94428,77902,94414,94296,112686,94284,94278,77870,94318,94252,94246,68336,99708,68216,99646,68156,68126,68476,68414,127162,123258,115450,99834,72416,101752,116414,72304,101692,72248,101662,72220,72206,67960,99518,72568,67900,72508,67870,72478,68030,72638,80576,105840,118460,80480,105784,118430,80432,105756,80408,105742,80396,80390,72048,101564,80752,71992,101534,80696,71964,80668,71950,80654,67772,72124,67742,80828,72094,80798,114016,122552,126814,96832,113968,122524,96800,113944,122510,96784,113932,96776,113926,96772,80224,105656,118366,97120,80176,105628,97072,114076,105614,97048,80140,97036,80134,97030,71864,101470,80312,71836,97208,80284,71822,97180,80270,97166,67678,71902,80350,97246,96576,113840,122460,96544,113816,122446,96528,113804,96520,113798,96516,96514,80048,105564,96688,80024,105550,96664,113870,96652,80006,96646,71772,80092,71758,96732,80078,96718,96416,113752,122414,96400,113740,96392,113734,96388,96386,79960,105518,96472,79948,96460,79942,96454,71726,79982,96494,96336,113708,96328,113702,96324,96322,79916,96364,79910,96358,96296,113686,96292,96290,79894,96310,66936,99006,66876,66846,67006,68976,100028,68920,99998,68892,68878,66748,69052,66718,69022,73056,102072,116574,73008,102044,72984,102030,72972,72966,68792,99934,73144,68764,73116,68750,73102,66654,68830,73182,81216,106160,118620,81184,106136,118606,81168,106124,81160,106118,81156,81154,72880,101980,81328,72856,101966,81304,106190,81292,72838,81286,68700,72924,68686,81372,72910,81358,114336,122712,126894,114320,122700,114312,122694,114308,114306,81056,106072,118574,97696,81040,106060,97680,114380,106054,97672,81028,97668,81026,97666,72792,101934,81112,72780,97752,81100,72774,97740,81094,97734,68654,72814,81134,97774,114256,122668,114248,122662,114244,114242,80976,106028,97488,80968,106022,97480,114278,97476,80962,97474,72748,81004,72742,97516,80998,97510,114216,122646,114212,114210,80936,106006,97384,80932,97380,80930,97378,72726,80950,97398,114196,114194,80916,97332,80914,97330,66236,66206,67256,99166,67228,67214,66142,67294,69296,100188,69272,100174,69260,69254,67164,69340,67150,69326,73376,102232,116654,73360,102220,73352,102214,73348,73346,69208,100142,73432,102254,73420,69190,73414,67118,69230,73454,106320,118700,106312,118694,106308,106306,73296,102188,81616,106348,102182,81608,73284,81604,73282,81602,69164,73324,69158,81644,73318,81638,122792,126934,122788,122786,106280,118678,114536,106276,114532,106274,114530,73256,102166,81512,73252,98024,81508,73250,98020,81506,98018,69142,73270,81526,98038,122772,122770,106260,114484,106258,114482,73236,81460,73234,97908,81458,97906,122762,106250,114458,73226,81434,97850,66396,66382,67416,99246,67404,67398,66350,67438,69456,100268,69448,100262,69444,69442,67372,69484,67366,69478,102312,116694,102308,102306,69416,100246,73576,102326,73572,69410,73570,67350,69430,73590,118740,118738,102292,106420,102290,106418,69396,73524,69394,81780,73522,81778,118730,102282,106394,69386,73498,81722,66476,66470,67496,99286,67492,67490,66454,67510,100308,100306,67476,69556,67474,69554,116714])]);$1.raps=$a([$a([802,930,946,818,882,890,826,954,922,986,970,906,778,794,786,914,978,982,980,916,948,932,934,942,940,936,808,812,814,806,822,950,918,790,788,820,884,868,870,878,876,872,840,856,860,862,846,844,836,838,834,866]),$a([718,590,622,558,550,566,534,530,538,570,562,546,610,626,634,762,754,758,630,628,612,614,582,578,706,738,742,740,748,620,556,552,616,744,712,716,708,710,646,654,652,668,664,696,688,656,720,592,600,604,732,734])]);$1.cwtobits=function(){var _G4=$g($1.clusters,$k[--$j]);$1.v=$g(_G4,$k[--$j]);$k[$j++]=Infinity;for(var _G7=0,_G8=17;_G7<_G8;_G7++){$k[$j++]=0}var _GB=$R($s(17),$1.v,2);for(var _GC=0,_GD=_GB.length;_GC<_GD;_GC++){$k[$j++]=$g(_GB,_GC)-48}var _GF=$a();$k[$j++]=$G(_GF,_GF.length-17,17)};$1.raptobits=function(){var _GJ=$g($1.raps,$k[--$j]);$1.v=$g(_GJ,$k[--$j]);$k[$j++]=Infinity;for(var _GM=0,_GN=10;_GM<_GN;_GM++){$k[$j++]=0}var _GQ=$R($s(10),$1.v,2);for(var _GR=0,_GS=_GQ.length;_GR<_GS;_GR++){$k[$j++]=$g(_GQ,_GR)-48}var _GU=$a();$k[$j++]=$G(_GU,_GU.length-10,10)};$1.rwid=$g($a([38,55,82,99]),$f($1.c-1));if($1.c==3&&$1.cca){$1.rwid=72}$1.pixs=$a($1.rwid*$1.r);for(var _Gg=0,_Gf=$f($1.r-1);_Gg<=_Gf;_Gg+=1){$1.i=_Gg;$1.clst=$f($f($1.i+$1.rapl)-1)%3;$k[$j++]=$1.pixs;$k[$j++]=$1.rwid*$1.i;$k[$j++]=Infinity;if($1.c==1){$k[$j++]=$f($f($1.i+$1.rapl)-1)%52;$k[$j++]=0;$1.raptobits();$q($k[--$j]);$k[$j++]=$g($1.cws,$1.i);$k[$j++]=$1.clst;$1.cwtobits();$q($k[--$j]);$k[$j++]=$f($f($1.i+$1.rapr)-1)%52;$k[$j++]=0;$1.raptobits();$q($k[--$j])}if($1.c==2){$k[$j++]=$f($f($1.i+$1.rapl)-1)%52;$k[$j++]=0;$1.raptobits();$q($k[--$j]);$k[$j++]=$g($1.cws,$1.i*2);$k[$j++]=$1.clst;$1.cwtobits();$q($k[--$j]);$k[$j++]=$g($1.cws,$1.i*2+1);$k[$j++]=$1.clst;$1.cwtobits();$q($k[--$j]);$k[$j++]=$f($f($1.i+$1.rapr)-1)%52;$k[$j++]=0;$1.raptobits();$q($k[--$j])}if($1.c==3){if(!$1.cca){$k[$j++]=$f($f($1.i+$1.rapl)-1)%52;$k[$j++]=0;$1.raptobits();$q($k[--$j])}$k[$j++]=$g($1.cws,$1.i*3);$k[$j++]=$1.clst;$1.cwtobits();$q($k[--$j]);$k[$j++]=$f($f($1.i+$1.rapc)-1)%52;$k[$j++]=1;$1.raptobits();$q($k[--$j]);$k[$j++]=$g($1.cws,$1.i*3+1);$k[$j++]=$1.clst;$1.cwtobits();$q($k[--$j]);$k[$j++]=$g($1.cws,$1.i*3+2);$k[$j++]=$1.clst;$1.cwtobits();$q($k[--$j]);$k[$j++]=$f($f($1.i+$1.rapr)-1)%52;$k[$j++]=0;$1.raptobits();$q($k[--$j])}if($1.c==4){$k[$j++]=$f($f($1.i+$1.rapl)-1)%52;$k[$j++]=0;$1.raptobits();$q($k[--$j]);$k[$j++]=$g($1.cws,$1.i*4);$k[$j++]=$1.clst;$1.cwtobits();$q($k[--$j]);$k[$j++]=$g($1.cws,$1.i*4+1);$k[$j++]=$1.clst;$1.cwtobits();$q($k[--$j]);$k[$j++]=$f($f($1.i+$1.rapc)-1)%52;$k[$j++]=1;$1.raptobits();$q($k[--$j]);$k[$j++]=$g($1.cws,$1.i*4+2);$k[$j++]=$1.clst;$1.cwtobits();$q($k[--$j]);$k[$j++]=$g($1.cws,$1.i*4+3);$k[$j++]=$1.clst;$1.cwtobits();$q($k[--$j]);$k[$j++]=$f($f($1.i+$1.rapr)-1)%52;$k[$j++]=0;$1.raptobits();$q($k[--$j])}$k[$j++]=1;var _I9=$a();var _IA=$k[--$j];$P($k[--$j],_IA,_I9)}var _IJ=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",$1.rwid],["pixy",$1.r],["height",$1.r/72*$1.rowmult],["width",$1.rwid/72],["opt",$1.options]]);$k[$j++]=_IJ;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_datamatrix(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.columns=0;$1.rows=0;$1.format="square";$1.version="unset";$1.parse=false;$1.parsefnc=false;$1.encoding="auto";$1.mailmark=false;$1.raw=false;$1.dmre=false;$1.dindmre=false;$1.isodmre=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});if($ne($1.version,"unset")){$x($1.version,"x");$j--;$1.rows=$k[--$j];$j--;$1.columns=$k[--$j]}$1.columns=~~$z($1.columns);$1.rows=~~$z($1.rows);$1.isodmre=$1.dmre;$k[$j++]=Infinity;$k[$j++]=$a([10,10,1,1,5,1]);$k[$j++]=$a([12,12,1,1,7,1]);$k[$j++]=$a([14,14,1,1,10,1]);$k[$j++]=$a([16,16,1,1,12,1]);$k[$j++]=$a([18,18,1,1,14,1]);$k[$j++]=$a([20,20,1,1,18,1]);$k[$j++]=$a([22,22,1,1,20,1]);$k[$j++]=$a([24,24,1,1,24,1]);$k[$j++]=$a([26,26,1,1,28,1]);$k[$j++]=$a([32,32,2,2,36,1]);$k[$j++]=$a([36,36,2,2,42,1]);$k[$j++]=$a([40,40,2,2,48,1]);$k[$j++]=$a([44,44,2,2,56,1]);$k[$j++]=$a([48,48,2,2,68,1]);$k[$j++]=$a([52,52,2,2,84,2]);$k[$j++]=$a([64,64,4,4,112,2]);$k[$j++]=$a([72,72,4,4,144,4]);$k[$j++]=$a([80,80,4,4,192,4]);$k[$j++]=$a([88,88,4,4,224,4]);$k[$j++]=$a([96,96,4,4,272,4]);$k[$j++]=$a([104,104,4,4,336,6]);$k[$j++]=$a([120,120,6,6,408,6]);$k[$j++]=$a([132,132,6,6,496,8]);$k[$j++]=$a([144,144,6,6,620,10]);$k[$j++]=$a([8,18,1,1,7,1]);$k[$j++]=$a([8,32,1,2,11,1]);if($1.dindmre||$1.isodmre){$k[$j++]=$a([8,48,1,2,15,1])}if($1.dindmre||$1.isodmre){$k[$j++]=$a([8,64,1,4,18,1])}if($1.isodmre){$k[$j++]=$a([8,80,1,4,22,1])}if($1.isodmre){$k[$j++]=$a([8,96,1,4,28,1])}if($1.isodmre){$k[$j++]=$a([8,120,1,6,32,1])}if($1.isodmre){$k[$j++]=$a([8,144,1,6,36,1])}$k[$j++]=$a([12,26,1,1,14,1]);$k[$j++]=$a([12,36,1,2,18,1]);if($1.dindmre||$1.isodmre){$k[$j++]=$a([12,64,1,4,27,1])}if($1.isodmre){$k[$j++]=$a([12,88,1,4,36,1])}$k[$j++]=$a([16,36,1,2,24,1]);$k[$j++]=$a([16,48,1,2,28,1]);if($1.dindmre||$1.isodmre){$k[$j++]=$a([16,64,1,4,36,1])}if($1.isodmre){$k[$j++]=$a([20,36,1,2,28,1])}if($1.isodmre){$k[$j++]=$a([20,44,1,2,34,1])}if($1.isodmre){$k[$j++]=$a([20,64,1,4,42,1])}if($1.isodmre){$k[$j++]=$a([22,48,1,2,38,1])}if($1.dindmre){$k[$j++]=$a([24,32,1,2,28,1])}if($1.dindmre){$k[$j++]=$a([24,36,1,2,33,1])}if($1.dindmre||$1.isodmre){$k[$j++]=$a([24,48,1,2,41,1])}if($1.dindmre||$1.isodmre){$k[$j++]=$a([24,64,1,4,46,1])}if($1.dindmre){$k[$j++]=$a([26,32,1,2,32,1])}if($1.dindmre||$1.isodmre){$k[$j++]=$a([26,40,1,2,38,1])}if($1.dindmre||$1.isodmre){$k[$j++]=$a([26,48,1,2,42,1])}if($1.dindmre||$1.isodmre){$k[$j++]=$a([26,64,1,4,50,1])}$1.metrics=$a();$1.urows=$1.rows;$1.ucols=$1.columns;$1.fullcws=$a([]);var _1Z=$1.metrics;for(var _1a=0,_1b=_1Z.length;_1a<_1b;_1a++){$1.m=$g(_1Z,_1a);$1.rows=$g($1.m,0);$1.cols=$g($1.m,1);$1.regh=$g($1.m,2);$1.regv=$g($1.m,3);$1.rscw=$g($1.m,4);$1.rsbl=$g($1.m,5);$1.mrows=$f($1.rows-2*$1.regh);$1.mcols=$f($1.cols-2*$1.regv);$1.ncws=$f(~~($1.mrows*$1.mcols/8)-$1.rscw);$1.okay=true;if($1.urows!=0&&$1.urows!=$1.rows){$1.okay=false}if($1.ucols!=0&&$1.ucols!=$1.cols){$1.okay=false}if($eq($1.format,"square")&&$ne($1.rows,$1.cols)){$1.okay=false}if($eq($1.format,"rectangle")&&$eq($1.rows,$1.cols)){$1.okay=false}if($1.okay){$k[$j++]=Infinity;$q($1.fullcws);$k[$j++]=$1.ncws;$1.fullcws=$a()}}$k[$j++]=Infinity;for(var _2C=0,_2D=1558;_2C<_2D;_2C++){$k[$j++]=1e4}$1.numremcws=$a();var _2F=$1.fullcws;for(var _2G=0,_2H=_2F.length;_2G<_2H;_2G++){$p($1.numremcws,$f($g(_2F,_2G)-1),1)}for(var _2K=1556;_2K>=0;_2K-=1){$1.i=_2K;if($g($1.numremcws,$1.i)!=1){$p($1.numremcws,$1.i,$f($g($1.numremcws,$1.i+1)+1))}}if($1.raw){$1.encoding="raw"}if($eq($1.encoding,"raw")){$1.cws=$a($1.barcode.length);$1.i=0;$1.j=0;for(;;){if($1.i==$1.barcode.length){break}$1.cw=~~$z($G($1.barcode,$1.i+1,3));$p($1.cws,$1.j,$1.cw);$1.i=$1.i+4;$1.j=$1.j+1}$1.cws=$G($1.cws,0,$1.j)}if($eq($1.encoding,"auto")){$1.fnc1=-1;$1.prog=-2;$1.m05=-3;$1.m06=-4;$1.lC=-5;$1.lB=-6;$1.lX=-7;$1.lT=-8;$1.lE=-9;$1.unl=-10;$1.sapp=-11;$1.usft=-12;$1.sft1=-13;$1.sft2=-14;$1.sft3=-15;$1.eci=-16;$1.pad=-17;$1.unlcw=254;var _2p=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["eci",true],["FNC1",$1.fnc1],["PROG",$1.prog]]);$1.fncvals=_2p;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _2s=$k[--$j];$1[$k[--$j]]=_2s;$1.msglen=$1.msg.length;if($1.msglen>=9){$q($G($1.msg,0,7));var _2y=$k[--$j];var _2z=$k[--$j];$k[$j++]=_2y==29;$k[$j++]=_2z;$j--;var _30=$k[--$j];var _31=$k[--$j];var _32=$k[--$j];var _33=$k[--$j];var _34=$k[--$j];var _35=$k[--$j];if(_30&&_31==48&&_32==30&&_33==62&&_34==41&&_35==91){$q($G($1.msg,$1.msglen-2,2));var _39=$k[--$j];var _3A=$k[--$j];if(_39==4&&_3A==30){if($g($1.msg,5)==53){$k[$j++]=Infinity;$k[$j++]=$1.m05;$q($G($1.msg,7,$1.msg.length-9));$1.msg=$a()}if($g($1.msg,5)==54){$k[$j++]=Infinity;$k[$j++]=$1.m06;$q($G($1.msg,7,$1.msg.length-9));$1.msg=$a()}}}}$1.msglen=$1.msg.length;$k[$j++]=Infinity;for(var _3Q=0;_3Q<=128;_3Q+=1){$k[$j++]=_3Q;$k[$j++]=_3Q+1}$k[$j++]=$1.pad;$k[$j++]=129;for(var _3S=0;_3S<=99;_3S+=1){var _3U=$R($s(2),_3S,10);var _3W=$Z($s(2),"00");$P(_3W,2-_3U.length,_3U);$k[$j++]=_3W;$k[$j++]=_3S+130}var _3j=$a([$1.lC,$1.lB,$1.fnc1,$1.sapp,$1.prog,$1.usft,$1.m05,$1.m06,$1.lX,$1.lT,$1.lE,$1.eci]);$k[$j++]=229;for(var _3k=0,_3l=_3j.length;_3k<_3l;_3k++){var _3o=$f($k[--$j]+1);$k[$j++]=$g(_3j,_3k);$k[$j++]=_3o;$k[$j++]=_3o}$j--;$1.Avals=$d();$k[$j++]=Infinity;var _3q=$1.Avals;for(var _3v=_3q.size,_3u=_3q.keys(),_3t=0;_3t<_3v;_3t++){var _3r=_3u.next().value;$k[$j++]=_3r;$k[$j++]=_3q.get(_3r);$k[$j++]=Infinity;var _3w=$k[--$j];var _3x=$k[--$j];$k[$j++]=_3w;$k[$j++]=_3x;var _3y=$a();$k[$j++]=_3y}$1.Avals=$d();$k[$j++]=Infinity;$k[$j++]=$1.sft1;$k[$j++]=0;$k[$j++]=$1.sft2;$k[$j++]=1;$k[$j++]=$1.sft3;$k[$j++]=2;$k[$j++]=32;$k[$j++]=3;for(var _43=48;_43<=57;_43+=1){$k[$j++]=_43;$k[$j++]=_43-44}for(var _44=65;_44<=90;_44+=1){$k[$j++]=_44;$k[$j++]=_44-51}$1.CNvals=$d();$k[$j++]=Infinity;for(var _46=0;_46<=31;_46+=1){$k[$j++]=_46;$k[$j++]=_46}$1.C1vals=$d();$k[$j++]=Infinity;for(var _48=33;_48<=47;_48+=1){$k[$j++]=_48;$k[$j++]=_48-33}for(var _49=58;_49<=64;_49+=1){$k[$j++]=_49;$k[$j++]=_49-43}for(var _4A=91;_4A<=95;_4A+=1){$k[$j++]=_4A;$k[$j++]=_4A-69}$k[$j++]=$1.fnc1;$k[$j++]=27;$k[$j++]=$1.usft;$k[$j++]=30;$1.C2vals=$d();$k[$j++]=Infinity;for(var _4E=96;_4E<=127;_4E+=1){$k[$j++]=_4E;$k[$j++]=_4E-96}$1.C3vals=$d();$k[$j++]=Infinity;var _4G=$1.CNvals;for(var _4L=_4G.size,_4K=_4G.keys(),_4J=0;_4J<_4L;_4J++){var _4H=_4K.next().value;$k[$j++]=_4H;$k[$j++]=_4G.get(_4H);$k[$j++]=Infinity;var _4M=$k[--$j];var _4N=$k[--$j];$k[$j++]=_4M;$k[$j++]=_4N;var _4O=$a();$k[$j++]=_4O}var _4P=$1.C1vals;for(var _4U=_4P.size,_4T=_4P.keys(),_4S=0;_4S<_4U;_4S++){var _4Q=_4T.next().value;$k[$j++]=_4Q;$k[$j++]=_4P.get(_4Q);$k[$j++]=Infinity;var _4V=$k[--$j];var _4W=$k[--$j];$k[$j++]=_4V;$k[$j++]=$g($1.CNvals,$1.sft1);$k[$j++]=_4W;var _4a=$a();$k[$j++]=_4a}var _4b=$1.C2vals;for(var _4g=_4b.size,_4f=_4b.keys(),_4e=0;_4e<_4g;_4e++){var _4c=_4f.next().value;$k[$j++]=_4c;$k[$j++]=_4b.get(_4c);$k[$j++]=Infinity;var _4h=$k[--$j];var _4i=$k[--$j];$k[$j++]=_4h;$k[$j++]=$g($1.CNvals,$1.sft2);$k[$j++]=_4i;var _4m=$a();$k[$j++]=_4m}var _4n=$1.C3vals;for(var _4s=_4n.size,_4r=_4n.keys(),_4q=0;_4q<_4s;_4q++){var _4o=_4r.next().value;$k[$j++]=_4o;$k[$j++]=_4n.get(_4o);$k[$j++]=Infinity;var _4t=$k[--$j];var _4u=$k[--$j];$k[$j++]=_4t;$k[$j++]=$g($1.CNvals,$1.sft3);$k[$j++]=_4u;var _4y=$a();$k[$j++]=_4y}$1.Cvals=$d();$k[$j++]=Infinity;$k[$j++]=$1.sft1;$k[$j++]=0;$k[$j++]=$1.sft2;$k[$j++]=1;$k[$j++]=$1.sft3;$k[$j++]=2;$k[$j++]=32;$k[$j++]=3;for(var _53=48;_53<=57;_53+=1){$k[$j++]=_53;$k[$j++]=_53-44}for(var _54=97;_54<=122;_54+=1){$k[$j++]=_54;$k[$j++]=_54-83}$1.TNvals=$d();$k[$j++]=Infinity;for(var _56=0;_56<=31;_56+=1){$k[$j++]=_56;$k[$j++]=_56}$1.T1vals=$d();$k[$j++]=Infinity;for(var _58=33;_58<=47;_58+=1){$k[$j++]=_58;$k[$j++]=_58-33}for(var _59=58;_59<=64;_59+=1){$k[$j++]=_59;$k[$j++]=_59-43}for(var _5A=91;_5A<=95;_5A+=1){$k[$j++]=_5A;$k[$j++]=_5A-69}$k[$j++]=$1.fnc1;$k[$j++]=27;$k[$j++]=$1.usft;$k[$j++]=30;$1.T2vals=$d();$k[$j++]=Infinity;$k[$j++]=96;$k[$j++]=0;for(var _5E=65;_5E<=90;_5E+=1){$k[$j++]=_5E;$k[$j++]=_5E-64}for(var _5F=123;_5F<=127;_5F+=1){$k[$j++]=_5F;$k[$j++]=_5F-96}$1.T3vals=$d();$k[$j++]=Infinity;var _5H=$1.TNvals;for(var _5M=_5H.size,_5L=_5H.keys(),_5K=0;_5K<_5M;_5K++){var _5I=_5L.next().value;$k[$j++]=_5I;$k[$j++]=_5H.get(_5I);$k[$j++]=Infinity;var _5N=$k[--$j];var _5O=$k[--$j];$k[$j++]=_5N;$k[$j++]=_5O;var _5P=$a();$k[$j++]=_5P}var _5Q=$1.T1vals;for(var _5V=_5Q.size,_5U=_5Q.keys(),_5T=0;_5T<_5V;_5T++){var _5R=_5U.next().value;$k[$j++]=_5R;$k[$j++]=_5Q.get(_5R);$k[$j++]=Infinity;var _5W=$k[--$j];var _5X=$k[--$j];$k[$j++]=_5W;$k[$j++]=$g($1.TNvals,$1.sft1);$k[$j++]=_5X;var _5b=$a();$k[$j++]=_5b}var _5c=$1.T2vals;for(var _5h=_5c.size,_5g=_5c.keys(),_5f=0;_5f<_5h;_5f++){var _5d=_5g.next().value;$k[$j++]=_5d;$k[$j++]=_5c.get(_5d);$k[$j++]=Infinity;var _5i=$k[--$j];var _5j=$k[--$j];$k[$j++]=_5i;$k[$j++]=$g($1.TNvals,$1.sft2);$k[$j++]=_5j;var _5n=$a();$k[$j++]=_5n}var _5o=$1.T3vals;for(var _5t=_5o.size,_5s=_5o.keys(),_5r=0;_5r<_5t;_5r++){var _5p=_5s.next().value;$k[$j++]=_5p;$k[$j++]=_5o.get(_5p);$k[$j++]=Infinity;var _5u=$k[--$j];var _5v=$k[--$j];$k[$j++]=_5u;$k[$j++]=$g($1.TNvals,$1.sft3);$k[$j++]=_5v;var _5z=$a();$k[$j++]=_5z}$1.Tvals=$d();for(var _61=128;_61<=255;_61+=1){$1.i=_61;$k[$j++]=$1.Avals;$k[$j++]=$1.i;$k[$j++]=Infinity;$q($g($1.Avals,$1.usft));$q($g($1.Avals,$1.i-128));var _6A=$a();var _6B=$k[--$j];$p($k[--$j],_6B,_6A);$k[$j++]=$1.Cvals;$k[$j++]=$1.i;$k[$j++]=Infinity;$q($g($1.Cvals,$1.usft));$q($g($1.Cvals,$1.i-128));var _6L=$a();var _6M=$k[--$j];$p($k[--$j],_6M,_6L);$k[$j++]=$1.Tvals;$k[$j++]=$1.i;$k[$j++]=Infinity;$q($g($1.Tvals,$1.usft));$q($g($1.Tvals,$1.i-128));var _6W=$a();var _6X=$k[--$j];$p($k[--$j],_6X,_6W)}$k[$j++]=Infinity;$k[$j++]=13;$k[$j++]=0;$k[$j++]=42;$k[$j++]=1;$k[$j++]=62;$k[$j++]=2;$k[$j++]=32;$k[$j++]=3;for(var _6Z=48;_6Z<=57;_6Z+=1){$k[$j++]=_6Z;$k[$j++]=_6Z-44}for(var _6a=65;_6a<=90;_6a+=1){$k[$j++]=_6a;$k[$j++]=_6a-51}$1.Xvals=$d();$k[$j++]=Infinity;var _6c=$1.Xvals;for(var _6h=_6c.size,_6g=_6c.keys(),_6f=0;_6f<_6h;_6f++){var _6d=_6g.next().value;$k[$j++]=_6d;$k[$j++]=_6c.get(_6d);$k[$j++]=Infinity;var _6i=$k[--$j];var _6j=$k[--$j];$k[$j++]=_6i;$k[$j++]=_6j;var _6k=$a();$k[$j++]=_6k}$1.Xvals=$d();$k[$j++]=Infinity;for(var _6m=64;_6m<=94;_6m+=1){$k[$j++]=_6m;$k[$j++]=_6m-64}$k[$j++]=$1.unl;$k[$j++]=31;for(var _6o=32;_6o<=63;_6o+=1){$k[$j++]=_6o;$k[$j++]=_6o}$1.Evals=$d();$k[$j++]=Infinity;var _6q=$1.Evals;for(var _6v=_6q.size,_6u=_6q.keys(),_6t=0;_6t<_6v;_6t++){var _6r=_6u.next().value;$k[$j++]=_6r;$k[$j++]=_6q.get(_6r);$k[$j++]=Infinity;var _6w=$k[--$j];var _6x=$k[--$j];$k[$j++]=_6w;$k[$j++]=_6x;var _6y=$a();$k[$j++]=_6y}$1.Evals=$d();$k[$j++]=Infinity;for(var _70=0;_70<=255;_70+=1){$k[$j++]=_70;$k[$j++]=_70}$1.Bvals=$d();$k[$j++]=Infinity;var _72=$1.Bvals;for(var _77=_72.size,_76=_72.keys(),_75=0;_75<_77;_75++){var _73=_76.next().value;$k[$j++]=_73;$k[$j++]=_72.get(_73);$k[$j++]=Infinity;var _78=$k[--$j];var _79=$k[--$j];$k[$j++]=_78;$k[$j++]=_79;var _7A=$a();$k[$j++]=_7A}$1.Bvals=$d();$1.encvals=$a([$1.Avals,$1.Cvals,$1.Tvals,$1.Xvals,$1.Evals,$1.Bvals]);$k[$j++]=Infinity;for(var _7K=0,_7L=$1.msglen;_7K<_7L;_7K++){$k[$j++]=0}$k[$j++]=0;$1.numD=$a();$k[$j++]=Infinity;for(var _7O=0,_7P=$1.msglen;_7O<_7P;_7O++){$k[$j++]=0}$k[$j++]=9999;$1.nextXterm=$a();$k[$j++]=Infinity;for(var _7S=0,_7T=$1.msglen;_7S<_7T;_7S++){$k[$j++]=0}$k[$j++]=9999;$1.nextNonX=$a();$k[$j++]=Infinity;for(var _7W=0,_7X=$1.msglen;_7W<_7X;_7W++){$k[$j++]=false}$k[$j++]=false;$1.isECI=$a();for(var _7a=$1.msglen-1;_7a>=0;_7a-=1){$1.i=_7a;$1.barchar=$g($1.msg,$1.i);if($1.barchar>=48&&$1.barchar<=57){$p($1.numD,$1.i,$f($g($1.numD,$1.i+1)+1))}if($1.barchar==13||$1.barchar==42||$1.barchar==62){$p($1.nextXterm,$1.i,0)}else{$p($1.nextXterm,$1.i,$f($g($1.nextXterm,$1.i+1)+1))}var _7x=$g($1.Xvals,$1.barchar)!==undefined;if(!_7x){$p($1.nextNonX,$1.i,0)}else{$p($1.nextNonX,$1.i,$f($g($1.nextNonX,$1.i+1)+1))}$p($1.isECI,$1.i,$1.barchar<=-1e6)}$k[$j++]=Infinity;var _88=$1.nextXterm;for(var _89=0,_8A=_88.length;_89<_8A;_89++){var _8B=$g(_88,_89);$k[$j++]=_8B;if(_8B>1e4){$j--;$k[$j++]=1e4}}$1.nextXterm=$a();$k[$j++]=Infinity;var _8D=$1.nextNonX;for(var _8E=0,_8F=_8D.length;_8E<_8F;_8E++){var _8G=$g(_8D,_8E);$k[$j++]=_8G;if(_8G>1e4){$j--;$k[$j++]=1e4}}$1.nextNonX=$a();$1.isD=function(){$k[$j++]=$1.char>=48&&$1.char<=57};$1.isC=function(){var _8M=$g($1.CNvals,$1.char)!==undefined;$k[$j++]=_8M};$1.isT=function(){var _8P=$g($1.TNvals,$1.char)!==undefined;$k[$j++]=_8P};$1.isX=function(){var _8S=$g($1.Xvals,$1.char)!==undefined;$k[$j++]=_8S};$1.isE=function(){var _8V=$g($1.Evals,$1.char)!==undefined;$k[$j++]=_8V};$1.isEA=function(){$k[$j++]=$1.char>127};$1.isFN=function(){$k[$j++]=$1.char<0};$1.XtermFirst=function(){var _8Y=$k[--$j];$k[$j++]=$lt($g($1.nextXterm,_8Y),$g($1.nextNonX,_8Y))};$1.A=0;$1.C=1;$1.T=2;$1.X=3;$1.E=4;$1.B=5;$1.lookup=function(){$1.ac=1;$1.cc=2;$1.tc=2;$1.xc=2;$1.ec=2;$1.bc=2.25;if($1.mode==$1.A){$1.ac=0;$1.cc=1;$1.tc=1;$1.xc=1;$1.ec=1;$1.bc=1.25}if($1.mode==$1.C){$1.cc=0}if($1.mode==$1.T){$1.tc=0}if($1.mode==$1.X){$1.xc=0}if($1.mode==$1.E){$1.ec=0}if($1.mode==$1.B){$1.bc=0}for(var _8p=0,_8q=1;_8p<_8q;_8p++){if($g($1.isECI,$1.i)){$k[$j++]=$1.A;break}if($1.mailmark&&$1.i<45){$k[$j++]=$1.C;break}$1.k=0;for(;;){if($1.i+$1.k==$1.msglen){var _91=$a(["ac","cc","tc","xc","ec","bc"]);for(var _92=0,_93=_91.length;_92<_93;_92++){var _94=$g(_91,_92);$1[_94]=Math.ceil($1[_94])}var _9B=$a([$1.cc,$1.tc,$1.xc,$1.ec,$1.bc]);$k[$j++]=true;for(var _9C=0,_9D=_9B.length;_9C<_9D;_9C++){var _9G=$k[--$j];$k[$j++]=_9G&&$1.ac<=$g(_9B,_9C)}if($k[--$j]){$k[$j++]=$1.A;break}var _9O=$a([$1.ac,$1.cc,$1.tc,$1.xc,$1.ec]);$k[$j++]=true;for(var _9P=0,_9Q=_9O.length;_9P<_9Q;_9P++){var _9T=$k[--$j];$k[$j++]=_9T&&$1.bc<$g(_9O,_9P)}if($k[--$j]){$k[$j++]=$1.B;break}var _9b=$a([$1.ac,$1.cc,$1.tc,$1.xc,$1.bc]);$k[$j++]=true;for(var _9c=0,_9d=_9b.length;_9c<_9d;_9c++){var _9g=$k[--$j];$k[$j++]=_9g&&$1.ec<$g(_9b,_9c)}if($k[--$j]){$k[$j++]=$1.E;break}var _9o=$a([$1.ac,$1.cc,$1.xc,$1.ec,$1.bc]);$k[$j++]=true;for(var _9p=0,_9q=_9o.length;_9p<_9q;_9p++){var _9t=$k[--$j];$k[$j++]=_9t&&$1.tc<$g(_9o,_9p)}if($k[--$j]){$k[$j++]=$1.T;break}var _A1=$a([$1.ac,$1.cc,$1.tc,$1.ec,$1.bc]);$k[$j++]=true;for(var _A2=0,_A3=_A1.length;_A2<_A3;_A2++){var _A6=$k[--$j];$k[$j++]=_A6&&$1.xc<$g(_A1,_A2)}if($k[--$j]){$k[$j++]=$1.X;break}$k[$j++]=$1.C;break}$1.char=$g($1.msg,$1.i+$1.k);$k[$j++]="ac";$k[$j++]=$1.ac;$1.isD();if($k[--$j]){var _AG=$k[--$j];$k[$j++]=$f(_AG+1/2)}else{$1.isEA();if($k[--$j]){var _AI=$k[--$j];$k[$j++]=Math.ceil(_AI)+2}else{var _AJ=$k[--$j];$k[$j++]=Math.ceil(_AJ)+1}}var _AK=$k[--$j];$1[$k[--$j]]=_AK;$k[$j++]="cc";$k[$j++]=$1.cc;$1.isC();if($k[--$j]){var _AO=$k[--$j];$k[$j++]=$f(_AO+.6666667)}else{$1.isEA();if($k[--$j]){var _AQ=$k[--$j];$k[$j++]=$f(_AQ+2.6666667)}else{var _AR=$k[--$j];$k[$j++]=$f(_AR+1.3333334)}}var _AS=$k[--$j];$1[$k[--$j]]=_AS;$k[$j++]="tc";$k[$j++]=$1.tc;$1.isT();if($k[--$j]){var _AW=$k[--$j];$k[$j++]=$f(_AW+.6666667)}else{$1.isEA();if($k[--$j]){var _AY=$k[--$j];$k[$j++]=$f(_AY+2.6666667)}else{var _AZ=$k[--$j];$k[$j++]=$f(_AZ+1.3333334)}}var _Aa=$k[--$j];$1[$k[--$j]]=_Aa;$k[$j++]="xc";$k[$j++]=$1.xc;$1.isX();if($k[--$j]){var _Ae=$k[--$j];$k[$j++]=$f(_Ae+.6666667)}else{$1.isEA();if($k[--$j]){var _Ag=$k[--$j];$k[$j++]=$f(_Ag+4.3333334)}else{var _Ah=$k[--$j];$k[$j++]=$f(_Ah+3.3333334)}}var _Ai=$k[--$j];$1[$k[--$j]]=_Ai;$k[$j++]="ec";$k[$j++]=$1.ec;$1.isE();if($k[--$j]){var _Am=$k[--$j];$k[$j++]=$f(_Am+3/4)}else{$1.isEA();if($k[--$j]){var _Ao=$k[--$j];$k[$j++]=$f(_Ao+17/4)}else{var _Ap=$k[--$j];$k[$j++]=$f(_Ap+13/4)}}var _Aq=$k[--$j];$1[$k[--$j]]=_Aq;$k[$j++]="bc";$k[$j++]=$1.bc;$1.isFN();if($k[--$j]){var _Au=$k[--$j];$k[$j++]=$f(_Au+4)}else{var _Av=$k[--$j];$k[$j++]=$f(_Av+1)}var _Aw=$k[--$j];$1[$k[--$j]]=_Aw;if($1.k>=4){var _B4=$a([$1.cc,$1.tc,$1.xc,$1.ec,$1.bc]);$k[$j++]=true;for(var _B5=0,_B6=_B4.length;_B5<_B6;_B5++){var _B9=$k[--$j];$k[$j++]=_B9&&$1.ac+1<=$g(_B4,_B5)}if($k[--$j]){$k[$j++]=$1.A;break}if($1.bc+1<=$1.ac){$k[$j++]=$1.B;break}var _BJ=$a([$1.cc,$1.tc,$1.xc,$1.ec]);$k[$j++]=true;for(var _BK=0,_BL=_BJ.length;_BK<_BL;_BK++){var _BO=$k[--$j];$k[$j++]=_BO&&$1.bc+1<$g(_BJ,_BK)}if($k[--$j]){$k[$j++]=$1.B;break}var _BW=$a([$1.ac,$1.cc,$1.tc,$1.xc,$1.bc]);$k[$j++]=true;for(var _BX=0,_BY=_BW.length;_BX<_BY;_BX++){var _Bb=$k[--$j];$k[$j++]=_Bb&&$1.ec+1<$g(_BW,_BX)}if($k[--$j]){$k[$j++]=$1.E;break}var _Bj=$a([$1.ac,$1.cc,$1.xc,$1.ec,$1.bc]);$k[$j++]=true;for(var _Bk=0,_Bl=_Bj.length;_Bk<_Bl;_Bk++){var _Bo=$k[--$j];$k[$j++]=_Bo&&$1.tc+1<$g(_Bj,_Bk)}if($k[--$j]){$k[$j++]=$1.T;break}var _Bw=$a([$1.ac,$1.cc,$1.tc,$1.ec,$1.bc]);$k[$j++]=true;for(var _Bx=0,_By=_Bw.length;_Bx<_By;_Bx++){var _C1=$k[--$j];$k[$j++]=_C1&&$1.xc+1<$g(_Bw,_Bx)}if($k[--$j]){$k[$j++]=$1.X;break}var _C8=$a([$1.ac,$1.tc,$1.ec,$1.bc]);$k[$j++]=true;for(var _C9=0,_CA=_C8.length;_C9<_CA;_C9++){var _CD=$k[--$j];$k[$j++]=_CD&&$1.cc+1<$g(_C8,_C9)}if($k[--$j]){if($1.cc<$1.xc){$k[$j++]=$1.C;break}if($1.cc==$1.xc){$k[$j++]=$1.i+$1.k+1;$1.XtermFirst();if($k[--$j]){$k[$j++]=$1.X;break}else{$k[$j++]=$1.C;break}}}}$1.k=$1.k+1}}};$1.addtocws=function(){var _CQ=$k[--$j];$P($1.cws,$1.j,_CQ);$1.j=_CQ.length+$1.j};$1.ECItocws=function(){var _CV=$f(-$k[--$j]-1e6);$k[$j++]=_CV;if(_CV<=126){var _CW=$k[--$j];$k[$j++]=$f(_CW+1);$r($a(1))}else{var _CY=$k[--$j];$k[$j++]=_CY;if(_CY<=16382){var _Ca=$f($k[--$j]-127);$k[$j++]=~~(_Ca/254)+128;$k[$j++]=$f(_Ca%254+1);$r($a(2))}else{var _Cd=$f($k[--$j]-16383);$k[$j++]=~~(_Cd/64516)+192;$k[$j++]=~~(_Cd/254)%254+1;$k[$j++]=$f(_Cd%254+1);$r($a(3))}}};$1.encA=function(){for(var _Cf=0,_Cg=1;_Cf<_Cg;_Cf++){if($g($1.isECI,$1.i)){$k[$j++]=$g($1.Avals,$1.eci);$1.addtocws();$k[$j++]=$g($1.msg,$1.i);$1.ECItocws();$1.addtocws();$1.i=$1.i+1;break}if($g($1.numD,$1.i)>=2){var _Cu=$s(2);$p(_Cu,0,$g($1.msg,$1.i));$p(_Cu,1,$g($1.msg,$1.i+1));$k[$j++]=$g($1.Avals,_Cu);$1.addtocws();$1.i=$1.i+2;break}$k[$j++]="newmode";$1.lookup();var _D4=$k[--$j];$1[$k[--$j]]=_D4;if($1.newmode!=$1.mode){$k[$j++]=$g($1.Avals,$g($a([-1,$1.lC,$1.lT,$1.lX,$1.lE,$1.lB]),$1.newmode));$1.addtocws();$1.mode=$1.newmode;break}$k[$j++]=$g($1.Avals,$g($1.msg,$1.i));$1.addtocws();$1.i=$1.i+1;break}};$1.CTXvalstocws=function(){$1.in=$k[--$j];$k[$j++]=Infinity;for(var _DS=0,_DR=$1.in.length-1;_DS<=_DR;_DS+=3){var _DU=$G($1.in,_DS,3);$k[$j++]=0;for(var _DV=0,_DW=_DU.length;_DV<_DW;_DV++){var _DY=$k[--$j];$k[$j++]=$f(_DY+$g(_DU,_DV))*40}var _Da=~~($k[--$j]/40)+1;$k[$j++]=~~(_Da/256);$k[$j++]=_Da%256}$r($a($m()));var _Dd=$k[--$j];var _De=$k[--$j];$k[$j++]=_Dd;$k[$j++]=_De;$j--};$1.encCTX=function(){$1.p=0;$1.ctxvals=$a(2500);$1.done=false;for(;;){if($1.i==$1.msglen){break}var _Do=$g($g($1.encvals,$1.mode),$g($1.msg,$1.i))!==undefined;if(!_Do){break}if($1.p%3==0){$k[$j++]="newmode";$1.lookup();var _Dq=$k[--$j];$1[$k[--$j]]=_Dq;if($ne($1.newmode,$1.mode)){$k[$j++]=$G($1.ctxvals,0,$1.p);$1.CTXvalstocws();$1.addtocws();$k[$j++]=$a([$1.unlcw]);$1.addtocws();if($1.newmode!=$1.A){$k[$j++]=$g($1.Avals,$g($a([-1,$1.lC,$1.lT,$1.lX,$1.lE,$1.lB]),$1.newmode));$1.addtocws()}$1.mode=$1.newmode;$1.done=true;break}if($1.msglen-$1.i<=3){$1.remcws=$g($1.numremcws,$1.j+~~($1.p/3)*2);$k[$j++]=Infinity;var _EM=$G($1.msg,$1.i,$1.msglen-$1.i);for(var _EN=0,_EO=_EM.length;_EN<_EO;_EN++){var _EP=$g(_EM,_EN);var _ET=$g($g($1.encvals,$1.mode),_EP)!==undefined;$k[$j++]=_EP;if(_ET){$q($g($g($1.encvals,$1.mode),$k[--$j]))}else{$j--;$k[$j++]=-1;$k[$j++]=-1;$k[$j++]=-1;$k[$j++]=-1}}$1.remvals=$a();if($1.remcws==2&&$1.remvals.length==3){$k[$j++]=Infinity;$q($G($1.ctxvals,0,$1.p));$q($1.remvals);var _Eg=$a();$k[$j++]=_Eg;$1.CTXvalstocws();$1.addtocws();$1.mode=$1.A;$1.i=$1.msglen;$1.done=true;break}if($1.remcws==2&&$1.remvals.length==2&&$1.mode!=$1.X){$k[$j++]=Infinity;$q($G($1.ctxvals,0,$1.p));$q($1.remvals);$q($g($g($1.encvals,$1.mode),$1.sft1));var _Ew=$a();$k[$j++]=_Ew;$1.CTXvalstocws();$1.addtocws();$1.mode=$1.A;$1.i=$1.msglen;$1.done=true;break}if($1.remcws==2&&$1.remvals.length==1){$k[$j++]=$G($1.ctxvals,0,$1.p);$1.CTXvalstocws();$1.addtocws();$k[$j++]=$a([$1.unlcw]);$1.addtocws();$k[$j++]=$g($1.Avals,$g($1.msg,$1.i));$1.addtocws();$1.mode=$1.A;$1.i=$1.msglen;$1.done=true;break}if($1.remcws==1&&$1.remvals.length==1){$k[$j++]=$G($1.ctxvals,0,$1.p);$1.CTXvalstocws();$1.addtocws();$k[$j++]=$g($1.Avals,$g($1.msg,$1.i));$1.addtocws();$1.mode=$1.A;$1.i=$1.msglen;$1.done=true;break}}}var _FV=$g($g($1.encvals,$1.mode),$g($1.msg,$1.i));$P($1.ctxvals,$1.p,_FV);$1.p=_FV.length+$1.p;$1.i=$1.i+1}if(!$1.done){for(;;){if($1.p%3==0){break}$1.i=$1.i-1;$1.p=$1.p-$g($g($1.encvals,$1.mode),$g($1.msg,$1.i)).length}$k[$j++]=Infinity;$q($G($1.ctxvals,0,$1.p));var _Fo=$a();$k[$j++]=_Fo;$1.CTXvalstocws();$1.addtocws();$k[$j++]=$a([$1.unlcw]);$1.addtocws();$1.mode=$1.A;if($1.i!=$1.msglen&&$nt($g($1.isECI,$1.i))){if($g($1.numD,$1.i)>=2){var _G0=$s(2);$p(_G0,0,$g($1.msg,$1.i));$p(_G0,1,$g($1.msg,$1.i+1));$k[$j++]=$g($1.Avals,_G0);$1.addtocws();$1.i=$1.i+2}else{$k[$j++]=$g($1.Avals,$g($1.msg,$1.i));$1.addtocws();$1.i=$1.i+1}}}};$1.Evalstocws=function(){$1.in=$k[--$j];$1.inlen=$1.in.length;$1.outlen=~~Math.ceil($1.in.length/4*3);$k[$j++]=Infinity;$q($1.in);$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$1.in=$a();$k[$j++]=Infinity;for(var _GN=0,_GM=$1.inlen-1;_GN<=_GM;_GN+=4){var _GP=$G($1.in,_GN,4);$k[$j++]=0;for(var _GQ=0,_GR=_GP.length;_GQ<_GR;_GQ++){var _GT=$k[--$j];$k[$j++]=$or(_GT,$g(_GP,_GQ))<<6}var _GV=$k[--$j]>>>6;$k[$j++]=_GV>>>16&255;$k[$j++]=_GV>>>8&255;$k[$j++]=_GV&255}$r($a($m()));var _GY=$k[--$j];var _GZ=$k[--$j];$k[$j++]=_GY;$k[$j++]=_GZ;$j--;var _Gc=$G($k[--$j],0,$1.outlen);$k[$j++]=_Gc};$1.encE=function(){$1.p=0;$1.edifactvals=$a(2100);for(;;){if($1.i==$1.msglen){break}var _Gk=$g($1.Evals,$g($1.msg,$1.i))!==undefined;if(!_Gk){break}if($1.p%4==0){if($1.msglen-$1.i<=2){$1.remcws=$g($1.numremcws,$1.j+~~($1.p/4)*3);$k[$j++]=Infinity;var _Gw=$G($1.msg,$1.i,$1.msglen-$1.i);for(var _Gx=0,_Gy=_Gw.length;_Gx<_Gy;_Gx++){$q($g($1.Avals,$g(_Gw,_Gx)))}$1.remvals=$a();if(($1.remcws==1||$1.remcws==2)&&$1.remvals.length<=$1.remcws){$k[$j++]=$G($1.edifactvals,0,$1.p);$1.Evalstocws();$1.addtocws();$k[$j++]=$1.remvals;$1.addtocws();$1.mode=$1.A;$1.i=$1.msglen;break}}$1.lookup();if($k[--$j]!=$1.mode){break}}var _HJ=$g($1.Evals,$g($1.msg,$1.i));$P($1.edifactvals,$1.p,_HJ);$1.p=_HJ.length+$1.p;$1.i=$1.i+1}if($1.mode!=$1.A){$1.remcws=$f($g($1.numremcws,$1.j+~~($1.p/4)*3-1)-1);if($1.p%4!=0||$1.i!=$1.msglen||$1.remcws>=3){var _Ha=$g($1.Evals,$1.unl);$P($1.edifactvals,$1.p,_Ha);$1.p=_Ha.length+$1.p}$k[$j++]=$G($1.edifactvals,0,$1.p);$1.Evalstocws();$1.addtocws();$1.mode=$1.A;if($1.i!=$1.msglen&&$nt($g($1.isECI,$1.i))){if($g($1.numD,$1.i)>=2){var _Hq=$s(2);$p(_Hq,0,$g($1.msg,$1.i));$p(_Hq,1,$g($1.msg,$1.i+1));$k[$j++]=$g($1.Avals,_Hq);$1.addtocws();$1.i=$1.i+2}else{$k[$j++]=$g($1.Avals,$g($1.msg,$1.i));$1.addtocws();$1.i=$1.i+1}}}};$1.encB=function(){$1.p=0;$1.bvals=$a(1558);for(;;){if($1.i==$1.msglen){break}$1.lookup();if($k[--$j]!=$1.mode){break}$p($1.bvals,$1.p,$g($1.msg,$1.i));$1.p=$1.p+1;$1.i=$1.i+1}$1.remcws=$f($g($1.numremcws,$1.j+$1.p)-1);$k[$j++]=Infinity;if($1.remcws==0&&$1.i==$1.msglen){$k[$j++]=0}else{if($1.p<250){$k[$j++]=$1.p}else{$k[$j++]=~~($1.p/250)+249;$k[$j++]=$1.p%250}}$q($G($1.bvals,0,$1.p));$1.bvals=$a();for(var _IZ=0,_IY=$1.bvals.length-1;_IZ<=_IY;_IZ+=1){$1.p=_IZ;var _If=$f(($1.j+$1.p+1)*149%255+1+$g($1.bvals,$1.p));$k[$j++]=_If;if(_If>=256){var _Ig=$k[--$j];$k[$j++]=$f(_Ig-256)}$p($1.bvals,$1.p,$k[--$j])}$k[$j++]=$1.bvals;$1.addtocws();$1.mode=$1.A};$1.cws=$a(1558);$1.mode=$1.A;$1.i=0;$1.j=0;for(;;){if($1.i>=$1.msglen){break}if($1[$g($a(["encA","encCTX","encCTX","encCTX","encE","encB"]),$1.mode)]()===true){break}}$1.cws=$G($1.cws,0,$1.j)}$1.datlen=$1.cws.length;$1.remcws=$f($g($1.numremcws,$1.j-1)-1);if($1.remcws>0){$k[$j++]=Infinity;$q($1.cws);for(var _J4=0,_J5=$1.remcws;_J4<_J5;_J4++){$k[$j++]=129}$1.cws=$a();for(var _JB=$1.datlen+1,_JA=$f($f($1.datlen+$1.remcws)-1);_JB<=_JA;_JB+=1){$1.i=_JB;var _JD=($1.i+1)*149%253+1+129;$k[$j++]=_JD;if(_JD>254){var _JE=$k[--$j];$k[$j++]=$f(_JE-254)}$p($1.cws,$1.i,$k[--$j])}}$1.i=0;for(;;){$1.m=$g($1.metrics,$1.i);$1.rows=$g($1.m,0);$1.cols=$g($1.m,1);$1.regh=$g($1.m,2);$1.regv=$g($1.m,3);$1.rscw=$g($1.m,4);$1.rsbl=$g($1.m,5);$1.mrows=$f($1.rows-2*$1.regh);$1.mcols=$f($1.cols-2*$1.regv);$1.rrows=~~($1.mrows/$1.regh);$1.rcols=~~($1.mcols/$1.regv);$1.ncws=$f(~~($1.mrows*$1.mcols/8)-$1.rscw);$1.okay=true;if($1.cws.length!=$1.ncws){$1.okay=false}if($1.urows!=0&&$1.urows!=$1.rows){$1.okay=false}if($1.ucols!=0&&$1.ucols!=$1.cols){$1.okay=false}if($eq($1.format,"square")&&$ne($1.rows,$1.cols)){$1.okay=false}if($eq($1.format,"rectangle")&&$eq($1.rows,$1.cols)){$1.okay=false}if($1.okay){break}$1.i=$1.i+1}$1.cwbs=$a($1.rsbl);$1.ecbs=$a($1.rsbl);for(var _K4=0,_K3=$f($1.rsbl-1);_K4<=_K3;_K4+=1){$1.i=_K4;if($1.cws.length!=1558){$1.cwbsize=~~($1.cws.length/$1.rsbl)}else{if($1.i<=7){$1.cwbsize=156}else{$1.cwbsize=155}}$1.cwb=$a($1.cwbsize);for(var _KD=0,_KC=$1.cwbsize-1;_KD<=_KC;_KD+=1){$1.j=_KD;$p($1.cwb,$1.j,$g($1.cws,$f($1.j*$1.rsbl+$1.i)))}$p($1.cwbs,$1.i,$1.cwb);$k[$j++]=$1.ecbs;$k[$j++]=$1.i;$k[$j++]=Infinity;for(var _KS=0,_KT=~~($1.rscw/$1.rsbl);_KS<_KT;_KS++){$k[$j++]=0}var _KU=$a();var _KV=$k[--$j];$p($k[--$j],_KV,_KU)}$k[$j++]=Infinity;$k[$j++]=1;for(var _KX=0,_KY=255;_KX<_KY;_KX++){var _KZ=$k[--$j];var _Ka=_KZ*2;$k[$j++]=_KZ;$k[$j++]=_Ka;if(_Ka>=256){var _Kb=$k[--$j];$k[$j++]=_Kb^301}}$1.rsalog=$a();$1.rslog=$a(256);for(var _Ke=1;_Ke<=255;_Ke+=1){$p($1.rslog,$g($1.rsalog,_Ke),_Ke)}$1.rsprod=function(){var _Ki=$k[--$j];var _Kj=$k[--$j];$k[$j++]=_Kj;$k[$j++]=_Ki;if(_Ki!=0&&_Kj!=0){var _Km=$g($1.rslog,$k[--$j]);var _Kr=$g($1.rsalog,$f(_Km+$g($1.rslog,$k[--$j]))%255);$k[$j++]=_Kr}else{$j-=2;$k[$j++]=0}};$k[$j++]=Infinity;$k[$j++]=1;for(var _Ku=0,_Kv=~~($1.rscw/$1.rsbl);_Ku<_Kv;_Ku++){$k[$j++]=0}$1.coeffs=$a();for(var _L0=1,_Kz=~~($1.rscw/$1.rsbl);_L0<=_Kz;_L0+=1){$1.i=_L0;$p($1.coeffs,$1.i,$g($1.coeffs,$1.i-1));for(var _L7=$1.i-1;_L7>=1;_L7-=1){$1.j=_L7;$k[$j++]=$1.coeffs;$k[$j++]=$1.j;$k[$j++]=$g($1.coeffs,$1.j-1);$k[$j++]=$g($1.coeffs,$1.j);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _LJ=$k[--$j];var _LK=$k[--$j];var _LL=$k[--$j];$p($k[--$j],_LL,$xo(_LK,_LJ))}$k[$j++]=$1.coeffs;$k[$j++]=0;$k[$j++]=$g($1.coeffs,0);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _LT=$k[--$j];var _LU=$k[--$j];$p($k[--$j],_LU,_LT)}$1.coeffs=$G($1.coeffs,0,$1.coeffs.length-1);for(var _Lb=0,_La=$1.cwbs.length-1;_Lb<=_La;_Lb+=1){$1.i=_Lb;$1.cwb=$g($1.cwbs,$1.i);$1.ecb=$g($1.ecbs,$1.i);for(var _Lk=0,_Lj=$1.cwb.length-1;_Lk<=_Lj;_Lk+=1){$1.t=$xo($g($1.cwb,_Lk),$g($1.ecb,0));for(var _Lq=$1.ecb.length-1;_Lq>=0;_Lq-=1){$1.j=_Lq;$1.p=$1.ecb.length-$1.j-1;$k[$j++]=$1.ecb;$k[$j++]=$1.p;$k[$j++]=$1.t;$k[$j++]=$g($1.coeffs,$1.j);$1.rsprod();var _Lz=$k[--$j];var _M0=$k[--$j];$p($k[--$j],_M0,_Lz);if($1.j>0){$p($1.ecb,$1.p,$xo($g($1.ecb,$1.p+1),$g($1.ecb,$1.p)))}}}}if($1.ncws==1558){$k[$j++]=Infinity;var _MD=$G($1.ecbs,8,2);for(var _ME=0,_MF=_MD.length;_ME<_MF;_ME++){$k[$j++]=$g(_MD,_ME)}var _MI=$G($1.ecbs,0,8);for(var _MJ=0,_MK=_MI.length;_MJ<_MK;_MJ++){$k[$j++]=$g(_MI,_MJ)}$1.ecbs=$a()}$k[$j++]=Infinity;var _MN=$1.cws;for(var _MO=0,_MP=_MN.length;_MO<_MP;_MO++){$k[$j++]=$g(_MN,_MO)}for(var _MS=0,_MT=$1.rscw;_MS<_MT;_MS++){$k[$j++]=0}$1.cws=$a();for(var _MX=0,_MW=$f($1.rscw-1);_MX<=_MW;_MX+=1){$1.i=_MX;$p($1.cws,$f($1.ncws+$1.i),$g($g($1.ecbs,$1.i%$1.rsbl),~~($1.i/$1.rsbl)))}$1.module=function(){var _Mi=$k[--$j];var _Mj=$k[--$j];var _Mk=$k[--$j];var _Mn=$Z($s(8),"00000000");var _Mp=$R($s(8),$k[--$j],2);$P(_Mn,8-_Mp.length,_Mp);$k[$j++]=_Mk;$k[$j++]=_Mj;$k[$j++]=_Mi;$k[$j++]=_Mn;for(var _Mq=7;_Mq>=0;_Mq-=1){var _Mr=$k[--$j];$k[$j++]=$f($g(_Mr,_Mq)-48);$k[$j++]=_Mr}$j--;var _Mt=$k[--$j];var _Mu=$k[--$j];var _Mv=$k[--$j];var _Mw=$k[--$j];var _Mx=$k[--$j];var _My=$k[--$j];var _Mz=$k[--$j];var _N0=$k[--$j];var _N1=$k[--$j];var _N2=$k[--$j];var _N3=$k[--$j];$k[$j++]=_N0;$k[$j++]=_Mz;$k[$j++]=_My;$k[$j++]=_Mx;$k[$j++]=_Mw;$k[$j++]=_Mv;$k[$j++]=_Mu;$k[$j++]=_Mt;$k[$j++]=_N3;$k[$j++]=_N2;$F(_N1,function(){if($k[--$j]()===true){return true}var _N5=$k[--$j];var _N6=$k[--$j];$k[$j++]=_N6;$k[$j++]=_N5;if(_N6<0){var _N7=$k[--$j];var _N8=$k[--$j];$k[$j++]=$f(_N8+$1.mrows);$k[$j++]=$f(_N7+$f(4-$f($1.mrows+4)%8))}var _NB=$k[--$j];$k[$j++]=_NB;if(_NB<0){var _ND=$k[--$j];var _NE=$k[--$j];$k[$j++]=$f(_NE+$f(4-$f($1.mcols+4)%8));$k[$j++]=$f(_ND+$1.mcols)}var _NG=$k[--$j];var _NH=$k[--$j];$k[$j++]=_NH;$k[$j++]=_NG;if(_NH>=$1.mrows){var _NJ=$k[--$j];var _NK=$k[--$j];$k[$j++]=$f(_NK-$1.mrows);$k[$j++]=_NJ}var _NM=$k[--$j];var _NN=$k[--$j];var _NQ=$k[--$j];var _NR=$k[--$j];$p($1.mmat,$f(_NM+_NN*$1.mcols),$k[--$j]);$k[$j++]=_NR;$k[$j++]=_NQ})};var _Nj=$a([function(){var _NT=$k[--$j];var _NU=$k[--$j];$k[$j++]=_NU;$k[$j++]=_NT;$k[$j++]=$f(_NU-2);$k[$j++]=$f(_NT-2)},function(){var _NV=$k[--$j];var _NW=$k[--$j];$k[$j++]=_NW;$k[$j++]=_NV;$k[$j++]=$f(_NW-2);$k[$j++]=$f(_NV-1)},function(){var _NX=$k[--$j];var _NY=$k[--$j];$k[$j++]=_NY;$k[$j++]=_NX;$k[$j++]=$f(_NY-1);$k[$j++]=$f(_NX-2)},function(){var _NZ=$k[--$j];var _Na=$k[--$j];$k[$j++]=_Na;$k[$j++]=_NZ;$k[$j++]=$f(_Na-1);$k[$j++]=$f(_NZ-1)},function(){var _Nb=$k[--$j];var _Nc=$k[--$j];$k[$j++]=_Nc;$k[$j++]=_Nb;$k[$j++]=$f(_Nc-1);$k[$j++]=_Nb},function(){var _Nd=$k[--$j];var _Ne=$k[--$j];$k[$j++]=_Ne;$k[$j++]=_Nd;$k[$j++]=_Ne;$k[$j++]=$f(_Nd-2)},function(){var _Nf=$k[--$j];var _Ng=$k[--$j];$k[$j++]=_Ng;$k[$j++]=_Nf;$k[$j++]=_Ng;$k[$j++]=$f(_Nf-1)},function(){var _Nh=$k[--$j];var _Ni=$k[--$j];$k[$j++]=_Ni;$k[$j++]=_Nh;$k[$j++]=_Ni;$k[$j++]=_Nh}]);$1.dmn=_Nj;var _Ns=$a([function(){$k[$j++]=$f($1.mrows-1);$k[$j++]=0},function(){$k[$j++]=$f($1.mrows-1);$k[$j++]=1},function(){$k[$j++]=$f($1.mrows-1);$k[$j++]=2},function(){$k[$j++]=0;$k[$j++]=$f($1.mcols-2)},function(){$k[$j++]=0;$k[$j++]=$f($1.mcols-1)},function(){$k[$j++]=1;$k[$j++]=$f($1.mcols-1)},function(){$k[$j++]=2;$k[$j++]=$f($1.mcols-1)},function(){$k[$j++]=3;$k[$j++]=$f($1.mcols-1)}]);$1.dmc1=_Ns;var _O1=$a([function(){$k[$j++]=$f($1.mrows-3);$k[$j++]=0},function(){$k[$j++]=$f($1.mrows-2);$k[$j++]=0},function(){$k[$j++]=$f($1.mrows-1);$k[$j++]=0},function(){$k[$j++]=0;$k[$j++]=$f($1.mcols-4)},function(){$k[$j++]=0;$k[$j++]=$f($1.mcols-3)},function(){$k[$j++]=0;$k[$j++]=$f($1.mcols-2)},function(){$k[$j++]=0;$k[$j++]=$f($1.mcols-1)},function(){$k[$j++]=1;$k[$j++]=$f($1.mcols-1)}]);$1.dmc2=_O1;var _OA=$a([function(){$k[$j++]=$f($1.mrows-3);$k[$j++]=0},function(){$k[$j++]=$f($1.mrows-2);$k[$j++]=0},function(){$k[$j++]=$f($1.mrows-1);$k[$j++]=0},function(){$k[$j++]=0;$k[$j++]=$f($1.mcols-2)},function(){$k[$j++]=0;$k[$j++]=$f($1.mcols-1)},function(){$k[$j++]=1;$k[$j++]=$f($1.mcols-1)},function(){$k[$j++]=2;$k[$j++]=$f($1.mcols-1)},function(){$k[$j++]=3;$k[$j++]=$f($1.mcols-1)}]);$1.dmc3=_OA;var _OK=$a([function(){$k[$j++]=$f($1.mrows-1);$k[$j++]=0},function(){$k[$j++]=$f($1.mrows-1);$k[$j++]=$f($1.mcols-1)},function(){$k[$j++]=0;$k[$j++]=$f($1.mcols-3)},function(){$k[$j++]=0;$k[$j++]=$f($1.mcols-2)},function(){$k[$j++]=0;$k[$j++]=$f($1.mcols-1)},function(){$k[$j++]=1;$k[$j++]=$f($1.mcols-3)},function(){$k[$j++]=1;$k[$j++]=$f($1.mcols-2)},function(){$k[$j++]=1;$k[$j++]=$f($1.mcols-1)}]);$1.dmc4=_OK;$k[$j++]=Infinity;for(var _ON=0,_OO=$1.mrows*$1.mcols;_ON<_OO;_ON++){$k[$j++]=-1}$1.mmat=$a();for(var _OR=$1.cws.length-1;_OR>=0;_OR-=1){$k[$j++]=$g($1.cws,_OR)}$k[$j++]=4;$k[$j++]=0;for(;;){var _OU=$k[--$j];var _OV=$k[--$j];$k[$j++]=_OV;$k[$j++]=_OU;if(_OU==0&&_OV==$1.mrows){$k[$j++]=$1.dmc1;$1.module()}var _OY=$k[--$j];var _OZ=$k[--$j];$k[$j++]=_OZ;$k[$j++]=_OY;if(_OY==0&&_OZ==$f($1.mrows-2)&&$1.mcols%4!=0){$k[$j++]=$1.dmc2;$1.module()}var _Od=$k[--$j];var _Oe=$k[--$j];$k[$j++]=_Oe;$k[$j++]=_Od;if(_Od==0&&_Oe==$f($1.mrows-2)&&$1.mcols%8==4){$k[$j++]=$1.dmc3;$1.module()}var _Oi=$k[--$j];var _Oj=$k[--$j];$k[$j++]=_Oj;$k[$j++]=_Oi;if(_Oi==2&&_Oj==$f($1.mrows+4)&&$1.mcols%8==0){$k[$j++]=$1.dmc4;$1.module()}for(;;){var _On=$k[--$j];var _Oo=$k[--$j];$k[$j++]=_Oo;$k[$j++]=_On;if(_On>=0&&_Oo<$1.mrows){var _Oq=$k[--$j];var _Or=$k[--$j];$k[$j++]=_Or;$k[$j++]=_Oq;if($g($1.mmat,$f(_Oq+_Or*$1.mcols))==-1){$k[$j++]=$1.dmn;$1.module()}}var _Ow=$k[--$j];var _Ox=$k[--$j];$k[$j++]=$f(_Ox-2);$k[$j++]=$f(_Ow+2);if(!($f(_Ow+2)<$1.mcols&&$f(_Ox-2)>=0)){break}}var _Oz=$k[--$j];var _P0=$k[--$j];$k[$j++]=$f(_P0+1);$k[$j++]=$f(_Oz+3);for(;;){var _P1=$k[--$j];var _P2=$k[--$j];$k[$j++]=_P2;$k[$j++]=_P1;if(_P1<$1.mcols&&_P2>=0){var _P4=$k[--$j];var _P5=$k[--$j];$k[$j++]=_P5;$k[$j++]=_P4;if($g($1.mmat,$f(_P4+_P5*$1.mcols))==-1){$k[$j++]=$1.dmn;$1.module()}}var _PA=$k[--$j];var _PB=$k[--$j];$k[$j++]=$f(_PB+2);$k[$j++]=$f(_PA-2);if(!($f(_PA-2)>=0&&$f(_PB+2)<$1.mrows)){break}}var _PD=$k[--$j];var _PE=$k[--$j];$k[$j++]=$f(_PE+3);$k[$j++]=$f(_PD+1);if(!($f(_PD+1)<$1.mcols||$f(_PE+3)<$1.mrows)){$j-=2;break}}if($g($1.mmat,$f($1.mrows*$1.mcols-1))==-1){$P($1.mmat,$f($1.mrows*$f($1.mcols-1)-2),$a([1,0]));$P($1.mmat,$f($1.mrows*$1.mcols-2),$a([0,1]))}$1.pixs=$a($1.rows*$1.cols);$1.cwpos=0;for(var _PY=0,_PX=$f($1.rows-1);_PY<=_PX;_PY+=1){$1.i=_PY;if($1.i%($1.rrows+2)==0){$k[$j++]=$1.pixs;$k[$j++]=$1.i*$1.cols;$k[$j++]=Infinity;for(var _Pf=0,_Pg=~~($1.cols/2);_Pf<_Pg;_Pf++){$k[$j++]=1;$k[$j++]=0}var _Ph=$a();var _Pi=$k[--$j];$P($k[--$j],_Pi,_Ph)}if($1.i%($1.rrows+2)==$1.rrows+1){$k[$j++]=$1.pixs;$k[$j++]=$1.i*$1.cols;$k[$j++]=Infinity;for(var _Pr=0,_Ps=$1.cols;_Pr<_Ps;_Pr++){$k[$j++]=1}var _Pt=$a();var _Pu=$k[--$j];$P($k[--$j],_Pu,_Pt)}if($1.i%($1.rrows+2)!=0&&$1.i%($1.rrows+2)!=$1.rrows+1){for(var _Q3=0,_Q2=$f($1.cols-1);_Q3<=_Q2;_Q3+=1){$1.j=_Q3;if($1.j%($1.rcols+2)==0){$p($1.pixs,$f($1.i*$1.cols+$1.j),1)}if($1.j%($1.rcols+2)==$1.rcols+1){$p($1.pixs,$f($1.i*$1.cols+$1.j),$1.i%2)}if($1.j%($1.rcols+2)!=0&&$1.j%($1.rcols+2)!=$1.rcols+1){$p($1.pixs,$f($1.i*$1.cols+$1.j),$g($1.mmat,$1.cwpos));$1.cwpos=$1.cwpos+1}}}}var _Qb=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",$1.cols],["pixy",$1.rows],["height",$1.rows*2/72],["width",$1.cols*2/72],["opt",$1.options]]);$k[$j++]=_Qb;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_datamatrixrectangular(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$p($1.options,"dontdraw",true);$p($1.options,"format","rectangle");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_datamatrix();var _9=$k[--$j];$1[$k[--$j]]=_9;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_datamatrixrectangularextension(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.dmre=false;$1.dindmre=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$p($1.options,"dontdraw",true);$p($1.options,"format","rectangle");if(!($1.dindmre||$1.dmre)){$p($1.options,"dmre",true)}else{$p($1.options,"dmre",$1.dmre);$p($1.options,"dindmre",$1.dindmre)}$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_datamatrix();var _G=$k[--$j];$1[$k[--$j]]=_G;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_mailmark(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.type="unset";$1.parse=false;$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});var _6=new Map([["parse",$1.parse],["parseonly",true],["parsefnc",false]]);$1.fncvals=_6;$k[$j++]="barcode";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _9=$k[--$j];$1[$k[--$j]]=_9;$1.barlen=$1.barcode.length;delete $1.options["parse"];var _D=new Map([["7","24x24"],["9","32x32"],["29","16x48"]]);$1.version=$g(_D,$1["type"]);var _G=new Map([["7","square"],["9","square"],["29","rectangle"]]);$1.format=$g(_G,$1["type"]);if($1.barcode.length<45){$k[$j++]="bwipp.mailmarkBadLength";$k[$j++]="Royal Mail Mailmark must contain at least 45 characters of Mailmark formatted data, including any required space padding";bwipp_raiseerror()}if($ne($G($1.barcode,0,4),"JGB ")){$k[$j++]="bwipp.mailmarkBadIndicator";$k[$j++]="Royal Mail Mailmark must begin with JGB identifier";bwipp_raiseerror()}$p($1.options,"dontdraw",true);$p($1.options,"version",$1.version);$p($1.options,"format",$1.format);$p($1.options,"mailmark",true);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_datamatrix();var _U=$k[--$j];$1[$k[--$j]]=_U;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_qrcode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.format="unset";$1.version="unset";$1.eclevel="unset";$1.parse=false;$1.parsefnc=false;$1.mask=-1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.mask=~~$1.mask;if($ne($1.version,"unset")){if($eq($1.format,"unset")){$k[$j++]="full";if($eq($G($1.version,0,1),"M")){$j--;$k[$j++]="micro"}if($eq($G($1.version,0,1),"R")){$j--;$k[$j++]="rmqr"}$1.format=$k[--$j]}}else{if($eq($1.format,"unset")){$1.format="full"}}if($eq($1.eclevel,"unset")){$k[$j++]="eclevel";if($ne($1.format,"micro")){$k[$j++]="M"}else{$k[$j++]="L"}var _G=$k[--$j];$1[$k[--$j]]=_G}$1.fn1=-1;var _L=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["eci",true],["FNC1",$1.fn1]]);$1.fncvals=_L;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _O=$k[--$j];$1[$k[--$j]]=_O;$1.msglen=$1.msg.length;$1.fnc1first=false;if($1.msglen>0){if($g($1.msg,0)==$1.fn1){$1.fnc1first=true;$k[$j++]=Infinity;var _X=$G($1.msg,1,$1.msglen-1);for(var _Y=0,_Z=_X.length;_Y<_Z;_Y++){var _a=$g(_X,_Y);$k[$j++]=_a;if(_a==37){var _b=$k[--$j];$k[$j++]=_b;$k[$j++]=_b}}$1.msg=$a();$1.msglen=$1.msg.length}}var _e=$a(["v1to9","v10to26","v27to40","vM1","vM2","vM3","vM4","vR7x43","vR7x59","vR7x77","vR7x99","vR7x139","vR9x43","vR9x59","vR9x77","vR9x99","vR9x139","vR11x27","vR11x43","vR11x59","vR11x77","vR11x99","vR11x139","vR13x27","vR13x43","vR13x59","vR13x77","vR13x99","vR13x139","vR15x43","vR15x59","vR15x77","vR15x99","vR15x139","vR17x43","vR17x59","vR17x77","vR17x99","vR17x139"]);$k[$j++]=0;for(var _f=0,_g=_e.length;_f<_g;_f++){var _i=$k[--$j];$1[$g(_e,_f)]=_i;$k[$j++]=$f(_i+1)}$j--;$1.N=0;$1.A=1;$1.B=2;$1.K=3;$1.E=4;$k[$j++]=Infinity;$k[$j++]=Infinity;for(var _j=48;_j<=57;_j+=1){$k[$j++]=_j}var _k=$a();for(var _l=0,_m=_k.length;_l<_m;_l++){$k[$j++]=$g(_k,_l);$k[$j++]=-1}$1.Nexcl=$d();$k[$j++]=Infinity;$k[$j++]=Infinity;$k[$j++]=32;$k[$j++]=36;$k[$j++]=37;$k[$j++]=42;$k[$j++]=43;$k[$j++]=45;$k[$j++]=46;$k[$j++]=47;$k[$j++]=58;for(var _p=65;_p<=90;_p+=1){$k[$j++]=_p}$k[$j++]=$1.fn1;var _r=$a();for(var _s=0,_t=_r.length;_s<_t;_s++){$k[$j++]=$g(_r,_s);$k[$j++]=-1}$1.Aexcl=$d();$k[$j++]=Infinity;$k[$j++]=Infinity;for(var _w=0;_w<=31;_w+=1){$k[$j++]=_w}$k[$j++]=33;$k[$j++]=34;$k[$j++]=35;$k[$j++]=38;$k[$j++]=39;$k[$j++]=40;$k[$j++]=41;$k[$j++]=44;for(var _x=59;_x<=64;_x+=1){$k[$j++]=_x}for(var _y=91;_y<=127;_y+=1){$k[$j++]=_y}for(var _z=160;_z<=223;_z+=1){$k[$j++]=_z}var _10=$a();for(var _11=0,_12=_10.length;_11<_12;_11++){$k[$j++]=$g(_10,_11);$k[$j++]=-1}$1.Bexcl=$d();$k[$j++]=Infinity;$k[$j++]=Infinity;for(var _15=129;_15<=159;_15+=1){$k[$j++]=_15}for(var _16=224;_16<=235;_16+=1){$k[$j++]=_16}var _17=$a();for(var _18=0,_19=_17.length;_18<_19;_18++){$k[$j++]=$g(_17,_18);$k[$j++]=-1}$1.Kexcl=$d();$k[$j++]=Infinity;$k[$j++]=$a(["0001","0010","0100","1000","0111"]);$k[$j++]=$a(["0001","0010","0100","1000","0111"]);$k[$j++]=$a(["0001","0010","0100","1000","0111"]);$k[$j++]=$a(["",-1,-1,-1,-1]);$k[$j++]=$a(["0","1",-1,-1,-1]);$k[$j++]=$a(["00","01","10","11",-1]);$k[$j++]=$a(["000","001","010","011",-1]);for(var _1J=0,_1K=32;_1J<_1K;_1J++){$k[$j++]=$a(["001","010","011","100",-1])}$1.mids=$a();$1.cclens=$a([$a([10,9,8,8]),$a([12,11,16,10]),$a([14,13,16,12]),$a([3,-1,-1,-1]),$a([4,3,-1,-1]),$a([5,4,4,3]),$a([6,5,5,4]),$a([4,3,3,2]),$a([5,5,4,3]),$a([6,5,5,4]),$a([7,6,5,5]),$a([7,6,6,5]),$a([5,5,4,3]),$a([6,5,5,4]),$a([7,6,5,5]),$a([7,6,6,5]),$a([8,7,6,6]),$a([4,4,3,2]),$a([6,5,5,4]),$a([7,6,5,5]),$a([7,6,6,5]),$a([8,7,6,6]),$a([8,7,7,6]),$a([5,5,4,3]),$a([6,6,5,5]),$a([7,6,6,5]),$a([8,7,6,6]),$a([8,7,7,6]),$a([8,8,7,7]),$a([7,6,6,5]),$a([7,7,6,5]),$a([8,7,7,6]),$a([8,7,7,6]),$a([9,8,7,7]),$a([7,6,6,5]),$a([8,7,6,6]),$a([8,7,7,6]),$a([8,8,7,6]),$a([9,8,8,7])]);$k[$j++]=Infinity;for(var _21=0,_22=3;_21<_22;_21++){$k[$j++]=4}$k[$j++]=3;$k[$j++]=5;$k[$j++]=7;$k[$j++]=9;for(var _23=0,_24=32;_23<_24;_23++){$k[$j++]=3}$1.termlens=$a();$1.tobin=function(){var _27=$s($k[--$j]);$k[$j++]=_27;for(var _29=0,_28=_27.length-1;_29<=_28;_29+=1){var _2A=$k[--$j];$p(_2A,_29,48);$k[$j++]=_2A}var _2B=$k[--$j];var _2E=$R($s(_2B.length),$k[--$j],2);$P(_2B,_2B.length-_2E.length,_2E);$k[$j++]=_2B};$1.charmap="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";$1.charvals=new Map;for(var _2F=0;_2F<=44;_2F+=1){$p($1.charvals,$g($1.charmap,_2F),_2F)}$1.encA=function(){$1.in=$k[--$j];if($1.fnc1first){$k[$j++]=Infinity;$F($1.in,function(){var _2M=$k[--$j];$k[$j++]=_2M;if(_2M==$1.fn1){$j--;$k[$j++]=37}});$1.in=$a()}$1.out=$s(~~($1.in.length*11/2)+1);$1.k=0;$1.m=0;for(;;){if($1.k==$1.in.length){break}if($1.k<$1.in.length-1){$k[$j++]=$f($g($1.charvals,$g($1.in,$1.k))*45+$g($1.charvals,$g($1.in,$1.k+1)));$k[$j++]=11;$1.tobin();$1.k=$1.k+2}else{$k[$j++]=$g($1.charvals,$g($1.in,$1.k));$k[$j++]=6;$1.tobin();$1.k=$1.k+1}var _2m=$k[--$j];$P($1.out,$1.m,_2m);$1.m=_2m.length+$1.m}$k[$j++]=$G($1.out,0,$1.m)};$1.encN=function(){$1.in=$k[--$j];$1.out=$s(~~($1.in.length*10/3)+1);$1.k=0;$1.m=0;for(;;){if($1.k==$1.in.length){break}if($1.k<$1.in.length-2){var _32=$G($1.in,$1.k,3);$k[$j++]=0;for(var _33=0,_34=_32.length;_33<_34;_33++){var _36=$k[--$j];$k[$j++]=$f($g(_32,_33)+$f(_36*10-48))}$k[$j++]=10;$1.tobin();$1.k=$1.k+3}else{if($1.k==$1.in.length-2){var _3C=$G($1.in,$1.k,2);$k[$j++]=0;for(var _3D=0,_3E=_3C.length;_3D<_3E;_3D++){var _3G=$k[--$j];$k[$j++]=$f($g(_3C,_3D)+$f(_3G*10-48))}$k[$j++]=7;$1.tobin();$1.k=$1.k+2}else{var _3K=$G($1.in,$1.k,1);$k[$j++]=0;for(var _3L=0,_3M=_3K.length;_3L<_3M;_3L++){var _3O=$k[--$j];$k[$j++]=$f($g(_3K,_3L)+$f(_3O*10-48))}$k[$j++]=4;$1.tobin();$1.k=$1.k+1}}var _3Q=$k[--$j];$P($1.out,$1.m,_3Q);$1.m=_3Q.length+$1.m}$k[$j++]=$G($1.out,0,$1.m)};$1.encB=function(){$1.in=$k[--$j];if($1.fnc1first){$k[$j++]=Infinity;$F($1.in,function(){var _3a=$k[--$j];$k[$j++]=_3a;if(_3a==$1.fn1){$j--;$k[$j++]=29}});$1.in=$a()}$1.out=$s($1.in.length*8);for(var _3h=0,_3g=$1.in.length-1;_3h<=_3g;_3h+=1){$1.k=_3h;$k[$j++]=~~$z($g($1.in,$1.k));$k[$j++]=8;$1.tobin();$P($1.out,$1.k*8,$k[--$j])}$k[$j++]=$1.out};$1.encK=function(){$1.in=$k[--$j];$1.out=$s(~~($1.in.length/2)*13);$1.k=0;$1.m=0;for(;;){if($1.k==$1.in.length){break}var _40=$f($g($1.in,$1.k)*256+$g($1.in,$1.k+1));$k[$j++]=_40;if(_40<57408){$k[$j++]=33088}else{$k[$j++]=49472}var _41=$k[--$j];var _43=$f($k[--$j]-_41);$k[$j++]=$f((_43>>>8)*192+(_43&255));$k[$j++]=13;$1.tobin();var _44=$k[--$j];$P($1.out,$1.m,_44);$1.m=_44.length+$1.m;$1.k=$1.k+2}$k[$j++]=$1.out};$1.encE=function(){var _4C=$f(-$g($k[--$j],0)-1e6);$k[$j++]=_4C;if(_4C<=127){$k[$j++]=8;$1.tobin()}else{var _4D=$k[--$j];$k[$j++]=_4D;if(_4D<=16383){var _4E=$k[--$j];$k[$j++]=$f(_4E+32768);$k[$j++]=16;$1.tobin()}else{var _4F=$k[--$j];$k[$j++]=$f(_4F+12582912);$k[$j++]=24;$1.tobin()}}};$1.encfuncs=$a(["encN","encA","encB","encK","encE"]);$1.addtobits=function(){var _4H=$k[--$j];$P($1.bits,$1.j,_4H);$1.j=_4H.length+$1.j};$k[$j++]=Infinity;for(var _4M=0,_4N=$1.msglen;_4M<_4N;_4M++){$k[$j++]=0}$k[$j++]=0;$1.numNs=$a();$k[$j++]=Infinity;for(var _4Q=0,_4R=$1.msglen;_4Q<_4R;_4Q++){$k[$j++]=0}$k[$j++]=0;$1.numAs=$a();$k[$j++]=Infinity;for(var _4U=0,_4V=$1.msglen;_4U<_4V;_4U++){$k[$j++]=0}$k[$j++]=0;$1.numBs=$a();$k[$j++]=Infinity;for(var _4Y=0,_4Z=$1.msglen;_4Y<_4Z;_4Y++){$k[$j++]=0}$k[$j++]=-1;$1.numKs=$a();$k[$j++]=Infinity;for(var _4c=0,_4d=$1.msglen;_4c<_4d;_4c++){$k[$j++]=0}$k[$j++]=9999;$1.nextNs=$a();$k[$j++]=Infinity;for(var _4g=0,_4h=$1.msglen;_4g<_4h;_4g++){$k[$j++]=0}$k[$j++]=9999;$1.nextBs=$a();$k[$j++]=Infinity;for(var _4k=0,_4l=$1.msglen;_4k<_4l;_4k++){$k[$j++]=0}$k[$j++]=9999;$1.nextAs=$a();$k[$j++]=Infinity;for(var _4o=0,_4p=$1.msglen;_4o<_4p;_4o++){$k[$j++]=0}$k[$j++]=9999;$1.nextKs=$a();$1.isECI=$a($1.msglen);for(var _4u=$1.msglen-1;_4u>=0;_4u-=1){$1.i=_4u;$1.barchar=$g($1.msg,$1.i);var _50=$g($1.Kexcl,$1.barchar)!==undefined;if(_50){$p($1.nextKs,$1.i,0);$p($1.numKs,$1.i,$f($g($1.numKs,$1.i+1)+1))}else{$p($1.nextKs,$1.i,$f($g($1.nextKs,$1.i+1)+1))}var _5F=$g($1.Nexcl,$1.barchar)!==undefined;if(_5F){$p($1.nextNs,$1.i,0);$p($1.numNs,$1.i,$f($g($1.numNs,$1.i+1)+1))}else{$p($1.nextNs,$1.i,$f($g($1.nextNs,$1.i+1)+1))}var _5U=$g($1.Bexcl,$1.barchar)!==undefined;if(_5U){$p($1.nextBs,$1.i,0);$p($1.numBs,$1.i,$f($g($1.numBs,$1.i+1)+1))}else{$p($1.nextBs,$1.i,$f($g($1.nextBs,$1.i+1)+1))}var _5j=$g($1.Aexcl,$1.barchar)!==undefined;if(_5j){$p($1.nextAs,$1.i,0);$p($1.numAs,$1.i,$f($g($1.numAs,$1.i+1)+1))}else{$p($1.nextAs,$1.i,$f($g($1.nextAs,$1.i+1)+1))}$p($1.isECI,$1.i,$1.barchar<=-1e6)}$k[$j++]=Infinity;var _5z=$1.numKs;for(var _60=0,_61=_5z.length;_60<_61;_60++){$k[$j++]=~~($f($g(_5z,_60)+1)/2)}$1.numKs=$a();$1.KbeforeB=function(){var _67=$g($k[--$j],$1.ver);$k[$j++]=$ge($1.numK,_67)&&$g($1.nextBs,$f($1.numK*2+$1.i))==0};$1.KbeforeA=function(){var _6F=$g($k[--$j],$1.ver);$k[$j++]=$ge($1.numK,_6F)&&$g($1.nextAs,$f($1.numK*2+$1.i))==0};$1.KbeforeN=function(){var _6N=$g($k[--$j],$1.ver);$k[$j++]=$ge($1.numK,_6N)&&$g($1.nextNs,$f($1.numK*2+$1.i))==0};$1.KbeforeE=function(){var _6V=$g($k[--$j],$1.ver);$k[$j++]=$ge($1.numK,_6V)&&$f($1.numK*2+$1.i)==$1.msglen};$1.AbeforeK=function(){var _6c=$g($k[--$j],$1.ver);$k[$j++]=$ge($1.numA,_6c)&&$g($1.nextKs,$f($1.numA+$1.i))==0};$1.AbeforeB=function(){var _6k=$g($k[--$j],$1.ver);$k[$j++]=$ge($1.numA,_6k)&&$g($1.nextBs,$f($1.numA+$1.i))==0};$1.AbeforeN=function(){var _6s=$g($k[--$j],$1.ver);$k[$j++]=$ge($1.numA,_6s)&&$g($1.nextNs,$f($1.numA+$1.i))==0};$1.AbeforeE=function(){var _70=$g($k[--$j],$1.ver);$k[$j++]=$ge($1.numA,_70)&&$f($1.numA+$1.i)==$1.msglen};$1.NbeforeK=function(){var _77=$g($k[--$j],$1.ver);$k[$j++]=$ge($1.numN,_77)&&$g($1.nextKs,$f($1.numN+$1.i))==0};$1.NbeforeB=function(){var _7F=$g($k[--$j],$1.ver);$k[$j++]=$ge($1.numN,_7F)&&$g($1.nextBs,$f($1.numN+$1.i))==0};$1.NbeforeA=function(){var _7N=$g($k[--$j],$1.ver);$k[$j++]=$ge($1.numN,_7N)&&$g($1.nextAs,$f($1.numN+$1.i))==0};$1.NbeforeE=function(){var _7V=$g($k[--$j],$1.ver);$k[$j++]=$ge($1.numN,_7V)&&$f($1.numN+$1.i)==$1.msglen};if($ne($1.version,"unset")){$k[$j++]=Infinity;for(var _7a=0;_7a<=9;_7a+=1){$k[$j++]=$R($s(2),_7a,10);$k[$j++]=$1.v1to9}for(var _7e=10;_7e<=26;_7e+=1){$k[$j++]=$R($s(2),_7e,10);$k[$j++]=$1.v10to26}for(var _7i=27;_7i<=40;_7i+=1){$k[$j++]=$R($s(2),_7i,10);$k[$j++]=$1.v27to40}$k[$j++]="M1";$k[$j++]=$1.vM1;$k[$j++]="M2";$k[$j++]=$1.vM2;$k[$j++]="M3";$k[$j++]=$1.vM3;$k[$j++]="M4";$k[$j++]=$1.vM4;$k[$j++]="R7x43";$k[$j++]=$1.vR7x43;$k[$j++]="R7x59";$k[$j++]=$1.vR7x59;$k[$j++]="R7x77";$k[$j++]=$1.vR7x77;$k[$j++]="R7x99";$k[$j++]=$1.vR7x99;$k[$j++]="R7x139";$k[$j++]=$1.vR7x139;$k[$j++]="R9x43";$k[$j++]=$1.vR9x43;$k[$j++]="R9x59";$k[$j++]=$1.vR9x59;$k[$j++]="R9x77";$k[$j++]=$1.vR9x77;$k[$j++]="R9x99";$k[$j++]=$1.vR9x99;$k[$j++]="R9x139";$k[$j++]=$1.vR9x139;$k[$j++]="R11x27";$k[$j++]=$1.vR11x27;$k[$j++]="R11x43";$k[$j++]=$1.vR11x43;$k[$j++]="R11x59";$k[$j++]=$1.vR11x59;$k[$j++]="R11x77";$k[$j++]=$1.vR11x77;$k[$j++]="R11x99";$k[$j++]=$1.vR11x99;$k[$j++]="R11x139";$k[$j++]=$1.vR11x139;$k[$j++]="R13x27";$k[$j++]=$1.vR13x27;$k[$j++]="R13x43";$k[$j++]=$1.vR13x43;$k[$j++]="R13x59";$k[$j++]=$1.vR13x59;$k[$j++]="R13x77";$k[$j++]=$1.vR13x77;$k[$j++]="R13x99";$k[$j++]=$1.vR13x99;$k[$j++]="R13x139";$k[$j++]=$1.vR13x139;$k[$j++]="R15x43";$k[$j++]=$1.vR15x43;$k[$j++]="R15x59";$k[$j++]=$1.vR15x59;$k[$j++]="R15x77";$k[$j++]=$1.vR15x77;$k[$j++]="R15x99";$k[$j++]=$1.vR15x99;$k[$j++]="R15x139";$k[$j++]=$1.vR15x139;$k[$j++]="R17x43";$k[$j++]=$1.vR17x43;$k[$j++]="R17x59";$k[$j++]=$1.vR17x59;$k[$j++]="R17x77";$k[$j++]=$1.vR17x77;$k[$j++]="R17x99";$k[$j++]=$1.vR17x99;$k[$j++]="R17x139";$k[$j++]=$1.vR17x139;var _8O=$g($d(),$1.version);$k[$j++]="verset";$k[$j++]=_8O;$k[$j++]=Infinity;var _8P=$k[--$j];var _8Q=$k[--$j];$k[$j++]=_8P;$k[$j++]=_8Q;var _8R=$a();$1[$k[--$j]]=_8R}else{if($eq($1.format,"full")){$1.verset=$a([$1.v1to9,$1.v10to26,$1.v27to40])}if($eq($1.format,"micro")){$1.verset=$a([$1.vM1,$1.vM2,$1.vM3,$1.vM4])}}$k[$j++]=Infinity;for(var _8e=0,_8f=39;_8e<_8f;_8e++){$k[$j++]=-1}$1.msgbits=$a();$1.e=1e4;var _8h=$1.verset;for(var _8i=0,_8j=_8h.length;_8i<_8j;_8i++){$1.ver=$g(_8h,_8i);$1.mode=-1;$1.seq=$a([]);$1.i=0;for(;;){if($1.i>=$1.msglen){break}$1.numK=$g($1.numKs,$1.i);$1.numB=$g($1.numBs,$1.i);$1.numA=$g($1.numAs,$1.i);$1.numN=$g($1.numNs,$1.i);$1.eci=$g($1.isECI,$1.i);if($eq($1.ver,$1.vM1)&&$1.numA>=1){$1.seq=-1;break}if($eq($1.ver,$1.vM1)&&$1.numB>=1){$1.seq=-1;break}if($eq($1.ver,$1.vM1)&&$1.numK>=1){$1.seq=-1;break}if($eq($1.ver,$1.vM1)&&$1.eci){$1.seq=-1;break}if($eq($1.ver,$1.vM2)&&$1.numB>=1){$1.seq=-1;break}if($eq($1.ver,$1.vM2)&&$1.numK>=1){$1.seq=-1;break}if($eq($1.ver,$1.vM2)&&$1.eci){$1.seq=-1;break}if($eq($1.ver,$1.vM3)&&$1.eci){$1.seq=-1;break}if($eq($1.ver,$1.vM4)&&$1.eci){$1.seq=-1;break}if($ge($1.ver,$1.vR7x43)&&$1.eci){$1.seq=-1;break}for(;;){if($1.eci){$k[$j++]=$1.E;break}if($ge($1.ver,$1.vR7x43)){$k[$j++]=$1.B;break}if($1.mode==-1){$k[$j++]=$a([1,1,1,$1.e,$1.e,1,1]);$1.KbeforeA();if($k[--$j]){$k[$j++]=$1.K;break}$k[$j++]=$a([1,1,1,$1.e,$1.e,1,1]);$1.KbeforeN();if($k[--$j]){$k[$j++]=$1.K;break}$k[$j++]=$a([5,5,6,$1.e,$1.e,2,3]);$1.KbeforeB();if($k[--$j]){$k[$j++]=$1.K;break}$k[$j++]=$a([1,1,1,$1.e,$1.e,1,1]);$1.KbeforeE();if($k[--$j]){$k[$j++]=$1.K;break}if($1.numK>=1){$k[$j++]=$1.B;break}$k[$j++]=$a([6,7,8,$1.e,$1.e,3,4]);$1.AbeforeB();if($k[--$j]){$k[$j++]=$1.A;break}$k[$j++]=$a([1,1,1,$1.e,1,1,1]);$1.AbeforeN();if($k[--$j]){$k[$j++]=$1.A;break}$k[$j++]=$a([1,1,1,$1.e,1,1,1]);$1.AbeforeE();if($k[--$j]){$k[$j++]=$1.A;break}if($1.numA>=1){var _AF=$ne($1.ver,$1.vM2)?$1.B:$1.A;$k[$j++]=_AF;break}$k[$j++]=$a([4,4,5,$1.e,$1.e,2,3]);$1.NbeforeB();if($k[--$j]){$k[$j++]=$1.N;break}$k[$j++]=$a([1,1,1,$1.e,$1.e,1,1]);$1.NbeforeB();if($k[--$j]){$k[$j++]=$1.B;break}$k[$j++]=$a([7,8,9,$1.e,3,4,5]);$1.NbeforeA();if($k[--$j]){$k[$j++]=$1.N;break}$k[$j++]=$a([1,1,1,$1.e,1,1,1]);$1.NbeforeA();if($k[--$j]){$k[$j++]=$1.A;break}if($1.numN>=1){$k[$j++]=$1.N;break}$k[$j++]=$1.B;break}if($1.mode==$1.B){$k[$j++]=$a([9,12,13,$1.e,$1.e,4,5]);$1.KbeforeB();if($k[--$j]){$k[$j++]=$1.K;break}$k[$j++]=$a([9,10,12,$1.e,$1.e,4,5]);$1.KbeforeA();if($k[--$j]){$k[$j++]=$1.K;break}$k[$j++]=$a([9,10,11,$1.e,$1.e,5,6]);$1.KbeforeN();if($k[--$j]){$k[$j++]=$1.K;break}$k[$j++]=$a([4,5,6,$1.e,$1.e,2,3]);$1.KbeforeE();if($k[--$j]){$k[$j++]=$1.K;break}$k[$j++]=$a([11,12,14,$1.e,$1.e,5,7]);$1.AbeforeK();if($k[--$j]){$k[$j++]=$1.A;break}$k[$j++]=$a([11,15,16,$1.e,$1.e,6,7]);$1.AbeforeB();if($k[--$j]){$k[$j++]=$1.A;break}$k[$j++]=$a([12,13,15,$1.e,$1.e,6,8]);$1.AbeforeN();if($k[--$j]){$k[$j++]=$1.A;break}$k[$j++]=$a([6,7,8,$1.e,$1.e,3,4]);$1.AbeforeE();if($k[--$j]){$k[$j++]=$1.A;break}$k[$j++]=$a([6,7,8,$1.e,$1.e,3,4]);$1.NbeforeK();if($k[--$j]){$k[$j++]=$1.N;break}$k[$j++]=$a([6,8,9,$1.e,$1.e,3,4]);$1.NbeforeB();if($k[--$j]){$k[$j++]=$1.N;break}$k[$j++]=$a([6,7,8,$1.e,$1.e,3,4]);$1.NbeforeA();if($k[--$j]){$k[$j++]=$1.N;break}$k[$j++]=$a([3,4,5,$1.e,$1.e,2,3]);$1.NbeforeE();if($k[--$j]){$k[$j++]=$1.N;break}$k[$j++]=$1.B;break}if($1.mode==$1.A){if($1.numK>=1){$k[$j++]=$1.K;break}if($1.numB>=1){$k[$j++]=$1.B;break}$k[$j++]=$a([13,15,17,$1.e,5,7,9]);$1.NbeforeA();if($k[--$j]){$k[$j++]=$1.N;break}$k[$j++]=$a([13,17,18,$1.e,$1.e,7,9]);$1.NbeforeB();if($k[--$j]){$k[$j++]=$1.N;break}$k[$j++]=$a([7,8,9,$1.e,3,4,5]);$1.NbeforeE();if($k[--$j]){$k[$j++]=$1.N;break}if($1.numA>=1||$1.numN>=1){$k[$j++]=$1.A;break}$k[$j++]=$1.B;break}if($1.mode==$1.N){if($1.numK>=1){$k[$j++]=$1.K;break}if($1.numB>=1){$k[$j++]=$1.B;break}if($1.numA>=1){$k[$j++]=$1.A;break}if($1.numN>=1){$k[$j++]=$1.N;break}$k[$j++]=$1.B;break}if($1.mode==$1.K){if($1.numB>=1){$k[$j++]=$1.B;break}if($1.numA>=1){$k[$j++]=$1.A;break}if($1.numN>=1){$k[$j++]=$1.N;break}if($1.numK>=1){$k[$j++]=$1.K;break}$k[$j++]=$1.B;break}}var _CL=$k[--$j];$k[$j++]=_CL;if(_CL==$1.K&&$1.fnc1first){$j--;$k[$j++]=$1.B}var _CP=$k[--$j];$k[$j++]=_CP;if(_CP==$1.mode){$j--;var _CV=$1.mode==$1.K?2:1;$1.dat=$G($1.msg,$1.i,_CV);$k[$j++]=Infinity;$q($1.seq);$k[$j++]=Infinity;var _CY=$k[--$j];var _CZ=$k[--$j];$k[$j++]=_CY;$q(_CZ);$q($1.dat);var _Cb=$a();$k[$j++]=_Cb;$1.seq=$a()}else{$1.mode=$k[--$j];if($1.mode==$1.K){$k[$j++]=$1.K;$k[$j++]=$G($1.msg,$1.i,$1.numK*2)}if($1.mode==$1.B){$k[$j++]=$1.B;$k[$j++]=$G($1.msg,$1.i,$1.numB)}if($1.mode==$1.A){$k[$j++]=$1.A;$k[$j++]=$G($1.msg,$1.i,$1.numA)}if($1.mode==$1.N){$k[$j++]=$1.N;$k[$j++]=$G($1.msg,$1.i,$1.numN)}if($1.mode==$1.E){$1.mode=-1;$k[$j++]=$1.E;$k[$j++]=$G($1.msg,$1.i,1)}$1.dat=$k[--$j];$1.sw=$k[--$j];$k[$j++]=Infinity;$q($1.seq);$k[$j++]=$1.sw;$k[$j++]=$1.dat;$1.seq=$a()}$1.i=$1.i+$1.dat.length}for(;;){if($1.seq==-1){break}$1.bits=$s(23648);$1.j=0;if($1.fnc1first){if($lt($1.ver,$1.vR7x43)){$k[$j++]="0101"}else{$k[$j++]="101"}$1.addtobits()}$1.abort=false;for(var _DR=0,_DQ=$1.seq.length-1;_DR<=_DQ;_DR+=2){$1.i=_DR;$1.mode=$g($1.seq,$1.i);$k[$j++]=$g($g($1.mids,$1.ver),$1.mode);$1.addtobits();$1.chars=$g($1.seq,$1.i+1);if($1.mode!=$1.E){$1.cclen=$g($g($1.cclens,$1.ver),$1.mode);if($1.chars.length>=~~Math.pow(2,$1.cclen)){$1.abort=true;break}$k[$j++]=$1.chars.length;if($1.mode==$1.K){var _Dp=$k[--$j];$k[$j++]=~~(_Dp/2)}$k[$j++]=$1.cclen;$1.tobin();$1.addtobits()}$k[$j++]=$1.chars;if($1[$g($1.encfuncs,$1.mode)]()===true){break}$1.addtobits()}if($1.abort){break}$1.bits=$G($1.bits,0,$1.j);$p($1.msgbits,$1.ver,$1.bits);break}}$1.metrics=$a([$a(["micro","M1",$1.vM1,11,11,98,99,36,$a([2,99,99,99]),$a([1,0,-1,-1,-1,-1,-1,-1])]),$a(["micro","M2",$1.vM2,13,13,98,99,80,$a([5,6,99,99]),$a([1,0,1,0,-1,-1,-1,-1])]),$a(["micro","M3",$1.vM3,15,15,98,99,132,$a([6,8,99,99]),$a([1,0,1,0,-1,-1,-1,-1])]),$a(["micro","M4",$1.vM4,17,17,98,99,192,$a([8,10,14,99]),$a([1,0,1,0,1,0,-1,-1])]),$a(["full","1",$1.v1to9,21,21,98,99,208,$a([7,10,13,17]),$a([1,0,1,0,1,0,1,0])]),$a(["full","2",$1.v1to9,25,25,18,99,359,$a([10,16,22,28]),$a([1,0,1,0,1,0,1,0])]),$a(["full","3",$1.v1to9,29,29,22,99,567,$a([15,26,36,44]),$a([1,0,1,0,2,0,2,0])]),$a(["full","4",$1.v1to9,33,33,26,99,807,$a([20,36,52,64]),$a([1,0,2,0,2,0,4,0])]),$a(["full","5",$1.v1to9,37,37,30,99,1079,$a([26,48,72,88]),$a([1,0,2,0,2,2,2,2])]),$a(["full","6",$1.v1to9,41,41,34,99,1383,$a([36,64,96,112]),$a([2,0,4,0,4,0,4,0])]),$a(["full","7",$1.v1to9,45,45,22,38,1568,$a([40,72,108,130]),$a([2,0,4,0,2,4,4,1])]),$a(["full","8",$1.v1to9,49,49,24,42,1936,$a([48,88,132,156]),$a([2,0,2,2,4,2,4,2])]),$a(["full","9",$1.v1to9,53,53,26,46,2336,$a([60,110,160,192]),$a([2,0,3,2,4,4,4,4])]),$a(["full","10",$1.v10to26,57,57,28,50,2768,$a([72,130,192,224]),$a([2,2,4,1,6,2,6,2])]),$a(["full","11",$1.v10to26,61,61,30,54,3232,$a([80,150,224,264]),$a([4,0,1,4,4,4,3,8])]),$a(["full","12",$1.v10to26,65,65,32,58,3728,$a([96,176,260,308]),$a([2,2,6,2,4,6,7,4])]),$a(["full","13",$1.v10to26,69,69,34,62,4256,$a([104,198,288,352]),$a([4,0,8,1,8,4,12,4])]),$a(["full","14",$1.v10to26,73,73,26,46,4651,$a([120,216,320,384]),$a([3,1,4,5,11,5,11,5])]),$a(["full","15",$1.v10to26,77,77,26,48,5243,$a([132,240,360,432]),$a([5,1,5,5,5,7,11,7])]),$a(["full","16",$1.v10to26,81,81,26,50,5867,$a([144,280,408,480]),$a([5,1,7,3,15,2,3,13])]),$a(["full","17",$1.v10to26,85,85,30,54,6523,$a([168,308,448,532]),$a([1,5,10,1,1,15,2,17])]),$a(["full","18",$1.v10to26,89,89,30,56,7211,$a([180,338,504,588]),$a([5,1,9,4,17,1,2,19])]),$a(["full","19",$1.v10to26,93,93,30,58,7931,$a([196,364,546,650]),$a([3,4,3,11,17,4,9,16])]),$a(["full","20",$1.v10to26,97,97,34,62,8683,$a([224,416,600,700]),$a([3,5,3,13,15,5,15,10])]),$a(["full","21",$1.v10to26,101,101,28,50,9252,$a([224,442,644,750]),$a([4,4,17,0,17,6,19,6])]),$a(["full","22",$1.v10to26,105,105,26,50,10068,$a([252,476,690,816]),$a([2,7,17,0,7,16,34,0])]),$a(["full","23",$1.v10to26,109,109,30,54,10916,$a([270,504,750,900]),$a([4,5,4,14,11,14,16,14])]),$a(["full","24",$1.v10to26,113,113,28,54,11796,$a([300,560,810,960]),$a([6,4,6,14,11,16,30,2])]),$a(["full","25",$1.v10to26,117,117,32,58,12708,$a([312,588,870,1050]),$a([8,4,8,13,7,22,22,13])]),$a(["full","26",$1.v10to26,121,121,30,58,13652,$a([336,644,952,1110]),$a([10,2,19,4,28,6,33,4])]),$a(["full","27",$1.v27to40,125,125,34,62,14628,$a([360,700,1020,1200]),$a([8,4,22,3,8,26,12,28])]),$a(["full","28",$1.v27to40,129,129,26,50,15371,$a([390,728,1050,1260]),$a([3,10,3,23,4,31,11,31])]),$a(["full","29",$1.v27to40,133,133,30,54,16411,$a([420,784,1140,1350]),$a([7,7,21,7,1,37,19,26])]),$a(["full","30",$1.v27to40,137,137,26,52,17483,$a([450,812,1200,1440]),$a([5,10,19,10,15,25,23,25])]),$a(["full","31",$1.v27to40,141,141,30,56,18587,$a([480,868,1290,1530]),$a([13,3,2,29,42,1,23,28])]),$a(["full","32",$1.v27to40,145,145,34,60,19723,$a([510,924,1350,1620]),$a([17,0,10,23,10,35,19,35])]),$a(["full","33",$1.v27to40,149,149,30,58,20891,$a([540,980,1440,1710]),$a([17,1,14,21,29,19,11,46])]),$a(["full","34",$1.v27to40,153,153,34,62,22091,$a([570,1036,1530,1800]),$a([13,6,14,23,44,7,59,1])]),$a(["full","35",$1.v27to40,157,157,30,54,23008,$a([570,1064,1590,1890]),$a([12,7,12,26,39,14,22,41])]),$a(["full","36",$1.v27to40,161,161,24,50,24272,$a([600,1120,1680,1980]),$a([6,14,6,34,46,10,2,64])]),$a(["full","37",$1.v27to40,165,165,28,54,25568,$a([630,1204,1770,2100]),$a([17,4,29,14,49,10,24,46])]),$a(["full","38",$1.v27to40,169,169,32,58,26896,$a([660,1260,1860,2220]),$a([4,18,13,32,48,14,42,32])]),$a(["full","39",$1.v27to40,173,173,26,54,28256,$a([720,1316,1950,2310]),$a([20,4,40,7,43,22,10,67])]),$a(["full","40",$1.v27to40,177,177,30,58,29648,$a([750,1372,2040,2430]),$a([19,6,18,31,34,34,20,61])]),$a(["rmqr","R7x43",$1.vR7x43,7,43,22,99,104,$a([99,7,99,10]),$a([-1,-1,1,0,-1,-1,1,0])]),$a(["rmqr","R7x59",$1.vR7x59,7,59,20,40,171,$a([99,9,99,14]),$a([-1,-1,1,0,-1,-1,1,0])]),$a(["rmqr","R7x77",$1.vR7x77,7,77,26,52,261,$a([99,12,99,22]),$a([-1,-1,1,0,-1,-1,1,0])]),$a(["rmqr","R7x99",$1.vR7x99,7,99,24,50,358,$a([99,16,99,30]),$a([-1,-1,1,0,-1,-1,1,0])]),$a(["rmqr","R7x139",$1.vR7x139,7,139,28,56,545,$a([99,24,99,44]),$a([-1,-1,1,0,-1,-1,2,0])]),$a(["rmqr","R9x43",$1.vR9x43,9,43,22,99,170,$a([99,9,99,14]),$a([-1,-1,1,0,-1,-1,1,0])]),$a(["rmqr","R9x59",$1.vR9x59,9,59,20,40,267,$a([99,12,99,22]),$a([-1,-1,1,0,-1,-1,1,0])]),$a(["rmqr","R9x77",$1.vR9x77,9,77,26,52,393,$a([99,18,99,32]),$a([-1,-1,1,0,-1,-1,1,1])]),$a(["rmqr","R9x99",$1.vR9x99,9,99,24,50,532,$a([99,24,99,44]),$a([-1,-1,1,0,-1,-1,2,0])]),$a(["rmqr","R9x139",$1.vR9x139,9,139,28,56,797,$a([99,36,99,66]),$a([-1,-1,1,1,-1,-1,3,0])]),$a(["rmqr","R11x27",$1.vR11x27,11,27,98,99,122,$a([99,8,99,10]),$a([-1,-1,1,0,-1,-1,1,0])]),$a(["rmqr","R11x43",$1.vR11x43,11,43,22,99,249,$a([99,12,99,20]),$a([-1,-1,1,0,-1,-1,1,0])]),$a(["rmqr","R11x59",$1.vR11x59,11,59,20,40,376,$a([99,16,99,32]),$a([-1,-1,1,0,-1,-1,1,1])]),$a(["rmqr","R11x77",$1.vR11x77,11,77,26,52,538,$a([99,24,99,44]),$a([-1,-1,1,0,-1,-1,1,1])]),$a(["rmqr","R11x99",$1.vR11x99,11,99,24,50,719,$a([99,32,99,60]),$a([-1,-1,1,1,-1,-1,1,1])]),$a(["rmqr","R11x139",$1.vR11x139,11,139,28,56,1062,$a([99,48,99,90]),$a([-1,-1,2,0,-1,-1,3,0])]),$a(["rmqr","R13x27",$1.vR13x27,13,27,98,99,172,$a([99,9,99,14]),$a([-1,-1,1,0,-1,-1,1,0])]),$a(["rmqr","R13x43",$1.vR13x43,13,43,22,99,329,$a([99,14,99,28]),$a([-1,-1,1,0,-1,-1,1,0])]),$a(["rmqr","R13x59",$1.vR13x59,13,59,20,40,486,$a([99,22,99,40]),$a([-1,-1,1,0,-1,-1,2,0])]),$a(["rmqr","R13x77",$1.vR13x77,13,77,26,52,684,$a([99,32,99,56]),$a([-1,-1,1,1,-1,-1,1,1])]),$a(["rmqr","R13x99",$1.vR13x99,13,99,24,50,907,$a([99,40,99,78]),$a([-1,-1,1,1,-1,-1,1,2])]),$a(["rmqr","R13x139",$1.vR13x139,13,139,28,56,1328,$a([99,60,99,112]),$a([-1,-1,2,1,-1,-1,2,2])]),$a(["rmqr","R15x43",$1.vR15x43,15,43,22,99,409,$a([99,18,99,36]),$a([-1,-1,1,0,-1,-1,1,1])]),$a(["rmqr","R15x59",$1.vR15x59,15,59,20,40,596,$a([99,26,99,48]),$a([-1,-1,1,0,-1,-1,2,0])]),$a(["rmqr","R15x77",$1.vR15x77,15,77,26,52,830,$a([99,36,99,72]),$a([-1,-1,1,1,-1,-1,2,1])]),$a(["rmqr","R15x99",$1.vR15x99,15,99,24,50,1095,$a([99,48,99,88]),$a([-1,-1,2,0,-1,-1,4,0])]),$a(["rmqr","R15x139",$1.vR15x139,15,139,28,56,1594,$a([99,72,99,130]),$a([-1,-1,2,1,-1,-1,1,4])]),$a(["rmqr","R17x43",$1.vR17x43,17,43,22,99,489,$a([99,22,99,40]),$a([-1,-1,1,0,-1,-1,1,1])]),$a(["rmqr","R17x59",$1.vR17x59,17,59,20,40,706,$a([99,32,99,60]),$a([-1,-1,2,0,-1,-1,2,0])]),$a(["rmqr","R17x77",$1.vR17x77,17,77,26,52,976,$a([99,44,99,84]),$a([-1,-1,2,0,-1,-1,1,2])]),$a(["rmqr","R17x99",$1.vR17x99,17,99,24,50,1283,$a([99,60,99,104]),$a([-1,-1,2,1,-1,-1,4,0])]),$a(["rmqr","R17x139",$1.vR17x139,17,139,28,56,1860,$a([99,80,99,156]),$a([-1,-1,4,0,-1,-1,2,4])])]);$k[$j++]="eclval";$x("LMQH",$1.eclevel);$j--;var _Iz=$k[--$j];var _J0=$k[--$j];$k[$j++]=_Iz.length;$k[$j++]=_J0;$j--;var _J1=$k[--$j];var _J2=$k[--$j];$k[$j++]=_J1;$k[$j++]=_J2;$j--;var _J3=$k[--$j];$1[$k[--$j]]=_J3;for(var _J7=0,_J6=$1.metrics.length-1;_J7<=_J6;_J7+=1){$1.i=_J7;$1.m=$g($1.metrics,$1.i);$1.frmt=$g($1.m,0);$1.vers=$g($1.m,1);$1.vergrp=$g($1.m,2);$1.verind=$1.i-44;$1.rows=$g($1.m,3);$1.cols=$g($1.m,4);$1.asp2=$g($1.m,5);$1.asp3=$g($1.m,6);$1.nmod=$g($1.m,7);$1.ncws=~~($1.nmod/8);$1.rbit=$1.nmod%8;$1.lc4b=false;if($eq($1.vers,"M1")||$eq($1.vers,"M3")){$1.ncws=$1.ncws+1;$1.rbit=0;$1.lc4b=true}$1.ecws=$g($g($1.m,8),$1.eclval);$1.dcws=$f($1.ncws-$1.ecws);var _Jf=$1.lc4b?4:0;$1.dmod=$f($1.dcws*8-_Jf);$1.ecb1=$g($g($1.m,9),$1.eclval*2);$1.ecb2=$g($g($1.m,9),$f($1.eclval*2+1));$1.okay=true;if($ne($1.format,$1.frmt)){$1.okay=false}if($eq($1.frmt,"micro")&&$1.fnc1first){$1.okay=false}if($ne($1.version,"unset")&&$ne($1.version,$1.vers)){$1.okay=false}if($1.ecb1==-1||$1.ecb2==-1){$1.okay=false}$1.verbits=$g($1.msgbits,$1.vergrp);if($1.verbits==-1){$1.okay=false}else{if($1.verbits.length>$1.dmod){$1.okay=false}}$1.term=$G("000000000",0,$g($1.termlens,$1.vergrp));if($1.okay){break}}if(!$1.okay){$k[$j++]="bwipp.qrcodeNoValidSymbol";$k[$j++]="No valid symbol available";bwipp_raiseerror()}$1.format=$1.frmt;$1.version=$1.vers;$1.msgbits=$1.verbits;$1.dcpb=~~($1.dcws/$f($1.ecb1+$1.ecb2));$1.ecpb=~~($1.ncws/$f($1.ecb1+$1.ecb2))-$1.dcpb;var _KJ=$1.term;var _KK=$1.dmod;var _KL=$1.msgbits;var _KM=$1.term;var _KN=_KM.length;var _KO=$f(_KK-_KL.length);if($f(_KK-_KL.length)>_KM.length){var _=_KN;_KN=_KO;_KO=_}$1.term=$G(_KJ,0,_KO);var _KS=$s($1.msgbits.length+$1.term.length);$P(_KS,0,$1.msgbits);$P(_KS,$1.msgbits.length,$1.term);$1.msgbits=_KS;$1.pad=$s($1.dmod);for(var _Ka=0,_KZ=$1.pad.length-1;_Ka<=_KZ;_Ka+=1){$p($1.pad,_Ka,48)}$P($1.pad,0,$1.msgbits);$1.padstrs=$a(["11101100","00010001"]);$1.padnum=0;var _Ki=$1.lc4b?5:1;for(var _Kk=~~(Math.ceil($1.msgbits.length/8)*8),_Kj=$f($1.dmod-_Ki);_Kk<=_Kj;_Kk+=8){$P($1.pad,_Kk,$g($1.padstrs,$1.padnum));$1.padnum=($1.padnum+1)%2}$1.cws=$a($1.dcws);for(var _Ku=0,_Kt=$1.cws.length-1;_Ku<=_Kt;_Ku+=1){$1.c=_Ku;$1.bpcw=8;if($1.lc4b&&$1.c==$1.cws.length-1){$1.bpcw=4}$1.cwb=$G($1.pad,$1.c*8,$1.bpcw);$1.cw=0;for(var _L4=0,_L3=$1.bpcw-1;_L4<=_L3;_L4+=1){$1.i=_L4;$1.cw=$f($1.cw+~~Math.pow(2,$1.bpcw-$1.i-1)*$f($g($1.cwb,$1.i)-48))}$p($1.cws,$1.c,$1.cw)}if($1.lc4b){var _LF=$1.cws;var _LG=$1.cws;$p(_LF,_LG.length-1,$g(_LF,_LG.length-1)<<4)}var _LJ=$g($1.options,"debugcws")!==undefined;if(_LJ){$k[$j++]="bwipp.debugcws";$k[$j++]=$1.cws;bwipp_raiseerror()}$k[$j++]=Infinity;$k[$j++]=1;for(var _LL=0,_LM=255;_LL<_LM;_LL++){var _LN=$k[--$j];var _LO=_LN*2;$k[$j++]=_LN;$k[$j++]=_LO;if(_LO>=256){var _LP=$k[--$j];$k[$j++]=_LP^285}}$1.rsalog=$a();$1.rslog=$a(256);for(var _LS=1;_LS<=255;_LS+=1){$p($1.rslog,$g($1.rsalog,_LS),_LS)}$1.rsprod=function(){var _LW=$k[--$j];var _LX=$k[--$j];$k[$j++]=_LX;$k[$j++]=_LW;if(_LW!=0&&_LX!=0){var _La=$g($1.rslog,$k[--$j]);var _Lf=$g($1.rsalog,$f(_La+$g($1.rslog,$k[--$j]))%255);$k[$j++]=_Lf}else{$j-=2;$k[$j++]=0}};$k[$j++]=Infinity;$k[$j++]=1;for(var _Lh=0,_Li=$1.ecpb;_Lh<_Li;_Lh++){$k[$j++]=0}$1.coeffs=$a();for(var _Lm=0,_Ll=$1.ecpb-1;_Lm<=_Ll;_Lm+=1){$1.i=_Lm;$p($1.coeffs,$1.i+1,$g($1.coeffs,$1.i));for(var _Lt=$1.i;_Lt>=1;_Lt-=1){$1.j=_Lt;$k[$j++]=$1.coeffs;$k[$j++]=$1.j;$k[$j++]=$g($1.coeffs,$1.j-1);$k[$j++]=$g($1.coeffs,$1.j);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _M5=$k[--$j];var _M6=$k[--$j];var _M7=$k[--$j];$p($k[--$j],_M7,$xo(_M6,_M5))}$k[$j++]=$1.coeffs;$k[$j++]=0;$k[$j++]=$g($1.coeffs,0);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _MF=$k[--$j];var _MG=$k[--$j];$p($k[--$j],_MG,_MF)}$1.coeffs=$G($1.coeffs,0,$1.coeffs.length-1);$1.rscodes=function(){$1.rscws=$k[--$j];$1.rsnd=$1.rscws.length;$k[$j++]=Infinity;$F($1.rscws);for(var _MP=0,_MQ=$1.ecpb;_MP<_MQ;_MP++){$k[$j++]=0}$1.rscws=$a();for(var _MU=0,_MT=$1.rsnd-1;_MU<=_MT;_MU+=1){$1.m=_MU;$1.k=$g($1.rscws,$1.m);for(var _Ma=0,_MZ=$1.ecpb-1;_Ma<=_MZ;_Ma+=1){$1.j=_Ma;$k[$j++]=$1.rscws;$k[$j++]=$1.m+$1.j+1;$k[$j++]=$g($1.coeffs,$1.ecpb-$1.j-1);$k[$j++]=$1.k;$1.rsprod();var _Mn=$k[--$j];var _Mo=$k[--$j];$p($k[--$j],_Mo,$xo(_Mn,$g($1.rscws,$1.m+$1.j+1)))}}$k[$j++]=$G($1.rscws,$1.rsnd,$1.ecpb)};$1.dcwsb=$a($f($1.ecb1+$1.ecb2));$1.ecwsb=$a($f($1.ecb1+$1.ecb2));for(var _N2=0,_N1=$f($1.ecb1-1);_N2<=_N1;_N2+=1){$1.i=_N2;$p($1.dcwsb,$1.i,$G($1.cws,$1.i*$1.dcpb,$1.dcpb));$k[$j++]=$1.ecwsb;$k[$j++]=$1.i;$k[$j++]=$g($1.dcwsb,$1.i);$1.rscodes();var _NF=$k[--$j];var _NG=$k[--$j];$p($k[--$j],_NG,_NF)}for(var _NK=0,_NJ=$f($1.ecb2-1);_NK<=_NJ;_NK+=1){$1.i=_NK;$p($1.dcwsb,$f($1.ecb1+$1.i),$G($1.cws,$f($1.ecb1*$1.dcpb+$1.i*($1.dcpb+1)),$1.dcpb+1));$k[$j++]=$1.ecwsb;$k[$j++]=$f($1.ecb1+$1.i);$k[$j++]=$g($1.dcwsb,$f($1.ecb1+$1.i));$1.rscodes();var _Nc=$k[--$j];var _Nd=$k[--$j];$p($k[--$j],_Nd,_Nc)}$1.cws=$a($1.ncws);$1.cw=0;for(var _Nj=0,_Ni=$1.dcpb;_Nj<=_Ni;_Nj+=1){$1.i=_Nj;for(var _Nn=0,_Nm=$f($f($1.ecb1+$1.ecb2)-1);_Nn<=_Nm;_Nn+=1){$1.j=_Nn;if($1.i<$g($1.dcwsb,$1.j).length){$p($1.cws,$1.cw,$g($g($1.dcwsb,$1.j),$1.i));$1.cw=$1.cw+1}}}for(var _O2=0,_O1=$1.ecpb-1;_O2<=_O1;_O2+=1){$1.i=_O2;for(var _O6=0,_O5=$f($f($1.ecb1+$1.ecb2)-1);_O6<=_O5;_O6+=1){$1.j=_O6;$p($1.cws,$1.cw,$g($g($1.ecwsb,$1.j),$1.i));$1.cw=$1.cw+1}}if($1.rbit>0){$1.pad=$a($1.cws.length+1);$P($1.pad,0,$1.cws);$p($1.pad,$1.pad.length-1,0);$1.cws=$1.pad}if($1.lc4b){var _OO=$1.cws;var _OP=$1.dcws;$p(_OO,$f(_OP-1),$g(_OO,$f(_OP-1))>>>4);for(var _OU=$f($1.dcws-1),_OT=$1.ncws-2;_OU<=_OT;_OU+=1){$1.i=_OU;$p($1.cws,$1.i,($g($1.cws,$1.i)&15)<<4);$p($1.cws,$1.i,$g($1.cws,$1.i+1)>>>4&15|$g($1.cws,$1.i))}$p($1.cws,$1.ncws-1,($g($1.cws,$1.ncws-1)&15)<<4)}var _Oo=$g($1.options,"debugecc")!==undefined;if(_Oo){$k[$j++]="bwipp.debugecc";$k[$j++]=$1.cws;bwipp_raiseerror()}$k[$j++]=Infinity;for(var _Os=0,_Ot=$1.rows*$1.cols;_Os<_Ot;_Os++){$k[$j++]=-1}$1.pixs=$a();$1.qmv=function(){var _Ow=$k[--$j];var _Ox=$k[--$j];$k[$j++]=$f(_Ox+_Ow*$1.cols)};if($eq($1.format,"full")){for(var _P1=8,_P0=$f($1.cols-9);_P1<=_P0;_P1+=1){$1.i=_P1;$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=6;$1.qmv();var _P5=$k[--$j];$p($k[--$j],_P5,($1.i+1)%2);$k[$j++]=$1.pixs;$k[$j++]=6;$k[$j++]=$1.i;$1.qmv();var _PA=$k[--$j];$p($k[--$j],_PA,($1.i+1)%2)}}if($eq($1.format,"micro")){for(var _PF=8,_PE=$f($1.cols-1);_PF<=_PE;_PF+=1){$1.i=_PF;$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=0;$1.qmv();var _PJ=$k[--$j];$p($k[--$j],_PJ,($1.i+1)%2);$k[$j++]=$1.pixs;$k[$j++]=0;$k[$j++]=$1.i;$1.qmv();var _PO=$k[--$j];$p($k[--$j],_PO,($1.i+1)%2)}}if($eq($1.format,"rmqr")){for(var _PT=3,_PS=$f($1.cols-4);_PT<=_PS;_PT+=1){$1.i=_PT;$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=0;$1.qmv();var _PX=$k[--$j];$p($k[--$j],_PX,($1.i+1)%2);$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=$f($1.rows-1);$1.qmv();var _Pd=$k[--$j];$p($k[--$j],_Pd,($1.i+1)%2)}for(var _Ph=3,_Pg=$f($1.rows-4);_Ph<=_Pg;_Ph+=1){$1.i=_Ph;$k[$j++]=$1.pixs;$k[$j++]=0;$k[$j++]=$1.i;$1.qmv();var _Pl=$k[--$j];$p($k[--$j],_Pl,($1.i+1)%2);$k[$j++]=$1.pixs;$k[$j++]=$f($1.cols-1);$k[$j++]=$1.i;$1.qmv();var _Pr=$k[--$j];$p($k[--$j],_Pr,($1.i+1)%2)}for(var _Py=$f($1.asp2-1),_Pz=$f($1.asp3-$1.asp2),_Px=$f($1.cols-13);_Pz<0?_Py>=_Px:_Py<=_Px;_Py+=_Pz){$1.i=_Py;for(var _Q2=3,_Q1=$f($1.rows-4);_Q2<=_Q1;_Q2+=1){$1.j=_Q2;$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=$1.j;$1.qmv();var _Q7=$k[--$j];$p($k[--$j],_Q7,($1.j+1)%2)}}}$1.fpat=$a([$a([1,1,1,1,1,1,1,0]),$a([1,0,0,0,0,0,1,0]),$a([1,0,1,1,1,0,1,0]),$a([1,0,1,1,1,0,1,0]),$a([1,0,1,1,1,0,1,0]),$a([1,0,0,0,0,0,1,0]),$a([1,1,1,1,1,1,1,0]),$a([0,0,0,0,0,0,0,0])]);$1.fsubpat=$a([$a([1,1,1,1,1,9,9,9]),$a([1,0,0,0,1,9,9,9]),$a([1,0,1,0,1,9,9,9]),$a([1,0,0,0,1,9,9,9]),$a([1,1,1,1,1,9,9,9]),$a([9,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9])]);$1.fcorpat=$a([$a([1,1,1,9,9,9,9,9]),$a([1,0,9,9,9,9,9,9]),$a([1,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9])]);$1.fnullpat=$a([$a([9,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9]),$a([9,9,9,9,9,9,9,9])]);var _Qy=new Map([["full",$a([$1.fpat,$1.fpat,$1.fpat,$1.fnullpat])],["micro",$a([$1.fpat,$1.fnullpat,$1.fnullpat,$1.fnullpat])],["rmqr",$a([$1.fpat,$1.fcorpat,$1.fcorpat,$1.fsubpat])]]);$1.fpats=$g(_Qy,$1.format);for(var _R1=0;_R1<=7;_R1+=1){$1.y=_R1;for(var _R2=0;_R2<=7;_R2+=1){$1.x=_R2;$1.fpb0=$g($g($g($1.fpats,0),$1.y),$1.x);$1.fpb1=$g($g($g($1.fpats,1),$1.y),$1.x);$1.fpb2=$g($g($g($1.fpats,2),$1.y),$1.x);$1.fpb3=$g($g($g($1.fpats,3),$1.y),$1.x);if($1.fpb0!=9&&$1.y<$1.rows){$k[$j++]=$1.pixs;$k[$j++]=$1.x;$k[$j++]=$1.y;$1.qmv();var _RY=$k[--$j];$p($k[--$j],_RY,$1.fpb0)}if($1.fpb1!=9){$k[$j++]=$1.pixs;$k[$j++]=$f($f($1.cols-$1.x)-1);$k[$j++]=$1.y;$1.qmv();var _Rg=$k[--$j];$p($k[--$j],_Rg,$1.fpb1)}if($1.fpb2!=9){$k[$j++]=$1.pixs;$k[$j++]=$1.x;$k[$j++]=$f($f($1.rows-$1.y)-1);$1.qmv();var _Ro=$k[--$j];$p($k[--$j],_Ro,$1.fpb2)}if($1.fpb3!=9){$k[$j++]=$1.pixs;$k[$j++]=$f($f($1.cols-$1.x)-1);$k[$j++]=$f($f($1.rows-$1.y)-1);$1.qmv();var _Rx=$k[--$j];$p($k[--$j],_Rx,$1.fpb3)}}}$1.putalgnpat=function(){$1.py=$k[--$j];$1.px=$k[--$j];for(var _S1=0;_S1<=4;_S1+=1){$1.pb=_S1;for(var _S2=0;_S2<=4;_S2+=1){$1.pa=_S2;$1.algnb=$g($g($1.algnpat,$1.pb),$1.pa);if($1.algnb!=9){$k[$j++]=$1.pixs;$k[$j++]=$f($1.px+$1.pa);$k[$j++]=$f($1.py+$1.pb);$1.qmv();var _SF=$k[--$j];$p($k[--$j],_SF,$1.algnb)}}}};if($eq($1.format,"full")){$1.algnpat=$a([$a([1,1,1,1,1]),$a([1,0,0,0,1]),$a([1,0,1,0,1]),$a([1,0,0,0,1]),$a([1,1,1,1,1])]);for(var _ST=$f($1.asp2-2),_SU=$f($1.asp3-$1.asp2),_SS=$f($1.cols-13);_SU<0?_ST>=_SS:_ST<=_SS;_ST+=_SU){$1.i=_ST;$k[$j++]=$1.i;$k[$j++]=4;$1.putalgnpat();$k[$j++]=4;$k[$j++]=$1.i;$1.putalgnpat()}for(var _Sc=$f($1.asp2-2),_Sd=$f($1.asp3-$1.asp2),_Sb=$f($1.cols-9);_Sd<0?_Sc>=_Sb:_Sc<=_Sb;_Sc+=_Sd){$1.x=_Sc;for(var _Sj=$f($1.asp2-2),_Sk=$f($1.asp3-$1.asp2),_Si=$f($1.rows-9);_Sk<0?_Sj>=_Si:_Sj<=_Si;_Sj+=_Sk){$1.y=_Sj;$k[$j++]=$1.x;$k[$j++]=$1.y;$1.putalgnpat()}}}if($eq($1.format,"rmqr")){$1.algnpat=$a([$a([1,1,1,9,9]),$a([1,0,1,9,9]),$a([1,1,1,9,9]),$a([9,9,9,9,9]),$a([9,9,9,9,9])]);for(var _Sz=$f($1.asp2-2),_T0=$f($1.asp3-$1.asp2),_Sy=$f($1.cols-13);_T0<0?_Sz>=_Sy:_Sz<=_Sy;_Sz+=_T0){$1.i=_Sz;$k[$j++]=$1.i;$k[$j++]=0;$1.putalgnpat();$k[$j++]=$1.i;$k[$j++]=$f($1.rows-3);$1.putalgnpat()}}var _W1=new Map([["full",$a([$a([$a([0,8]),$a([8,$f($1.cols-1)])]),$a([$a([1,8]),$a([8,$f($1.cols-2)])]),$a([$a([2,8]),$a([8,$f($1.cols-3)])]),$a([$a([3,8]),$a([8,$f($1.cols-4)])]),$a([$a([4,8]),$a([8,$f($1.cols-5)])]),$a([$a([5,8]),$a([8,$f($1.cols-6)])]),$a([$a([7,8]),$a([8,$f($1.cols-7)])]),$a([$a([8,8]),$a([$f($1.cols-8),8])]),$a([$a([8,7]),$a([$f($1.cols-7),8])]),$a([$a([8,5]),$a([$f($1.cols-6),8])]),$a([$a([8,4]),$a([$f($1.cols-5),8])]),$a([$a([8,3]),$a([$f($1.cols-4),8])]),$a([$a([8,2]),$a([$f($1.cols-3),8])]),$a([$a([8,1]),$a([$f($1.cols-2),8])]),$a([$a([8,0]),$a([$f($1.cols-1),8])])])],["micro",$a([$a([$a([1,8])]),$a([$a([2,8])]),$a([$a([3,8])]),$a([$a([4,8])]),$a([$a([5,8])]),$a([$a([6,8])]),$a([$a([7,8])]),$a([$a([8,8])]),$a([$a([8,7])]),$a([$a([8,6])]),$a([$a([8,5])]),$a([$a([8,4])]),$a([$a([8,3])]),$a([$a([8,2])]),$a([$a([8,1])])])],["rmqr",$a([$a([$a([11,3]),$a([$f($1.cols-3),$f($1.rows-6)])]),$a([$a([11,2]),$a([$f($1.cols-4),$f($1.rows-6)])]),$a([$a([11,1]),$a([$f($1.cols-5),$f($1.rows-6)])]),$a([$a([10,5]),$a([$f($1.cols-6),$f($1.rows-2)])]),$a([$a([10,4]),$a([$f($1.cols-6),$f($1.rows-3)])]),$a([$a([10,3]),$a([$f($1.cols-6),$f($1.rows-4)])]),$a([$a([10,2]),$a([$f($1.cols-6),$f($1.rows-5)])]),$a([$a([10,1]),$a([$f($1.cols-6),$f($1.rows-6)])]),$a([$a([9,5]),$a([$f($1.cols-7),$f($1.rows-2)])]),$a([$a([9,4]),$a([$f($1.cols-7),$f($1.rows-3)])]),$a([$a([9,3]),$a([$f($1.cols-7),$f($1.rows-4)])]),$a([$a([9,2]),$a([$f($1.cols-7),$f($1.rows-5)])]),$a([$a([9,1]),$a([$f($1.cols-7),$f($1.rows-6)])]),$a([$a([8,5]),$a([$f($1.cols-8),$f($1.rows-2)])]),$a([$a([8,4]),$a([$f($1.cols-8),$f($1.rows-3)])]),$a([$a([8,3]),$a([$f($1.cols-8),$f($1.rows-4)])]),$a([$a([8,2]),$a([$f($1.cols-8),$f($1.rows-5)])]),$a([$a([8,1]),$a([$f($1.cols-8),$f($1.rows-6)])])])]]);$1.formatmap=$g(_W1,$1.format);$F($1.formatmap,function(){$F($k[--$j],function(){$F($k[--$j]);$1.qmv();$p($1.pixs,$k[--$j],1)})});if($eq($1.format,"full")&&$1.cols>=45){$1.versionmap=$a([$a([$a([$f($1.cols-9),5]),$a([5,$f($1.cols-9)])]),$a([$a([$f($1.cols-10),5]),$a([5,$f($1.cols-10)])]),$a([$a([$f($1.cols-11),5]),$a([5,$f($1.cols-11)])]),$a([$a([$f($1.cols-9),4]),$a([4,$f($1.cols-9)])]),$a([$a([$f($1.cols-10),4]),$a([4,$f($1.cols-10)])]),$a([$a([$f($1.cols-11),4]),$a([4,$f($1.cols-11)])]),$a([$a([$f($1.cols-9),3]),$a([3,$f($1.cols-9)])]),$a([$a([$f($1.cols-10),3]),$a([3,$f($1.cols-10)])]),$a([$a([$f($1.cols-11),3]),$a([3,$f($1.cols-11)])]),$a([$a([$f($1.cols-9),2]),$a([2,$f($1.cols-9)])]),$a([$a([$f($1.cols-10),2]),$a([2,$f($1.cols-10)])]),$a([$a([$f($1.cols-11),2]),$a([2,$f($1.cols-11)])]),$a([$a([$f($1.cols-9),1]),$a([1,$f($1.cols-9)])]),$a([$a([$f($1.cols-10),1]),$a([1,$f($1.cols-10)])]),$a([$a([$f($1.cols-11),1]),$a([1,$f($1.cols-11)])]),$a([$a([$f($1.cols-9),0]),$a([0,$f($1.cols-9)])]),$a([$a([$f($1.cols-10),0]),$a([0,$f($1.cols-10)])]),$a([$a([$f($1.cols-11),0]),$a([0,$f($1.cols-11)])])])}else{$1.versionmap=$a([])}var _Xf=$1.versionmap;for(var _Xg=0,_Xh=_Xf.length;_Xg<_Xh;_Xg++){$F($g(_Xf,_Xg),function(){$F($k[--$j]);$1.qmv();$p($1.pixs,$k[--$j],0)})}if($eq($1.format,"full")){$k[$j++]=$1.pixs;$k[$j++]=8;$k[$j++]=$f($1.rows-8);$1.qmv();var _Xp=$k[--$j];$p($k[--$j],_Xp,0)}var _Y9=$a([function(){var _Xr=$k[--$j];var _Xs=$k[--$j];$k[$j++]=$f(_Xs+_Xr)%2},function(){var _Xt=$k[--$j];var _Xu=$k[--$j];$k[$j++]=_Xt;$k[$j++]=_Xu;$j--;var _Xv=$k[--$j];$k[$j++]=_Xv%2},function(){$j--;var _Xw=$k[--$j];$k[$j++]=_Xw%3},function(){var _Xx=$k[--$j];var _Xy=$k[--$j];$k[$j++]=$f(_Xy+_Xx)%3},function(){var _Xz=$k[--$j];var _Y0=$k[--$j];$k[$j++]=(~~(_Xz/2)+~~(_Y0/3))%2},function(){var _Y1=$k[--$j];var _Y3=$k[--$j]*_Y1;$k[$j++]=$f(_Y3%2+_Y3%3)},function(){var _Y4=$k[--$j];var _Y6=$k[--$j]*_Y4;$k[$j++]=$f(_Y6%2+_Y6%3)%2},function(){var _Y7=$k[--$j];var _Y8=$k[--$j];$k[$j++]=$f(_Y8*_Y7%3+$f(_Y8+_Y7)%2)%2}]);var _YK=$a([function(){var _YA=$k[--$j];var _YB=$k[--$j];$k[$j++]=_YA;$k[$j++]=_YB;$j--;var _YC=$k[--$j];$k[$j++]=_YC%2},function(){var _YD=$k[--$j];var _YE=$k[--$j];$k[$j++]=(~~(_YD/2)+~~(_YE/3))%2},function(){var _YF=$k[--$j];var _YH=$k[--$j]*_YF;$k[$j++]=$f(_YH%2+_YH%3)%2},function(){var _YI=$k[--$j];var _YJ=$k[--$j];$k[$j++]=$f(_YJ*_YI%3+$f(_YJ+_YI)%2)%2}]);var _YN=$a([function(){var _YL=$k[--$j];var _YM=$k[--$j];$k[$j++]=(~~(_YL/2)+~~(_YM/3))%2}]);var _YO=new Map([["full",_Y9],["micro",_YK],["rmqr",_YN]]);$1.maskfuncs=$g(_YO,$1.format);if($1.mask!=-1){$1.maskfuncs=$a([$g($1.maskfuncs,$1.mask-1)]);$1.bestmaskval=$1.mask-1}$1.masks=$a($1.maskfuncs.length);for(var _Yb=0,_Ya=$1.masks.length-1;_Yb<=_Ya;_Yb+=1){$1.m=_Yb;$1.mask=$a($1.rows*$1.cols);for(var _Yh=0,_Yg=$f($1.rows-1);_Yh<=_Yg;_Yh+=1){$1.j=_Yh;for(var _Yk=0,_Yj=$f($1.cols-1);_Yk<=_Yj;_Yk+=1){$1.i=_Yk;$k[$j++]=$1.i;$k[$j++]=$1.j;if($g($1.maskfuncs,$1.m)()===true){break}var _Yq=$k[--$j];$k[$j++]=_Yq==0;$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=$1.j;$1.qmv();var _Yu=$k[--$j];var _Yw=$g($k[--$j],_Yu);var _Yx=$k[--$j];var _Yy=_Yx&&_Yw==-1?1:0;$k[$j++]=_Yy;$k[$j++]=$1.mask;$k[$j++]=$1.i;$k[$j++]=$1.j;$1.qmv();var _Z2=$k[--$j];var _Z3=$k[--$j];$p(_Z3,_Z2,$k[--$j])}}$p($1.masks,$1.m,$1.mask)}var _ZA=$ne($1.format,"rmqr")?1:2;$1.posx=$f($1.cols-_ZA);$1.posy=$f($1.rows-1);$1.dir=-1;$1.col=1;$1.num=0;for(;;){if($1.posx<0){break}$k[$j++]=$1.pixs;$k[$j++]=$1.posx;$k[$j++]=$1.posy;$1.qmv();var _ZG=$k[--$j];if($g($k[--$j],_ZG)==-1){var _ZL=$g($1.cws,~~($1.num/8));var _ZN=-(7-$1.num%8);$k[$j++]=(_ZN<0?_ZL>>>-_ZN:_ZL<<_ZN)&1;$k[$j++]=$1.pixs;$k[$j++]=$1.posx;$k[$j++]=$1.posy;$1.qmv();var _ZR=$k[--$j];var _ZS=$k[--$j];$p(_ZS,_ZR,$k[--$j]);$1.num=$1.num+1}if($1.col==1){$1.col=0;$1.posx=$f($1.posx-1)}else{$1.col=1;$1.posx=$f($1.posx+1);$1.posy=$f($1.posy+$1.dir);if($1.posy<0||$1.posy>=$1.rows){$1.dir=$1.dir*-1;$1.posy=$f($1.posy+$1.dir);$1.posx=$f($1.posx-2);if($eq($1.format,"full")&&$1.posx==6){$1.posx=$f($1.posx-1)}}}}$1.evalfulln1n3=function(){$1.scrle=$k[--$j];$k[$j++]="scr1";$k[$j++]=0;$F($1.scrle,function(){var _Zm=$k[--$j];$k[$j++]=_Zm;if(_Zm>=5){var _Zn=$k[--$j];var _Zp=$f($f($k[--$j]+_Zn)-2);$k[$j++]=_Zp;$k[$j++]=_Zp}$j--});var _Zq=$k[--$j];$1[$k[--$j]]=_Zq;$1.scr3=0;for(var _Zu=3,_Zt=$1.scrle.length-3;_Zu<=_Zt;_Zu+=2){$1.j=_Zu;if($g($1.scrle,$1.j)%3==0){$1.fact=~~($g($1.scrle,$1.j)/3);var _a3=$G($1.scrle,$1.j-2,5);for(var _a4=0,_a5=_a3.length;_a4<_a5;_a4++){$k[$j++]=$g(_a3,_a4)==$1.fact}var _a8=$k[--$j];var _a9=$k[--$j];var _aA=$k[--$j];$k[$j++]=$an(_a9,_a8);$k[$j++]=_aA;$j--;var _aB=$k[--$j];var _aC=$k[--$j];var _aD=$k[--$j];if(_aD&&(_aC&&_aB)){if($1.j==3||$1.j+4>=$1.scrle.length){$1.scr3=$1.scr3+40}else{if($g($1.scrle,$1.j-3)>=4||$g($1.scrle,$1.j+3)>=4){$1.scr3=$1.scr3+40}}}}}$k[$j++]=$1.scr1;$k[$j++]=$1.scr3};$1.evalfull=function(){$1.sym=$k[--$j];$1.n1=0;$1.n2=0;$1.n3=0;$1.rle=$a($f($1.cols+1));$1.lastpairs=$a($1.cols);$1.thispairs=$a($1.cols);$1.colsadd1=$f($1.cols+1);for(var _ab=0,_aa=$f($1.cols-1);_ab<=_aa;_ab+=1){$1.i=_ab;$k[$j++]=Infinity;var _ad=$1.cols;$k[$j++]=0;$k[$j++]=0;for(var _af=$1.i,_ag=_ad,_ae=$f(_ad*_ad-1);_ag<0?_af>=_ae:_af<=_ae;_af+=_ag){var _ai=$g($1.sym,_af);var _aj=$k[--$j];$k[$j++]=_ai;if($eq(_aj,_ai)){var _ak=$k[--$j];var _al=$k[--$j];$k[$j++]=$f(_al+1);$k[$j++]=_ak}else{var _am=$k[--$j];$k[$j++]=1;$k[$j++]=_am}}$j--;var _ao=$m()+2;$r($G($1.rle,0,_ao-2));$1.evalfulln1n3();$1.n3=$f($k[--$j]+$1.n3);$1.n1=$f($k[--$j]+$1.n1);$j--;$1.symrow=$G($1.sym,$1.i*$1.cols,$1.cols);$k[$j++]=Infinity;var _az=$1.symrow;$k[$j++]=0;$k[$j++]=0;for(var _b0=0,_b1=_az.length;_b0<_b1;_b0++){var _b2=$g(_az,_b0);var _b3=$k[--$j];$k[$j++]=_b2;if($eq(_b3,_b2)){var _b4=$k[--$j];var _b5=$k[--$j];$k[$j++]=$f(_b5+1);$k[$j++]=_b4}else{var _b6=$k[--$j];$k[$j++]=1;$k[$j++]=_b6}}$j--;var _b8=$m()+2;$r($G($1.rle,0,_b8-2));$1.evalfulln1n3();$1.n3=$f($k[--$j]+$1.n3);$1.n1=$f($k[--$j]+$1.n1);$j--;var _bE=$1.thispairs;$1.thispairs=$1.lastpairs;$1.lastpairs=_bE;var _bI=$g($1.symrow,0)==1?0:1;var _bJ=$1.symrow;$k[$j++]=_bI;for(var _bK=0,_bL=_bJ.length;_bK<_bL;_bK++){var _bM=$g(_bJ,_bK);var _bN=$k[--$j];$k[$j++]=$f(_bN+_bM);$k[$j++]=_bM}$j--;$r($1.thispairs);$j--;if($1.i>0){$k[$j++]=Infinity;$q($1.lastpairs);$q($1.thispairs);$k[$j++]=$1.n2;for(var _bU=0,_bV=$1.cols;_bU<_bV;_bU++){var _bW=$k[--$j];var _bX=$k[--$j];$k[$j++]=_bW;$k[$j++]=_bX;var _bZ=$k[$j-1-$1.colsadd1];if(($f($k[--$j]+_bZ)&3)==0){var _bb=$k[--$j];$k[$j++]=$f(_bb+3)}}$1.n2=$k[--$j];$l()}}$k[$j++]="dark";$k[$j++]=0;$F($1.sym,function(){var _be=$k[--$j];var _bf=$k[--$j];$k[$j++]=$f(_bf+_be)});var _bg=$k[--$j];$1[$k[--$j]]=_bg;var _bj=$1.cols;$1.n4=~~(Math.abs($f($1.dark*100/(_bj*_bj)-50))/5)*10;$k[$j++]=$f($f($f($1.n1+$1.n2)+$1.n3)+$1.n4)};$1.evalmicro=function(){$1.sym=$k[--$j];$1.dkrhs=0;$1.dkbot=0;for(var _br=1,_bq=$f($1.cols-1);_br<=_bq;_br+=1){$1.i=_br;$k[$j++]="dkrhs";$k[$j++]=$1.dkrhs;$k[$j++]=$1.sym;$k[$j++]=$f($1.cols-1);$k[$j++]=$1.i;$1.qmv();var _bw=$k[--$j];var _by=$g($k[--$j],_bw);var _bz=$k[--$j];$1[$k[--$j]]=$f(_bz+_by);$k[$j++]="dkbot";$k[$j++]=$1.dkbot;$k[$j++]=$1.sym;$k[$j++]=$1.i;$k[$j++]=$f($1.cols-1);$1.qmv();var _c5=$k[--$j];var _c7=$g($k[--$j],_c5);var _c8=$k[--$j];$1[$k[--$j]]=$f(_c8+_c7)}if($1.dkrhs<=$1.dkbot){$k[$j++]=-($1.dkrhs*16+$1.dkbot)}else{$k[$j++]=-($1.dkbot*16+$1.dkrhs)}};$1.bestscore=999999999;for(var _cI=0,_cH=$1.masks.length-1;_cI<=_cH;_cI+=1){$1.m=_cI;$1.masksym=$a($1.rows*$1.cols);for(var _cP=0,_cO=$f($1.rows*$1.cols-1);_cP<=_cO;_cP+=1){$1.i=_cP;$p($1.masksym,$1.i,$xo($g($1.pixs,$1.i),$g($g($1.masks,$1.m),$1.i)))}if($1.masks.length!=1){if($eq($1.format,"full")){$k[$j++]=$1.masksym;$1.evalfull();$1.score=$k[--$j]}else{$k[$j++]=$1.masksym;$1.evalmicro();$1.score=$k[--$j]}if($1.score<$1.bestscore){$1.bestsym=$1.masksym;$1.bestmaskval=$1.m;$1.bestscore=$1.score}}else{$1.bestsym=$1.masksym}}$1.pixs=$1.bestsym;if($eq($1.format,"full")){$k[$j++]=$1.pixs;$k[$j++]=8;$k[$j++]=$f($1.cols-8);$1.qmv();var _cq=$k[--$j];$p($k[--$j],_cq,1)}if($eq($1.format,"full")){$1.fmtvals=$a([21522,20773,24188,23371,17913,16590,20375,19104,30660,29427,32170,30877,26159,25368,27713,26998,5769,5054,7399,6608,1890,597,3340,2107,13663,12392,16177,14854,9396,8579,11994,11245]);$k[$j++]="ecid";$x("MLHQ",$1.eclevel);$j--;var _cv=$k[--$j];var _cw=$k[--$j];$k[$j++]=_cv.length;$k[$j++]=_cw;$j--;var _cx=$k[--$j];var _cy=$k[--$j];$k[$j++]=_cx;$k[$j++]=_cy;$j--;var _cz=$k[--$j];$1[$k[--$j]]=_cz;$1.fmtval=$g($1.fmtvals,($1.ecid<<3)+$1.bestmaskval);for(var _d7=0,_d6=$1.formatmap.length-1;_d7<=_d6;_d7+=1){$1.i=_d7;$F($g($1.formatmap,$1.i),function(){var _dC=$k[--$j];$k[$j++]=$1.pixs;$q(_dC);$1.qmv();var _dD=$1.fmtval;var _dF=-(14-$1.i);var _dG=$k[--$j];$p($k[--$j],_dG,(_dF<0?_dD>>>-_dF:_dD<<_dF)&1)})}}if($eq($1.format,"micro")){$1.fmtvals=$a([17477,16754,20011,19228,21934,20633,24512,23287,26515,25252,28157,26826,30328,29519,31766,31009,1758,1001,3248,2439,5941,4610,7515,6252,9480,8255,12134,10833,13539,12756,16013,15290]);$1.symid=$g($g($a([$a([0]),$a([1,2]),$a([3,4]),$a([5,6,7])]),~~($f($1.cols-11)/2)),$1.eclval);$1.fmtval=$g($1.fmtvals,($1.symid<<2)+$1.bestmaskval);for(var _dZ=0,_dY=$1.formatmap.length-1;_dZ<=_dY;_dZ+=1){$1.i=_dZ;$k[$j++]=$1.pixs;$q($g($g($1.formatmap,$1.i),0));$1.qmv();var _df=$1.fmtval;var _dh=-(14-$1.i);var _di=$k[--$j];$p($k[--$j],_di,(_dh<0?_df>>>-_dh:_df<<_dh)&1)}}if($eq($1.format,"rmqr")){$1.fmtvals1=$a([129714,124311,121821,115960,112748,108361,104707,99878,98062,90155,89697,82244,81360,74485,72895,66458,61898,61167,53413,53120,45844,44081,37499,36190,29814,27475,21785,19004,13992,10637,6087,2274,258919,257090,250376,249133,242105,241308,233686,233459,227035,223742,219060,215185,209925,207648,202090,199247,194591,190266,186736,181845,178881,173540,170926,165003,163235,156294,154828,148457,147325,139352,138770,131383]);$1.fmtvals2=$a([133755,136542,142100,144433,149669,153472,158154,161519,167879,168162,175784,176525,183577,184892,191606,193363,196867,204326,204908,212809,213981,220408,221874,228759,230591,236442,239056,244469,247393,252228,255758,260139,942,7307,8897,15844,16752,24149,24607,32570,34322,39223,42877,47192,50380,56297,58787,64134,67798,71667,76217,79516,84488,87341,93031,95298,101738,102991,109573,111392,118708,118929,126683,127486]);$k[$j++]="fmtvalu";$x("MH",$1.eclevel);$j--;var _do=$k[--$j];var _dp=$k[--$j];$k[$j++]=_do.length;$k[$j++]=_dp;$j--;var _dq=$k[--$j];var _dr=$k[--$j];$k[$j++]=_dq;$k[$j++]=_dr;$j--;var _ds=$k[--$j];$1[$k[--$j]]=(_ds<<5)+$1.verind;$1.fmtval1=$g($1.fmtvals1,$1.fmtvalu);$1.fmtval2=$g($1.fmtvals2,$1.fmtvalu);for(var _e3=0,_e2=$1.formatmap.length-1;_e3<=_e2;_e3+=1){$1.i=_e3;$k[$j++]=$1.pixs;$q($g($g($1.formatmap,$1.i),0));$1.qmv();var _e9=$1.fmtval1;var _eB=-(17-$1.i);var _eC=$k[--$j];$p($k[--$j],_eC,(_eB<0?_e9>>>-_eB:_e9<<_eB)&1);$k[$j++]=$1.pixs;$q($g($g($1.formatmap,$1.i),1));$1.qmv();var _eJ=$1.fmtval2;var _eL=-(17-$1.i);var _eM=$k[--$j];$p($k[--$j],_eM,(_eL<0?_eJ>>>-_eL:_eJ<<_eL)&1)}}if($eq($1.format,"full")&&$1.cols>=45){$1.vervals=$a([31892,34236,39577,42195,48118,51042,55367,58893,63784,68472,70749,76311,79154,84390,87683,92361,96236,102084,102881,110507,110734,117786,119615,126325,127568,133589,136957,141498,145311,150283,152622,158308,161089,167017]);$1.verval=$g($1.vervals,~~($f($1.cols-17)/4)-7);for(var _eW=0,_eV=$1.versionmap.length-1;_eW<=_eV;_eW+=1){$1.i=_eW;$F($g($1.versionmap,$1.i),function(){var _eb=$k[--$j];$k[$j++]=$1.pixs;$F(_eb);$1.qmv();var _ec=$1.verval;var _ee=-(17-$1.i);var _ef=$k[--$j];$p($k[--$j],_ef,(_ee<0?_ec>>>-_ee:_ec<<_ee)&1)})}}var _en=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",$1.cols],["pixy",$1.rows],["height",$1.rows*2/72],["width",$1.cols*2/72],["opt",$1.options]]);$k[$j++]=_en;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_swissqrcode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.parse=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});var _6=new Map([["parse",$1.parse],["parseonly",true],["parsefnc",false]]);$1.fncvals=_6;$k[$j++]="barcode";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _9=$k[--$j];$1[$k[--$j]]=_9;$1.barlen=$1.barcode.length;delete $1.options["parse"];if($1.barcode.length>997){$k[$j++]="bwipp.swissqrcodeBadLength";$k[$j++]="Swiss QR Code input must not exceed 997 digits";bwipp_raiseerror()}$p($1.options,"dontdraw",true);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_qrcode();var _H=$k[--$j];$1[$k[--$j]]=_H;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){$$.save();var _N=$$.currpos();$$.translate(_N.x,_N.y);var _O=72/25.4;$$.scale(_O,_O);$$.save();$$.newpath();$$.moveto(0,0);$$.lineto(46,0);$$.lineto(46,46);$$.lineto(0,46);$$.closepath();$$.moveto(19.5,19.5);$$.lineto(19.5,26.5);$$.lineto(26.5,26.5);$$.lineto(26.5,19.5);$$.closepath();$$.clip();$$.save();$$.moveto(0,0);var _R=46/$g($1.args,"pixx")/2;$$.scale(_R,_R);bwipp_renmatrix();$$.restore();$$.restore();$$.translate(19.5,19.5);var _S=7/83;$$.scale(_S,_S);$$.newpath();$$.moveto(6,6);$$.lineto(6,77);$$.lineto(77,77);$$.lineto(77,6);$$.closepath();$$.moveto(49,18);$$.lineto(49,34);$$.lineto(65,34);$$.lineto(65,49);$$.lineto(49,49);$$.lineto(49,65);$$.lineto(34,65);$$.lineto(34,49);$$.lineto(18,49);$$.lineto(18,34);$$.lineto(34,34);$$.lineto(34,18);$$.closepath();$$.setrgbcolor(0,0,0);$$.fill();$$.restore()}}function bwipp_microqrcode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$p($1.options,"dontdraw",true);$p($1.options,"format","micro");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_qrcode();var _9=$k[--$j];$1[$k[--$j]]=_9;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_rectangularmicroqrcode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$p($1.options,"dontdraw",true);$p($1.options,"format","rmqr");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_qrcode();var _9=$k[--$j];$1[$k[--$j]]=_9;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_maxicode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.mode=-1;$1.sam=-1;$1.parse=false;$1.parsefnc=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.mode=~~$1.mode;$1.sam=~~$1.sam;var _9=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["eci",true]]);$1.fncvals=_9;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _C=$k[--$j];$1[$k[--$j]]=_C;$1.msglen=$1.msg.length;if($1.mode==2||$1.mode==3){$1.barcode=$s($1.msglen);for(var _L=0,_K=$1.msglen-1;_L<=_K;_L+=1){$1.i=_L;if($g($1.msg,$1.i)>0){$p($1.barcode,$1.i,$g($1.msg,$1.i))}}$1.barlen=$1.barcode.length;if($eq($G($1.barcode,0,7),"[)>01")){$1.fid=$G($1.barcode,0,9);$1.barcode=$G($1.barcode,9,$1.barlen-9)}else{$1.fid=""}$x($1.barcode,"");$j--;$1.pcode=$k[--$j];$j--;$x($k[--$j],"");$j--;$1.ccode=$k[--$j];$j--;$x($k[--$j],"");$j--;$1.scode=$k[--$j];$j--;$1.barcode=$k[--$j];var _l=$s($1.barcode.length+$1.fid.length);$P(_l,0,$1.fid);$P(_l,$1.fid.length,$1.barcode);$1.barcode=_l;$1.barlen=$1.barcode.length;$k[$j++]=Infinity;var _q=$1.barcode;for(var _r=0,_s=_q.length;_r<_s;_r++){$k[$j++]=$g(_q,_r)}$1.msg=$a();$1.msglen=$1.msg.length}$1.eci=-1;$1.pad=-2;$1.ns=-3;$1.la=-4;$1.lb=-5;$1.sa=-6;$1.sb=-7;$1.sc=-8;$1.sd=-9;$1.se=-10;$1.sa2=-11;$1.sa3=-12;$1.lkc=-13;$1.lkd=-14;$1.lke=-15;$1.pd2=-16;$1.pd3=-17;$1.charmaps=$a([$a([13,"`",192,224,0]),$a(["A","a",193,225,1]),$a(["B","b",194,226,2]),$a(["C","c",195,227,3]),$a(["D","d",196,228,4]),$a(["E","e",197,229,5]),$a(["F","f",198,230,6]),$a(["G","g",199,231,7]),$a(["H","h",200,232,8]),$a(["I","i",201,233,9]),$a(["J","j",202,234,10]),$a(["K","k",203,235,11]),$a(["L","l",204,236,12]),$a(["M","m",205,237,13]),$a(["N","n",206,238,14]),$a(["O","o",207,239,15]),$a(["P","p",208,240,16]),$a(["Q","q",209,241,17]),$a(["R","r",210,242,18]),$a(["S","s",211,243,19]),$a(["T","t",212,244,20]),$a(["U","u",213,245,21]),$a(["V","v",214,246,22]),$a(["W","w",215,247,23]),$a(["X","x",216,248,24]),$a(["Y","y",217,249,25]),$a(["Z","z",218,250,26]),$a([$1.eci,$1.eci,$1.eci,$1.eci,$1.eci]),$a([28,28,28,28,$1.pad]),$a([29,29,29,29,$1.pad]),$a([30,30,30,30,27]),$a([$1.ns,$1.ns,$1.ns,$1.ns,$1.ns]),$a([" ","{",219,251,28]),$a([$1.pad,$1.pad,220,252,29]),$a(['"',"}",221,253,30]),$a(["#","~",222,254,31]),$a(["$",127,223,255,159]),$a(["%",";",170,161,160]),$a(["&","<",172,168,162]),$a(["'","=",177,171,163]),$a([40,">",178,175,164]),$a([41,"?",179,176,165]),$a(["*","[",181,180,166]),$a(["+",92,185,183,167]),$a([",","]",186,184,169]),$a(["-","^",188,187,173]),$a([".","_",189,191,174]),$a(["/"," ",190,138,182]),$a(["0",",",128,139,149]),$a(["1",".",129,140,150]),$a(["2","/",130,141,151]),$a(["3",":",131,142,152]),$a(["4","@",132,143,153]),$a(["5","!",133,144,154]),$a(["6","|",134,145,155]),$a(["7",$1.pd2,135,146,156]),$a(["8",$1.sa2,136,147,157]),$a(["9",$1.sa3,137,148,158]),$a([":",$1.pd3,$1.la,$1.la,$1.la]),$a([$1.sb,$1.sa," "," "," "]),$a([$1.sc,$1.sc,$1.lkc,$1.sc,$1.sc]),$a([$1.sd,$1.sd,$1.sd,$1.lkd,$1.sd]),$a([$1.se,$1.se,$1.se,$1.se,$1.lke]),$a([$1.lb,$1.la,$1.lb,$1.lb,$1.lb])]);$1.charvals=$a([new Map,new Map,new Map,new Map,new Map]);for(var _2j=0,_2i=$1.charmaps.length-1;_2j<=_2i;_2j+=1){$1.i=_2j;$1.encs=$g($1.charmaps,$1.i);for(var _2n=0;_2n<=4;_2n+=1){$1.j=_2n;var _2q=$g($1.encs,$1.j);$k[$j++]=_2q;if($eq($t(_2q),"stringtype")){var _2t=$g($k[--$j],0);$k[$j++]=_2t}$p($g($1.charvals,$1.j),$k[--$j],$1.i)}}$1.seta=$g($1.charvals,0);$1.setb=$g($1.charvals,1);$1.setc=$g($1.charvals,2);$1.setd=$g($1.charvals,3);$1.sete=$g($1.charvals,4);$k[$j++]=Infinity;for(var _3A=0,_3B=$1.msglen+1;_3A<_3B;_3A++){$k[$j++]=0}$1.nseq=$a();for(var _3E=$1.msglen-1;_3E>=0;_3E-=1){$1.i=_3E;var _3H=$g($1.msg,$1.i);if(_3H>=48&&_3H<=57){$p($1.nseq,$1.i,$f($g($1.nseq,$1.i+1)+1))}else{$p($1.nseq,$1.i,0)}}$1.nseq=$G($1.nseq,0,$1.msglen);$1.prefixinset=function(){$k[$j++]=0;for(;;){var _3S=$k[--$j];var _3T=$k[--$j];$k[$j++]=_3T;$k[$j++]=_3S;if(_3S>=_3T.length){break}var _3U=$k[--$j];var _3V=$k[--$j];var _3X=$k[--$j];var _3Y=$g(_3X,$g(_3V,_3U))!==undefined;$k[$j++]=_3X;$k[$j++]=_3V;$k[$j++]=_3U;if(_3Y){var _3Z=$k[--$j];$k[$j++]=$f(_3Z+1)}else{break}}var _3a=$k[--$j];var _3b=$k[--$j];$k[$j++]=_3a;$k[$j++]=_3b;$j--;var _3c=$k[--$j];var _3d=$k[--$j];$k[$j++]=_3c;$k[$j++]=_3d;$j--};$1.enc=function(){var _3e=$k[--$j];$p($1.out,$1.j,$g(_3e,$k[--$j]));$1.j=$1.j+1};$1.out=$a(144);$1.i=0;$1.j=0;$1.cset="seta";for(;;){if($1.i==$1.msglen){if($ne($1.cset,"seta")&&$ne($1.cset,"setb")){$k[$j++]=$1.la;$k[$j++]=$1[$1.cset];$1.enc();$1.cset="seta"}break}for(;;){if($g($1.msg,$1.i)<=-1e6){$k[$j++]=$1.eci;$k[$j++]=$1[$1.cset];$1.enc();var _41=$f(-$g($1.msg,$1.i)-1e6);$k[$j++]=_41;if(_41<=31){var _42=$k[--$j];$k[$j++]=_42&63;$r($a(1))}else{var _44=$k[--$j];$k[$j++]=_44;if(_44<=1023){var _45=$k[--$j];$k[$j++]=_45>>>6&31|32;$k[$j++]=_45&63;$r($a(2))}else{var _47=$k[--$j];$k[$j++]=_47;if(_47<=32767){var _48=$k[--$j];$k[$j++]=_48>>>12&47|48;$k[$j++]=_48>>>6&63;$k[$j++]=_48&63;$r($a(3))}else{var _4A=$k[--$j];$k[$j++]=_4A>>>18&55|56;$k[$j++]=_4A>>>12&63;$k[$j++]=_4A>>>6&63;$k[$j++]=_4A&63;$r($a(4))}}}var _4C=$k[--$j];$P($1.out,$1.j,_4C);$1.j=_4C.length+$1.j;$1.i=$1.i+1;break}if($g($1.nseq,$1.i)>=9){var _4M=$G($1.msg,$1.i,9);$k[$j++]=0;for(var _4N=0,_4O=_4M.length;_4N<_4O;_4N++){var _4Q=$k[--$j];$k[$j++]=$f(_4Q+$f($g(_4M,_4N)-48))*10}var _4R=$k[--$j];$k[$j++]=~~(_4R/10);for(var _4S=0,_4T=4;_4S<_4T;_4S++){var _4U=$k[--$j];$k[$j++]=_4U&63;$k[$j++]=_4U>>>6}$k[$j++]=$g($1[$1.cset],$1.ns);for(var _4Z=0;_4Z<=10;_4Z+=2){var _4a=$k[$j-1-_4Z];$k[$j++]=_4a}$r($a(6));var _4c=$k[--$j];var _4d=$k[--$j];var _4e=$k[--$j];var _4f=$k[--$j];var _4g=$k[--$j];var _4h=$k[--$j];var _4i=$k[--$j];$k[$j++]=_4c;$k[$j++]=_4i;$k[$j++]=_4h;$k[$j++]=_4g;$k[$j++]=_4f;$k[$j++]=_4e;$k[$j++]=_4d;for(var _4j=0,_4k=6;_4j<_4k;_4j++){$j--}$P($1.out,$1.j,$k[--$j]);$1.i=$1.i+9;$1.j=$1.j+6;break}$1.char1=$g($1.msg,$1.i);$k[$j++]="char2";if($1.i+1<$1.msglen){$k[$j++]=$g($1.msg,$1.i+1)}else{$k[$j++]=-99}var _4y=$k[--$j];$1[$k[--$j]]=_4y;$k[$j++]="char3";if($1.i+2<$1.msglen){$k[$j++]=$g($1.msg,$1.i+2)}else{$k[$j++]=-99}var _55=$k[--$j];$1[$k[--$j]]=_55;var _5A=$g($1[$1.cset],$1.char1)!==undefined;if(_5A){$k[$j++]=$1.char1;$k[$j++]=$1[$1.cset];$1.enc();$1.i=$1.i+1;break}var _5I=$g($1.setb,$1.char1)!==undefined;if($eq($1.cset,"seta")&&_5I){var _5L=$g($1.setb,$1.char2)!==undefined;if(_5L){$k[$j++]=$1.lb;$k[$j++]=$1.seta;$1.enc();$1.cset="setb"}else{$k[$j++]=$1.sb;$k[$j++]=$1.seta;$1.enc();$k[$j++]=$1.char1;$k[$j++]=$1.setb;$1.enc();$1.i=$1.i+1}break}var _5W=$g($1.seta,$1.char1)!==undefined;if($eq($1.cset,"setb")&&_5W){var _5X=$1.seta;var _5Y=$1.msg;var _5Z=$1.i;var _5a=$1.msglen;var _5b=$1.i;var _5c=_5a-_5b;var _5d=4;if(4>_5a-_5b){var _=_5c;_5c=_5d;_5d=_}$k[$j++]="p";$k[$j++]=_5X;$k[$j++]=$G(_5Y,_5Z,_5d);$1.prefixinset();var _5f=$k[--$j];$1[$k[--$j]]=_5f;if($1.p==1){$k[$j++]=$1.sa;$k[$j++]=$1.setb;$1.enc();$k[$j++]=$1.char1;$k[$j++]=$1.seta;$1.enc();$1.i=$1.i+1}if($1.p==2){$k[$j++]=$1.sa2;$k[$j++]=$1.setb;$1.enc();$k[$j++]=$1.char1;$k[$j++]=$1.seta;$1.enc();$k[$j++]=$1.char2;$k[$j++]=$1.seta;$1.enc();$1.i=$1.i+2}if($1.p==3){$k[$j++]=$1.sa3;$k[$j++]=$1.setb;$1.enc();$k[$j++]=$1.char1;$k[$j++]=$1.seta;$1.enc();$k[$j++]=$1.char2;$k[$j++]=$1.seta;$1.enc();$k[$j++]=$1.char3;$k[$j++]=$1.seta;$1.enc();$1.i=$1.i+3}if($1.p>=4){$k[$j++]=$1.la;$k[$j++]=$1.setb;$1.enc();$1.cset="seta"}break}var _6A=$g($1.seta,$1.char1)!==undefined;if(_6A){$k[$j++]=$1.la;$k[$j++]=$1[$1.cset];$1.enc();$1.cset="seta";break}var _6G=$g($1.setb,$1.char1)!==undefined;if(_6G){$k[$j++]=$1.lb;$k[$j++]=$1[$1.cset];$1.enc();$1.cset="setb";break}var _6M=$g($1.setc,$1.char1)!==undefined;if(_6M){$1.setx="setc";$1.sx=$1.sc;$1.lkx=$1.lkc}var _6R=$g($1.setd,$1.char1)!==undefined;if(_6R){$1.setx="setd";$1.sx=$1.sd;$1.lkx=$1.lkd}var _6W=$g($1.sete,$1.char1)!==undefined;if(_6W){$1.setx="sete";$1.sx=$1.se;$1.lkx=$1.lke}var _6a=$1[$1.setx];var _6b=$1.msg;var _6c=$1.i;var _6d=$1.msglen;var _6e=$1.i;var _6f=_6d-_6e;var _6g=4;if(4>_6d-_6e){var _=_6f;_6f=_6g;_6g=_}$k[$j++]="p";$k[$j++]=_6a;$k[$j++]=$G(_6b,_6c,_6g);$1.prefixinset();var _6i=$k[--$j];$1[$k[--$j]]=_6i;if($1.p==1){$k[$j++]=$1.sx;$k[$j++]=$1[$1.cset];$1.enc();$k[$j++]=$1.char1;$k[$j++]=$1[$1.setx];$1.enc();$1.i=$1.i+1}if($1.p==2){$k[$j++]=$1.sx;$k[$j++]=$1[$1.cset];$1.enc();$k[$j++]=$1.char1;$k[$j++]=$1[$1.setx];$1.enc();$k[$j++]=$1.sx;$k[$j++]=$1[$1.cset];$1.enc();$k[$j++]=$1.char2;$k[$j++]=$1[$1.setx];$1.enc();$1.i=$1.i+2}if($1.p==3){$k[$j++]=$1.sx;$k[$j++]=$1[$1.cset];$1.enc();$k[$j++]=$1.char1;$k[$j++]=$1[$1.setx];$1.enc();$k[$j++]=$1.sx;$k[$j++]=$1[$1.cset];$1.enc();$k[$j++]=$1.char2;$k[$j++]=$1[$1.setx];$1.enc();$k[$j++]=$1.sx;$k[$j++]=$1[$1.cset];$1.enc();$k[$j++]=$1.char3;$k[$j++]=$1[$1.setx];$1.enc();$1.i=$1.i+3}if($1.p>=4){$k[$j++]=$1.sx;$k[$j++]=$1[$1.cset];$1.enc();$k[$j++]=$1.lkx;$k[$j++]=$1[$1.setx];$1.enc();$1.cset=$1.setx}break}}$1.encmsg=$G($1.out,0,$1.j);$1.padval=$g($1[$1.cset],$1.pad);$1.sami=$a([]);if($1.sam!=-1){$1.sami=$a(2);$p($1.sami,0,$g($1.seta,$1.pad));$p($1.sami,1,(~~($1.sam/10)-1)*8+($1.sam%10-1))}$k[$j++]=Infinity;$q($1.sami);$q($1.encmsg);$1.encmsg=$a();if($1.mode==2||$1.mode==3){var _7v=$Z($s(4),"0000");var _7y=$R($s(4),~~$1.mode,2);$P(_7v,4-_7y.length,_7y);$1.mdb=_7v;var _80=$Z($s(10),"0000000000");var _83=$R($s(10),~~$z($1.ccode),2);$P(_80,10-_83.length,_83);$1.ccb=_80;var _85=$Z($s(10),"0000000000");var _88=$R($s(10),~~$z($1.scode),2);$P(_85,10-_88.length,_88);$1.scb=_85;$1.pcb=$Z($s(36),"000000000000000000000000000000000000");if($1.mode==2){var _8F=$R($s(6),$1.pcode.length,2);$P($1.pcb,6-_8F.length,_8F);var _8J=$R($s(30),~~$z($1.pcode),2);$P($1.pcb,36-_8J.length,_8J)}else{$k[$j++]=Infinity;var _8L=$Z($s(6)," ");$k[$j++]=_8L;$k[$j++]=_8L;$k[$j++]=0;if($1.pcode.length>6){$k[$j++]=$G($1.pcode,0,6)}else{$k[$j++]=$1.pcode}var _8Q=$k[--$j];var _8R=$k[--$j];$P($k[--$j],_8R,_8Q);$F($k[--$j],function(){var _8W=$g($1.seta,$k[--$j]);$k[$j++]=_8W});$1.pccw=$a();for(var _8Y=0;_8Y<=5;_8Y+=1){$1.i=_8Y;var _8e=$R($s(6),$g($1.pccw,$1.i),2);$P($1.pcb,6*$1.i+6-_8e.length,_8e)}}$1.scm=$s(60);$P($1.scm,2,$1.mdb);$P($1.scm,38,$G($1.pcb,0,4));$P($1.scm,30,$G($1.pcb,4,6));$P($1.scm,24,$G($1.pcb,10,6));$P($1.scm,18,$G($1.pcb,16,6));$P($1.scm,12,$G($1.pcb,22,6));$P($1.scm,6,$G($1.pcb,28,6));$P($1.scm,0,$G($1.pcb,34,2));$P($1.scm,52,$G($1.ccb,0,2));$P($1.scm,42,$G($1.ccb,2,6));$P($1.scm,36,$G($1.ccb,8,2));$P($1.scm,54,$G($1.scb,0,6));$P($1.scm,48,$G($1.scb,6,4));$1.pri=$a([0,0,0,0,0,0,0,0,0,0]);for(var _9K=0;_9K<=59;_9K+=1){$1.i=_9K;$1.ps=~~($1.i/6);$1.ep=~~Math.pow(2,5-$1.i%6)*($g($1.scm,$1.i)-48);$p($1.pri,$1.ps,$f($g($1.pri,$1.ps)+$1.ep))}$k[$j++]=Infinity;for(var _9W=0,_9X=84;_9W<_9X;_9W++){$k[$j++]=$1.padval}$1.sec=$a();$P($1.sec,0,$1.encmsg)}if($1.mode==4||$1.mode==5||$1.mode==6||$1.mode==-1){if($1.mode==-1){var _9i=$1.encmsg.length<=77?5:4;$1.mode=_9i}$k[$j++]=Infinity;var _9k=$1.mode==5?78:94;for(var _9l=0,_9m=_9k;_9l<_9m;_9l++){$k[$j++]=$1.padval}$1.cws=$a();$p($1.cws,0,$1.mode);$P($1.cws,1,$1.encmsg);$1.pri=$G($1.cws,0,10);$1.sec=$G($1.cws,10,$1.cws.length-10)}$k[$j++]=Infinity;$k[$j++]=1;for(var _9y=0,_9z=63;_9y<_9z;_9y++){var _A0=$k[--$j];var _A1=_A0*2;$k[$j++]=_A0;$k[$j++]=_A1;if(_A1>=64){var _A2=$k[--$j];$k[$j++]=_A2^67}}$1.rsalog=$a();$1.rslog=$a(64);for(var _A5=1;_A5<=63;_A5+=1){$p($1.rslog,$g($1.rsalog,_A5),_A5)}$1.rsprod=function(){var _A9=$k[--$j];var _AA=$k[--$j];$k[$j++]=_AA;$k[$j++]=_A9;if(_A9!=0&&_AA!=0){var _AD=$g($1.rslog,$k[--$j]);var _AI=$g($1.rsalog,$f(_AD+$g($1.rslog,$k[--$j]))%63);$k[$j++]=_AI}else{$j-=2;$k[$j++]=0}};$1.rscodes=function(){$1.rsnum=$k[--$j];$1.cwb=$k[--$j];$k[$j++]=Infinity;$k[$j++]=1;for(var _AM=0,_AN=$1.rsnum;_AM<_AN;_AM++){$k[$j++]=0}$1.coeffs=$a();for(var _AR=1,_AQ=$1.rsnum;_AR<=_AQ;_AR+=1){$1.i=_AR;$p($1.coeffs,$1.i,$g($1.coeffs,$1.i-1));for(var _AY=$1.i-1;_AY>=1;_AY-=1){$1.j=_AY;$k[$j++]=$1.coeffs;$k[$j++]=$1.j;$k[$j++]=$g($1.coeffs,$1.j-1);$k[$j++]=$g($1.coeffs,$1.j);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _Ak=$k[--$j];var _Al=$k[--$j];var _Am=$k[--$j];$p($k[--$j],_Am,$xo(_Al,_Ak))}$k[$j++]=$1.coeffs;$k[$j++]=0;$k[$j++]=$g($1.coeffs,0);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _Au=$k[--$j];var _Av=$k[--$j];$p($k[--$j],_Av,_Au)}$1.coeffs=$G($1.coeffs,0,$1.coeffs.length-1);$k[$j++]=Infinity;for(var _B1=0,_B2=$1.rsnum;_B1<_B2;_B1++){$k[$j++]=0}$1.ecb=$a();for(var _B6=0,_B5=$1.cwb.length-1;_B6<=_B5;_B6+=1){$1.t=$xo($g($1.cwb,_B6),$g($1.ecb,0));for(var _BC=$1.ecb.length-1;_BC>=0;_BC-=1){$1.i=_BC;$1.p=$1.ecb.length-$1.i-1;$k[$j++]=$1.ecb;$k[$j++]=$1.p;$k[$j++]=$1.t;$k[$j++]=$g($1.coeffs,$1.i);$1.rsprod();var _BL=$k[--$j];var _BM=$k[--$j];$p($k[--$j],_BM,_BL);if($1.i>0){$p($1.ecb,$1.p,$xo($g($1.ecb,$1.p+1),$g($1.ecb,$1.p)))}}}$k[$j++]=$1.ecb};$k[$j++]=Infinity;for(var _Ba=0,_BZ=$1.sec.length-1;_Ba<=_BZ;_Ba+=2){$k[$j++]=$g($1.sec,_Ba)}$1.seco=$a();$k[$j++]=Infinity;for(var _Bg=1,_Bf=$1.sec.length-1;_Bg<=_Bf;_Bg+=2){$k[$j++]=$g($1.sec,_Bg)}$1.sece=$a();var _Bl=$1.sec.length==84?20:28;$1.scodes=_Bl;$k[$j++]="secochk";$k[$j++]=$1.seco;$k[$j++]=$1.scodes;$1.rscodes();var _Bo=$k[--$j];$1[$k[--$j]]=_Bo;$k[$j++]="secechk";$k[$j++]=$1.sece;$k[$j++]=$1.scodes;$1.rscodes();var _Bs=$k[--$j];$1[$k[--$j]]=_Bs;$k[$j++]=Infinity;for(var _Bw=0,_Bv=$1.scodes-1;_Bw<=_Bv;_Bw+=1){$k[$j++]=$g($1.secochk,_Bw);$k[$j++]=$g($1.secechk,_Bw)}$1.secchk=$a();$k[$j++]=Infinity;$q($1.pri);$k[$j++]=$1.pri;$k[$j++]=10;$1.rscodes();$q($k[--$j]);$q($1.sec);$q($1.secchk);$1.codewords=$a();$k[$j++]=Infinity;for(var _C8=0,_C9=864;_C8<_C9;_C8++){$k[$j++]=0}$1.mods=$a();for(var _CB=0;_CB<=143;_CB+=1){$1.i=_CB;$k[$j++]=Infinity;var _CG=$R($s(6),$g($1.codewords,$1.i),2);for(var _CH=0,_CI=_CG.length;_CH<_CI;_CH++){$k[$j++]=$g(_CG,_CH)-48}$1.cw=$a();$P($1.mods,6*$1.i+(6-$1.cw.length),$1.cw)}$1.modmap=$a([469,529,286,316,347,346,673,672,703,702,647,676,283,282,313,312,370,610,618,379,378,409,408,439,705,704,559,589,588,619,458,518,640,701,675,674,285,284,315,314,310,340,531,289,288,319,349,348,456,486,517,516,471,470,369,368,399,398,429,428,549,548,579,578,609,608,649,648,679,678,709,708,639,638,669,668,699,698,279,278,309,308,339,338,381,380,411,410,441,440,561,560,591,590,621,620,547,546,577,576,607,606,367,366,397,396,427,426,291,290,321,320,351,350,651,650,681,680,711,710,1,0,31,30,61,60,3,2,33,32,63,62,5,4,35,34,65,64,7,6,37,36,67,66,9,8,39,38,69,68,11,10,41,40,71,70,13,12,43,42,73,72,15,14,45,44,75,74,17,16,47,46,77,76,19,18,49,48,79,78,21,20,51,50,81,80,23,22,53,52,83,82,25,24,55,54,85,84,27,26,57,56,87,86,117,116,147,146,177,176,115,114,145,144,175,174,113,112,143,142,173,172,111,110,141,140,171,170,109,108,139,138,169,168,107,106,137,136,167,166,105,104,135,134,165,164,103,102,133,132,163,162,101,100,131,130,161,160,99,98,129,128,159,158,97,96,127,126,157,156,95,94,125,124,155,154,93,92,123,122,153,152,91,90,121,120,151,150,181,180,211,210,241,240,183,182,213,212,243,242,185,184,215,214,245,244,187,186,217,216,247,246,189,188,219,218,249,248,191,190,221,220,251,250,193,192,223,222,253,252,195,194,225,224,255,254,197,196,227,226,257,256,199,198,229,228,259,258,201,200,231,230,261,260,203,202,233,232,263,262,205,204,235,234,265,264,207,206,237,236,267,266,297,296,327,326,357,356,295,294,325,324,355,354,293,292,323,322,353,352,277,276,307,306,337,336,275,274,305,304,335,334,273,272,303,302,333,332,271,270,301,300,331,330,361,360,391,390,421,420,363,362,393,392,423,422,365,364,395,394,425,424,383,382,413,412,443,442,385,384,415,414,445,444,387,386,417,416,447,446,477,476,507,506,537,536,475,474,505,504,535,534,473,472,503,502,533,532,455,454,485,484,515,514,453,452,483,482,513,512,451,450,481,480,511,510,541,540,571,570,601,600,543,542,573,572,603,602,545,544,575,574,605,604,563,562,593,592,623,622,565,564,595,594,625,624,567,566,597,596,627,626,657,656,687,686,717,716,655,654,685,684,715,714,653,652,683,682,713,712,637,636,667,666,697,696,635,634,665,664,695,694,633,632,663,662,693,692,631,630,661,660,691,690,721,720,751,750,781,780,723,722,753,752,783,782,725,724,755,754,785,784,727,726,757,756,787,786,729,728,759,758,789,788,731,730,761,760,791,790,733,732,763,762,793,792,735,734,765,764,795,794,737,736,767,766,797,796,739,738,769,768,799,798,741,740,771,770,801,800,743,742,773,772,803,802,745,744,775,774,805,804,747,746,777,776,807,806,837,836,867,866,897,896,835,834,865,864,895,894,833,832,863,862,893,892,831,830,861,860,891,890,829,828,859,858,889,888,827,826,857,856,887,886,825,824,855,854,885,884,823,822,853,852,883,882,821,820,851,850,881,880,819,818,849,848,879,878,817,816,847,846,877,876,815,814,845,844,875,874,813,812,843,842,873,872,811,810,841,840,871,870,901,900,931,930,961,960,903,902,933,932,963,962,905,904,935,934,965,964,907,906,937,936,967,966,909,908,939,938,969,968,911,910,941,940,971,970,913,912,943,942,973,972,915,914,945,944,975,974,917,916,947,946,977,976,919,918,949,948,979,978,921,920,951,950,981,980,923,922,953,952,983,982,925,924,955,954,985,984,927,926,957,956,987,986,58,89,88,118,149,148,178,209,208,238,269,268,298,329,328,358,389,388,418,449,448,478,509,508,538,569,568,598,629,628,658,689,688,718,749,748,778,809,808,838,869,868,898,929,928,958,989,988]);$1.pixs=$a(864);$1.j=0;for(var _CT=0,_CS=$1.mods.length-1;_CT<=_CS;_CT+=1){$1.i=_CT;if($g($1.mods,$1.i)==1){$p($1.pixs,$1.j,$g($1.modmap,$1.i));$1.j=$1.j+1}}$k[$j++]=Infinity;var _Cf=$G($1.pixs,0,$1.j);for(var _Cg=0,_Ch=_Cf.length;_Cg<_Ch;_Cg++){$k[$j++]=$g(_Cf,_Cg)}$k[$j++]=28;$k[$j++]=29;$k[$j++]=280;$k[$j++]=281;$k[$j++]=311;$k[$j++]=457;$k[$j++]=488;$k[$j++]=500;$k[$j++]=530;$k[$j++]=670;$k[$j++]=700;$k[$j++]=677;$k[$j++]=707;$1.pixs=$a();var _Cm=new Map([["ren",bwipp_renmaximatrix],["pixs",$1.pixs],["opt",$1.options]]);$k[$j++]=_Cm;if(!$1.dontdraw){bwipp_renmaximatrix()}}function bwipp_azteccode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.format="unset";$1.readerinit=false;$1.layers=-1;$1.eclevel=23;$1.ecaddchars=3;$1.raw=false;$1.parse=false;$1.parsefnc=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.layers=~~$1.layers;$1.eclevel=+$1.eclevel;$1.ecaddchars=~~$1.ecaddchars;$1.fn1=-1;var _B=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["eci",true],["FNC1",$1.fn1]]);$1.fncvals=_B;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _E=$k[--$j];$1[$k[--$j]]=_E;$1.msglen=$1.msg.length;$1.msgbits="";if($ne($1.format,"rune")&&$1.raw){$1.msgbits=$1.barcode}if($ne($1.format,"rune")&&!$1.raw){$1.U=0;$1.L=1;$1.M=2;$1.P=3;$1.D=4;$1.B=5;$1.lu=-2;$1.ll=-3;$1.lm=-4;$1.lp=-5;$1.ld=-6;$1.su=-7;$1.sp=-8;$1.sb=-9;$1.fl=-10;$1.p2=-11;$1.p3=-12;$1.p4=-13;$1.p5=-14;$1.charmaps=$a([$a([$1.sp,$1.sp,$1.sp,$1.fl,$1.sp]),$a([32,32,32,13,32]),$a(["A","a",1,$1.p2,"0"]),$a(["B","b",2,$1.p3,"1"]),$a(["C","c",3,$1.p4,"2"]),$a(["D","d",4,$1.p5,"3"]),$a(["E","e",5,"!","4"]),$a(["F","f",6,'"',"5"]),$a(["G","g",7,"#","6"]),$a(["H","h",8,"$","7"]),$a(["I","i",9,"%","8"]),$a(["J","j",10,"&","9"]),$a(["K","k",11,"'",","]),$a(["L","l",12,40,"."]),$a(["M","m",13,41,$1.lu]),$a(["N","n",27,"*",$1.su]),$a(["O","o",28,"+",-99]),$a(["P","p",29,",",-99]),$a(["Q","q",30,"-",-99]),$a(["R","r",31,".",-99]),$a(["S","s","@","/",-99]),$a(["T","t",92,":",-99]),$a(["U","u","^",";",-99]),$a(["V","v","_","<",-99]),$a(["W","w","`","=",-99]),$a(["X","x","|",">",-99]),$a(["Y","y","~","?",-99]),$a(["Z","z",127,"[",-99]),$a([$1.ll,$1.su,$1.ll,"]",-99]),$a([$1.lm,$1.lm,$1.lu,"{",-99]),$a([$1.ld,$1.ld,$1.lp,"}",-99]),$a([$1.sb,$1.sb,$1.sb,$1.lu,-99])]);$1.charvals=$a([new Map,new Map,new Map,new Map,new Map]);for(var _1K=0,_1J=$1.charmaps.length-1;_1K<=_1J;_1K+=1){$1.i=_1K;$1.encs=$g($1.charmaps,$1.i);for(var _1O=0;_1O<=4;_1O+=1){$1.j=_1O;var _1R=$g($1.encs,$1.j);$k[$j++]=_1R;if($eq($t(_1R),"stringtype")){var _1U=$g($k[--$j],0);$k[$j++]=_1U}$p($g($1.charvals,$1.j),$k[--$j],$1.i)}}var _1e=new Map([["\r\n",$1.p2],[". ",$1.p3],[", ",$1.p4],[": ",$1.p5]]);$1.pcomp=_1e;$1.e=1e4;$1.latlen=$a([$a([0,5,5,10,5,10]),$a([9,0,5,10,5,10]),$a([5,5,0,5,10,10]),$a([5,10,10,0,10,15]),$a([4,9,9,14,0,14]),$a([0,0,0,0,0,0])]);$1.latseq=$a([$a([$a([]),$a([$1.ll]),$a([$1.lm]),$a([$1.lm,$1.lp]),$a([$1.ld]),$a([$1.sb])]),$a([$a([$1.ld,$1.lu]),$a([]),$a([$1.lm]),$a([$1.lm,$1.lp]),$a([$1.ld]),$a([$1.sb])]),$a([$a([$1.lu]),$a([$1.ll]),$a([]),$a([$1.lp]),$a([$1.lu,$1.ld]),$a([$1.sb])]),$a([$a([$1.lu]),$a([$1.lu,$1.ll]),$a([$1.lu,$1.lm]),$a([]),$a([$1.lu,$1.ld]),$a([$1.lu,$1.sb])]),$a([$a([$1.lu]),$a([$1.lu,$1.ll]),$a([$1.lu,$1.lm]),$a([$1.lu,$1.lm,$1.lp]),$a([]),$a([$1.lu,$1.sb])]),$a([$a([$1.lu]),$a([$1.ll]),$a([$1.lm]),$a([]),$a([]),$a([])])]);$1.shftlen=$a([$a([$1.e,$1.e,$1.e,5,$1.e]),$a([5,$1.e,$1.e,5,$1.e]),$a([$1.e,$1.e,$1.e,5,$1.e]),$a([$1.e,$1.e,$1.e,$1.e,$1.e]),$a([4,$1.e,$1.e,4,$1.e])]);$1.charsize=function(){var _3X=$k[--$j];$k[$j++]=_3X;if(_3X>=0){$j--;var _3a=$g($a([5,5,5,5,4,8]),$k[--$j]);$k[$j++]=_3a}else{var _3b=$k[--$j];var _3c=$k[--$j];$k[$j++]=_3b;$k[$j++]=_3c;$j--;var _3d=$k[--$j];$k[$j++]=_3d;if(_3d==$1.fn1){$j--;$k[$j++]=8}else{var _3g=$f(-$k[--$j]-1e6);$k[$j++]=_3g;if(_3g==0){$j--;$k[$j++]=1}var _3h=$k[--$j];$k[$j++]=(~~(Math.log(_3h)/Math.log(10))+1)*4+8}}};$1.curlen=$a([0,$1.e,$1.e,$1.e,$1.e,$1.e]);$1.curseq=$a([$a([]),$a([]),$a([]),$a([]),$a([]),$a([])]);$1.backto=$1.U;$1.lastchar="";$F($1.msg,function(){$1.char=$k[--$j];for(;;){$1.imp=false;var _44=$a([$1.U,$1.L,$1.M,$1.P,$1.D,$1.B]);for(var _45=0,_46=_44.length;_45<_46;_45++){$1.x=$g(_44,_45);var _4E=$a([$1.U,$1.L,$1.M,$1.P,$1.D,$1.B]);for(var _4F=0,_4G=_4E.length;_4F<_4G;_4F++){$1.y=$g(_4E,_4F);if($1.x!=$1.B||$1.y==$1.backto){$1.cost=$f($g($1.curlen,$1.x)+$g($g($1.latlen,$1.x),$1.y));if($1.cost<$g($1.curlen,$1.y)){$p($1.curlen,$1.y,$1.cost);$k[$j++]=$1.curseq;$k[$j++]=$1.y;$k[$j++]=Infinity;$q($g($1.curseq,$1.x));$q($g($g($1.latseq,$1.x),$1.y));var _4l=$a();var _4m=$k[--$j];$p($k[--$j],_4m,_4l);if($1.y==$1.B){$k[$j++]="backto";if($1.x==$1.P||$1.x==$1.D){$k[$j++]=$1.U}else{$k[$j++]=$1.x}var _4w=$k[--$j];$1[$k[--$j]]=_4w}$1.imp=true}}}}if(!$1.imp){break}}$1.nxtlen=$a([$1.e,$1.e,$1.e,$1.e,$1.e,$1.e]);$1.nxtseq=$a(6);var _5D=$a([$1.U,$1.L,$1.M,$1.P,$1.D,$1.B]);for(var _5E=0,_5F=_5D.length;_5E<_5F;_5E++){$1.x=$g(_5D,_5E);for(;;){if($1.char>=0){if($1.x!=$1.B){var _5O=$g($g($1.charvals,$1.x),$1.char)!==undefined;if(!_5O){break}}}else{if($1.x!=$1.P){break}}$k[$j++]="cost";$k[$j++]=$g($1.curlen,$1.x);$k[$j++]=$1.x;$k[$j++]=$1.char;$1.charsize();var _5W=$k[--$j];var _5X=$k[--$j];$1[$k[--$j]]=$f(_5X+_5W);if($1.cost<$g($1.nxtlen,$1.x)){$p($1.nxtlen,$1.x,$1.cost);$k[$j++]=$1.nxtseq;$k[$j++]=$1.x;$k[$j++]=Infinity;$q($g($1.curseq,$1.x));$k[$j++]=$1.char;var _5m=$a();var _5n=$k[--$j];$p($k[--$j],_5n,_5m)}if($1.x==$1.B){break}var _5w=$a([$1.U,$1.L,$1.M,$1.P,$1.D]);for(var _5x=0,_5y=_5w.length;_5x<_5y;_5x++){$1.y=$g(_5w,_5x);if($ne($1.x,$1.y)){$k[$j++]="cost";$k[$j++]=$f($g($1.curlen,$1.y)+$g($g($1.shftlen,$1.y),$1.x));$k[$j++]=$1.x;$k[$j++]=$1.char;$1.charsize();var _6C=$k[--$j];var _6D=$k[--$j];$1[$k[--$j]]=$f(_6D+_6C);if($1.cost<$g($1.nxtlen,$1.y)){$p($1.nxtlen,$1.y,$1.cost);$k[$j++]=$1.nxtseq;$k[$j++]=$1.y;$k[$j++]=Infinity;$q($g($1.curseq,$1.y));var _6T=$1.x==$1.U?$1.su:$1.sp;$k[$j++]=_6T;$k[$j++]=$1.char;var _6V=$a();var _6W=$k[--$j];$p($k[--$j],_6W,_6V)}}}break}}if($ne($1.lastchar,"")&&$1.char>=0){var _6a=$s(2);$p(_6a,0,$1.lastchar);$p(_6a,1,$1.char);$1.pchars=_6a;var _6f=$g($1.pcomp,$1.pchars)!==undefined;if(_6f){if($lt($g($1.curlen,$1.P),$g($1.nxtlen,$1.P))){$p($1.nxtlen,$1.P,$g($1.curlen,$1.P));$k[$j++]=$1.nxtseq;$k[$j++]=$1.P;$k[$j++]=Infinity;$q($g($1.curseq,$1.P));$j--;$k[$j++]=$g($1.pcomp,$1.pchars);var _6z=$a();var _70=$k[--$j];$p($k[--$j],_70,_6z)}}}if($ne($g($1.nxtseq,$1.B),null)){$1.numbytes=0;$F($g($1.nxtseq,$1.B),function(){if($k[--$j]==$1.sb){$k[$j++]=0}else{$k[$j++]=$1.numbytes+1}$1.numbytes=$k[--$j]});if($1.numbytes==32){$p($1.nxtlen,$1.B,$f($g($1.nxtlen,$1.B)+11))}}$1.curlen=$1.nxtlen;$1.curseq=$1.nxtseq;$k[$j++]="lastchar";if($1.char>=0){$k[$j++]=$1.char}else{$k[$j++]=""}var _7M=$k[--$j];$1[$k[--$j]]=_7M});$1.minseq=$1.e;var _7V=$a([$1.U,$1.L,$1.M,$1.P,$1.D,$1.B]);for(var _7W=0,_7X=_7V.length;_7W<_7X;_7W++){$1.i=$g(_7V,_7W);if($g($1.curlen,$1.i)<$1.minseq){$1.minseq=$g($1.curlen,$1.i);$1.seq=$g($1.curseq,$1.i)}}$1.tobin=function(){var _7k=$s($k[--$j]);$k[$j++]=_7k;for(var _7m=0,_7l=_7k.length-1;_7m<=_7l;_7m+=1){var _7n=$k[--$j];$p(_7n,_7m,48);$k[$j++]=_7n}var _7o=$k[--$j];var _7r=$R($s(_7o.length),$k[--$j],2);$P(_7o,_7o.length-_7r.length,_7r);$k[$j++]=_7o};$1.encu=function(){var _7w=$g($g($1.charvals,$1.U),$k[--$j]);$k[$j++]=_7w;$k[$j++]=5;$1.tobin()};$1.encl=function(){var _81=$g($g($1.charvals,$1.L),$k[--$j]);$k[$j++]=_81;$k[$j++]=5;$1.tobin()};$1.encm=function(){var _86=$g($g($1.charvals,$1.M),$k[--$j]);$k[$j++]=_86;$k[$j++]=5;$1.tobin()};$1.encd=function(){var _8B=$g($g($1.charvals,$1.D),$k[--$j]);$k[$j++]=_8B;$k[$j++]=4;$1.tobin()};$1.encp=function(){var _8C=$k[--$j];$k[$j++]=_8C;if(_8C==$1.fn1){$j--;$k[$j++]="00000000"}else{var _8E=$k[--$j];$k[$j++]=_8E;if(_8E<=-1e6){var _8G=$f(-$k[--$j]-1e6);$k[$j++]=_8G;$k[$j++]=_8G;if(_8G==0){$j--;$k[$j++]=1}var _8I=~~(Math.log($k[--$j])/Math.log(10));var _8J=$s((_8I+1)*4+8);$P(_8J,0,"00000");$k[$j++]=_8I;$k[$j++]=_8J;$k[$j++]=_8J;$k[$j++]=_8I+1;$k[$j++]=3;$1.tobin();var _8K=$k[--$j];$P($k[--$j],5,_8K);var _8M=$k[--$j];var _8N=$k[--$j];var _8O=$k[--$j];$k[$j++]=_8M;$k[$j++]=_8O;for(var _8P=_8N;_8P>=0;_8P-=1){var _8Q=$k[--$j];var _8R=$k[--$j];$k[$j++]=_8R;$k[$j++]=~~(_8Q/10);$k[$j++]=_8R;$k[$j++]=_8P*4+8;$k[$j++]=$f(_8Q%10+2);$k[$j++]=4;$1.tobin();var _8S=$k[--$j];var _8T=$k[--$j];$P($k[--$j],_8T,_8S)}$j--}else{var _8Z=$g($g($1.charvals,$1.P),$k[--$j]);$k[$j++]=_8Z;$k[$j++]=5;$1.tobin()}}};$1.encfuncs=$a(["encu","encl","encm","encp","encd"]);$1.addtomsgbits=function(){$1.v=$k[--$j];$P($1.msgbits,$1.j,$1.v);$1.j=$1.j+$1.v.length};$1.state=$1.U;$1.msgbits=$s($1.minseq);$1.i=0;$1.j=0;for(;;){if($1.i>=$1.seq.length){break}if($1.state!=$1.B){$1.char=$g($1.seq,$1.i);$k[$j++]=$1.char;if($1[$g($1.encfuncs,$1.state)]()===true){break}$1.addtomsgbits();$1.i=$1.i+1;if($1.char==$1.su||$1.char==$1.sp){$k[$j++]=$g($1.seq,$1.i);if($1.char==$1.su){$1.encu()}else{$1.encp()}$1.addtomsgbits();$1.i=$1.i+1}if($1.char==$1.lu){$1.state=$1.U}if($1.char==$1.ll){$1.state=$1.L}if($1.char==$1.lm){$1.state=$1.M}if($1.char==$1.lp){$1.state=$1.P}if($1.char==$1.ld){$1.state=$1.D}if($1.char==$1.sb){$1.state=$1.B}}else{$1.numbytes=0;for(;;){if($1.i+$1.numbytes>=$1.seq.length){break}if($g($1.seq,$1.i+$1.numbytes)<0){break}$1.numbytes=$1.numbytes+1}if($1.numbytes<=31){$k[$j++]=$1.numbytes;$k[$j++]=5;$1.tobin();$1.addtomsgbits()}else{$k[$j++]=0;$k[$j++]=5;$1.tobin();$1.addtomsgbits();$k[$j++]=$1.numbytes-31;$k[$j++]=11;$1.tobin();$1.addtomsgbits()}for(var _9b=0,_9c=$1.numbytes;_9b<_9c;_9b++){$k[$j++]=$g($1.seq,$1.i);$k[$j++]=8;$1.tobin();$1.addtomsgbits();$1.i=$1.i+1}if($1.i<$1.seq.length){$1.char=$g($1.seq,$1.i);$1.i=$1.i+1;if($1.char==$1.lu){$1.state=$1.U}if($1.char==$1.ll){$1.state=$1.L}if($1.char==$1.lm){$1.state=$1.M}}}}}$1.metrics=$a([$a(["rune",0,0,0,6]),$a(["compact",1,1,17,6]),$a(["full",1,1,21,6]),$a(["compact",2,0,40,6]),$a(["full",2,1,48,6]),$a(["compact",3,0,51,8]),$a(["full",3,1,60,8]),$a(["compact",4,0,76,8]),$a(["full",4,1,88,8]),$a(["full",5,1,120,8]),$a(["full",6,1,156,8]),$a(["full",7,1,196,8]),$a(["full",8,1,240,8]),$a(["full",9,1,230,10]),$a(["full",10,1,272,10]),$a(["full",11,1,316,10]),$a(["full",12,1,364,10]),$a(["full",13,1,416,10]),$a(["full",14,1,470,10]),$a(["full",15,1,528,10]),$a(["full",16,1,588,10]),$a(["full",17,1,652,10]),$a(["full",18,1,720,10]),$a(["full",19,1,790,10]),$a(["full",20,1,864,10]),$a(["full",21,1,940,10]),$a(["full",22,1,1020,10]),$a(["full",23,0,920,12]),$a(["full",24,0,992,12]),$a(["full",25,0,1066,12]),$a(["full",26,0,1144,12]),$a(["full",27,0,1224,12]),$a(["full",28,0,1306,12]),$a(["full",29,0,1392,12]),$a(["full",30,0,1480,12]),$a(["full",31,0,1570,12]),$a(["full",32,0,1664,12])]);$1.i=0;for(;;){$1.m=$g($1.metrics,$1.i);$1.frmt=$g($1.m,0);$1.mlyr=$g($1.m,1);$1.icap=$g($1.m,2);$1.ncws=$g($1.m,3);$1.bpcw=$g($1.m,4);$1.numecw=~~Math.ceil($f($1.ncws*$1.eclevel/100+$1.ecaddchars));if($1.msgbits.length==0){$1.numecw=0}$1.numdcw=$f($1.ncws-$1.numecw);$1.okay=true;if($ne($1.format,"unset")&&$ne($1.format,$1.frmt)){$1.okay=false}if($1.readerinit&&$1.icap!=1){$1.okay=false}if($1.layers!=-1&&$1.layers!=$1.mlyr){$1.okay=false}if(~~Math.ceil($1.msgbits.length/$1.bpcw)>$1.numdcw){$1.okay=false}if($1.okay){break}$1.i=$1.i+1}$1.layers=$1.mlyr;$1.format=$1.frmt;$1.allzero=function(){var _B6=$k[--$j];$k[$j++]=$eq(_B6,$G("000000000000",0,_B6.length))};$1.allones=function(){var _B8=$k[--$j];$k[$j++]=$eq(_B8,$G("111111111111",0,_B8.length))};$1.cws=$a($1.ncws);$1.m=0;$1.c=0;for(;;){if($1.msgbits.length<=$1.m){break}if($1.msgbits.length-$1.m>=$1.bpcw){$1.cwb=$G($1.msgbits,$1.m,$f($1.bpcw-1));$1.cwf=$G($1.msgbits,$f($f($1.m+$1.bpcw)-1),1);$k[$j++]=$1.cwb;$1.allzero();if($k[--$j]){$1.cwf="1";$1.m=$1.m-1}$k[$j++]=$1.cwb;$1.allones();if($k[--$j]){$1.cwf="0";$1.m=$1.m-1}var _BV=$s(12);$P(_BV,0,$1.cwb);$P(_BV,$f($1.bpcw-1),$1.cwf);$1.cwb=$G(_BV,0,$1.bpcw)}else{$1.cwb=$G($1.msgbits,$1.m,$1.msgbits.length-$1.m);var _Bh=$Z($s(12),"111111111111");$P(_Bh,0,$1.cwb);$1.cwb=$G(_Bh,0,$1.bpcw);$k[$j++]=$1.cwb;$1.allones();if($k[--$j]){$P($1.cwb,$1.cwb.length-1,"0")}}$1.cw=0;for(var _Br=0,_Bq=$f($1.bpcw-1);_Br<=_Bq;_Br+=1){$1.i=_Br;$1.cw=$f($1.cw+~~Math.pow(2,$f($f($1.bpcw-$1.i)-1))*$f($g($1.cwb,$1.i)-48))}$p($1.cws,$1.c,$1.cw);$1.m=$f($1.m+$1.bpcw);$1.c=$1.c+1}$1.cws=$G($1.cws,0,$1.c);$1.rscodes=function(){$1.rspm=$k[--$j];$1.rsgf=$k[--$j];$1.rsnc=$k[--$j];$1.rscws=$k[--$j];$k[$j++]=Infinity;$k[$j++]=1;for(var _CC=0,_CD=$f($1.rsgf-1);_CC<_CD;_CC++){var _CE=$k[--$j];var _CF=_CE*2;$k[$j++]=_CE;$k[$j++]=_CF;if(_CF>=$1.rsgf){var _CI=$k[--$j];$k[$j++]=$xo(_CI,$1.rspm)}}$1.rsalog=$a();$1.rslog=$a($1.rsgf);for(var _CO=1,_CN=$f($1.rsgf-1);_CO<=_CN;_CO+=1){$p($1.rslog,$g($1.rsalog,_CO),_CO)}$1.rsprod=function(){var _CS=$k[--$j];var _CT=$k[--$j];$k[$j++]=_CT;$k[$j++]=_CS;if(_CS!=0&&_CT!=0){var _CW=$g($1.rslog,$k[--$j]);var _Cc=$g($1.rsalog,$f(_CW+$g($1.rslog,$k[--$j]))%$f($1.rsgf-1));$k[$j++]=_Cc}else{$j-=2;$k[$j++]=0}};$k[$j++]=Infinity;$k[$j++]=1;for(var _Ce=0,_Cf=$1.rsnc;_Ce<_Cf;_Ce++){$k[$j++]=0}$1.coeffs=$a();for(var _Cj=1,_Ci=$1.rsnc;_Cj<=_Ci;_Cj+=1){$1.i=_Cj;$p($1.coeffs,$1.i,$g($1.coeffs,$1.i-1));for(var _Cq=$1.i-1;_Cq>=1;_Cq-=1){$1.j=_Cq;$k[$j++]=$1.coeffs;$k[$j++]=$1.j;$k[$j++]=$g($1.coeffs,$1.j-1);$k[$j++]=$g($1.coeffs,$1.j);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _D2=$k[--$j];var _D3=$k[--$j];var _D4=$k[--$j];$p($k[--$j],_D4,$xo(_D3,_D2))}$k[$j++]=$1.coeffs;$k[$j++]=0;$k[$j++]=$g($1.coeffs,0);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _DC=$k[--$j];var _DD=$k[--$j];$p($k[--$j],_DD,_DC)}$1.nd=$1.rscws.length;$k[$j++]=Infinity;$F($1.rscws);for(var _DI=0,_DJ=$1.rsnc;_DI<_DJ;_DI++){$k[$j++]=0}$k[$j++]=0;$1.rscws=$a();for(var _DN=0,_DM=$1.nd-1;_DN<=_DM;_DN+=1){$1.k=$xo($g($1.rscws,_DN),$g($1.rscws,$1.nd));for(var _DV=0,_DU=$f($1.rsnc-1);_DV<=_DU;_DV+=1){$1.j=_DV;$k[$j++]=$1.rscws;$k[$j++]=$1.nd+$1.j;$k[$j++]=$g($1.rscws,$1.nd+$1.j+1);$k[$j++]=$1.k;$k[$j++]=$g($1.coeffs,$f($f($1.rsnc-$1.j)-1));$1.rsprod();var _Di=$k[--$j];var _Dj=$k[--$j];var _Dk=$k[--$j];$p($k[--$j],_Dk,$xo(_Dj,_Di))}}$k[$j++]=$G($1.rscws,0,$1.rscws.length-1)};if($eq($1.format,"full")){$1.mode=($f($1.layers-1)<<11)+($1.cws.length-1);if($1.readerinit){$1.mode=$1.mode|1024}$1.mode=$a([($1.mode&61440)>>>12,($1.mode&3840)>>>8,($1.mode&240)>>>4,$1.mode&15]);$k[$j++]="mode";$k[$j++]=$1.mode;$k[$j++]=6;$k[$j++]=16;$k[$j++]=19;$1.rscodes();var _E0=$k[--$j];$1[$k[--$j]]=_E0}if($eq($1.format,"compact")){$1.mode=($f($1.layers-1)<<6)+($1.cws.length-1);if($1.readerinit){$1.mode=$1.mode|32}$1.mode=$a([($1.mode&240)>>>4,$1.mode&15]);$k[$j++]="mode";$k[$j++]=$1.mode;$k[$j++]=5;$k[$j++]=16;$k[$j++]=19;$1.rscodes();var _EB=$k[--$j];$1[$k[--$j]]=_EB}if($eq($1.format,"rune")){$1.mode=~~$z($1.barcode);$1.mode=$a([($1.mode&240)>>>4,$1.mode&15]);$k[$j++]="mode";$k[$j++]=$1.mode;$k[$j++]=5;$k[$j++]=16;$k[$j++]=19;$1.rscodes();var _EJ=$k[--$j];$1[$k[--$j]]=_EJ;$k[$j++]=Infinity;var _EL=$1.mode;for(var _EM=0,_EN=_EL.length;_EM<_EN;_EM++){$k[$j++]=$g(_EL,_EM)^10}$1.mode=$a()}$1.modebits=$s($1.mode.length*4);for(var _EU=0,_ET=$1.modebits.length-1;_EU<=_ET;_EU+=1){$P($1.modebits,_EU,"0")}for(var _EY=0,_EX=$1.mode.length-1;_EY<=_EX;_EY+=1){$1.i=_EY;var _Ee=$R($s(4),$g($1.mode,$1.i),2);$P($1.modebits,4-_Ee.length+4*$1.i,_Ee)}$1.rsparams=$a([$a([]),$a([]),$a([]),$a([]),$a([]),$a([]),$a([64,67]),$a([]),$a([256,301]),$a([]),$a([1024,1033]),$a([]),$a([4096,4201])]);$k[$j++]="cws";$k[$j++]=$1.cws;$k[$j++]=$f($1.ncws-$1.cws.length);$F($g($1.rsparams,$1.bpcw));$1.rscodes();var _F0=$k[--$j];$1[$k[--$j]]=_F0;if($eq($1.format,"full")){$1.databits=$s($f($1.layers*$1.layers*16+$1.layers*112))}else{$1.databits=$s($f($1.layers*$1.layers*16+$1.layers*88))}for(var _FD=0,_FC=$1.databits.length-1;_FD<=_FC;_FD+=1){$P($1.databits,_FD,"0")}for(var _FH=0,_FG=$f($1.ncws-1);_FH<=_FG;_FH+=1){$1.i=_FH;var _FO=$R($s($1.bpcw),$g($1.cws,$1.i),2);$P($1.databits,$f($f($f($1.bpcw-_FO.length)+$1.bpcw*$1.i)+$f($1.databits.length-$1.ncws*$1.bpcw)),_FO)}$1.cmv=function(){var _FW=$k[--$j];var _FX=$k[--$j];$k[$j++]=$f($f(_FX-_FW*$1.size)+$1.mid)};$1.lmv=function(){$1.lbit=$k[--$j];$1.llyr=$k[--$j];$1.lwid=$f($1.fw+$1.llyr*4);$1.ldir=~~(~~($1.lbit/2)/$1.lwid);if($1.ldir==0){$k[$j++]=$f(-~~($f($1.lwid-1)/2)+1+~~($1.lbit/2)%$1.lwid);$k[$j++]=$f($f(~~(($1.fw-1)/2)+$1.llyr*2)+$1.lbit%2);$1.cmv()}if($1.ldir==1){$k[$j++]=$f($f(~~($1.fw/2)+$1.llyr*2)+$1.lbit%2);$k[$j++]=$f(~~($f($1.lwid-1)/2)-1-~~($1.lbit/2)%$1.lwid);$1.cmv()}if($1.ldir==2){$k[$j++]=-$f(-~~($1.lwid/2)+1+~~($1.lbit/2)%$1.lwid);$k[$j++]=-$f($f(~~($1.fw/2)+$1.llyr*2)+$1.lbit%2);$1.cmv()}if($1.ldir==3){$k[$j++]=-$f($f(~~(($1.fw-1)/2)+$1.llyr*2)+$1.lbit%2);$k[$j++]=-$f(~~($1.lwid/2)-1-~~($1.lbit/2)%$1.lwid);$1.cmv()}};if($eq($1.format,"full")){$1.fw=12}else{$1.fw=9}$1.size=$f($f($1.fw+$1.layers*4)+2);$k[$j++]=Infinity;for(var _GC=0,_GD=$1.size*$1.size;_GC<_GD;_GC++){$k[$j++]=-1}$1.pixs=$a();$1.mid=$f(~~($f($1.size-1)/2)*$1.size+~~($f($1.size-1)/2));$1.i=0;for(var _GK=1,_GJ=$1.layers;_GK<=_GJ;_GK+=1){$1.layer=_GK;for(var _GO=0,_GN=($1.fw+$1.layer*4)*8-1;_GO<=_GN;_GO+=1){$1.pos=_GO;$k[$j++]=$1.pixs;$k[$j++]=$1.layer;$k[$j++]=$1.pos;$1.lmv();var _GW=$k[--$j];$p($k[--$j],_GW,$g($1.databits,$1.databits.length-$1.i-1)-48);$1.i=$1.i+1}}if($eq($1.format,"full")){$1.fw=13;$1.size=$f($f($f($1.fw+$1.layers*4)+2)+~~$f($f($1.layers+10.5)/7.5-1)*2);$1.mid=~~($1.size*$1.size/2);$k[$j++]=Infinity;for(var _Gh=0,_Gi=$1.size*$1.size;_Gh<_Gi;_Gh++){$k[$j++]=-2}$1.npixs=$a();for(var _Gm=0,_Gl=~~($1.size/2);_Gm<=_Gl;_Gm+=16){$1.i=_Gm;for(var _Gp=0,_Go=$f($1.size-1);_Gp<=_Go;_Gp+=1){$1.j=_Gp;$k[$j++]=$1.npixs;$k[$j++]=-~~($1.size/2)+$1.j;$k[$j++]=$1.i;$1.cmv();var _Gy=$k[--$j];$P($k[--$j],_Gy,$a([(~~($1.size/2)+$1.j+$1.i+1)%2]));$k[$j++]=$1.npixs;$k[$j++]=-~~($1.size/2)+$1.j;$k[$j++]=-$1.i;$1.cmv();var _H8=$k[--$j];$P($k[--$j],_H8,$a([(~~($1.size/2)+$1.j+$1.i+1)%2]));$k[$j++]=$1.npixs;$k[$j++]=$1.i;$k[$j++]=-~~($1.size/2)+$1.j;$1.cmv();var _HI=$k[--$j];$P($k[--$j],_HI,$a([(~~($1.size/2)+$1.j+$1.i+1)%2]));$k[$j++]=$1.npixs;$k[$j++]=-$1.i;$k[$j++]=-~~($1.size/2)+$1.j;$1.cmv();var _HS=$k[--$j];$P($k[--$j],_HS,$a([(~~($1.size/2)+$1.j+$1.i+1)%2]))}}$1.j=0;for(var _HW=0,_HV=$1.npixs.length-1;_HW<=_HV;_HW+=1){$1.i=_HW;if($g($1.npixs,$1.i)==-2){$p($1.npixs,$1.i,$g($1.pixs,$1.j));$1.j=$1.j+1}}$1.pixs=$1.npixs}$1.fw=~~($1.fw/2);for(var _Hl=-$1.fw,_Hk=$1.fw;_Hl<=_Hk;_Hl+=1){$1.i=_Hl;for(var _Hp=-$1.fw,_Ho=$1.fw;_Hp<=_Ho;_Hp+=1){$1.j=_Hp;$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=$1.j;$1.cmv();if(Math.abs($1.i)>Math.abs($1.j)){$k[$j++]=Math.abs($1.i)}else{$k[$j++]=Math.abs($1.j)}var _Hx=$k[--$j];var _Hy=$k[--$j];$p($k[--$j],_Hy,$f(_Hx+1)%2)}}var _Ia=$a([$a([-($1.fw+1),$1.fw,1]),$a([-($1.fw+1),$1.fw+1,1]),$a([-$1.fw,$1.fw+1,1]),$a([$1.fw+1,$1.fw+1,1]),$a([$1.fw+1,$1.fw,1]),$a([$1.fw+1,-$1.fw,1]),$a([$1.fw,$1.fw+1,0]),$a([$1.fw+1,-($1.fw+1),0]),$a([$1.fw,-($1.fw+1),0]),$a([-$1.fw,-($1.fw+1),0]),$a([-($1.fw+1),-($1.fw+1),0]),$a([-($1.fw+1),-$1.fw,0])]);for(var _Ib=0,_Ic=_Ia.length;_Ib<_Ic;_Ib++){$k[$j++]=$1.pixs;$F($g(_Ia,_Ib));var _If=$k[--$j];var _Ig=$k[--$j];var _Ih=$k[--$j];$k[$j++]=_If;$k[$j++]=_Ih;$k[$j++]=_Ig;$1.cmv();var _Ii=$k[--$j];var _Ij=$k[--$j];$p($k[--$j],_Ii,_Ij)}if($eq($1.format,"full")){$1.modemap=$a([$a([-5,7]),$a([-4,7]),$a([-3,7]),$a([-2,7]),$a([-1,7]),$a([1,7]),$a([2,7]),$a([3,7]),$a([4,7]),$a([5,7]),$a([7,5]),$a([7,4]),$a([7,3]),$a([7,2]),$a([7,1]),$a([7,-1]),$a([7,-2]),$a([7,-3]),$a([7,-4]),$a([7,-5]),$a([5,-7]),$a([4,-7]),$a([3,-7]),$a([2,-7]),$a([1,-7]),$a([-1,-7]),$a([-2,-7]),$a([-3,-7]),$a([-4,-7]),$a([-5,-7]),$a([-7,-5]),$a([-7,-4]),$a([-7,-3]),$a([-7,-2]),$a([-7,-1]),$a([-7,1]),$a([-7,2]),$a([-7,3]),$a([-7,4]),$a([-7,5])])}else{$1.modemap=$a([$a([-3,5]),$a([-2,5]),$a([-1,5]),$a([0,5]),$a([1,5]),$a([2,5]),$a([3,5]),$a([5,3]),$a([5,2]),$a([5,1]),$a([5,0]),$a([5,-1]),$a([5,-2]),$a([5,-3]),$a([3,-5]),$a([2,-5]),$a([1,-5]),$a([0,-5]),$a([-1,-5]),$a([-2,-5]),$a([-3,-5]),$a([-5,-3]),$a([-5,-2]),$a([-5,-1]),$a([-5,0]),$a([-5,1]),$a([-5,2]),$a([-5,3])])}for(var _Jw=0,_Jv=$1.modemap.length-1;_Jw<=_Jv;_Jw+=1){$1.i=_Jw;$k[$j++]=$1.pixs;$F($g($1.modemap,$1.i));$1.cmv();var _K4=$k[--$j];$p($k[--$j],_K4,$g($1.modebits,$1.i)-48)}var _KC=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",$1.size],["pixy",$1.size],["height",$1.size*2/72],["width",$1.size*2/72],["opt",$1.options]]);$k[$j++]=_KC;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_azteccodecompact(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$p($1.options,"dontdraw",true);$p($1.options,"format","compact");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_azteccode();var _9=$k[--$j];$1[$k[--$j]]=_9;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_aztecrune(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$p($1.options,"dontdraw",true);$p($1.options,"format","rune");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_azteccode();var _9=$k[--$j];$1[$k[--$j]]=_9;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_codeone(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.version="unset";$1.parse=false;$1.parsefnc=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.stype=$eq($G($1.version,0,1),"S");if($1.stype){$1.vals=$a(["1","1010","1100100","1111101000","10011100010000","11000011010100000","11110100001001000000","100110001001011010000000","101111101011110000100000000","111011100110101100101000000000","1001010100000010111110010000000000","1011101001000011101101110100000000000","1110100011010100101001010001000000000000","10010001100001001110011100101010000000000000","10110101111001100010000011110100100000000000000","11100011010111111010100100110001101000000000000000","100011100001101111001001101111110000010000000000000000","101100011010001010111100001011101100010100000000000000000"]);$1.normalize=function(){$1.base=$k[--$j];$1.num=$k[--$j];for(var _C=$1.num.length-1;_C>=1;_C-=1){$1.i=_C;var _D=$1.num;var _E=$1.i;$p(_D,_E-1,$f($g(_D,_E-1)+~~($g($1.num,$1.i)/$1.base)));$p($1.num,$1.i,$g($1.num,$1.i)%$1.base)}for(;;){if($lt($g($1.num,0),$1.base)){break}$k[$j++]=Infinity;$k[$j++]=0;$F($1.num);$1.num=$a();$p($1.num,0,$f($g($1.num,0)+~~($g($1.num,1)/$1.base)));$p($1.num,1,$g($1.num,1)%$1.base)}$k[$j++]=Infinity;$1.i=true;var _f=$1.num;for(var _g=0,_h=_f.length;_g<_h;_g++){var _i=$g(_f,_g);$k[$j++]=_i;if(_i==0&&$1.i){$j--}else{$1.i=false}}$1.num=$a();if($1.num.length==0){$1.num=$a([0])}$k[$j++]=$1.num};$1.bigadd=function(){var _o=$k[--$j];var _p=$k[--$j];$1.offset=Math.abs(_o.length-_p.length);if(_o.length<_p.length){var _=_o;_o=_p;_p=_}$1.a=_o;$1.b=_p;for(var _s=0,_r=$1.b.length-1;_s<=_r;_s+=1){var _t=$1.a;var _u=$1.offset;$p(_t,_s+_u,$f($g(_t,_s+_u)+$g($1.b,_s)))}$k[$j++]=$1.a};$1.barlen=$1.barcode.length;$1.v=$a([1]);for(var _13=0,_12=$1.barlen-1;_13<=_12;_13+=1){$1.i=_13;$k[$j++]=Infinity;$F($g($1.vals,$1.i),function(){var _17=$k[--$j];$k[$j++]=$f(_17-48)*$f($g($1.barcode,$1.barlen-$1.i-1)-48)});var _1C=$a();$k[$j++]=_1C;$k[$j++]=$1.v;$1.bigadd();$1.v=$k[--$j]}$k[$j++]="v";$k[$j++]=$1.v;$k[$j++]=2;$1.normalize();var _1G=$k[--$j];$1[$k[--$j]]=_1G;$k[$j++]=Infinity;for(var _1J=0,_1K=(5-$1.v.length%5)%5;_1J<_1K;_1J++){$k[$j++]=0}$q($1.v);$1.v=$a();$1.cws=$a(~~($1.v.length/5));for(var _1R=0,_1Q=$1.cws.length-1;_1R<=_1Q;_1R+=1){$1.i=_1R;var _1U=$G($1.v,$1.i*5,5);$k[$j++]=0;for(var _1V=0,_1W=_1U.length;_1V<_1W;_1V++){var _1Y=$k[--$j];$k[$j++]=$f(_1Y+$g(_1U,_1V))*2}$p($1.cws,$1.i,~~($k[--$j]/2))}$1.metrics=$a([$a(["S-10",8,11,10,4,4,1,99,99,99]),$a(["S-20",8,21,20,8,8,1,99,99,99]),$a(["S-30",8,31,30,12,12,1,99,99,99])])}else{$1.fnc1=-1;$1.fnc3=-2;var _1k=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["eci",true],["FNC1",$1.fnc1],["FNC3",$1.fnc3]]);$1.fncvals=_1k;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _1n=$k[--$j];$1[$k[--$j]]=_1n;$1.msglen=$1.msg.length;$1.lC=-5;$1.lB=-6;$1.lX=-7;$1.lT=-8;$1.lD=-9;$1.unl=-10;$1.fnc2=-11;$1.fnc4=-12;$1.sft1=-13;$1.sft2=-14;$1.sft3=-15;$1.eci=-16;$1.pad=-17;$1.fnc1lD=-18;$1.unlcw=255;$1.eciesc=92;var _1q=$1.msg;$k[$j++]="numecis";$k[$j++]=0;for(var _1r=0,_1s=_1q.length;_1r<_1s;_1r++){if($g(_1q,_1r)<=-1e6){var _1u=$k[--$j];$k[$j++]=$f(_1u+1)}}var _1v=$k[--$j];$1[$k[--$j]]=_1v;if($1.numecis>0){$1.msgtmp=$a($f($f($1.msg.length*2+$1.numecis*6)+2));$p($1.msgtmp,0,$1.pad);$p($1.msgtmp,1,$1.eciesc);$1.j=2;for(var _27=0,_26=$1.msg.length-1;_27<=_26;_27+=1){var _29=$g($1.msg,_27);$k[$j++]=_29;if(_29<=-1e6){var _2D=$G($R($s(7),-$k[--$j],10),1,6);for(var _2E=0,_2F=_2D.length;_2E<_2F;_2E++){$k[$j++]=$g(_2D,_2E)}$r($a(6));$P($1.msgtmp,$1.j+1,$k[--$j]);$p($1.msgtmp,$1.j,$1.eciesc);$1.j=$1.j+7}else{var _2P=$k[--$j];$k[$j++]=_2P;if(_2P!=$1.eciesc){$p($1.msgtmp,$1.j,$k[--$j]);$1.j=$1.j+1}else{$j--;$p($1.msgtmp,$1.j,$1.eciesc);$p($1.msgtmp,$1.j+1,$1.eciesc);$1.j=$1.j+2}}}$1.msg=$G($1.msgtmp,0,$1.j);$1.msglen=$1.msg.length}$1.metrics=$a([$a(["A",16,18,16,10,10,1,4,99,6]),$a(["B",22,22,20,19,16,1,4,99,8]),$a(["C",28,32,28,44,26,1,4,22,11]),$a(["D",40,42,36,91,44,1,4,16,16]),$a(["E",52,54,48,182,70,1,4,22,22]),$a(["F",70,76,68,370,140,2,4,22,31]),$a(["G",104,98,88,732,280,4,6,21,47]),$a(["H",148,134,120,1480,560,8,6,20,69]),$a(["T-16",16,17,16,10,10,1,99,99,99]),$a(["T-32",16,33,32,24,16,1,99,99,99]),$a(["T-48",16,49,48,38,22,1,99,99,99])]);$1.fullcws=$a([]);var _2t=$1.metrics;for(var _2u=0,_2v=_2t.length;_2u<_2v;_2u++){$1.m=$g(_2t,_2u);$1.vers=$g($1.m,0);$1.dcws=$g($1.m,4);$1.okay=true;if($eq($1.version,"unset")){if($1.vers.length!=1){$1.okay=false}}else{if($ne($1.version,$1.vers)){$1.okay=false}}if($1.okay){$k[$j++]=Infinity;$q($1.fullcws);$k[$j++]=$1.dcws;$1.fullcws=$a()}}$k[$j++]=Infinity;for(var _39=0,_3A=1480;_39<_3A;_39++){$k[$j++]=1e4}$1.numremcws=$a();var _3C=$1.fullcws;for(var _3D=0,_3E=_3C.length;_3D<_3E;_3D++){$p($1.numremcws,$f($g(_3C,_3D)-1),1)}for(var _3H=1478;_3H>=0;_3H-=1){$1.i=_3H;if($g($1.numremcws,$1.i)!=1){$p($1.numremcws,$1.i,$f($g($1.numremcws,$1.i+1)+1))}}$k[$j++]=Infinity;for(var _3Q=0;_3Q<=128;_3Q+=1){$k[$j++]=_3Q;$k[$j++]=_3Q+1}$k[$j++]=$1.pad;$k[$j++]=129;for(var _3S=0;_3S<=99;_3S+=1){var _3U=$R($s(2),_3S,10);var _3W=$Z($s(2),"00");$P(_3W,2-_3U.length,_3U);$k[$j++]=_3W;$k[$j++]=_3S+130}var _3e=$a([$1.lC,$1.lB,$1.fnc1,$1.fnc2,$1.fnc3,$1.fnc4,$1.fnc1lD]);$k[$j++]=229;for(var _3f=0,_3g=_3e.length;_3f<_3g;_3f++){var _3j=$f($k[--$j]+1);$k[$j++]=$g(_3e,_3f);$k[$j++]=_3j;$k[$j++]=_3j}$j--;$k[$j++]=$1.lX;$k[$j++]=238;$k[$j++]=$1.lT;$k[$j++]=239;$1.Avals=$d();$k[$j++]=Infinity;var _3n=$1.Avals;for(var _3s=_3n.size,_3r=_3n.keys(),_3q=0;_3q<_3s;_3q++){var _3o=_3r.next().value;$k[$j++]=_3o;$k[$j++]=_3n.get(_3o);$k[$j++]=Infinity;var _3t=$k[--$j];var _3u=$k[--$j];$k[$j++]=_3t;$k[$j++]=_3u;var _3v=$a();$k[$j++]=_3v}$1.Avals=$d();$k[$j++]=Infinity;$k[$j++]=$1.sft1;$k[$j++]=0;$k[$j++]=$1.sft2;$k[$j++]=1;$k[$j++]=$1.sft3;$k[$j++]=2;$k[$j++]=32;$k[$j++]=3;for(var _40=48;_40<=57;_40+=1){$k[$j++]=_40;$k[$j++]=_40-44}for(var _41=65;_41<=90;_41+=1){$k[$j++]=_41;$k[$j++]=_41-51}$1.CNvals=$d();$k[$j++]=Infinity;for(var _43=0;_43<=31;_43+=1){$k[$j++]=_43;$k[$j++]=_43}$1.C1vals=$d();$k[$j++]=Infinity;for(var _45=33;_45<=47;_45+=1){$k[$j++]=_45;$k[$j++]=_45-33}for(var _46=58;_46<=64;_46+=1){$k[$j++]=_46;$k[$j++]=_46-43}for(var _47=91;_47<=95;_47+=1){$k[$j++]=_47;$k[$j++]=_47-69}$k[$j++]=$1.fnc1;$k[$j++]=27;$k[$j++]=$1.fnc2;$k[$j++]=28;$k[$j++]=$1.fnc3;$k[$j++]=29;$k[$j++]=$1.fnc4;$k[$j++]=30;$k[$j++]=$1.pad;$k[$j++]=31;$1.C2vals=$d();$k[$j++]=Infinity;for(var _4E=96;_4E<=127;_4E+=1){$k[$j++]=_4E;$k[$j++]=_4E-96}$1.C3vals=$d();$k[$j++]=Infinity;var _4G=$1.CNvals;for(var _4L=_4G.size,_4K=_4G.keys(),_4J=0;_4J<_4L;_4J++){var _4H=_4K.next().value;$k[$j++]=_4H;$k[$j++]=_4G.get(_4H);$k[$j++]=Infinity;var _4M=$k[--$j];var _4N=$k[--$j];$k[$j++]=_4M;$k[$j++]=_4N;var _4O=$a();$k[$j++]=_4O}var _4P=$1.C1vals;for(var _4U=_4P.size,_4T=_4P.keys(),_4S=0;_4S<_4U;_4S++){var _4Q=_4T.next().value;$k[$j++]=_4Q;$k[$j++]=_4P.get(_4Q);$k[$j++]=Infinity;var _4V=$k[--$j];var _4W=$k[--$j];$k[$j++]=_4V;$k[$j++]=$g($1.CNvals,$1.sft1);$k[$j++]=_4W;var _4a=$a();$k[$j++]=_4a}var _4b=$1.C2vals;for(var _4g=_4b.size,_4f=_4b.keys(),_4e=0;_4e<_4g;_4e++){var _4c=_4f.next().value;$k[$j++]=_4c;$k[$j++]=_4b.get(_4c);$k[$j++]=Infinity;var _4h=$k[--$j];var _4i=$k[--$j];$k[$j++]=_4h;$k[$j++]=$g($1.CNvals,$1.sft2);$k[$j++]=_4i;var _4m=$a();$k[$j++]=_4m}var _4n=$1.C3vals;for(var _4s=_4n.size,_4r=_4n.keys(),_4q=0;_4q<_4s;_4q++){var _4o=_4r.next().value;$k[$j++]=_4o;$k[$j++]=_4n.get(_4o);$k[$j++]=Infinity;var _4t=$k[--$j];var _4u=$k[--$j];$k[$j++]=_4t;$k[$j++]=$g($1.CNvals,$1.sft3);$k[$j++]=_4u;var _4y=$a();$k[$j++]=_4y}$1.Cvals=$d();$k[$j++]=Infinity;$k[$j++]=$1.sft1;$k[$j++]=0;$k[$j++]=$1.sft2;$k[$j++]=1;$k[$j++]=$1.sft3;$k[$j++]=2;$k[$j++]=32;$k[$j++]=3;for(var _53=48;_53<=57;_53+=1){$k[$j++]=_53;$k[$j++]=_53-44}for(var _54=97;_54<=122;_54+=1){$k[$j++]=_54;$k[$j++]=_54-83}$1.TNvals=$d();$k[$j++]=Infinity;for(var _56=0;_56<=31;_56+=1){$k[$j++]=_56;$k[$j++]=_56}$1.T1vals=$d();$k[$j++]=Infinity;for(var _58=33;_58<=47;_58+=1){$k[$j++]=_58;$k[$j++]=_58-33}for(var _59=58;_59<=64;_59+=1){$k[$j++]=_59;$k[$j++]=_59-43}for(var _5A=91;_5A<=95;_5A+=1){$k[$j++]=_5A;$k[$j++]=_5A-69}$k[$j++]=$1.fnc1;$k[$j++]=27;$k[$j++]=$1.fnc2;$k[$j++]=28;$k[$j++]=$1.fnc3;$k[$j++]=29;$k[$j++]=$1.fnc4;$k[$j++]=30;$k[$j++]=$1.pad;$k[$j++]=31;$1.T2vals=$d();$k[$j++]=Infinity;$k[$j++]=96;$k[$j++]=0;for(var _5H=65;_5H<=90;_5H+=1){$k[$j++]=_5H;$k[$j++]=_5H-64}for(var _5I=123;_5I<=127;_5I+=1){$k[$j++]=_5I;$k[$j++]=_5I-96}$1.T3vals=$d();$k[$j++]=Infinity;var _5K=$1.TNvals;for(var _5P=_5K.size,_5O=_5K.keys(),_5N=0;_5N<_5P;_5N++){var _5L=_5O.next().value;$k[$j++]=_5L;$k[$j++]=_5K.get(_5L);$k[$j++]=Infinity;var _5Q=$k[--$j];var _5R=$k[--$j];$k[$j++]=_5Q;$k[$j++]=_5R;var _5S=$a();$k[$j++]=_5S}var _5T=$1.T1vals;for(var _5Y=_5T.size,_5X=_5T.keys(),_5W=0;_5W<_5Y;_5W++){var _5U=_5X.next().value;$k[$j++]=_5U;$k[$j++]=_5T.get(_5U);$k[$j++]=Infinity;var _5Z=$k[--$j];var _5a=$k[--$j];$k[$j++]=_5Z;$k[$j++]=$g($1.TNvals,$1.sft1);$k[$j++]=_5a;var _5e=$a();$k[$j++]=_5e}var _5f=$1.T2vals;for(var _5k=_5f.size,_5j=_5f.keys(),_5i=0;_5i<_5k;_5i++){var _5g=_5j.next().value;$k[$j++]=_5g;$k[$j++]=_5f.get(_5g);$k[$j++]=Infinity;var _5l=$k[--$j];var _5m=$k[--$j];$k[$j++]=_5l;$k[$j++]=$g($1.TNvals,$1.sft2);$k[$j++]=_5m;var _5q=$a();$k[$j++]=_5q}var _5r=$1.T3vals;for(var _5w=_5r.size,_5v=_5r.keys(),_5u=0;_5u<_5w;_5u++){var _5s=_5v.next().value;$k[$j++]=_5s;$k[$j++]=_5r.get(_5s);$k[$j++]=Infinity;var _5x=$k[--$j];var _5y=$k[--$j];$k[$j++]=_5x;$k[$j++]=$g($1.TNvals,$1.sft3);$k[$j++]=_5y;var _62=$a();$k[$j++]=_62}$1.Tvals=$d();for(var _64=128;_64<=255;_64+=1){$1.i=_64;$k[$j++]=$1.Avals;$k[$j++]=$1.i;$k[$j++]=Infinity;$q($g($1.Avals,$1.fnc4));$q($g($1.Avals,$1.i-128));var _6D=$a();var _6E=$k[--$j];$p($k[--$j],_6E,_6D);$k[$j++]=$1.Cvals;$k[$j++]=$1.i;$k[$j++]=Infinity;$q($g($1.Cvals,$1.fnc4));$q($g($1.Cvals,$1.i-128));var _6O=$a();var _6P=$k[--$j];$p($k[--$j],_6P,_6O);$k[$j++]=$1.Tvals;$k[$j++]=$1.i;$k[$j++]=Infinity;$q($g($1.Tvals,$1.fnc4));$q($g($1.Tvals,$1.i-128));var _6Z=$a();var _6a=$k[--$j];$p($k[--$j],_6a,_6Z)}$k[$j++]=Infinity;$k[$j++]=13;$k[$j++]=0;$k[$j++]=42;$k[$j++]=1;$k[$j++]=62;$k[$j++]=2;$k[$j++]=32;$k[$j++]=3;for(var _6c=48;_6c<=57;_6c+=1){$k[$j++]=_6c;$k[$j++]=_6c-44}for(var _6d=65;_6d<=90;_6d+=1){$k[$j++]=_6d;$k[$j++]=_6d-51}$1.Xvals=$d();$k[$j++]=Infinity;var _6f=$1.Xvals;for(var _6k=_6f.size,_6j=_6f.keys(),_6i=0;_6i<_6k;_6i++){var _6g=_6j.next().value;$k[$j++]=_6g;$k[$j++]=_6f.get(_6g);$k[$j++]=Infinity;var _6l=$k[--$j];var _6m=$k[--$j];$k[$j++]=_6l;$k[$j++]=_6m;var _6n=$a();$k[$j++]=_6n}$1.Xvals=$d();$k[$j++]=Infinity;for(var _6p=0;_6p<=255;_6p+=1){$k[$j++]=_6p;$k[$j++]=_6p}$1.Bvals=$d();$k[$j++]=Infinity;var _6r=$1.Bvals;for(var _6w=_6r.size,_6v=_6r.keys(),_6u=0;_6u<_6w;_6u++){var _6s=_6v.next().value;$k[$j++]=_6s;$k[$j++]=_6r.get(_6s);$k[$j++]=Infinity;var _6x=$k[--$j];var _6y=$k[--$j];$k[$j++]=_6x;$k[$j++]=_6y;var _6z=$a();$k[$j++]=_6z}$1.Bvals=$d();$1.encvals=$a([$1.Avals,$1.Cvals,$1.Tvals,$1.Xvals,-1,$1.Bvals]);$k[$j++]=Infinity;for(var _78=0,_79=$1.msglen;_78<_79;_78++){$k[$j++]=0}$k[$j++]=0;$1.numD=$a();$k[$j++]=Infinity;for(var _7C=0,_7D=$1.msglen;_7C<_7D;_7C++){$k[$j++]=0}$k[$j++]=9999;$1.nextXterm=$a();$k[$j++]=Infinity;for(var _7G=0,_7H=$1.msglen;_7G<_7H;_7G++){$k[$j++]=0}$k[$j++]=9999;$1.nextNonX=$a();for(var _7K=$1.msglen-1;_7K>=0;_7K-=1){$1.i=_7K;$1.barchar=$g($1.msg,$1.i);if($1.barchar>=48&&$1.barchar<=57){$p($1.numD,$1.i,$f($g($1.numD,$1.i+1)+1))}if($1.barchar==13||$1.barchar==42||$1.barchar==62){$p($1.nextXterm,$1.i,0)}else{$p($1.nextXterm,$1.i,$f($g($1.nextXterm,$1.i+1)+1))}var _7h=$g($1.Xvals,$1.barchar)!==undefined;if(!_7h){$p($1.nextNonX,$1.i,0)}else{$p($1.nextNonX,$1.i,$f($g($1.nextNonX,$1.i+1)+1))}}$k[$j++]=Infinity;var _7p=$1.nextXterm;for(var _7q=0,_7r=_7p.length;_7q<_7r;_7q++){var _7s=$g(_7p,_7q);$k[$j++]=_7s;if(_7s>1e4){$j--;$k[$j++]=1e4}}$1.nextXterm=$a();$k[$j++]=Infinity;var _7u=$1.nextNonX;for(var _7v=0,_7w=_7u.length;_7v<_7w;_7v++){var _7x=$g(_7u,_7v);$k[$j++]=_7x;if(_7x>1e4){$j--;$k[$j++]=1e4}}$1.nextNonX=$a();$1.isD=function(){$k[$j++]=$1.char>=48&&$1.char<=57};$1.isC=function(){var _83=$g($1.CNvals,$1.char)!==undefined;$k[$j++]=_83};$1.isT=function(){var _86=$g($1.TNvals,$1.char)!==undefined;$k[$j++]=_86};$1.isX=function(){var _89=$g($1.Xvals,$1.char)!==undefined;$k[$j++]=_89};$1.isEA=function(){$k[$j++]=$1.char>127};$1.isFN=function(){$k[$j++]=$1.char<0};$1.XtermFirst=function(){var _8C=$k[--$j];$k[$j++]=$lt($g($1.nextXterm,_8C),$g($1.nextNonX,_8C))};$1.A=0;$1.C=1;$1.T=2;$1.X=3;$1.D=4;$1.B=5;$1.lookup=function(){$1.ac=1;$1.cc=2;$1.tc=2;$1.xc=2;$1.bc=3;if($1.mode==$1.A){$1.ac=0;$1.cc=1;$1.tc=1;$1.xc=1;$1.bc=2}if($1.mode==$1.C){$1.cc=0}if($1.mode==$1.T){$1.tc=0}if($1.mode==$1.X){$1.xc=0}if($1.mode==$1.B){$1.bc=0}for(var _8R=0,_8S=1;_8R<_8S;_8R++){$1.k=0;for(;;){if($1.i+$1.k==$1.msglen){var _8a=$a([$1.ac,$1.cc,$1.tc,$1.xc]);$k[$j++]=true;for(var _8b=0,_8c=_8a.length;_8b<_8c;_8b++){var _8f=$k[--$j];$k[$j++]=_8f&&$1.bc<=Math.ceil($g(_8a,_8b))}if($k[--$j]){$k[$j++]=$1.B;break}var _8m=$a([$1.cc,$1.tc,$1.xc,$1.bc]);$k[$j++]=true;for(var _8n=0,_8o=_8m.length;_8n<_8o;_8n++){var _8r=$k[--$j];$k[$j++]=_8r&&$1.ac<=Math.ceil($g(_8m,_8n))}if($k[--$j]){$k[$j++]=$1.A;break}var _8w=$a([$1.tc,$1.xc]);$k[$j++]=true;for(var _8x=0,_8y=_8w.length;_8x<_8y;_8x++){var _91=$k[--$j];$k[$j++]=_91&&Math.ceil($1.cc)<=Math.ceil($g(_8w,_8x))}if($k[--$j]){$k[$j++]=$1.C;break}var _95=$a([$1.xc]);$k[$j++]=true;for(var _96=0,_97=_95.length;_96<_97;_96++){var _9A=$k[--$j];$k[$j++]=_9A&&Math.ceil($1.tc)<=Math.ceil($g(_95,_96))}if($k[--$j]){$k[$j++]=$1.T;break}$k[$j++]=$1.X;break}$1.char=$g($1.msg,$1.i+$1.k);$k[$j++]="ac";$k[$j++]=$1.ac;$1.isD();if($k[--$j]){var _9K=$k[--$j];$k[$j++]=$f(_9K+1/2)}else{$1.isEA();if($k[--$j]){var _9M=$k[--$j];$k[$j++]=Math.ceil(_9M)+2}else{var _9N=$k[--$j];$k[$j++]=Math.ceil(_9N)+1}}var _9O=$k[--$j];$1[$k[--$j]]=_9O;$k[$j++]="cc";$k[$j++]=$1.cc;$1.isC();if($k[--$j]){var _9S=$k[--$j];$k[$j++]=$f(_9S+.6666667)}else{$1.isEA();if($k[--$j]){var _9U=$k[--$j];$k[$j++]=$f(_9U+2.6666667)}else{var _9V=$k[--$j];$k[$j++]=$f(_9V+1.3333334)}}var _9W=$k[--$j];$1[$k[--$j]]=_9W;$k[$j++]="tc";$k[$j++]=$1.tc;$1.isT();if($k[--$j]){var _9a=$k[--$j];$k[$j++]=$f(_9a+.6666667)}else{$1.isEA();if($k[--$j]){var _9c=$k[--$j];$k[$j++]=$f(_9c+2.6666667)}else{var _9d=$k[--$j];$k[$j++]=$f(_9d+1.3333334)}}var _9e=$k[--$j];$1[$k[--$j]]=_9e;$k[$j++]="xc";$k[$j++]=$1.xc;$1.isX();if($k[--$j]){var _9i=$k[--$j];$k[$j++]=$f(_9i+.6666667)}else{$1.isEA();if($k[--$j]){var _9k=$k[--$j];$k[$j++]=$f(_9k+4.3333334)}else{var _9l=$k[--$j];$k[$j++]=$f(_9l+3.3333334)}}var _9m=$k[--$j];$1[$k[--$j]]=_9m;$k[$j++]="bc";$k[$j++]=$1.bc;$1.isFN();if($k[--$j]){var _9q=$k[--$j];$k[$j++]=$f(_9q+3)}else{var _9r=$k[--$j];$k[$j++]=$f(_9r+1)}var _9s=$k[--$j];$1[$k[--$j]]=_9s;if($1.k>=3){var _9z=$a([$1.ac,$1.cc,$1.tc,$1.xc]);$k[$j++]=true;for(var _A0=0,_A1=_9z.length;_A0<_A1;_A0++){var _A4=$k[--$j];$k[$j++]=_A4&&$1.bc+1<=Math.ceil($g(_9z,_A0))}if($k[--$j]){$k[$j++]=$1.B;break}var _AB=$a([$1.cc,$1.tc,$1.xc,$1.bc]);$k[$j++]=true;for(var _AC=0,_AD=_AB.length;_AC<_AD;_AC++){var _AG=$k[--$j];$k[$j++]=_AG&&$1.ac+1<=Math.ceil($g(_AB,_AC))}if($k[--$j]){$k[$j++]=$1.A;break}var _AN=$a([$1.ac,$1.cc,$1.xc,$1.bc]);$k[$j++]=true;for(var _AO=0,_AP=_AN.length;_AO<_AP;_AO++){var _AS=$k[--$j];$k[$j++]=_AS&&Math.ceil($1.tc)+1<=Math.ceil($g(_AN,_AO))}if($k[--$j]){$k[$j++]=$1.T;break}var _AX=$a([$1.ac,$1.tc]);$k[$j++]=true;for(var _AY=0,_AZ=_AX.length;_AY<_AZ;_AY++){var _Ac=$k[--$j];$k[$j++]=_Ac&&Math.ceil($1.cc)+1<=Math.ceil($g(_AX,_AY))}if($k[--$j]){if(Math.ceil($1.cc)=21){$1.Dbits=$a([1,1,1,1]);$1.mode=$1.D;break}var _BR=$g($1.numD,$1.i);if(_BR>=13&&$f(_BR+$1.i)==$1.msglen){$1.Dbits=$a([1,1,1,1]);$1.mode=$1.D;break}if($g($1.numD,$1.i)>=2){var _BZ=$s(2);$p(_BZ,0,$g($1.msg,$1.i));$p(_BZ,1,$g($1.msg,$1.i+1));$k[$j++]=$g($1.Avals,_BZ);$1.addtocws();$1.i=$1.i+2;break}if($g($1.msg,$1.i)==$1.fnc1){if($g($1.numD,$1.i+1)>=15){$k[$j++]=$g($1.Avals,$1.fnc1lD);$1.addtocws();$1.i=$1.i+1;$1.Dbits=$a([]);$1.mode=$1.D;break}var _By=$g($1.numD,$1.i+1);if(_By>=7&&$f($f(_By+$1.i)+1)==$1.msglen){$k[$j++]=$g($1.Avals,$1.fnc1lD);$1.addtocws();$1.i=$1.i+1;$1.Dbits=$a([]);$1.mode=$1.D;break}}$k[$j++]="newmode";$1.lookup();var _C7=$k[--$j];$1[$k[--$j]]=_C7;if($1.newmode!=$1.mode){$k[$j++]=$g($1.Avals,$g($a([-1,$1.lC,$1.lT,$1.lX,$1.lD,$1.lB]),$1.newmode));$1.addtocws();$1.mode=$1.newmode;break}$k[$j++]=$g($1.Avals,$g($1.msg,$1.i));$1.addtocws();$1.i=$1.i+1;break}};$1.CTXvalstocws=function(){$1.in=$k[--$j];$k[$j++]=Infinity;for(var _CV=0,_CU=$1.in.length-1;_CV<=_CU;_CV+=3){var _CX=$G($1.in,_CV,3);$k[$j++]=0;for(var _CY=0,_CZ=_CX.length;_CY<_CZ;_CY++){var _Cb=$k[--$j];$k[$j++]=$f(_Cb+$g(_CX,_CY))*40}var _Cd=~~($k[--$j]/40)+1;$k[$j++]=~~(_Cd/256);$k[$j++]=_Cd%256}$r($a($m()));var _Cg=$k[--$j];var _Ch=$k[--$j];$k[$j++]=_Cg;$k[$j++]=_Ch;$j--};$1.encCTX=function(){$1.p=0;$1.ctxvals=$a(2220);for(;;){if($1.i==$1.msglen){break}if($1.p%3==0){if($g($1.numD,$1.i)>=12){$k[$j++]=$G($1.ctxvals,0,$1.p);$1.CTXvalstocws();$1.addtocws();$k[$j++]=$a([$1.unlcw]);$1.addtocws();$1.mode=$1.A;break}var _Cx=$g($1.numD,$1.i);if(_Cx>=8&&$f(_Cx+$1.i)==$1.msglen){$k[$j++]=$G($1.ctxvals,0,$1.p);$1.CTXvalstocws();$1.addtocws();$k[$j++]=$a([$1.unlcw]);$1.addtocws();$1.mode=$1.A;break}if($1.mode==$1.X){var _DC=$g($1.Xvals,$g($1.msg,$1.i))!==undefined;if(!_DC){$k[$j++]=$G($1.ctxvals,0,$1.p);$1.CTXvalstocws();$1.addtocws();if($g($1.numremcws,$1.j)!=1||$g($1.msg,$1.i)>127){$k[$j++]=$a([$1.unlcw]);$1.addtocws()}$1.mode=$1.A;break}if($1.i+1<$1.msglen){var _DV=$g($1.Xvals,$g($1.msg,$1.i+1))!==undefined;if(!_DV){break}if($1.i+2<$1.msglen){var _Dc=$g($1.Xvals,$g($1.msg,$1.i+2))!==undefined;if(!_Dc){break}}}}else{$1.lookup();if($k[--$j]!=$1.mode){$k[$j++]=$G($1.ctxvals,0,$1.p);$1.CTXvalstocws();$1.addtocws();$k[$j++]=$a([$1.unlcw]);$1.addtocws();$1.mode=$1.A;break}}if($1.msglen-$1.i<=3){$1.remcws=$g($1.numremcws,$1.j+~~($1.p/3)*2);$k[$j++]=Infinity;var _Dv=$G($1.msg,$1.i,$1.msglen-$1.i);for(var _Dw=0,_Dx=_Dv.length;_Dw<_Dx;_Dw++){var _Dy=$g(_Dv,_Dw);var _E2=$g($g($1.encvals,$1.mode),_Dy)!==undefined;$k[$j++]=_Dy;if(_E2){$q($g($g($1.encvals,$1.mode),$k[--$j]))}else{$j--;$k[$j++]=-1;$k[$j++]=-1;$k[$j++]=-1;$k[$j++]=-1}}$1.remvals=$a();if($1.remcws==2&&$1.remvals.length==3){$k[$j++]=Infinity;$q($G($1.ctxvals,0,$1.p));$q($1.remvals);var _EF=$a();$k[$j++]=_EF;$1.CTXvalstocws();$1.addtocws();$1.mode=$1.A;$1.i=$1.msglen;break}if($1.remcws==2&&$1.remvals.length==2&&$1.mode!=$1.X){$k[$j++]=Infinity;$q($G($1.ctxvals,0,$1.p));$q($1.remvals);$q($g($g($1.encvals,$1.mode),$1.sft1));var _EV=$a();$k[$j++]=_EV;$1.CTXvalstocws();$1.addtocws();$1.mode=$1.A;$1.i=$1.msglen;break}if($1.remcws==2&&$1.remvals.length==1){$k[$j++]=$G($1.ctxvals,0,$1.p);$1.CTXvalstocws();$1.addtocws();$k[$j++]=$a([$1.unlcw]);$1.addtocws();$k[$j++]=$g($1.Avals,$g($1.msg,$1.i));$1.addtocws();$1.mode=$1.A;$1.i=$1.msglen;break}if($1.remcws==1&&$1.remvals.length==1){$k[$j++]=$G($1.ctxvals,0,$1.p);$1.CTXvalstocws();$1.addtocws();$k[$j++]=$g($1.Avals,$g($1.msg,$1.i));$1.addtocws();$1.mode=$1.A;$1.i=$1.msglen;break}}}var _F4=$g($g($1.encvals,$1.mode),$g($1.msg,$1.i));$P($1.ctxvals,$1.p,_F4);$1.p=_F4.length+$1.p;$1.i=$1.i+1}if($1.mode!=$1.A){for(;;){if($1.p%3==0){break}$1.i=$1.i-1;$1.p=$1.p-$g($g($1.encvals,$1.mode),$g($1.msg,$1.i)).length}$k[$j++]=Infinity;$q($G($1.ctxvals,0,$1.p));var _FO=$a();$k[$j++]=_FO;$1.CTXvalstocws();$1.addtocws();$k[$j++]=$a([$1.unlcw]);$1.addtocws();$1.mode=$1.A;if($1.i!=$1.msglen){if($g($1.numD,$1.i)>=2){var _FX=$s(2);$p(_FX,0,$g($1.msg,$1.i));$p(_FX,1,$g($1.msg,$1.i+1));$k[$j++]=$g($1.Avals,_FX);$1.addtocws();$1.i=$1.i+2}else{$k[$j++]=$g($1.Avals,$g($1.msg,$1.i));$1.addtocws();$1.i=$1.i+1}}}};$1.encD=function(){for(;;){if($g($1.numD,$1.i)<3){$1.Drem=(8-$1.Dbits.length%8)%8;$1.remcws=$g($1.numremcws,$1.j+~~($1.Dbits.length/8));if(($f($g($1.numremcws,$1.j+~~($1.Dbits.length/8)-1)-1)==0&&$1.Drem==0||$1.remcws==1&&$1.Drem!=0)&&$1.i==$1.msglen){if($1.Drem==4||$1.Drem==6){$k[$j++]=Infinity;$q($1.Dbits);$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$1.Dbits=$a()}if($1.Drem==2||$1.Drem==6){$k[$j++]=Infinity;$q($1.Dbits);$k[$j++]=0;$k[$j++]=1;$1.Dbits=$a()}break}if(($1.i==$1.msglen-1&&$g($1.numD,$1.i)==1||$1.i==$1.msglen-2&&$g($1.numD,$1.i)==2)&&$1.remcws==1&&$1.Drem==0){break}if(!($1.i==$1.msglen-1&&$g($1.numD,$1.i)==1&&$1.remcws==1&&($1.Drem==4||$1.Drem==6))){$k[$j++]=Infinity;$q($1.Dbits);$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$1.Dbits=$a();$1.Drem=(8-$1.Dbits.length%8)%8}if($1.Drem==4||$1.Drem==6){if($g($1.numD,$1.i)>=1){$k[$j++]=Infinity;$q($1.Dbits);$k[$j++]=$f($f($g($1.msg,$1.i)-48)+1);$k[$j++]=4;$1.tobin();$q($k[--$j]);$1.Dbits=$a();$1.i=$1.i+1}else{$k[$j++]=Infinity;$q($1.Dbits);$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$1.Dbits=$a()}$1.Drem=$1.Drem-4}if($1.Drem==2){$k[$j++]=Infinity;$q($1.Dbits);$k[$j++]=0;$k[$j++]=1;$1.Dbits=$a();$1.Drem=0}break}$k[$j++]=Infinity;$q($1.Dbits);var _Gu=$G($1.msg,$1.i,3);$k[$j++]=0;for(var _Gv=0,_Gw=_Gu.length;_Gv<_Gw;_Gv++){var _Gy=$k[--$j];$k[$j++]=$f(_Gy+$f($g(_Gu,_Gv)-48))*10}var _Gz=$k[--$j];$k[$j++]=~~(_Gz/10)+1;$k[$j++]=10;$1.tobin();$q($k[--$j]);$1.Dbits=$a();$1.i=$1.i+3}$k[$j++]=Infinity;for(var _H5=0,_H4=$1.Dbits.length-1;_H5<=_H4;_H5+=8){var _H7=$G($1.Dbits,_H5,8);$k[$j++]=0;for(var _H8=0,_H9=_H7.length;_H8<_H9;_H8++){var _HB=$k[--$j];$k[$j++]=$f(_HB+$g(_H7,_H8))*2}var _HC=$k[--$j];$k[$j++]=~~(_HC/2)}$r($a($m()));var _HF=$k[--$j];var _HG=$k[--$j];$k[$j++]=_HF;$k[$j++]=_HG;$j--;$1.addtocws();$1.mode=$1.A};$1.encB=function(){$1.p=0;$1.bvals=$a(1480);for(;;){if($1.i==$1.msglen){break}if($g($1.msg,$1.i)<0){break}$1.lookup();if($k[--$j]!=$1.mode){break}$p($1.bvals,$1.p,$g($1.msg,$1.i));$1.p=$1.p+1;$1.i=$1.i+1}$1.remcws=$f($g($1.numremcws,$1.j+$1.p)-1);$k[$j++]=Infinity;if($1.remcws==0&&$1.i==$1.msglen){$k[$j++]=0}else{if($1.p<250){$k[$j++]=$1.p}else{$k[$j++]=~~($1.p/250)+249;$k[$j++]=$1.p%250}}$q($G($1.bvals,0,$1.p));$1.bvals=$a();$k[$j++]=$1.bvals;$1.addtocws();$1.mode=$1.A};$1.cws=$a(1480);$1.mode=$1.A;$1.i=0;$1.j=0;for(;;){if($1.i>=$1.msglen){break}if($1[$g($a(["encA","encCTX","encCTX","encCTX","encD","encB"]),$1.mode)]()===true){break}}$1.cws=$G($1.cws,0,$1.j)}$1.i=0;for(;;){$1.m=$g($1.metrics,$1.i);$1.vers=$g($1.m,0);$1.rows=$g($1.m,1);$1.cols=$g($1.m,2);$1.dcol=$g($1.m,3);$1.dcws=$g($1.m,4);$1.rscw=$g($1.m,5);$1.rsbl=$g($1.m,6);$1.riso=$g($1.m,7);$1.risi=$g($1.m,8);$1.risl=$g($1.m,9);$1.dcpb=~~($1.dcws/$1.rsbl);$1.ecpb=~~($1.rscw/$1.rsbl);$1.okay=true;if($ne($1.version,"unset")&&$ne($1.version,$1.vers)){$1.okay=false}if($1.cws.length>$1.dcws){$1.okay=false}if($1.okay){break}$1.i=$1.i+1}if(!$1.stype){$k[$j++]=Infinity;$q($1.cws);for(var _Ib=0,_Ic=$f($1.dcws-$1.cws.length);_Ib<_Ic;_Ib++){$k[$j++]=129}$1.cws=$a()}else{$k[$j++]=Infinity;for(var _Ig=0,_Ih=$f($1.dcws-$1.cws.length);_Ig<_Ih;_Ig++){$k[$j++]=0}$q($1.cws);$1.cws=$a()}var _Il=$g($1.options,"debugcws")!==undefined;if(_Il){$k[$j++]="bwipp.debugcws";$k[$j++]=$1.cws;bwipp_raiseerror()}$1.cwbs=$a($1.rsbl);$1.ecbs=$a($1.rsbl);for(var _It=0,_Is=$f($1.rsbl-1);_It<=_Is;_It+=1){$1.i=_It;$1.cwb=$a($1.dcpb);for(var _Iy=0,_Ix=$1.dcpb-1;_Iy<=_Ix;_Iy+=1){$1.j=_Iy;$p($1.cwb,$1.j,$g($1.cws,$f($1.j*$1.rsbl+$1.i)))}$p($1.cwbs,$1.i,$1.cwb)}var _JA=$1.stype?32:256;$1.gf=_JA;$1["gf-1"]=$1.gf-1;var _JD=$1.stype?37:301;$1.pm=_JD;$k[$j++]=Infinity;$k[$j++]=1;for(var _JF=0,_JG=$1["gf-1"];_JF<_JG;_JF++){var _JH=$k[--$j];var _JI=_JH*2;$k[$j++]=_JH;$k[$j++]=_JI;if(_JI>=$1.gf){var _JL=$k[--$j];$k[$j++]=_JL^$1.pm}}$1.rsalog=$a();$1.rslog=$a($1.gf);for(var _JR=1,_JQ=$1["gf-1"];_JR<=_JQ;_JR+=1){$p($1.rslog,$g($1.rsalog,_JR),_JR)}$1.rsprod=function(){var _JV=$k[--$j];var _JW=$k[--$j];$k[$j++]=_JW;$k[$j++]=_JV;if(_JV!=0&&_JW!=0){var _JZ=$g($1.rslog,$k[--$j]);var _Jf=$g($1.rsalog,$f(_JZ+$g($1.rslog,$k[--$j]))%$1["gf-1"]);$k[$j++]=_Jf}else{$j-=2;$k[$j++]=0}};$k[$j++]=Infinity;$k[$j++]=1;for(var _Jh=0,_Ji=$1.ecpb;_Jh<_Ji;_Jh++){$k[$j++]=0}$1.coeffs=$a();for(var _Jm=0,_Jl=$1.ecpb-1;_Jm<=_Jl;_Jm+=1){$1.i=_Jm;$p($1.coeffs,$1.i+1,$g($1.coeffs,$1.i));for(var _Jt=$1.i;_Jt>=1;_Jt-=1){$1.j=_Jt;$k[$j++]=$1.coeffs;$k[$j++]=$1.j;$k[$j++]=$g($1.coeffs,$1.j-1);$k[$j++]=$g($1.coeffs,$1.j);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _K5=$k[--$j];var _K6=$k[--$j];var _K7=$k[--$j];$p($k[--$j],_K7,$xo(_K6,_K5))}$k[$j++]=$1.coeffs;$k[$j++]=0;$k[$j++]=$g($1.coeffs,0);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _KF=$k[--$j];var _KG=$k[--$j];$p($k[--$j],_KG,_KF)}$1.coeffs=$G($1.coeffs,0,$1.coeffs.length-1);for(var _KN=0,_KM=$1.cwbs.length-1;_KN<=_KM;_KN+=1){$1.i=_KN;$k[$j++]=Infinity;$q($g($1.cwbs,$1.i));for(var _KS=0,_KT=$1.ecpb;_KS<_KT;_KS++){$k[$j++]=0}$1.rscws=$a();for(var _KX=0,_KW=$1.dcpb-1;_KX<=_KW;_KX+=1){$1.m=_KX;$1.k=$g($1.rscws,$1.m);for(var _Kd=0,_Kc=$1.ecpb-1;_Kd<=_Kc;_Kd+=1){$1.j=_Kd;$k[$j++]=$1.rscws;$k[$j++]=$1.m+$1.j+1;$k[$j++]=$g($1.coeffs,$1.ecpb-$1.j-1);$k[$j++]=$1.k;$1.rsprod();var _Kq=$k[--$j];var _Kr=$k[--$j];$p($k[--$j],_Kr,$xo(_Kq,$g($1.rscws,$1.m+$1.j+1)))}}$p($1.ecbs,$1.i,$G($1.rscws,$1.dcpb,$1.ecpb))}$k[$j++]=Infinity;$q($1.cws);for(var _L1=0,_L2=$1.rscw;_L1<_L2;_L1++){$k[$j++]=0}$1.cws=$a();for(var _L6=0,_L5=$f($1.rscw-1);_L6<=_L5;_L6+=1){$1.i=_L6;$p($1.cws,$f($1.dcws+$1.i),$g($g($1.ecbs,$1.i%$1.rsbl),~~($1.i/$1.rsbl)))}var _LK=$1.stype?5:8;$1.mmat=$a($f($1.dcws+$1.rscw)*_LK);$1.r=0;$1.c=0;var _LN=$1.stype?2:1;for(var _LQ=0,_LR=_LN,_LP=$1.cws.length-1;_LR<0?_LQ>=_LP:_LQ<=_LP;_LQ+=_LR){$1.i=_LQ;if(!$1.stype){var _LU=$Z($s(8),"00000000");var _LZ=$R($s(8),$g($1.cws,$1.i),2);$P(_LU,8-_LZ.length,_LZ);$k[$j++]=_LU;$k[$j++]=Infinity;var _La=$k[--$j];var _Lb=$k[--$j];$k[$j++]=_La;$F(_Lb,function(){var _Lc=$k[--$j];$k[$j++]=$f(_Lc-48)});var _Ld=$a();$1.top=$G(_Ld,0,4);$1.bot=$G(_Ld,4,4)}else{var _Lh=$Z($s(5),"00000");var _Lm=$R($s(5),$g($1.cws,$1.i),2);$P(_Lh,5-_Lm.length,_Lm);$k[$j++]=_Lh;$k[$j++]=Infinity;var _Ln=$k[--$j];var _Lo=$k[--$j];$k[$j++]=_Ln;$F(_Lo,function(){var _Lp=$k[--$j];$k[$j++]=$f(_Lp-48)});$1.c1=$a();var _Ls=$Z($s(5),"00000");var _Lx=$R($s(5),$g($1.cws,$1.i+1),2);$P(_Ls,5-_Lx.length,_Lx);$k[$j++]=_Ls;$k[$j++]=Infinity;var _Ly=$k[--$j];var _Lz=$k[--$j];$k[$j++]=_Ly;$F(_Lz,function(){var _M0=$k[--$j];$k[$j++]=$f(_M0-48)});$1.c2=$a();$k[$j++]=Infinity;$q($G($1.c1,0,3));$q($G($1.c2,0,2));$1.top=$a();$k[$j++]=Infinity;$q($G($1.c1,3,2));$q($G($1.c2,2,3));$1.bot=$a()}$P($1.mmat,$f($1.r*$1.dcol+$1.c),$1.top);$P($1.mmat,$f(($1.r+1)*$1.dcol+$1.c),$1.bot);$1.c=$1.c+$1.top.length;if($1.c==$1.dcol){$1.c=0;$1.r=$1.r+2}}$1.mmv=function(){var _MS=$k[--$j];var _MT=$k[--$j];$k[$j++]=$f(_MT+_MS*$1.cols)};$k[$j++]=Infinity;for(var _MW=0,_MX=$1.rows*$1.cols;_MW<_MX;_MW++){$k[$j++]=-1}$1.pixs=$a();var _N3=$a([function(){for(var _Ma=0,_Mb=$1.cols;_Ma<_Mb;_Ma++){$k[$j++]=0}},function(){for(var _Md=0,_Me=$1.cols;_Md<_Me;_Md++){$k[$j++]=1}},function(){$k[$j++]=0;for(var _Mg=0,_Mh=$f($1.cols-2);_Mg<_Mh;_Mg++){$k[$j++]=1}$k[$j++]=0},function(){$k[$j++]=0;$k[$j++]=1;for(var _Mj=0,_Mk=$f($1.cols-4);_Mj<_Mk;_Mj++){$k[$j++]=0}$k[$j++]=1;$k[$j++]=0},function(){for(var _Mm=0,_Mn=~~($f($1.cols-1)/2);_Mm<_Mn;_Mm++){$k[$j++]=-1}$k[$j++]=1;for(var _Mp=0,_Mq=~~($f($1.cols-1)/2);_Mp<_Mq;_Mp++){$k[$j++]=-1}},function(){for(var _Ms=0,_Mt=~~($f($1.cols-1)/2);_Ms<_Mt;_Ms++){$k[$j++]=-1}$k[$j++]=0;for(var _Mv=0,_Mw=~~($f($1.cols-1)/2);_Mv<_Mw;_Mv++){$k[$j++]=-1}},function(){$k[$j++]=1;for(var _My=0,_Mz=$f($1.cols-2);_My<_Mz;_My++){$k[$j++]=0}$k[$j++]=1},function(){$k[$j++]=1;$k[$j++]=0;for(var _N1=0,_N2=$f($1.cols-4);_N1<_N2;_N1++){$k[$j++]=1}$k[$j++]=0;$k[$j++]=1}]);$1.artifact=_N3;var _N4=new Map([["A","121343"],["B","12134343"],["C","12121343"],["D","1213434343"],["E","1212134343"],["F","1212121343"],["G","121213434343"],["H","121212134343"],["S","56661278"],["T","5666666666127878"]]);$1.cpat=$g(_N4,$G($1.vers,0,1));$k[$j++]=$1.pixs;$k[$j++]=0;$k[$j++]=~~($f($1.rows-$1.cpat.length)/2);$1.mmv();$k[$j++]=Infinity;$F($1.cpat,function(){if($g($1.artifact,$f($k[--$j]-49))()===true){return true}});var _NF=$a();var _NG=$k[--$j];$P($k[--$j],_NG,_NF);for(var _NK=0,_NJ=$f($1.risl-1);_NK<=_NJ;_NK+=1){$1.i=_NK;for(var _NP=$1.riso,_NQ=$1.risi,_NO=$f($1.cols-1);_NQ<0?_NP>=_NO:_NP<=_NO;_NP+=_NQ){$1.j=_NP;var _NS=$1.i%12==0?1:0;var _NT=$a([1,_NS]);$k[$j++]=_NT;$k[$j++]=_NT;$k[$j++]=$1.pixs;$k[$j++]=$1.j;$k[$j++]=$1.i;$1.mmv();var _NX=$k[--$j];var _NY=$k[--$j];$P(_NY,_NX,$k[--$j]);if($1.i!=$f($1.risl-1)){$k[$j++]=$1.pixs;$k[$j++]=$f($f($1.cols-$1.j)-2);$k[$j++]=$f($f($1.rows-$1.i)-1);$1.mmv();var _Nh=$k[--$j];var _Ni=$k[--$j];$P(_Ni,_Nh,$k[--$j])}else{$j--}}}var _ON=new Map([["A",$a([$a([12,5])])],["B",$a([$a([16,7])])],["C",$a([$a([26,12])])],["D",$a([])],["E",$a([$a([26,23])])],["F",$a([$a([26,32]),$a([70,32]),$a([26,34]),$a([70,34])])],["G",$a([$a([27,48]),$a([69,48])])],["H",$a([$a([26,70]),$a([66,70]),$a([106,70]),$a([26,72]),$a([66,72]),$a([106,72])])],["S-10",$a([])],["S-20",$a([$a([10,4])])],["S-30",$a([$a([15,4]),$a([15,6])])],["T-16",$a([$a([8,10])])],["T-32",$a([$a([16,10]),$a([16,12])])],["T-48",$a([$a([24,10]),$a([24,12]),$a([24,14])])]]);$F($g(_ON,$1.vers),function(){var _OR=$k[--$j];$k[$j++]=$1.pixs;$q(_OR);$1.mmv();var _OS=$k[--$j];$p($k[--$j],_OS,1)});$1.j=0;for(var _OW=0,_OV=$1.pixs.length-1;_OW<=_OV;_OW+=1){$1.i=_OW;if($g($1.pixs,$1.i)==-1){$p($1.pixs,$1.i,$g($1.mmat,$1.j));$1.j=$1.j+1}}var _Om=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",$1.cols],["pixy",$1.rows],["height",$1.rows/72*2],["width",$1.cols/72*2],["opt",$1.options]]);$k[$j++]=_Om;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_hanxin(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.version="unset";$1.eclevel="unset";$1.parse=false;$1.parsefnc=false;$1.mask=-1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.mask=~~$1.mask;var _8=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc]]);$1.fncvals=_8;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _B=$k[--$j];$1[$k[--$j]]=_B;$1.msglen=$1.msg.length;if($eq($1.eclevel,"unset")){$1.eclevel="L2"}$1.tobin=function(){var _G=$s($k[--$j]);$k[$j++]=_G;for(var _I=0,_H=_G.length-1;_I<=_H;_I+=1){var _J=$k[--$j];$p(_J,_I,48);$k[$j++]=_J}var _K=$k[--$j];var _N=$R($s(_K.length),$k[--$j],2);$P(_K,_K.length-_N.length,_N);$k[$j++]=_K};$1.bits=$s(4+13+$1.msglen*8);$P($1.bits,0,"0011");$k[$j++]=$1.bits;$k[$j++]=4;$k[$j++]=$1.msglen;$k[$j++]=13;$1.tobin();var _T=$k[--$j];var _U=$k[--$j];$P($k[--$j],_U,_T);for(var _Y=0,_X=$1.msglen-1;_Y<=_X;_Y+=1){$1.i=_Y;$k[$j++]=$1.bits;$k[$j++]=17+$1.i*8;$k[$j++]=$g($1.msg,$1.i);$k[$j++]=8;$1.tobin();var _e=$k[--$j];var _f=$k[--$j];$P($k[--$j],_f,_e)}$1.metrics=$a([$a(["1",23,-1,0,205,$a([1,21,4]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,17,8]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,13,12]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,9,16]),$a([0,-1,-1]),$a([0,-1,-1])]),$a(["2",25,-1,0,301,$a([1,31,6]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,25,12]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,19,18]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,15,22]),$a([0,-1,-1]),$a([0,-1,-1])]),$a(["3",27,-1,0,405,$a([1,42,8]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,34,16]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,26,24]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,20,30]),$a([0,-1,-1]),$a([0,-1,-1])]),$a(["4",29,14,1,439,$a([1,46,8]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,38,16]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,30,24]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,22,32]),$a([0,-1,-1]),$a([0,-1,-1])]),$a(["5",31,16,1,555,$a([1,57,12]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,49,20]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,37,32]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,14,20]),$a([1,13,22]),$a([0,-1,-1])]),$a(["6",33,16,1,675,$a([1,70,14]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,58,26]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,24,20]),$a([1,22,18]),$a([0,-1,-1]),$a([1,16,24]),$a([1,18,26]),$a([0,-1,-1])]),$a(["7",35,17,1,805,$a([1,84,16]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,70,30]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,26,22]),$a([1,28,24]),$a([0,-1,-1]),$a([2,14,20]),$a([1,12,20]),$a([0,-1,-1])]),$a(["8",37,18,1,943,$a([1,99,18]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,40,18]),$a([1,41,18]),$a([0,-1,-1]),$a([1,31,26]),$a([1,32,28]),$a([0,-1,-1]),$a([2,16,24]),$a([1,15,22]),$a([0,-1,-1])]),$a(["9",39,19,1,1089,$a([1,114,22]),$a([0,-1,-1]),$a([0,-1,-1]),$a([2,48,20]),$a([0,-1,-1]),$a([0,-1,-1]),$a([2,24,20]),$a([1,26,22]),$a([0,-1,-1]),$a([2,18,28]),$a([1,18,26]),$a([0,-1,-1])]),$a(["10",41,20,1,1243,$a([1,131,24]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,52,22]),$a([1,57,24]),$a([0,-1,-1]),$a([2,27,24]),$a([1,29,24]),$a([0,-1,-1]),$a([2,21,32]),$a([1,19,30]),$a([0,-1,-1])]),$a(["11",43,14,2,1289,$a([1,135,26]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,56,24]),$a([1,57,24]),$a([0,-1,-1]),$a([2,28,24]),$a([1,31,26]),$a([0,-1,-1]),$a([2,22,32]),$a([1,21,32]),$a([0,-1,-1])]),$a(["12",45,15,2,1455,$a([1,153,28]),$a([0,-1,-1]),$a([0,-1,-1]),$a([1,62,26]),$a([1,65,28]),$a([0,-1,-1]),$a([2,32,28]),$a([1,33,28]),$a([0,-1,-1]),$a([3,17,26]),$a([1,22,30]),$a([0,-1,-1])]),$a(["13",47,16,2,1629,$a([1,86,16]),$a([1,85,16]),$a([0,-1,-1]),$a([1,71,30]),$a([1,72,30]),$a([0,-1,-1]),$a([2,37,32]),$a([1,35,30]),$a([0,-1,-1]),$a([3,20,30]),$a([1,21,32]),$a([0,-1,-1])]),$a(["14",49,16,2,1805,$a([1,94,18]),$a([1,95,18]),$a([0,-1,-1]),$a([2,51,22]),$a([1,55,24]),$a([0,-1,-1]),$a([3,30,26]),$a([1,31,26]),$a([0,-1,-1]),$a([4,18,28]),$a([1,17,24]),$a([0,-1,-1])]),$a(["15",51,17,2,1995,$a([1,104,20]),$a([1,105,20]),$a([0,-1,-1]),$a([2,57,24]),$a([1,61,26]),$a([0,-1,-1]),$a([3,33,28]),$a([1,36,30]),$a([0,-1,-1]),$a([4,20,30]),$a([1,19,30]),$a([0,-1,-1])]),$a(["16",53,17,2,2187,$a([1,115,22]),$a([1,114,22]),$a([0,-1,-1]),$a([2,65,28]),$a([1,61,26]),$a([0,-1,-1]),$a([3,38,32]),$a([1,33,30]),$a([0,-1,-1]),$a([5,19,28]),$a([1,14,24]),$a([0,-1,-1])]),$a(["17",55,18,2,2393,$a([1,126,24]),$a([1,125,24]),$a([0,-1,-1]),$a([2,70,30]),$a([1,69,30]),$a([0,-1,-1]),$a([4,33,28]),$a([1,29,26]),$a([0,-1,-1]),$a([5,20,30]),$a([1,19,30]),$a([0,-1,-1])]),$a(["18",57,19,2,2607,$a([1,136,26]),$a([1,137,26]),$a([0,-1,-1]),$a([3,56,24]),$a([1,59,26]),$a([0,-1,-1]),$a([5,35,30]),$a([0,-1,-1]),$a([0,-1,-1]),$a([6,18,28]),$a([1,21,28]),$a([0,-1,-1])]),$a(["19",59,20,2,2829,$a([1,148,28]),$a([1,149,28]),$a([0,-1,-1]),$a([3,61,26]),$a([1,64,28]),$a([0,-1,-1]),$a([7,24,20]),$a([1,23,22]),$a([0,-1,-1]),$a([6,20,30]),$a([1,21,32]),$a([0,-1,-1])]),$a(["20",61,20,2,3053,$a([3,107,20]),$a([0,-1,-1]),$a([0,-1,-1]),$a([3,65,28]),$a([1,72,30]),$a([0,-1,-1]),$a([7,26,22]),$a([1,23,22]),$a([0,-1,-1]),$a([7,19,28]),$a([1,20,32]),$a([0,-1,-1])]),$a(["21",63,21,2,3291,$a([3,115,22]),$a([0,-1,-1]),$a([0,-1,-1]),$a([4,56,24]),$a([1,63,28]),$a([0,-1,-1]),$a([7,28,24]),$a([1,25,22]),$a([0,-1,-1]),$a([8,18,28]),$a([1,21,22]),$a([0,-1,-1])]),$a(["22",65,16,3,3383,$a([2,116,22]),$a([1,122,24]),$a([0,-1,-1]),$a([4,56,24]),$a([1,72,30]),$a([0,-1,-1]),$a([7,28,24]),$a([1,32,26]),$a([0,-1,-1]),$a([8,18,28]),$a([1,24,30]),$a([0,-1,-1])]),$a(["23",67,17,3,3631,$a([3,127,24]),$a([0,-1,-1]),$a([0,-1,-1]),$a([5,51,22]),$a([1,62,26]),$a([0,-1,-1]),$a([7,30,26]),$a([1,35,26]),$a([0,-1,-1]),$a([8,20,30]),$a([1,21,32]),$a([0,-1,-1])]),$a(["24",69,17,3,3887,$a([2,135,26]),$a([1,137,26]),$a([0,-1,-1]),$a([5,56,24]),$a([1,59,26]),$a([0,-1,-1]),$a([7,33,28]),$a([1,30,28]),$a([0,-1,-1]),$a([11,16,24]),$a([1,19,26]),$a([0,-1,-1])]),$a(["25",71,18,3,4151,$a([3,105,20]),$a([1,121,22]),$a([0,-1,-1]),$a([5,61,26]),$a([1,57,26]),$a([0,-1,-1]),$a([9,28,24]),$a([1,28,22]),$a([0,-1,-1]),$a([10,19,28]),$a([1,18,30]),$a([0,-1,-1])]),$a(["26",73,18,3,4423,$a([2,157,30]),$a([1,150,28]),$a([0,-1,-1]),$a([5,65,28]),$a([1,61,26]),$a([0,-1,-1]),$a([8,33,28]),$a([1,34,30]),$a([0,-1,-1]),$a([10,19,28]),$a([2,15,26]),$a([0,-1,-1])]),$a(["27",75,19,3,4703,$a([3,126,24]),$a([1,115,22]),$a([0,-1,-1]),$a([7,51,22]),$a([1,54,22]),$a([0,-1,-1]),$a([8,35,30]),$a([1,37,30]),$a([0,-1,-1]),$a([15,15,22]),$a([1,10,22]),$a([0,-1,-1])]),$a(["28",77,19,3,4991,$a([4,105,20]),$a([1,103,20]),$a([0,-1,-1]),$a([7,56,24]),$a([1,45,18]),$a([0,-1,-1]),$a([10,31,26]),$a([1,27,26]),$a([0,-1,-1]),$a([10,17,26]),$a([3,20,28]),$a([1,21,28])]),$a(["29",79,20,3,5287,$a([3,139,26]),$a([1,137,28]),$a([0,-1,-1]),$a([6,66,28]),$a([1,66,30]),$a([0,-1,-1]),$a([9,36,30]),$a([1,34,32]),$a([0,-1,-1]),$a([13,19,28]),$a([1,17,32]),$a([0,-1,-1])]),$a(["30",81,20,3,5591,$a([6,84,16]),$a([1,82,16]),$a([0,-1,-1]),$a([6,70,30]),$a([1,68,30]),$a([0,-1,-1]),$a([7,35,30]),$a([3,33,28]),$a([1,32,28]),$a([13,20,30]),$a([1,20,28]),$a([0,-1,-1])]),$a(["31",83,21,3,5903,$a([5,105,20]),$a([1,94,18]),$a([0,-1,-1]),$a([6,74,32]),$a([1,71,30]),$a([0,-1,-1]),$a([11,33,28]),$a([1,34,32]),$a([0,-1,-1]),$a([13,19,28]),$a([3,16,26]),$a([0,-1,-1])]),$a(["32",85,17,4,6033,$a([4,127,24]),$a([1,126,24]),$a([0,-1,-1]),$a([7,66,28]),$a([1,66,30]),$a([0,-1,-1]),$a([12,30,24]),$a([1,24,28]),$a([1,24,30]),$a([15,19,28]),$a([1,17,32]),$a([0,-1,-1])]),$a(["33",87,17,4,6353,$a([7,84,16]),$a([1,78,16]),$a([0,-1,-1]),$a([7,70,30]),$a([1,66,28]),$a([0,-1,-1]),$a([12,33,28]),$a([1,32,30]),$a([0,-1,-1]),$a([14,21,32]),$a([1,24,28]),$a([0,-1,-1])]),$a(["34",89,18,4,6689,$a([5,117,22]),$a([1,117,24]),$a([0,-1,-1]),$a([8,66,28]),$a([1,58,26]),$a([0,-1,-1]),$a([11,38,32]),$a([1,34,32]),$a([0,-1,-1]),$a([15,20,30]),$a([2,17,26]),$a([0,-1,-1])]),$a(["35",91,18,4,7025,$a([4,148,28]),$a([1,146,28]),$a([0,-1,-1]),$a([8,68,30]),$a([1,70,24]),$a([0,-1,-1]),$a([10,36,32]),$a([3,38,28]),$a([0,-1,-1]),$a([16,19,28]),$a([3,16,26]),$a([0,-1,-1])]),$a(["36",93,19,4,7377,$a([4,126,24]),$a([2,135,26]),$a([0,-1,-1]),$a([8,70,28]),$a([2,43,26]),$a([0,-1,-1]),$a([13,32,28]),$a([2,41,30]),$a([0,-1,-1]),$a([17,19,28]),$a([3,15,26]),$a([0,-1,-1])]),$a(["37",95,19,4,7729,$a([5,136,26]),$a([1,132,24]),$a([0,-1,-1]),$a([5,67,30]),$a([4,68,28]),$a([1,69,28]),$a([14,35,30]),$a([1,32,24]),$a([0,-1,-1]),$a([18,18,26]),$a([3,16,28]),$a([1,14,28])]),$a(["38",97,19,4,8089,$a([3,142,26]),$a([3,141,28]),$a([0,-1,-1]),$a([8,70,30]),$a([1,73,32]),$a([1,74,32]),$a([12,34,30]),$a([3,34,26]),$a([1,35,28]),$a([18,21,32]),$a([1,27,30]),$a([0,-1,-1])]),$a(["39",99,20,4,8465,$a([5,116,22]),$a([2,103,20]),$a([1,102,20]),$a([9,74,32]),$a([1,74,30]),$a([0,-1,-1]),$a([14,34,28]),$a([2,32,32]),$a([1,32,30]),$a([19,21,32]),$a([1,25,26]),$a([0,-1,-1])]),$a(["40",101,20,4,8841,$a([7,116,22]),$a([1,117,22]),$a([0,-1,-1]),$a([11,65,28]),$a([1,58,24]),$a([0,-1,-1]),$a([15,38,32]),$a([1,27,28]),$a([0,-1,-1]),$a([20,20,30]),$a([1,20,32]),$a([1,21,32])]),$a(["41",103,17,5,9009,$a([6,136,26]),$a([1,130,24]),$a([0,-1,-1]),$a([11,66,28]),$a([1,62,30]),$a([0,-1,-1]),$a([14,34,28]),$a([3,34,32]),$a([1,30,30]),$a([18,20,30]),$a([3,20,28]),$a([2,15,26])]),$a(["42",105,17,5,9401,$a([5,105,20]),$a([2,115,22]),$a([2,116,22]),$a([10,75,32]),$a([1,73,32]),$a([0,-1,-1]),$a([16,38,32]),$a([1,27,28]),$a([0,-1,-1]),$a([22,19,28]),$a([2,16,30]),$a([1,19,30])]),$a(["43",107,18,5,9799,$a([6,147,28]),$a([1,146,28]),$a([0,-1,-1]),$a([11,66,28]),$a([2,65,30]),$a([0,-1,-1]),$a([18,33,28]),$a([2,33,30]),$a([0,-1,-1]),$a([22,21,32]),$a([1,28,30]),$a([0,-1,-1])]),$a(["44",109,18,5,10207,$a([6,116,22]),$a([3,125,24]),$a([0,-1,-1]),$a([11,75,32]),$a([1,68,30]),$a([0,-1,-1]),$a([13,35,28]),$a([6,34,32]),$a([1,30,30]),$a([23,21,32]),$a([1,26,30]),$a([0,-1,-1])]),$a(["45",111,18,5,10623,$a([7,105,20]),$a([4,95,18]),$a([0,-1,-1]),$a([12,67,28]),$a([1,63,30]),$a([1,62,32]),$a([21,31,26]),$a([2,33,32]),$a([0,-1,-1]),$a([23,21,32]),$a([2,24,30]),$a([0,-1,-1])]),$a(["46",113,19,5,11045,$a([10,116,22]),$a([0,-1,-1]),$a([0,-1,-1]),$a([12,74,32]),$a([1,78,30]),$a([0,-1,-1]),$a([18,37,32]),$a([1,39,30]),$a([1,41,28]),$a([25,21,32]),$a([1,27,28]),$a([0,-1,-1])]),$a(["47",115,19,5,11477,$a([5,126,24]),$a([4,115,22]),$a([1,114,22]),$a([12,67,28]),$a([2,66,32]),$a([1,68,30]),$a([21,35,30]),$a([1,39,30]),$a([0,-1,-1]),$a([26,21,32]),$a([1,28,28]),$a([0,-1,-1])]),$a(["48",117,19,5,11917,$a([9,126,24]),$a([1,117,22]),$a([0,-1,-1]),$a([13,75,32]),$a([1,68,30]),$a([0,-1,-1]),$a([20,35,30]),$a([3,35,28]),$a([0,-1,-1]),$a([27,21,32]),$a([1,28,30]),$a([0,-1,-1])]),$a(["49",119,17,6,12111,$a([9,126,24]),$a([1,137,26]),$a([0,-1,-1]),$a([13,71,30]),$a([2,68,32]),$a([0,-1,-1]),$a([20,37,32]),$a([1,39,28]),$a([1,38,28]),$a([24,20,32]),$a([5,25,28]),$a([0,-1,-1])]),$a(["50",121,17,6,12559,$a([8,147,28]),$a([1,141,28]),$a([0,-1,-1]),$a([10,73,32]),$a([4,74,30]),$a([1,73,30]),$a([16,36,32]),$a([6,39,30]),$a([1,37,30]),$a([27,21,32]),$a([3,20,26]),$a([0,-1,-1])]),$a(["51",123,18,6,13025,$a([9,137,26]),$a([1,135,26]),$a([0,-1,-1]),$a([12,70,30]),$a([4,75,32]),$a([0,-1,-1]),$a([24,35,30]),$a([1,40,28]),$a([0,-1,-1]),$a([23,20,32]),$a([8,24,30]),$a([0,-1,-1])]),$a(["52",125,18,6,13489,$a([14,95,18]),$a([1,86,18]),$a([0,-1,-1]),$a([13,73,32]),$a([3,77,30]),$a([0,-1,-1]),$a([24,35,30]),$a([2,35,28]),$a([0,-1,-1]),$a([26,21,32]),$a([5,21,30]),$a([1,23,30])]),$a(["53",127,18,6,13961,$a([9,147,28]),$a([1,142,28]),$a([0,-1,-1]),$a([10,73,30]),$a([6,70,32]),$a([1,71,32]),$a([25,35,30]),$a([2,34,26]),$a([0,-1,-1]),$a([29,21,32]),$a([4,22,30]),$a([0,-1,-1])]),$a(["54",129,18,6,14441,$a([11,126,24]),$a([1,131,24]),$a([0,-1,-1]),$a([16,74,32]),$a([1,79,30]),$a([0,-1,-1]),$a([25,38,32]),$a([1,25,30]),$a([0,-1,-1]),$a([33,21,32]),$a([1,28,28]),$a([0,-1,-1])]),$a(["55",131,19,6,14939,$a([14,105,20]),$a([1,99,18]),$a([0,-1,-1]),$a([19,65,28]),$a([1,72,28]),$a([0,-1,-1]),$a([24,37,32]),$a([2,40,30]),$a([1,41,30]),$a([31,21,32]),$a([4,24,32]),$a([0,-1,-1])]),$a(["56",133,19,6,15435,$a([10,147,28]),$a([1,151,28]),$a([0,-1,-1]),$a([15,71,30]),$a([3,71,32]),$a([1,73,32]),$a([24,37,32]),$a([3,38,30]),$a([1,39,30]),$a([36,19,30]),$a([3,29,26]),$a([0,-1,-1])]),$a(["57",135,19,6,15939,$a([15,105,20]),$a([1,99,18]),$a([0,-1,-1]),$a([19,70,30]),$a([1,64,28]),$a([0,-1,-1]),$a([27,38,32]),$a([2,25,26]),$a([0,-1,-1]),$a([38,20,30]),$a([2,18,28]),$a([0,-1,-1])]),$a(["58",137,17,7,16171,$a([14,105,20]),$a([1,113,22]),$a([1,114,22]),$a([17,67,30]),$a([3,92,32]),$a([0,-1,-1]),$a([30,35,30]),$a([1,41,30]),$a([0,-1,-1]),$a([36,21,32]),$a([1,26,30]),$a([1,27,30])]),$a(["59",139,17,7,16691,$a([11,146,28]),$a([1,146,26]),$a([0,-1,-1]),$a([20,70,30]),$a([1,60,26]),$a([0,-1,-1]),$a([29,38,32]),$a([1,24,32]),$a([0,-1,-1]),$a([40,20,30]),$a([2,17,26]),$a([0,-1,-1])]),$a(["60",141,18,7,17215,$a([3,137,26]),$a([1,136,26]),$a([10,126,24]),$a([22,65,28]),$a([1,75,30]),$a([0,-1,-1]),$a([30,37,32]),$a([1,51,30]),$a([0,-1,-1]),$a([42,20,30]),$a([1,21,30]),$a([0,-1,-1])]),$a(["61",143,18,7,17751,$a([12,126,24]),$a([2,118,22]),$a([1,116,22]),$a([19,74,32]),$a([1,74,30]),$a([1,72,28]),$a([30,38,32]),$a([2,29,30]),$a([0,-1,-1]),$a([39,20,32]),$a([2,37,26]),$a([1,38,26])]),$a(["62",145,18,7,18295,$a([12,126,24]),$a([3,136,26]),$a([0,-1,-1]),$a([21,70,30]),$a([2,65,28]),$a([0,-1,-1]),$a([34,35,30]),$a([1,44,32]),$a([0,-1,-1]),$a([42,20,30]),$a([2,19,28]),$a([2,18,28])]),$a(["63",147,18,7,18847,$a([12,126,24]),$a([3,117,22]),$a([1,116,22]),$a([25,61,26]),$a([2,62,28]),$a([0,-1,-1]),$a([34,35,30]),$a([1,40,32]),$a([1,41,32]),$a([45,20,30]),$a([1,20,32]),$a([1,21,32])]),$a(["64",149,19,7,19403,$a([15,105,20]),$a([2,115,22]),$a([2,116,22]),$a([25,65,28]),$a([1,72,28]),$a([0,-1,-1]),$a([18,35,30]),$a([17,37,32]),$a([1,50,32]),$a([42,20,30]),$a([6,19,28]),$a([1,15,28])]),$a(["65",151,19,7,19971,$a([19,105,20]),$a([1,101,20]),$a([0,-1,-1]),$a([33,51,22]),$a([1,65,22]),$a([0,-1,-1]),$a([40,33,28]),$a([1,28,28]),$a([0,-1,-1]),$a([49,20,30]),$a([1,18,28]),$a([0,-1,-1])]),$a(["66",153,17,8,20229,$a([18,105,20]),$a([2,117,22]),$a([0,-1,-1]),$a([26,65,28]),$a([1,80,30]),$a([0,-1,-1]),$a([35,35,30]),$a([3,35,28]),$a([1,36,28]),$a([52,18,28]),$a([2,38,30]),$a([0,-1,-1])]),$a(["67",155,17,8,20805,$a([26,84,16]),$a([0,-1,-1]),$a([0,-1,-1]),$a([26,70,30]),$a([0,-1,-1]),$a([0,-1,-1]),$a([45,31,26]),$a([1,9,26]),$a([0,-1,-1]),$a([52,20,30]),$a([0,-1,-1]),$a([0,-1,-1])]),$a(["68",157,17,8,21389,$a([16,126,24]),$a([1,114,22]),$a([1,115,22]),$a([23,70,30]),$a([3,65,28]),$a([1,66,28]),$a([40,35,30]),$a([1,43,30]),$a([0,-1,-1]),$a([46,20,30]),$a([7,19,28]),$a([1,16,28])]),$a(["69",159,18,8,21993,$a([19,116,22]),$a([1,105,22]),$a([0,-1,-1]),$a([20,70,30]),$a([7,66,28]),$a([1,63,28]),$a([40,35,30]),$a([1,42,32]),$a([1,43,32]),$a([54,20,30]),$a([1,19,30]),$a([0,-1,-1])]),$a(["70",161,18,8,22593,$a([17,126,24]),$a([2,115,22]),$a([0,-1,-1]),$a([24,70,30]),$a([4,74,32]),$a([0,-1,-1]),$a([48,31,26]),$a([2,18,26]),$a([0,-1,-1]),$a([54,19,28]),$a([6,15,26]),$a([1,14,26])]),$a(["71",163,18,8,23201,$a([29,84,16]),$a([0,-1,-1]),$a([0,-1,-1]),$a([29,70,30]),$a([0,-1,-1]),$a([0,-1,-1]),$a([6,34,30]),$a([3,36,30]),$a([38,33,28]),$a([58,20,30]),$a([0,-1,-1]),$a([0,-1,-1])]),$a(["72",165,18,8,23817,$a([16,147,28]),$a([1,149,28]),$a([0,-1,-1]),$a([31,66,28]),$a([1,37,26]),$a([0,-1,-1]),$a([48,33,28]),$a([1,23,26]),$a([0,-1,-1]),$a([53,20,30]),$a([6,19,28]),$a([1,17,28])]),$a(["73",167,19,8,24453,$a([20,115,22]),$a([2,134,24]),$a([0,-1,-1]),$a([29,66,28]),$a([2,56,26]),$a([2,57,26]),$a([45,36,30]),$a([2,15,28]),$a([0,-1,-1]),$a([59,20,30]),$a([2,21,32]),$a([0,-1,-1])]),$a(["74",169,19,8,25085,$a([17,147,28]),$a([1,134,26]),$a([0,-1,-1]),$a([26,70,30]),$a([5,75,32]),$a([0,-1,-1]),$a([47,35,30]),$a([1,48,32]),$a([0,-1,-1]),$a([64,18,28]),$a([2,33,30]),$a([1,35,30])]),$a(["75",171,17,9,25373,$a([22,115,22]),$a([1,133,24]),$a([0,-1,-1]),$a([33,65,28]),$a([1,74,28]),$a([0,-1,-1]),$a([43,36,30]),$a([5,27,28]),$a([1,30,28]),$a([57,20,30]),$a([5,21,32]),$a([1,24,32])]),$a(["76",173,17,9,26021,$a([18,136,26]),$a([2,142,26]),$a([0,-1,-1]),$a([33,66,28]),$a([2,49,26]),$a([0,-1,-1]),$a([48,35,30]),$a([2,38,28]),$a([0,-1,-1]),$a([64,20,30]),$a([1,20,32]),$a([0,-1,-1])]),$a(["77",175,17,9,26677,$a([19,126,24]),$a([2,135,26]),$a([1,136,26]),$a([32,66,28]),$a([2,55,26]),$a([2,56,26]),$a([49,36,30]),$a([2,18,32]),$a([0,-1,-1]),$a([65,18,28]),$a([5,27,30]),$a([1,29,30])]),$a(["78",177,18,9,27335,$a([20,137,26]),$a([1,130,26]),$a([0,-1,-1]),$a([30,75,32]),$a([2,71,32]),$a([0,-1,-1]),$a([46,35,30]),$a([6,39,32]),$a([0,-1,-1]),$a([3,12,30]),$a([70,19,28]),$a([0,-1,-1])]),$a(["79",179,18,9,28007,$a([20,147,28]),$a([0,-1,-1]),$a([0,-1,-1]),$a([35,70,30]),$a([0,-1,-1]),$a([0,-1,-1]),$a([49,35,30]),$a([5,35,28]),$a([0,-1,-1]),$a([70,20,30]),$a([0,-1,-1]),$a([0,-1,-1])]),$a(["80",181,18,9,28687,$a([21,136,26]),$a([1,155,28]),$a([0,-1,-1]),$a([34,70,30]),$a([1,64,28]),$a([1,65,28]),$a([54,35,30]),$a([1,45,30]),$a([0,-1,-1]),$a([68,20,30]),$a([3,18,28]),$a([1,19,28])]),$a(["81",183,18,9,29375,$a([19,126,24]),$a([5,115,22]),$a([1,114,22]),$a([33,70,30]),$a([3,65,28]),$a([1,64,28]),$a([52,35,30]),$a([3,41,32]),$a([1,40,32]),$a([67,20,30]),$a([5,21,32]),$a([1,24,32])]),$a(["82",185,18,9,30071,$a([2,150,28]),$a([21,136,26]),$a([0,-1,-1]),$a([32,70,30]),$a([6,65,28]),$a([0,-1,-1]),$a([52,38,32]),$a([2,27,32]),$a([0,-1,-1]),$a([73,20,30]),$a([2,22,32]),$a([0,-1,-1])]),$a(["83",187,17,10,30387,$a([21,126,24]),$a([4,136,26]),$a([0,-1,-1]),$a([30,74,32]),$a([6,73,30]),$a([0,-1,-1]),$a([54,35,30]),$a([4,40,32]),$a([0,-1,-1]),$a([75,20,30]),$a([1,20,28]),$a([0,-1,-1])]),$a(["84",189,17,10,31091,$a([30,105,20]),$a([1,114,22]),$a([0,-1,-1]),$a([3,45,22]),$a([55,47,20]),$a([0,-1,-1]),$a([2,26,26]),$a([62,33,28]),$a([0,-1,-1]),$a([79,18,28]),$a([4,33,30]),$a([0,-1,-1])])]);$1.eclval=$g($1.eclevel,1)-49;for(var _IM=0;_IM<=83;_IM+=1){$1.i=_IM;$1.m=$g($1.metrics,$1.i);$1.vers=$g($1.m,0);$1.size=$g($1.m,1);$1.alnk=$g($1.m,2);$1.alnn=$g($1.m,3);$1.alnr=$f($1.size-$1.alnk*$1.alnn);$1.nmod=$g($1.m,4);$1.ncws=~~($1.nmod/8);$1.rbit=$1.nmod%8;$1.ecbs=$G($1.m,5+$1.eclval*3,3);var _Ii=$1.ecbs;$k[$j++]="ecws";$k[$j++]=0;for(var _Ij=0,_Ik=_Ii.length;_Ij<_Ik;_Ij++){var _Il=$g(_Ii,_Ij);var _Io=$k[--$j];$k[$j++]=$f(_Io+$g(_Il,0)*$g(_Il,2))}var _Ip=$k[--$j];$1[$k[--$j]]=_Ip;$1.dcws=$f($1.ncws-$1.ecws);$1.dmod=$1.dcws*8;$1.okay=true;if($ne($1.version,"unset")&&$ne($1.version,$1.vers)){$1.okay=false}if($1.bits.length>$1.dmod){$1.okay=false}if($1.okay){break}}if(!$1.okay){$k[$j++]="bwipp.hanxinNoValidSymbol";$k[$j++]="No valid symbol available";bwipp_raiseerror()}$1.version=$1.vers;$1.msgbits=$1.bits;$1.e1nb=$g($g($1.ecbs,0),0);$1.e2nb=$g($g($1.ecbs,1),0);$1.e3nb=$g($g($1.ecbs,2),0);$1.e1dcws=$g($g($1.ecbs,0),1);$1.e2dcws=$g($g($1.ecbs,1),1);$1.e3dcws=$g($g($1.ecbs,2),1);$1.e1ecws=$g($g($1.ecbs,0),2);$1.e2ecws=$g($g($1.ecbs,1),2);$1.e3ecws=$g($g($1.ecbs,2),2);$1.pad=$s($1.dmod);for(var _JY=0,_JX=$1.pad.length-1;_JY<=_JX;_JY+=1){$p($1.pad,_JY,48)}$P($1.pad,0,$1.msgbits);$1.cws=$a($1.dcws);for(var _Jg=0,_Jf=$1.cws.length-1;_Jg<=_Jf;_Jg+=1){$1.c=_Jg;$1.cwb=$G($1.pad,$1.c*8,8);$1.cw=0;for(var _Jk=0;_Jk<=7;_Jk+=1){$1.i=_Jk;$1.cw=$f($1.cw+~~Math.pow(2,8-$1.i-1)*$f($g($1.cwb,$1.i)-48))}$p($1.cws,$1.c,$1.cw)}$1.rscodes=function(){$1.rspm=$k[--$j];$1.rsgf=$k[--$j];$1.rsnc=$k[--$j];$1.rscws=$k[--$j];$k[$j++]=Infinity;$k[$j++]=1;for(var _Jy=0,_Jz=$f($1.rsgf-1);_Jy<_Jz;_Jy++){var _K0=$k[--$j];var _K1=_K0*2;$k[$j++]=_K0;$k[$j++]=_K1;if(_K1>=$1.rsgf){var _K4=$k[--$j];$k[$j++]=$xo(_K4,$1.rspm)}}$1.rsalog=$a();$1.rslog=$a($1.rsgf);for(var _KA=1,_K9=$f($1.rsgf-1);_KA<=_K9;_KA+=1){$p($1.rslog,$g($1.rsalog,_KA),_KA)}$1.rsprod=function(){var _KE=$k[--$j];var _KF=$k[--$j];$k[$j++]=_KF;$k[$j++]=_KE;if(_KE!=0&&_KF!=0){var _KI=$g($1.rslog,$k[--$j]);var _KO=$g($1.rsalog,$f(_KI+$g($1.rslog,$k[--$j]))%$f($1.rsgf-1));$k[$j++]=_KO}else{$j-=2;$k[$j++]=0}};$k[$j++]=Infinity;$k[$j++]=1;for(var _KQ=0,_KR=$1.rsnc;_KQ<_KR;_KQ++){$k[$j++]=0}$1.coeffs=$a();for(var _KV=1,_KU=$1.rsnc;_KV<=_KU;_KV+=1){$1.i=_KV;$p($1.coeffs,$1.i,$g($1.coeffs,$1.i-1));for(var _Kc=$1.i-1;_Kc>=1;_Kc-=1){$1.j=_Kc;$k[$j++]=$1.coeffs;$k[$j++]=$1.j;$k[$j++]=$g($1.coeffs,$1.j-1);$k[$j++]=$g($1.coeffs,$1.j);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _Ko=$k[--$j];var _Kp=$k[--$j];var _Kq=$k[--$j];$p($k[--$j],_Kq,$xo(_Kp,_Ko))}$k[$j++]=$1.coeffs;$k[$j++]=0;$k[$j++]=$g($1.coeffs,0);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _Ky=$k[--$j];var _Kz=$k[--$j];$p($k[--$j],_Kz,_Ky)}$1.nd=$1.rscws.length;$k[$j++]=Infinity;$F($1.rscws);for(var _L4=0,_L5=$1.rsnc;_L4<_L5;_L4++){$k[$j++]=0}$k[$j++]=0;$1.rscws=$a();for(var _L9=0,_L8=$1.nd-1;_L9<=_L8;_L9+=1){$1.k=$xo($g($1.rscws,_L9),$g($1.rscws,$1.nd));for(var _LH=0,_LG=$f($1.rsnc-1);_LH<=_LG;_LH+=1){$1.j=_LH;$k[$j++]=$1.rscws;$k[$j++]=$1.nd+$1.j;$k[$j++]=$g($1.rscws,$1.nd+$1.j+1);$k[$j++]=$1.k;$k[$j++]=$g($1.coeffs,$f($f($1.rsnc-$1.j)-1));$1.rsprod();var _LU=$k[--$j];var _LV=$k[--$j];var _LW=$k[--$j];$p($k[--$j],_LW,$xo(_LV,_LU))}}$k[$j++]=$G($1.rscws,0,$1.rscws.length-1)};$1.dcwsb=$a($f($f($1.e1nb+$1.e2nb)+$1.e3nb));$1.ecwsb=$a($1.dcwsb.length);$1.in=0;$1.out=0;for(var _Li=0,_Lj=$1.e1nb;_Li<_Lj;_Li++){$k[$j++]=$G($1.cws,$1.in,$1.e1dcws);$k[$j++]=$1.e1ecws;$k[$j++]=256;$k[$j++]=355;$1.rscodes();var _Lp=$k[--$j];$p($1.dcwsb,$1.out,$G(_Lp,0,$1.e1dcws));$p($1.ecwsb,$1.out,$G(_Lp,$1.e1dcws,$1.e1ecws));$1.in=$f($1.in+$1.e1dcws);$1.out=$1.out+1}for(var _M3=0,_M4=$1.e2nb;_M3<_M4;_M3++){$k[$j++]=$G($1.cws,$1.in,$1.e2dcws);$k[$j++]=$1.e2ecws;$k[$j++]=256;$k[$j++]=355;$1.rscodes();var _MA=$k[--$j];$p($1.dcwsb,$1.out,$G(_MA,0,$1.e2dcws));$p($1.ecwsb,$1.out,$G(_MA,$1.e2dcws,$1.e2ecws));$1.in=$f($1.in+$1.e2dcws);$1.out=$1.out+1}for(var _MO=0,_MP=$1.e3nb;_MO<_MP;_MO++){$k[$j++]=$G($1.cws,$1.in,$1.e3dcws);$k[$j++]=$1.e3ecws;$k[$j++]=256;$k[$j++]=355;$1.rscodes();var _MV=$k[--$j];$p($1.dcwsb,$1.out,$G(_MV,0,$1.e3dcws));$p($1.ecwsb,$1.out,$G(_MV,$1.e3dcws,$1.e3ecws));$1.in=$f($1.in+$1.e3dcws);$1.out=$1.out+1}$1.cws=$a($1.ncws);$1.cw=0;for(var _Mm=0,_Ml=$1.dcwsb.length-1;_Mm<=_Ml;_Mm+=1){$1.i=_Mm;var _Mp=$g($1.dcwsb,$1.i);$P($1.cws,$1.cw,_Mp);$1.cw=_Mp.length+$1.cw;var _Mv=$g($1.ecwsb,$1.i);$P($1.cws,$1.cw,_Mv);$1.cw=_Mv.length+$1.cw}$k[$j++]=Infinity;var _Mz=$1.ncws;var _N0=12;var _N1=_Mz-1;if(_Mz-1>12){var _=_N0;_N0=_N1;_N1=_}for(var _N3=0,_N2=_N1;_N3<=_N2;_N3+=1){for(var _N6=_N3,_N5=$1.ncws-1;_N6<=_N5;_N6+=13){$k[$j++]=_N6;if(_N6<$1.ncws){var _NA=$g($1.cws,$k[--$j]);$k[$j++]=_NA}else{$j--}}}$1.cws=$a();if($1.rbit>0){$1.pad=$a($1.cws.length+1);$P($1.pad,0,$1.cws);$p($1.pad,$1.pad.length-1,0);$1.cws=$1.pad}$k[$j++]=Infinity;for(var _NM=0,_NN=$1.size*$1.size;_NM<_NN;_NM++){$k[$j++]=-1}$1.pixs=$a();$1.qmv=function(){var _NQ=$k[--$j];var _NR=$k[--$j];$k[$j++]=$f(_NR+_NQ*$1.size)};if($1.alnn!=0){$1.trmv=function(){var _NU=$k[--$j];var _NW=$k[--$j];$k[$j++]=$f($f($f(_NU*$1.size+$1.size)-1)-_NW)};$1.aplot=function(){var _NX=$k[--$j];var _NY=$k[--$j];var _NZ=$k[--$j];$k[$j++]=_NX;$k[$j++]=_NZ;$k[$j++]=_NY;$k[$j++]=_NX;$k[$j++]=_NY;$k[$j++]=_NZ;$1.trmv();var _Nb=$k[--$j];$p($1.pixs,_Nb,$k[--$j]);$1.trmv();var _Ne=$k[--$j];$p($1.pixs,_Ne,$k[--$j])};$1.i=0;$1.stag=0;for(;;){if($1.i>=$1.size){break}for(var _Nk=0,_Nj=$f($1.size-1);_Nk<=_Nj;_Nk+=1){$1.j=_Nk;if($f($1.j+$1.alnr)<$1.size){$k[$j++]=(~~($1.j/$1.alnk)+$1.stag)%2==0&&!($1.i==0&&$1.j<$1.alnk)||$1.j%$1.alnk==0}else{$k[$j++]=$f($1.alnn+$1.stag)%2==0}if($k[--$j]){$k[$j++]=$1.j;$k[$j++]=$1.i;$k[$j++]=1;$1.aplot();var _O1=$1.j;var _O2=$1.i;$k[$j++]=_O1+1;$k[$j++]=_O2+1;if(_O2+1<$1.size&&_O1+1<$1.size){$k[$j++]=0;$1.aplot()}else{$j-=2}}}if($f($1.i+$1.alnr)==$1.size){$1.i=$f($f($1.i+$1.alnr)-1)}else{$1.i=$f($1.i+$1.alnk)}$1.stag=1-$1.stag}for(var _OH=$1.alnk,_OI=$1.alnk,_OG=$f($1.size-2);_OI<0?_OH>=_OG:_OH<=_OG;_OH+=_OI){$1.i=_OH;if(~~($1.i/$1.alnk)%2!=0){$k[$j++]=$1.pixs;$k[$j++]=0;$k[$j++]=$1.i-1;$1.trmv();var _ON=$k[--$j];$p($k[--$j],_ON,0);$k[$j++]=$1.pixs;$k[$j++]=0;$k[$j++]=$1.i+1;$1.trmv();var _OR=$k[--$j];$p($k[--$j],_OR,0);$k[$j++]=$1.pixs;$k[$j++]=1;$k[$j++]=$1.i-1;$1.trmv();var _OV=$k[--$j];$p($k[--$j],_OV,0);$k[$j++]=$1.pixs;$k[$j++]=1;$k[$j++]=$1.i;$1.trmv();var _OZ=$k[--$j];$p($k[--$j],_OZ,0);$k[$j++]=$1.pixs;$k[$j++]=1;$k[$j++]=$1.i+1;$1.trmv();var _Od=$k[--$j];$p($k[--$j],_Od,0);$k[$j++]=$1.pixs;$k[$j++]=$1.i-1;$k[$j++]=0;$1.trmv();var _Oh=$k[--$j];$p($k[--$j],_Oh,0);$k[$j++]=$1.pixs;$k[$j++]=$1.i+1;$k[$j++]=0;$1.trmv();var _Ol=$k[--$j];$p($k[--$j],_Ol,0);$k[$j++]=$1.pixs;$k[$j++]=$1.i-1;$k[$j++]=1;$1.trmv();var _Op=$k[--$j];$p($k[--$j],_Op,0);$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=1;$1.trmv();var _Ot=$k[--$j];$p($k[--$j],_Ot,0);$k[$j++]=$1.pixs;$k[$j++]=$1.i+1;$k[$j++]=1;$1.trmv();var _Ox=$k[--$j];$p($k[--$j],_Ox,0)}$k[$j++]=$1.pixs;$k[$j++]=$f($1.size-1);$k[$j++]=$1.i-1;$1.trmv();var _P2=$k[--$j];if($g($k[--$j],_P2)!=1){$k[$j++]=$1.pixs;$k[$j++]=$f($1.size-1);$k[$j++]=$1.i-1;$1.trmv();var _P8=$k[--$j];$p($k[--$j],_P8,0);$k[$j++]=$1.pixs;$k[$j++]=$f($1.size-2);$k[$j++]=$1.i-1;$1.trmv();var _PD=$k[--$j];$p($k[--$j],_PD,0);$k[$j++]=$1.pixs;$k[$j++]=$f($1.size-2);$k[$j++]=$1.i;$1.trmv();var _PI=$k[--$j];$p($k[--$j],_PI,0);$k[$j++]=$1.pixs;$k[$j++]=$f($1.size-2);$k[$j++]=$1.i+1;$1.trmv();var _PN=$k[--$j];$p($k[--$j],_PN,0);$k[$j++]=$1.pixs;$k[$j++]=$f($1.size-1);$k[$j++]=$1.i+1;$1.trmv();var _PS=$k[--$j];$p($k[--$j],_PS,0);$k[$j++]=$1.pixs;$k[$j++]=$1.i-1;$k[$j++]=$f($1.size-1);$1.trmv();var _PX=$k[--$j];$p($k[--$j],_PX,0);$k[$j++]=$1.pixs;$k[$j++]=$1.i-1;$k[$j++]=$f($1.size-2);$1.trmv();var _Pc=$k[--$j];$p($k[--$j],_Pc,0);$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=$f($1.size-2);$1.trmv();var _Ph=$k[--$j];$p($k[--$j],_Ph,0);$k[$j++]=$1.pixs;$k[$j++]=$1.i+1;$k[$j++]=$f($1.size-2);$1.trmv();var _Pm=$k[--$j];$p($k[--$j],_Pm,0);$k[$j++]=$1.pixs;$k[$j++]=$1.i+1;$k[$j++]=$f($1.size-1);$1.trmv();var _Pr=$k[--$j];$p($k[--$j],_Pr,0)}}}$1.fpat=$a([$a([1,1,1,1,1,1,1,0]),$a([1,0,0,0,0,0,0,0]),$a([1,0,1,1,1,1,1,0]),$a([1,0,1,0,0,0,0,0]),$a([1,0,1,0,1,1,1,0]),$a([1,0,1,0,1,1,1,0]),$a([1,0,1,0,1,1,1,0]),$a([0,0,0,0,0,0,0,0])]);$1.fpat2=$a([$a([1,1,1,0,1,0,1,0]),$a([1,1,1,0,1,0,1,0]),$a([1,1,1,0,1,0,1,0]),$a([0,0,0,0,1,0,1,0]),$a([1,1,1,1,1,0,1,0]),$a([0,0,0,0,0,0,1,0]),$a([1,1,1,1,1,1,1,0]),$a([0,0,0,0,0,0,0,0])]);for(var _QD=0,_QC=$1.fpat.length-1;_QD<=_QC;_QD+=1){$1.y=_QD;for(var _QH=0,_QG=$g($1.fpat,0).length-1;_QH<=_QG;_QH+=1){$1.x=_QH;$1.fpb=$g($g($1.fpat,$1.y),$1.x);$1.fpb2=$g($g($1.fpat2,$1.y),$1.x);$k[$j++]=$1.pixs;$k[$j++]=$1.x;$k[$j++]=$1.y;$1.qmv();var _QW=$k[--$j];$p($k[--$j],_QW,$1.fpb);$k[$j++]=$1.pixs;$k[$j++]=$f($f($1.size-$1.x)-1);$k[$j++]=$1.y;$1.qmv();var _Qd=$k[--$j];$p($k[--$j],_Qd,$1.fpb);$k[$j++]=$1.pixs;$k[$j++]=$f($f($1.size-$1.x)-1);$k[$j++]=$f($f($1.size-$1.y)-1);$1.qmv();var _Ql=$k[--$j];$p($k[--$j],_Ql,$1.fpb);$k[$j++]=$1.pixs;$k[$j++]=$1.x;$k[$j++]=$f($f($1.size-$1.y)-1);$1.qmv();var _Qs=$k[--$j];$p($k[--$j],_Qs,$1.fpb2)}}$1.functionmap=$a([$a([$a([0,8]),$a([$f($1.size-1),$f($1.size-9)])]),$a([$a([1,8]),$a([$f($1.size-2),$f($1.size-9)])]),$a([$a([2,8]),$a([$f($1.size-3),$f($1.size-9)])]),$a([$a([3,8]),$a([$f($1.size-4),$f($1.size-9)])]),$a([$a([4,8]),$a([$f($1.size-5),$f($1.size-9)])]),$a([$a([5,8]),$a([$f($1.size-6),$f($1.size-9)])]),$a([$a([6,8]),$a([$f($1.size-7),$f($1.size-9)])]),$a([$a([7,8]),$a([$f($1.size-8),$f($1.size-9)])]),$a([$a([8,8]),$a([$f($1.size-9),$f($1.size-9)])]),$a([$a([8,7]),$a([$f($1.size-9),$f($1.size-8)])]),$a([$a([8,6]),$a([$f($1.size-9),$f($1.size-7)])]),$a([$a([8,5]),$a([$f($1.size-9),$f($1.size-6)])]),$a([$a([8,4]),$a([$f($1.size-9),$f($1.size-5)])]),$a([$a([8,3]),$a([$f($1.size-9),$f($1.size-4)])]),$a([$a([8,2]),$a([$f($1.size-9),$f($1.size-3)])]),$a([$a([8,1]),$a([$f($1.size-9),$f($1.size-2)])]),$a([$a([8,0]),$a([$f($1.size-9),$f($1.size-1)])]),$a([$a([$f($1.size-9),0]),$a([8,$f($1.size-1)])]),$a([$a([$f($1.size-9),1]),$a([8,$f($1.size-2)])]),$a([$a([$f($1.size-9),2]),$a([8,$f($1.size-3)])]),$a([$a([$f($1.size-9),3]),$a([8,$f($1.size-4)])]),$a([$a([$f($1.size-9),4]),$a([8,$f($1.size-5)])]),$a([$a([$f($1.size-9),5]),$a([8,$f($1.size-6)])]),$a([$a([$f($1.size-9),6]),$a([8,$f($1.size-7)])]),$a([$a([$f($1.size-9),7]),$a([8,$f($1.size-8)])]),$a([$a([$f($1.size-9),8]),$a([8,$f($1.size-9)])]),$a([$a([$f($1.size-8),8]),$a([7,$f($1.size-9)])]),$a([$a([$f($1.size-7),8]),$a([6,$f($1.size-9)])]),$a([$a([$f($1.size-6),8]),$a([5,$f($1.size-9)])]),$a([$a([$f($1.size-5),8]),$a([4,$f($1.size-9)])]),$a([$a([$f($1.size-4),8]),$a([3,$f($1.size-9)])]),$a([$a([$f($1.size-3),8]),$a([2,$f($1.size-9)])]),$a([$a([$f($1.size-2),8]),$a([1,$f($1.size-9)])]),$a([$a([$f($1.size-1),8]),$a([0,$f($1.size-9)])])]);var _Tf=$1.functionmap;for(var _Tg=0,_Th=_Tf.length;_Tg<_Th;_Tg++){$F($g(_Tf,_Tg),function(){$F($k[--$j]);$1.qmv();$p($1.pixs,$k[--$j],0)})}var _Ts=$a([function(){$j-=2;$k[$j++]=1},function(){var _Tm=$k[--$j];var _Tn=$k[--$j];$k[$j++]=$f(_Tn+_Tm)%2},function(){var _To=$k[--$j];var _Tp=$k[--$j];$k[$j++]=$f($f(_To+_Tp)%3+_Tp%3)%2},function(){var _Tq=$k[--$j];var _Tr=$k[--$j];$k[$j++]=$f(_Tr%_Tq+$f(_Tq%_Tr+$f(_Tq%3+_Tr%3)))%2}]);$1.maskfuncs=_Ts;if($1.mask!=-1){$1.maskfuncs=$a([$g($1.maskfuncs,$1.mask-1)]);$1.bestmaskval=$1.mask-1}$1.masks=$a($1.maskfuncs.length);for(var _U3=0,_U2=$1.masks.length-1;_U3<=_U2;_U3+=1){$1.m=_U3;$1.mask=$a($1.size*$1.size);for(var _U9=0,_U8=$f($1.size-1);_U9<=_U8;_U9+=1){$1.j=_U9;for(var _UC=0,_UB=$f($1.size-1);_UC<=_UB;_UC+=1){$1.i=_UC;$k[$j++]=$1.i+1;$k[$j++]=$1.j+1;if($g($1.maskfuncs,$1.m)()===true){break}var _UI=$k[--$j];$k[$j++]=_UI==0;$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=$1.j;$1.qmv();var _UM=$k[--$j];var _UO=$g($k[--$j],_UM);var _UP=$k[--$j];var _UQ=_UP&&_UO==-1?1:0;$k[$j++]=_UQ;$k[$j++]=$1.mask;$k[$j++]=$1.i;$k[$j++]=$1.j;$1.qmv();var _UU=$k[--$j];var _UV=$k[--$j];$p(_UV,_UU,$k[--$j])}}$p($1.masks,$1.m,$1.mask)}$1.posx=0;$1.posy=0;$1.num=0;for(;;){if($1.posy==$1.size){break}$k[$j++]=$1.pixs;$k[$j++]=$1.posx;$k[$j++]=$1.posy;$1.qmv();var _Uf=$k[--$j];if($g($k[--$j],_Uf)==-1){var _Uk=$g($1.cws,~~($1.num/8));var _Um=-(7-$1.num%8);$k[$j++]=(_Um<0?_Uk>>>-_Um:_Uk<<_Um)&1;$k[$j++]=$1.pixs;$k[$j++]=$1.posx;$k[$j++]=$1.posy;$1.qmv();var _Uq=$k[--$j];var _Ur=$k[--$j];$p(_Ur,_Uq,$k[--$j]);$1.num=$1.num+1}$1.posx=$1.posx+1;if($1.posx==$1.size){$1.posx=0;$1.posy=$1.posy+1}}$1.evalfulln1n3=function(){$1.scrle=$k[--$j];$k[$j++]="scr1";$k[$j++]=0;$F($1.scrle,function(){var _V0=$k[--$j];$k[$j++]=_V0;if(_V0>=3){var _V1=$k[--$j];var _V3=$f($k[--$j]+_V1*4);$k[$j++]=_V3;$k[$j++]=_V3}$j--});var _V4=$k[--$j];$1[$k[--$j]]=_V4;$1.scr3=0;for(var _V8=5,_V7=$1.scrle.length-1;_V8<=_V7;_V8+=2){$1.j=_V8;if($g($1.scrle,$1.j)%3==0){$1.fact=~~($g($1.scrle,$1.j)/3);var _VH=$G($1.scrle,$1.j-4,4);for(var _VI=0,_VJ=_VH.length;_VI<_VJ;_VI++){$k[$j++]=$g(_VH,_VI)==$1.fact}var _VM=$k[--$j];var _VN=$k[--$j];var _VO=$k[--$j];var _VP=$k[--$j];if(_VP&&(_VO&&(_VN&&_VM))){if($1.j==5||$1.j+2>=$1.scrle.length){$1.scr3=$1.scr3+50}else{if($g($1.scrle,$1.j-5)>=3||$g($1.scrle,$1.j+1)>=3){$1.scr3=$1.scr3+50}}}}}for(var _Vd=1,_Vc=$1.scrle.length-5;_Vd<=_Vc;_Vd+=2){$1.j=_Vd;if($g($1.scrle,$1.j)%3==0){$1.fact=~~($g($1.scrle,$1.j)/3);var _Vm=$G($1.scrle,$1.j+1,4);for(var _Vn=0,_Vo=_Vm.length;_Vn<_Vo;_Vn++){$k[$j++]=$g(_Vm,_Vn)==$1.fact}var _Vr=$k[--$j];var _Vs=$k[--$j];var _Vt=$k[--$j];var _Vu=$k[--$j];if(_Vu&&(_Vt&&(_Vs&&_Vr))){if($1.j==1||$1.j+6>=$1.scrle.length){$1.scr3=$1.scr3+50}else{if($g($1.scrle,$1.j-1)>=3||$g($1.scrle,$1.j+5)>=3){$1.scr3=$1.scr3+50}}}}}$k[$j++]=$1.scr1;$k[$j++]=$1.scr3};$1.evalfull=function(){$1.sym=$k[--$j];$1.n1=0;$1.n3=0;$1.rle=$a($f($1.size+1));$1.lastpairs=$a($1.size);$1.thispairs=$a($1.size);$1.sizeadd1=$f($1.size+1);for(var _WI=0,_WH=$f($1.size-1);_WI<=_WH;_WI+=1){$1.i=_WI;$k[$j++]=Infinity;var _WK=$1.size;$k[$j++]=0;$k[$j++]=0;for(var _WM=$1.i,_WN=_WK,_WL=$f(_WK*_WK-1);_WN<0?_WM>=_WL:_WM<=_WL;_WM+=_WN){var _WP=$g($1.sym,_WM);var _WQ=$k[--$j];$k[$j++]=_WP;if($eq(_WQ,_WP)){var _WR=$k[--$j];var _WS=$k[--$j];$k[$j++]=$f(_WS+1);$k[$j++]=_WR}else{var _WT=$k[--$j];$k[$j++]=1;$k[$j++]=_WT}}$j--;var _WV=$m()+2;$r($G($1.rle,0,_WV-2));$1.evalfulln1n3();$1.n3=$f($k[--$j]+$1.n3);$1.n1=$f($k[--$j]+$1.n1);$j--;$1.symrow=$G($1.sym,$1.i*$1.size,$1.size);$k[$j++]=Infinity;var _Wg=$1.symrow;$k[$j++]=0;$k[$j++]=0;for(var _Wh=0,_Wi=_Wg.length;_Wh<_Wi;_Wh++){var _Wj=$g(_Wg,_Wh);var _Wk=$k[--$j];$k[$j++]=_Wj;if($eq(_Wk,_Wj)){var _Wl=$k[--$j];var _Wm=$k[--$j];$k[$j++]=$f(_Wm+1);$k[$j++]=_Wl}else{var _Wn=$k[--$j];$k[$j++]=1;$k[$j++]=_Wn}}$j--;var _Wp=$m()+2;$r($G($1.rle,0,_Wp-2));$1.evalfulln1n3();$1.n3=$f($k[--$j]+$1.n3);$1.n1=$f($k[--$j]+$1.n1);$j--}$k[$j++]=$f($1.n1+$1.n3)};$1.bestscore=999999999;for(var _Wz=0,_Wy=$1.masks.length-1;_Wz<=_Wy;_Wz+=1){$1.m=_Wz;$1.masksym=$a($1.size*$1.size);for(var _X6=0,_X5=$f($1.size*$1.size-1);_X6<=_X5;_X6+=1){$1.i=_X6;$p($1.masksym,$1.i,$xo($g($1.pixs,$1.i),$g($g($1.masks,$1.m),$1.i)))}if($1.masks.length!=1){$k[$j++]=$1.masksym;$1.evalfull();$1.score=$k[--$j];if($1.score<$1.bestscore){$1.bestsym=$1.masksym;$1.bestmaskval=$1.m;$1.bestscore=$1.score}}else{$1.bestsym=$1.masksym}}$1.pixs=$1.bestsym;$1.funval=((~~($f($1.size-21)/2)+20)*4+$1.eclval)*4+$1.bestmaskval;$k[$j++]=$a([($1.funval&3840)>>>8,($1.funval&240)>>>4,$1.funval&15]);$k[$j++]=4;$k[$j++]=16;$k[$j++]=19;$1.rscodes();$1.funvals=$k[--$j];$k[$j++]=Infinity;$F($1.funvals,function(){$k[$j++]=4;$1.tobin();$F($k[--$j],function(){var _Xb=$k[--$j];$k[$j++]=$f(_Xb-48)})});$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$1.funbits=$a();for(var _Xf=0,_Xe=$1.functionmap.length-1;_Xf<=_Xe;_Xf+=1){$1.i=_Xf;$F($g($1.functionmap,$1.i),function(){var _Xk=$k[--$j];$k[$j++]=$1.pixs;$q(_Xk);$1.qmv();var _Xo=$k[--$j];$p($k[--$j],_Xo,$g($1.funbits,$1.i))})}var _Xw=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",$1.size],["pixy",$1.size],["height",$1.size*2/72],["width",$1.size*2/72],["opt",$1.options]]);$k[$j++]=_Xw;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_dotcode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.rows=-1;$1.columns=-1;$1.ratio=-1;$1.parse=false;$1.parsefnc=false;$1.raw=false;$1.fast=false;$1.mask=-1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.rows=~~$1.rows;$1.columns=~~$1.columns;$1.ratio=+$1.ratio;$1.mask=~~$1.mask;if($1.ratio==-1&&$1.rows==-1&&$1.columns==-1){$1.ratio=3/2}$1.laa=-1;$1.lab=-2;$1.lac=-3;$1.bin=-4;$1.sfa=-5;$1.sfb=-6;$1.sb2=-7;$1.sb3=-8;$1.sb4=-9;$1.sb5=-10;$1.sb6=-11;$1.sfc=-12;$1.sc2=-13;$1.sc3=-14;$1.sc4=-15;$1.sc5=-16;$1.sc6=-17;$1.sc7=-18;$1.bsa=-19;$1.bsb=-20;$1.tma=-21;$1.tmb=-22;$1.tmc=-23;$1.tms=-24;$1.fn1=-25;$1.fn2=-26;$1.fn3=-27;$1.crl=-28;$1.aim=-29;$1.m05=-30;$1.m06=-31;$1.m12=-32;$1.mac=-33;var _G=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["eci",true],["FNC1",$1.fn1],["FNC3",$1.fn3]]);$1.fncvals=_G;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _J=$k[--$j];$1[$k[--$j]]=_J;$1.msglen=$1.msg.length;var _M=$1.msg;$k[$j++]="numecis";$k[$j++]=0;for(var _N=0,_O=_M.length;_N<_O;_N++){if($g(_M,_N)<=-1e6){var _Q=$k[--$j];$k[$j++]=$f(_Q+1)}}var _R=$k[--$j];$1[$k[--$j]]=_R;$1.msgtmp=$a($f($1.msg.length+$1.numecis*6));$1.j=0;for(var _Y=0,_X=$1.msg.length-1;_Y<=_X;_Y+=1){var _a=$g($1.msg,_Y);$k[$j++]=_a;if(_a<=-1e6){var _e=$G($R($s(7),-$k[--$j],10),1,6);for(var _f=0,_g=_e.length;_f<_g;_f++){$k[$j++]=$g(_e,_f)}$r($a(6));$P($1.msgtmp,$1.j+1,$k[--$j]);$p($1.msgtmp,$1.j,$1.fn2);$1.j=$1.j+7}else{$p($1.msgtmp,$1.j,$k[--$j]);$1.j=$1.j+1}}$1.msg=$1.msgtmp;$1.msglen=$1.msg.length;$1.charmaps=$a([$a([32,32,"00"]),$a(["!","!","01"]),$a(['"','"',"02"]),$a(["#","#","03"]),$a(["$","$","04"]),$a(["%","%","05"]),$a(["&","&","06"]),$a(["'","'","07"]),$a([40,40,"08"]),$a([41,41,"09"]),$a(["*","*","10"]),$a(["+","+","11"]),$a([",",",","12"]),$a(["-","-","13"]),$a([".",".","14"]),$a(["/","/","15"]),$a(["0","0","16"]),$a(["1","1","17"]),$a(["2","2","18"]),$a(["3","3","19"]),$a(["4","4","20"]),$a(["5","5","21"]),$a(["6","6","22"]),$a(["7","7","23"]),$a(["8","8","24"]),$a(["9","9","25"]),$a([":",":","26"]),$a([";",";","27"]),$a(["<","<","28"]),$a(["=","=","29"]),$a([">",">","30"]),$a(["?","?","31"]),$a(["@","@","32"]),$a(["A","A","33"]),$a(["B","B","34"]),$a(["C","C","35"]),$a(["D","D","36"]),$a(["E","E","37"]),$a(["F","F","38"]),$a(["G","G","39"]),$a(["H","H","40"]),$a(["I","I","41"]),$a(["J","J","42"]),$a(["K","K","43"]),$a(["L","L","44"]),$a(["M","M","45"]),$a(["N","N","46"]),$a(["O","O","47"]),$a(["P","P","48"]),$a(["Q","Q","49"]),$a(["R","R","50"]),$a(["S","S","51"]),$a(["T","T","52"]),$a(["U","U","53"]),$a(["V","V","54"]),$a(["W","W","55"]),$a(["X","X","56"]),$a(["Y","Y","57"]),$a(["Z","Z","58"]),$a(["[","[","59"]),$a([92,92,"60"]),$a(["]","]","61"]),$a(["^","^","62"]),$a(["_","_","63"]),$a([0,"`","64"]),$a([1,"a","65"]),$a([2,"b","66"]),$a([3,"c","67"]),$a([4,"d","68"]),$a([5,"e","69"]),$a([6,"f","70"]),$a([7,"g","71"]),$a([8,"h","72"]),$a([9,"i","73"]),$a([10,"j","74"]),$a([11,"k","75"]),$a([12,"l","76"]),$a([13,"m","77"]),$a([14,"n","78"]),$a([15,"o","79"]),$a([16,"p","80"]),$a([17,"q","81"]),$a([18,"r","82"]),$a([19,"s","83"]),$a([20,"t","84"]),$a([21,"u","85"]),$a([22,"v","86"]),$a([23,"w","87"]),$a([24,"x","88"]),$a([25,"y","89"]),$a([26,"z","90"]),$a([27,"{","91"]),$a([28,"|","92"]),$a([29,"}","93"]),$a([30,"~","94"]),$a([31,127,"95"]),$a([$1.sfb,$1.crl,"96"]),$a([$1.sb2,9,"97"]),$a([$1.sb3,28,"98"]),$a([$1.sb4,29,"99"]),$a([$1.sb5,30,$1.aim]),$a([$1.sb6,$1.sfa,$1.laa]),$a([$1.lab,$1.laa,$1.sfb]),$a([$1.sc2,$1.sc2,$1.sb2]),$a([$1.sc3,$1.sc3,$1.sb3]),$a([$1.sc4,$1.sc4,$1.sb4]),$a([$1.lac,$1.lac,$1.lab]),$a([$1.fn1,$1.fn1,$1.fn1]),$a([$1.fn2,$1.fn2,$1.fn2]),$a([$1.fn3,$1.fn3,$1.fn3]),$a([$1.bsa,$1.bsa,$1.bsa]),$a([$1.bsb,$1.bsb,$1.bsb]),$a([$1.bin,$1.bin,$1.bin])]);$1.charvals=$a([new Map,new Map,new Map]);for(var _3W=0,_3V=$1.charmaps.length-1;_3W<=_3V;_3W+=1){$1.i=_3W;$1.encs=$g($1.charmaps,$1.i);for(var _3a=0;_3a<=2;_3a+=1){$1.j=_3a;var _3d=$g($1.encs,$1.j);$k[$j++]=_3d;if($eq($t(_3d),"stringtype")){var _3g=$g($k[--$j],0);$k[$j++]=_3g}$p($g($1.charvals,$1.j),$k[--$j],$1.i)}}$1.Avals=$g($1.charvals,0);$1.Bvals=$g($1.charvals,1);$p($1.Bvals,$1.m05,97);$p($1.Bvals,$1.m06,98);$p($1.Bvals,$1.m12,99);$p($1.Bvals,$1.mac,100);$1.Cvals=$g($1.charvals,2);$k[$j++]=Infinity;var _4A=$a([$1.sc2,$1.sc3,$1.sc4,$1.sc5,$1.sc6,$1.sc7,$1.tma,$1.tmb,$1.tmc,$1.tms]);$k[$j++]=102;for(var _4B=0,_4C=_4A.length;_4B<_4C;_4B++){var _4F=$f($k[--$j]+1);$k[$j++]=$g(_4A,_4B);$k[$j++]=_4F;$k[$j++]=_4F}$j--;$1.BINvals=$d();$k[$j++]=Infinity;for(var _4I=0,_4J=$1.msglen+1;_4I<_4J;_4I++){$k[$j++]=0}$1.nDigits=$a();$k[$j++]=Infinity;for(var _4M=0,_4N=$1.msglen+1;_4M<_4N;_4M++){$k[$j++]=false}$1.SeventeenTen=$a();$k[$j++]=Infinity;for(var _4Q=0,_4R=$1.msglen+1;_4Q<_4R;_4Q++){$k[$j++]=false}$1.ECI=$a();$k[$j++]=Infinity;for(var _4U=0,_4V=$1.msglen+1;_4U<_4V;_4U++){$k[$j++]=false}$1.DatumA=$a();$k[$j++]=Infinity;for(var _4Y=0,_4Z=$1.msglen+1;_4Y<_4Z;_4Y++){$k[$j++]=false}$1.DatumB=$a();$k[$j++]=Infinity;for(var _4c=0,_4d=$1.msglen+1;_4c<_4d;_4c++){$k[$j++]=false}$1.DatumC=$a();$k[$j++]=Infinity;for(var _4g=0,_4h=$1.msglen+8;_4g<_4h;_4g++){$k[$j++]=false}$1.Binary=$a();$k[$j++]=Infinity;for(var _4k=0,_4l=$1.msglen+1;_4k<_4l;_4k++){$k[$j++]=0}$1.AheadC=$a();$k[$j++]=Infinity;for(var _4o=0,_4p=$1.msglen+1;_4o<_4p;_4o++){$k[$j++]=0}$1.TryC=$a();$k[$j++]=Infinity;for(var _4s=0,_4t=$1.msglen+1;_4s<_4t;_4s++){$k[$j++]=0}$1.AheadA=$a();$k[$j++]=Infinity;for(var _4w=0,_4x=$1.msglen+1;_4w<_4x;_4w++){$k[$j++]=0}$1.AheadB=$a();$k[$j++]=Infinity;for(var _50=0,_51=$1.msglen+1;_50<_51;_50++){$k[$j++]=0}$1.UntilEndSeg=$a();for(var _54=$1.msglen-1;_54>=0;_54-=1){$1.i=_54;$1.barchar=$g($1.msg,$1.i);if($1.barchar>=48&&$1.barchar<=57){$p($1.nDigits,$1.i,$f($g($1.nDigits,$1.i+1)+1))}var _5H=$g($1.Avals,$1.barchar)!==undefined;if(_5H){$p($1.DatumA,$1.i,true)}var _5M=$g($1.Bvals,$1.barchar)!==undefined;if(_5M){$p($1.DatumB,$1.i,true)}$1.CRLF=false;if($1.barchar==13&&$1.i<$1.msglen-1){if($g($1.msg,$1.i+1)==10){$1.CRLF=true}}if($1.CRLF){$p($1.DatumB,$1.i,true)}if($g($1.nDigits,$1.i)>=2){$p($1.DatumC,$1.i,true)}if($1.barchar<0){$p($1.DatumC,$1.i,true)}if($1.barchar>=128){$p($1.Binary,$1.i,true)}if($g($1.nDigits,$1.i)>=10){$k[$j++]=Infinity;$q($G($1.msg,$1.i,10));for(var _5p=0,_5q=1;_5p<_5q;_5p++){if($k[--$j]!=48){$k[$j++]=false;break}if($k[--$j]!=49){$k[$j++]=false;break}$j-=6;if($k[--$j]!=55){$k[$j++]=false;break}if($k[--$j]!=49){$k[$j++]=false;break}$k[$j++]=true}$p($1.SeventeenTen,$1.i,$k[--$j]);$l()}if($g($1.nDigits,$1.i+1)>=6&&$g($1.msg,$1.i)==$1.fn2){$p($1.ECI,$1.i,true)}if($1.barchar<0&&$1.barchar!=$1.fn3){$p($1.AheadC,$1.i,$f($g($1.AheadC,$1.i+1)+1))}else{if($g($1.nDigits,$1.i)<=1){$p($1.AheadC,$1.i,0)}else{$p($1.AheadC,$1.i,$f($g($1.AheadC,$1.i+2)+1))}}if($g($1.nDigits,$1.i)>0&&$gt($g($1.AheadC,$1.i),$g($1.AheadC,$1.i+1))){$p($1.TryC,$1.i,$g($1.AheadC,$1.i))}if($g($1.DatumA,$1.i)&&$g($1.TryC,$1.i)<2&&$1.barchar!=$1.fn3){$p($1.AheadA,$1.i,$f($g($1.AheadA,$1.i+1)+1))}if($g($1.DatumB,$1.i)&&$g($1.TryC,$1.i)<2&&$1.barchar!=$1.fn3){$k[$j++]=$1.AheadB;$k[$j++]=$1.i;$k[$j++]=$1.AheadB;$k[$j++]=$1.i+1;if($1.CRLF){var _73=$k[--$j];$k[$j++]=$f(_73+1)}var _74=$k[--$j];var _76=$g($k[--$j],_74);var _77=$k[--$j];$p($k[--$j],_77,$f(_76+1))}if($1.barchar!=$1.fn3){$p($1.UntilEndSeg,$1.i,$f($g($1.UntilEndSeg,$1.i+1)+1))}}$1.A=0;$1.B=1;$1.C=2;$1.BIN=3;$1.addtocws=function(){var _7G=$k[--$j];$P($1.cws,$1.j,_7G);$1.j=_7G.length+$1.j};$1.base259to103=function(){$1.in=$k[--$j];$1.inlen=$1.in.length;$k[$j++]=Infinity;for(var _7N=0,_7O=5-$1.inlen;_7N<_7O;_7N++){$k[$j++]=0}$q($1.in);$1.in=$a();$1.out=$a(6);$k[$j++]=Infinity;$q($G($1.in,0,2));$1.msbs=$a();$k[$j++]=Infinity;$q($1.msbs);var _7W=$k[--$j];var _7X=$k[--$j];$k[$j++]=$f(_7W+_7X*259);for(var _7Y=0,_7Z=2;_7Y<_7Z;_7Y++){var _7a=$k[--$j];$k[$j++]=_7a%103;$k[$j++]=~~(_7a/103)}$1.mscs=$a();$k[$j++]=Infinity;$q($G($1.in,2,3));$1.lsbs=$a();$k[$j++]=Infinity;$q($1.lsbs);var _7g=$k[--$j];var _7h=$k[--$j];var _7i=$k[--$j];$k[$j++]=$f($f(_7g+_7h*259)+_7i*67081);for(var _7j=0,_7k=3;_7j<_7k;_7j++){var _7l=$k[--$j];$k[$j++]=_7l%103;$k[$j++]=~~(_7l/103)}$1.lscs=$a();var _7o=$g($1.lscs,0);var _7q=$g($1.mscs,0);$p($1.out,5,$f(_7o+_7q*42)%103);var _7t=$g($1.lscs,1);var _7v=$g($1.mscs,0);var _7x=$g($1.mscs,1);$p($1.out,4,$f($f($f(~~($f(_7o+_7q*42)/103)+_7t)+_7v*68)+_7x*42)%103);var _80=$g($1.lscs,2);var _82=$g($1.mscs,0);var _84=$g($1.mscs,1);var _86=$g($1.mscs,2);$p($1.out,3,$f($f($f($f(~~($f($f($f(~~($f(_7o+_7q*42)/103)+_7t)+_7v*68)+_7x*42)/103)+_80)+_82*92)+_84*68)+_86*42)%103);var _89=$g($1.lscs,3);var _8B=$g($1.mscs,0);var _8D=$g($1.mscs,1);var _8F=$g($1.mscs,2);$p($1.out,2,$f($f($f($f(~~($f($f($f($f(~~($f($f($f(~~($f(_7o+_7q*42)/103)+_7t)+_7v*68)+_7x*42)/103)+_80)+_82*92)+_84*68)+_86*42)/103)+_89)+_8B*15)+_8D*92)+_8F*68)%103);var _8I=$g($1.mscs,1);var _8K=$g($1.mscs,2);$p($1.out,1,$f($f(~~($f($f($f($f(~~($f($f($f($f(~~($f($f($f(~~($f(_7o+_7q*42)/103)+_7t)+_7v*68)+_7x*42)/103)+_80)+_82*92)+_84*68)+_86*42)/103)+_89)+_8B*15)+_8D*92)+_8F*68)/103)+_8I*15)+_8K*92)%103);$p($1.out,0,$f(~~($f($f(~~($f($f($f($f(~~($f($f($f($f(~~($f($f($f(~~($f(_7o+_7q*42)/103)+_7t)+_7v*68)+_7x*42)/103)+_80)+_82*92)+_84*68)+_86*42)/103)+_89)+_8B*15)+_8D*92)+_8F*68)/103)+_8I*15)+_8K*92)/103)+$g($1.mscs,2)*15)%103);$k[$j++]=$G($1.out,6-$1.inlen-1,$1.inlen+1)};$1.finaliseBIN=function(){if($1.bpos!=0){$k[$j++]=$G($1.bvals,0,$1.bpos);$1.base259to103();$q($k[--$j]);$1.bpos=0}};$1.addtobin=function(){$p($1.bvals,$1.bpos,$k[--$j]);$1.bpos=$1.bpos+1;if($1.bpos==5){$1.finaliseBIN()}};$1.ECIabc=function(){var _8f=$G($1.msg,$1.i+1,6);$k[$j++]=0;for(var _8g=0,_8h=_8f.length;_8g<_8h;_8g++){var _8j=$k[--$j];$k[$j++]=$f(_8j+$f($g(_8f,_8g)-48))*10}var _8l=~~($k[--$j]/10);$k[$j++]=_8l;if(_8l>=40){var _8n=$f($k[--$j]-40);var _8o=_8n%12769;$k[$j++]=~~(_8n/12769)+40;$k[$j++]=~~(_8o/113);$k[$j++]=_8o%113}};$1.ECIbin=function(){$k[$j++]=Infinity;var _8r=$G($1.msg,$1.i+1,6);$k[$j++]=0;for(var _8s=0,_8t=_8r.length;_8s<_8t;_8s++){var _8v=$k[--$j];$k[$j++]=$f(_8v+$f($g(_8r,_8s)-48))*10}var _8x=~~($k[--$j]/10);$k[$j++]=_8x;if(_8x>=65536){var _8y=$k[--$j];var _8z=_8y%65536;$k[$j++]=258;$k[$j++]=~~(_8y/65536);$k[$j++]=~~(_8z/256);$k[$j++]=_8z%256}else{var _90=$k[--$j];$k[$j++]=_90;if(_90>=256){var _91=$k[--$j];$k[$j++]=257;$k[$j++]=~~(_91/256);$k[$j++]=_91%256}else{var _92=$k[--$j];$k[$j++]=256;$k[$j++]=_92}}var _93=$a();for(var _94=0,_95=_93.length;_94<_95;_94++){$k[$j++]=$g(_93,_94);$1.addtobin()}};$1.encC=function(){for(var _97=0,_98=1;_97<_98;_97++){if($1.i==$1.segstart){for(var _9B=0,_9C=1;_9B<_9C;_9B++){if($1.i>$f($1.segend-7)){$k[$j++]=0;break}if($g($1.msg,$1.segstart)!=91){$k[$j++]=0;break}if($g($1.msg,$1.segstart+1)!=41){$k[$j++]=0;break}if($g($1.msg,$1.segstart+2)!=62){$k[$j++]=0;break}if($g($1.msg,$1.segstart+3)!=30){$k[$j++]=0;break}var _9T=$g($1.msg,$1.segstart+4);if(_9T<48||_9T>57){$k[$j++]=0;break}var _9W=$g($1.msg,$1.segstart+5);if(_9W<48||_9W>57){$k[$j++]=0;break}if($g($1.msg,$f($1.segend-1))!=4){$k[$j++]=0;break}var _9g=$f($f($g($1.msg,$1.segstart+4)-48)*10+$f($g($1.msg,$1.segstart+5)-48));$k[$j++]=_9g;if(_9g!=5&&(_9g!=6&&_9g!=12)){$j--;$k[$j++]=$1.mac;break}if($g($1.msg,$1.segstart+6)!=29){$j--;$k[$j++]=0;break}if($g($1.msg,$f($1.segend-2))!=30){$j--;$k[$j++]=0;break}var _9o=$k[--$j];$k[$j++]=_9o;if(_9o==5){$j--;$k[$j++]=$1.m05;break}if($k[--$j]==6){$k[$j++]=$1.m06;break}$k[$j++]=$1.m12;break}$1.inmac=$k[--$j];if($1.inmac!=0){$1.mode=$1.B;$k[$j++]=$a([$g($1.Bvals,$1.inmac)]);$1.addtocws();var _A3=$1.inmac!=$1.mac?7:6;$1.i=$1.i+_A3;break}}if($1.i==$1.segstart){if($g($1.nDigits,$1.i)>=2){$k[$j++]=$a([$g($1.Cvals,$1.fn1)]);$1.addtocws()}if($g($1.msg,$1.i)==$1.fn1&&$g($1.nDigits,$1.i+1)>=2){$1.i=$1.i+1}}if($g($1.SeventeenTen,$1.i)){$k[$j++]=$a([$g($1.Cvals,$1.aim),$f($f($g($1.msg,$1.i+2)-48)*10+$f($g($1.msg,$1.i+3)-48)),$f($f($g($1.msg,$1.i+4)-48)*10+$f($g($1.msg,$1.i+5)-48)),$f($f($g($1.msg,$1.i+6)-48)*10+$f($g($1.msg,$1.i+7)-48))]);$1.addtocws();$1.i=$1.i+10;break}if($g($1.DatumC,$1.i)){var _Aq=$g($1.msg,$1.i);if(_Aq==$1.fn1||(_Aq==$1.fn2||_Aq==$1.fn3)){$k[$j++]=$a([$g($1.Cvals,$g($1.msg,$1.i))]);$1.addtocws();if($g($1.ECI,$1.i)){$k[$j++]=Infinity;$1.ECIabc();var _B3=$a();$k[$j++]=_B3;$1.addtocws();$1.i=$1.i+7;break}if($g($1.msg,$1.i)==$1.fn3&&$1.segstart!=$1.i){$1.i=$1.i+1;$1.inmac=0;$1.segstart=$1.i;$1.segend=$f($1.i+$g($1.UntilEndSeg,$1.i));break}$1.i=$1.i+1;break}$k[$j++]=$a([$f($f($g($1.msg,$1.i)-48)*10+$f($g($1.msg,$1.i+1)-48))]);$1.addtocws();$1.i=$1.i+2;break}if($g($1.Binary,$1.i)){if($g($1.nDigits,$1.i+1)>0){if($g($1.msg,$1.i)<160){$k[$j++]=$a([$g($1.Cvals,$1.bsa),$g($1.Avals,$f($g($1.msg,$1.i)-128))]);$1.addtocws()}else{$k[$j++]=$a([$g($1.Cvals,$1.bsb),$g($1.Bvals,$f($g($1.msg,$1.i)-128))]);$1.addtocws()}$1.i=$1.i+1;break}$k[$j++]=$a([$g($1.Cvals,$1.bin)]);$1.addtocws();$1.mode=$1.BIN;break}$1.m=$g($1.AheadA,$1.i);$1.n=$g($1.AheadB,$1.i);if($gt($1.m,$1.n)){$k[$j++]=$a([$g($1.Cvals,$1.laa)]);$1.addtocws();$1.mode=$1.A;break}if($1.i==$1.segstart){var _CE=$g($1.msg,$1.i);if(_CE==9||(_CE==28||(_CE==29||_CE==30))){$k[$j++]=$a([$g($1.Cvals,$1.laa)]);$1.addtocws();$1.mode=$1.A;break}}if($1.n>4){$k[$j++]=$a([$g($1.Cvals,$1.lab)]);$1.addtocws();$1.mode=$1.B;break}$k[$j++]=$a([$g($1.Cvals,$g($a([$1.sfb,$1.sb2,$1.sb3,$1.sb4]),$f($1.n-1)))]);$1.addtocws();for(var _Cb=0,_Cc=$1.n;_Cb<_Cc;_Cb++){$k[$j++]=$a([$g($1.Bvals,$g($1.msg,$1.i))]);$1.addtocws();$1.i=$1.i+1}break}};$1.encB=function(){for(var _Ck=0,_Cl=1;_Ck<_Cl;_Ck++){$1.n=$g($1.TryC,$1.i);if($1.n>=2){if($1.n>4){$k[$j++]=$a([$g($1.Bvals,$1.lac)]);$1.addtocws();$1.mode=$1.C;break}$k[$j++]=$a([$g($1.Bvals,$g($a([$1.sfc,$1.sc2,$1.sc3,$1.sc4]),$f($1.n-1)))]);$1.addtocws();for(var _D7=0,_D8=$1.n;_D7<_D8;_D7++){if($g($1.msg,$1.i)<0){$k[$j++]=$a([$g($1.Cvals,$g($1.msg,$1.i))]);$1.addtocws();$1.i=$1.i+1}else{$k[$j++]=$a([$f($f($g($1.msg,$1.i)-48)*10+$f($g($1.msg,$1.i+1)-48))]);$1.addtocws();$1.i=$1.i+2}}break}if($g($1.DatumB,$1.i)){var _DW=$g($1.msg,$1.i);if(_DW==$1.fn1||(_DW==$1.fn2||_DW==$1.fn3)){$k[$j++]=$a([$g($1.Bvals,$g($1.msg,$1.i))]);$1.addtocws();if($g($1.ECI,$1.i)){$k[$j++]=Infinity;$1.ECIabc();var _Dj=$a();$k[$j++]=_Dj;$1.addtocws();$1.i=$1.i+7;break}if($g($1.msg,$1.i)==$1.fn3&&$1.i!=$1.segstart){$1.i=$1.i+1;$1.mode=$1.C;$1.inmac=0;$1.segstart=$1.i;$1.segend=$f($1.i+$g($1.UntilEndSeg,$1.i));break}$1.i=$1.i+1;break}if($g($1.msg,$1.i)==13&&$1.i<$1.msglen-1){if($g($1.msg,$1.i+1)==10){$k[$j++]=$a([$g($1.Bvals,$1.crl)]);$1.addtocws();$1.i=$1.i+2;break}}$k[$j++]=$a([$g($1.Bvals,$g($1.msg,$1.i))]);$1.addtocws();$1.i=$1.i+1;break}if($g($1.Binary,$1.i)){if($g($1.DatumB,$1.i+1)){if($g($1.msg,$1.i)<160){$k[$j++]=$a([$g($1.Bvals,$1.bsa),$g($1.Avals,$f($g($1.msg,$1.i)-128))]);$1.addtocws()}else{$k[$j++]=$a([$g($1.Bvals,$1.bsb),$g($1.Bvals,$f($g($1.msg,$1.i)-128))]);$1.addtocws()}$1.i=$1.i+1;break}$k[$j++]=$a([$g($1.Bvals,$1.bin)]);$1.addtocws();$1.mode=$1.BIN;break}if($g($1.AheadA,$1.i)==1){$k[$j++]=$a([$g($1.Bvals,$1.sfa),$g($1.Avals,$g($1.msg,$1.i))]);$1.addtocws();$1.i=$1.i+1;break}$k[$j++]=$a([$g($1.Bvals,$1.laa)]);$1.addtocws();$1.mode=$1.A;break}};$1.encA=function(){for(var _F8=0,_F9=1;_F8<_F9;_F8++){$1.n=$g($1.TryC,$1.i);if($1.n>=2){if($1.n>4){$k[$j++]=$a([$g($1.Avals,$1.lac)]);$1.addtocws();$1.mode=$1.C;break}$k[$j++]=$a([$g($1.Avals,$g($a([$1.sfc,$1.sc2,$1.sc3,$1.sc4]),$f($1.n-1)))]);$1.addtocws();for(var _FV=0,_FW=$1.n;_FV<_FW;_FV++){if($g($1.msg,$1.i)<0){$k[$j++]=$a([$g($1.Cvals,$g($1.msg,$1.i))]);$1.addtocws();$1.i=$1.i+1}else{$k[$j++]=$a([$f($f($g($1.msg,$1.i)-48)*10+$f($g($1.msg,$1.i+1)-48))]);$1.addtocws();$1.i=$1.i+2}}break}if($g($1.DatumA,$1.i)){var _Fu=$g($1.msg,$1.i);if(_Fu==$1.fn1||(_Fu==$1.fn2||_Fu==$1.fn3)){$k[$j++]=$a([$g($1.Avals,$g($1.msg,$1.i))]);$1.addtocws();if($g($1.ECI,$1.i)){$k[$j++]=Infinity;$1.ECIabc();var _G7=$a();$k[$j++]=_G7;$1.addtocws();$1.i=$1.i+7;break}if($g($1.msg,$1.i)==$1.fn3&&$1.i!=$1.segstart){$1.i=$1.i+1;$1.mode=$1.C;$1.inmac=0;$1.segstart=$1.i;$1.segend=$f($1.i+$g($1.UntilEndSeg,$1.i));break}$1.i=$1.i+1;break}$k[$j++]=$a([$g($1.Avals,$g($1.msg,$1.i))]);$1.addtocws();$1.i=$1.i+1;break}if($g($1.Binary,$1.i)){if($g($1.DatumA,$1.i+1)){if($g($1.msg,$1.i)<160){$k[$j++]=$a([$g($1.Avals,$1.bsa),$g($1.Avals,$f($g($1.msg,$1.i)-128))]);$1.addtocws()}else{$k[$j++]=$a([$g($1.Avals,$1.bsb),$g($1.Bvals,$f($g($1.msg,$1.i)-128))]);$1.addtocws()}$1.i=$1.i+1;break}$k[$j++]=$a([$g($1.Avals,$1.bin)]);$1.addtocws();$1.mode=$1.BIN;break}$1.n=$g($1.AheadB,$1.i);if($1.n>6){$k[$j++]=$a([$g($1.Avals,$1.lab)]);$1.addtocws();$1.mode=$1.B;break}$k[$j++]=$a([$g($1.Avals,$g($a([$1.sfb,$1.sb2,$1.sb3,$1.sb4,$1.sb5,$1.sb6]),$f($1.n-1)))]);$1.addtocws();for(var _HN=0,_HO=$1.n;_HN<_HO;_HN++){$k[$j++]=$a([$g($1.Bvals,$g($1.msg,$1.i))]);$1.addtocws();$1.i=$1.i+1}break}};$1.encBIN=function(){for(var _HW=0,_HX=1;_HW<_HX;_HW++){$1.n=$g($1.TryC,$1.i);if($1.n>=2){$k[$j++]=Infinity;$1.finaliseBIN();var _Hc=$a();$k[$j++]=_Hc;$1.addtocws();if($1.n>7){var _Hi=$k[--$j];$1.mode=$1.C;$k[$j++]=$f(_Hi+$a([$g($1.BINvals,$1.tmc)]));break}$k[$j++]=$a([$g($1.BINvals,$g($a([$1.sc2,$1.sc3,$1.sc4,$1.sc5,$1.sc6,$1.sc7]),$f($1.n-2)))]);$1.addtocws();for(var _Hx=0,_Hy=$1.n;_Hx<_Hy;_Hx++){if($g($1.msg,$1.i)<0){$k[$j++]=$a([$g($1.Cvals,$g($1.msg,$1.i))]);$1.addtocws();$1.i=$1.i+1}else{$k[$j++]=$a([$f($f($g($1.msg,$1.i)-48)*10+$f($g($1.msg,$1.i+1)-48))]);$1.addtocws();$1.i=$1.i+2}}break}if($g($1.ECI,$1.i)&&$g($1.Binary,$1.i+7)){$k[$j++]=Infinity;$1.ECIbin();var _IN=$a();$k[$j++]=_IN;$1.addtocws();$1.i=$1.i+7;if($1.i==$1.msglen){$k[$j++]=Infinity;$1.finaliseBIN();var _IR=$a();$k[$j++]=_IR;$1.addtocws()}break}if($g($1.msg,$1.i)>=0){if($g($1.Binary,$1.i)||$g($1.Binary,$1.i+1)||$g($1.Binary,$1.i+2)||$g($1.Binary,$1.i+3)||$g($1.ECI,$1.i+1)&&$g($1.Binary,$1.i+8)){$k[$j++]=Infinity;$k[$j++]=$g($1.msg,$1.i);$1.addtobin();var _Iq=$a();$k[$j++]=_Iq;$1.addtocws();$1.i=$1.i+1;if($1.i==$1.msglen){$k[$j++]=Infinity;$1.finaliseBIN();var _Iu=$a();$k[$j++]=_Iu;$1.addtocws()}break}}$k[$j++]=Infinity;$1.finaliseBIN();var _Iv=$a();$k[$j++]=_Iv;$1.addtocws();if($1.i!=$1.msglen-1){if($g($1.msg,$1.i)==$1.fn3&&$1.i!=$1.segstart){$k[$j++]=$a([$g($1.BINvals,$1.tms)]);$1.addtocws();$1.i=$1.i+1;$1.mode=$1.C;$1.inmac=0;$1.segstart=$1.i;$1.segend=$f($1.i+$g($1.UntilEndSeg,$1.i));break}if($gt($g($1.AheadA,$1.i),$g($1.AheadB,$1.i))){$k[$j++]=$a([$g($1.BINvals,$1.tma)]);$1.addtocws();$1.mode=$1.A}else{$k[$j++]=$a([$g($1.BINvals,$1.tmb)]);$1.addtocws();$1.mode=$1.B}break}break}};$1.cws=$a($1.msglen*2+1);$1.mode=$1.C;$1.bvals=$a(5);$1.bpos=0;$1.inmac=0;$1.segstart=0;$1.segend=$g($1.UntilEndSeg,0);$1.i=0;$1.j=0;for(;;){if($1.i>=$1.msglen){break}if($1.inmac!=0){if($1.inmac!=$1.mac&&$1.i==$f($1.segend-2)){$1.i=$1.i+2;if($1.i>=$1.msglen){break}}if($1.inmac==$1.mac&&$1.i==$f($1.segend-1)){$1.i=$1.i+1;if($1.i>=$1.msglen){break}}}if($1[$g($a(["encA","encB","encC","encBIN"]),$1.mode)]()===true){break}}$1.cws=$G($1.cws,0,$1.j);$1.nd=$1.cws.length;$1.minarea=(($1.nd+3+~~($1.nd/2))*9+2)*2;if($1.ratio!=-1){$1.hgt=Math.sqrt($1.minarea/$1.ratio);$1.wid=Math.sqrt($1.minarea*$1.ratio);$1.h=~~$1.hgt;$1.w=~~$1.wid;if(($1.h+$1.w)%2==1){if($1.h*$1.w<$1.minarea){$1.h=$1.h+1;$1.w=$1.w+1}}else{if($1.hgt*$1.w<$1.wid*$1.h){$1.w=$1.w+1;if($1.h*$1.w<$1.minarea){$1.w=$1.w-1;$1.h=$1.h+1;if($1.h*$1.w<$1.minarea){$1.w=$1.w+2}}}else{$1.h=$1.h+1;if($1.h*$1.w<$1.minarea){$1.h=$1.h-1;$1.w=$1.w+1;if($1.h*$1.w<$1.minarea){$1.h=$1.h+2}}}}$1.rows=$1.h;$1.columns=$1.w}else{if($1.columns==-1){var _Kk=~~(($1.minarea+$1.rows-1)/$1.rows);$k[$j++]="columns";$k[$j++]=_Kk;if((_Kk+$1.rows)%2==0){var _Km=$k[--$j];$k[$j++]=$f(_Km+1)}var _Kn=$k[--$j];$1[$k[--$j]]=_Kn}if($1.rows==-1){var _Kt=~~(($1.minarea+$1.columns-1)/$1.columns);$k[$j++]="rows";$k[$j++]=_Kt;if((_Kt+$1.columns)%2==0){var _Kv=$k[--$j];$k[$j++]=$f(_Kv+1)}var _Kw=$k[--$j];$1[$k[--$j]]=_Kw}}$1.ndots=~~($1.rows*$1.columns/2);for(;;){var _L1=$1.nd+1;if((_L1+(~~(_L1/2)+3))*9+2>$1.ndots){break}$1.nd=$1.nd+1}$1.nc=~~($1.nd/2)+3;$1.nw=$1.nd+$1.nc;$1.rembits=$1.ndots-($1.nw*9+2);if($1.nd>$1.cws.length){$k[$j++]=Infinity;$q($1.cws);var _LE=$1.mode==$1.BIN?109:106;$k[$j++]=_LE;for(var _LH=0,_LI=$1.nd-$1.cws.length-1;_LH<_LI;_LH++){$k[$j++]=106}$1.cws=$a()}$1.encs=$a(["101010101","010101011","010101101","010110101","011010101","101010110","101011010","101101010","110101010","010101110","010110110","010111010","011010110","011011010","011101010","100101011","100101101","100110101","101001011","101001101","101010011","101011001","101100101","101101001","110010101","110100101","110101001","001010111","001011011","001011101","001101011","001101101","001110101","010010111","010011011","010011101","010100111","010110011","010111001","011001011","011001101","011010011","011011001","011100101","011101001","100101110","100110110","100111010","101001110","101011100","101100110","101101100","101110010","101110100","110010110","110011010","110100110","110101100","110110010","110110100","111001010","111010010","111010100","001011110","001101110","001110110","001111010","010011110","010111100","011001110","011011100","011100110","011101100","011110010","011110100","100010111","100011011","100011101","100100111","100110011","100111001","101000111","101100011","101110001","110001011","110001101","110010011","110011001","110100011","110110001","111000101","111001001","111010001","000101111","000110111","000111011","000111101","001001111","001100111","001110011","001111001","010001111","011000111","011100011","011110001","100011110","100111100","101111000","110001110","110011100","110111000","111000110","111001100"]);if($1.rows%2==0){$1.sixedges=$a([$a([$1.columns-1,$1.rows-2]),$a([0,$1.rows-2]),$a([$1.columns-2,$1.rows-1]),$a([1,$1.rows-1]),$a([$1.columns-1,0]),$a([0,0])])}else{$1.sixedges=$a([$a([$1.columns-2,0]),$a([$1.columns-2,$1.rows-1]),$a([$1.columns-1,1]),$a([$1.columns-1,$1.rows-2]),$a([0,0]),$a([0,$1.rows-1])])}$1.dmv=function(){var _Lp=$k[--$j];var _Lq=$k[--$j];$k[$j++]=$f(_Lq+_Lp*$1.columns)};$1.outline=$a($1.rows*$1.columns);for(var _Lw=0,_Lv=$1.rows-1;_Lw<=_Lv;_Lw+=1){$1.y=_Lw;for(var _Lz=0,_Ly=$1.columns-1;_Lz<=_Ly;_Lz+=1){$1.x=_Lz;$k[$j++]=$1.outline;$k[$j++]=$1.x;$k[$j++]=$1.y;$1.dmv();var _M5=$k[--$j];$p($k[--$j],_M5,($1.x+$1.y)%2-1)}}var _M7=$1.sixedges;for(var _M8=0,_M9=_M7.length;_M8<_M9;_M8++){$k[$j++]=$1.outline;$q($g(_M7,_M8));$1.dmv();var _MC=$k[--$j];$p($k[--$j],_MC,1)}$1.clearcol=function(){$1.x=$k[--$j];$k[$j++]=true;for(var _MI=$1.x&1,_MH=$1.rows-1;_MI<=_MH;_MI+=2){$k[$j++]=$1.x;$k[$j++]=_MI;$1.dmv();if($g($1.sym,$k[--$j])==1){$j--;$k[$j++]=false;break}}};$1.clearrow=function(){$1.y=$k[--$j];$k[$j++]=true;for(var _MR=$1.y&1,_MQ=$1.columns-1;_MR<=_MQ;_MR+=2){$k[$j++]=_MR;$k[$j++]=$1.y;$1.dmv();if($g($1.sym,$k[--$j])==1){$j--;$k[$j++]=false;break}}};$1.evalsymbol=function(){$1.sym=$k[--$j];$1.worst=9999999;var _Mb=$a([$a(["x",0]),$a(["x",1]),$a(["y",0]),$a(["y",1])]);for(var _Mc=0,_Md=_Mb.length;_Mc<_Md;_Mc++){$q($g(_Mb,_Mc));$1.fl=$k[--$j];$1.dir=$k[--$j];$1.sum=0;$1.first=-1;$1.last=-1;var _Mi=$eq($1.dir,"x")?$1.columns:$1.rows;for(var _Mk=0,_Mj=_Mi-1;_Mk<=_Mj;_Mk+=1){$1[$1.dir]=_Mk;var _Mm=$1.sym;var _Mo=$1[$1.dir];var _Mq=$eq($1.dir,"x")?$1.rows:$1.columns;var _Mt=(_Mq-1)*$1.fl;if($eq($1.dir,"y")){var _=_Mt;_Mt=_Mo;_Mo=_}$k[$j++]=_Mm;$k[$j++]=_Mo;$k[$j++]=_Mt;$1.dmv();var _Mu=$k[--$j];if($g($k[--$j],_Mu)==1){if($1.first==-1){$1.first=$1[$1.dir]}$1.last=$1[$1.dir];$1.sum=$1.sum+1}}var _N7=$eq($1.dir,"x")?$1.rows:$1.columns;var _N8=$f($f($1.sum+$1.last)-$1.first)*_N7;$k[$j++]=_N8;if(_N8<$1.worst){$1.worst=$k[--$j]}else{$j--}}$1.pen=0;if($1.rows%2==1||$1.rows<=12){$1.sum=0;$1.p=0;for(var _NF=1,_NE=$1.columns-2;_NF<=_NE;_NF+=1){$k[$j++]=_NF;$1.clearcol();if($k[--$j]){$1.sum=$1.sum+1;$k[$j++]="p";if($1.sum==1){$k[$j++]=$1.rows}else{$k[$j++]=$1.p*$1.rows}var _NM=$k[--$j];$1[$k[--$j]]=_NM}else{$1.sum=0;$1.pen=$1.pen+$1.p;$1.p=0}}$1.pen=$1.pen+$1.p}if($1.rows%2==0||$1.columns<=12){$1.sum=0;$1.p=0;for(var _NW=1,_NV=$1.rows-2;_NW<=_NV;_NW+=1){$k[$j++]=_NW;$1.clearrow();if($k[--$j]){$1.sum=$1.sum+1;$k[$j++]="p";if($1.sum==1){$k[$j++]=$1.columns}else{$k[$j++]=$1.p*$1.columns}var _Nd=$k[--$j];$1[$k[--$j]]=_Nd}else{$1.sum=0;$1.pen=$1.pen+$1.p;$1.p=0}}$1.pen=$1.pen+$1.p}$k[$j++]=Infinity;for(var _Nk=0,_Nl=($1.columns+4)*2;_Nk<_Nl;_Nk++){$k[$j++]=0}for(var _Np=0,_Nq=$1.columns,_No=$1.sym.length-1;_Nq<0?_Np>=_No:_Np<=_No;_Np+=_Nq){$k[$j++]=0;$k[$j++]=0;$q($G($1.sym,_Np,$1.columns));$k[$j++]=0;$k[$j++]=0}for(var _Nv=0,_Nw=($1.columns+4)*2;_Nv<_Nw;_Nv++){$k[$j++]=0}$1.symp=$a();$1.columns=$1.columns+4;$1.rows=$1.rows+4;$1.sum=0;for(var _O2=2,_O1=$1.rows-3;_O2<=_O1;_O2+=1){$1.y=_O2;for(var _O6=($1.y&1)+2,_O5=$1.columns-3;_O6<=_O5;_O6+=2){$1.x=_O6;for(var _O7=0,_O8=1;_O7<_O8;_O7++){$k[$j++]=$1.symp;$k[$j++]=$1.x-1;$k[$j++]=$1.y-1;$1.dmv();var _OC=$k[--$j];if($g($k[--$j],_OC)==1){break}$k[$j++]=$1.symp;$k[$j++]=$1.x+1;$k[$j++]=$1.y-1;$1.dmv();var _OI=$k[--$j];if($g($k[--$j],_OI)==1){break}$k[$j++]=$1.symp;$k[$j++]=$1.x-1;$k[$j++]=$1.y+1;$1.dmv();var _OO=$k[--$j];if($g($k[--$j],_OO)==1){break}$k[$j++]=$1.symp;$k[$j++]=$1.x+1;$k[$j++]=$1.y+1;$1.dmv();var _OU=$k[--$j];if($g($k[--$j],_OU)==1){break}$k[$j++]=$1.symp;$k[$j++]=$1.x;$k[$j++]=$1.y;$1.dmv();var _Oa=$k[--$j];if($g($k[--$j],_Oa)==0){$1.sum=$1.sum+1;break}$k[$j++]=$1.symp;$k[$j++]=$1.x-2;$k[$j++]=$1.y;$1.dmv();var _Oh=$k[--$j];if($g($k[--$j],_Oh)==1){break}$k[$j++]=$1.symp;$k[$j++]=$1.x;$k[$j++]=$1.y-2;$1.dmv();var _On=$k[--$j];if($g($k[--$j],_On)==1){break}$k[$j++]=$1.symp;$k[$j++]=$1.x+2;$k[$j++]=$1.y;$1.dmv();var _Ot=$k[--$j];if($g($k[--$j],_Ot)==1){break}$k[$j++]=$1.symp;$k[$j++]=$1.x;$k[$j++]=$1.y+2;$1.dmv();var _Oz=$k[--$j];if($g($k[--$j],_Oz)==1){break}$1.sum=$1.sum+1}}}$1.columns=$1.columns-4;$1.rows=$1.rows-4;if($1.worst==0){$k[$j++]=-99999}else{$k[$j++]=$f($f($1.worst-$1.sum*$1.sum)-$1.pen)}};$k[$j++]=Infinity;$k[$j++]=1;for(var _PA=0,_PB=112;_PA<_PB;_PA++){var _PC=$k[--$j];$k[$j++]=_PC;$k[$j++]=_PC*3%113}$1.rsalog=$a();$1.step=~~($1.nw/112)+1;$1.offset=function(){var _PG=$k[--$j];$k[$j++]=$f(_PG*$1.step+$1.start)};$1.bestscore=-99999999;$k[$j++]="masks";if($1.mask!=-1){$k[$j++]=$a([$1.mask])}else{if($1.fast){$k[$j++]=$a([3,2,1,0])}else{$k[$j++]=$a([0,1,2,3])}}var _PO=$k[--$j];$1[$k[--$j]]=_PO;$1.litmasks=$a(4);$F($1.masks,function(){$1.mask=$k[--$j];$k[$j++]=Infinity;var _PX=$1.cws;$k[$j++]=$1.mask;$k[$j++]=$g($a([0,3,7,17]),$1.mask);$k[$j++]=0;for(var _PY=0,_PZ=_PX.length;_PY<_PZ;_PY++){var _Pb=$k[--$j];var _Pc=$k[--$j];$k[$j++]=$f($g(_PX,_PY)+_Pb)%113;$k[$j++]=_Pc;$k[$j++]=$f(_Pb+_Pc)}$j-=2;for(var _Pe=0,_Pf=$1.nc;_Pe<_Pf;_Pe++){$k[$j++]=0}$1.rscws=$a();for(var _Pj=0,_Pi=$1.step-1;_Pj<=_Pi;_Pj+=1){$1.start=_Pj;$1.ND=~~(($1.nd+1-$1.start+$1.step-1)/$1.step);$1.NW=~~(($1.nw+1-$1.start+$1.step-1)/$1.step);$1.NC=$1.NW-$1.ND;$k[$j++]=Infinity;$k[$j++]=1;for(var _Pv=0,_Pw=$1.NC;_Pv<_Pw;_Pv++){$k[$j++]=0}$1.coeffs=$a();for(var _Q0=1,_Pz=$1.NC;_Q0<=_Pz;_Q0+=1){$1.i=_Q0;for(var _Q2=$1.NC;_Q2>=1;_Q2-=1){$1.j=_Q2;$p($1.coeffs,$1.j,$f($f($g($1.coeffs,$1.j)+113)-$g($1.rsalog,$1.i)*$g($1.coeffs,$1.j-1)%113)%113)}}for(var _QG=0,_QF=$1.ND-1;_QG<=_QF;_QG+=1){$k[$j++]="t";$k[$j++]=$1.rscws;$k[$j++]=_QG;$1.offset();var _QI=$k[--$j];var _QK=$g($k[--$j],_QI);$k[$j++]=_QK;$k[$j++]=$1.rscws;$k[$j++]=$1.ND;$1.offset();var _QN=$k[--$j];var _QP=$g($k[--$j],_QN);var _QQ=$k[--$j];$1[$k[--$j]]=$f(_QQ+_QP)%113;for(var _QU=0,_QT=$1.NC-2;_QU<=_QT;_QU+=1){$1.j=_QU;$k[$j++]=$1.rscws;$k[$j++]=$1.ND+$1.j;$1.offset();$k[$j++]=$1.rscws;$k[$j++]=$1.ND+$1.j+1;$1.offset();var _Qb=$k[--$j];var _Qd=$g($k[--$j],_Qb);var _Qi=$k[--$j];$p($k[--$j],_Qi,$f($f(_Qd+113)-$1.t*$g($1.coeffs,$1.j+1)%113)%113)}$k[$j++]=$1.rscws;$k[$j++]=$1.ND+$1.NC-1;$1.offset();var _Qr=$k[--$j];$p($k[--$j],_Qr,$f(113-$1.t*$g($1.coeffs,$1.NC)%113)%113)}for(var _Qw=$1.ND,_Qv=$1.NW-1;_Qw<=_Qv;_Qw+=1){$k[$j++]=_Qw;$k[$j++]=$1.rscws;$k[$j++]=_Qw;$1.offset();var _Qz=$k[--$j];var _R0=$k[--$j];var _R1=$k[--$j];$k[$j++]=_R0;$k[$j++]=_Qz;$k[$j++]=113;$k[$j++]=$1.rscws;$k[$j++]=_R1;$1.offset();var _R2=$k[--$j];var _R4=$g($k[--$j],_R2);var _R5=$k[--$j];var _R6=$k[--$j];$p($k[--$j],_R6,$f(_R5-_R4)%113)}}$1.bits=$s($1.ndots);$P($1.bits,0,$g($a(["00","01","10","11"]),$1.mask));for(var _RG=1,_RF=$1.nw;_RG<=_RF;_RG+=1){$1.i=_RG;$P($1.bits,($1.i-1)*9+2,$g($1.encs,$g($1.rscws,$1.i)))}if($1.rembits>0){$P($1.bits,$1.nw*9+2,$G("11111111111111111",0,$1.rembits))}var _RT=$1.outline;$1.pixs=$A($a(_RT.length),_RT);$1.posx=0;$k[$j++]="posy";if($1.rows%2==0){$k[$j++]=0}else{$k[$j++]=$1.rows-1}var _RY=$k[--$j];$1[$k[--$j]]=_RY;var _Rc=$G($1.bits,0,$1.bits.length-6);for(var _Rd=0,_Re=_Rc.length;_Rd<_Re;_Rd++){$k[$j++]=$g(_Rc,_Rd);for(;;){$k[$j++]=$1.pixs;$k[$j++]=$1.posx;$k[$j++]=$1.posy;$1.dmv();var _Rj=$k[--$j];if($g($k[--$j],_Rj)==-1){break}if($1.rows%2==0){$1.posy=$1.posy+1;if($1.posy==$1.rows){$1.posy=0;$1.posx=$1.posx+1}}else{$1.posx=$1.posx+1;if($1.posx==$1.columns){$1.posx=0;$1.posy=$1.posy-1}}}$k[$j++]=$1.pixs;$k[$j++]=$1.posx;$k[$j++]=$1.posy;$1.dmv();var _Ry=$k[--$j];var _Rz=$k[--$j];$p(_Rz,_Ry,$f($k[--$j]-48))}for(var _S1=0;_S1<=5;_S1+=1){$1.i=_S1;$k[$j++]=$1.pixs;$q($g($1.sixedges,$1.i));$1.dmv();var _SA=$k[--$j];$p($k[--$j],_SA,$g($1.bits,$1.bits.length-6+$1.i)-48)}$k[$j++]="score";$k[$j++]=$1.pixs;$1.evalsymbol();var _SD=$k[--$j];$1[$k[--$j]]=_SD;if($1.score>$1.bestscore){$1.bestsym=$1.pixs;$1.bestscore=$1.score;if($1.fast&&$1.bestscore>~~($1.rows*$1.columns/2)){return true}}var _SN=$1.pixs;$1.litmask=$A($a(_SN.length),_SN);for(var _SQ=0;_SQ<=5;_SQ+=1){$1.i=_SQ;$k[$j++]=$1.litmask;$q($g($1.sixedges,$1.i));$1.dmv();var _SV=$k[--$j];$p($k[--$j],_SV,1)}$p($1.litmasks,$1.mask,$1.litmask);if($1.fast){$k[$j++]="score";$k[$j++]=$1.litmask;$1.evalsymbol();var _Sc=$k[--$j];$1[$k[--$j]]=_Sc;if($gt($1.score,$1.bestscore)){$1.bestsym=$1.litmask;$1.bestscore=$1.score;if($1.bestscore>~~($1.rows*$1.columns/2)){return true}}}});$1.pixs=$1.bestsym;if(!$1.fast&&$1.bestscore<=~~($1.rows*$1.columns/2)){$1.bestscore=-99999999;$F($1.masks,function(){$1.litmask=$g($1.litmasks,$k[--$j]);$k[$j++]="score";$k[$j++]=$1.litmask;$1.evalsymbol();var _Sv=$k[--$j];$1[$k[--$j]]=_Sv;if($1.score>$1.bestscore){$1.bestsym=$1.litmask;$1.bestscore=$1.score}});$1.pixs=$1.bestsym}var _T8=new Map([["ren",bwipp_renmatrix],["dotty",true],["pixs",$1.pixs],["pixx",$1.columns],["pixy",$1.rows],["height",$1.rows*2/72],["width",$1.columns*2/72],["opt",$1.options]]);$k[$j++]=_T8;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_ultracode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.eclevel="EC2";$1.parse=false;$1.parsefnc=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.fn1=-1;$1.fn3=-2;var _9=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["FNC1",$1.fn1],["FNC3",$1.fn3]]);$1.fncvals=_9;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _C=$k[--$j];$1[$k[--$j]]=_C;$1.msglen=$1.msg.length;$1.start=257;$1.scr=$a([]);$1.link1=0;$1.scp=$a([]);$k[$j++]=Infinity;$q($1.scr);$q($1.scp);$F($1.msg,function(){var _K=$k[--$j];$k[$j++]=_K;if(_K==$1.fn1){$j--;$k[$j++]=268}var _M=$k[--$j];$k[$j++]=_M;if(_M==$1.fn3){$j--;$k[$j++]=269}});$1.dcws=$a();$1.mcc=$1.dcws.length+3;$1.eclval=$g($1.eclevel,2)-48;if($1.eclval!=0){$k[$j++]="qcc";$k[$j++]=$g($a([0,1,2,4,6,8]),$1.eclval);$k[$j++]=~~($1.mcc/25);if($1.mcc%25!=0){var _Y=$k[--$j];$k[$j++]=$f(_Y+1)}var _Z=$k[--$j];var _a=$k[--$j];$1[$k[--$j]]=$f(_a*_Z+5)}else{$1.qcc=3}$1.acc=$1.qcc-3+78*$1.link1;$1.tcc=$1.mcc+$1.qcc;$1.metrics=$a([$a([2,4,34,5]),$a([3,33,82,13]),$a([4,82,158,23]),$a([5,134,282,30])]);for(var _l=0;_l<=3;_l+=1){$1.i=_l;$1.m=$g($1.metrics,$1.i);$1.rows=$g($1.m,0);$1.minc=$g($1.m,1);$1.maxc=$g($1.m,2);$1.mcol=$g($1.m,3);$1.okay=true;if($1.tcc<$1.minc||$1.tcc>$1.maxc){$1.okay=false}if($1.okay){break}}if(!$1.okay){$k[$j++]="bwipp.ultracodeNoValidSymbol";$k[$j++]="No valid symbol available";bwipp_raiseerror()}for(var _14=$1.mcol;_14<=61;_14+=1){$1.columns=_14;$k[$j++]=$1.columns;if($1.columns>=15){var _17=$k[--$j];$k[$j++]=$f(_17-1)}if($1.columns>=31){var _19=$k[--$j];$k[$j++]=$f(_19-1)}if($1.columns>=47){var _1B=$k[--$j];$k[$j++]=$f(_1B-1)}var _1F=$f($f($k[--$j]*$1.rows-3)-$1.tcc);$k[$j++]=_1F;if(_1F>=0){$1.pads=$k[--$j];break}$j--}$1.dcc=$f($1.columns-$1.mcol);$k[$j++]=Infinity;$k[$j++]=$1.start;$k[$j++]=$1.mcc;$k[$j++]=$1.acc;$q($1.scr);$q($1.dcws);for(var _1P=0,_1Q=$1.qcc;_1P<_1Q;_1P++){$k[$j++]=0}$k[$j++]=0;$1.rsseq=$a();$k[$j++]=Infinity;$k[$j++]=1;for(var _1S=0,_1T=282;_1S<_1T;_1S++){var _1U=$k[--$j];$k[$j++]=_1U;$k[$j++]=_1U*3%283}$1.rsalog=$a();$1.rslog=$a(283);for(var _1X=1;_1X<=282;_1X+=1){$p($1.rslog,$g($1.rsalog,_1X),_1X)}$1.rsprod=function(){var _1b=$k[--$j];var _1c=$k[--$j];$k[$j++]=_1c;$k[$j++]=_1b;if(_1b!=0&&_1c!=0){var _1f=$g($1.rslog,$k[--$j]);var _1k=$g($1.rsalog,$f(_1f+$g($1.rslog,$k[--$j]))%282);$k[$j++]=_1k}else{$j-=2;$k[$j++]=0}};$1.n=$1.mcc;$1.k=$1.qcc;$k[$j++]=Infinity;$k[$j++]=1;for(var _1o=0,_1p=$1.k;_1o<_1p;_1o++){$k[$j++]=0}$1.coeffs=$a();for(var _1t=1,_1s=$1.k;_1t<=_1s;_1t+=1){$1.i=_1t;$p($1.coeffs,$1.i,$g($1.coeffs,$1.i-1));for(var _20=$1.i-1;_20>=1;_20-=1){$1.j=_20;$k[$j++]=$1.coeffs;$k[$j++]=$1.j;$k[$j++]=$g($1.coeffs,$1.j-1);$k[$j++]=$g($1.coeffs,$1.j);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _2C=$k[--$j];var _2D=$k[--$j];var _2E=$k[--$j];$p($k[--$j],_2E,$f(_2D+_2C)%283)}$k[$j++]=$1.coeffs;$k[$j++]=0;$k[$j++]=$g($1.coeffs,0);$k[$j++]=$g($1.rsalog,$1.i);$1.rsprod();var _2M=$k[--$j];var _2N=$k[--$j];$p($k[--$j],_2N,_2M)}$1.coeffs=$G($1.coeffs,0,$1.coeffs.length-1);for(var _2T=$1.coeffs.length-1;_2T>=0;_2T-=2){var _2U=$1.coeffs;$p(_2U,_2T,$f(283-$g(_2U,_2T)))}for(var _2Y=0,_2X=$1.n-1;_2Y<=_2X;_2Y+=1){$1.t=$f($g($1.rsseq,_2Y)+$g($1.rsseq,$1.n))%283;for(var _2g=0,_2f=$1.k-1;_2g<=_2f;_2g+=1){$1.j=_2g;$p($1.rsseq,$1.n+$1.j,$f($g($1.rsseq,$1.n+$1.j+1)+$f(283-$1.t*$g($1.coeffs,$1.k-$1.j-1)%283))%283)}}for(var _2x=$1.n,_2w=$1.n+$1.k;_2x<=_2w;_2x+=1){$p($1.rsseq,_2x,$f(283-$g($1.rsseq,_2x))%283)}$1.ecws=$G($1.rsseq,$1.n,$1.k);$1.dccu=$a([51363,51563,51653,53153,53163,53513,53563,53613,53653,56153,56163,56313,56353,56363,56513,56563,51316,51356,51536,51616,53156,53516,53536,53616,53636,53656,56136,56156,56316,56356,56516,56536]);$1.dccl=$a([61351,61361,61531,61561,61631,61651,63131,63151,63161,63531,63561,63631,65131,65161,65351,65631,31351,31361,31531,31561,31631,31651,35131,35151,35161,35361,35631,35651,36131,36151,36351,36531]);$1.tiles=$a([13135,13136,13153,13156,13163,13165,13513,13515,13516,13531,13535,13536,13561,13563,13565,13613,13615,13616,13631,13635,13636,13651,13653,13656,15135,15136,15153,15163,15165,15313,15315,15316,15351,15353,15356,15361,15363,15365,15613,15615,15616,15631,15635,15636,15651,15653,15656,16135,16136,16153,16156,16165,16313,16315,16316,16351,16353,16356,16361,16363,16365,16513,16515,16516,16531,16535,16536,16561,16563,16565,31315,31316,31351,31356,31361,31365,31513,31515,31516,31531,31535,31536,31561,31563,31565,31613,31615,31631,31635,31636,31651,31653,31656,35131,35135,35136,35151,35153,35156,35161,35163,35165,35315,35316,35351,35356,35361,35365,35613,35615,35616,35631,35635,35636,35651,35653,35656,36131,36135,36136,36151,36153,36156,36163,36165,36315,36316,36351,36356,36361,36365,36513,36515,36516,36531,36535,36536,36561,36563,36565,51313,51315,51316,51351,51353,51356,51361,51363,51365,51513,51516,51531,51536,51561,51563,51613,51615,51616,51631,51635,51636,51651,51653,51656,53131,53135,53136,53151,53153,53156,53161,53163,53165,53513,53516,53531,53536,53561,53563,53613,53615,53616,53631,53635,53636,53651,53653,53656,56131,56135,56136,56151,56153,56156,56161,56163,56165,56313,56315,56316,56351,56353,56356,56361,56363,56365,56513,56516,56531,56536,56561,56563,61313,61315,61316,61351,61353,61356,61361,61363,61365,61513,61515,61516,61531,61535,61536,61561,61563,61565,61615,61631,61635,61651,61653,63131,63135,63136,63151,63153,63156,63161,63163,63165,63513,63515,63516,63531,63535,63536,63561,63563,63565,63613,63615,63631,63635,63651,63653,65131,65135,65136,65151,65153,65156,65161,65163,65165,65313,65315,65316,65351,65353,65356,65361,65363,65365,65613,65615,65631,65635,65651,65653,56565,51515]);$1.rows=$f($1.rows*6+1);$1.columns=$1.columns+6;$k[$j++]=Infinity;for(var _3C=0,_3D=$1.rows*$1.columns;_3C<_3D;_3C++){$k[$j++]=-1}$1.pixs=$a();$1.qmv=function(){var _3G=$k[--$j];var _3H=$k[--$j];$k[$j++]=$f(_3H+_3G*$1.columns)};for(var _3K=0,_3J=$1.columns-1;_3K<=_3J;_3K+=1){$1.i=_3K;for(var _3N=0,_3M=$f($1.rows-1);_3N<=_3M;_3N+=6){$1.j=_3N;if($1.i>=5){$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=$1.j;$1.qmv();var _3T=$k[--$j];$p($k[--$j],_3T,$1.i%2*9)}}$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=0;$1.qmv();var _3X=$k[--$j];$p($k[--$j],_3X,9);$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=$f($1.rows-1);$1.qmv();var _3c=$k[--$j];$p($k[--$j],_3c,9)}for(var _3g=1,_3f=$f($1.rows-2);_3g<=_3f;_3g+=1){$1.i=_3g;for(var _3j=3,_3i=$1.columns-1;_3j<=_3i;_3j+=16){$1.j=_3j;$k[$j++]=$1.pixs;$k[$j++]=$1.j;$k[$j++]=$1.i;$1.qmv();var _3o=$k[--$j];$p($k[--$j],_3o,(1-$1.i%2)*9)}$k[$j++]=$1.pixs;$k[$j++]=0;$k[$j++]=$1.i;$1.qmv();var _3s=$k[--$j];$p($k[--$j],_3s,9);$k[$j++]=$1.pixs;$k[$j++]=1;$k[$j++]=$1.i;$1.qmv();var _3x=$k[--$j];$p($k[--$j],_3x,(1-$1.i%2)*9);$k[$j++]=$1.pixs;$k[$j++]=2;$k[$j++]=$1.i;$1.qmv();var _41=$k[--$j];$p($k[--$j],_41,0);$k[$j++]=$1.pixs;$k[$j++]=3;$k[$j++]=$1.i;$1.qmv();var _45=$k[--$j];$p($k[--$j],_45,9);$k[$j++]=$1.pixs;$k[$j++]=4;$k[$j++]=$1.i;$1.qmv();var _49=$k[--$j];$p($k[--$j],_49,0);$k[$j++]=$1.pixs;$k[$j++]=$1.columns-1;$k[$j++]=$1.i;$1.qmv();var _4E=$k[--$j];$p($k[--$j],_4E,9)}$1.i=~~($1.rows/2)-5;$k[$j++]=Infinity;var _4L=$R($s(5),$g($1.dccu,$1.dcc),10);for(var _4M=0,_4N=_4L.length;_4M<_4N;_4M++){$k[$j++]=$g(_4L,_4M)-48}var _4T=$R($s(5),$g($1.dccl,$1.dcc),10);$k[$j++]=0;for(var _4U=0,_4V=_4T.length;_4U<_4V;_4U++){$k[$j++]=$g(_4T,_4U)-48}var _4X=$a();for(var _4Y=0,_4Z=_4X.length;_4Y<_4Z;_4Y++){$k[$j++]=$g(_4X,_4Y);$k[$j++]=$1.pixs;$k[$j++]=2;$k[$j++]=$1.i;$1.qmv();var _4d=$k[--$j];var _4e=$k[--$j];$p(_4e,_4d,$k[--$j]);$1.i=$1.i+1}$k[$j++]=Infinity;$k[$j++]=$1.start;$k[$j++]=$1.mcc;$q($1.ecws);$k[$j++]=$1.tcc;$k[$j++]=283;$k[$j++]=$1.acc;$q($1.scr);$q($1.dcws);for(var _4p=0,_4q=$1.pads;_4p<_4q;_4p++){$k[$j++]=284}$k[$j++]=$1.qcc;$1.tileseq=$a();$1.x=5;$1.y=1;var _4t=$1.tileseq;for(var _4u=0,_4v=_4t.length;_4u<_4v;_4u++){var _50=$R($s(5),$g($1.tiles,$g(_4t,_4u)),10);for(var _51=0,_52=_50.length;_51<_52;_51++){$k[$j++]=$g(_50,_51);$k[$j++]=$1.pixs;$k[$j++]=$1.x;$k[$j++]=$1.y;$1.qmv();var _57=$k[--$j];var _58=$k[--$j];$p(_58,_57,$f($k[--$j]-48));$1.y=$1.y+1}if($1.y!=$f($1.rows-1)){$1.y=$1.y+1}else{$1.x=$1.x+1;$1.y=1;$k[$j++]=$1.pixs;$k[$j++]=$1.x;$k[$j++]=$1.y;$1.qmv();var _5I=$k[--$j];if($g($k[--$j],_5I)!=-1){$1.x=$1.x+1}}}var _5R=new Map([[0,"00000000"],[9,"000000FF"],[1,"FF000000"],[3,"00FF0000"],[5,"0000FF00"],[6,"7F00FF00"]]);var _5T=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",$1.columns],["pixy",$1.rows],["height",$1.rows/72*2],["width",$1.columns/72*2],["colormap",_5R],["opt",$1.options]]);$k[$j++]=_5T;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_jabcode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.rows=-1;$1.columns=-1;$1.slave=false;$1.colors=16;$1.eclevel=6;$1.raw=false;$1.parse=false;$1.parsefnc=false;$1.mask=-1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.rows=~~$1.rows;$1.columns=~~$1.columns;$1.colors=~~$1.colors;$1.eclevel=~~$1.eclevel;$1.mask=~~$1.mask;$1.databpm=~~Math.round(Math.log($1.colors)/Math.log(2));var _B=$1.colors;$k[$j++]="metabpm";$k[$j++]=_B;if(_B>8){$j--;$k[$j++]=8}var _C=$k[--$j];$1[$k[--$j]]=~~Math.round(Math.log(_C)/Math.log(2));$1.fn1=-1;var _H=new Map([["parse",$1.parse],["parsefnc",$1.parsefnc],["FNC1",$1.fn1]]);$1.fncvals=_H;$k[$j++]="msg";$k[$j++]=$1.barcode;$k[$j++]=$1.fncvals;bwipp_parseinput();var _K=$k[--$j];$1[$k[--$j]]=_K;$1.msglen=$1.msg.length;$1.addtobits=function(){var _N=$k[--$j];$P($1.bits,$1.j,_N);$1.j=_N.length+$1.j};$1.tofixedbits=function(){var _S=$Z($s(13),"0000000000000");var _T=$k[--$j];var _W=$R($s(13),$k[--$j],2);$P(_S,$f(_T-_W.length),_W);$k[$j++]=$G(_S,0,_T)};var _Z=$1.msglen<=15?4:17;$1.bits=$s(7+_Z+$1.msglen*8+12);$1.j=0;$k[$j++]=31;$k[$j++]=5;$1.tofixedbits();$1.addtobits();$k[$j++]="00";$1.addtobits();if($1.msglen<=15){$k[$j++]=$1.msglen;$k[$j++]=4;$1.tofixedbits();$1.addtobits()}else{$k[$j++]="0000";$1.addtobits();$k[$j++]=$1.msglen-16;$k[$j++]=13;$1.tofixedbits();$1.addtobits()}for(var _h=0,_g=$1.msglen-1;_h<=_g;_h+=1){$k[$j++]=$g($1.msg,_h);$k[$j++]=8;$1.tofixedbits();$1.addtobits()}$k[$j++]=28;$k[$j++]=5;$1.tofixedbits();$1.addtobits();$k[$j++]=31;$k[$j++]=5;$1.tofixedbits();$1.addtobits();$k[$j++]="11";$1.addtobits();$1.diffside=21;$1.sameshape=false;$1.sameecc=false;$1.hasslaves=false;$1.metrics=function(){$1.mc=$k[--$j];$1.mr=$k[--$j];var _m=$1.mc;var _n=$1.mr;var _o=$1.mc;var _p=$1.mr;var _q=$1.colors;var _r=64;if(64>_q){var _=_q;_q=_r;_r=_}var _t=$1.slave?7:17;$k[$j++]=_m*_n;$k[$j++]=((~~($f(_o-25)/16)+2)*(~~($f(_p-25)/16)+2)-4)*7;$k[$j++]=_r*2;$k[$j++]=_t*4;if(!$1.slave){var _x=$eq($1.mr,$1.mc)?0:1;$1.metass=_x;var _y=$1.mr;var _z=$1.mc;if($lt(_y,_z)){var _=_z;_z=_y;_y=_}$1.metavf=$g($a([0,1,2,2,3,3,3,3]),~~($f(_y-21)/16));$k[$j++]="metavlen";if($1.metass==0){$k[$j++]=$a([2,2,3,4])}else{$k[$j++]=$a([4,6,8,10])}var _17=$g($k[--$j],$1.metavf);$1[$k[--$j]]=_17;$1.metaelen=$f($1.metavf*2+10);$k[$j++]=$f($f(7+$1.metavlen)+$1.metaelen);if($1.hasslaves){var _1D=$k[--$j];$k[$j++]=$f(_1D+4)}var _1G=~~Math.ceil($k[--$j]*2/$1.metabpm);$1.nummetabits=$f(_1G*$1.metabpm+6);$k[$j++]=_1G+6}else{$k[$j++]=3;if(!$1.sameshape){var _1J=$k[--$j];$k[$j++]=$f(_1J+5)}if($1.hasslaves){var _1L=$k[--$j];$k[$j++]=$f(_1L+3)}if(!$1.sameecc){var _1N=$1.mr;var _1O=$1.mc;if($lt(_1N,_1O)){var _=_1O;_1O=_1N;_1N=_}var _1R=$k[--$j];$k[$j++]=$f(_1R+$g($a([10,12,14,14,16,16,16,16]),~~($f(_1N-21)/16)))}var _1U=~~Math.ceil($k[--$j]*2/$1.metabpm);$1.nummetabits=_1U*$1.metabpm;$k[$j++]=_1U}var _1W=$k[--$j];$1.nummetamodules=_1W;var _1X=$k[--$j];var _1Y=$k[--$j];var _1Z=$k[--$j];$1.numdatamodules=$f($k[--$j]-$f(_1Z+$f(_1Y+$f(_1X+_1W))));$1.numdatabits=$1.numdatamodules*$1.databpm};$1.coderate=$g($a([.67,.63,.57,.55,.5,.43,.34,.25,.2,.17,.14]),$1.eclevel);$1.grosslen=~~Math.ceil($1.bits.length/$1.coderate);$1.snapsize=function(){var _1j=~~Math.ceil($k[--$j])-18;$k[$j++]=_1j;if(_1j<0){$j--;$k[$j++]=0}var _1k=$k[--$j];$k[$j++]=~~(_1k/4)*4+21};if($1.rows==-1&&$1.columns==-1){$k[$j++]="size";$k[$j++]=Math.sqrt($1.grosslen);$1.snapsize();var _1o=$k[--$j];$1[$k[--$j]]=_1o;for(;;){$k[$j++]=$1.size;$k[$j++]=$1.size;$1.metrics();if($1.grosslen<=$1.numdatabits){break}$1.size=$f($1.size+4)}$1.rows=$1.size;$1.columns=$1.size}else{if($1.columns==-1&&$1.rows!=-1){$k[$j++]="columns";$k[$j++]=$1.grosslen/$1.rows;$1.snapsize();var _21=$k[--$j];$1[$k[--$j]]=_21;for(;;){$k[$j++]=$1.rows;$k[$j++]=$1.columns;$1.metrics();if($1.grosslen<=$1.numdatabits){break}$1.columns=$f($1.columns+4)}}if($1.rows==-1&&$1.columns!=-1){$k[$j++]="rows";$k[$j++]=$1.grosslen/$1.columns;$1.snapsize();var _2C=$k[--$j];$1[$k[--$j]]=_2C;for(;;){$k[$j++]=$1.rows;$k[$j++]=$1.columns;$1.metrics();if($1.grosslen<=$1.numdatabits){break}$1.rows=$f($1.rows+4)}}if($1.rows!=-1&&$1.columns!=-1){$k[$j++]=$1.rows;$k[$j++]=$1.columns;$1.metrics()}}$1.C=$1.numdatabits;$1.cols=$1.columns;if($1.rows>145||$1.cols>145){$k[$j++]="bwipp.jabcodeNoValidSymbol";$k[$j++]="No valid symbol available";bwipp_raiseerror()}$1.min=$1.C;for(var _2S=3;_2S<=8;_2S+=1){$1.i=_2S;for(var _2U=$1.i+1;_2U<=9;_2U+=1){$1.j=_2U;$1.dist=~~($1.C/$1.j)*$1.j-~~($1.C/$1.j)*$1.i-$1.bits.length;if($1.dist<$1.min&&$1.dist>=0){$1.datawc=$1.i;$1.datawr=$1.j;$1.min=$1.dist}}}$1.tmpbits=$s(~~($1.C/$1.datawr)*$1.datawr-~~($1.C/$1.datawr)*$1.datawc);$P($1.tmpbits,0,$1.bits);for(var _2u=$1.bits.length,_2t=$1.tmpbits.length-1;_2u<=_2t;_2u+=1){var _2w=$g($1.tmpbits,_2u-1);$k[$j++]=_2u;$k[$j++]=_2w;if(_2w%2==0){var _2x=$k[--$j];$k[$j++]=$f(_2x+1)}else{var _2y=$k[--$j];$k[$j++]=$f(_2y-1)}var _30=$k[--$j];$p($1.tmpbits,$k[--$j],_30)}$1.bits=$1.tmpbits;$1.m0=22609+1;$1.m1=62509-65536;$1.m2=19605;$1.m3=32557;$1.lcg64_temper=function(){$1.p00=$1.m0*$1.s0;$1.p01=$1.m0*$1.s1;$1.p02=$1.m0*$1.s2;$1.p03=$1.m0*$1.s3;$1.p10=$1.m1*$1.s0;$1.p11=$1.m1*$1.s1;$1.p12=$1.m1*$1.s2;$1.p13=$1.m1*$1.s3;$1.p20=$1.m2*$1.s0;$1.p21=$1.m2*$1.s1;$1.p22=$1.m2*$1.s2;$1.p23=$1.m2*$1.s3;$1.p30=$1.m3*$1.s0;$1.p31=$1.m3*$1.s1;$1.p32=$1.m3*$1.s2;$1.p33=$1.m3*$1.s3;$1.s3=$1.p33%65536+1;$1.s2=$1.p32%65536+~~($1.p33/65536)+$1.p23%65536;$1.s1=$1.p31%65536+~~($1.p32/65536)+($1.p22%65536+~~($1.p23/65536))+$1.p13%65536;$1.s0=$1.p30%65536+~~($1.p31/65536)+($1.p21%65536+~~($1.p22/65536))+($1.p12%65536+~~($1.p13/65536))+$1.p03%65536;$1.s3=$1.s3+65536;$1.s2=$1.s2+65535;$1.s1=$1.s1+65535;$1.s0=$1.s0+65535;$1.s2=~~($1.s3/65536)+$1.s2;$1.s3=$1.s3%65536;$1.s1=~~($1.s2/65536)+$1.s1;$1.s2=$1.s2%65536;$1.s0=~~($1.s1/65536)+$1.s0;$1.s1=$1.s1%65536;$1.s0=$1.s0%65536;var _45=(($1.s0-32768)*65536+$1.s1^2147483648)&4294967295;var _46=_45^_45>>>11;var _47=_46^_46<<7&-1658038656;var _48=(_47^_47<<15&-272236544)&4294967295;$k[$j++]=_48^_48>>>18};$1.createMatrixA=function(){$k[$j++]="nb_pcb";if($1.wr<4){$k[$j++]=~~($1.Pg_sub_block/2)}else{$k[$j++]=~~($1.Pg_sub_block/$1.wr)*$1.wc}var _4E=$k[--$j];$1[$k[--$j]]=_4E;$1.offset=~~Math.ceil($1.Pg_sub_block/32);$1.effwidth=$1.offset*32;$1.matrixA=$a($1.offset*$1.nb_pcb);for(var _4O=0,_4N=$1.offset*$1.nb_pcb-1;_4O<=_4N;_4O+=1){$p($1.matrixA,_4O,0)}$1.permutation=$a($1.Pg_sub_block);for(var _4U=0,_4T=$1.Pg_sub_block-1;_4U<=_4T;_4U+=1){$p($1.permutation,_4U,_4U)}for(var _4Z=0,_4Y=~~($1.Pg_sub_block/$1.wr)-1;_4Z<=_4Y;_4Z+=1){$1.i=_4Z;for(var _4c=0,_4b=$f($1.wr-1);_4c<=_4b;_4c+=1){$1.j=_4c;var _4d=$1.matrixA;var _4e=$1.i;var _4f=$1.effwidth;var _4g=$1.wr;var _4h=$1.j;var _4n=$f(31-$f($1.i*$f($1.effwidth+$1.wr)+$1.j)%32);$p(_4d,~~($f(_4e*$f(_4f+_4g)+_4h)/32),$g(_4d,~~($f(_4e*$f(_4f+_4g)+_4h)/32))|(_4n<0?1>>>-_4n:1<<_4n))}}$1.s0=0;$1.s1=0;$1.s2=11;$1.s3=64569;for(var _4q=1,_4p=$f($1.wc-1);_4q<=_4p;_4q+=1){$1.i=_4q;$1.off_index=~~($1.Pg_sub_block/$1.wr)*$1.i;for(var _4w=0,_4v=$1.Pg_sub_block-1;_4w<=_4v;_4w+=1){$1.j=_4w;$1.lcg64_temper();var _4x=$k[--$j];$k[$j++]=_4x;if(_4x<0){var _4y=$k[--$j];$k[$j++]=$f((_4y^2147483648)+2147483648)}$1.pos=~~($k[--$j]/4294967296*($1.Pg_sub_block-$1.j));for(var _55=0,_54=~~($1.Pg_sub_block/$1.wr)-1;_55<=_54;_55+=1){$1.k=_55;var _56=$1.matrixA;var _57=$1.off_index;var _58=$1.k;var _59=$1.offset;var _5A=$1.j;var _5I=$g($1.matrixA,~~($g($1.permutation,$1.pos)/32)+$1.k*$1.offset);var _5M=-$f(31-$g($1.permutation,$1.pos)%32);var _5O=31-$1.j%32;$p(_56,(_57+_58)*_59+~~(_5A/32),$g(_56,(_57+_58)*_59+~~(_5A/32))|(_5O<0?((_5M<0?_5I>>>-_5M:_5I<<_5M)&1)>>>-_5O:((_5M<0?_5I>>>-_5M:_5I<<_5M)&1)<<_5O))}var _5R=$1.permutation;var _5S=$1.Pg_sub_block;var _5T=$1.j;$p(_5R,_5S-1-_5T,$g($1.permutation,$1.pos));$p($1.permutation,$1.pos,$g(_5R,_5S-1-_5T))}}};$1.createMetadataMatrixA=function(){$1.nb_pcb=~~($1.Pg_sub_block/2);$1.offset=~~Math.ceil($1.Pg_sub_block/32);$1.matrixA=$a($1.offset*$1.nb_pcb);for(var _5g=0,_5f=$1.offset*$1.nb_pcb-1;_5g<=_5f;_5g+=1){$p($1.matrixA,_5g,0)}$1.permutation=$a($1.Pg_sub_block);for(var _5m=0,_5l=$1.Pg_sub_block-1;_5m<=_5l;_5m+=1){$p($1.permutation,_5m,_5m)}$1.s0=0;$1.s1=0;$1.s2=0;$1.s3=38545;$1.nb_once=~~(~~$f($1.nb_pcb/$1.wc*$1.Pg_sub_block+3)/$1.nb_pcb);for(var _5u=0,_5t=$1.nb_pcb-1;_5u<=_5t;_5u+=1){$1.i=_5u;for(var _5x=0,_5w=$1.nb_once-1;_5x<=_5w;_5x+=1){$1.j=_5x;$1.lcg64_temper();var _5y=$k[--$j];$k[$j++]=_5y;if(_5y<0){var _5z=$k[--$j];$k[$j++]=$f((_5z^2147483648)+2147483648)}$1.pos=~~($k[--$j]/4294967296*($1.Pg_sub_block-$1.j));var _63=$1.matrixA;var _64=$1.i;var _65=$1.offset;var _68=$g($1.permutation,$1.pos);var _6D=$f(31-$g($1.permutation,$1.pos)%32);$p(_63,_64*_65+~~(_68/32),$g(_63,_64*_65+~~(_68/32))|(_6D<0?1>>>-_6D:1<<_6D));var _6G=$1.permutation;var _6H=$1.Pg_sub_block;var _6I=$1.j;$p(_6G,_6H-1-_6I,$g($1.permutation,$1.pos));$p($1.permutation,$1.pos,$g(_6G,_6H-1-_6I))}}};$1.GaussJordan=function(){$k[$j++]="nb_pcb";if($1.wr<4){$k[$j++]=~~($1.Pg_sub_block/2)}else{$k[$j++]=~~($1.Pg_sub_block/$1.wr)*$1.wc}var _6S=$k[--$j];$1[$k[--$j]]=_6S;$1.offset=~~Math.ceil($1.Pg_sub_block/32);var _6V=$1.matrixA;$1.matrixH=$A($a(_6V.length),_6V);$k[$j++]=Infinity;for(var _6Z=0,_6a=$1.Pg_sub_block;_6Z<_6a;_6Z++){$k[$j++]=0}$1.column_arrangement=$a();$k[$j++]=Infinity;for(var _6d=0,_6e=$1.Pg_sub_block;_6d<_6e;_6d++){$k[$j++]=false}$1.processed_column=$a();$k[$j++]=Infinity;for(var _6h=0,_6i=$1.nb_pcb;_6h<_6i;_6h++){$k[$j++]=0}$1.zero_lines_nb=$a();$k[$j++]=Infinity;for(var _6l=0,_6m=$1.Pg_sub_block*2;_6l<_6m;_6l++){$k[$j++]=0}$1.swap_col=$a();$1.zero_lines=0;$1.loop0=0;for(var _6q=0,_6p=$1.nb_pcb-1;_6q<=_6p;_6q+=1){$1.i=_6q;$1.pivot_column=$1.Pg_sub_block+1;for(var _6u=0,_6t=$1.Pg_sub_block-1;_6u<=_6t;_6u+=1){$1.j=_6u;var _6z=$g($1.matrixH,~~(($1.offset*32*$1.i+$1.j)/32));var _73=-(31-($1.offset*32*$1.i+$1.j)%32);if(((_73<0?_6z>>>-_73:_6z<<_73)&1)==1){$1.pivot_column=$1.j;break}}if($1.pivot_column<$1.Pg_sub_block){$p($1.processed_column,$1.pivot_column,true);$p($1.column_arrangement,$1.pivot_column,$1.i);if($1.pivot_column>=$1.nb_pcb){$p($1.swap_col,$1.loop0*2,$1.pivot_column);$1.loop0=$1.loop0+1}$1.off_index=~~($1.pivot_column/32);$1.off_index1=$1.pivot_column%32;for(var _7M=0,_7L=$1.nb_pcb-1;_7M<=_7L;_7M+=1){$1.j=_7M;if($1.i!=$1.j){var _7T=$g($1.matrixH,$1.off_index+$1.j*$1.offset);var _7V=-(31-$1.off_index1);if(((_7V<0?_7T>>>-_7V:_7T<<_7V)&1)==1){for(var _7Y=0,_7X=$1.offset-1;_7Y<=_7X;_7Y+=1){$1.k=_7Y;var _7Z=$1.matrixH;var _7a=$1.offset;var _7b=$1.j;var _7c=$1.k;$p(_7Z,_7a*_7b+_7c,$xo($g(_7Z,_7a*_7b+_7c),$g($1.matrixH,$1.offset*$1.i+$1.k)))}}}}}else{$p($1.zero_lines_nb,$1.zero_lines,$1.i);$1.zero_lines=$1.zero_lines+1}}$1.matrix_rank=$1.nb_pcb-$1.zero_lines;$1.loop2=0;for(var _7s=$1.matrix_rank,_7r=$1.nb_pcb-1;_7s<=_7r;_7s+=1){$1.i=_7s;if($g($1.column_arrangement,$1.i)>0){for(var _7y=0,_7x=$1.nb_pcb-1;_7y<=_7x;_7y+=1){$1.j=_7y;if($nt($g($1.processed_column,$1.j))){$p($1.column_arrangement,$1.j,$g($1.column_arrangement,$1.i));$p($1.column_arrangement,$1.i,0);$p($1.processed_column,$1.j,true);$p($1.processed_column,$1.i,false);$p($1.swap_col,$1.loop0*2,$1.i);$p($1.swap_col,$1.loop0*2+1,$1.j);$p($1.column_arrangement,$1.i,$1.j);$1.loop0=$1.loop0+1;$1.loop2=$1.loop2+1;break}}}}$1.loop1=0;for(var _8Q=0,_8P=$1.nb_pcb-1;_8Q<=_8P;_8Q+=1){$1.kl=_8Q;if($nt($g($1.processed_column,$1.kl))&&$1.loop1<$1.loop0-$1.loop2){$p($1.column_arrangement,$1.kl,$g($1.column_arrangement,$g($1.swap_col,$1.loop1*2)));$p($1.processed_column,$1.kl,true);$p($1.swap_col,$1.loop1*2+1,$1.kl);$1.loop1=$1.loop1+1}}$1.loop1=0;for(var _8m=0,_8l=$1.nb_pcb-1;_8m<=_8l;_8m+=1){$1.kl=_8m;if($nt($g($1.processed_column,$1.kl))){$p($1.column_arrangement,$1.kl,$g($1.zero_lines_nb,$1.loop1));$1.loop1=$1.loop1+1}}for(var _8y=0,_8x=$1.nb_pcb-1;_8y<=_8x;_8y+=1){$1.i=_8y;for(var _91=0,_90=$1.offset-1;_91<=_90;_91+=1){$1.j=_91;$p($1.matrixA,$1.i*$1.offset+$1.j,$g($1.matrixH,$f($g($1.column_arrangement,$1.i)*$1.offset+$1.j)))}}$1.tmp=0;for(var _9F=0,_9E=$1.loop0-1;_9F<=_9E;_9F+=1){$1.i=_9F;for(var _9I=0,_9H=$1.nb_pcb-1;_9I<=_9H;_9I+=1){$1.j=_9I;var _9P=$g($1.matrixA,~~($g($1.swap_col,$1.i*2)/32)+$1.j*$1.offset);var _9T=-$f(31-$g($1.swap_col,$1.i*2)%32);$1.tmp=(-((_9T<0?_9P>>>-_9T:_9P<<_9T)&1)^$1.tmp)&1^$1.tmp;var _9W=$1.matrixA;var _9Z=$g($1.swap_col,$1.i*2);var _9a=$1.j;var _9b=$1.offset;var _9j=$g($1.matrixA,~~($g($1.swap_col,$1.i*2+1)/32)+$1.j*$1.offset);var _9n=-$f(31-$g($1.swap_col,$1.i*2+1)%32);var _9y=$f(31-$g($1.swap_col,$1.i*2)%32);$p(_9W,~~(_9Z/32)+_9a*_9b,$g(_9W,~~(_9Z/32)+_9a*_9b)^(-((_9n<0?_9j>>>-_9n:_9j<<_9n)&1)^$g($1.matrixA,~~($g($1.swap_col,$1.i*2)/32)+$1.j*$1.offset))&(_9y<0?1>>>-_9y:1<<_9y));var _9z=$1.matrixA;var _A2=$g($1.swap_col,$1.i*2+1);var _A3=$1.j;var _A4=$1.offset;var _AH=$f(31-$g($1.swap_col,$1.i*2+1)%32);$p(_9z,~~(_A2/32)+_A3*_A4,$g(_9z,~~(_A2/32)+_A3*_A4)^(-($1.tmp&1)^$g($1.matrixA,~~($g($1.swap_col,$1.i*2+1)/32)+$1.j*$1.offset))&(_AH<0?1>>>-_AH:1<<_AH))}}};$1.createGeneratorMatrix=function(){$1.pn=$1.Pg_sub_block-$1.matrix_rank;$1.offset=~~Math.ceil($1.pn/32);$1.effwidth=$1.offset*32;$1.offset_cap=~~Math.ceil($1.Pg_sub_block/32);$1.G=$a($1.offset*$1.Pg_sub_block);for(var _AT=0,_AS=$1.offset*$1.Pg_sub_block-1;_AT<=_AS;_AT+=1){$p($1.G,_AT,0)}for(var _AX=0,_AW=$1.pn-1;_AX<=_AW;_AX+=1){$1.i=_AX;var _AY=$1.G;var _AZ=$1.Pg_sub_block;var _Aa=$1.pn;var _Ab=$1.i;var _Ac=$1.offset;var _Ad=$1.i;var _Ag=31-$1.i%32;$p(_AY,(_AZ-_Aa+_Ab)*_Ac+~~(_Ad/32),$g(_AY,(_AZ-_Aa+_Ab)*_Ac+~~(_Ad/32))|(_Ag<0?1>>>-_Ag:1<<_Ag))}$1.matrix_index=$1.Pg_sub_block-$1.pn;$1.loop0=0;for(var _An=0,_Am=($1.Pg_sub_block-$1.pn)*$1.effwidth-1;_An<=_Am;_An+=1){$1.i=_An;if($1.matrix_index>=$1.Pg_sub_block){$1.loop0=$1.loop0+1;$1.matrix_index=$1.Pg_sub_block-$1.pn}if($1.i%$1.effwidth<$1.pn){var _Aw=$1.G;var _Ax=$1.i;var _B3=$g($1.matrixA,~~($1.matrix_index/32)+$1.offset_cap*$1.loop0);var _B5=-(31-$1.matrix_index%32);var _BA=31-$1.i%32;$p(_Aw,~~(_Ax/32),$g(_Aw,~~(_Ax/32))^(-((_B5<0?_B3>>>-_B5:_B3<<_B5)&1)^$g($1.G,~~($1.i/32)))&(_BA<0?1>>>-_BA:1<<_BA));$1.matrix_index=$1.matrix_index+1}}};$1.ldpc=function(){$1.wr=$k[--$j];$1.wc=$k[--$j];$k[$j++]=Infinity;var _BE=$k[--$j];var _BF=$k[--$j];$k[$j++]=_BE;$F(_BF,function(){var _BG=$k[--$j];$k[$j++]=$f(_BG-48)});$1.data=$a();$1.Pn=$1.data.length;if($1.wr!=-1){$1.Pg=~~(Math.ceil(Math.ceil($1.Pn*$1.wr/$f($1.wr-$1.wc))/$1.wr)*$1.wr);$1.nb_sub_blocks=~~($1.Pg/2700)+1;$1.Pg_sub_block=~~(~~($1.Pg/$1.nb_sub_blocks)/$1.wr)*$1.wr;$1.Pn_sub_block=~~($1.Pg_sub_block*$f($1.wr-$1.wc)/$1.wr);$1.nb_sub_blocks=~~($1.Pg/$1.Pg_sub_block);$k[$j++]="encoding_iterations";$k[$j++]=~~($1.Pg/$1.Pg_sub_block);if($1.Pn_sub_block*$1.nb_sub_blocks<$1.Pn){var _Bg=$k[--$j];$k[$j++]=$f(_Bg-1)}var _Bh=$k[--$j];$1[$k[--$j]]=_Bh;$1.createMatrixA()}else{$1.Pg=$1.Pn*2;$1.nb_sub_blocks=1;$1.Pg_sub_block=$1.Pg;$1.Pn_sub_block=$1.Pn;$1.encoding_iterations=1;$1.createMetadataMatrixA()}$1.GaussJordan();$1.createGeneratorMatrix();$1.ecc_encoded_data=$a($1.Pg);$1.offset=~~Math.ceil(($1.Pg_sub_block-$1.matrix_rank)/32);for(var _Bs=0,_Br=$1.encoding_iterations-1;_Bs<=_Br;_Bs+=1){$1.iter=_Bs;for(var _Bv=0,_Bu=$1.Pg_sub_block-1;_Bv<=_Bu;_Bv+=1){$1.i=_Bv;$1.temp=0;$1.loop0=0;$1.offset_index=$1.offset*$1.i;for(var _C3=$1.iter*$1.Pn_sub_block,_C2=($1.iter+1)*$1.Pn_sub_block-1;_C3<=_C2;_C3+=1){var _C9=$g($1.G,$1.offset_index+~~($1.loop0/32));var _CB=-(31-$1.loop0%32);$1.temp=$g($1.data,_C3)&((_CB<0?_C9>>>-_CB:_C9<<_CB)&1)^$1.temp;$1.loop0=$1.loop0+1}$p($1.ecc_encoded_data,$1.i+$1.iter*$1.Pg_sub_block,$1.temp)}}if($1.encoding_iterations!=$1.nb_sub_blocks){$1.start=$1.encoding_iterations*$1.Pn_sub_block;$1.last_index=$1.encoding_iterations*$1.Pg_sub_block;$1.Pg_sub_block=$1.Pg-$1.encoding_iterations*$1.Pg_sub_block;$1.Pn_sub_block=~~($1.Pg_sub_block*$f($1.wr-$1.wc)/$1.wr);$1.createMatrixA();$1.GaussJordan();$1.createGeneratorMatrix();$1.offset=~~Math.ceil(($1.Pg_sub_block-$1.matrix_rank)/32);for(var _Ca=0,_CZ=$1.Pg_sub_block-1;_Ca<=_CZ;_Ca+=1){$1.i=_Ca;$1.temp=0;$1.loop0=0;$1.offset_index=$1.offset*$1.i;for(var _Cg=$1.start,_Cf=$1.Pn-1;_Cg<=_Cf;_Cg+=1){var _Cm=$g($1.G,$1.offset_index+~~($1.loop0/32));var _Co=-(31-$1.loop0%32);$1.temp=$g($1.data,_Cg)&((_Co<0?_Cm>>>-_Co:_Cm<<_Co)&1)^$1.temp;$1.loop0=$1.loop0+1}$p($1.ecc_encoded_data,$1.i+$1.last_index,$1.temp)}}$1.out=$s($1.Pg);for(var _Cz=0,_Cy=$1.Pg-1;_Cz<=_Cy;_Cz+=1){$p($1.out,_Cz,$f($g($1.ecc_encoded_data,_Cz)+48))}$k[$j++]=$1.out};$k[$j++]="bits";$k[$j++]=$1.bits;$k[$j++]=$1.datawc;$k[$j++]=$1.datawr;$1.ldpc();var _D7=$k[--$j];$1[$k[--$j]]=_D7;$1.s0=0;$1.s1=0;$1.s2=3;$1.s3=30151;for(var _DA=$1.bits.length-1;_DA>=1;_DA-=1){$1.l=_DA;$1.lcg64_temper();var _DB=$k[--$j];$k[$j++]=_DB;if(_DB<0){var _DC=$k[--$j];$k[$j++]=$f((_DC^2147483648)+2147483648)}$1.r=~~($k[--$j]/4294967296*($1.l+1));$p($1.bits,$1.l,$g($1.bits,$1.r));$p($1.bits,$1.r,$g($1.bits,$1.l))}$1.tmpbits=$s($1.C);$P($1.tmpbits,0,$1.bits);$1.j=$1.bits.length;for(var _DW=0,_DX=~~($f($f($1.C-$1.j)+1)/2);_DW<_DX;_DW++){$p($1.tmpbits,$1.j,48);if($1.j+1<$1.C){$p($1.tmpbits,$1.j+1,49)}$1.j=$1.j+2}$1.bits=$1.tmpbits;if($1.colors==4){$1.bi=0;$1.gi=1;$1.mi=2;$1.yi=3;$1.ki=4;$1.wi=5;$k[$j++]=Infinity;$k[$j++]=$1.bi;$k[$j++]="0000FF";$k[$j++]=$1.gi;$k[$j++]="00FF00";$k[$j++]=$1.mi;$k[$j++]="FF00FF";$k[$j++]=$1.yi;$k[$j++]="FFFF00";$k[$j++]=$1.ki;$k[$j++]="000000";$k[$j++]=$1.wi;$k[$j++]="FFFFFF";$1.palette=$d();$1.metacolorindex=$a([$1.bi,$1.gi,$1.mi,$1.yi]);$1.palettelayout=$a([$1.bi,$1.gi,$1.mi,$1.yi])}else{var _E4=new Map([[8,$a([2,2,2])],[16,$a([4,2,2])],[32,$a([4,4,2])],[64,$a([4,4,4])],[128,$a([8,4,4])],[256,$a([8,8,4])]]);$1.rgbres=$g(_E4,$1.colors);$k[$j++]="rvals";$k[$j++]=$g($1.rgbres,0);$k[$j++]=Infinity;var _E9=$k[--$j];var _EB=$f($k[--$j]-1);$k[$j++]=_E9;$k[$j++]=_EB;for(var _ED=0,_EC=_EB;_ED<=_EC;_ED+=1){var _EE=$k[--$j];$k[$j++]=~~Math.round(_ED*(255/_EE));$k[$j++]=_EE}$j--;var _EF=$a();$1[$k[--$j]]=_EF;$k[$j++]="gvals";$k[$j++]=$g($1.rgbres,1);$k[$j++]=Infinity;var _EJ=$k[--$j];var _EL=$f($k[--$j]-1);$k[$j++]=_EJ;$k[$j++]=_EL;for(var _EN=0,_EM=_EL;_EN<=_EM;_EN+=1){var _EO=$k[--$j];$k[$j++]=~~Math.round(_EN*(255/_EO));$k[$j++]=_EO}$j--;var _EP=$a();$1[$k[--$j]]=_EP;$k[$j++]="bvals";$k[$j++]=$g($1.rgbres,2);$k[$j++]=Infinity;var _ET=$k[--$j];var _EV=$f($k[--$j]-1);$k[$j++]=_ET;$k[$j++]=_EV;for(var _EX=0,_EW=_EV;_EX<=_EW;_EX+=1){var _EY=$k[--$j];$k[$j++]=~~Math.round(_EX*(255/_EY));$k[$j++]=_EY}$j--;var _EZ=$a();$1[$k[--$j]]=_EZ;var _Eb=$1.colors;$1.palette=new Map;var _Ec=$1.colors;var _Ed=64;if(64>_Ec){var _=_Ec;_Ec=_Ed;_Ed=_}$1.palettelayout=$a(_Ed);$1.i=0;$1.j=8;$F($1.rvals,function(){$1.r=$k[--$j];$F($1.gvals,function(){$1.g=$k[--$j];$F($1.bvals,function(){$1.b=$k[--$j];var _Ep=$Z($s(6),"000000");var _Er=$R($s(6),$1.r<<16|$1.g<<8|$1.b,16);$P(_Ep,6-_Er.length,_Er);$k[$j++]=_Ep;$k[$j++]=false;if($eq(_Ep,"000000")){$1.ki=$1.i;$j--;$k[$j++]=true}var _Et=$k[--$j];var _Eu=$k[--$j];$k[$j++]=_Eu;$k[$j++]=_Et;if($eq(_Eu,"0000FF")){$1.bi=$1.i;$j--;$k[$j++]=true}var _Ew=$k[--$j];var _Ex=$k[--$j];$k[$j++]=_Ex;$k[$j++]=_Ew;if($eq(_Ex,"00FF00")){$1.gi=$1.i;$j--;$k[$j++]=true}var _Ez=$k[--$j];var _F0=$k[--$j];$k[$j++]=_F0;$k[$j++]=_Ez;if($eq(_F0,"00FFFF")){$1.ci=$1.i;$j--;$k[$j++]=true}var _F2=$k[--$j];var _F3=$k[--$j];$k[$j++]=_F3;$k[$j++]=_F2;if($eq(_F3,"FF0000")){$1.ri=$1.i;$j--;$k[$j++]=true}var _F5=$k[--$j];var _F6=$k[--$j];$k[$j++]=_F6;$k[$j++]=_F5;if($eq(_F6,"FF00FF")){$1.mi=$1.i;$j--;$k[$j++]=true}var _F8=$k[--$j];var _F9=$k[--$j];$k[$j++]=_F9;$k[$j++]=_F8;if($eq(_F9,"FFFF00")){$1.yi=$1.i;$j--;$k[$j++]=true}var _FB=$k[--$j];var _FC=$k[--$j];$k[$j++]=_FC;$k[$j++]=_FB;if($eq(_FC,"FFFFFF")){$1.wi=$1.i;$j--;$k[$j++]=true}if($nt($k[--$j])){if($1.colors<=64||($1.colors==128&&($1.r==0||$1.r==73||$1.r==182||$1.r==255)||$1.colors==256&&($1.r==0||$1.r==73||$1.r==182||$1.r==255)&&($1.g==0||$1.g==73||$1.g==182||$1.g==255))){$p($1.palettelayout,$1.j,$1.i);$1.j=$1.j+1}}$p($1.palette,$1.i,$k[--$j]);$1.i=$1.i+1})})});$P($1.palettelayout,0,$a([$1.ki,$1.bi,$1.gi,$1.ci,$1.ri,$1.mi,$1.yi,$1.wi]));$1.metacolorindex=$a([$1.ki,$1.bi,$1.gi,$1.ci,$1.ri,$1.mi,$1.yi,$1.wi])}$k[$j++]=Infinity;for(var _Fx=0,_Fy=$1.rows*$1.cols;_Fx<_Fy;_Fx++){$k[$j++]=-1}$1.pixs=$a();$1.jmv=function(){var _G1=$k[--$j];var _G2=$k[--$j];$k[$j++]=$f(_G2+_G1*$1.cols)};if(!$1.slave){$1.fpat=$a([$a([1,1,1,0,0]),$a([1,2,2,0,0]),$a([1,2,1,2,1]),$a([0,0,2,2,1]),$a([0,0,1,1,1])]);$1.fmap=$a([$a([-1,$1.bi,$1.yi]),$a([-1,$1.yi,$1.bi]),$a([-1,$1.gi,$1.mi]),$a([-1,$1.mi,$1.gi])])}else{$1.fpat=$a([$a([0,0,0,0,0]),$a([0,2,2,0,0]),$a([0,2,1,2,0]),$a([0,0,2,2,0]),$a([0,0,0,0,0])]);$1.fmap=$a([$a([-1,$1.ki,$1.wi]),$a([-1,$1.ki,$1.wi]),$a([-1,$1.ki,$1.wi]),$a([-1,$1.ki,$1.wi])])}for(var _Gg=0;_Gg<=4;_Gg+=1){$1.y=_Gg;for(var _Gh=0;_Gh<=4;_Gh+=1){$1.x=_Gh;$1.fpb=$g($g($1.fpat,$1.y),$1.x);$k[$j++]=$1.pixs;$k[$j++]=$1.x+1;$k[$j++]=$1.y+1;$1.jmv();var _Gu=$k[--$j];$p($k[--$j],_Gu,$g($g($1.fmap,0),$1.fpb));$k[$j++]=$1.pixs;$k[$j++]=$1.x+1;$k[$j++]=$f($f($1.rows-$1.y)-2);$1.jmv();var _H4=$k[--$j];$p($k[--$j],_H4,$g($g($1.fmap,1),$1.fpb));$k[$j++]=$1.pixs;$k[$j++]=$f($f($1.x+$1.cols)-6);$k[$j++]=$1.y+1;$1.jmv();var _HE=$k[--$j];$p($k[--$j],_HE,$g($g($1.fmap,2),$1.fpb));$k[$j++]=$1.pixs;$k[$j++]=$f($f($1.x+$1.cols)-6);$k[$j++]=$f($f($1.rows-$1.y)-2);$1.jmv();var _HP=$k[--$j];$p($k[--$j],_HP,$g($g($1.fmap,3),$1.fpb))}}$1.algnpat0=$a([$a([$1.ki,$1.ki,-1]),$a([$1.ki,$1.wi,$1.ki]),$a([-1,$1.ki,$1.ki])]);$1.algnpat1=$a([$a([-1,$1.ki,$1.ki]),$a([$1.ki,$1.wi,$1.ki]),$a([$1.ki,$1.ki,-1])]);$1.num=~~Math.round($1.cols/16)-1;$1.algnrpos=$a([3,17]);if($1.num>0){$k[$j++]=Infinity;for(var _Hs=0,_Hr=$1.num;_Hs<=_Hr;_Hs+=1){$k[$j++]=~~(_Hs*($f($1.cols-7)/$1.num))+3}$1.algnrpos=$a()}$1.num=~~Math.round($1.rows/16)-1;$1.algncpos=$a([3,17]);if($1.num>0){$k[$j++]=Infinity;for(var _I1=0,_I0=$1.num;_I1<=_I0;_I1+=1){$k[$j++]=~~(_I1*($f($1.rows-7)/$1.num))+3}$1.algncpos=$a()}$1.putalgnpat=function(){$1.pp=$k[--$j];$1.py=$k[--$j];$1.px=$k[--$j];for(var _I8=0;_I8<=2;_I8+=1){$1.pb=_I8;for(var _I9=0;_I9<=2;_I9+=1){$1.pa=_I9;$k[$j++]=$1.pixs;$k[$j++]=$f($1.px+$1.pa);$k[$j++]=$f($1.py+$1.pb);$1.jmv();var _IK=$k[--$j];$p($k[--$j],_IK,$g($g($1.pp,$1.pb),$1.pa))}}};for(var _IO=0,_IN=$1.algncpos.length-1;_IO<=_IN;_IO+=1){$1.j=_IO;$1.y=$g($1.algncpos,$1.j);for(var _IU=0,_IT=$1.algnrpos.length-1;_IU<=_IT;_IU+=1){$1.i=_IU;$1.x=$g($1.algnrpos,$1.i);$k[$j++]=$1.pixs;$k[$j++]=$1.x;$k[$j++]=$1.y;$1.jmv();var _Ib=$k[--$j];if($g($k[--$j],_Ib)==-1){$k[$j++]=$f($1.x-1);$k[$j++]=$f($1.y-1);if(($1.i+$1.j)%2==0){$k[$j++]=$1.algnpat0}else{$k[$j++]=$1.algnpat1}$1.putalgnpat()}}}if(!$1.slave){var _JT=$a([$a([6,1]),$a([6,2]),$a([6,3]),$a([6,4]),$a([6,5]),$a([6,6]),$a([5,6]),$a([4,6]),$a([3,6]),$a([2,6]),$a([1,6]),$a([7,1]),$a([7,2]),$a([7,3]),$a([7,4]),$a([7,5]),$a([7,6]),$a([7,7]),$a([6,7]),$a([5,7]),$a([4,7]),$a([3,7]),$a([2,7]),$a([1,7]),$a([8,1]),$a([8,2]),$a([8,3]),$a([8,4]),$a([8,5]),$a([8,6]),$a([8,7]),$a([8,8]),$a([7,8]),$a([6,8]),$a([5,8]),$a([4,8]),$a([3,8]),$a([2,8]),$a([1,8]),$a([9,1]),$a([9,2]),$a([9,3]),$a([9,4]),$a([9,5])]);for(var _JU=0,_JV=_JT.length;_JU<_JV;_JU++){$q($g(_JT,_JU));$1.y=$k[--$j];$1.x=$k[--$j];$k[$j++]=$a([$1.x,$1.y]);$k[$j++]=$a([-$1.x,$1.y]);$k[$j++]=$a([-$1.x,-$1.y]);$k[$j++]=$a([$1.x,-$1.y])}$r($a(176));$1.metadatamap=$k[--$j];$1.palettemap1=$a([$a([4,1]),$a([4,2]),$a([5,1]),$a([5,2]),$a([2,4]),$a([2,5]),$a([1,4]),$a([1,5]),$a([-2,1]),$a([-2,2]),$a([-1,1]),$a([-1,2]),$a([-4,4]),$a([-4,5]),$a([-5,4]),$a([-5,5])]);$1.palettemap2=$a([$a([-4,-5]),$a([-4,-4]),$a([-5,-5]),$a([-5,-4]),$a([-2,-2]),$a([-2,-1]),$a([-1,-2]),$a([-1,-1]),$a([2,-5]),$a([2,-4]),$a([1,-5]),$a([1,-4]),$a([4,-2]),$a([4,-1]),$a([5,-2]),$a([5,-1])])}else{$k[$j++]=Infinity;for(var _KL=1;_KL<=19;_KL+=1){$1.i=_KL;$k[$j++]=$a([0,$1.i]);$k[$j++]=$a([1,$1.i])}for(var _KQ=5;_KQ<=12;_KQ+=1){$1.i=_KQ;$k[$j++]=$a([2,$1.i]);$k[$j++]=$a([3,$1.i])}$1.metadatamap=$a();$k[$j++]=Infinity;for(var _KW=5;_KW<=12;_KW+=1){$k[$j++]=_KW;$k[$j++]=Infinity;var _KX=$k[--$j];var _KY=$k[--$j];$k[$j++]=_KX;$k[$j++]=4;$k[$j++]=_KY;var _KZ=$a();$k[$j++]=_KZ}for(var _Ka=12;_Ka>=5;_Ka-=1){$k[$j++]=_Ka;$k[$j++]=Infinity;var _Kb=$k[--$j];var _Kc=$k[--$j];$k[$j++]=_Kb;$k[$j++]=5;$k[$j++]=_Kc;var _Kd=$a();$k[$j++]=_Kd}for(var _Ke=5;_Ke<=12;_Ke+=1){$k[$j++]=_Ke;$k[$j++]=Infinity;var _Kf=$k[--$j];var _Kg=$k[--$j];$k[$j++]=_Kf;$k[$j++]=6;$k[$j++]=_Kg;var _Kh=$a();$k[$j++]=_Kh}for(var _Ki=12;_Ki>=5;_Ki-=1){$k[$j++]=_Ki;$k[$j++]=Infinity;var _Kj=$k[--$j];var _Kk=$k[--$j];$k[$j++]=_Kj;$k[$j++]=7;$k[$j++]=_Kk;var _Kl=$a();$k[$j++]=_Kl}$1.palettemap1=$a();$k[$j++]=Infinity;var _Kn=$1.palettemap1;for(var _Ko=0,_Kp=_Kn.length;_Ko<_Kp;_Ko++){$k[$j++]=$g(_Kn,_Ko);$k[$j++]=Infinity;var _Kr=$k[--$j];var _Ks=$k[--$j];$k[$j++]=_Kr;$q(_Ks);var _Kt=$k[--$j];var _Ku=$k[--$j];$k[$j++]=-_Ku;$k[$j++]=-_Kt;var _Kv=$a();$k[$j++]=_Kv}$1.palettemap2=$a()}var _Kx=$1.metadatamap;for(var _Ky=0,_Kz=_Kx.length;_Ky<_Kz;_Ky++){var _L0=$g(_Kx,_Ky);var _L1=$g(_L0,0);$k[$j++]=_L0;$k[$j++]=_L1;if(_L1<0){var _L3=$k[--$j];var _L4=$k[--$j];$p(_L4,0,$f($f(_L3+$1.cols)-1));$k[$j++]=_L4}else{$j--}var _L5=$k[--$j];var _L6=$g(_L5,1);$k[$j++]=_L5;$k[$j++]=_L6;if(_L6<0){var _L8=$k[--$j];var _L9=$k[--$j];$p(_L9,1,$f($f(_L8+$1.rows)-1));$k[$j++]=_L9}else{$j--}$j--}var _LA=$1.palettemap1;for(var _LB=0,_LC=_LA.length;_LB<_LC;_LB++){var _LD=$g(_LA,_LB);var _LE=$g(_LD,0);$k[$j++]=_LD;$k[$j++]=_LE;if(_LE<0){var _LG=$k[--$j];var _LH=$k[--$j];$p(_LH,0,$f($f(_LG+$1.cols)-1));$k[$j++]=_LH}else{$j--}var _LI=$k[--$j];var _LJ=$g(_LI,1);$k[$j++]=_LI;$k[$j++]=_LJ;if(_LJ<0){var _LL=$k[--$j];var _LM=$k[--$j];$p(_LM,1,$f($f(_LL+$1.rows)-1));$k[$j++]=_LM}else{$j--}$j--}var _LN=$1.palettemap2;for(var _LO=0,_LP=_LN.length;_LO<_LP;_LO++){var _LQ=$g(_LN,_LO);var _LR=$g(_LQ,0);$k[$j++]=_LQ;$k[$j++]=_LR;if(_LR<0){var _LT=$k[--$j];var _LU=$k[--$j];$p(_LU,0,$f($f(_LT+$1.cols)-1));$k[$j++]=_LU}else{$j--}var _LV=$k[--$j];var _LW=$g(_LV,1);$k[$j++]=_LV;$k[$j++]=_LW;if(_LW<0){var _LY=$k[--$j];var _LZ=$k[--$j];$p(_LZ,1,$f($f(_LY+$1.rows)-1));$k[$j++]=_LZ}else{$j--}$j--}for(var _Lc=0,_Lb=$f($1.nummetamodules-1);_Lc<=_Lb;_Lc+=1){$k[$j++]=$1.pixs;$q($g($1.metadatamap,_Lc));$1.jmv();var _Lg=$k[--$j];$p($k[--$j],_Lg,0)}if(!$1.slave){var _Lj=$1.colors;var _Lk=16;if(16>_Lj){var _=_Lj;_Lj=_Lk;_Lk=_}for(var _Lm=0,_Ll=_Lk-1;_Lm<=_Ll;_Lm+=1){$1.i=_Lm;var _Lp=$g($1.palettelayout,$1.i);$k[$j++]=_Lp;$k[$j++]=$1.pixs;$k[$j++]=_Lp;$q($g($1.palettemap1,$1.i));$1.jmv();var _Lu=$k[--$j];var _Lv=$k[--$j];$p($k[--$j],_Lu,_Lv);var _Ly=$k[--$j];$k[$j++]=$1.pixs;$k[$j++]=_Ly;$q($g($1.palettemap2,$1.i));$1.jmv();var _M2=$k[--$j];var _M3=$k[--$j];$p($k[--$j],_M2,_M3)}$1.i=16}else{$1.i=0}$1.j=$1.nummetamodules;for(var _M9=$1.i,_M8=$1.palettelayout.length-1;_M9<=_M8;_M9+=2){$1.i=_M9;var _MC=$g($1.palettelayout,$1.i);$k[$j++]=_MC;$k[$j++]=$1.pixs;$k[$j++]=_MC;$q($g($1.metadatamap,$1.j));$1.jmv();var _MH=$k[--$j];var _MI=$k[--$j];$p($k[--$j],_MH,_MI);var _ML=$k[--$j];$k[$j++]=$1.pixs;$k[$j++]=_ML;$q($g($1.metadatamap,$f($1.j+2)));$1.jmv();var _MP=$k[--$j];var _MQ=$k[--$j];$p($k[--$j],_MP,_MQ);var _MU=$g($1.palettelayout,$1.i+1);$k[$j++]=_MU;$k[$j++]=$1.pixs;$k[$j++]=_MU;$q($g($1.metadatamap,$f($1.j+1)));$1.jmv();var _MZ=$k[--$j];var _Ma=$k[--$j];$p($k[--$j],_MZ,_Ma);var _Md=$k[--$j];$k[$j++]=$1.pixs;$k[$j++]=_Md;$q($g($1.metadatamap,$f($1.j+3)));$1.jmv();var _Mh=$k[--$j];var _Mi=$k[--$j];$p($k[--$j],_Mh,_Mi);$1.j=$f($1.j+4)}var _NA=$a([function(){var _Ml=$k[--$j];var _Mm=$k[--$j];$k[$j++]=$f(_Mm+_Ml)%$1.colors},function(){$j--;var _Mp=$k[--$j];$k[$j++]=_Mp%$1.colors},function(){var _Mq=$k[--$j];var _Mr=$k[--$j];$k[$j++]=_Mq;$k[$j++]=_Mr;$j--;var _Mt=$k[--$j];$k[$j++]=_Mt%$1.colors},function(){var _Mu=$k[--$j];var _Mv=$k[--$j];$k[$j++]=(~~(_Mu/3)+~~(_Mv/2))%$1.colors},function(){var _Mx=$k[--$j];var _My=$k[--$j];$k[$j++]=(~~(_Mx/2)+~~(_My/3))%$1.colors},function(){var _N0=$k[--$j];var _N2=$f($k[--$j]+_N0);$k[$j++]=(~~(_N2/2)+~~(_N2/3))%$1.colors},function(){var _N4=$k[--$j];var _N5=$k[--$j];$k[$j++]=$f(_N4*(_N5*_N5)%7+$f(_N4+_N5*_N5)*2%19)%$1.colors},function(){var _N7=$k[--$j];var _N8=$k[--$j];$k[$j++]=$f(_N8*(_N7*_N7)%5+$f(_N7*_N7+_N8*2)%13)%$1.colors}]);$1.maskfuncs=_NA;if($1.mask!=-1){$1.maskfuncs=$a([$g($1.maskfuncs,$1.mask)]);$1.bestmaskval=$1.mask}$1.masks=$a($1.maskfuncs.length);for(var _NL=0,_NK=$1.masks.length-1;_NL<=_NK;_NL+=1){$1.m=_NL;$1.mask=$a($1.rows*$1.cols);for(var _NR=0,_NQ=$f($1.rows-1);_NR<=_NQ;_NR+=1){$1.j=_NR;for(var _NU=0,_NT=$f($1.cols-1);_NU<=_NT;_NU+=1){$1.i=_NU;$k[$j++]=$1.pixs;$k[$j++]=$1.i;$k[$j++]=$1.j;$1.jmv();var _NY=$k[--$j];if($g($k[--$j],_NY)==-1){$k[$j++]=$1.i;$k[$j++]=$1.j;if($g($1.maskfuncs,$1.m)()===true){break}}else{$k[$j++]=0}$k[$j++]=$1.mask;$k[$j++]=$1.i;$k[$j++]=$1.j;$1.jmv();var _Nj=$k[--$j];var _Nk=$k[--$j];$p(_Nk,_Nj,$k[--$j])}}$p($1.masks,$1.m,$1.mask)}$1.posx=0;$1.posy=0;$1.i=0;for(;;){if($1.posx==$1.cols){break}$k[$j++]=$1.pixs;$k[$j++]=$1.posx;$k[$j++]=$1.posy;$1.jmv();var _Nu=$k[--$j];if($g($k[--$j],_Nu)==-1){var _O0=$G($1.bits,$1.i,$1.databpm);$k[$j++]=0;for(var _O1=0,_O2=_O0.length;_O1<_O2;_O1++){var _O4=$k[--$j];$k[$j++]=$f(_O4+$f($g(_O0,_O1)-48))*2}var _O5=$k[--$j];$k[$j++]=~~(_O5/2);$k[$j++]=$1.pixs;$k[$j++]=$1.posx;$k[$j++]=$1.posy;$1.jmv();var _O9=$k[--$j];var _OA=$k[--$j];$p(_OA,_O9,$k[--$j]);$1.i=$1.i+$1.databpm}$1.posy=$1.posy+1;if($1.posy==$1.rows){$1.posy=0;$1.posx=$1.posx+1}}$1.evalrle=function(){$1.scrle=$k[--$j];$1.scr1=0;$1.scr3=0;for(var _OL=0,_OK=$1.scrle.length-2;_OL<=_OK;_OL+=2){$1.j=_OL;if($g($1.scrle,$1.j+1)!=-1){var _OR=$g($1.scrle,$1.j);$k[$j++]=_OR;if(_OR>=5){$1.scr1=$f($f($k[--$j]-2)+$1.scr1)}else{$j--}if($1.j>=4&&$1.j<=$1.scrle.length-5){var _OZ=$G($1.scrle,$1.j-4,10);$k[$j++]=_OZ;for(var _Oa=0,_Ob=_OZ.length;_Oa<_Ob;_Oa++){$k[$j++]=$g(_OZ,_Oa)==1}$j--;for(var _Od=0,_Oe=4;_Od<_Oe;_Od++){var _Of=$k[--$j];var _Og=$k[--$j];$k[$j++]=_Of;$k[$j++]=_Og;$j--;var _Oh=$k[--$j];var _Oi=$k[--$j];$k[$j++]=$an(_Oi,_Oh)}if($k[--$j]){$k[$j++]=Infinity;var _Ok=$k[--$j];var _Ol=$k[--$j];$k[$j++]=_Ok;$q(_Ol);var _Om=$k[--$j];var _On=$k[--$j];var _Oo=$k[--$j];var _Op=$k[--$j];var _Oq=$k[--$j];var _Or=$k[--$j];var _Os=$k[--$j];var _Ot=$k[--$j];var _Ou=$k[--$j];$k[$j++]=_Ou;$k[$j++]=_Ot;$k[$j++]=_Os;$k[$j++]=_Or;$k[$j++]=_Oq;$k[$j++]=_Op;$k[$j++]=_Oo;$k[$j++]=_On;$k[$j++]=_Om;if($eq(_Ou,_Oq)&&$eq(_Ou,_Om)&&$eq(_Os,_Oo)){var _Ov=$k[--$j];var _Ow=$k[--$j];var _Ox=$k[--$j];$1.c4=_Ox;$1.c5=_Ov;$k[$j++]=_Ox;$k[$j++]=_Ow;if($1.c4==$1.bi&&$1.c5==$1.yi||($1.c4==$1.yi&&$1.c5==$1.bi||($1.c4==$1.gi&&$1.c5==$1.mi||$1.c4==$1.mi&&$1.c5==$1.gi))){$1.scr3=$1.scr3+100}}$l()}else{$j--}}}}$k[$j++]=$1.scr1;$k[$j++]=$1.scr3};$1.evalmask=function(){$1.sym=$k[--$j];$1.n1=0;$1.n2=0;$1.n3=0;var _PI=$1.rows;var _PJ=$1.cols;if(_PI<_PJ){var _=_PJ;_PJ=_PI;_PI=_}$1.rle=$a($f(_PI*2+2));$1.lastpairs=$a($1.cols);$1.thispairs=$a($1.cols);for(var _PR=0,_PQ=$f($1.cols-1);_PR<=_PQ;_PR+=1){$1.i=_PR;$k[$j++]=Infinity;$k[$j++]=0;$k[$j++]=-1;for(var _PX=$1.i,_PY=$1.cols,_PW=$f($1.rows*$1.cols-1);_PY<0?_PX>=_PW:_PX<=_PW;_PX+=_PY){var _Pa=$g($1.sym,_PX);var _Pb=$k[--$j];$k[$j++]=_Pb;$k[$j++]=_Pa;if($eq(_Pb,_Pa)){$j--;var _Pc=$k[--$j];var _Pd=$k[--$j];$k[$j++]=$f(_Pd+1);$k[$j++]=_Pc}else{var _Pe=$k[--$j];$k[$j++]=1;$k[$j++]=_Pe}}var _Pg=$m()+2;$r($G($1.rle,0,_Pg-2));$1.evalrle();$1.n3=$f($k[--$j]+$1.n3);$1.n1=$f($k[--$j]+$1.n1);$j--}for(var _Po=0,_Pn=$f($1.rows-1);_Po<=_Pn;_Po+=1){$1.i=_Po;$1.symrow=$G($1.sym,$1.i*$1.cols,$1.cols);$k[$j++]=Infinity;var _Pu=$1.symrow;$k[$j++]=0;$k[$j++]=-1;for(var _Pv=0,_Pw=_Pu.length;_Pv<_Pw;_Pv++){var _Px=$g(_Pu,_Pv);var _Py=$k[--$j];$k[$j++]=_Py;$k[$j++]=_Px;if($eq(_Py,_Px)){$j--;var _Pz=$k[--$j];var _Q0=$k[--$j];$k[$j++]=$f(_Q0+1);$k[$j++]=_Pz}else{var _Q1=$k[--$j];$k[$j++]=1;$k[$j++]=_Q1}}var _Q3=$m()+2;$r($G($1.rle,0,_Q3-2));$1.evalrle();$1.n3=$f($k[--$j]+$1.n3);$1.n1=$f($k[--$j]+$1.n1);$j--;var _Q9=$1.thispairs;$1.thispairs=$1.lastpairs;$1.lastpairs=_Q9;var _QB=$1.symrow;$k[$j++]=-1;for(var _QC=0,_QD=_QB.length;_QC<_QD;_QC++){var _QE=$g(_QB,_QC);var _QF=$k[--$j];$k[$j++]=_QE;$k[$j++]=_QF;if($ne(_QE,_QF)){$j--;var _QG=$k[--$j];$k[$j++]=-1;$k[$j++]=_QG}}$j--;$r($1.thispairs);$j--;if($1.i>0){$k[$j++]=Infinity;$q($1.lastpairs);$q($1.thispairs);$k[$j++]=$1.n2;for(var _QN=0,_QO=$1.cols;_QN<_QO;_QN++){var _QP=$k[--$j];var _QQ=$k[--$j];$k[$j++]=_QP;$k[$j++]=_QQ;if(_QQ!=-1){var _QS=$k[$j-1-$f($1.cols+1)];if($eq($k[--$j],_QS)){var _QU=$k[--$j];$k[$j++]=$f(_QU+3)}}else{$j--}}$1.n2=$k[--$j];$l()}}$k[$j++]=$f($f($1.n1+$1.n2)+$1.n3)};$1.bestscore=999999999;for(var _Qb=0,_Qa=$1.masks.length-1;_Qb<=_Qa;_Qb+=1){$1.m=_Qb;$1.masksym=$a($1.rows*$1.cols);for(var _Qi=0,_Qh=$f($1.rows*$1.cols-1);_Qi<=_Qh;_Qi+=1){$1.i=_Qi;$p($1.masksym,$1.i,$xo($g($1.pixs,$1.i),$g($g($1.masks,$1.m),$1.i)))}if($1.masks.length!=1){$k[$j++]=$1.masksym;$1.evalmask();$1.score=$k[--$j];if($1.score<$1.bestscore){$1.bestsym=$1.masksym;$1.bestmaskval=$1.m;$1.bestscore=$1.score}}else{$1.bestsym=$1.masksym}}$1.pixs=$1.bestsym;$1.metamask=$1.bestmaskval;$1.addtometapart=function(){var _R4=$k[--$j];$P($1.metapart,$1.p,_R4);$1.p=_R4.length+$1.p};$1.addtometabits=function(){var _R8=$k[--$j];$P($1.metabits,$1.q,_R8);$1.q=_R8.length+$1.q};$1.metapart=$s(40);$1.metabits=$s($1.nummetabits);$1.p=0;$1.q=0;if(!$1.slave){$k[$j++]=~~Math.round(Math.log($1.colors)/Math.log(2))-1;$k[$j++]=3;$1.tofixedbits();$1.addtometapart();$k[$j++]=$G($1.metapart,0,$1.p);$k[$j++]=2;$k[$j++]=-1;$1.ldpc();$1.addtometabits();$1.p=0;$k[$j++]=$1.metass;$k[$j++]=1;$1.tofixedbits();$1.addtometapart();$k[$j++]=$1.metavf;$k[$j++]=2;$1.tofixedbits();$1.addtometapart();$k[$j++]=$1.metamask;$k[$j++]=3;$1.tofixedbits();$1.addtometapart();if($1.hasslaves){$k[$j++]="1"}else{$k[$j++]="0"}$1.addtometapart();$k[$j++]=$G($1.metapart,0,$1.p);$k[$j++]=2;$k[$j++]=-1;$1.ldpc();$1.addtometabits();$1.p=0;if($1.metass==0){$k[$j++]=$f($f(~~($f($1.cols-17)/4)-$g($a([0,4,8,16]),$1.metavf))-1);$k[$j++]=$1.metavlen;$1.tofixedbits();$1.addtometapart()}else{$k[$j++]=~~($f($1.cols-17)/4)-1;$k[$j++]=~~($1.metavlen/2);$1.tofixedbits();$1.addtometapart();$k[$j++]=~~($f($1.rows-17)/4)-1;$k[$j++]=~~($1.metavlen/2);$1.tofixedbits();$1.addtometapart()}$k[$j++]=$1.datawc-3;$k[$j++]=~~($1.metaelen/2);$1.tofixedbits();$1.addtometapart();$k[$j++]=$1.datawr-4;$k[$j++]=~~($1.metaelen/2);$1.tofixedbits();$1.addtometapart();if($1.hasslaves){$k[$j++]=0;$k[$j++]=4;$1.tofixedbits();$1.addtometapart()}$k[$j++]=$G($1.metapart,0,$1.p);$k[$j++]=2;$k[$j++]=-1;$1.ldpc();$1.addtometabits();$1.p=0}else{var _Rk=$1.sameshape?0:1;$k[$j++]=_Rk;$k[$j++]=1;$1.tofixedbits();$1.addtometapart();var _Rm=$1.sameecc?0:1;$k[$j++]=_Rm;$k[$j++]=1;$1.tofixedbits();$1.addtometapart();var _Ro=$1.hasslaves?1:0;$k[$j++]=_Ro;$k[$j++]=1;$1.tofixedbits();$1.addtometapart();$k[$j++]=$G($1.metapart,0,$1.p);$k[$j++]=2;$k[$j++]=-1;$1.ldpc();$1.addtometabits();$1.p=0;if(!$1.sameshape){$k[$j++]=~~(($1.diffside-17)/4)-1;$k[$j++]=5;$1.tofixedbits();$1.addtometapart()}if($1.hasslaves){$k[$j++]=0;$k[$j++]=3;$1.tofixedbits();$1.addtometapart()}$k[$j++]=$G($1.metapart,0,$1.p);$k[$j++]=2;$k[$j++]=-1;$1.ldpc();$1.addtometabits();$1.p=0;if(!$1.sameecc){$k[$j++]=$1.datawc-3;$k[$j++]=~~($1.metaelen/2);$1.tofixedbits();$1.addtometapart();$k[$j++]=$1.datawr-4;$k[$j++]=~~($1.metaelen/2);$1.tofixedbits();$1.addtometapart()}$k[$j++]=$G($1.metapart,0,$1.p);$k[$j++]=2;$k[$j++]=-1;$1.ldpc();$1.addtometabits();$1.p=0}for(var _S9=$1.q,_S8=$f($1.nummetabits-1);_S9<=_S8;_S9+=1){$p($1.metabits,_S9,48)}$1.i=0;$1.j=0;if(!$1.slave){var _SE=$G($1.metabits,$1.i,6);for(var _SF=0,_SG=_SE.length;_SF<_SG;_SF++){$k[$j++]=$g(_SE,_SF);if($1.colors==4){$k[$j++]=$a([$1.bi,$1.yi])}else{$k[$j++]=$a([$1.ki,$1.wi])}var _SP=$k[--$j];var _SR=$g(_SP,$f($k[--$j]-48));$k[$j++]=_SR;$k[$j++]=$1.pixs;$q($g($1.metadatamap,$1.j));$1.jmv();var _SW=$k[--$j];var _SX=$k[--$j];$p(_SX,_SW,$k[--$j]);$1.j=$1.j+1}$1.i=$1.i+6}for(var _Se=0,_Sf=~~($f($1.nummetabits-$1.i)/$1.metabpm);_Se<_Sf;_Se++){var _Sj=$G($1.metabits,$1.i,$1.metabpm);$k[$j++]=0;for(var _Sk=0,_Sl=_Sj.length;_Sk<_Sl;_Sk++){var _Sn=$k[--$j];$k[$j++]=$f(_Sn+$f($g(_Sj,_Sk)-48))*2}var _Sq=$g($1.metacolorindex,~~($k[--$j]/2));$k[$j++]=_Sq;$k[$j++]=$1.pixs;$q($g($1.metadatamap,$1.j));$1.jmv();var _Sv=$k[--$j];var _Sw=$k[--$j];$p(_Sw,_Sv,$k[--$j]);$1.i=$f($1.i+$1.metabpm);$1.j=$1.j+1}var _T8=new Map([["ren",bwipp_renmatrix],["pixs",$1.pixs],["pixx",$1.cols],["pixy",$1.rows],["colormap",$1.palette],["height",$1.rows*2/72],["width",$1.cols*2/72],["opt",$1.options]]);$k[$j++]=_T8;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_gs1_cc(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.parse=false;$1.dontlint=false;$1.dontdraw=false;$1.ccversion="a";$1.cccolumns=-1;$1.lintype="";$1.linwidth=-1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.cccolumns=~~$1.cccolumns;$1.linwidth=~~$1.linwidth;if($1.cccolumns==-1){$k[$j++]=Infinity;$k[$j++]="ean13";$k[$j++]=4;$k[$j++]="upca";$k[$j++]=4;$k[$j++]="ean8";$k[$j++]=3;$k[$j++]="upce";$k[$j++]=2;$k[$j++]="gs1-128";if($ne($1.ccversion,"c")){$k[$j++]=4}else{$k[$j++]=~~(($1.linwidth-52)/17)}$k[$j++]="databaromni";$k[$j++]=4;$k[$j++]="databarstacked";$k[$j++]=2;$k[$j++]="databarstackedomni";$k[$j++]=2;$k[$j++]="databartruncated";$k[$j++]=4;$k[$j++]="databarlimited";$k[$j++]=3;$k[$j++]="databarexpanded";$k[$j++]=4;$k[$j++]="databarexpandedstacked";$k[$j++]=4;$1.cccolumns=$g($d(),$1.lintype)}$1.expand=function(){var _E=new Map([["parse",$1.parse],["parseonly",true],["parsefnc",false]]);$1.fncvals=_E;$k[$j++]=$1.fncvals;bwipp_parseinput()};$1.ais=$a([]);$1.vals=$a([]);var _I=$1.barcode;$k[$j++]=$G(_I,1,_I.length-1);for(;;){var _K=$k[--$j];$k[$j++]=_K;if($eq(_K,"")){break}$x($k[--$j],")");$j--;var _M=$k[--$j];var _N=$k[--$j];$k[$j++]=_M;$k[$j++]=_N;$j--;var _O=$k[--$j];var _P=$k[--$j];$k[$j++]=_O;$x(_P,"(");if($k[--$j]){var _R=$k[--$j];var _S=$k[--$j];$k[$j++]=_R;$k[$j++]=_S;$j--;var _T=$k[--$j];var _U=$k[--$j];var _V=$k[--$j];$k[$j++]=_U;$k[$j++]=_V;$k[$j++]=_T}else{var _W=$k[--$j];var _X=$k[--$j];$k[$j++]="";$k[$j++]=_X;$k[$j++]=_W}$k[$j++]=Infinity;$q($1.ais);var _a=$k[$j-1-($m()+2)];$k[$j++]=_a;$1.ais=$a();$k[$j++]=Infinity;$q($1.vals);var _e=$k[$j-1-($m()+1)];$k[$j++]=_e;$1.expand();$1.vals=$a();$j-=2}$j--;var _g=$1.dontlint;if(!_g){$k[$j++]=$1.ais;$k[$j++]=$1.vals;bwipp_gs1lint();$j--}$1.isupper=function(){var _j=$k[--$j];$k[$j++]=_j>=65&&_j<=90};$1.isnum0=function(){var _k=$k[--$j];$k[$j++]=_k>=48&&_k<=57};$1.isnum1=function(){var _l=$k[--$j];$k[$j++]=_l>=49&&_l<=57};for(;;){if($1.ais.length>=1){if($eq($g($1.ais,0),"10")||$eq($g($1.ais,0),"11")||$eq($g($1.ais,0),"17")){$k[$j++]=-1;$k[$j++]="10";break}}if($1.ais.length>=1){if($eq($g($1.ais,0),"90")){$1.v=$g($1.vals,0);if($1.v.length>=1){$k[$j++]=$g($1.v,0);$1.isupper();if($k[--$j]){$k[$j++]=0;$k[$j++]="11";break}}if($1.v.length>=2){$k[$j++]=$g($1.v,0);$1.isnum1();$k[$j++]=$g($1.v,1);$1.isupper();var _17=$k[--$j];var _18=$k[--$j];if(_18&&_17){$k[$j++]=1;$k[$j++]="11";break}}if($1.v.length>=3){$k[$j++]=$g($1.v,0);$1.isnum1();$k[$j++]=$g($1.v,1);$1.isnum0();var _1E=$k[--$j];var _1F=$k[--$j];$k[$j++]=$an(_1F,_1E);$k[$j++]=$g($1.v,2);$1.isupper();var _1I=$k[--$j];var _1J=$k[--$j];if(_1J&&_1I){$k[$j++]=2;$k[$j++]="11";break}}if($1.v.length>=4){$k[$j++]=$g($1.v,0);$1.isnum1();$k[$j++]=$g($1.v,1);$1.isnum0();var _1P=$k[--$j];var _1Q=$k[--$j];$k[$j++]=$an(_1Q,_1P);$k[$j++]=$g($1.v,2);$1.isnum0();var _1T=$k[--$j];var _1U=$k[--$j];$k[$j++]=$an(_1U,_1T);$k[$j++]=$g($1.v,3);$1.isupper();var _1X=$k[--$j];var _1Y=$k[--$j];if(_1Y&&_1X){$k[$j++]=3;$k[$j++]="11";break}}}}$k[$j++]=-1;$k[$j++]="0";break}$1.method=$k[--$j];$1.npre=$k[--$j];$1.fnc1=-1;$1.lnumeric=-2;$1.lalphanumeric=-3;$1.liso646=-4;$1.tobin=function(){var _1c=$s($k[--$j]);$k[$j++]=_1c;for(var _1e=0,_1d=_1c.length-1;_1e<=_1d;_1e+=1){var _1f=$k[--$j];$p(_1f,_1e,48);$k[$j++]=_1f}var _1g=$k[--$j];var _1j=$R($s(_1g.length),$k[--$j],2);$P(_1g,_1g.length-_1j.length,_1j);$k[$j++]=_1g};$k[$j++]=Infinity;for(var _1k=65;_1k<=90;_1k+=1){$k[$j++]=_1k;$k[$j++]=_1k-65;$k[$j++]=5;$1.tobin()}for(var _1l=48;_1l<=57;_1l+=1){$k[$j++]=_1l;$k[$j++]=_1l+4;$k[$j++]=6;$1.tobin()}$k[$j++]=$1.fnc1;$k[$j++]="11111";$1.alpha=$d();$k[$j++]=Infinity;for(var _1o=0;_1o<=119;_1o+=1){var _1q=$Z($s(2),"00");var _1s=$R($s(2),_1o,11);$P(_1q,2-_1s.length,_1s);$k[$j++]=_1o;$k[$j++]=_1q;if($g(_1q,0)==65){var _1u=$k[--$j];$p(_1u,0,94);$k[$j++]=_1u}var _1v=$k[--$j];$k[$j++]=_1v;if($g(_1v,1)==65){var _1x=$k[--$j];$p(_1x,1,94);$k[$j++]=_1x}var _1y=$k[--$j];var _21=$Z($s(7),"0000000");var _23=$R($s(7),$f($k[--$j]+8),2);$P(_21,7-_23.length,_23);$k[$j++]=_1y;$k[$j++]=_21}$k[$j++]=$1.lalphanumeric;$k[$j++]="0000";$1.numeric=$d();$k[$j++]=Infinity;for(var _26=48;_26<=57;_26+=1){$k[$j++]=_26;$k[$j++]=_26-43;$k[$j++]=5;$1.tobin()}$k[$j++]=$1.fnc1;$k[$j++]="01111";for(var _28=65;_28<=90;_28+=1){$k[$j++]=_28;$k[$j++]=_28-33;$k[$j++]=6;$1.tobin()}$k[$j++]=42;$k[$j++]="111010";for(var _29=44;_29<=47;_29+=1){$k[$j++]=_29;$k[$j++]=_29+15;$k[$j++]=6;$1.tobin()}$k[$j++]=$1.lnumeric;$k[$j++]="000";$k[$j++]=$1.liso646;$k[$j++]="00100";$1.alphanumeric=$d();$k[$j++]=Infinity;for(var _2D=48;_2D<=57;_2D+=1){$k[$j++]=_2D;$k[$j++]=_2D-43;$k[$j++]=5;$1.tobin()}$k[$j++]=$1.fnc1;$k[$j++]="01111";for(var _2F=65;_2F<=90;_2F+=1){$k[$j++]=_2F;$k[$j++]=_2F-1;$k[$j++]=7;$1.tobin()}for(var _2G=97;_2G<=122;_2G+=1){$k[$j++]=_2G;$k[$j++]=_2G-7;$k[$j++]=7;$1.tobin()}$k[$j++]=33;$k[$j++]="11101000";$k[$j++]=34;$k[$j++]="11101001";for(var _2H=37;_2H<=47;_2H+=1){$k[$j++]=_2H;$k[$j++]=_2H+197;$k[$j++]=8;$1.tobin()}for(var _2I=58;_2I<=63;_2I+=1){$k[$j++]=_2I;$k[$j++]=_2I+187;$k[$j++]=8;$1.tobin()}$k[$j++]=95;$k[$j++]="11111011";$k[$j++]=32;$k[$j++]="11111100";$k[$j++]=$1.lnumeric;$k[$j++]="000";$k[$j++]=$1.lalphanumeric;$k[$j++]="00100";$1.iso646=$d();if($eq($1.method,"10")){if($eq($g($1.ais,0),"11")||$eq($g($1.ais,0),"17")){var _2S=$g($1.vals,0);var _2X=$Z($s(16),"0000000000000000");var _2Z=$R($s(16),~~$z($G(_2S,0,2))*384+((~~$z($G(_2S,2,2))-1)*32+~~$z($G(_2S,4,2))),2);$P(_2X,16-_2Z.length,_2Z);$k[$j++]=_2X;$k[$j++]=Infinity;var _2a=$k[--$j];var _2b=$k[--$j];$k[$j++]=_2a;$k[$j++]=1;$k[$j++]=0;$F(_2b,function(){var _2c=$k[--$j];$k[$j++]=$f(_2c-48)});var _2f=$eq($g($1.ais,0),"11")?0:1;$k[$j++]=_2f;$1.cdf=$a();$1.ais=$G($1.ais,1,$1.ais.length-1);$1.vals=$G($1.vals,1,$1.vals.length-1)}else{$1.cdf=$a([1,0,1,1])}if($1.ais.length!=0){if($eq($g($1.ais,0),"10")){$k[$j++]=Infinity;$F($g($1.vals,0));$1.gpf=$a();if($1.ais.length>1){$k[$j++]=Infinity;$q($1.gpf);$k[$j++]=$1.fnc1;$1.gpf=$a()}$1.ais=$G($1.ais,1,$1.ais.length-1);$1.vals=$G($1.vals,1,$1.vals.length-1)}else{$1.gpf=$a([$1.fnc1])}}else{$k[$j++]=Infinity;$q($1.cdf);$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$1.cdf=$a();$1.gpf=$a([])}$1.mode="numeric"}if($eq($1.method,"11")){$1.cdf=$a([1,1]);$1.ai90=$g($1.vals,0);if($f($1.npre+1)!=$1.ai90.length){$k[$j++]=$G($1.ai90,$f($1.npre+1),$f($f($1.ai90.length-$1.npre)-1))}else{$k[$j++]=""}$1.ai90rem=$k[--$j];$k[$j++]="nalpha";$k[$j++]=0;$F($1.ai90rem,function(){var _3M=$k[--$j];if(_3M>=65&&_3M<=90){var _3N=$k[--$j];$k[$j++]=$f(_3N+1)}});var _3O=$k[--$j];$1[$k[--$j]]=_3O;$k[$j++]="nnums";$k[$j++]=0;$F($1.ai90rem,function(){var _3R=$k[--$j];if(_3R>=48&&_3R<=57){var _3S=$k[--$j];$k[$j++]=$f(_3S+1)}});var _3T=$k[--$j];$1[$k[--$j]]=_3T;$k[$j++]="mode";if($gt($1.nalpha,$1.nnums)){$k[$j++]="alpha"}else{if($1.nalpha==0){$k[$j++]="numeric"}else{$k[$j++]="alphanumeric"}}var _3Y=$k[--$j];$1[$k[--$j]]=_3Y;if($f($1.nalpha+$1.nnums)!=$1.ai90rem.length){$1.mode="alphanumeric"}$k[$j++]=Infinity;$q($1.cdf);if($eq($1.mode,"alphanumeric")){$k[$j++]=0}if($eq($1.mode,"numeric")){$k[$j++]=1;$k[$j++]=0}if($eq($1.mode,"alpha")){$k[$j++]=1;$k[$j++]=1}$1.cdf=$a();$k[$j++]="ais1";if($1.ais.length>1){$k[$j++]=$g($1.ais,1)}else{$k[$j++]=-1}var _3l=$k[--$j];$1[$k[--$j]]=_3l;$k[$j++]="vals1";if($1.vals.length>1){$k[$j++]=$g($1.vals,1)}else{$k[$j++]=-1}var _3q=$k[--$j];$1[$k[--$j]]=_3q;if($eq($1.ais1,"21")||$eq($1.ais1,"8004")){$k[$j++]=Infinity;$q($1.cdf);if($eq($1.ais1,"21")){$k[$j++]=1;$k[$j++]=0}else{$k[$j++]=1;$k[$j++]=1}$1.cdf=$a()}else{$k[$j++]=Infinity;$q($1.cdf);$k[$j++]=0;$1.cdf=$a()}$k[$j++]="nval";if($1.npre!=0){$k[$j++]=~~$z($G($1.ai90,0,$1.npre))}else{$k[$j++]=0}var _43=$k[--$j];$1[$k[--$j]]=_43;$x("BDHIJKLNPQRSTVXZ",$G($1.ai90,$1.npre,1));if($k[--$j]){var _49=$k[--$j];var _4A=$k[--$j];$k[$j++]=_49.length;$k[$j++]=_4A;$j--;var _4B=$k[--$j];var _4C=$k[--$j];$k[$j++]=_4B;$k[$j++]=_4C;$j--;$1.aval=$k[--$j]}else{$j--;$1.aval=-1}if($1.nval<31&&$1.aval!=-1){var _4H=$Z($s(5),"00000");var _4K=$R($s(5),$1.nval,2);$P(_4H,5-_4K.length,_4K);$k[$j++]=_4H;$k[$j++]=Infinity;var _4L=$k[--$j];var _4M=$k[--$j];$k[$j++]=_4L;$F(_4M,function(){var _4N=$k[--$j];$k[$j++]=$f(_4N-48)});$1.nbits=$a();var _4Q=$Z($s(4),"0000");var _4T=$R($s(4),$1.aval,2);$P(_4Q,4-_4T.length,_4T);$k[$j++]=_4Q;$k[$j++]=Infinity;var _4U=$k[--$j];var _4V=$k[--$j];$k[$j++]=_4U;$F(_4V,function(){var _4W=$k[--$j];$k[$j++]=$f(_4W-48)});$1.abits=$a();$k[$j++]=Infinity;$q($1.cdf);$q($1.nbits);$q($1.abits);$1.cdf=$a()}else{var _4d=$Z($s(10),"0000000000");var _4g=$R($s(10),$1.nval,2);$P(_4d,10-_4g.length,_4g);$k[$j++]=_4d;$k[$j++]=Infinity;var _4h=$k[--$j];var _4i=$k[--$j];$k[$j++]=_4h;$F(_4i,function(){var _4j=$k[--$j];$k[$j++]=$f(_4j-48)});$1.nbits=$a();var _4m=$Z($s(5),"00000");var _4r=$R($s(5),$f($g($1.ai90,$1.npre)-65),2);$P(_4m,5-_4r.length,_4r);$k[$j++]=_4m;$k[$j++]=Infinity;var _4s=$k[--$j];var _4t=$k[--$j];$k[$j++]=_4s;$F(_4t,function(){var _4u=$k[--$j];$k[$j++]=$f(_4u-48)});$1.abits=$a();$k[$j++]=Infinity;$q($1.cdf);$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$q($1.nbits);$q($1.abits);$1.cdf=$a()}if($ne($1.mode,"alpha")){$k[$j++]=Infinity;$F($1.ai90rem);if($1.ais.length>1){$k[$j++]=$1.fnc1}$1.gpf=$a()}else{$k[$j++]=Infinity;$F($1.ai90rem);if($1.ais.length>1){$k[$j++]=$1.fnc1}$1.in=$a();$1.out=$a($1.in.length*6);$1.j=0;for(var _5D=0,_5C=$1.in.length-1;_5D<=_5C;_5D+=1){var _5H=$g($1.alpha,$g($1.in,_5D));$k[$j++]=_5H;$k[$j++]=_5H;$k[$j++]=Infinity;var _5I=$k[--$j];var _5J=$k[--$j];$k[$j++]=_5I;$F(_5J,function(){var _5K=$k[--$j];$k[$j++]=$f(_5K-48)});$P($1.out,$1.j,$a());$1.j=$k[--$j].length+$1.j}$k[$j++]=Infinity;$q($1.cdf);$q($G($1.out,0,$1.j));$1.cdf=$a();$1.gpf=$a([]);if($1.ais.length>1){$1.mode="numeric"}}$1.ais=$G($1.ais,1,$1.ais.length-1);$1.vals=$G($1.vals,1,$1.vals.length-1);if($1.ais.length!=0){if($eq($1.ais1,"21")||$eq($1.ais1,"8004")){$k[$j++]=Infinity;$q($1.gpf);$F($1.vals1);$1.gpf=$a();$1.ais=$G($1.ais,1,$1.ais.length-1);$1.vals=$G($1.vals,1,$1.vals.length-1);if($1.ais.length!=0){$k[$j++]=Infinity;$q($1.gpf);$k[$j++]=$1.fnc1;$1.gpf=$a()}}}}if($eq($1.method,"0")){$1.cdf=$a([0]);$1.gpf=$a([]);$1.mode="numeric"}$1.aifixed=new Map;$k[$j++]=Infinity;for(var _5w=0;_5w<=4;_5w+=1){$k[$j++]=_5w}var _5x=$a();for(var _5y=0,_5z=_5x.length;_5y<_5z;_5y++){var _62=$Z($s(2),"00");$p(_62,1,$f($g(_5x,_5y)+48));$p($1.aifixed,_62,_62)}$k[$j++]=Infinity;for(var _64=11;_64<=20;_64+=1){$k[$j++]=_64}$k[$j++]=23;for(var _65=31;_65<=36;_65+=1){$k[$j++]=_65}$k[$j++]=41;var _66=$a();for(var _67=0,_68=_66.length;_67<_68;_67++){var _6B=$R($s(2),$g(_66,_67),10);$p($1.aifixed,_6B,_6B)}for(var _6F=0,_6E=$1.ais.length-1;_6F<=_6E;_6F+=1){$1.i=_6F;$1.ai=$g($1.ais,$1.i);$1.val=$g($1.vals,$1.i);var _6P=$a($1.gpf.length+$1.ai.length+$1.val.length);$P(_6P,0,$1.gpf);$k[$j++]=_6P;$k[$j++]=_6P;$k[$j++]=$1.gpf.length;$k[$j++]=$1.ai;$k[$j++]=Infinity;var _6T=$k[--$j];var _6U=$k[--$j];$k[$j++]=_6T;$F(_6U);var _6V=$a();var _6W=$k[--$j];$P($k[--$j],_6W,_6V);var _6Y=$k[--$j];$k[$j++]=_6Y;$k[$j++]=_6Y;$k[$j++]=$1.gpf.length+$1.ai.length;$k[$j++]=$1.val;$k[$j++]=Infinity;var _6c=$k[--$j];var _6d=$k[--$j];$k[$j++]=_6c;$F(_6d);var _6e=$a();var _6f=$k[--$j];$P($k[--$j],_6f,_6e);$1.gpf=$k[--$j];var _6n=$g($1.aifixed,$G($1.ai,0,2))!==undefined;if($1.i!=$1.ais.length-1&&!_6n){var _6p=$a($1.gpf.length+1);$P(_6p,0,$1.gpf);$p(_6p,$1.gpf.length,$1.fnc1);$1.gpf=_6p}}$1.rembits=function(){$1.used=$k[--$j];if($ne($1.ccversion,"c")){var _73=new Map([["a",$a([$a([167,138,118,108,88,78,59]),$a([167,138,118,98,78]),$a([197,167,138,108,78])])],["b",$a([$a([336,296,256,208,160,104,56]),$a([768,648,536,416,304,208,152,112,72,32]),$a([1184,1016,840,672,496,352,264,208,152,96,56])])]]);$1.bitcaps=$g($g(_73,$1.ccversion),$f($1.cccolumns-2));$k[$j++]=-1;$F($1.bitcaps,function(){var _79=$k[--$j];$k[$j++]=_79;if($ge(_79,$1.used)){var _7B=$k[--$j];var _7C=$k[--$j];$k[$j++]=_7B;$k[$j++]=_7C}$j--});var _7D=$k[--$j];$k[$j++]=_7D;if(_7D!=-1){var _7F=$k[--$j];$k[$j++]=$f(_7F-$1.used)}}else{var _7G=new Map([["c",-1]]);var _7I=$g(_7G,$1.ccversion);var _7K=~~Math.ceil($1.used/8);$1.m=~~(_7K/6)*5+_7K%6;if($1.m<=40){$k[$j++]=8}if($1.m>=41&&$1.m<=160){$k[$j++]=16}if($1.m>=161&&$1.m<=320){$k[$j++]=32}if($1.m>=321&&$1.m<=833){$k[$j++]=64}if($1.m>=834){$k[$j++]=32}$1.eccws=$k[--$j];$1.m=$f($f($1.m+$1.eccws)+3);for(;;){if(~~Math.ceil($1.m/$1.cccolumns)<=30||$1.cccolumns>=30){break}$1.cccolumns=$f($1.cccolumns+1)}$1.r=~~Math.ceil($1.m/$1.cccolumns);var _7f=$f($f($1.cccolumns*$1.r-$1.eccws)-3);$1.tgt=$f(~~(_7f/5)*6+_7f%5)*8;if($1.used<=8304){$k[$j++]=$f($1.tgt-$1.used)}else{$k[$j++]=-1}}var _7j=$k[--$j];$k[$j++]=_7j;if(_7j==-1){$j--;if($eq($1.ccversion,"a")){$1.ccversion="b"}else{if($eq($1.ccversion,"b")&&$eq($1.lintype,"gs1-128")){$1.ccversion="c";$1.cccolumns=~~(($1.linwidth-52)/17)}else{$1.ccversion=-1}}$k[$j++]=$1.used;$1.rembits()}};$1.encode=function(){var _7p=$k[--$j];$k[$j++]=_7p;if($ne(_7p,"raw")){var _7q=$k[--$j];var _7s=$g(_7q,$k[--$j]);$k[$j++]=_7s}else{$j--}$k[$j++]=Infinity;var _7t=$k[--$j];var _7u=$k[--$j];$k[$j++]=_7t;$F(_7u,function(){var _7v=$k[--$j];$k[$j++]=$f(_7v-48)});var _7w=$a();$P($1.gpfenc,$1.j,_7w);$1.j=_7w.length+$1.j};$k[$j++]=Infinity;for(var _81=0,_82=$1.gpf.length;_81<_82;_81++){$k[$j++]=0}$k[$j++]=0;$k[$j++]=-1;$1.numericruns=$a();$k[$j++]=Infinity;for(var _85=0,_86=$1.gpf.length;_85<_86;_85++){$k[$j++]=0}$k[$j++]=0;$1.alphanumericruns=$a();$k[$j++]=Infinity;for(var _89=0,_8A=$1.gpf.length;_89<_8A;_89++){$k[$j++]=0}$k[$j++]=9999;$1.nextiso646only=$a();for(var _8D=$1.gpf.length-1;_8D>=0;_8D-=1){$1.i=_8D;var _8I=$Z($s(2),"00");var _8L=$g($1.gpf,$1.i);$k[$j++]=$g($1.gpf,$1.i);$k[$j++]=_8I;$k[$j++]=_8I;$k[$j++]=0;$k[$j++]=_8L;if(_8L==$1.fnc1){$j--;$k[$j++]=94}var _8N=$k[--$j];var _8O=$k[--$j];$p($k[--$j],_8O,_8N);if($1.i<$1.gpf.length-1){var _8S=$k[--$j];var _8V=$g($1.gpf,$1.i+1);$k[$j++]=_8S;$k[$j++]=_8S;$k[$j++]=1;$k[$j++]=_8V;if(_8V==$1.fnc1){$j--;$k[$j++]=94}var _8X=$k[--$j];var _8Y=$k[--$j];$p($k[--$j],_8Y,_8X)}var _8c=$g($1.numeric,$k[--$j])!==undefined;if(_8c){$p($1.numericruns,$1.i,$f($g($1.numericruns,$1.i+2)+2))}else{$p($1.numericruns,$1.i,0)}var _8k=$k[--$j];var _8m=$g($1.alphanumeric,_8k)!==undefined;$k[$j++]=_8k;if(_8m){$p($1.alphanumericruns,$1.i,$f($g($1.alphanumericruns,$1.i+1)+1))}else{$p($1.alphanumericruns,$1.i,0)}var _8u=$k[--$j];var _8w=$g($1.iso646,_8u)!==undefined;var _8y=$g($1.alphanumeric,_8u)!==undefined;if(_8w&&!_8y){$p($1.nextiso646only,$1.i,0)}else{$p($1.nextiso646only,$1.i,$f($g($1.nextiso646only,$1.i+1)+1))}}$1.gpfenc=$a(8304);$1.i=0;$1.j=0;for(;;){if($1.i==$1.gpf.length){break}for(;;){if($eq($1.mode,"numeric")){if($1.i<=$1.gpf.length-2){var _9C=$s(2);var _9F=$g($1.gpf,$1.i);$k[$j++]=_9C;$k[$j++]=_9C;$k[$j++]=0;$k[$j++]=_9F;if(_9F==$1.fnc1){$j--;$k[$j++]=94}var _9H=$k[--$j];var _9I=$k[--$j];$p($k[--$j],_9I,_9H);var _9K=$k[--$j];var _9N=$g($1.gpf,$1.i+1);$k[$j++]=_9K;$k[$j++]=_9K;$k[$j++]=1;$k[$j++]=_9N;if(_9N==$1.fnc1){$j--;$k[$j++]=94}var _9P=$k[--$j];var _9Q=$k[--$j];$p($k[--$j],_9Q,_9P);var _9S=$k[--$j];var _9U=$g($1.numeric,_9S)!==undefined;$k[$j++]=_9S;if(_9U){$k[$j++]=$1.numeric;$1.encode();$1.i=$1.i+2;break}$j--;$k[$j++]=$1.lalphanumeric;$k[$j++]=$1.numeric;$1.encode();$1.mode="alphanumeric";break}else{var _9b=$g($1.gpf,$1.i);if(_9b<48||_9b>57){$k[$j++]=$1.lalphanumeric;$k[$j++]=$1.numeric;$1.encode();$1.mode="alphanumeric";break}$k[$j++]="rem";$k[$j++]=$1.cdf.length+$1.j;$1.rembits();var _9g=$k[--$j];$1[$k[--$j]]=_9g;if($1.rem>=4&&$1.rem<=6){var _9n=$G($Z($s(6),"000000"),0,$1.rem);var _9s=$R($s(4),$f($g($1.gpf,$1.i)-47),2);$P(_9n,4-_9s.length,_9s);$k[$j++]=_9n;$k[$j++]="raw";$1.encode();$1.i=$1.i+1;break}else{var _9u=$s(2);$p(_9u,0,$g($1.gpf,$1.i));$p(_9u,1,94);$k[$j++]=_9u;$k[$j++]=$1.numeric;$1.encode();$1.i=$1.i+1;break}}}if($eq($1.mode,"alphanumeric")){if($g($1.gpf,$1.i)==$1.fnc1){$k[$j++]=$1.fnc1;$k[$j++]=$1.alphanumeric;$1.encode();$1.mode="numeric";$1.i=$1.i+1;break}var _AA=$g($1.gpf,$1.i);var _AC=$g($1.iso646,_AA)!==undefined;var _AE=$g($1.alphanumeric,_AA)!==undefined;if(_AC&&!_AE){$k[$j++]=$1.liso646;$k[$j++]=$1.alphanumeric;$1.encode();$1.mode="iso646";break}if($g($1.numericruns,$1.i)>=6){$k[$j++]=$1.lnumeric;$k[$j++]=$1.alphanumeric;$1.encode();$1.mode="numeric";break}var _AO=$g($1.numericruns,$1.i);if(_AO>=4&&$f(_AO+$1.i)==$1.gpf.length){$k[$j++]=$1.lnumeric;$k[$j++]=$1.alphanumeric;$1.encode();$1.mode="numeric";break}$k[$j++]=$g($1.gpf,$1.i);$k[$j++]=$1.alphanumeric;$1.encode();$1.i=$1.i+1;break}if($eq($1.mode,"iso646")){if($g($1.gpf,$1.i)==$1.fnc1){$k[$j++]=$1.fnc1;$k[$j++]=$1.iso646;$1.encode();$1.mode="numeric";$1.i=$1.i+1;break}if($g($1.numericruns,$1.i)>=4&&$g($1.nextiso646only,$1.i)>=10){$k[$j++]=$1.lnumeric;$k[$j++]=$1.iso646;$1.encode();$1.mode="numeric";break}if($g($1.alphanumericruns,$1.i)>=5&&$g($1.nextiso646only,$1.i)>=10){$k[$j++]=$1.lalphanumeric;$k[$j++]=$1.iso646;$1.encode();$1.mode="alphanumeric";break}$k[$j++]=$g($1.gpf,$1.i);$k[$j++]=$1.iso646;$1.encode();$1.i=$1.i+1;break}}}$1.gpf=$G($1.gpfenc,0,$1.j);$k[$j++]="pad";$k[$j++]=$1.cdf.length+$1.gpf.length;$1.rembits();var _B7=$a($k[--$j]);$1[$k[--$j]]=_B7;if($1.pad.length>0){for(var _BC=0,_BB=$1.pad.length-1;_BC<=_BB;_BC+=5){$1.i=_BC;var _BD=$1.pad;var _BE=$1.i;var _BF=$a([0,0,1,0,0]);var _BG=$1.pad;var _BH=$1.i;var _BI=5;var _BJ=_BG.length-_BH;if(_BG.length-_BH>5){var _=_BI;_BI=_BJ;_BJ=_}$P(_BD,_BE,$G(_BF,0,_BJ))}if($eq($1.mode,"numeric")){$k[$j++]=Infinity;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$q($1.pad);$1.pad=$G($a(),0,$1.pad.length)}if($eq($1.mode,"alpha")){$k[$j++]=Infinity;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=1;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$k[$j++]=0;$q($1.pad);$1.pad=$G($a(),0,$1.pad.length)}}$k[$j++]=Infinity;$q($1.cdf);$q($1.gpf);$q($1.pad);$1.bits=$a();if($1.ccversion=="a"){$k[$j++]=Infinity;$k[$j++]=$a([0,0,0,0,0,0,1]);for(var _Bb=0,_Bc=68;_Bb<_Bc;_Bb++){$k[$j++]=$a(7)}$1.pwr928=$a();for(var _Bf=1;_Bf<=68;_Bf+=1){$1.j=_Bf;$1.v=0;for(var _Bg=6;_Bg>=1;_Bg-=1){$1.i=_Bg;$1.v=$f($g($g($1.pwr928,$1.j-1),$1.i)*2+~~($1.v/928));$p($g($1.pwr928,$1.j),$1.i,$1.v%928)}$p($g($1.pwr928,$1.j),0,$f($g($g($1.pwr928,$1.j-1),0)*2+~~($1.v/928)))}$k[$j++]=Infinity;for(var _C0=0,_C1=28;_C0<_C1;_C0++){$k[$j++]=0}$1.cws=$a();$1.b=0;$1.c=0;for(;;){if($1.b==$1.bits.length){break}var _C5=$1.bits;var _C6=$1.b;var _C7=$1.bits;var _C8=$1.b;var _C9=_C7.length-_C8;var _CA=69;if(69>_C7.length-_C8){var _=_C9;_C9=_CA;_CA=_}$1.bs=$G(_C5,_C6,_CA);$1.bsl=$1.bs.length;$1.cs=$G($1.cws,$1.c,~~($1.bsl/10)+1);$1.csl=$1.cs.length;for(var _CK=0,_CJ=$1.bsl-1;_CK<=_CJ;_CK+=1){$1.i=_CK;for(var _CN=0,_CM=$1.csl-1;_CN<=_CM;_CN+=1){$1.j=_CN;var _CO=$1.cs;var _CP=$1.j;$p(_CO,_CP,$f($g(_CO,_CP)+$g($g($1.pwr928,$1.i),$1.j+7-$1.csl)*$g($1.bs,$1.bsl-$1.i-1)))}}for(var _Cc=$1.csl-1;_Cc>=1;_Cc-=1){$1.i=_Cc;var _Cd=$1.cs;var _Ce=$1.i;$p(_Cd,_Ce-1,$f($g(_Cd,_Ce-1)+~~($g($1.cs,$1.i)/928)));var _Cj=$1.cs;var _Ck=$1.i;$p(_Cj,_Ck,$g(_Cj,_Ck)%928)}$1.b=$1.b+$1.bsl;$1.c=$1.c+$1.csl}$1.cws=$G($1.cws,0,$1.c);$1.barcode=$s($1.cws.length*4);for(var _Cx=0,_Cw=$1.cws.length-1;_Cx<=_Cw;_Cx+=1){$1.i=_Cx;var _Cz=$Z($s(4),"^ ");var _D4=$R($s(4),$g($1.cws,$1.i),10);$P(_Cz,4-_D4.length,_D4);$P($1.barcode,$1.i*4,_Cz)}delete $1.options["parse"];$p($1.options,"dontdraw",true);$p($1.options,"cca",true);$p($1.options,"columns",$1.cccolumns);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_micropdf417();var _DE=$k[--$j];$1[$k[--$j]]=_DE}if($1.ccversion=="b"){$1.barcode=$s(~~($1.bits.length/8));for(var _DL=0,_DK=$1.barcode.length-1;_DL<=_DK;_DL+=1){$1.i=_DL;var _DO=$G($1.bits,$1.i*8,8);$k[$j++]=0;for(var _DP=0,_DQ=_DO.length;_DP<_DQ;_DP++){var _DS=$k[--$j];$k[$j++]=$f(_DS+$g(_DO,_DP))*2}$p($1.barcode,$1.i,~~($k[--$j]/2))}delete $1.options["parse"];$p($1.options,"dontdraw",true);$p($1.options,"ccb",true);$p($1.options,"columns",$1.cccolumns);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_micropdf417();var _Dd=$k[--$j];$1[$k[--$j]]=_Dd}if($1.ccversion=="c"){$1.barcode=$s(~~($1.bits.length/8));for(var _Dk=0,_Dj=$1.barcode.length-1;_Dk<=_Dj;_Dk+=1){$1.i=_Dk;var _Dn=$G($1.bits,$1.i*8,8);$k[$j++]=0;for(var _Do=0,_Dp=_Dn.length;_Do<_Dp;_Do++){var _Dr=$k[--$j];$k[$j++]=$f(_Dr+$g(_Dn,_Do))*2}$p($1.barcode,$1.i,~~($k[--$j]/2))}delete $1.options["parse"];$p($1.options,"dontdraw",true);$p($1.options,"ccc",true);$p($1.options,"columns",$1.cccolumns);$p($1.options,"eclevel",~~(Math.log($1.eccws)/Math.log(2))-1);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_pdf417();var _E4=$k[--$j];$1[$k[--$j]]=_E4}$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_ean13composite(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$x($1.barcode,"|");if($k[--$j]){$1.linear=$k[--$j];$j--;$1.comp=$k[--$j]}else{$j--}$$.save();$p($1.options,"lintype","ean13");$p($1.options,"inkspread","0");$p($1.options,"dontdraw",true);$k[$j++]=$1.linear;$k[$j++]=$1.options;bwipp_ean13();bwipp_renlinear();$$.rmoveto(-1,72);$k[$j++]=Infinity;$k[$j++]=Infinity;$k[$j++]=0;$k[$j++]=1;for(var _E=0,_F=93;_E<_F;_E++){$k[$j++]=0}$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;for(var _G=0,_H=93;_G<_H;_G++){$k[$j++]=0}$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;for(var _I=0,_J=93;_I<_J;_I++){$k[$j++]=0}$k[$j++]=1;$k[$j++]=0;var _K=$a();$k[$j++]="ren";$k[$j++]=bwipp_renmatrix;$k[$j++]="pixs";$k[$j++]=_K;$k[$j++]="pixx";$k[$j++]=97;$k[$j++]="pixy";$k[$j++]=3;$k[$j++]="height";$k[$j++]=6/72;$k[$j++]="width";$k[$j++]=97/72;$k[$j++]="opt";$k[$j++]=$1.options;var _M=$d();$k[$j++]=_M;bwipp_renmatrix();$$.rmoveto(-2,6);$k[$j++]=$1.comp;$k[$j++]=$1.options;bwipp_gs1_cc();bwipp_renmatrix();$$.restore()}function bwipp_ean8composite(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$x($1.barcode,"|");if($k[--$j]){$1.linear=$k[--$j];$j--;$1.comp=$k[--$j]}else{$j--}$$.save();$p($1.options,"lintype","ean8");$p($1.options,"inkspread","0");$p($1.options,"dontdraw",true);$k[$j++]=$1.linear;$k[$j++]=$1.options;bwipp_ean8();bwipp_renlinear();$$.rmoveto(-1,72);$k[$j++]=Infinity;$k[$j++]=Infinity;$k[$j++]=0;$k[$j++]=1;for(var _E=0,_F=65;_E<_F;_E++){$k[$j++]=0}$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;for(var _G=0,_H=65;_G<_H;_G++){$k[$j++]=0}$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;for(var _I=0,_J=65;_I<_J;_I++){$k[$j++]=0}$k[$j++]=1;$k[$j++]=0;var _K=$a();$k[$j++]="ren";$k[$j++]=bwipp_renmatrix;$k[$j++]="pixs";$k[$j++]=_K;$k[$j++]="pixx";$k[$j++]=69;$k[$j++]="pixy";$k[$j++]=3;$k[$j++]="height";$k[$j++]=6/72;$k[$j++]="width";$k[$j++]=69/72;$k[$j++]="opt";$k[$j++]=$1.options;var _M=$d();$k[$j++]=_M;bwipp_renmatrix();$k[$j++]=$1.comp;$k[$j++]=$1.options;bwipp_gs1_cc();var _P=$k[--$j];$$.rmoveto($f(69-$g(_P,"pixx")),6);$k[$j++]=_P;bwipp_renmatrix();$$.restore()}function bwipp_upcacomposite(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$x($1.barcode,"|");if($k[--$j]){$1.linear=$k[--$j];$j--;$1.comp=$k[--$j]}else{$j--}$$.save();$p($1.options,"lintype","ean13");$p($1.options,"inkspread","0");$p($1.options,"dontdraw",true);$k[$j++]=$1.linear;$k[$j++]=$1.options;bwipp_upca();bwipp_renlinear();$$.rmoveto(-1,72);$k[$j++]=Infinity;$k[$j++]=Infinity;$k[$j++]=0;$k[$j++]=1;for(var _E=0,_F=93;_E<_F;_E++){$k[$j++]=0}$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;for(var _G=0,_H=93;_G<_H;_G++){$k[$j++]=0}$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;for(var _I=0,_J=93;_I<_J;_I++){$k[$j++]=0}$k[$j++]=1;$k[$j++]=0;var _K=$a();$k[$j++]="ren";$k[$j++]=bwipp_renmatrix;$k[$j++]="pixs";$k[$j++]=_K;$k[$j++]="pixx";$k[$j++]=97;$k[$j++]="pixy";$k[$j++]=3;$k[$j++]="height";$k[$j++]=6/72;$k[$j++]="width";$k[$j++]=97/72;$k[$j++]="opt";$k[$j++]=$1.options;var _M=$d();$k[$j++]=_M;bwipp_renmatrix();$$.rmoveto(-2,6);$k[$j++]=$1.comp;$k[$j++]=$1.options;bwipp_gs1_cc();bwipp_renmatrix();$$.restore()}function bwipp_upcecomposite(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$x($1.barcode,"|");if($k[--$j]){$1.linear=$k[--$j];$j--;$1.comp=$k[--$j]}else{$j--}$k[$j++]=Infinity;$k[$j++]=Infinity;$k[$j++]=0;$k[$j++]=1;for(var _9=0,_A=49;_9<_A;_9++){$k[$j++]=0}$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;for(var _B=0,_C=49;_B<_C;_B++){$k[$j++]=0}$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;for(var _D=0,_E=49;_D<_E;_D++){$k[$j++]=0}$k[$j++]=1;$k[$j++]=0;var _F=$a();$k[$j++]="ren";$k[$j++]=bwipp_renmatrix;$k[$j++]="pixs";$k[$j++]=_F;$k[$j++]="pixx";$k[$j++]=53;$k[$j++]="pixy";$k[$j++]=3;$k[$j++]="height";$k[$j++]=6/72;$k[$j++]="width";$k[$j++]=53/72;$k[$j++]="opt";$k[$j++]=$1.options;$1.sep=$d();$$.save();$p($1.options,"lintype","upce");$p($1.options,"inkspread","0");$p($1.options,"dontdraw",true);$k[$j++]=$1.linear;$k[$j++]=$1.options;bwipp_upce();bwipp_renlinear();$$.rmoveto(-1,72);$k[$j++]=Infinity;$k[$j++]=Infinity;$k[$j++]=0;$k[$j++]=1;for(var _N=0,_O=49;_N<_O;_N++){$k[$j++]=0}$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;for(var _P=0,_Q=49;_P<_Q;_P++){$k[$j++]=0}$k[$j++]=0;$k[$j++]=1;$k[$j++]=0;$k[$j++]=1;for(var _R=0,_S=49;_R<_S;_R++){$k[$j++]=0}$k[$j++]=1;$k[$j++]=0;var _T=$a();$k[$j++]="ren";$k[$j++]=bwipp_renmatrix;$k[$j++]="pixs";$k[$j++]=_T;$k[$j++]="pixx";$k[$j++]=53;$k[$j++]="pixy";$k[$j++]=3;$k[$j++]="height";$k[$j++]=6/72;$k[$j++]="width";$k[$j++]=53/72;$k[$j++]="opt";$k[$j++]=$1.options;var _V=$d();$k[$j++]=_V;bwipp_renmatrix();$$.rmoveto(-2,6);$k[$j++]=$1.comp;$k[$j++]=$1.options;bwipp_gs1_cc();bwipp_renmatrix();$$.restore()}function bwipp_databaromnicomposite(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$x($1.barcode,"|");if($k[--$j]){$1.linear=$k[--$j];$j--;$1.comp=$k[--$j]}else{$j--}$$.save();$p($1.options,"lintype","databaromni");$p($1.options,"linkage",true);$p($1.options,"inkspread","0");$p($1.options,"dontdraw",true);$k[$j++]=$1.linear;$k[$j++]=$1.options;bwipp_databaromni();var _F=$k[--$j];$1.linsbs=$g(_F,"sbs");$1.linheight=$g($g(_F,"bhs"),0)*72;$k[$j++]=_F;bwipp_renlinear();$1.sepfinder=function(){$1.fp=$k[--$j];for(var _N=$1.fp,_M=$f($1.fp+12);_N<=_M;_N+=1){$1.i=_N;if($g($1.bot,$1.i)==0){if($g($1.bot,$1.i-1)==1){$k[$j++]=1}else{var _X=$g($1.sep,$1.i-1)==0?1:0;$k[$j++]=_X}}else{$k[$j++]=0}$p($1.sep,$1.i,$k[--$j])}$1.f3=$a([1,1,1,1,1,1,1,1,1,0,1,1,1]);$k[$j++]=true;for(var _c=0;_c<=12;_c+=1){var _i=$k[--$j];$k[$j++]=_i&&$eq($g($1.bot,$f(_c+$1.fp)),$g($1.f3,_c))}if($k[--$j]){$P($1.sep,$1.fp,$a([0,0,0,0,0,0,0,0,0,0,1,0,0]))}};$k[$j++]=Infinity;$k[$j++]=0;$F($1.linsbs,function(){var _o=$k[--$j];var _p=$k[--$j];var _q=_p==1?0:1;$k[$j++]=_p;for(var _r=0,_s=_o;_r<_s;_r++){$k[$j++]=_q}});$r($a($m()-1));$1.bot=$k[--$j];$j-=2;$k[$j++]=Infinity;$F($1.bot,function(){var _x=$k[--$j];$k[$j++]=$f(1-_x)});$1.sep=$a();$P($1.sep,0,$a([0,0,0]));$P($1.sep,$1.sep.length-4,$a([0,0,0,0]));$k[$j++]=18;$1.sepfinder();$k[$j++]=64;$1.sepfinder();$$.rmoveto(0,$1.linheight);var _19=new Map([["ren",bwipp_renmatrix],["pixs",$1.sep],["pixx",$1.sep.length],["pixy",1],["height",1/72],["width",$1.sep.length/72],["opt",$1.options]]);$k[$j++]=_19;bwipp_renmatrix();$$.rmoveto(-5,1);$k[$j++]=$1.comp;$k[$j++]=$1.options;bwipp_gs1_cc();bwipp_renmatrix();$$.restore()}function bwipp_databarstackedcomposite(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$x($1.barcode,"|");if($k[--$j]){$1.linear=$k[--$j];$j--;$1.comp=$k[--$j]}else{$j--}$$.save();$p($1.options,"lintype","databarstacked");$p($1.options,"linkage",true);$p($1.options,"inkspread","0");$p($1.options,"dontdraw",true);$k[$j++]=$1.linear;$k[$j++]=$1.options;bwipp_databarstacked();var _F=$k[--$j];$1.bot=$G($g(_F,"pixs"),0,$g(_F,"pixx"));$1.linheight=$g(_F,"pixy");$k[$j++]=_F;bwipp_renmatrix();$1.sepfinder=function(){$1.fp=$k[--$j];for(var _O=$1.fp,_N=$f($1.fp+12);_O<=_N;_O+=1){$1.i=_O;if($g($1.bot,$1.i)==0){if($g($1.bot,$1.i-1)==1){$k[$j++]=1}else{var _Y=$g($1.sep,$1.i-1)==0?1:0;$k[$j++]=_Y}}else{$k[$j++]=0}$p($1.sep,$1.i,$k[--$j])}$1.f3=$a([1,1,1,1,1,1,1,1,1,0,1,1,1]);$k[$j++]=true;for(var _d=0;_d<=12;_d+=1){var _j=$k[--$j];$k[$j++]=_j&&$eq($g($1.bot,$f(_d+$1.fp)),$g($1.f3,_d))}if($k[--$j]){$P($1.sep,$1.fp,$a([0,0,0,0,0,0,0,0,0,0,1,0,0]))}};$k[$j++]=Infinity;var _o=$1.bot;for(var _p=0,_q=_o.length;_p<_q;_p++){$k[$j++]=$f(1-$g(_o,_p))}$1.sep=$a();$P($1.sep,0,$a([0,0,0,0]));$P($1.sep,$1.sep.length-4,$a([0,0,0,0]));$k[$j++]=18;$1.sepfinder();$$.rmoveto(0,$1.linheight);var _13=new Map([["ren",bwipp_renmatrix],["pixs",$1.sep],["pixx",$1.sep.length],["pixy",1],["height",1/72],["width",$1.sep.length/72],["opt",$1.options]]);$k[$j++]=_13;bwipp_renmatrix();$$.rmoveto(1,1);$k[$j++]=$1.comp;$k[$j++]=$1.options;bwipp_gs1_cc();bwipp_renmatrix();$$.restore()}function bwipp_databarstackedomnicomposite(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$x($1.barcode,"|");if($k[--$j]){$1.linear=$k[--$j];$j--;$1.comp=$k[--$j]}else{$j--}$$.save();$p($1.options,"lintype","databarstackedomni");$p($1.options,"linkage",true);$p($1.options,"inkspread","0");$p($1.options,"dontdraw",true);$k[$j++]=$1.linear;$k[$j++]=$1.options;bwipp_databarstackedomni();var _F=$k[--$j];$1.bot=$G($g(_F,"pixs"),0,$g(_F,"pixx"));$1.linheight=$g(_F,"pixy");$k[$j++]=_F;bwipp_renmatrix();$1.sepfinder=function(){$1.fp=$k[--$j];for(var _O=$1.fp,_N=$f($1.fp+12);_O<=_N;_O+=1){$1.i=_O;if($g($1.bot,$1.i)==0){if($g($1.bot,$1.i-1)==1){$k[$j++]=1}else{var _Y=$g($1.sep,$1.i-1)==0?1:0;$k[$j++]=_Y}}else{$k[$j++]=0}$p($1.sep,$1.i,$k[--$j])}$1.f3=$a([1,1,1,1,1,1,1,1,1,0,1,1,1]);$k[$j++]=true;for(var _d=0;_d<=12;_d+=1){var _j=$k[--$j];$k[$j++]=_j&&$eq($g($1.bot,$f(_d+$1.fp)),$g($1.f3,_d))}if($k[--$j]){$P($1.sep,$1.fp,$a([0,0,0,0,0,0,0,0,0,0,1,0,0]))}};$k[$j++]=Infinity;var _o=$1.bot;for(var _p=0,_q=_o.length;_p<_q;_p++){$k[$j++]=$f(1-$g(_o,_p))}$1.sep=$a();$P($1.sep,0,$a([0,0,0,0]));$P($1.sep,$1.sep.length-4,$a([0,0,0,0]));$k[$j++]=18;$1.sepfinder();$$.rmoveto(0,$1.linheight);var _13=new Map([["ren",bwipp_renmatrix],["pixs",$1.sep],["pixx",$1.sep.length],["pixy",1],["height",1/72],["width",$1.sep.length/72],["opt",$1.options]]);$k[$j++]=_13;bwipp_renmatrix();$$.rmoveto(1,1);$k[$j++]=$1.comp;$k[$j++]=$1.options;bwipp_gs1_cc();bwipp_renmatrix();$$.restore()}function bwipp_databartruncatedcomposite(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$x($1.barcode,"|");if($k[--$j]){$1.linear=$k[--$j];$j--;$1.comp=$k[--$j]}else{$j--}$$.save();$p($1.options,"lintype","databartruncated");$p($1.options,"linkage",true);$p($1.options,"inkspread","0");$p($1.options,"dontdraw",true);$k[$j++]=$1.linear;$k[$j++]=$1.options;bwipp_databartruncated();var _F=$k[--$j];$1.linsbs=$g(_F,"sbs");$1.linheight=$g($g(_F,"bhs"),0)*72;$k[$j++]=_F;bwipp_renlinear();$1.sepfinder=function(){$1.fp=$k[--$j];for(var _N=$1.fp,_M=$f($1.fp+12);_N<=_M;_N+=1){$1.i=_N;if($g($1.bot,$1.i)==0){if($g($1.bot,$1.i-1)==1){$k[$j++]=1}else{var _X=$g($1.sep,$1.i-1)==0?1:0;$k[$j++]=_X}}else{$k[$j++]=0}$p($1.sep,$1.i,$k[--$j])}$1.f3=$a([1,1,1,1,1,1,1,1,1,0,1,1,1]);$k[$j++]=true;for(var _c=0;_c<=12;_c+=1){var _i=$k[--$j];$k[$j++]=_i&&$eq($g($1.bot,$f(_c+$1.fp)),$g($1.f3,_c))}if($k[--$j]){$P($1.sep,$1.fp,$a([0,0,0,0,0,0,0,0,0,0,1,0,0]))}};$k[$j++]=Infinity;$k[$j++]=0;$F($1.linsbs,function(){var _o=$k[--$j];var _p=$k[--$j];var _q=_p==1?0:1;$k[$j++]=_p;for(var _r=0,_s=_o;_r<_s;_r++){$k[$j++]=_q}});$r($a($m()-1));$1.bot=$k[--$j];$j-=2;$k[$j++]=Infinity;$F($1.bot,function(){var _x=$k[--$j];$k[$j++]=$f(1-_x)});$1.sep=$a();$P($1.sep,0,$a([0,0,0]));$P($1.sep,$1.sep.length-4,$a([0,0,0,0]));$k[$j++]=18;$1.sepfinder();$k[$j++]=64;$1.sepfinder();$$.rmoveto(0,$1.linheight);var _19=new Map([["ren",bwipp_renmatrix],["pixs",$1.sep],["pixx",$1.sep.length],["pixy",1],["height",1/72],["width",$1.sep.length/72],["opt",$1.options]]);$k[$j++]=_19;bwipp_renmatrix();$$.rmoveto(-5,1);$k[$j++]=$1.comp;$k[$j++]=$1.options;bwipp_gs1_cc();bwipp_renmatrix();$$.restore()}function bwipp_databarlimitedcomposite(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$x($1.barcode,"|");if($k[--$j]){$1.linear=$k[--$j];$j--;$1.comp=$k[--$j]}else{$j--}$$.save();$p($1.options,"lintype","databarlimited");$p($1.options,"linkage",true);$p($1.options,"inkspread","0");$p($1.options,"dontdraw",true);$k[$j++]=$1.linear;$k[$j++]=$1.options;bwipp_databarlimited();var _F=$k[--$j];$1.linsbs=$g(_F,"sbs");$1.linheight=$g($g(_F,"bhs"),0)*72;$k[$j++]=_F;bwipp_renlinear();$k[$j++]=Infinity;$k[$j++]=1;$F($1.linsbs,function(){var _K=$k[--$j];var _L=$k[--$j];var _M=_L==0?1:0;$k[$j++]=_L;for(var _N=0,_O=_K;_N<_O;_N++){$k[$j++]=_M}});$r($a($m()-1));$1.sep=$k[--$j];$j-=2;$P($1.sep,0,$a([0,0,0]));$P($1.sep,$1.sep.length-9,$a([0,0,0,0,0,0,0,0,0]));$$.rmoveto(0,$1.linheight);var _c=new Map([["ren",bwipp_renmatrix],["pixs",$1.sep],["pixx",$1.sep.length],["pixy",1],["height",1/72],["width",$1.sep.length/72],["opt",$1.options]]);$k[$j++]=_c;bwipp_renmatrix();$k[$j++]=$1.comp;$k[$j++]=$1.options;bwipp_gs1_cc();var _f=$k[--$j];$$.rmoveto($f(72-$g(_f,"pixx")),1);$k[$j++]=_f;bwipp_renmatrix();$$.restore()}function bwipp_databarexpandedcomposite(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$x($1.barcode,"|");if($k[--$j]){$1.linear=$k[--$j];$j--;$1.comp=$k[--$j]}else{$j--}$$.save();$p($1.options,"lintype","databarexpanded");$p($1.options,"linkage",true);$p($1.options,"inkspread","0");$p($1.options,"dontdraw",true);$k[$j++]=$1.linear;$k[$j++]=$1.options;bwipp_databarexpanded();var _F=$k[--$j];$1.linsbs=$g(_F,"sbs");$1.linheight=$g($g(_F,"bhs"),0)*72;$k[$j++]=_F;bwipp_renlinear();$1.sepfinder=function(){$1.fp=$k[--$j];for(var _N=$1.fp,_M=$f($1.fp+12);_N<=_M;_N+=1){$1.i=_N;if($g($1.bot,$1.i)==0){if($g($1.bot,$1.i-1)==1){$k[$j++]=1}else{var _X=$g($1.sep,$1.i-1)==0?1:0;$k[$j++]=_X}}else{$k[$j++]=0}$p($1.sep,$1.i,$k[--$j])}};$k[$j++]=Infinity;$k[$j++]=0;$F($1.linsbs,function(){var _c=$k[--$j];var _d=$k[--$j];var _e=_d==1?0:1;$k[$j++]=_d;for(var _f=0,_g=_c;_f<_g;_f++){$k[$j++]=_e}});$r($a($m()-1));$1.bot=$k[--$j];$j-=2;$k[$j++]=Infinity;$F($1.bot,function(){var _l=$k[--$j];$k[$j++]=$f(1-_l)});$1.sep=$a();$P($1.sep,0,$a([0,0,0]));$P($1.sep,$1.sep.length-4,$a([0,0,0,0]));$k[$j++]=Infinity;for(var _u=18,_t=$1.bot.length-13;_u<=_t;_u+=98){$k[$j++]=_u}for(var _x=69,_w=$1.bot.length-13;_x<=_w;_x+=98){$k[$j++]=_x}var _y=$a();for(var _z=0,_10=_y.length;_z<_10;_z++){$k[$j++]=$g(_y,_z);$1.sepfinder()}$$.rmoveto(0,$1.linheight);var _17=new Map([["ren",bwipp_renmatrix],["pixs",$1.sep],["pixx",$1.sep.length],["pixy",1],["height",1/72],["width",$1.sep.length/72],["opt",$1.options]]);$k[$j++]=_17;bwipp_renmatrix();$$.rmoveto(1,1);$k[$j++]=$1.comp;$k[$j++]=$1.options;bwipp_gs1_cc();bwipp_renmatrix();$$.restore()}function bwipp_databarexpandedstackedcomposite(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$x($1.barcode,"|");if($k[--$j]){$1.linear=$k[--$j];$j--;$1.comp=$k[--$j]}else{$j--}$$.save();$p($1.options,"lintype","databarexpandedstacked");$p($1.options,"linkage",true);$p($1.options,"inkspread","0");$p($1.options,"dontdraw",true);$k[$j++]=$1.linear;$k[$j++]=$1.options;bwipp_databarexpandedstacked();var _F=$k[--$j];$1.bot=$G($g(_F,"pixs"),0,$g(_F,"pixx"));$1.linheight=$g(_F,"pixy");$k[$j++]=_F;bwipp_renmatrix();$1.sepfinder=function(){$1.fp=$k[--$j];for(var _O=$1.fp,_N=$f($1.fp+12);_O<=_N;_O+=1){$1.i=_O;if($g($1.bot,$1.i)==0){if($g($1.bot,$1.i-1)==1){$k[$j++]=1}else{var _Y=$g($1.sep,$1.i-1)==0?1:0;$k[$j++]=_Y}}else{$k[$j++]=0}$p($1.sep,$1.i,$k[--$j])}};$k[$j++]=Infinity;var _c=$1.bot;for(var _d=0,_e=_c.length;_d<_e;_d++){$k[$j++]=$f(1-$g(_c,_d))}$1.sep=$a();$P($1.sep,0,$a([0,0,0,0]));$P($1.sep,$1.sep.length-4,$a([0,0,0,0]));$k[$j++]=Infinity;for(var _o=19,_n=$1.bot.length-13;_o<=_n;_o+=98){$k[$j++]=_o}for(var _r=70,_q=$1.bot.length-13;_r<=_q;_r+=98){$k[$j++]=_r}var _s=$a();for(var _t=0,_u=_s.length;_t<_u;_t++){$k[$j++]=$g(_s,_t);$1.sepfinder()}$$.rmoveto(0,$1.linheight);var _11=new Map([["ren",bwipp_renmatrix],["pixs",$1.sep],["pixx",$1.sep.length],["pixy",1],["height",1/72],["width",$1.sep.length/72],["opt",$1.options]]);$k[$j++]=_11;bwipp_renmatrix();var _14=$g($1.bot,0)==0?2:0;$$.rmoveto(_14,1);$k[$j++]=$1.comp;$k[$j++]=$1.options;bwipp_gs1_cc();bwipp_renmatrix();$$.restore()}function bwipp_gs1_128composite(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$x($1.barcode,"|");if($k[--$j]){$1.linear=$k[--$j];$j--;$1.comp=$k[--$j]}else{$j--}$$.save();$p($1.options,"inkspread","0");$p($1.options,"dontdraw",true);$p($1.options,"linkagea",true);$k[$j++]=$1.linear;$k[$j++]=Infinity;$F($1.options);var _E=$d();$k[$j++]=_E;bwipp_gs1_128();var _G=$g($k[--$j],"sbs");$k[$j++]=0;$F(_G,function(){var _H=$k[--$j];var _I=$k[--$j];$k[$j++]=$f(_I+_H)});$1.linwidth=$k[--$j];$p($1.options,"lintype","gs1-128");$p($1.options,"linwidth",$1.linwidth);$k[$j++]=$1.comp;$k[$j++]=$1.options;bwipp_gs1_cc();$1.compsym=$k[--$j];if($g($1.compsym,"pixx")==99){$k[$j++]="a"}else{$k[$j++]="c"}$1.linktype=$k[--$j];if($eq($1.linktype,"a")){$p($1.options,"linkagea",true);$p($1.options,"linkagec",false)}else{$p($1.options,"linkagea",false);$p($1.options,"linkagec",true)}$k[$j++]=$1.linear;$k[$j++]=Infinity;$F($1.options);var _a=$d();$k[$j++]=_a;bwipp_gs1_128();var _b=$k[--$j];$1.linsbs=$g(_b,"sbs");$1.linheight=$g($g(_b,"bhs"),0)*72;$k[$j++]=_b;bwipp_renlinear();$k[$j++]=Infinity;$k[$j++]=1;$F($1.linsbs,function(){var _g=$k[--$j];var _h=$k[--$j];var _i=_h==0?1:0;$k[$j++]=_h;for(var _j=0,_k=_g;_j<_k;_j++){$k[$j++]=_i}});$r($a($m()-1));$1.sep=$k[--$j];$j-=2;$$.rmoveto(0,$1.linheight);var _r=$1.sep;var _t=new Map([["ren",bwipp_renmatrix],["pixs",$1.sep],["pixx",$1.sep.length],["pixy",1],["height",1/72],["width",_r.length/72],["opt",$1.options]]);$k[$j++]=_t;bwipp_renmatrix();if($eq($1.linktype,"a")){$1.s=~~($f($1.linwidth-2)/11);$1.p=~~(($1.s-9)/2);$k[$j++]="x";$k[$j++]=($1.s-$1.p-1)*11+10;if($1.p==0){var _10=$k[--$j];$k[$j++]=$f(_10+2)}var _11=$k[--$j];$1[$k[--$j]]=$f(_11-99);$$.rmoveto($1.x,1)}else{$$.rmoveto(-7,1)}$k[$j++]=$1.compsym;bwipp_renmatrix();$$.restore()}function bwipp_gs1datamatrix(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.parse=false;$1.dontlint=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.expand=function(){var _6=new Map([["parse",$1.parse],["parseonly",true],["parsefnc",false]]);$1.fncvals=_6;$k[$j++]=$1.fncvals;bwipp_parseinput()};$1.ais=$a([]);$1.vals=$a([]);var _A=$1.barcode;$k[$j++]=$G(_A,1,_A.length-1);for(;;){var _C=$k[--$j];$k[$j++]=_C;if($eq(_C,"")){break}$x($k[--$j],")");$j--;var _E=$k[--$j];var _F=$k[--$j];$k[$j++]=_E;$k[$j++]=_F;$j--;var _G=$k[--$j];var _H=$k[--$j];$k[$j++]=_G;$x(_H,"(");if($k[--$j]){var _J=$k[--$j];var _K=$k[--$j];$k[$j++]=_J;$k[$j++]=_K;$j--;var _L=$k[--$j];var _M=$k[--$j];var _N=$k[--$j];$k[$j++]=_M;$k[$j++]=_N;$k[$j++]=_L}else{var _O=$k[--$j];var _P=$k[--$j];$k[$j++]="";$k[$j++]=_P;$k[$j++]=_O}$k[$j++]=Infinity;$q($1.ais);var _S=$k[$j-1-($m()+2)];$k[$j++]=_S;$1.ais=$a();$k[$j++]=Infinity;$q($1.vals);var _W=$k[$j-1-($m()+1)];$k[$j++]=_W;$1.expand();$1.vals=$a();$j-=2}$j--;if(!$1.dontlint){$k[$j++]=$1.ais;$k[$j++]=$1.vals;bwipp_gs1lint();$j--}$1.aifixed=new Map;$k[$j++]=Infinity;for(var _b=0;_b<=4;_b+=1){$k[$j++]=_b}var _c=$a();for(var _d=0,_e=_c.length;_d<_e;_d++){var _h=$Z($s(2),"00");$p(_h,1,$f($g(_c,_d)+48));$p($1.aifixed,_h,_h)}$k[$j++]=Infinity;for(var _j=11;_j<=20;_j+=1){$k[$j++]=_j}$k[$j++]=23;for(var _k=31;_k<=36;_k+=1){$k[$j++]=_k}$k[$j++]=41;var _l=$a();for(var _m=0,_n=_l.length;_m<_n;_m++){var _q=$R($s(2),$g(_l,_m),10);$p($1.aifixed,_q,_q)}$1.fnc1=-1;$1.dmtx=$a([$1.fnc1]);for(var _w=0,_v=$1.ais.length-1;_w<=_v;_w+=1){$1.i=_w;$1.ai=$g($1.ais,$1.i);$1.val=$g($1.vals,$1.i);var _16=$a($1.dmtx.length+$1.ai.length+$1.val.length);$P(_16,0,$1.dmtx);$k[$j++]=_16;$k[$j++]=_16;$k[$j++]=$1.dmtx.length;$k[$j++]=$1.ai;$k[$j++]=Infinity;var _1A=$k[--$j];var _1B=$k[--$j];$k[$j++]=_1A;$F(_1B);var _1C=$a();var _1D=$k[--$j];$P($k[--$j],_1D,_1C);var _1F=$k[--$j];$k[$j++]=_1F;$k[$j++]=_1F;$k[$j++]=$1.dmtx.length+$1.ai.length;$k[$j++]=$1.val;$k[$j++]=Infinity;var _1J=$k[--$j];var _1K=$k[--$j];$k[$j++]=_1J;$F(_1K);var _1L=$a();var _1M=$k[--$j];$P($k[--$j],_1M,_1L);$1.dmtx=$k[--$j];var _1U=$g($1.aifixed,$G($1.ai,0,2))!==undefined;if($1.i!=$1.ais.length-1&&!_1U){var _1W=$a($1.dmtx.length+1);$P(_1W,0,$1.dmtx);$p(_1W,$1.dmtx.length,$1.fnc1);$1.dmtx=_1W}}$1.barcode=$s(($1.dmtx.length+1)*5);$1.i=0;$1.j=0;for(;;){if($1.i==$1.dmtx.length){break}var _1g=$g($1.dmtx,$1.i);$k[$j++]=_1g;if(_1g==$1.fnc1){$j--;$P($1.barcode,$1.j,"^FNC1");$1.j=$1.j+4}else{$p($1.barcode,$1.j,$k[--$j])}$1.i=$1.i+1;$1.j=$1.j+1}$1.barcode=$G($1.barcode,0,$1.j);delete $1.options["parse"];$p($1.options,"dontdraw",true);$p($1.options,"parsefnc",true);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_datamatrix();var _1y=$k[--$j];$1[$k[--$j]]=_1y;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_gs1datamatrixrectangular(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.parse=false;$1.dontlint=false;$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.expand=function(){var _6=new Map([["parse",$1.parse],["parseonly",true],["parsefnc",false]]);$1.fncvals=_6;$k[$j++]=$1.fncvals;bwipp_parseinput()};$1.ais=$a([]);$1.vals=$a([]);var _A=$1.barcode;$k[$j++]=$G(_A,1,_A.length-1);for(;;){var _C=$k[--$j];$k[$j++]=_C;if($eq(_C,"")){break}$x($k[--$j],")");$j--;var _E=$k[--$j];var _F=$k[--$j];$k[$j++]=_E;$k[$j++]=_F;$j--;var _G=$k[--$j];var _H=$k[--$j];$k[$j++]=_G;$x(_H,"(");if($k[--$j]){var _J=$k[--$j];var _K=$k[--$j];$k[$j++]=_J;$k[$j++]=_K;$j--;var _L=$k[--$j];var _M=$k[--$j];var _N=$k[--$j];$k[$j++]=_M;$k[$j++]=_N;$k[$j++]=_L}else{var _O=$k[--$j];var _P=$k[--$j];$k[$j++]="";$k[$j++]=_P;$k[$j++]=_O}$k[$j++]=Infinity;$q($1.ais);var _S=$k[$j-1-($m()+2)];$k[$j++]=_S;$1.ais=$a();$k[$j++]=Infinity;$q($1.vals);var _W=$k[$j-1-($m()+1)];$k[$j++]=_W;$1.expand();$1.vals=$a();$j-=2}$j--;if(!$1.dontlint){$k[$j++]=$1.ais;$k[$j++]=$1.vals;bwipp_gs1lint();$j--}$1.aifixed=new Map;$k[$j++]=Infinity;for(var _b=0;_b<=4;_b+=1){$k[$j++]=_b}var _c=$a();for(var _d=0,_e=_c.length;_d<_e;_d++){var _h=$Z($s(2),"00");$p(_h,1,$f($g(_c,_d)+48));$p($1.aifixed,_h,_h)}$k[$j++]=Infinity;for(var _j=11;_j<=20;_j+=1){$k[$j++]=_j}$k[$j++]=23;for(var _k=31;_k<=36;_k+=1){$k[$j++]=_k}$k[$j++]=41;var _l=$a();for(var _m=0,_n=_l.length;_m<_n;_m++){var _q=$R($s(2),$g(_l,_m),10);$p($1.aifixed,_q,_q)}$1.fnc1=-1;$1.dmtx=$a([$1.fnc1]);for(var _w=0,_v=$1.ais.length-1;_w<=_v;_w+=1){$1.i=_w;$1.ai=$g($1.ais,$1.i);$1.val=$g($1.vals,$1.i);var _16=$a($1.dmtx.length+$1.ai.length+$1.val.length);$P(_16,0,$1.dmtx);$k[$j++]=_16;$k[$j++]=_16;$k[$j++]=$1.dmtx.length;$k[$j++]=$1.ai;$k[$j++]=Infinity;var _1A=$k[--$j];var _1B=$k[--$j];$k[$j++]=_1A;$F(_1B);var _1C=$a();var _1D=$k[--$j];$P($k[--$j],_1D,_1C);var _1F=$k[--$j];$k[$j++]=_1F;$k[$j++]=_1F;$k[$j++]=$1.dmtx.length+$1.ai.length;$k[$j++]=$1.val;$k[$j++]=Infinity;var _1J=$k[--$j];var _1K=$k[--$j];$k[$j++]=_1J;$F(_1K);var _1L=$a();var _1M=$k[--$j];$P($k[--$j],_1M,_1L);$1.dmtx=$k[--$j];var _1U=$g($1.aifixed,$G($1.ai,0,2))!==undefined;if($1.i!=$1.ais.length-1&&!_1U){var _1W=$a($1.dmtx.length+1);$P(_1W,0,$1.dmtx);$p(_1W,$1.dmtx.length,$1.fnc1);$1.dmtx=_1W}}$1.barcode=$s(($1.dmtx.length+1)*5);$1.i=0;$1.j=0;for(;;){if($1.i==$1.dmtx.length){break}var _1g=$g($1.dmtx,$1.i);$k[$j++]=_1g;if(_1g==$1.fnc1){$j--;$P($1.barcode,$1.j,"^FNC1");$1.j=$1.j+4}else{$p($1.barcode,$1.j,$k[--$j])}$1.i=$1.i+1;$1.j=$1.j+1}$1.barcode=$G($1.barcode,0,$1.j);delete $1.options["parse"];$p($1.options,"dontdraw",true);$p($1.options,"parsefnc",true);$p($1.options,"format","rectangle");$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_datamatrix();var _1z=$k[--$j];$1[$k[--$j]]=_1z;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_gs1qrcode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.parse=false;$1.dontlint=false;$1.dontdraw=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.expand=function(){var _6=new Map([["parse",$1.parse],["parseonly",true],["parsefnc",false]]);$1.fncvals=_6;$k[$j++]=$1.fncvals;bwipp_parseinput()};$1.ais=$a([]);$1.vals=$a([]);var _A=$1.barcode;$k[$j++]=$G(_A,1,_A.length-1);for(;;){var _C=$k[--$j];$k[$j++]=_C;if($eq(_C,"")){break}$x($k[--$j],")");$j--;var _E=$k[--$j];var _F=$k[--$j];$k[$j++]=_E;$k[$j++]=_F;$j--;var _G=$k[--$j];var _H=$k[--$j];$k[$j++]=_G;$x(_H,"(");if($k[--$j]){var _J=$k[--$j];var _K=$k[--$j];$k[$j++]=_J;$k[$j++]=_K;$j--;var _L=$k[--$j];var _M=$k[--$j];var _N=$k[--$j];$k[$j++]=_M;$k[$j++]=_N;$k[$j++]=_L}else{var _O=$k[--$j];var _P=$k[--$j];$k[$j++]="";$k[$j++]=_P;$k[$j++]=_O}$k[$j++]=Infinity;$q($1.ais);var _S=$k[$j-1-($m()+2)];$k[$j++]=_S;$1.ais=$a();$k[$j++]=Infinity;$q($1.vals);var _W=$k[$j-1-($m()+1)];$k[$j++]=_W;$1.expand();$1.vals=$a();$j-=2}$j--;if(!$1.dontlint){$k[$j++]=$1.ais;$k[$j++]=$1.vals;bwipp_gs1lint();$j--}$1.aifixed=new Map;$k[$j++]=Infinity;for(var _b=0;_b<=4;_b+=1){$k[$j++]=_b}var _c=$a();for(var _d=0,_e=_c.length;_d<_e;_d++){var _h=$Z($s(2),"00");$p(_h,1,$f($g(_c,_d)+48));$p($1.aifixed,_h,_h)}$k[$j++]=Infinity;for(var _j=11;_j<=20;_j+=1){$k[$j++]=_j}$k[$j++]=23;for(var _k=31;_k<=36;_k+=1){$k[$j++]=_k}$k[$j++]=41;var _l=$a();for(var _m=0,_n=_l.length;_m<_n;_m++){var _q=$R($s(2),$g(_l,_m),10);$p($1.aifixed,_q,_q)}$1.fnc1=-1;$1.qrc=$a([$1.fnc1]);for(var _w=0,_v=$1.ais.length-1;_w<=_v;_w+=1){$1.i=_w;$1.ai=$g($1.ais,$1.i);$1.val=$g($1.vals,$1.i);var _16=$a($1.qrc.length+$1.ai.length+$1.val.length);$P(_16,0,$1.qrc);$k[$j++]=_16;$k[$j++]=_16;$k[$j++]=$1.qrc.length;$k[$j++]=$1.ai;$k[$j++]=Infinity;var _1A=$k[--$j];var _1B=$k[--$j];$k[$j++]=_1A;$F(_1B);var _1C=$a();var _1D=$k[--$j];$P($k[--$j],_1D,_1C);var _1F=$k[--$j];$k[$j++]=_1F;$k[$j++]=_1F;$k[$j++]=$1.qrc.length+$1.ai.length;$k[$j++]=$1.val;$k[$j++]=Infinity;var _1J=$k[--$j];var _1K=$k[--$j];$k[$j++]=_1J;$F(_1K);var _1L=$a();var _1M=$k[--$j];$P($k[--$j],_1M,_1L);$1.qrc=$k[--$j];var _1U=$g($1.aifixed,$G($1.ai,0,2))!==undefined;if($1.i!=$1.ais.length-1&&!_1U){var _1W=$a($1.qrc.length+1);$P(_1W,0,$1.qrc);$p(_1W,$1.qrc.length,$1.fnc1);$1.qrc=_1W}}$1.barcode=$s(($1.qrc.length+1)*5);$1.i=0;$1.j=0;for(;;){if($1.i==$1.qrc.length){break}var _1g=$g($1.qrc,$1.i);$k[$j++]=_1g;if(_1g==$1.fnc1){$j--;$P($1.barcode,$1.j,"^FNC1");$1.j=$1.j+4}else{$p($1.barcode,$1.j,$k[--$j])}$1.i=$1.i+1;$1.j=$1.j+1}$1.barcode=$G($1.barcode,0,$1.j);delete $1.options["parse"];$p($1.options,"dontdraw",true);$p($1.options,"parsefnc",true);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_qrcode();var _1y=$k[--$j];$1[$k[--$j]]=_1y;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_gs1dotcode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.parse=false;$1.dontlint=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.expand=function(){var _6=new Map([["parse",$1.parse],["parseonly",true],["parsefnc",false]]);$1.fncvals=_6;$k[$j++]=$1.fncvals;bwipp_parseinput()};$1.ais=$a([]);$1.vals=$a([]);var _A=$1.barcode;$k[$j++]=$G(_A,1,_A.length-1);for(;;){var _C=$k[--$j];$k[$j++]=_C;if($eq(_C,"")){break}$x($k[--$j],")");$j--;var _E=$k[--$j];var _F=$k[--$j];$k[$j++]=_E;$k[$j++]=_F;$j--;var _G=$k[--$j];var _H=$k[--$j];$k[$j++]=_G;$x(_H,"(");if($k[--$j]){var _J=$k[--$j];var _K=$k[--$j];$k[$j++]=_J;$k[$j++]=_K;$j--;var _L=$k[--$j];var _M=$k[--$j];var _N=$k[--$j];$k[$j++]=_M;$k[$j++]=_N;$k[$j++]=_L}else{var _O=$k[--$j];var _P=$k[--$j];$k[$j++]="";$k[$j++]=_P;$k[$j++]=_O}$k[$j++]=Infinity;$q($1.ais);var _S=$k[$j-1-($m()+2)];$k[$j++]=_S;$1.ais=$a();$k[$j++]=Infinity;$q($1.vals);var _W=$k[$j-1-($m()+1)];$k[$j++]=_W;$1.expand();$1.vals=$a();$j-=2}$j--;if(!$1.dontlint){$k[$j++]=$1.ais;$k[$j++]=$1.vals;bwipp_gs1lint();$j--}$1.aifixed=new Map;$k[$j++]=Infinity;for(var _b=0;_b<=4;_b+=1){$k[$j++]=_b}var _c=$a();for(var _d=0,_e=_c.length;_d<_e;_d++){var _h=$Z($s(2),"00");$p(_h,1,$f($g(_c,_d)+48));$p($1.aifixed,_h,_h)}$k[$j++]=Infinity;for(var _j=11;_j<=20;_j+=1){$k[$j++]=_j}$k[$j++]=23;for(var _k=31;_k<=36;_k+=1){$k[$j++]=_k}$k[$j++]=41;var _l=$a();for(var _m=0,_n=_l.length;_m<_n;_m++){var _q=$R($s(2),$g(_l,_m),10);$p($1.aifixed,_q,_q)}$1.fnc1=-1;$1.dmtx=$a([$1.fnc1]);for(var _w=0,_v=$1.ais.length-1;_w<=_v;_w+=1){$1.i=_w;$1.ai=$g($1.ais,$1.i);$1.val=$g($1.vals,$1.i);var _16=$a($1.dmtx.length+$1.ai.length+$1.val.length);$P(_16,0,$1.dmtx);$k[$j++]=_16;$k[$j++]=_16;$k[$j++]=$1.dmtx.length;$k[$j++]=$1.ai;$k[$j++]=Infinity;var _1A=$k[--$j];var _1B=$k[--$j];$k[$j++]=_1A;$F(_1B);var _1C=$a();var _1D=$k[--$j];$P($k[--$j],_1D,_1C);var _1F=$k[--$j];$k[$j++]=_1F;$k[$j++]=_1F;$k[$j++]=$1.dmtx.length+$1.ai.length;$k[$j++]=$1.val;$k[$j++]=Infinity;var _1J=$k[--$j];var _1K=$k[--$j];$k[$j++]=_1J;$F(_1K);var _1L=$a();var _1M=$k[--$j];$P($k[--$j],_1M,_1L);$1.dmtx=$k[--$j];var _1U=$g($1.aifixed,$G($1.ai,0,2))!==undefined;if($1.i!=$1.ais.length-1&&!_1U){var _1W=$a($1.dmtx.length+1);$P(_1W,0,$1.dmtx);$p(_1W,$1.dmtx.length,$1.fnc1);$1.dmtx=_1W}}$1.barcode=$s(($1.dmtx.length+1)*5);$1.i=0;$1.j=0;for(;;){if($1.i==$1.dmtx.length){break}var _1g=$g($1.dmtx,$1.i);$k[$j++]=_1g;if(_1g==$1.fnc1){$j--;$P($1.barcode,$1.j,"^FNC1");$1.j=$1.j+4}else{$p($1.barcode,$1.j,$k[--$j])}$1.i=$1.i+1;$1.j=$1.j+1}$1.barcode=$G($1.barcode,0,$1.j);delete $1.options["parse"];$p($1.options,"dontdraw",true);$p($1.options,"parsefnc",true);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_dotcode();var _1y=$k[--$j];$1[$k[--$j]]=_1y;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_hibccode39(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.textxoffset=0;$1.validatecheck=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.textxoffset=+$1.textxoffset;$1.barchars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";$1.charvals=new Map;for(var _9=0;_9<=42;_9+=1){$p($1.charvals,$G($1.barchars,_9,1),_9)}for(var _F=0,_E=$1.barcode.length-1;_F<=_E;_F+=1){var _J=$g($1.charvals,$G($1.barcode,_F,1))!==undefined;if(!_J){$k[$j++]="bwipp.hibccode39badCharacter";$k[$j++]="HIBC Code 39 must contain only digits, capital letters, spaces and the symbols -.$/+%";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _M=$k[--$j];$k[$j++]=$f(_M-1)}var _N=$k[--$j];$1[$k[--$j]]=_N;$1.checksum=41;for(var _R=0,_Q=$f($1.barlen-1);_R<=_Q;_R+=1){$1.checksum=$f($g($1.charvals,$G($1.barcode,_R,1))+$1.checksum)}$1.checksum=$1.checksum%43;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.hibccode39badCheckDigit";$k[$j++]="Incorrect HIBC Code 39 check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen)}var _j=$s($f($1.barlen+2));$P(_j,1,$1.barcode);$1.barcode=_j;$p($1.barcode,0,43);$p($1.barcode,$f($1.barlen+1),$g($1.barchars,$1.checksum));$1.text=$s($f($1.barlen+4));$p($1.text,0,42);$P($1.text,1,$1.barcode);var _10=$g($1.barcode,$f($1.barlen+1));$k[$j++]=$1.text;$k[$j++]=$f($1.barlen+2);$k[$j++]=_10;if(_10==32){$j--;$k[$j++]=95}var _11=$k[--$j];var _12=$k[--$j];$p($k[--$j],_12,_11);$p($1.text,$f($1.barlen+3),42);$p($1.options,"dontdraw",true);$p($1.options,"includecheck",false);$p($1.options,"validatecheck",false);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_code39();var _1B=$k[--$j];$1[$k[--$j]]=_1B;$p($1.args,"txt",$a([$a([$1.text,$1.textxoffset,$1.textyoffset,$1.textfont,$1.textsize])]));$p($1.args,"textxalign","center");$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_hibccode128(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.textfont="OCR-B";$1.textsize=10;$1.textyoffset=-8;$1.textxoffset=0;$1.validatecheck=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textyoffset=+$1.textyoffset;$1.textxoffset=+$1.textxoffset;$1.barchars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";$1.charvals=new Map;for(var _9=0;_9<=42;_9+=1){$p($1.charvals,$G($1.barchars,_9,1),_9)}for(var _F=0,_E=$1.barcode.length-1;_F<=_E;_F+=1){var _J=$g($1.charvals,$G($1.barcode,_F,1))!==undefined;if(!_J){$k[$j++]="bwipp.hibccode128badCharacter";$k[$j++]="HIBC Code 128 must contain only digits, capital letters, spaces and the symbols -.$/+%";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _M=$k[--$j];$k[$j++]=$f(_M-1)}var _N=$k[--$j];$1[$k[--$j]]=_N;$1.checksum=41;for(var _R=0,_Q=$f($1.barlen-1);_R<=_Q;_R+=1){$1.checksum=$f($g($1.charvals,$G($1.barcode,_R,1))+$1.checksum)}$1.checksum=$1.checksum%43;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.hibccode128badCheckDigit";$k[$j++]="Incorrect HIBC Code 128 check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen)}var _j=$s($f($1.barlen+2));$P(_j,1,$1.barcode);$1.barcode=_j;$p($1.barcode,0,43);$p($1.barcode,$f($1.barlen+1),$g($1.barchars,$1.checksum));$1.text=$s($f($1.barlen+4));$p($1.text,0,42);$P($1.text,1,$1.barcode);var _10=$g($1.barcode,$f($1.barlen+1));$k[$j++]=$1.text;$k[$j++]=$f($1.barlen+2);$k[$j++]=_10;if(_10==32){$j--;$k[$j++]=95}var _11=$k[--$j];var _12=$k[--$j];$p($k[--$j],_12,_11);$p($1.text,$f($1.barlen+3),42);$p($1.options,"dontdraw",true);$p($1.options,"validatecheck",false);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_code128();var _1A=$k[--$j];$1[$k[--$j]]=_1A;$p($1.args,"txt",$a([$a([$1.text,$1.textxoffset,$1.textyoffset,$1.textfont,$1.textsize])]));$p($1.args,"textxalign","center");$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_hibcdatamatrix(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.validatecheck=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.barchars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";$1.charvals=new Map;for(var _5=0;_5<=42;_5+=1){$p($1.charvals,$G($1.barchars,_5,1),_5)}for(var _B=0,_A=$1.barcode.length-1;_B<=_A;_B+=1){var _F=$g($1.charvals,$G($1.barcode,_B,1))!==undefined;if(!_F){$k[$j++]="bwipp.hibcdatamatrixBadCharacter";$k[$j++]="HIBC Data Matrix must contain only digits, capital letters, spaces and the symbols -.$/+%";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _I=$k[--$j];$k[$j++]=$f(_I-1)}var _J=$k[--$j];$1[$k[--$j]]=_J;$1.checksum=41;for(var _N=0,_M=$f($1.barlen-1);_N<=_M;_N+=1){$1.checksum=$f($g($1.charvals,$G($1.barcode,_N,1))+$1.checksum)}$1.checksum=$1.checksum%43;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.hibcdatamatrixBadCheckDigit";$k[$j++]="Incorrect HIBC Data Matrix check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen)}var _f=$s($f($1.barlen+2));$P(_f,1,$1.barcode);$1.barcode=_f;$p($1.barcode,0,43);$p($1.barcode,$f($1.barlen+1),$g($1.barchars,$1.checksum));$p($1.options,"dontdraw",true);$p($1.options,"validatecheck",false);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_datamatrix();var _r=$k[--$j];$1[$k[--$j]]=_r;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_hibcdatamatrixrectangular(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.validatecheck=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.barchars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";$1.charvals=new Map;for(var _5=0;_5<=42;_5+=1){$p($1.charvals,$G($1.barchars,_5,1),_5)}for(var _B=0,_A=$1.barcode.length-1;_B<=_A;_B+=1){var _F=$g($1.charvals,$G($1.barcode,_B,1))!==undefined;if(!_F){$k[$j++]="bwipp.hibcdatamatrixrectangularBadCharacter";$k[$j++]="HIBC Data Matrix Rectangular must contain only digits, capital letters, spaces and the symbols -.$/+%";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _I=$k[--$j];$k[$j++]=$f(_I-1)}var _J=$k[--$j];$1[$k[--$j]]=_J;$1.checksum=41;for(var _N=0,_M=$f($1.barlen-1);_N<=_M;_N+=1){$1.checksum=$f($g($1.charvals,$G($1.barcode,_N,1))+$1.checksum)}$1.checksum=$1.checksum%43;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.hibcdatamatrixrectangularBadCheckDigit";$k[$j++]="Incorrect HIBC Data Matrix Rectangular check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen)}var _f=$s($f($1.barlen+2));$P(_f,1,$1.barcode);$1.barcode=_f;$p($1.barcode,0,43);$p($1.barcode,$f($1.barlen+1),$g($1.barchars,$1.checksum));$p($1.options,"dontdraw",true);$p($1.options,"validatecheck",false);$p($1.options,"format","rectangle");var _r=$1.options;$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=_r;bwipp_datamatrix();var _s=$k[--$j];$1[$k[--$j]]=_s;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_hibcpdf417(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.columns=2;$1.validatecheck=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.columns=~~$1.columns;$1.barchars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";$1.charvals=new Map;for(var _6=0;_6<=42;_6+=1){$p($1.charvals,$G($1.barchars,_6,1),_6)}for(var _C=0,_B=$1.barcode.length-1;_C<=_B;_C+=1){var _G=$g($1.charvals,$G($1.barcode,_C,1))!==undefined;if(!_G){$k[$j++]="bwipp.hibcpdf417BadCharacter";$k[$j++]="HIBC PDF417 must contain only digits, capital letters, spaces and the symbols -.$/+%";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _J=$k[--$j];$k[$j++]=$f(_J-1)}var _K=$k[--$j];$1[$k[--$j]]=_K;$1.checksum=41;for(var _O=0,_N=$f($1.barlen-1);_O<=_N;_O+=1){$1.checksum=$f($g($1.charvals,$G($1.barcode,_O,1))+$1.checksum)}$1.checksum=$1.checksum%43;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.hibcpdf417BadCheckDigit";$k[$j++]="Incorrect HIBC PDF417 check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen)}var _g=$s($f($1.barlen+2));$P(_g,1,$1.barcode);$1.barcode=_g;$p($1.barcode,0,43);$p($1.barcode,$f($1.barlen+1),$g($1.barchars,$1.checksum));$p($1.options,"dontdraw",true);var _p=$1.options;$p(_p,"columns",$1.columns);var _r=$1.options;$p(_r,"validatecheck",false);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_pdf417();var _u=$k[--$j];$1[$k[--$j]]=_u;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_hibcmicropdf417(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.columns=2;$1.validatecheck=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.columns=~~$1.columns;$1.barchars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";$1.charvals=new Map;for(var _6=0;_6<=42;_6+=1){$p($1.charvals,$G($1.barchars,_6,1),_6)}for(var _C=0,_B=$1.barcode.length-1;_C<=_B;_C+=1){var _G=$g($1.charvals,$G($1.barcode,_C,1))!==undefined;if(!_G){$k[$j++]="bwipp.hibcmicropdf417BadCharacter";$k[$j++]="HIBC MicroPDF417 must contain only digits, capital letters, spaces and the symbols -.$/+%";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _J=$k[--$j];$k[$j++]=$f(_J-1)}var _K=$k[--$j];$1[$k[--$j]]=_K;$1.checksum=41;for(var _O=0,_N=$f($1.barlen-1);_O<=_N;_O+=1){$1.checksum=$f($g($1.charvals,$G($1.barcode,_O,1))+$1.checksum)}$1.checksum=$1.checksum%43;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.hibcmicropdf417BadCheckDigit";$k[$j++]="Incorrect HIBC MicroPDF417 check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen)}var _g=$s($f($1.barlen+2));$P(_g,1,$1.barcode);$1.barcode=_g;$p($1.barcode,0,43);var _m=$1.checksum;$p($1.barcode,$f($1.barlen+1),$g($1.barchars,_m));$p($1.options,"dontdraw",true);$p($1.options,"columns",$1.columns);var _r=$1.options;$p(_r,"validatecheck",false);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_micropdf417();var _u=$k[--$j];$1[$k[--$j]]=_u;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_hibcqrcode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.validatecheck=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.barchars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";$1.charvals=new Map;for(var _5=0;_5<=42;_5+=1){$p($1.charvals,$G($1.barchars,_5,1),_5)}for(var _B=0,_A=$1.barcode.length-1;_B<=_A;_B+=1){var _F=$g($1.charvals,$G($1.barcode,_B,1))!==undefined;if(!_F){$k[$j++]="bwipp.hibcqrcodeBadCharacter";$k[$j++]="HIBC QR Code must contain only digits, capital letters, spaces and the symbols -.$/+%";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _I=$k[--$j];$k[$j++]=$f(_I-1)}var _J=$k[--$j];$1[$k[--$j]]=_J;$1.checksum=41;for(var _N=0,_M=$f($1.barlen-1);_N<=_M;_N+=1){$1.checksum=$f($g($1.charvals,$G($1.barcode,_N,1))+$1.checksum)}$1.checksum=$1.checksum%43;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.hibcqrcodeBadCheckDigit";$k[$j++]="Incorrect HIBC QR Code check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen)}var _f=$s($f($1.barlen+2));$P(_f,1,$1.barcode);$1.barcode=_f;$p($1.barcode,0,43);$p($1.barcode,$f($1.barlen+1),$g($1.barchars,$1.checksum));$p($1.options,"dontdraw",true);$p($1.options,"validatecheck",false);var _q=$1.options;$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=_q;bwipp_qrcode();var _r=$k[--$j];$1[$k[--$j]]=_r;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_hibccodablockf(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.validatecheck=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.barchars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";$1.charvals=new Map;for(var _5=0;_5<=42;_5+=1){$p($1.charvals,$G($1.barchars,_5,1),_5)}for(var _B=0,_A=$1.barcode.length-1;_B<=_A;_B+=1){var _F=$g($1.charvals,$G($1.barcode,_B,1))!==undefined;if(!_F){$k[$j++]="bwipp.codablockfBadCharacter";$k[$j++]="HIBC Codablock F must contain only digits, capital letters, spaces and the symbols -.$/+%";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _I=$k[--$j];$k[$j++]=$f(_I-1)}var _J=$k[--$j];$1[$k[--$j]]=_J;$1.checksum=41;for(var _N=0,_M=$f($1.barlen-1);_N<=_M;_N+=1){$1.checksum=$f($g($1.charvals,$G($1.barcode,_N,1))+$1.checksum)}$1.checksum=$1.checksum%43;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.hibccodablockfBadCheckDigit";$k[$j++]="Incorrect HIBC Codablock F check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen)}var _f=$s($f($1.barlen+2));$P(_f,1,$1.barcode);$1.barcode=_f;$p($1.barcode,0,43);$p($1.barcode,$f($1.barlen+1),$g($1.barchars,$1.checksum));$p($1.options,"dontdraw",true);$p($1.options,"validatecheck",false);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_codablockf();var _r=$k[--$j];$1[$k[--$j]]=_r;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_hibcazteccode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.validatecheck=false;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.barchars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";$1.charvals=new Map;for(var _5=0;_5<=42;_5+=1){$p($1.charvals,$G($1.barchars,_5,1),_5)}for(var _B=0,_A=$1.barcode.length-1;_B<=_A;_B+=1){var _F=$g($1.charvals,$G($1.barcode,_B,1))!==undefined;if(!_F){$k[$j++]="bwipp.hibcazteccodeBadCharacter";$k[$j++]="HIBC Aztec Code must contain only digits, capital letters, spaces and the symbols -.$/+%";bwipp_raiseerror()}}$k[$j++]="barlen";$k[$j++]=$1.barcode.length;if($1.validatecheck){var _I=$k[--$j];$k[$j++]=$f(_I-1)}var _J=$k[--$j];$1[$k[--$j]]=_J;$1.checksum=41;for(var _N=0,_M=$f($1.barlen-1);_N<=_M;_N+=1){$1.checksum=$f($g($1.charvals,$G($1.barcode,_N,1))+$1.checksum)}$1.checksum=$1.checksum%43;if($1.validatecheck){if($g($1.barcode,$1.barlen)!=$g($1.barchars,$1.checksum)){$k[$j++]="bwipp.hibcazteccodeBadCheckDigit";$k[$j++]="Incorrect HIBC Aztec Code check digit provided";bwipp_raiseerror()}$1.barcode=$G($1.barcode,0,$1.barlen)}var _f=$s($f($1.barlen+2));$P(_f,1,$1.barcode);$1.barcode=_f;$p($1.barcode,0,43);$p($1.barcode,$f($1.barlen+1),$g($1.barchars,$1.checksum));$p($1.options,"dontdraw",true);$p($1.options,"validatecheck",false);$k[$j++]="args";$k[$j++]=$1.barcode;$k[$j++]=$1.options;bwipp_azteccode();var _r=$k[--$j];$1[$k[--$j]]=_r;$p($1.args,"opt",$1.options);$k[$j++]=$1.args;if(!$1.dontdraw){bwipp_renmatrix()}}function bwipp_channelcode(){var $1={};$1.options=$k[--$j];$1.barcode=$k[--$j];$1.dontdraw=false;$1.shortfinder=false;$1.includetext=false;$1.includecheck=false;$1.height=1;$F($1.options,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});$1.height=+$1.height;if($1.barcode.length<2||$1.barcode.length>7){$k[$j++]="bwipp.channelcodeBadLength";$k[$j++]="Channel Code must be 2 to 7 digits";bwipp_raiseerror()}$F($1.barcode,function(){var _9=$k[--$j];if(_9<48||_9>57){$k[$j++]="bwipp.channelcodeBadCharacter";$k[$j++]="Channel Code must contain only digits";bwipp_raiseerror()}});if(~~$z($1.barcode)>$g($a([26,292,3493,44072,576688,7742862]),$1.barcode.length-2)){$k[$j++]="bwipp.channelcodeTooBig";$k[$j++]="The Channel Code value is too big for the number of channels";bwipp_raiseerror()}$1.nextb=function(){var _E=$k[--$j];var _N=$f($g($1.s,_E)+$f($g($1.b,$f(_E-1))+$f($g($1.b,$f(_E-2))+$g($1.s,$f(_E-1)))))>4?1:2;$k[$j++]=_E;$k[$j++]=_N;if(_E<$f($1.chan+2)){var _P=$k[--$j];var _Q=$k[--$j];var _R=$k[--$j];$k[$j++]=_R;$k[$j++]=_Q;$k[$j++]=_P;for(var _T=_P,_S=_R;_T<=_S;_T+=1){var _V=$k[--$j];var _W=$k[--$j];$p($1.b,_W,_T);var _X=$k[--$j];var _Y=$k[--$j];$k[$j++]=_Y;$k[$j++]=_X;$k[$j++]=_W;$k[$j++]=_V;$k[$j++]=$f($f(_X-_T)+1);$k[$j++]=_Y;$k[$j++]=$f(_W+1);$1.nexts()}}else{var _Z=$k[--$j];var _a=$k[--$j];var _b=$k[--$j];$k[$j++]=_b;$k[$j++]=_a;$k[$j++]=_Z;if($le(_Z,_b)){var _d=$k[--$j];var _e=$k[--$j];var _f=$k[--$j];$p($1.b,_e,_f);$k[$j++]=_f;$k[$j++]=_e;$k[$j++]=_d;if($1.value==$1.target){$k[$j++]=Infinity;for(var _i=3;_i<=10;_i+=1){$k[$j++]=$g($1.s,_i);$k[$j++]=$g($1.b,_i)}$1.out=$G($a(),0,$1.chan*2)}$1.value=$1.value+1}}$j-=4};$1.nexts=function(){var _r=$k[--$j];$k[$j++]=_r;if(_r<$f($1.chan+2)){$k[$j++]=1}else{var _t=$k[--$j];var _u=$k[--$j];$k[$j++]=_u;$k[$j++]=_t;$k[$j++]=_u}var _v=$k[--$j];var _w=$k[--$j];var _x=$k[--$j];$k[$j++]=_x;$k[$j++]=_w;for(var _z=_v,_y=_x;_z<=_y;_z+=1){var _11=$k[--$j];$p($1.s,_11,_z);var _12=$k[--$j];var _13=$k[--$j];$k[$j++]=_13;$k[$j++]=_12;$k[$j++]=_11;$k[$j++]=$f($f(_12-_z)+1);$k[$j++]=_13;$k[$j++]=_11;$1.nextb()}$j-=3};$1.encode=function(){$1.chan=$k[--$j];$1.target=$k[--$j];$1.value=0;$1.out=-1;$1.b=$a([1,1,1,0,0,0,0,0,0,0,0]);$1.s=$a([0,1,1,0,0,0,0,0,0,0,0]);var _18=$1.chan;$k[$j++]=_18;$k[$j++]=_18;$k[$j++]=3;$1.nexts();$k[$j++]=$1.out};$1.barlen=$1.barcode.length;$k[$j++]="finder";if($1.shortfinder){$k[$j++]=$a([1,1,1,1,1])}else{$k[$j++]=$a([1,1,1,1,1,1,1,1,1])}var _1E=$k[--$j];$1[$k[--$j]]=_1E;$k[$j++]="data";$k[$j++]=~~$z($1.barcode);$k[$j++]=$1.barlen+1;$1.encode();var _1I=$k[--$j];$1[$k[--$j]]=_1I;$1.check=$a([]);if($1.includecheck){$1.mod23=$g($a([$a([]),$a([]),$a([13,12,4,9,3,1]),$a([13,2,12,3,18,16,4,1]),$a([11,16,17,8,20,4,10,2,5,1]),$a([1,4,16,18,3,12,2,8,9,13,6,1]),$a([20,16,22,13,15,12,5,4,8,9,21,3,7,1]),$a([2,6,18,8,1,3,9,4,12,13,16,2,6,18,8,1])]),$1.barlen);$k[$j++]=0;for(var _1Z=0,_1Y=$1.data.length-1;_1Z<=_1Y;_1Z+=1){var _1e=$k[--$j];$k[$j++]=$f(_1e+$f($g($1.data,_1Z)-1)*$g($1.mod23,_1Z))}var _1f=$k[--$j];$k[$j++]=_1f%23;$k[$j++]=3;$1.encode();$1.check=$k[--$j]}$k[$j++]=Infinity;$q($1.finder);$q($1.data);$q($1.check);$1.sbs=$a();$1.txt=$a($1.barlen);for(var _1p=0,_1o=$1.barlen-1;_1p<=_1o;_1p+=1){$1.i=_1p;$p($1.txt,$1.i,$a([$G($1.barcode,$1.i,1),0,0,"",0]))}$k[$j++]=Infinity;var _1w=$1.sbs;$k[$j++]=Infinity;for(var _1y=0,_1z=~~(($1.sbs.length+1)/2);_1y<_1z;_1y++){$k[$j++]=$1.height}var _21=$a();$k[$j++]=Infinity;for(var _23=0,_24=~~(($1.sbs.length+1)/2);_23<_24;_23++){$k[$j++]=0}var _25=$a();$k[$j++]="ren";$k[$j++]=bwipp_renlinear;$k[$j++]="sbs";$k[$j++]=_1w;$k[$j++]="bhs";$k[$j++]=_21;$k[$j++]="bbs";$k[$j++]=_25;$k[$j++]="txt";$k[$j++]=$1.txt;$k[$j++]="textxalign";$k[$j++]="center";$k[$j++]="opt";$k[$j++]=$1.options;var _28=$d();$k[$j++]=_28;if(!$1.dontdraw){bwipp_renlinear()}}function bwipp_renlinear(){if($0.bwipjs_dontdraw){return}var $1={};$1.args=$k[--$j];$1.sbs=$a([]);$1.bhs=$a([]);$1.bbs=$a([]);$1.txt=$a([]);$1.barcolor="unset";$1.includetext=false;$1.textcolor="unset";$1.textxalign="unset";$1.textyalign="unset";$1.textfont="OCR-B";$1.textsize=10;$1.textxoffset=0;$1.textyoffset=0;$1.textgaps=0;$1.alttext="";$1.bordercolor="unset";$1.backgroundcolor="unset";$1.inkspread=0;$1.width=0;$1.barratio=1;$1.spaceratio=1;$1.showborder=false;$1.borderleft=10;$1.borderright=10;$1.bordertop=1;$1.borderbottom=1;$1.borderwidth=.5;$1.guardwhitespace=false;$1.guardleftpos=0;$1.guardleftypos=0;$1.guardrightpos=0;$1.guardrightypos=0;$1.guardwidth=7;$1.guardheight=7;$F($1.args,function(){var _7=$k[--$j];$1[$k[--$j]]=_7});var _9=$1.opt;for(var _E=_9.size,_D=_9.keys(),_C=0;_C<_E;_C++){var _A=_D.next().value;$1[_A]=_9.get(_A)}$1.barcolor=""+$1.barcolor;$1.textcolor=""+$1.textcolor;$1.textxalign=""+$1.textxalign;$1.textyalign=""+$1.textyalign;$1.textfont=""+$1.textfont;$1.textsize=+$1.textsize;$1.textxoffset=+$1.textxoffset;$1.textyoffset=+$1.textyoffset;$1.textgaps=+$1.textgaps;$1.alttext=""+$1.alttext;$1.bordercolor=""+$1.bordercolor;$1.backgroundcolor=""+$1.backgroundcolor;$1.inkspread=+$1.inkspread;$1.width=+$1.width;$1.barratio=+$1.barratio;$1.spaceratio=+$1.spaceratio;$1.borderleft=+$1.borderleft;$1.borderright=+$1.borderright;$1.bordertop=+$1.bordertop;$1.borderbottom=+$1.borderbottom;$1.borderwidth=+$1.borderwidth;$1.guardleftpos=+$1.guardleftpos;$1.guardleftypos=+$1.guardleftypos;$1.guardrightpos=+$1.guardrightpos;$1.guardrightypos=+$1.guardrightypos;$1.guardwidth=+$1.guardwidth;$1.guardheight=+$1.guardheight;$1.bars=$a(~~(($1.sbs.length+1)/2));$1.x=0;$1.maxh=0;for(var _k=0,_j=~~(($1.sbs.length+1)/2)*2-2;_k<=_j;_k+=1){$1.i=_k;if($1.i%2==0){$1.d=$f($f($g($1.sbs,$1.i)*$1.barratio-$1.barratio)+1);if($g($1.sbs,$1.i)!=0){$1.h=$g($1.bhs,~~($1.i/2))*72;$1.c=$f($1.d/2+$1.x);$1.y=$g($1.bbs,~~($1.i/2))*72;$1.w=$f($1.d-$1.inkspread);$p($1.bars,~~($1.i/2),$a([$1.h,$1.c,$1.y,$1.w]));if($f($1.h+$1.y)>$1.maxh){$1.maxh=$f($1.h+$1.y)}}else{$p($1.bars,~~($1.i/2),-1)}}else{$1.d=$f($f($g($1.sbs,$1.i)*$1.spaceratio-$1.spaceratio)+1)}$1.x=$f($1.x+$1.d)}$$.save();var _1P=$$.currpos();$$.translate(_1P.x,_1P.y);if($1.width!=0){$$.scale($1.width*72/$1.x,1)}$$.newpath();$$.moveto(-$1.borderleft,-$1.borderbottom);$$.rlineto($f($f($1.x+$1.borderleft)+$1.borderright),0);$$.rlineto(0,$f($f($1.maxh+$1.borderbottom)+$1.bordertop));$$.rlineto(-$f($f($1.x+$1.borderleft)+$1.borderright),0);$$.rlineto(0,-$f($f($1.maxh+$1.borderbottom)+$1.bordertop));$$.closepath();if($1.showborder){$$.save();if($ne($1.bordercolor,"unset")){$$.setcolor($1.bordercolor)}$$.setlinewidth($1.borderwidth);$$.stroke();$$.restore()}$$.save();if($ne($1.barcolor,"unset")){$$.setcolor($1.barcolor)}var _1n=$1.bars;for(var _1o=0,_1p=_1n.length;_1o<_1p;_1o++){var _1q=$g(_1n,_1o);$k[$j++]=_1q;if(_1q!=-1){$q($k[--$j]);$$.newpath();$$.setlinewidth($k[--$j]);var _1t=$k[--$j];$$.moveto($k[--$j],_1t);$$.rlineto(0,$k[--$j]);$$.stroke()}else{$j--}}$$.restore();if($ne($1.textcolor,"unset")){$$.setcolor($1.textcolor)}if($1.includetext){if($eq($1.textxalign,"unset")&&$eq($1.textyalign,"unset")&&$eq($1.alttext,"")){$1.s=0;$1.fn="";var _22=$1.txt;for(var _23=0,_24=_22.length;_23<_24;_23++){$F($g(_22,_23));var _26=$k[--$j];var _27=$k[--$j];$k[$j++]=_27;$k[$j++]=_26;if(_26!=$1.s||$ne(_27,$1.fn)){var _2A=$k[--$j];var _2B=$k[--$j];$1.s=_2A;$1.fn=_2B;$$.selectfont(_2B,_2A)}else{$j-=2}var _2C=$k[--$j];$$.moveto($k[--$j],_2C);$$.show($k[--$j],0,0)}}else{$$.selectfont($1.textfont,$1.textsize);if($eq($1.alttext,"")){$k[$j++]=Infinity;var _2I=$1.txt;for(var _2J=0,_2K=_2I.length;_2J<_2K;_2J++){$F($g($g(_2I,_2J),0))}$1.txt=$a();$1.tstr=$s($1.txt.length);for(var _2S=0,_2R=$1.txt.length-1;_2S<=_2R;_2S+=1){$p($1.tstr,_2S,$g($1.txt,_2S))}}else{$1.tstr=$1.alttext}if($1.tstr.length==0){$k[$j++]=0}else{$$.save();$$.newpath();$$.moveto(0,0);$$.charpath("0",false);var _2Y=$$.pathbbox();$$.restore();$k[$j++]=_2Y.ury}$1.textascent=$k[--$j];var _2b=$$.stringwidth($1.tstr);$1.textwidth=$f(_2b.w+($1.tstr.length-1)*$1.textgaps);$1.textxpos=$f($1.textxoffset+$f($1.x-$1.textwidth)/2);if($eq($1.textxalign,"left")){$1.textxpos=$1.textxoffset}if($eq($1.textxalign,"right")){$1.textxpos=$f($f($1.x-$1.textxoffset)-$1.textwidth)}if($eq($1.textxalign,"offleft")){$1.textxpos=-$f($1.textwidth+$1.textxoffset)}if($eq($1.textxalign,"offright")){$1.textxpos=$f($1.x+$1.textxoffset)}if($eq($1.textxalign,"justify")&&$1.textwidth<$1.x){$1.textxpos=0;$1.textgaps=$f($1.x-$1.textwidth)/($1.tstr.length-1)}$1.textypos=-$f($f($1.textyoffset+$1.textascent)+1);if($eq($1.textyalign,"above")){$1.textypos=$f($f($1.textyoffset+$1.maxh)+1)}if($eq($1.textyalign,"center")){$1.textypos=$f($1.textyoffset+$f($1.maxh-$1.textascent)/2)}$$.moveto($1.textxpos,$1.textypos);$$.show($1.tstr,$1.textgaps,0)}}if($1.guardwhitespace){$$.selectfont("OCR-B",$1.guardheight*2);if($1.guardleftpos!=0){$$.moveto($f(-$1.guardleftpos-1),$f($f($1.guardleftypos-$1.guardheight/2)-1.5));$$.show("<",0,0)}if($1.guardrightpos!=0){$$.moveto($f($f($f($1.guardrightpos+$1.x)-$1.guardwidth)+1),$f($f($1.guardrightypos-$1.guardheight/2)-1.5));$$.show(">",0,0)}}$$.restore()}function bwipp_renmaximatrix(){if($0.bwipjs_dontdraw){return}var $1={};$1.args=$k[--$j];$1.barcolor="unset";$1.backgroundcolor="unset";$F($1.args,function(){var _3=$k[--$j];$1[$k[--$j]]=_3});var _5=$1.opt;for(var _A=_5.size,_9=_5.keys(),_8=0;_8<_A;_8++){var _6=_9.next().value;$1[_6]=_5.get(_6)}$1.barcolor=""+$1.barcolor;$1.backgroundcolor=""+$1.backgroundcolor;$$.save();if($ne($1.barcolor,"unset")){$$.setcolor($1.barcolor)}$$.maxicode($1.pixs);$$.restore()}function bwipp_encode(bwipjs,encoder,text,opts,dontdraw){if(typeof text!=="string"){throw new Error("bwipp.typeError: barcode text not a string ("+text+")")}opts=opts||{};if(typeof opts==="string"){var tmp=opts.split(" ");opts={};for(var i=0;i=0&&r[e].op=="l";e--);e++;if(ev.x0)n=v.x0;if(sv.y0)a=v.y0;if(fv.x1)n=v.x1;if(sv.y1)a=v.y1;if(fs.x0)t=s.x0;if(is.x1)t=s.x1;if(is.y0)e=s.y0;if(ns.y1)e=s.y1;if(no){var y=s;s=o;o=y}if(f>v){var y=f;f=v;v=y}if(s==o){this.bbox(s-u,f,s+c-u-1,v)}else{this.bbox(s,f-p+g+1,o,v+g)}e++}else if(a.op=="p"){var x=Infinity;var d=Infinity;var _=-Infinity;var b=-Infinity;var w=a.poly;if(w.length!=4){throw new Error("stroke: --not-a-rect--")}for(var n=0,m=w.length-1;n_)_=S;if(Tb)b=T}var u=M(c/2);var g=M(p/2);this.bbox(x-u,d-g,_+u,b+g);i++}else{throw new Error("stroke: --not-a-line--")}}var B=this;this.cmds.push(function(){var r=M(c/2);var t=M(p/2);var e=c-r;var i=p-t;for(var n=0;ns)s=u;if(hf)f=h}this.bbox(e,a+1,s-1,f)}else if(t.op=="e"){this.bbox(t.x-t.rx,t.y-t.ry,t.x+t.rx,t.y+t.ry)}else{throw new Error("fill: --not-a-polygon--")}}var l=this;this.cmds.push(function(){for(var r=0;r9){v++}var t,u,e,i;e=s/2|0;i=f/2|0;t=v-e;if(t&1){t--}u=(4*f|0)-i;var h=t/2-1;var l=(h+1)/2|0;var c=u-2-2*l;this.bbox(0,0,v*30-e,f*3*32+f*4-i);var p=this;this.cmds.push(function(){for(var r=0;re){var n=r;r=e;e=n}if(t>i){var n=t;t=i;i=n}r=F(r);t=F(t);e=M(e);i=M(i);if(this.minx>r)this.minx=r;if(this.maxxt)this.miny=t;if(this.maxys){for(var f=s+1;fa){E(n,a)}if(u>a){E(o,a)}}else{var v=r[i==t-1?0:i+1][1];var u=r[e==0?t-1:e-1][1];if(v>a){E(o,a)}if(u>a){E(n,a)}}}}},hexagon:function(r,t){var e=r[0][0]|0;var i=r[0][1]|0;var n=r[1][1]-r[0][1]|0;var a=r[2][1]-r[1][1]-1|0;var s=r[2][0]|0;var f=r[4][0]|0;I=parseInt(t.substr(0,2),16);S=parseInt(t.substr(2,2),16);T=parseInt(t.substr(4,2),16);C(e,e+1,i++);for(var o=1;o=1;o--){C(e-2*o,e+1+2*o,i++)}C(e,e+1,i)},ellipse:function(r,t,e,i,n){a(r-e|0,t-i|0,r+e|0,t+i|0,n)},fill:function(r){I=parseInt(r.substr(0,2),16);S=parseInt(r.substr(2,2),16);T=parseInt(r.substr(4,2),16);t();B=[];B.min=Infinity},clip:function(r){if(!F){F=[];F.min=Infinity}var t=B;B=F;for(var e=0,i=r.length;et)B.min=t;if(!B[t]){B[t]=[r]}else{B[t].push(r)}}function C(r,t,e){while(r<=t){M(r++,e,255)}}function t(){var r=B.min;var t=B.length-1;for(var e=r;e<=t;e++){var i=B[e];if(!i){continue}i.sort(function(r,t){return r-t});var n=false;var a=0;for(var s=0,f=i.length;se){r=e;e+=a}if(t>i)t=i;t+=(s+1)/2|0;i=t-f;a*=8*a;f=8*s*s;do{x(c,e,t);y(l,r,t);y(l,r,i);x(c,e,i);h=2*u;if(h>=o){r++;e--;o+=f;u+=o}if(h<=v){t++;i--;v+=a;u+=v}}while(r<=e);while(t-ie)r.min=e;var i=r[e];if(i==null||i>t){r[e]=t}}function x(r,t,e){if(r.min>e)r.min=e;var i=r[e];if(i==null||ir){return!i}else if(s==r){return i}i=!i}return true}function e(r){var t=0;for(var e=0,i=r.length,n=i-1;e0?1:-1}}function DrawingCanvas(e,i){if(typeof window==null){throw new Error("DrawingCanvas: not a browser")}var n;var a=i.getContext("2d");var r=DrawingBuiltin(e);r.image=t;r.end=s;return r;function t(r,t){i.width=r;i.height=t;a.setTransform(1,0,0,1,0,0);if(/^[0-9a-fA-F]{6}$/.test(""+e.backgroundcolor)){a.fillStyle="#"+e.backgroundcolor;a.fillRect(0,0,r,t)}else{a.clearRect(0,0,r,t)}n=a.getImageData(0,0,r,t);return{buffer:n.data,ispng:false}}function s(){a.putImageData(n,0,0)}}var FontLib=function(){var v=[];var s={};var u={};var h={};var l=0;h.next=h;h.prev=h;return{lookup:t,monochrome:e,getglyph:i,getpaths:n,loadFont:r};function r(r){var t=100;var e=100;var i=null;if(arguments.length==2){i=arguments[1]}else if(arguments.length==3){t=e=+arguments[1]||100;i=arguments[2]}else if(arguments.length==4){e=+arguments[1]||100;t=+arguments[2]||100;i=arguments[3]}else{throw new Error("loadFont(): invalid number of arguments")}var n=STBTT.InitFont(toUint8Array(i));n.bwipjs_name=r;n.bwipjs_multx=t;n.bwipjs_multy=e;var a=v.push(n)-1;s[r.toUpperCase()]=a;return a}function t(r){var t=s[r.toUpperCase()];return t===undefined?1:t}function e(r){if(r){throw new Error("fontlib: monochrome not implemented")}}function i(r,t,e,i){r=r|0;t=t|0;e=+e;i=+i;if(!e||e<8){e=8}if(!i||i<8){i=e}if(r<0||r>=v.length){r=1}if(!t||t<32){t=32}var n=""+r+"c"+t+"w"+e+"h"+i;var a=u[n];if(a){a.prev.next=a.next;a.next.prev=a.prev;var s=h;s.next.prev=a;a.next=s.next;a.prev=s;s.next=a;return a}var f=v[r];var a=STBTT.GetGlyph(f,t,e*f.bwipjs_multx/100,i*f.bwipjs_multy/100);a.bytes=a.pixels;a.cachekey=n;a.offset=0;if(l>250){var s=h;var o=s.prev;o.prev.next=s;s.prev=o.prev;o.next=o.prev=null;delete u[o.cachekey]}else{l++}u[n]=a;var s=h;s.next.prev=a;a.next=s.next;a.prev=s;s.next=a;return a}function n(r,t,e,i){r=r|0;t=t|0;e=+e;i=+i;if(!e||e<8){e=8}if(!i||i<8){i=e}if(r<0||r>=v.length){r=1}if(!t||t<32){t=32}var n=v[r];return STBTT.GetPaths(n,t,e*n.bwipjs_multx/100,i*n.bwipjs_multy/100)}}();var STBTT=function(){var P=1,D=2,O=3,y=4,x=0,r=1,t=2,d=3,e=0,i=1,n=2,a=3,s=4,f=0,_=1,o=2,b=10;var W=Math.floor;var v=Math.ceil;var z=Math.sqrt;var R=Math.abs;function q(r){var t=[];for(var e=0;er.length||t<0?r.length:t}function j(r,t){w(r,r.cursor+t)}function L(r,t){var e=0;for(var i=0;ir.length||e>r.length-t){return m()}var i=r.subarray(t,t+e);i.cursor=0;return i}function I(r){var t=r.cursor;var e=L(r,2);if(e){var i=U(r);j(r,i*e);j(r,L(r,i)-1)}return k(r,t,r.cursor-t)}function G(r){var t=U(r);if(t>=32&&t<=246){return t-139}else if(t>=247&&t<=250){return(t-247)*256+U(r)+108}else if(t>=251&&t<=254){return-(t-251)*256-U(r)-108}else if(t==28){return L(r,2)}else if(t==29){return L(r,4)}return 0}function h(r){var t=u(r);if(t==30){j(r,1);while(r.cursor>4==15){break}}}else{G(r)}}function l(r,t){w(r,0);while(r.cursor=28){h(r)}i=r.cursor;n=U(r);if(n==12){n=U(r)|256}if(n==t){return k(r,e,i-e)}}return k(r,0,0)}function c(r,t,e,i){var n=l(r,t);for(var a=0;a>0:r}function H(r,t){return r[t]*256+r[t+1]}function X(r,t){var e=r[t]*256+r[t+1];return e&32768?(4294901760|e)>>0:e}function T(r,t){return(r[t]<<24)+(r[t+1]<<16)+(r[t+2]<<8)+r[t+3]}function B(r,t,e){var i=H(r,t+4);var n=t+12;for(var a=0;a=s&&t>1,v=H(e,i+8)>>1,u=H(e,i+10),h=H(e,i+12)>>1,l=i+14,c=l;if(t>65535){return 0}if(t>=H(e,c+h*2)){c+=h*2}c-=2;while(u){v>>=1;var p=H(e,c+v*2);if(t>p){c+=v*2}--u}c+=2;var g,y,x=c-l>>>1;y=H(e,i+14+o*2+2+2*x);if(t>1);var m=T(e,i+16+w*12);var k=T(e,i+16+w*12+4);if(tk){_=w+1}else{var I=T(e,i+16+w*12+8);if(n==12){return I+t-m}else{return I}}}return 0}return 0}function Y(r,t,e,i,n,a){r.type=t;r.x=e;r.y=i;r.cx=n;r.cy=a}function $(r,t){var e,i;if(t>=r.numGlyphs){return-1}if(r.indexToLocFormat>=2){return-1}if(r.indexToLocFormat==0){e=r.glyf+H(r.data,r.loca+t*2)*2;i=r.glyf+H(r.data,r.loca+t*2+2)*2}else{e=r.glyf+T(r.data,r.loca+t*4);i=r.glyf+T(r.data,r.loca+t*4+4)}return e==i?-1:e}function E(r,t,e){if(r.cff.length){fr(r,t,e)}else{var i=$(r,t);if(i<0){return 0}e.x0=X(r.data,i+2);e.y0=X(r.data,i+4);e.x1=X(r.data,i+6);e.y1=X(r.data,i+8)}return 1}function Q(r,t,e,i,n,a,s,f,o,v){if(i){if(e){Y(r[t++],O,o+s>>1,v+f>>1,o,v)}Y(r[t++],O,n,a,s,f)}else{if(e){Y(r[t++],O,n,a,o,v)}else{Y(r[t++],D,n,a,0,0)}}return t}function C(r,t){var e=r.data,i=$(r,t);if(i<0){return null}var n=[];var a=X(e,i);if(a>0){var s=0,f,o,v=0,u,h,l,c=0,p,g=0,y,x,d,_,b,w,m,k;var I=i+10;var S=H(e,i+10+a*2);var T=e.subarray(i+10+a*2+2+S);var B=0;h=1+H(e,I+a*2-2);u=h+2*a;n=q(u);l=0;f=0;p=u-h;for(o=0;o>1;w=x+n[p+o+1].y>>1}else{b=n[p+o+1].x;w=n[p+o+1].y;++o}}else{b=y;w=x}Y(n[E++],P,b,w,0,0);c=0;l=1+H(e,I+v*2);++v}else{if(!(s&1)){if(c){Y(n[E++],O,d+y>>1,_+x>>1,d,_)}d=y;_=x;c=1}else{if(c){Y(n[E++],O,y,x,d,_)}else{Y(n[E++],D,y,x,0,0)}c=0}}}n.length=Q(n,E,c,g,b,w,m,k,d,_)}else if(a==-1){var C=1;var R=i+10;while(C){var s,j,L=[1,0,0,1,0,0];s=X(e,R);R+=2;j=X(e,R);R+=2;if(s&2){if(s&1){L[4]=X(e,R);R+=2;L[5]=X(e,R);R+=2}else{L[4]=U(e,R);R+=1;L[5]=U(e,R);R+=1}}if(s&1<<3){L[0]=L[3]=X(e,R)/16384;R+=2;L[1]=L[2]=0}else if(s&1<<6){L[0]=X(e,R)/16384;R+=2;L[1]=L[2]=0;L[3]=X(e,R)/16384;R+=2}else if(s&1<<7){L[0]=X(e,R)/16384;R+=2;L[1]=X(e,R)/16384;R+=2;L[2]=X(e,R)/16384;R+=2;L[3]=X(e,R)/16384;R+=2}var u=z(L[0]*L[0]+L[1]*L[1]);var h=z(L[2]*L[2]+L[3]*L[3]);var G=or(r,j);if(G.length>0){for(var o=0,A=G.length;or.max_x||!r.started){r.max_x=t}if(e>r.max_y||!r.started){r.max_y=e}if(t=33900){i=32768}else if(e>=1240){i=1131}t+=i;if(t<0||t>=e){return m()}return A(r,t)}function ir(r,t){var e=r.fdselect;var i,n,a,s,f,o=-1,v;w(e,0);f=U(e);if(f==0){j(e,t);o=U(e)}else if(f==3){i=L(e,2);n=L(e,2);for(v=0;v=n&&t=s){break}rr(e,0,l[o]);o++;if(o>=s){break}rr(e,l[o],0);o++}break;case 6:if(s<1){return 0}for(;;){if(o>=s){break}rr(e,l[o],0);o++;if(o>=s){break}rr(e,0,l[o]);o++}break;case 31:if(s<4){return 0}for(;;){if(o+3>=s){break}tr(e,l[o],0,l[o+1],l[o+2],s-o==5?l[o+4]:0,l[o+3]);o+=4;if(o+3>=s){break}tr(e,0,l[o],l[o+1],l[o+2],l[o+3],s-o==5?l[o+4]:0);o+=4}break;case 30:if(s<4){return 0}for(;;){if(o+3>=s){break}tr(e,0,l[o],l[o+1],l[o+2],l[o+3],s-o==5?l[o+4]:0);o+=4;if(o+3>=s){break}tr(e,l[o],0,l[o+1],l[o+2],s-o==5?l[o+4]:0,l[o+3]);o+=4}break;case 8:if(s<6){return 0}for(;o+5=s){return 0}rr(e,l[o],l[o+1]);break;case 25:if(s<8){return 0}for(;o+1=s){return 0}tr(e,l[o],l[o+1],l[o+2],l[o+3],l[o+4],l[o+5]);break;case 26:case 27:if(s<4){return 0}y=0;if(s&1){y=l[o];o++}for(;o+3=10){return 0}c[a++]=g;g=er(v==10?p:r.gsubrs,f);if(g.length==0){return 0}g.cursor=0;h=0;break;case 11:if(a<=0){return 0}g=c[--a];h=0;break;case 14:V(e);return 1;case 12:var x,d,_,b,w,m,k,I,S,T,B,F,M,E,C=U(g);switch(C){case 34:if(s<7){return 0}x=l[0];d=l[1];I=l[2];_=l[3];b=l[4];w=l[5];m=l[6];tr(e,x,0,d,I,_,0);tr(e,b,0,w,-I,m,0);break;case 35:if(s<13){return 0}x=l[0];k=l[1];d=l[2];I=l[3];_=l[4];S=l[5];b=l[6];T=l[7];w=l[8];B=l[9];m=l[10];F=l[11];tr(e,x,k,d,I,_,S);tr(e,b,T,w,B,m,F);break;case 36:if(s<9){return 0}x=l[0];k=l[1];d=l[2];I=l[3];_=l[4];b=l[5];w=l[6];B=l[7];m=l[8];tr(e,x,k,d,I,_,0);tr(e,b,0,w,B,m,-(k+I+B));break;case 37:if(s<11){return 0}x=l[0];k=l[1];d=l[2];I=l[3];_=l[4];S=l[5];b=l[6];T=l[7];w=l[8];B=l[9];m=F=l[10];M=x+d+_+b+w;E=k+I+S+T+B;if(R(M)>R(E)){F=-E}else{m=-M}tr(e,x,k,d,I,_,S);tr(e,b,T,w,B,m,F);break;default:return 0}break;default:if(v!=255&&v!=28&&(v<32||v>254)){return 0}if(v==255){y=(L(g,4)|0)/65536}else{j(g,-1);y=(G(g)<<16|0)>>16}if(s>=48){return 0}l[s++]=y;h=0;break}if(h){s=0}}return 0}function ar(){return{started:0,first_x:0,first_y:0,x:0,y:0,min_x:0,max_x:0,min_y:0,max_y:0,vertices:[]}}function sr(r,t){var e=ar();if(nr(r,t,e)){return e.vertices}return null}function fr(r,t,e){var i=ar();var n=nr(r,t,i);e.x0=n?i.min_x:0;e.y0=n?i.min_y:0;e.x1=n?i.max_x:0;e.y1=n?i.max_y:0;return n&&i.vertices?i.vertices.length:0}function or(r,t){if(!r.cff.length){return C(r,t)}else{return sr(r,t)}}function vr(r,t){var e=H(r.data,r.hhea+34);if(te.ey){return}if(se.ey){a+=(a-i)*(e.ey-s)/(s-n);s=e.ey}if(i<=t&&a<=t){r[t]+=e.direction*(s-n)}else if(i>=t+1&&a>=t+1){}else{r[t]+=e.direction*(s-n)*(1-(i-t+(a-t))/2)}}function xr(r,t,e,i,n){var a=n+1;while(i){if(i.fdx==0){var s=i.fx;if(s=0){yr(r,s,i,s,n,s,a);yr(t,s+1,i,s,n,s,a)}else{yr(t,0,i,s,n,s,a)}}}else{var s=i.fx,f=i.fdx,o=s+f,v,u,h,l,c=i.fdy;if(i.sy>n){v=s+f*(i.sy-n);h=i.sy}else{v=s;h=n}if(i.ey=0&&u>=0&&vu){h=a-(h-n);l=a-(l-n);y=h,h=l,l=y;y=u,u=v,v=y;f=-f;c=-c;y=s,s=o,o=y}x=v|0;d=u|0;_=(x+1-s)*c+n;w=i.direction;m=w*(_-h);r[x]+=m*(1-(v-x+(x+1-x))/2);b=w*c;for(g=x+1;gd){yr(r,g,i,s,k,x,T);yr(r,g,i,x,T,d,B);yr(r,g,i,d,B,I,S)}else if(Id){yr(r,g,i,s,k,d,B);yr(r,g,i,d,B,x,T);yr(r,g,i,x,T,I,S)}else if(sx){yr(r,g,i,s,k,x,T);yr(r,g,i,x,T,I,S)}else if(Ix){yr(r,g,i,s,k,x,T);yr(r,g,i,x,T,I,S)}else if(sd){yr(r,g,i,s,k,d,B);yr(r,g,i,d,B,I,S)}else if(Id){yr(r,g,i,s,k,d,B);yr(r,g,i,d,B,I,S)}else{yr(r,g,i,s,k,I,S)}}}}i=i.next}}function dr(r,t,e,i,n,a){i|=0,n|=0,a|=0;var s=null,f;var o=a,v=0,u;var h=new Float32Array(r.w*2+1);var l=h.subarray(r.w);var c=0;t[e].y0=a+r.h+1;while(v>>0;if(w>255){w=255}r.pixels[v*r.stride+u]=w}y=s;while(y){f=y;f.fx+=f.fdx;y=f.next}++o;++v}}function _r(r,t){for(var e=1;e0&&i.y012){var i,n,a,s,f,o=e>>1,v=r[t].y0=s){break}i=r[t+a];r[t+a]=r[t+s];r[t+s]=i;++a;--s}if(st[d+g].y:t[d+p].y16){return 1}if(h*h+l*l>f){kr(r,t,e,(t+i)/2,(e+n)/2,v,u,f,o+1);kr(r,v,u,(i+a)/2,(n+s)/2,a,s,f,o+1)}else{r.push({x:a,y:s})}return 1}function Ir(r,t,e,i,n,a,s,f,o,v,u){var h=i-t,l=n-e,c=a-i,p=s-n,g=f-a,y=o-s,x=f-t,d=o-e,_=z(h*h+l*l)+z(c*c+p*p)+z(g*g+y*y),b=z(x*x+d*d),w=_*_-b*b;if(u>16){return}if(w>v){var m=(t+i)/2,k=(e+n)/2,I=(i+a)/2,S=(n+s)/2,T=(a+f)/2,B=(s+o)/2,F=(m+I)/2,M=(k+S)/2,E=(I+T)/2,C=(S+B)/2,R=(F+E)/2,j=(M+C)/2;Ir(r,t,e,m,k,F,M,R,j,v,u+1);Ir(r,R,j,E,C,T,B,f,o,v,u+1)}else{r.push({x:f,y:o})}}function Sr(r,t,e){var i=[],n=t*t,a=-1,s=0,f=0,o=0;for(var v=0,u=r.length;v=0){e[a]=i.length-s}++a;s=i.length;f=r[v].x,o=r[v].y;i.push({x:f,y:o});break;case D:f=r[v].x,o=r[v].y;i.push({x:f,y:o});break;case O:kr(i,f,o,r[v].cx,r[v].cy,r[v].x,r[v].y,n,0);f=r[v].x,o=r[v].y;break;case y:Ir(i,f,o,r[v].cx,r[v].cy,r[v].cx1,r[v].cy1,r[v].x,r[v].y,n,0);f=r[v].x,o=r[v].y;break}}e[a]=i.length-s;return i}function Tr(r,t,e,i,n,a,s,f,o,v){f|=0,o|=0;var u=i>n?n:i,h=[],l=Sr(e,t/u,h);if(l){mr(r,l,h,i,n,a,s,f,o,v)}}function Br(r,t,e,i,n,a,s,f,o,v){var u=or(r,v);var h={};var l=lr(r,v,a,s,f,o);h.pixels=t;h.w=e|0;h.h=i|0;h.stride=n|0;if(h.w&&h.h){Tr(h,.35,u,a,s,f,o,l.x0,l.y0,1)}}function Fr(r,t,e,i,n,a,s,f,o,v){Br(r,t,e,i,n,a,s,f,o,M(r,v))}function Mr(r,t,e){return g(r,t,e)}function Er(r){var t={};if(!g(t,r,0)){return null}var e=hr(t);t.ascent=e.ascent;t.descent=e.descent;t.linegap=e.linegap;return t}function Cr(r,t,e,i){i=i||e;var n=e/r.ascent;var a=i/r.ascent;var s=M(r,t);if(!s&&t){return null}var f=or(r,s);var o=lr(r,s,n,a,0,0);var v=o.x1-o.x0;var u=o.y1-o.y0;var h=null;if(v&&u){var l={pixels:h=new Uint8Array(v*u),w:v,h:u,stride:v};Tr(l,.35,f,n,a,0,0,o.x0,o.y0,1)}var c=vr(r,s);return{glyph:s,pixels:h,width:v,height:u,top:-o.y0,left:o.x0,advance:W(c.advanceWidth*n)}}function Rr(r,t,e,i){i=i||e;var n=e/r.ascent;var a=i/r.ascent;var s=M(r,t);if(!s&&t){return null}var f=or(r,s);var o=vr(r,s);var v=0;var u=0;var h=[];if(f){for(var l=0,c=f.length;lv)v=g.y;if(g.y= 2.0.0-beta.1",7:">= 4.0.0 <4.3.0",8:">= 4.3.0"};b.REVISION_CHANGES=q;var r="[object Object]";d.prototype={constructor:d,logger:l["default"],log:l["default"].log,registerHelper:function(a,b){if(f.toString.call(a)===r){if(b)throw new h["default"]("Arg not supported with multiple helpers");f.extend(this.helpers,a)}else this.helpers[a]=b},unregisterHelper:function(a){delete this.helpers[a]},registerPartial:function(a,b){if(f.toString.call(a)===r)f.extend(this.partials,a);else{if("undefined"==typeof b)throw new h["default"]('Attempting to register a partial called "'+a+'" as undefined');this.partials[a]=b}},unregisterPartial:function(a){delete this.partials[a]},registerDecorator:function(a,b){if(f.toString.call(a)===r){if(b)throw new h["default"]("Arg not supported with multiple decorators");f.extend(this.decorators,a)}else this.decorators[a]=b},unregisterDecorator:function(a){delete this.decorators[a]},resetLoggedPropertyAccesses:function(){m.resetLoggedProperties()}};var s=l["default"].log;b.log=s,b.createFrame=f.createFrame,b.logger=l["default"]},function(a,b){"use strict";function c(a){return k[a]}function d(a){for(var b=1;b":">",'"':""","'":"'","`":"`","=":"="},l=/[&<>"'`=]/g,m=/[&<>"'`=]/,n=Object.prototype.toString;b.toString=n;var o=function(a){return"function"==typeof a};o(/x/)&&(b.isFunction=o=function(a){return"function"==typeof a&&"[object Function]"===n.call(a)}),b.isFunction=o;var p=Array.isArray||function(a){return!(!a||"object"!=typeof a)&&"[object Array]"===n.call(a)};b.isArray=p},function(a,b,c){"use strict";function d(a,b){var c=b&&b.loc,g=void 0,h=void 0,i=void 0,j=void 0;c&&(g=c.start.line,h=c.end.line,i=c.start.column,j=c.end.column,a+=" - "+g+":"+i);for(var k=Error.prototype.constructor.call(this,a),l=0;l0?(c.ids&&(c.ids=[c.name]),a.helpers.each(b,c)):e(this);if(c.data&&c.ids){var g=d.createFrame(c.data);g.contextPath=d.appendContextPath(c.data.contextPath,c.name),c={data:g}}return f(b,c)})},a.exports=b["default"]},function(a,b,c){(function(d){"use strict";var e=c(13)["default"],f=c(1)["default"];b.__esModule=!0;var g=c(5),h=c(6),i=f(h);b["default"]=function(a){a.registerHelper("each",function(a,b){function c(b,c,d){l&&(l.key=b,l.index=c,l.first=0===c,l.last=!!d,m&&(l.contextPath=m+b)),k+=f(a[b],{data:l,blockParams:g.blockParams([a[b],b],[m+b,null])})}if(!b)throw new i["default"]("Must pass iterator to #each");var f=b.fn,h=b.inverse,j=0,k="",l=void 0,m=void 0;if(b.data&&b.ids&&(m=g.appendContextPath(b.data.contextPath,b.ids[0])+"."),g.isFunction(a)&&(a=a.call(this)),b.data&&(l=g.createFrame(b.data)),a&&"object"==typeof a)if(g.isArray(a))for(var n=a.length;j=0?b:parseInt(a,10)}return a},log:function(a){if(a=e.lookupLevel(a),"undefined"!=typeof console&&e.lookupLevel(e.level)<=a){var b=e.methodMap[a];console[b]||(b="log");for(var c=arguments.length,d=Array(c>1?c-1:0),f=1;f=v.LAST_COMPATIBLE_COMPILER_REVISION&&b<=v.COMPILER_REVISION)){if(b2&&v.push("'"+this.terminals_[s]+"'");x=this.lexer.showPosition?"Parse error on line "+(i+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+v.join(", ")+", got '"+(this.terminals_[n]||n)+"'":"Parse error on line "+(i+1)+": Unexpected "+(1==n?"end of input":"'"+(this.terminals_[n]||n)+"'"),this.parseError(x,{text:this.lexer.match,token:this.terminals_[n]||n,line:this.lexer.yylineno,loc:l,expected:v})}}if(q[0]instanceof Array&&q.length>1)throw new Error("Parse Error: multiple actions possible at state: "+p+", token: "+n);switch(q[0]){case 1:d.push(n),e.push(this.lexer.yytext),f.push(this.lexer.yylloc),d.push(q[1]),n=null,o?(n=o,o=null):(j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,l=this.lexer.yylloc,k>0&&k--);break;case 2:if(t=this.productions_[q[1]][1],w.$=e[e.length-t],w._$={first_line:f[f.length-(t||1)].first_line,last_line:f[f.length-1].last_line,first_column:f[f.length-(t||1)].first_column,last_column:f[f.length-1].last_column},m&&(w._$.range=[f[f.length-(t||1)].range[0],f[f.length-1].range[1]]),r=this.performAction.call(w,h,j,i,this.yy,q[1],e,f),"undefined"!=typeof r)return r;t&&(d=d.slice(0,-1*t*2),e=e.slice(0,-1*t),f=f.slice(0,-1*t)),d.push(this.productions_[q[1]][0]),e.push(w.$),f.push(w._$),u=g[d[d.length-2]][d[d.length-1]],d.push(u);break;case 3:return!0}}return!0}},c=function(){var a={EOF:1,parseError:function(a,b){if(!this.yy.parser)throw new Error(a);this.yy.parser.parseError(a,b)},setInput:function(a){return this._input=a,this._more=this._less=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var a=this._input[0];this.yytext+=a,this.yyleng++,this.offset++,this.match+=a,this.matched+=a;var b=a.match(/(?:\r\n?|\n).*/g);return b?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),a},unput:function(a){var b=a.length,c=a.split(/(?:\r\n?|\n)/g);this._input=a+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-b-1),this.offset-=b;var d=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),c.length-1&&(this.yylineno-=c.length-1);var e=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:c?(c.length===d.length?this.yylloc.first_column:0)+d[d.length-c.length].length-c[0].length:this.yylloc.first_column-b},this.options.ranges&&(this.yylloc.range=[e[0],e[0]+this.yyleng-b]),this},more:function(){return this._more=!0,this},less:function(a){this.unput(this.match.slice(a))},pastInput:function(){var a=this.matched.substr(0,this.matched.length-this.match.length);return(a.length>20?"...":"")+a.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var a=this.match;return a.length<20&&(a+=this._input.substr(0,20-a.length)),(a.substr(0,20)+(a.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var a=this.pastInput(),b=new Array(a.length+1).join("-");return a+this.upcomingInput()+"\n"+b+"^"},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var a,b,c,d,e;this._more||(this.yytext="",this.match="");for(var f=this._currentRules(),g=0;gb[0].length)||(b=c,d=g,this.options.flex));g++);return b?(e=b[0].match(/(?:\r\n?|\n).*/g),e&&(this.yylineno+=e.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:e?e[e.length-1].length-e[e.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+b[0].length},this.yytext+=b[0],this.match+=b[0],this.matches=b,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._input=this._input.slice(b[0].length),this.matched+=b[0],a=this.performAction.call(this,this.yy,this,f[d],this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),a?a:void 0):""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var a=this.next();return"undefined"!=typeof a?a:this.lex()},begin:function(a){this.conditionStack.push(a)},popState:function(){return this.conditionStack.pop()},_currentRules:function(){return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules},topState:function(){return this.conditionStack[this.conditionStack.length-2]},pushState:function(a){this.begin(a)}};return a.options={},a.performAction=function(a,b,c,d){function e(a,c){return b.yytext=b.yytext.substring(a,b.yyleng-c+a)}switch(c){case 0:if("\\\\"===b.yytext.slice(-2)?(e(0,1),this.begin("mu")):"\\"===b.yytext.slice(-1)?(e(0,1),this.begin("emu")):this.begin("mu"),b.yytext)return 15;break;case 1:return 15;case 2:return this.popState(),15;case 3:return this.begin("raw"),15;case 4:return this.popState(),"raw"===this.conditionStack[this.conditionStack.length-1]?15:(e(5,9),"END_RAW_BLOCK");case 5:return 15;case 6:return this.popState(),14;case 7:return 65;case 8:return 68;case 9:return 19;case 10:return this.popState(),this.begin("raw"),23;case 11:return 55;case 12:return 60;case 13:return 29;case 14:return 47;case 15:return this.popState(),44;case 16:return this.popState(),44;case 17:return 34;case 18:return 39;case 19:return 51;case 20:return 48;case 21:this.unput(b.yytext),this.popState(),this.begin("com");break;case 22:return this.popState(),14;case 23:return 48;case 24:return 73;case 25:return 72;case 26:return 72;case 27:return 87;case 28:break;case 29:return this.popState(),54;case 30:return this.popState(),33;case 31:return b.yytext=e(1,2).replace(/\\"/g,'"'),80;case 32:return b.yytext=e(1,2).replace(/\\'/g,"'"),80;case 33:return 85;case 34:return 82;case 35:return 82;case 36:return 83;case 37:return 84;case 38:return 81;case 39:return 75;case 40:return 77;case 41:return 72;case 42:return b.yytext=b.yytext.replace(/\\([\\\]])/g,"$1"),72;case 43:return"INVALID";case 44:return 5}},a.rules=[/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/,/^(?:\{\{\{\{(?=[^\/]))/,/^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/,/^(?:[^\x00]+?(?=(\{\{\{\{)))/,/^(?:[\s\S]*?--(~)?\}\})/,/^(?:\()/,/^(?:\))/,/^(?:\{\{\{\{)/,/^(?:\}\}\}\})/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#>)/,/^(?:\{\{(~)?#\*?)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^\s*(~)?\}\})/,/^(?:\{\{(~)?\s*else\s*(~)?\}\})/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{(~)?!--)/,/^(?:\{\{(~)?![\s\S]*?\}\})/,/^(?:\{\{(~)?\*?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.)|])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s)])))/,/^(?:false(?=([~}\s)])))/,/^(?:undefined(?=([~}\s)])))/,/^(?:null(?=([~}\s)])))/,/^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/,/^(?:as\s+\|)/,/^(?:\|)/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)|]))))/,/^(?:\[(\\\]|[^\]])*\])/,/^(?:.)/,/^(?:$)/],a.conditions={mu:{rules:[7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44],inclusive:!1},emu:{rules:[2],inclusive:!1},com:{rules:[6],inclusive:!1},raw:{rules:[3,4,5],inclusive:!1},INITIAL:{rules:[0,1,44],inclusive:!0}},a}();return b.lexer=c,a.prototype=b,b.Parser=a,new a}();b["default"]=c,a.exports=b["default"]},function(a,b,c){"use strict";function d(){var a=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];this.options=a}function e(a,b,c){void 0===b&&(b=a.length);var d=a[b-1],e=a[b-2];return d?"ContentStatement"===d.type?(e||!c?/\r?\n\s*?$/:/(^|\r?\n)\s*?$/).test(d.original):void 0:c}function f(a,b,c){void 0===b&&(b=-1);var d=a[b+1],e=a[b+2];return d?"ContentStatement"===d.type?(e||!c?/^\s*?\r?\n/:/^\s*?(\r?\n|$)/).test(d.original):void 0:c}function g(a,b,c){var d=a[null==b?0:b+1];if(d&&"ContentStatement"===d.type&&(c||!d.rightStripped)){var e=d.value;d.value=d.value.replace(c?/^\s+/:/^[ \t]*\r?\n?/,""),d.rightStripped=d.value!==e}}function h(a,b,c){var d=a[null==b?a.length-1:b-1];if(d&&"ContentStatement"===d.type&&(c||!d.leftStripped)){var e=d.value;return d.value=d.value.replace(c?/\s+$/:/[ \t]+$/,""),d.leftStripped=d.value!==e,d.leftStripped}}var i=c(1)["default"];b.__esModule=!0;var j=c(49),k=i(j);d.prototype=new k["default"],d.prototype.Program=function(a){var b=!this.options.ignoreStandalone,c=!this.isRootSeen;this.isRootSeen=!0;for(var d=a.body,i=0,j=d.length;i0)throw new q["default"]("Invalid path: "+d,{loc:c});".."===i&&f++}}return{type:"PathExpression",data:a,depth:f,parts:e,original:d,loc:c}}function j(a,b,c,d,e,f){var g=d.charAt(3)||d.charAt(2),h="{"!==g&&"&"!==g,i=/\*/.test(d);return{type:i?"Decorator":"MustacheStatement",path:a,params:b,hash:c,escaped:h,strip:e,loc:this.locInfo(f)}}function k(a,b,c,e){d(a,c),e=this.locInfo(e);var f={type:"Program",body:b,strip:{},loc:e};return{type:"BlockStatement",path:a.path,params:a.params,hash:a.hash,program:f,openStrip:{},inverseStrip:{},closeStrip:{},loc:e}}function l(a,b,c,e,f,g){e&&e.path&&d(a,e);var h=/\*/.test(a.open);b.blockParams=a.blockParams;var i=void 0,j=void 0;if(c){if(h)throw new q["default"]("Unexpected inverse block on decorator",c);c.chain&&(c.program.body[0].closeStrip=e.strip),j=c.strip,i=c.program}return f&&(f=i,i=b,b=f),{type:h?"DecoratorBlock":"BlockStatement",path:a.path,params:a.params,hash:a.hash,program:b,inverse:i,openStrip:a.strip,inverseStrip:j,closeStrip:e&&e.strip,loc:this.locInfo(g)}}function m(a,b){if(!b&&a.length){var c=a[0].loc,d=a[a.length-1].loc;c&&d&&(b={source:c.source,start:{line:c.start.line,column:c.start.column},end:{line:d.end.line,column:d.end.column}})}return{type:"Program",body:a,strip:{},loc:b}}function n(a,b,c,e){return d(a,c),{type:"PartialBlockStatement",name:a.path,params:a.params,hash:a.hash,program:b,openStrip:a.strip,closeStrip:c&&c.strip,loc:this.locInfo(e)}}var o=c(1)["default"];b.__esModule=!0,b.SourceLocation=e,b.id=f,b.stripFlags=g,b.stripComment=h,b.preparePath=i,b.prepareMustache=j,b.prepareRawBlock=k,b.prepareBlock=l,b.prepareProgram=m,b.preparePartialBlock=n;var p=c(6),q=o(p)},function(a,b,c){"use strict";function d(){}function e(a,b,c){if(null==a||"string"!=typeof a&&"Program"!==a.type)throw new l["default"]("You must pass a string or Handlebars AST to Handlebars.precompile. You passed "+a);b=b||{},"data"in b||(b.data=!0),b.compat&&(b.useDepths=!0);var d=c.parse(a,b),e=(new c.Compiler).compile(d,b);return(new c.JavaScriptCompiler).compile(e,b)}function f(a,b,c){function d(){var d=c.parse(a,b),e=(new c.Compiler).compile(d,b),f=(new c.JavaScriptCompiler).compile(e,b,void 0,!0);return c.template(f)}function e(a,b){return f||(f=d()),f.call(this,a,b)}if(void 0===b&&(b={}),null==a||"string"!=typeof a&&"Program"!==a.type)throw new l["default"]("You must pass a string or Handlebars AST to Handlebars.compile. You passed "+a);b=m.extend({},b),"data"in b||(b.data=!0),b.compat&&(b.useDepths=!0);var f=void 0;return e._setup=function(a){return f||(f=d()),f._setup(a)},e._child=function(a,b,c,e){return f||(f=d()),f._child(a,b,c,e)},e}function g(a,b){if(a===b)return!0;if(m.isArray(a)&&m.isArray(b)&&a.length===b.length){for(var c=0;c1)throw new l["default"]("Unsupported number of partial arguments: "+c.length,a);c.length||(this.options.explicitPartialContext?this.opcode("pushLiteral","undefined"):c.push({type:"PathExpression",parts:[],depth:0}));var d=a.name.original,e="SubExpression"===a.name.type;e&&this.accept(a.name),this.setupFullMustacheParams(a,b,void 0,!0);var f=a.indent||"";this.options.preventIndent&&f&&(this.opcode("appendContent",f),f=""),this.opcode("invokePartial",e,d,f),this.opcode("append")},PartialBlockStatement:function(a){this.PartialStatement(a)},MustacheStatement:function(a){this.SubExpression(a),a.escaped&&!this.options.noEscape?this.opcode("appendEscaped"):this.opcode("append")},Decorator:function(a){this.DecoratorBlock(a)},ContentStatement:function(a){a.value&&this.opcode("appendContent",a.value)},CommentStatement:function(){},SubExpression:function(a){h(a);var b=this.classifySexpr(a);"simple"===b?this.simpleSexpr(a):"helper"===b?this.helperSexpr(a):this.ambiguousSexpr(a)},ambiguousSexpr:function(a,b,c){var d=a.path,e=d.parts[0],f=null!=b||null!=c;this.opcode("getContext",d.depth),this.opcode("pushProgram",b),this.opcode("pushProgram",c),d.strict=!0,this.accept(d),this.opcode("invokeAmbiguous",e,f)},simpleSexpr:function(a){var b=a.path;b.strict=!0,this.accept(b),this.opcode("resolvePossibleLambda")},helperSexpr:function(a,b,c){var d=this.setupFullMustacheParams(a,b,c),e=a.path,f=e.parts[0];if(this.options.knownHelpers[f])this.opcode("invokeKnownHelper",d.length,f);else{if(this.options.knownHelpersOnly)throw new l["default"]("You specified knownHelpersOnly, but used the unknown helper "+f,a);e.strict=!0,e.falsy=!0,this.accept(e),this.opcode("invokeHelper",d.length,e.original,o["default"].helpers.simpleId(e))}},PathExpression:function(a){this.addDepth(a.depth),this.opcode("getContext",a.depth);var b=a.parts[0],c=o["default"].helpers.scopedId(a),d=!a.depth&&!c&&this.blockParamIndex(b);d?this.opcode("lookupBlockParam",d,a.parts):b?a.data?(this.options.data=!0,this.opcode("lookupData",a.depth,a.parts,a.strict)):this.opcode("lookupOnContext",a.parts,a.falsy,a.strict,c):this.opcode("pushContext")},StringLiteral:function(a){this.opcode("pushString",a.value)},NumberLiteral:function(a){this.opcode("pushLiteral",a.value)},BooleanLiteral:function(a){this.opcode("pushLiteral",a.value)},UndefinedLiteral:function(){this.opcode("pushLiteral","undefined")},NullLiteral:function(){this.opcode("pushLiteral","null")},Hash:function(a){var b=a.pairs,c=0,d=b.length;for(this.opcode("pushHash");c=0)return[b,e]}}}},function(a,b,c){"use strict";function d(a){this.value=a}function e(){}function f(a,b,c,d){var e=b.popStack(),f=0,g=c.length;for(a&&g--;f0&&(c+=", "+d.join(", "));var e=0;g(this.aliases).forEach(function(a){var d=b.aliases[a];d.children&&d.referenceCount>1&&(c+=", alias"+ ++e+"="+a,d.children[0]="alias"+e)}),this.lookupPropertyFunctionIsUsed&&(c+=", "+this.lookupPropertyFunctionVarDeclaration());var f=["container","depth0","helpers","partials","data"];(this.useBlockParams||this.useDepths)&&f.push("blockParams"),this.useDepths&&f.push("depths");var h=this.mergeSource(c);return a?(f.push(h),Function.apply(this,f)):this.source.wrap(["function(",f.join(","),") {\n ",h,"}"])},mergeSource:function(a){var b=this.environment.isSimple,c=!this.forceBuffer,d=void 0,e=void 0,f=void 0,g=void 0;return this.source.each(function(a){a.appendToBuffer?(f?a.prepend(" + "):f=a,g=a):(f&&(e?f.prepend("buffer += "):d=!0,g.add(";"),f=g=void 0),e=!0,b||(c=!1))}),c?f?(f.prepend("return "),g.add(";")):e||this.source.push('return "";'):(a+=", buffer = "+(d?"":this.initializeBuffer()),f?(f.prepend("return buffer + "),g.add(";")):this.source.push("return buffer;")),a&&this.source.prepend("var "+a.substring(2)+(d?"":";\n")),this.source.merge()},lookupPropertyFunctionVarDeclaration:function(){return"\n lookupProperty = container.lookupProperty || function(parent, propertyName) {\n if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {\n return parent[propertyName];\n }\n return undefined\n }\n ".trim()},blockValue:function(a){var b=this.aliasable("container.hooks.blockHelperMissing"),c=[this.contextName(0)];this.setupHelperArgs(a,0,c);var d=this.popStack();c.splice(1,0,d),this.push(this.source.functionCall(b,"call",c))},ambiguousBlockValue:function(){var a=this.aliasable("container.hooks.blockHelperMissing"),b=[this.contextName(0)];this.setupHelperArgs("",0,b,!0),this.flushInline();var c=this.topStack();b.splice(1,0,c),this.pushSource(["if (!",this.lastHelper,") { ",c," = ",this.source.functionCall(a,"call",b),"}"])},appendContent:function(a){this.pendingContent?a=this.pendingContent+a:this.pendingLocation=this.source.currentLocation,this.pendingContent=a},append:function(){if(this.isInline())this.replaceStack(function(a){return[" != null ? ",a,' : ""']}),this.pushSource(this.appendToBuffer(this.popStack()));else{var a=this.popStack();this.pushSource(["if (",a," != null) { ",this.appendToBuffer(a,void 0,!0)," }"]),this.environment.isSimple&&this.pushSource(["else { ",this.appendToBuffer("''",void 0,!0)," }"])}},appendEscaped:function(){this.pushSource(this.appendToBuffer([this.aliasable("container.escapeExpression"),"(",this.popStack(),")"]))},getContext:function(a){this.lastContext=a},pushContext:function(){this.pushStackLiteral(this.contextName(this.lastContext))},lookupOnContext:function(a,b,c,d){var e=0;d||!this.options.compat||this.lastContext?this.pushContext():this.push(this.depthedLookup(a[e++])),this.resolvePath("context",a,e,b,c)},lookupBlockParam:function(a,b){this.useBlockParams=!0,this.push(["blockParams[",a[0],"][",a[1],"]"]),this.resolvePath("context",b,1)},lookupData:function(a,b,c){a?this.pushStackLiteral("container.data(data, "+a+")"):this.pushStackLiteral("data"),this.resolvePath("data",b,0,!0,c)},resolvePath:function(a,b,c,d,e){var g=this;if(this.options.strict||this.options.assumeObjects)return void this.push(f(this.options.strict&&e,this,b,a));for(var h=b.length;cthis.stackVars.length&&this.stackVars.push("stack"+this.stackSlot),this.topStackName()},topStackName:function(){return"stack"+this.stackSlot},flushInline:function(){var a=this.inlineStack;this.inlineStack=[];for(var b=0,c=a.length;be.length)&&(t=e.length);for(var u=0,n=new Array(t);u=e.length?{done:!0}:{done:!1,value:e[n++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function e(){return{baseUrl:null,breaks:!1,extensions:null,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:null,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tokenizer:null,walkTokens:null,xhtml:!1}}r.defaults=e();function u(e){return t[e]}var n=/[&<>"']/,l=/[&<>"']/g,a=/[<>"']|&(?!#?\w+;)/,o=/[<>"']|&(?!#?\w+;)/g,t={"&":"&","<":"<",">":">",'"':""","'":"'"};function D(e,t){if(t){if(n.test(e))return e.replace(l,u)}else if(a.test(e))return e.replace(o,u);return e}var c=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;function m(e){return e.replace(c,function(e,t){return"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}var h=/(^|[^\[])\^/g;function p(u,e){u=u.source||u,e=e||"";var n={replace:function(e,t){return t=(t=t.source||t).replace(h,"$1"),u=u.replace(e,t),n},getRegex:function(){return new RegExp(u,e)}};return n}var f=/[^\w:]/g,g=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;function F(e,t,u){if(e){var n;try{n=decodeURIComponent(m(u)).replace(f,"").toLowerCase()}catch(e){return null}if(0===n.indexOf("javascript:")||0===n.indexOf("vbscript:")||0===n.indexOf("data:"))return null}t&&!g.test(u)&&(u=function(e,t){A[" "+e]||(d.test(e)?A[" "+e]=e+"/":A[" "+e]=w(e,"/",!0));var u=-1===(e=A[" "+e]).indexOf(":");return"//"===t.substring(0,2)?u?t:e.replace(C,"$1")+t:"/"===t.charAt(0)?u?t:e.replace(k,"$1")+t:e+t}(t,u));try{u=encodeURI(u).replace(/%25/g,"%")}catch(e){return null}return u}var A={},d=/^[^:]+:\/*[^/]*$/,C=/^([^:]+:)[\s\S]*$/,k=/^([^:]+:\/*[^/]*)[\s\S]*$/;var E={exec:function(){}};function b(e){for(var t,u,n=1;nt)u.splice(t);else for(;u.length>=1,e+=e;return u+e}function _(e,t,u,n){var r=t.href,i=t.title?D(t.title):null,t=e[1].replace(/\\([\[\]])/g,"$1");if("!"===e[0].charAt(0))return{type:"image",raw:u,href:r,title:i,text:D(t)};n.state.inLink=!0;t={type:"link",raw:u,href:r,title:i,text:t,tokens:n.inlineTokens(t,[])};return n.state.inLink=!1,t}var z=function(){function e(e){this.options=e||r.defaults}var t=e.prototype;return t.space=function(e){e=this.rules.block.newline.exec(e);if(e&&0=u.length?e.slice(u.length):e}).join("\n")}(u,t[3]||"");return{type:"code",raw:u,lang:t[2]&&t[2].trim(),text:e}}},t.heading=function(e){var t=this.rules.block.heading.exec(e);if(t){var u=t[2].trim();/#$/.test(u)&&(e=w(u,"#"),!this.options.pedantic&&e&&!/ $/.test(e)||(u=e.trim()));u={type:"heading",raw:t[0],depth:t[1].length,text:u,tokens:[]};return this.lexer.inline(u.text,u.tokens),u}},t.hr=function(e){e=this.rules.block.hr.exec(e);if(e)return{type:"hr",raw:e[0]}},t.blockquote=function(e){var t=this.rules.block.blockquote.exec(e);if(t){e=t[0].replace(/^ *> ?/gm,"");return{type:"blockquote",raw:t[0],tokens:this.lexer.blockTokens(e,[]),text:e}}},t.list=function(e){var t=this.rules.block.list.exec(e);if(t){var u,n,r,i,s,l,a,o,D,c,h,p=1<(g=t[1].trim()).length,f={type:"list",raw:"",ordered:p,start:p?+g.slice(0,-1):"",loose:!1,items:[]},g=p?"\\d{1,9}\\"+g.slice(-1):"\\"+g;this.options.pedantic&&(g=p?g:"[*+-]");for(var F=new RegExp("^( {0,3}"+g+")((?: [^\\n]*)?(?:\\n|$))");e&&(h=!1,t=F.exec(e))&&!this.rules.block.hr.test(e);){if(u=t[0],e=e.substring(u.length),a=t[2].split("\n",1)[0],o=e.split("\n",1)[0],this.options.pedantic?(i=2,c=a.trimLeft()):(i=t[2].search(/[^ ]/),c=a.slice(i=4=i||!a.trim())c+="\n"+a.slice(i);else{if(s)break;c+="\n"+a}s||a.trim()||(s=!0),u+=D+"\n",e=e.substring(D.length+1)}f.loose||(l?f.loose=!0:/\n *\n *$/.test(u)&&(l=!0)),this.options.gfm&&(n=/^\[[ xX]\] /.exec(c))&&(r="[ ] "!==n[0],c=c.replace(/^\[[ xX]\] +/,"")),f.items.push({type:"list_item",raw:u,task:!!n,checked:r,loose:!1,text:c}),f.raw+=u}f.items[f.items.length-1].raw=u.trimRight(),f.items[f.items.length-1].text=c.trimRight(),f.raw=f.raw.trimRight();for(var d=f.items.length,C=0;C/i.test(e[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(e[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(e[0])&&(this.lexer.state.inRawBlock=!1),{type:this.options.sanitize?"text":"html",raw:e[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,text:this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):D(e[0]):e[0]}},t.link=function(e){var t=this.rules.inline.link.exec(e);if(t){e=t[2].trim();if(!this.options.pedantic&&/^$/.test(e))return;var u=w(e.slice(0,-1),"\\");if((e.length-u.length)%2==0)return}else{var n=function(e,t){if(-1===e.indexOf(t[1]))return-1;for(var u=e.length,n=0,r=0;r$/.test(e)?u.slice(1):u.slice(1,-1):u)&&u.replace(this.rules.inline._escapes,"$1"),title:n&&n.replace(this.rules.inline._escapes,"$1")},t[0],this.lexer)}},t.reflink=function(e,t){if((u=this.rules.inline.reflink.exec(e))||(u=this.rules.inline.nolink.exec(e))){var e=(u[2]||u[1]).replace(/\s+/g," ");if((e=t[e.toLowerCase()])&&e.href)return _(u,e,u[0],this.lexer);var u=u[0].charAt(0);return{type:"text",raw:u,text:u}}},t.emStrong=function(e,t,u){void 0===u&&(u="");var n=this.rules.inline.emStrong.lDelim.exec(e);if(n&&(!n[3]||!u.match(/(?:[0-9A-Za-z\xAA\xB2\xB3\xB5\xB9\xBA\xBC-\xBE\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088E\u08A0-\u08C9\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09F9\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BF2\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C66-\u0C6F\u0C78-\u0C7E\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D58-\u0D61\u0D66-\u0D78\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F33\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1369-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4C\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2070\u2071\u2074-\u2079\u207F-\u2089\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2150-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2CFD\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3192-\u3195\u31A0-\u31BF\u31F0-\u31FF\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA830-\uA835\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDE80-\uDE9C\uDEA0-\uDED0\uDEE1-\uDEFB\uDF00-\uDF23\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC58-\uDC76\uDC79-\uDC9E\uDCA7-\uDCAF\uDCE0-\uDCF2\uDCF4\uDCF5\uDCFB-\uDD1B\uDD20-\uDD39\uDD80-\uDDB7\uDDBC-\uDDCF\uDDD2-\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE40-\uDE48\uDE60-\uDE7E\uDE80-\uDE9F\uDEC0-\uDEC7\uDEC9-\uDEE4\uDEEB-\uDEEF\uDF00-\uDF35\uDF40-\uDF55\uDF58-\uDF72\uDF78-\uDF91\uDFA9-\uDFAF]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDCFA-\uDD23\uDD30-\uDD39\uDE60-\uDE7E\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF27\uDF30-\uDF45\uDF51-\uDF54\uDF70-\uDF81\uDFB0-\uDFCB\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC52-\uDC6F\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDDE1-\uDDF4\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF30-\uDF3B\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCF2\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC6C\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF2\uDFB0\uDFC0-\uDFD4]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDE70-\uDEBE\uDEC0-\uDEC9\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF5B-\uDF61\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE96\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD834[\uDEE0-\uDEF3\uDF60-\uDF78]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD837[\uDF00-\uDF1E]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDCC7-\uDCCF\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83C[\uDD00-\uDD0C]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])/))){var r=n[1]||n[2]||"";if(!r||""===u||this.rules.inline.punctuation.exec(u)){var i,s=n[0].length-1,l=s,a=0,o="*"===n[0][0]?this.rules.inline.emStrong.rDelimAst:this.rules.inline.emStrong.rDelimUnd;for(o.lastIndex=0,t=t.slice(-1*e.length+s);null!=(n=o.exec(t));)if(i=n[1]||n[2]||n[3]||n[4]||n[5]||n[6])if(i=i.length,n[3]||n[4])l+=i;else if(!((n[5]||n[6])&&s%3)||(s+i)%3){if(!(0<(l-=i))){if(i=Math.min(i,i+l+a),Math.min(s,i)%2){var D=e.slice(1,s+n.index+i);return{type:"em",raw:e.slice(0,s+n.index+i+1),text:D,tokens:this.lexer.inlineTokens(D,[])}}D=e.slice(2,s+n.index+i-1);return{type:"strong",raw:e.slice(0,s+n.index+i+1),text:D,tokens:this.lexer.inlineTokens(D,[])}}}else a+=i}}},t.codespan=function(e){var t=this.rules.inline.code.exec(e);if(t){var u=t[2].replace(/\n/g," "),n=/[^ ]/.test(u),e=/^ /.test(u)&&/ $/.test(u),u=D(u=n&&e?u.substring(1,u.length-1):u,!0);return{type:"codespan",raw:t[0],text:u}}},t.br=function(e){e=this.rules.inline.br.exec(e);if(e)return{type:"br",raw:e[0]}},t.del=function(e){e=this.rules.inline.del.exec(e);if(e)return{type:"del",raw:e[0],text:e[2],tokens:this.lexer.inlineTokens(e[2],[])}},t.autolink=function(e,t){e=this.rules.inline.autolink.exec(e);if(e){var u,t="@"===e[2]?"mailto:"+(u=D(this.options.mangle?t(e[1]):e[1])):u=D(e[1]);return{type:"link",raw:e[0],text:u,href:t,tokens:[{type:"text",raw:u,text:u}]}}},t.url=function(e,t){var u,n,r,i;if(u=this.rules.inline.url.exec(e)){if("@"===u[2])r="mailto:"+(n=D(this.options.mangle?t(u[0]):u[0]));else{for(;i=u[0],u[0]=this.rules.inline._backpedal.exec(u[0])[0],i!==u[0];);n=D(u[0]),r="www."===u[1]?"http://"+n:n}return{type:"link",raw:u[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}}},t.inlineText=function(e,t){e=this.rules.inline.text.exec(e);if(e){t=this.lexer.state.inRawBlock?this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):D(e[0]):e[0]:D(this.options.smartypants?t(e[0]):e[0]);return{type:"text",raw:e[0],text:t}}},e}(),$={newline:/^(?: *(?:\n|$))+/,code:/^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,fences:/^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?=\n|$)|$)/,hr:/^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( {0,3}bull)( [^\n]+?)?(?:\n|$)/,html:"^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n *)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$))",def:/^ {0,3}\[(label)\]: *(?:\n *)?]+)>?(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/,table:E,lheading:/^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,_paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,text:/^[^\n]+/,_label:/(?!\s*\])(?:\\.|[^\[\]\\])+/,_title:/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/};$.def=p($.def).replace("label",$._label).replace("title",$._title).getRegex(),$.bullet=/(?:[*+-]|\d{1,9}[.)])/,$.listItemStart=p(/^( *)(bull) */).replace("bull",$.bullet).getRegex(),$.list=p($.list).replace(/bull/g,$.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+$.def.source+")").getRegex(),$._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",$._comment=/|$)/,$.html=p($.html,"i").replace("comment",$._comment).replace("tag",$._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),$.paragraph=p($._paragraph).replace("hr",$.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",$._tag).getRegex(),$.blockquote=p($.blockquote).replace("paragraph",$.paragraph).getRegex(),$.normal=b({},$),$.gfm=b({},$.normal,{table:"^ *([^\\n ].*\\|.*)\\n {0,3}(?:\\| *)?(:?-+:? *(?:\\| *:?-+:? *)*)(?:\\| *)?(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)"}),$.gfm.table=p($.gfm.table).replace("hr",$.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",$._tag).getRegex(),$.gfm.paragraph=p($._paragraph).replace("hr",$.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("table",$.gfm.table).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",$._tag).getRegex(),$.pedantic=b({},$.normal,{html:p("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",$._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:E,paragraph:p($.normal._paragraph).replace("hr",$.hr).replace("heading"," *#{1,6} *[^\n]").replace("lheading",$.lheading).replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").getRegex()});var S={escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:E,tag:"^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(ref)\]/,nolink:/^!?\[(ref)\](?:\[\])?/,reflinkSearch:"reflink|nolink(?!\\()",emStrong:{lDelim:/^(?:\*+(?:([punct_])|[^\s*]))|^_+(?:([punct*])|([^\s_]))/,rDelimAst:/^[^_*]*?\_\_[^_*]*?\*[^_*]*?(?=\_\_)|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/,rDelimUnd:/^[^_*]*?\*\*[^_*]*?\_[^_*]*?(?=\*\*)|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/},code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:E,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\?@\\[\\]`^{|}~",S.punctuation=p(S.punctuation).replace(/punctuation/g,S._punctuation).getRegex(),S.blockSkip=/\[[^\]]*?\]\([^\)]*?\)|`[^`]*?`|<[^>]*?>/g,S.escapedEmSt=/\\\*|\\_/g,S._comment=p($._comment).replace("(?:--\x3e|$)","--\x3e").getRegex(),S.emStrong.lDelim=p(S.emStrong.lDelim).replace(/punct/g,S._punctuation).getRegex(),S.emStrong.rDelimAst=p(S.emStrong.rDelimAst,"g").replace(/punct/g,S._punctuation).getRegex(),S.emStrong.rDelimUnd=p(S.emStrong.rDelimUnd,"g").replace(/punct/g,S._punctuation).getRegex(),S._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,S._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,S._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,S.autolink=p(S.autolink).replace("scheme",S._scheme).replace("email",S._email).getRegex(),S._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,S.tag=p(S.tag).replace("comment",S._comment).replace("attribute",S._attribute).getRegex(),S._label=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,S._href=/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/,S._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,S.link=p(S.link).replace("label",S._label).replace("href",S._href).replace("title",S._title).getRegex(),S.reflink=p(S.reflink).replace("label",S._label).replace("ref",$._label).getRegex(),S.nolink=p(S.nolink).replace("ref",$._label).getRegex(),S.reflinkSearch=p(S.reflinkSearch,"g").replace("reflink",S.reflink).replace("nolink",S.nolink).getRegex(),S.normal=b({},S),S.pedantic=b({},S.normal,{strong:{start:/^__|\*\*/,middle:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,endAst:/\*\*(?!\*)/g,endUnd:/__(?!_)/g},em:{start:/^_|\*/,middle:/^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,endAst:/\*(?!\*)/g,endUnd:/_(?!_)/g},link:p(/^!?\[(label)\]\((.*?)\)/).replace("label",S._label).getRegex(),reflink:p(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",S._label).getRegex()}),S.gfm=b({},S.normal,{escape:p(S.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\'+(u?e:D(e,!0))+"\n":"
"+(u?e:D(e,!0))+"
\n"},t.blockquote=function(e){return"
\n"+e+"
\n"},t.html=function(e){return e},t.heading=function(e,t,u,n){return this.options.headerIds?"'+e+"\n":""+e+"\n"},t.hr=function(){return this.options.xhtml?"
\n":"
\n"},t.list=function(e,t,u){var n=t?"ol":"ul";return"<"+n+(t&&1!==u?' start="'+u+'"':"")+">\n"+e+"\n"},t.listitem=function(e){return"
  • "+e+"
  • \n"},t.checkbox=function(e){return" "},t.paragraph=function(e){return"

    "+e+"

    \n"},t.table=function(e,t){return"\n\n"+e+"\n"+(t=t&&""+t+"")+"
    \n"},t.tablerow=function(e){return"\n"+e+"\n"},t.tablecell=function(e,t){var u=t.header?"th":"td";return(t.align?"<"+u+' align="'+t.align+'">':"<"+u+">")+e+"\n"},t.strong=function(e){return""+e+""},t.em=function(e){return""+e+""},t.codespan=function(e){return""+e+""},t.br=function(){return this.options.xhtml?"
    ":"
    "},t.del=function(e){return""+e+""},t.link=function(e,t,u){if(null===(e=F(this.options.sanitize,this.options.baseUrl,e)))return u;e='"},t.image=function(e,t,u){if(null===(e=F(this.options.sanitize,this.options.baseUrl,e)))return u;u=''+u+'":">"},t.text=function(e){return e},e}(),O=function(){function e(){}var t=e.prototype;return t.strong=function(e){return e},t.em=function(e){return e},t.codespan=function(e){return e},t.del=function(e){return e},t.html=function(e){return e},t.text=function(e){return e},t.link=function(e,t,u){return""+u},t.image=function(e,t,u){return""+u},t.br=function(){return""},e}(),q=function(){function e(){this.seen={}}var t=e.prototype;return t.serialize=function(e){return e.toLowerCase().trim().replace(/<[!\/a-z].*?>/gi,"").replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g,"").replace(/\s/g,"-")},t.getNextSafeSlug=function(e,t){var u=e,n=0;if(this.seen.hasOwnProperty(u))for(n=this.seen[e];u=e+"-"+ ++n,this.seen.hasOwnProperty(u););return t||(this.seen[e]=n,this.seen[u]=0),u},t.slug=function(e,t){void 0===t&&(t={});e=this.serialize(e);return this.getNextSafeSlug(e,t.dryrun)},e}(),L=function(){function u(e){this.options=e||r.defaults,this.options.renderer=this.options.renderer||new Z,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new O,this.slugger=new q}u.parse=function(e,t){return new u(t).parse(e)},u.parseInline=function(e,t){return new u(t).parseInline(e)};var e=u.prototype;return e.parse=function(e,t){void 0===t&&(t=!0);for(var u,n,r,i,s,l,a,o,D,c,h,p,f,g,F,A,d="",C=e.length,k=0;kAn error occurred:

    "+D(e.message+"",!0)+"
    ";throw e}}j.options=j.setOptions=function(e){return b(j.defaults,e),e=j.defaults,r.defaults=e,j},j.getDefaults=e,j.defaults=r.defaults,j.use=function(){for(var e=arguments.length,t=new Array(e),u=0;uAn error occurred:

    "+D(e.message+"",!0)+"
    ";throw e}},j.Parser=L,j.parser=L.parse,j.Renderer=Z,j.TextRenderer=O,j.Lexer=I,j.lexer=I.lex,j.Tokenizer=z,j.Slugger=q;var P=(j.parse=j).options,Q=j.setOptions,U=j.use,M=j.walkTokens,N=j.parseInline,X=j,G=L.parse,E=I.lex;r.Lexer=I,r.Parser=L,r.Renderer=Z,r.Slugger=q,r.TextRenderer=O,r.Tokenizer=z,r.getDefaults=e,r.lexer=E,r.marked=j,r.options=P,r.parse=X,r.parseInline=N,r.parser=G,r.setOptions=Q,r.use=U,r.walkTokens=M,Object.defineProperty(r,"__esModule",{value:!0})}); \ No newline at end of file diff --git a/server/resource/rpt/ay-pf.js b/server/resource/rpt/ay-pf.js new file mode 100644 index 0000000..bf4f814 --- /dev/null +++ b/server/resource/rpt/ay-pf.js @@ -0,0 +1,3 @@ +/*! @license DOMPurify 2.3.5 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.5/LICENSE */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).DOMPurify=t()}(this,(function(){"use strict";var e=Object.hasOwnProperty,t=Object.setPrototypeOf,n=Object.isFrozen,r=Object.getPrototypeOf,o=Object.getOwnPropertyDescriptor,i=Object.freeze,a=Object.seal,l=Object.create,c="undefined"!=typeof Reflect&&Reflect,s=c.apply,u=c.construct;s||(s=function(e,t,n){return e.apply(t,n)}),i||(i=function(e){return e}),a||(a=function(e){return e}),u||(u=function(e,t){return new(Function.prototype.bind.apply(e,[null].concat(function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t1?n-1:0),o=1;o/gm),z=a(/^data-[\-\w.\u00B7-\uFFFF]/),B=a(/^aria-[\-\w]+$/),P=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),j=a(/^(?:\w+script|data):/i),G=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),W="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function q(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:Y(),n=function(t){return e(t)};if(n.version="2.3.5",n.removed=[],!t||!t.document||9!==t.document.nodeType)return n.isSupported=!1,n;var r=t.document,o=t.document,a=t.DocumentFragment,l=t.HTMLTemplateElement,c=t.Node,s=t.Element,u=t.NodeFilter,m=t.NamedNodeMap,E=void 0===m?t.NamedNodeMap||t.MozNamedAttrMap:m,V=t.HTMLFormElement,X=t.DOMParser,$=t.trustedTypes,Z=s.prototype,J=k(Z,"cloneNode"),Q=k(Z,"nextSibling"),ee=k(Z,"childNodes"),te=k(Z,"parentNode");if("function"==typeof l){var ne=o.createElement("template");ne.content&&ne.content.ownerDocument&&(o=ne.content.ownerDocument)}var re=K($,r),oe=re?re.createHTML(""):"",ie=o,ae=ie.implementation,le=ie.createNodeIterator,ce=ie.createDocumentFragment,se=ie.getElementsByTagName,ue=r.importNode,me={};try{me=x(o).documentMode?o.documentMode:{}}catch(e){}var fe={};n.isSupported="function"==typeof te&&ae&&void 0!==ae.createHTMLDocument&&9!==me;var de=H,pe=U,he=z,ge=B,ye=j,ve=G,be=P,Te=null,Ne=A({},[].concat(q(S),q(w),q(_),q(C),q(M))),Ee=null,Ae=A({},[].concat(q(L),q(R),q(I),q(F))),xe=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),ke=null,Se=null,we=!0,_e=!0,Oe=!1,Ce=!1,De=!1,Me=!1,Le=!1,Re=!1,Ie=!1,Fe=!1,He=!1,Ue=!0,ze=!0,Be=!1,Pe={},je=null,Ge=A({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),We=null,qe=A({},["audio","video","img","source","image","track"]),Ye=null,Ke=A({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Ve="http://www.w3.org/1998/Math/MathML",Xe="http://www.w3.org/2000/svg",$e="http://www.w3.org/1999/xhtml",Ze=$e,Je=!1,Qe=void 0,et=["application/xhtml+xml","text/html"],tt="text/html",nt=void 0,rt=null,ot=o.createElement("form"),it=function(e){return e instanceof RegExp||e instanceof Function},at=function(e){rt&&rt===e||(e&&"object"===(void 0===e?"undefined":W(e))||(e={}),e=x(e),Te="ALLOWED_TAGS"in e?A({},e.ALLOWED_TAGS):Ne,Ee="ALLOWED_ATTR"in e?A({},e.ALLOWED_ATTR):Ae,Ye="ADD_URI_SAFE_ATTR"in e?A(x(Ke),e.ADD_URI_SAFE_ATTR):Ke,We="ADD_DATA_URI_TAGS"in e?A(x(qe),e.ADD_DATA_URI_TAGS):qe,je="FORBID_CONTENTS"in e?A({},e.FORBID_CONTENTS):Ge,ke="FORBID_TAGS"in e?A({},e.FORBID_TAGS):{},Se="FORBID_ATTR"in e?A({},e.FORBID_ATTR):{},Pe="USE_PROFILES"in e&&e.USE_PROFILES,we=!1!==e.ALLOW_ARIA_ATTR,_e=!1!==e.ALLOW_DATA_ATTR,Oe=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Ce=e.SAFE_FOR_TEMPLATES||!1,De=e.WHOLE_DOCUMENT||!1,Ie=e.RETURN_DOM||!1,Fe=e.RETURN_DOM_FRAGMENT||!1,He=e.RETURN_TRUSTED_TYPE||!1,Le=e.FORCE_BODY||!1,Re=e.FORCE_HTML_DOCTYPE||!1,Ue=!1!==e.SANITIZE_DOM,ze=!1!==e.KEEP_CONTENT,Be=e.IN_PLACE||!1,be=e.ALLOWED_URI_REGEXP||be,Ze=e.NAMESPACE||$e,e.CUSTOM_ELEMENT_HANDLING&&it(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(xe.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&it(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(xe.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(xe.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Qe=Qe=-1===et.indexOf(e.PARSER_MEDIA_TYPE)?tt:e.PARSER_MEDIA_TYPE,nt="application/xhtml+xml"===Qe?function(e){return e}:h,Ce&&(_e=!1),Fe&&(Ie=!0),Pe&&(Te=A({},[].concat(q(M))),Ee=[],!0===Pe.html&&(A(Te,S),A(Ee,L)),!0===Pe.svg&&(A(Te,w),A(Ee,R),A(Ee,F)),!0===Pe.svgFilters&&(A(Te,_),A(Ee,R),A(Ee,F)),!0===Pe.mathMl&&(A(Te,C),A(Ee,I),A(Ee,F))),e.ADD_TAGS&&(Te===Ne&&(Te=x(Te)),A(Te,e.ADD_TAGS)),e.ADD_ATTR&&(Ee===Ae&&(Ee=x(Ee)),A(Ee,e.ADD_ATTR)),e.ADD_URI_SAFE_ATTR&&A(Ye,e.ADD_URI_SAFE_ATTR),e.FORBID_CONTENTS&&(je===Ge&&(je=x(je)),A(je,e.FORBID_CONTENTS)),ze&&(Te["#text"]=!0),De&&A(Te,["html","head","body"]),Te.table&&(A(Te,["tbody"]),delete ke.tbody),i&&i(e),rt=e)},lt=A({},["mi","mo","mn","ms","mtext"]),ct=A({},["foreignobject","desc","title","annotation-xml"]),st=A({},w);A(st,_),A(st,O);var ut=A({},C);A(ut,D);var mt=function(e){var t=te(e);t&&t.tagName||(t={namespaceURI:$e,tagName:"template"});var n=h(e.tagName),r=h(t.tagName);if(e.namespaceURI===Xe)return t.namespaceURI===$e?"svg"===n:t.namespaceURI===Ve?"svg"===n&&("annotation-xml"===r||lt[r]):Boolean(st[n]);if(e.namespaceURI===Ve)return t.namespaceURI===$e?"math"===n:t.namespaceURI===Xe?"math"===n&&ct[r]:Boolean(ut[n]);if(e.namespaceURI===$e){if(t.namespaceURI===Xe&&!ct[r])return!1;if(t.namespaceURI===Ve&&!lt[r])return!1;var o=A({},["title","style","font","a","script"]);return!ut[n]&&(o[n]||!st[n])}return!1},ft=function(e){p(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){try{e.outerHTML=oe}catch(t){e.remove()}}},dt=function(e,t){try{p(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){p(n.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!Ee[e])if(Ie||Fe)try{ft(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},pt=function(e){var t=void 0,n=void 0;if(Le)e=""+e;else{var r=g(e,/^[\r\n\t ]+/);n=r&&r[0]}"application/xhtml+xml"===Qe&&(e=''+e+"");var i=re?re.createHTML(e):e;if(Ze===$e)try{t=(new X).parseFromString(i,Qe)}catch(e){}if(!t||!t.documentElement){t=ae.createDocument(Ze,"template",null);try{t.documentElement.innerHTML=Je?"":i}catch(e){}}var a=t.body||t.documentElement;return e&&n&&a.insertBefore(o.createTextNode(n),a.childNodes[0]||null),Ze===$e?se.call(t,De?"html":"body")[0]:De?t.documentElement:a},ht=function(e){return le.call(e.ownerDocument||e,e,u.SHOW_ELEMENT|u.SHOW_COMMENT|u.SHOW_TEXT,null,!1)},gt=function(e){return e instanceof V&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof E)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore)},yt=function(e){return"object"===(void 0===c?"undefined":W(c))?e instanceof c:e&&"object"===(void 0===e?"undefined":W(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},vt=function(e,t,r){fe[e]&&f(fe[e],(function(e){e.call(n,t,r,rt)}))},bt=function(e){var t=void 0;if(vt("beforeSanitizeElements",e,null),gt(e))return ft(e),!0;if(g(e.nodeName,/[\u0080-\uFFFF]/))return ft(e),!0;var r=nt(e.nodeName);if(vt("uponSanitizeElement",e,{tagName:r,allowedTags:Te}),!yt(e.firstElementChild)&&(!yt(e.content)||!yt(e.content.firstElementChild))&&T(/<[/\w]/g,e.innerHTML)&&T(/<[/\w]/g,e.textContent))return ft(e),!0;if("select"===r&&T(/