Wiki source code of Контроль активности пропуска
Show last authors
| author | version | line-number | content |
|---|---|---|---|
| 1 | Часто бывает полезно выявить неактивные пропуска, то есть пропуска, по которым давно не было никаких событий доступа. Для решения этой задачи в Платформе НЕЙРОСС реализован скрипт автоматизации, позволяет автоматически обнаружить неактивные пропуска и поместить их в отдельную группу, а также, при необходимости, — выполнить бессрочную приостановку пропуска. | ||
| 2 | |||
| 3 | Контролируются не все пропуска, а только пропуска выбранных владельцев. | ||
| 4 | |||
| 5 | Посредством настройки свойств скрипта автоматизации вы можете: | ||
| 6 | |||
| 7 | * задать имя группы для помещения пропусков; | ||
| 8 | * определить, требуется ли автоматически выполнять изъятие пропусков, либо решение будет принимать оператор бюро пропусков; | ||
| 9 | * задать период неактивности — временной интервал в днях, по истечении которого при отсутствии событий доступа пропуск считается неактивным; | ||
| 10 | * указать, требуется ли учитывать факт изменения пропуска совместно с событием доступа — пропуск будет считаться неактивным, если за заданный период не было ни события доступа, ни факта изменения пропуска оператором бюро пропусков; | ||
| 11 | * пометить, что пропуск приостановлен скриптом автоматизации. | ||
| 12 | |||
| 13 | {{info}} | ||
| 14 | Задание автоматизации может запускаться оператором вручную, по расписанию (например, раз в неделю по воскресеньям), либо по какому-либо событию/системному действию. | ||
| 15 | {{/info}} | ||
| 16 | |||
| 17 | **Пример: Найти пропуска, по которым не было событий доступа в течение 60 дней и поместить их в группу «Устаревшие пропуска»** | ||
| 18 | |||
| 19 | Задание автоматизации запускается автоматически ежедневно в 23:00, производится поиск за интервал в 60 дне. Найденные неактивные пропуска не приостанавливаются, остаются действительными и принадлежащими «своим» группам, но дополнительно помещаются в группу «Устаревшие пропуска». Анализируются только события доступа, факт изменения пропуска оператором АРМ Доступ не анализируется. | ||
| 20 | |||
| 21 | [[image:attach:card_inactivity_scrypt.png]] | ||
| 22 | |||
| 23 | \\ | ||
| 24 | |||
| 25 | === {{id name="Контрольактивностипропуска-НастройкиСКУД"/}}Настройки СКУД === | ||
| 26 | |||
| 27 | Вам потребуется: | ||
| 28 | |||
| 29 | 1. Создать пользовательское свойство владельца пропуска для указания перечня лиц, для которых требуется проводить контроль активности пропуска. | ||
| 30 | 1. Добавить свойство на форму владельца пропуска и задать в значение true для владельцев, чьи пропуска требуется анализировать. | ||
| 31 | 1. Создать пользовательское свойство пропуска для пометок о том, что пропуск приостановлен скриптом. | ||
| 32 | 1. Добавить свойство на форму пропуска. | ||
| 33 | |||
| 34 | Порядок действий: | ||
| 35 | |||
| 36 | (% class="wrapped" %) | ||
| 37 | |=((( | ||
| 38 | Задача | ||
| 39 | )))|=((( | ||
| 40 | Комментарий | ||
| 41 | ))) | ||
| 42 | |((( | ||
| 43 | Создание пользовательского свойства | ||
| 44 | )))|((( | ||
| 45 | (% class="content-wrapper" %) | ||
| 46 | ((( | ||
| 47 | В разделе **Настройки СКУД** АРМ НЕЙРОСС Доступ: | ||
| 48 | |||
| 49 | 1. На вкладке **Свойства владельца пропуска** добавьте новое свойство, задайте название и ключ свойства, укажите тип «Переключатель». | ||
| 50 | [[image:attach:card_inactivity_appprp.png||height="250"]] | ||
| 51 | 1. На вкладке (% style="letter-spacing: 0.0px;" %)**Свойства пропуска** добавьте новое свойство, задайте название и ключ свойства, укажите тип «Переключатель». | ||
| 52 | [[image:attach:PausedByScrypt_addprp.png||height="250"]] | ||
| 53 | |||
| 54 | [[doc:neyross.Настройка Платформы НЕЙРОСС.Настройка функций контроля доступа.Добавление пользовательских свойств.WebHome]] | ||
| 55 | ))) | ||
| 56 | ))) | ||
| 57 | |((( | ||
| 58 | Изменение формы владельца пропуска | ||
| 59 | )))|((( | ||
| 60 | (% class="content-wrapper" %) | ||
| 61 | ((( | ||
| 62 | (% style="color: rgb(23,43,77);" %)В разделе **Персонал** АРМ НЕЙРОСС Доступ выберите группу пропусков, для которой нужно настроить контроль активности, перейдите к вкладке **Настройки группы**: | ||
| 63 | |||
| 64 | 1. (% style="color: rgb(23,43,77);" %)На вкладке (%%)**Форма ввода: Владелец пропуска** (% style="color: rgb(23,43,77);" %)добавьте поле **Контроль активности** (название может быть любым) на форму владельца пропуска. | ||
| 65 | 1. (% style="color: rgb(23,43,77);" %)На вкладке **Форма ввода: Пропуск добавьте** поле **Приостановлен скриптом** (название может быть любым) на форму пропуска. | ||
| 66 | |||
| 67 | {{info}} | ||
| 68 | Рекомендуем использовать Конструктор форм. При использовании нестандартных форм, обратитесь к специалистам компании ИТРИУМ за услугой по доработке формы. | ||
| 69 | {{/info}} | ||
| 70 | |||
| 71 | [[doc:neyross.Настройка Платформы НЕЙРОСС.Настройка функций контроля доступа.Настройка форм ввода данных.WebHome]] | ||
| 72 | |||
| 73 | (% style="color: rgb(23,43,77);" %)При необходимости вывода информации в таблице пропусков, на вкладке **Настройка таблицы** добавьте вывод данных свойств. | ||
| 74 | |||
| 75 | (% style="color: rgb(23,43,77);" %)[[doc:neyross.Настройка Платформы НЕЙРОСС.Настройка функций контроля доступа.Настройка таблицы пропусков.WebHome]] | ||
| 76 | ))) | ||
| 77 | ))) | ||
| 78 | |((( | ||
| 79 | Включение необходимости контроля активности пропуска | ||
| 80 | )))|((( | ||
| 81 | (% class="content-wrapper" %) | ||
| 82 | ((( | ||
| 83 | Выберите владельцев, для которых требуется проводить контроль активности пропуска, и в режиме редактирования пропуска / группы пропусков в поле **Контроль активности** установите переключатель в положение [[image:attach:ui.Параметры узла.WebHome@fa-on.png||thumbnail="true" height="18"]]**Включено**. Сохраните данные владельца. | ||
| 84 | |||
| 85 | [[image:attach:card_inactivity_person_form.png||width="500"]] | ||
| 86 | |||
| 87 | [[doc:neyross.АРМ НЕЙРОСС Доступ.Управление пропусками.WebHome]] | ||
| 88 | ))) | ||
| 89 | ))) | ||
| 90 | |((( | ||
| 91 | Просмотр результатов работы скрипта | ||
| 92 | )))|((( | ||
| 93 | Дождитесь выполнения условия запуска задания (в нашем примере это 23:00 текущего дня). | ||
| 94 | ))) | ||
| 95 | |||
| 96 | == {{id name="Контрольактивностипропуска-Настройкиавтоматизации"/}}Настройки автоматизации == | ||
| 97 | |||
| 98 | (% style="color: rgb(23,43,77);" %)Скопируйте приведённый ниже код скрипта (% class="auto-cursor-target" style="color: rgb(23, 43, 77); color: rgb(23, 43, 77)" %)и создайте файл с произвольным именем и расширением **JSON**, например: (%%)//задание_автоматизации_Контроль_устаревших пропусков.json//(% class="auto-cursor-target" style="color: rgb(23, 43, 77); color: rgb(23, 43, 77)" %). Для этого удобно использовать простые текстовые редакторы типа Блокнот или Notepad++. Вы также можете обратиться к специалистам компании ИТРИУМ, мы вышлем подготовленный файл. | ||
| 99 | |||
| 100 | {{expand title="Раскрыть | Код скрипта"}} | ||
| 101 | {{code language="java"}} | ||
| 102 | |||
| 103 | import play.api.libs.json.Format | ||
| 104 | import beans.pacs.{PacsFolderBean, PassBean} | ||
| 105 | import extensions.automation.scripts.AutomationActionScript | ||
| 106 | import extensions.automation.signals.AutomationSignal | ||
| 107 | import models.common.UserIdentity | ||
| 108 | import models.neyross.{Pass, PassComponent} | ||
| 109 | import models.pacs.{PacsFolder, PacsFolderComponent} | ||
| 110 | import dto.pacs.PacsFolderMovementDto | ||
| 111 | import services.logging.web.{LoggerWithWeb => Logger} | ||
| 112 | import play.api.libs.json.{JsObject, Json} | ||
| 113 | import proto.neyross.PassProto | ||
| 114 | import services.common.{SystemLogService, SystemLogTag} | ||
| 115 | import services.neyross.NeyrossEmbedApiService | ||
| 116 | import slick.dbio.DBIOAction | ||
| 117 | import utils.TableExtension | ||
| 118 | import utils.bootstrap.CustomPostgresProfile | ||
| 119 | import utils.common.ScalaUtils | ||
| 120 | |||
| 121 | import java.time.OffsetDateTime | ||
| 122 | import java.util.UUID | ||
| 123 | import scala.concurrent.Future | ||
| 124 | |||
| 125 | class ObsoletePassControlAutomationScript extends AutomationActionScript { | ||
| 126 | |||
| 127 | val logger = Logger("ObsoletePassControlAutomationScript") | ||
| 128 | |||
| 129 | private val postgresProfile = ctx.dbConfig.profile.asInstanceOf[CustomPostgresProfile] | ||
| 130 | |||
| 131 | import postgresProfile.api._ | ||
| 132 | |||
| 133 | implicit val ec = ctx.executionContext | ||
| 134 | private val neyrossEmbedApi = ctx.injector.instanceOf[NeyrossEmbedApiService] | ||
| 135 | private val pacsFolderBean = ctx.injector.instanceOf[PacsFolderBean] | ||
| 136 | private val systemLogService = ctx.injector.instanceOf[SystemLogService] | ||
| 137 | private val passBean = ctx.injector.instanceOf[PassBean] | ||
| 138 | |||
| 139 | private val folders = new PacsFolderComponent()(ctx.dbConfig) | ||
| 140 | private val passes = new PassComponent()(ctx.dbConfig, neyrossEmbedApi, ec) | ||
| 141 | |||
| 142 | // @parameter { "type": "string", "title": "Название группы для устаревших пропусков", "key": "workingFolder" } | ||
| 143 | val workingFolder = "Устаревшие пропуска" | ||
| 144 | |||
| 145 | // @parameter { "type": "string", "title": "Идентификатор свойства-признака контроля", "key": "shouldControlOutdateProperty", "description": "Контролироваться будут пропуска только тех владельцев, которые имеют данное свойство" } | ||
| 146 | val shouldControlOutdateProperty = "inactivity" | ||
| 147 | |||
| 148 | // @parameter { "type": "string", "title": "Значение свойства-признака контроля", "key": "shouldControlOutdatePropertyValue", "description": "Контролироваться будут пропуска только тех владельцев, которые имеют указанное значение данного свойства (true означает Да)" } | ||
| 149 | val shouldControlOutdatePropertyValue = "true" | ||
| 150 | |||
| 151 | // @parameter { "type": "select", "title": "Режим работы", "key": "mode", "options": [ { "value": "default", "label": "Помещение устаревших пропусков группу" }, { "value": "withStopping", "label": "Приостановка и помещение устаревших пропусков в группу" } ] } | ||
| 152 | val mode = "withStopping" | ||
| 153 | |||
| 154 | // @parameter { "type": "number", "title": "Максимальный период неактивности, дней", "key": "inactivityPeriod" } | ||
| 155 | val inactivityPeriod = 1 | ||
| 156 | |||
| 157 | // @parameter { "type": "boolean", "title": "Анализировать время изменения пропуска", "key": "isUpdateTimeAnalysisEnabled", "description": "Если флаг установлен, при расчёте активности пропуска будут учитываться также события изменения пропуска оператором бюро пропусков. В противном случае будет анализироваться только время последнего прохода по данному пропуску" } | ||
| 158 | val isUpdateTimeAnalysisEnabled = false | ||
| 159 | |||
| 160 | // @parameter { "type": "string", "title": "Идентификатор свойства-маркера контроля", "key": "scriptMarkPropertyKey", "description": "При автоматической приостановке пропуска к нему будет добавлено свойство с данным идентификатором и значением true (Да). Вы можете вынести данное свойство на форму пропуска или в таблицу пропусков, при необходимости" } | ||
| 161 | val scriptMarkPropertyKey = "PausedByScrypt" | ||
| 162 | |||
| 163 | val modeLocalization: Map[String, String] = | ||
| 164 | Map( | ||
| 165 | "default" -> "Помещение устаревших пропусков в группу", | ||
| 166 | "withStopping" -> "Приостановка и помещение устаревших пропусков в группу" | ||
| 167 | ) | ||
| 168 | |||
| 169 | logger.debug(s"mode: ${modeLocalization.getOrElse(mode, "")}") | ||
| 170 | logger.debug(s"modification time analysing: $isUpdateTimeAnalysisEnabled") | ||
| 171 | logger.debug(s"inactivity period: $inactivityPeriod") | ||
| 172 | |||
| 173 | override def onSignal(signal: AutomationSignal): Future[Unit] = { | ||
| 174 | workingFolder.trim match { | ||
| 175 | case "" => | ||
| 176 | logger.warn("working folder name is empty. do nothing") | ||
| 177 | Future.unit | ||
| 178 | case nonEmptyFolderName => | ||
| 179 | runWithNonEmptyFolderName(nonEmptyFolderName) | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | private def createFolderWithNameAndParent(name: String, parent: Option[String]) = { | ||
| 184 | val uuid = UUID.randomUUID().toString | ||
| 185 | val folder = PacsFolder( | ||
| 186 | id = None, | ||
| 187 | uuid = Some(uuid), | ||
| 188 | name = Some(name), | ||
| 189 | parent = parent, | ||
| 190 | isDefault = false, | ||
| 191 | templates = None, | ||
| 192 | defaultValues = None, | ||
| 193 | settings = None, | ||
| 194 | created = OffsetDateTime.now(), | ||
| 195 | deleted = false | ||
| 196 | ) | ||
| 197 | folders.insert(folder).map(_ => Some(uuid)) | ||
| 198 | } | ||
| 199 | |||
| 200 | private def getFolderUuidByName(folderName: String) = { | ||
| 201 | folders.folders | ||
| 202 | .filter(f => f.name === folderName && !f.deleted) | ||
| 203 | .result | ||
| 204 | .headOption | ||
| 205 | .flatMap({ | ||
| 206 | case Some(folder) => | ||
| 207 | DBIOAction.successful(folder.uuid) | ||
| 208 | case _ => | ||
| 209 | folders | ||
| 210 | .findDefault() | ||
| 211 | .flatMap({ | ||
| 212 | case Some(defaultFolder) if defaultFolder.uuid.nonEmpty => | ||
| 213 | createFolderWithNameAndParent(folderName, defaultFolder.uuid) | ||
| 214 | case _ => | ||
| 215 | logger.warn("there is no valid default folder. wtf?") | ||
| 216 | DBIOAction.successful(None) | ||
| 217 | }) | ||
| 218 | }) | ||
| 219 | } | ||
| 220 | |||
| 221 | private def isOutdated(timestamp: OffsetDateTime): Boolean = { | ||
| 222 | val threshold = OffsetDateTime.now().minusDays(inactivityPeriod) | ||
| 223 | logger.trace(s"checking if chosen timestamp $timestamp is older than threshold $threshold") | ||
| 224 | threshold.isAfter(timestamp) | ||
| 225 | } | ||
| 226 | |||
| 227 | private def isStoppedOrInOutdatedFolder( | ||
| 228 | pass: Pass, | ||
| 229 | outdatedFolderUuid: String, | ||
| 230 | passFolders: List[String] | ||
| 231 | ): Boolean = { | ||
| 232 | pass.getState.contains(PassProto.Pass.State.sSTOPPED) || passFolders.contains(outdatedFolderUuid) | ||
| 233 | } | ||
| 234 | |||
| 235 | private def isOutdatedPassRecord(record: PassRecord): Boolean = { | ||
| 236 | // from all pass times, choose latest one and check if it outdated | ||
| 237 | val lastAccessGrantedAtOption = record._2 | ||
| 238 | val created = record._3 | ||
| 239 | val lastUpdatedAtOption = record._4 | ||
| 240 | logger.trace(s"last access granted: $lastAccessGrantedAtOption, created: $created, last updated: $lastUpdatedAtOption") | ||
| 241 | // in any cases, pass has "created" time | ||
| 242 | var latestTimeToCheck: OffsetDateTime = created | ||
| 243 | if ( | ||
| 244 | lastAccessGrantedAtOption.isDefined && | ||
| 245 | lastAccessGrantedAtOption.get.isAfter(latestTimeToCheck) | ||
| 246 | ) { | ||
| 247 | // last access event was later than created time - using it | ||
| 248 | latestTimeToCheck = lastAccessGrantedAtOption.get | ||
| 249 | } | ||
| 250 | if (isUpdateTimeAnalysisEnabled) { | ||
| 251 | if ( | ||
| 252 | lastUpdatedAtOption.isDefined && | ||
| 253 | lastUpdatedAtOption.get.isAfter(latestTimeToCheck) | ||
| 254 | ) { | ||
| 255 | // last update event was later than created time and last access time - using it | ||
| 256 | latestTimeToCheck = lastUpdatedAtOption.get | ||
| 257 | } | ||
| 258 | } | ||
| 259 | isOutdated(latestTimeToCheck) | ||
| 260 | } | ||
| 261 | |||
| 262 | private def getPassesToMove(records: Seq[PassRecord], outdatedFolderUuid: String): Seq[Pass] = { | ||
| 263 | records | ||
| 264 | .filter(record => { | ||
| 265 | logger.trace(s"pass ${record._5.uuid} handling") | ||
| 266 | !isStoppedOrInOutdatedFolder(record._5, outdatedFolderUuid, record._1.getOrElse(Nil)) && | ||
| 267 | isOutdatedPassRecord(record) | ||
| 268 | }) | ||
| 269 | .map(_._5) | ||
| 270 | } | ||
| 271 | |||
| 272 | type PassFolders = Option[List[String]] | ||
| 273 | type LastAccessGrantedAt = Option[OffsetDateTime] | ||
| 274 | type Created = OffsetDateTime | ||
| 275 | type LastUpdatedAt = Option[OffsetDateTime] | ||
| 276 | type PassRecord = (PassFolders, LastAccessGrantedAt, Created, LastUpdatedAt, Pass) | ||
| 277 | |||
| 278 | type PersonUUID = String | ||
| 279 | type PersonFolders = Option[Seq[String]] | ||
| 280 | type PersonRecord = (PersonUUID, PersonFolders) | ||
| 281 | |||
| 282 | private def personToControlHandler( | ||
| 283 | outdatedFolderUuid: String, | ||
| 284 | personRecord: PersonRecord | ||
| 285 | ): Future[Seq[String]] = { | ||
| 286 | val dbAction = | ||
| 287 | passes | ||
| 288 | .extendedPasses4( | ||
| 289 | TableExtension.folders(ctx.dbConfig), | ||
| 290 | TableExtension.lastAccessGrantedAt(ctx.dbConfig), | ||
| 291 | TableExtension.created(ctx.dbConfig), | ||
| 292 | TableExtension.lastUpdatedAt(ctx.dbConfig) | ||
| 293 | ) | ||
| 294 | .filter(r => r.person === personRecord._1 && !r.deleted) | ||
| 295 | .result | ||
| 296 | |||
| 297 | ctx | ||
| 298 | .dbRun(dbAction) | ||
| 299 | .flatMap(result => { | ||
| 300 | logger.trace(s"found passes of person ${personRecord._1}: ${result.map(_._5.uuid)}") | ||
| 301 | val passesToMove = getPassesToMove(result, outdatedFolderUuid) | ||
| 302 | logger.trace(s"found outdated passes of person ${personRecord._1}: ${passesToMove.map(_.uuid)}") | ||
| 303 | if (passesToMove.nonEmpty) { | ||
| 304 | val isPersonInOutdatedFolder = personRecord._2.exists(_.contains(outdatedFolderUuid)) | ||
| 305 | val personsToMove = Option.when(!isPersonInOutdatedFolder)(personRecord._1).toList | ||
| 306 | val dto = PacsFolderMovementDto(personsToMove, passesToMove.map(_.uuid)) | ||
| 307 | pacsFolderBean.copyToFolder(outdatedFolderUuid, dto)(ui = null, None).map(_ => passesToMove.map(_.uuid)) | ||
| 308 | } else { | ||
| 309 | Future.successful(Nil) | ||
| 310 | } | ||
| 311 | }) | ||
| 312 | } | ||
| 313 | |||
| 314 | private def getPersonsToControl: Future[Vector[PersonRecord]] = { | ||
| 315 | val personsToControl = | ||
| 316 | sql""" | ||
| 317 | SELECT DISTINCT uuid, folders | ||
| 318 | FROM | ||
| 319 | neyross_person, | ||
| 320 | jsonb_to_recordset((person_json->>'properties'):: jsonb) AS props(key text, value text) | ||
| 321 | WHERE | ||
| 322 | key = '#${shouldControlOutdateProperty}' AND | ||
| 323 | value = '#${shouldControlOutdatePropertyValue}' AND | ||
| 324 | neyross_person.deleted = false | ||
| 325 | """.as[PersonRecord] | ||
| 326 | |||
| 327 | ctx.dbRun(personsToControl) | ||
| 328 | } | ||
| 329 | |||
| 330 | case class Property(key: String, value: String, index: Option[Long] = None) | ||
| 331 | |||
| 332 | object Property { | ||
| 333 | implicit val jsonFormat: Format[Property] = Json.format[Property] | ||
| 334 | } | ||
| 335 | |||
| 336 | private def stopPassesWithUuid(uuids: Seq[String]): Future[Unit] = { | ||
| 337 | ctx | ||
| 338 | .dbRun(passes.findByUuids(uuids)) | ||
| 339 | .flatMap(updatedPasses => { | ||
| 340 | ScalaUtils | ||
| 341 | .sequentialTraverse(updatedPasses)(updatedPass => { | ||
| 342 | logger.trace(s"stopping pass ${updatedPass.uuid}; setting property $scriptMarkPropertyKey and disabled_from fields") | ||
| 343 | val passJson = updatedPass.toFullJson.asOpt[JsObject].get | ||
| 344 | val passProperties = (passJson \ "properties").asOpt[List[Property]].getOrElse(Nil) | ||
| 345 | |||
| 346 | val newPassProperties = passProperties.filter(p => { | ||
| 347 | p.key != scriptMarkPropertyKey | ||
| 348 | }) :+ Property( | ||
| 349 | scriptMarkPropertyKey, | ||
| 350 | "true" | ||
| 351 | ) | ||
| 352 | val newPassJson = passJson ++ Json.obj( | ||
| 353 | "disabled_from" -> OffsetDateTime.now().toInstant.getEpochSecond.toInt, | ||
| 354 | "properties" -> Json.toJson(newPassProperties) | ||
| 355 | ) | ||
| 356 | logger.trace(s"new pass json is: $newPassJson") | ||
| 357 | passBean | ||
| 358 | .updateV2(updatedPass.uuid, newPassJson)(UserIdentity.getFakeUserIdentityFor("Автоматизация"), None) | ||
| 359 | }) | ||
| 360 | .map(_ => ()) | ||
| 361 | }) | ||
| 362 | } | ||
| 363 | |||
| 364 | private def runWithNonEmptyFolderName(folderName: String): Future[Unit] = { | ||
| 365 | systemLogService | ||
| 366 | .log( | ||
| 367 | SystemLogTag.NORMAL, | ||
| 368 | SystemLogTag.AUTOMATION | ||
| 369 | )(s"Запущен процесс контроля устаревших пропусков") | ||
| 370 | .map(_ => ()) | ||
| 371 | ctx | ||
| 372 | .dbRun(getFolderUuidByName(folderName)) | ||
| 373 | .flatMap({ | ||
| 374 | case Some(folderUUID) => | ||
| 375 | logger.debug(s"working folder is $folderName ($folderUUID)") | ||
| 376 | getPersonsToControl | ||
| 377 | .flatMap(persons => { | ||
| 378 | logger.debug(s"found persons to control: $persons") | ||
| 379 | ScalaUtils.sequentialTraverse(persons)(personToControlHandler(folderUUID, _)) | ||
| 380 | }) | ||
| 381 | .flatMap(uuids => { | ||
| 382 | val flatUuidsList = uuids.flatten.distinct | ||
| 383 | if (flatUuidsList.nonEmpty) { | ||
| 384 | val stopping = if (mode == "withStopping") { | ||
| 385 | stopPassesWithUuid(flatUuidsList) | ||
| 386 | } else { | ||
| 387 | Future.unit | ||
| 388 | } | ||
| 389 | stopping.flatMap(_ => { | ||
| 390 | val yesOrNo = Option | ||
| 391 | .when(isUpdateTimeAnalysisEnabled)("да") | ||
| 392 | .getOrElse("нет") | ||
| 393 | logger.debug(s"were moved ${flatUuidsList.size} passes") | ||
| 394 | val message = | ||
| 395 | s"""В группу '$folderName' было помещено ${flatUuidsList.size} пропусков. | ||
| 396 | |Режим работы: ${modeLocalization.getOrElse(mode, "")}. | ||
| 397 | |Анализировать время изменения пропуска: $yesOrNo | ||
| 398 | |Максимальный период неактивности: $inactivityPeriod дней""".stripMargin | ||
| 399 | systemLogService | ||
| 400 | .log( | ||
| 401 | SystemLogTag.NORMAL, | ||
| 402 | SystemLogTag.AUTOMATION | ||
| 403 | )( | ||
| 404 | message = message, | ||
| 405 | data = Some(Json.toJson(flatUuidsList)) | ||
| 406 | ) | ||
| 407 | .map(_ => ()) | ||
| 408 | }) | ||
| 409 | } else { | ||
| 410 | logger.debug("not found persons with obsolete persons or passes") | ||
| 411 | systemLogService | ||
| 412 | .log( | ||
| 413 | SystemLogTag.NORMAL, | ||
| 414 | SystemLogTag.AUTOMATION | ||
| 415 | )(s"Не найдено владельцев для контроля устаревших пропусков, либо все пропуска таких владельцев не являются устаревшими") | ||
| 416 | .map(_ => ()) | ||
| 417 | } | ||
| 418 | }) | ||
| 419 | case _ => | ||
| 420 | Future.unit | ||
| 421 | }) | ||
| 422 | Future.unit | ||
| 423 | } | ||
| 424 | } | ||
| 425 | |||
| 426 | new ObsoletePassControlAutomationScript | ||
| 427 | {{/code}} | ||
| 428 | {{/expand}} | ||
| 429 | |||
| 430 | (% class="auto-cursor-target" style="color: rgb(23, 43, 77); color: rgb(23, 43, 77)" %)Добавьте задание автоматизации | ||
| 431 | |||
| 432 | 1. (% class="auto-cursor-target" style="color: rgb(23, 43, 77); color: rgb(23, 43, 77)" %)В разделе **Автоматизация** нажмите на кнопку **Добавить новое задание [[image:attach:ui.Параметры узла.WebHome@btn_add_new.png||thumbnail="true" height="18"]]**, нажмите на кнопку **Импорт** и укажите путь к подготовленному на предыдущем этапе файлу ~[[[Автоматизация>>doc:ui.Параметры узла.Автоматизация.WebHome||style="" shape="rect"]]]. | ||
| 433 | 1. В блоке **Параметры сигнала** выберите **По расписанию**, настройте расписание (например, 0 23 * * * для ежедневного запуска, или 0 23 * * 1 для запуска раз в неделю по воскресеньям). | ||
| 434 | 1. ((( | ||
| 435 | В блоке **Параметры действия** настройте параметры задания автоматизации согласно таблице ниже. | ||
| 436 | |||
| 437 | (% class="wrapped" %) | ||
| 438 | |=((( | ||
| 439 | Параметр | ||
| 440 | )))|=((( | ||
| 441 | Комментарий | ||
| 442 | ))) | ||
| 443 | |((( | ||
| 444 | Название группы устаревших пропусков | ||
| 445 | )))|((( | ||
| 446 | Название группы. При запуске скрипта проверяется наличие группы с указанным именем в группе **Все**. При отсутствии, — она будет создана. В эту группу помещаться все найденные пропуска и их владельцы. Если название пустое, задание не выполняется. | ||
| 447 | ))) | ||
| 448 | |((( | ||
| 449 | Идентификатор свойства — признака контроля | ||
| 450 | )))|((( | ||
| 451 | Уникальный идентификатор пользовательского свойства владельца пропуска, созданный на этапе [[#Настройки СКУД>>doc:||anchor="Контрольактивностипропуска-НастройкиСКУД"]]. В нашем примере: inactivity | ||
| 452 | ))) | ||
| 453 | |((( | ||
| 454 | Значение свойства — признака контроля | ||
| 455 | )))|((( | ||
| 456 | (% class="content-wrapper" %) | ||
| 457 | ((( | ||
| 458 | * true — если требуется проверять пропуска, для владельцев которого в поле **Контроль пропусков** переключатель установлен в положение [[image:attach:ui.Параметры узла.WebHome@fa-on.png||thumbnail="true" height="18"]] **Включено**. | ||
| 459 | * false — если требуется проверять пропуска, для владельцев которого в поле **Контроль пропусков** переключатель установлен в положение [[image:attach:ui.Параметры узла.WebHome@fa-off.png||thumbnail="true" height="18"]] **Выключено**. | ||
| 460 | |||
| 461 | В нашем примере: true | ||
| 462 | ))) | ||
| 463 | ))) | ||
| 464 | |((( | ||
| 465 | Режим работы | ||
| 466 | )))|((( | ||
| 467 | Выберите из раскрывающегося списка требуемые действия с пропусками: требуется ли просто помещать пропуска в дополнительную группу для принятия решения оператором о дальнейших действиях с пропуском, либо необходимо приостанавливать действие найденных пропусков и помещать в группу. | ||
| 468 | |||
| 469 | * Помещение устаревших пропусков в группу — просто помещать пропуска в дополнительную группу для принятия решения оператором о дальнейших действиях с пропуском. Пропуск остаётся действительным. Доступ по нему возможен. | ||
| 470 | * Приостановка и помещение устаревших пропусков в группу — приостанавливать пропуск и помещать в дополнительную группу. (% style="color: rgb(23,43,77);" %)Пропуск не удаляется из контроллеров доступа, но доступ блокируется. | ||
| 471 | ))) | ||
| 472 | |((( | ||
| 473 | Максимальный период неактивности, дней | ||
| 474 | )))|((( | ||
| 475 | Укажите временной интервал в днях (количество дней), по истечению которого пропуск без событий доступа считается устаревшим. | ||
| 476 | ))) | ||
| 477 | |((( | ||
| 478 | Анализировать время изменения пропуска | ||
| 479 | )))|((( | ||
| 480 | Установите флаг в поле, если, помимо событий доступа при расчёте активности пропуска требуется учитывать не только события доступа, но и события изменения пропуска оператором бюро пропусков. | ||
| 481 | ))) | ||
| 482 | |((( | ||
| 483 | (% style="color: rgb(70,70,70);" %)Идентификатор свойства-маркера контроля | ||
| 484 | )))|((( | ||
| 485 | Уникальный идентификатор пользовательского свойства пропуска, значение которого будет устанавливаться в true для пропусков, которые приостановлены данным скриптом. В нашем примере: PausedByScrypt | ||
| 486 | ))) | ||
| 487 | |||
| 488 | === {{id name="Контрольактивностипропуска-Алгоритмработы"/}}Алгоритм работы === | ||
| 489 | |||
| 490 | (% class="wiki-list0" %) | ||
| 491 | 1. Поиск владельцев, для которых в поле с указанным идентификатором задано указанное значение. | ||
| 492 | 1. Поиск активных пропусков для найденных владельцев (если пропуск уже приостановлен или находится в группе устаревших пропусков операции с ним не выполняются). | ||
| 493 | 1. Поиск времени «активизации пропуска»: | ||
| 494 | (% class="wiki-list0" %) | ||
| 495 | 11. Если флаг в поле **Анализировать время изменения пропуска** установлен, то выбирается наиболее позднее из времени последнего редактирования и времени последнего прохода по пропуску | ||
| 496 | 11. Если флаг не установлен, выбирается время последнего прохода по пропуску. | ||
| 497 | 1. Если «время активизации» раньше чем (% class="inline-code" %)##текущее время минус **Максимальный период неактивности**,##(%%) пропуск дополнительно помещается в группу устаревших пропусков. | ||
| 498 | 1. Если указан режим работы «Приостановка и перемещение устаревших пропусков в группу», все найденные пропуска бессрочно приостанавливаются. В поле** Приостановлен с** задаётся текущее время. | ||
| 499 | ))) | ||
| 500 | 1. Нажмите на кнопку **Сохранить задание**. | ||
| 501 | |||
| 502 | {{error}} | ||
| 503 | **ВАЖНО** | ||
| 504 | |||
| 505 | В НЕЙРОСС пропуск не «привязывается» жёстко к одной группе. Принадлежность пропуска группе пропусков является просто свойством пропуска. Пропуск может принадлежать нескольким группам одновременно. При помещении пропуска в группу **Устаревшие пропуска**, пропуск остаётся также и в групе, в которой он располагался до запуска скрипта. Изъятие пропуска из группы осуществляется оператором АРМ Доступ с помощью команды (% style="text-align: left;" %)**Другие действия**(% style="color: rgb(23,43,77);" %) (% style="text-align: left;" %)**> Действие с группами > Изъять из текущей группы.** | ||
| 506 | {{/error}} | ||
| 507 | |||
| 508 | === {{id name="Контрольактивностипропуска-Запускзадания"/}}Запуск задания === | ||
| 509 | |||
| 510 | Дождитесь выполнения условия запуска задания или запустите задание вручную. Для ручного запуска в поле **Тип сигнала** выберите значение **По HTTP-запросу**, сохраните задание и нажмите (% style="color: rgb(0,0,255);" %)отправить запрос(%%). | ||
| 511 | |||
| 512 | [[image:attach:task_run_manually.png||height="205"]] | ||
| 513 | |||
| 514 | Вы можете отслеживать процедуру инициализации и выполнения задания в Журнале аудита ~[[[doc:ui.Журнал аудита.WebHome]]]. | ||
| 515 | |||
| 516 | [[image:attach:card_inactivity_scrypt_log.png||width="900"]] | ||
| 517 | |||
| 518 | Запуск и выполненные скриптом действия фиксируются в системном журнале ~[[[doc:ui.Системный журнал.WebHome]]]. | ||
| 519 | |||
| 520 | [[image:attach:card_inactivity_journal.png]] | ||
| 521 | |||
| 522 | Если в поле** Режим работы** задана необходимость приостановки пропуска. В системном журнале отражается факт блокировки действия на контроллерах и задания срока приостановки с текущего времени: | ||
| 523 | |||
| 524 | [[image:attach:card_inactivity_journal1.png]] | ||
| 525 | |||
| 526 | Пропуска приостанавливаются с текущего времени, свойство **Приостановлен скриптом** у приостановленных пропусков устанавливается в значение **Да** (true). | ||
| 527 | |||
| 528 | [[image:attach:card_inactivity_card_form.png||height="250"]] |