Контрольные списки используются повсеместно в большинстве отраслей, и строительная отрасль не является исключением. При выполнении сложных задач легко упустить мелочи, которые впоследствии могут привести к большим проблемам. Контрольные списки помогают разбить сложные задачи на логические шаги, гарантируя, что все критические элементы проверены и проанализированы. Это помогает обеспечить соблюдение законодательных требований, устанавливает ответственность и служит важной документацией на будущее.

В этой статье мы разрабатываем контрольный список контроля качества. Такой контрольный список предоставит надзорным органам минимальные требования, которые необходимо проверить после завершения деятельности. Пример контрольного списка армирования показан на рисунке ниже; мы разработаем этот контрольный список в ходе этой статьи.

Как мы видим, контрольные списки являются первыми кандидатами на оцифровку с использованием форм PDF. Поля формы, такие как текстовые поля, переключатели, флажки и поля для подписи, очень похожи на то, что можно было бы ожидать найти в рукописном контрольном списке. Оцифровка также имеет дополнительное преимущество, заключающееся в возможности быстрого сбора данных. Например, если бы у нас были тысячи контрольных списков, что характерно для большинства строительных площадок, можно было бы быстро идентифицировать все контрольные списки, подготовленные конкретным пользователем. В предыдущей статье я описал один из способов получения данных формы PDF.

Важной частью любой цифровой системы является пользовательский интерфейс. Плохо спроектированный пользовательский интерфейс может привести к тому, что разочарованные пользователи быстро откажутся от использования системы. Одним из аспектов взаимодействия с пользователем является содержание контрольного списка; хорошо разработанный контрольный список является исчерпывающим, но кратким. Другим важным критерием хорошей системы является то, что она должна вести себя предсказуемо. Здесь важную роль играет проверка ввода пользователя.

Разработка надежного пользовательского интерфейса — трудоемкий процесс. Непредсказуемый характер реакции человека затрудняет разработку системы, которая может учитывать все различные возможные сценарии и реагировать соответствующим образом. Представьте себе разработку простой системы, которая вычисляет скорость автомобиля по пройденному расстоянию и времени, затраченному на преодоление этого расстояния, где значения расстояния и скорости предоставляются пользователем. То, что должно было быть очень простой задачей деления расстояния на время, взрывается, когда пользователь вводит ноль в качестве значения времени. Деление на ноль — одна из самых распространенных и досадных ошибок. К счастью, с такими ошибками легко справиться, проверив ввод пользователя. Валидация — это обработка пользовательского ввода и проверка того, находится ли он в пределах некоторого заданного диапазона, прежде чем принять его.

Другой метод контроля взаимодействия с пользователем — ограничение доступа к определенным полям. Значения в этих полях могут быть рассчитаны на основе ввода, который пользователь предоставил в другом месте формы. Например, поле, отображающее скорость в предыдущем примере, будет вычисляемым полем.

Adobe Acrobat предоставляет множество возможностей для проверки и вычисления значений в поле. Доступ к большинству этих параметров можно получить, щелкнув поле правой кнопкой мыши и открыв его диалоговое окно Свойства и перейдя на три последние вкладки: Формат, Проверить и Рассчитать. Большинство этих параметров можно выбрать из списка предопределенных форматов, и для этого не требуются знания программирования. Однако для включения более сложной логики знание JavaScript может иметь большое значение. В этой статье мы демонстрируем использование обоих методов.

Вкладка Формат диалогового окна свойств позволяет нам установить различные предопределенные форматы для ввода текста. Например, в нашем контрольном списке мы хотим, чтобы поле даты было представлено в формате дд/мм/гггг. На вкладке Формат в категории формата Дата мы видим множество предопределенных форматов даты. Однако, поскольку в большинстве из них месяц предшествует дате, мы не можем найти нужный формат. Мы определяем наш собственный пользовательский формат, выбирая Пользовательский из списка и вводя требуемый формат в текстовое поле ниже.

Текст в текстовом поле может быть ограничен любым шаблоном с помощью параметра Произвольная маска в категории формата Специальный. Мы можем указать, что номер нашего контрольного списка должен соответствовать определенному шаблону: пятизначный код проекта, за которым следует текст «-CHK-» и четырехзначный идентификационный номер. Произвольная маска работает, используя специальные символы для определения допустимого символа в каждом месте текста. В таблице ниже перечислены возможные специальные символы. Для нашего случая подойдет маска 99999-CHK-9999.

Вкладка Проверка содержит параметры для ограничения значений числовых полей заданным диапазоном. Чтобы использовать этот параметр, категория на вкладке Формат должна быть установлена ​​на Число. В нашем контрольном списке мы хотели бы ограничить значения ревизии целым числом в диапазоне от 0 до 99, включая оба числа. Чтобы отображались только целые числа, установите для параметра Десятичные разряды на вкладке Формат значение ноль. Теперь мы можем указать требуемый диапазон на вкладке Проверить, выбрав параметр Значение поля находится в диапазоне и указав требуемый диапазон.

На вкладке Проверка также есть параметр Запустить собственный скрипт проверки. Используя эту опцию, можно выполнить более сложную проверку поля с помощью JavaScript. Как и в случае с номером контрольного списка, мы хотим, чтобы наши номера чертежей соответствовали фиксированному формату. Чтобы сделать ситуацию более интересной, должна быть возможность связать несколько рисунков с одним контрольным списком; каждый рисунок через запятую.

Наши номера чертежей состоят из пятизначного кода проекта, за которым следует строка «-DWG-», затем трехзначный код дисциплины «CIV-» или «STR-» и, наконец, четырехзначный идентификационный номер. Чтобы проверить, соответствует ли ввод требуемому шаблону, мы используем регулярные выражения. Регулярные выражения — это мощный инструмент для обработки текста, а JavaScript предоставляет широкие возможности для его использования.

//The following code is the Validation script for 'Drawing No.' field.
 //Split the field's text at the comma locations.
 var dwgs = event.value.split(',');
 //Define a regular expresion that evaluates to true for a text of the following form
 //99999-DWG-CIV-9999 or 99999-DWG-STR-9999, where 9 indicates any digit.
 var reg = /^\s*\d\d\d\d\d-DWG-(CIV|STR)-\d\d\d\d\s*$/;
 //Iterate through all the drawings.
 for (var i = 0; i<dwgs.length;i++){
  //validation fails if the pattern does not match. 
  if (dwgs[i].search(reg) == -1){
   app.alert(dwgs[i] +" is an invalid document number. Please check.");
   event.rc = false;
  }
 }

Как мы видим, JavaScript позволяет нам создавать сколь угодно сложную логику проверки. Сделав еще один шаг вперед, скажем, мы хотим ограничить номера проектов в нашем контрольном списке одним из нескольких проектов, которые компания выполняет в настоящее время. В дополнение к проверке поля мы также хотели бы, чтобы имя проекта заполнялось автоматически на основе номера проекта. Для этой цели мы предоставляем список номеров и названий проектов в виде вложенного файла Значения, разделенные запятыми (CSV). Затем мы проверяем поле на основе значений во вложении.

Чтобы получить содержимое файла CSV, мы используем метод документа getDataObjectContent(). Это считывает содержимое вложения в объект потока. Чтобы преобразовать объект потока в удобочитаемую строку, мы используем метод stringFromStream(). Получив строку, мы приступаем к ее анализу с помощью функции split().

//The following code is the Validation script for the 'Project No.' field.
 var line_dat = [];
 var found = false;
 var lines = '';
 var contents = '';
 var proj_name_field = this.getField('proj_name');
 //Read the attached 'project_details.csv' file.
 var proj = this.getDataObjectContents('project_details.csv');
 if (proj != null){  
  //Convert the contents of the file to a string.
  contents = util.stringFromStream(proj);
  //Split the file contents into lines
  lines = contents.split('\n');
  found = false;
  for (var i = 0;i<lines.length; i++){
   //Split the each line at commas to obtain the data.
   line_dat = lines[i].split(',');
   proj_no = line_dat[0]; 
   proj_name = line_dat[1];
   //Check if the value of the field matches the project number in the file.
   if (event.value == proj_no){
    //update the 'Project name' field with the corresponding value in the file. 
    proj_name_field.value = proj_name;
    found = true;   
   }
  }
  //If not project number is not found in file then the validation fails.
  if (!found)
   app.alert('Invalid project number.');
  event.rc = found;
 }

Как показано в предыдущем примере, скрипт проверки можно использовать для улучшения взаимодействия с пользователем путем автоматического заполнения полей формы. В нашем контрольном списке также есть несколько полей со списком, которые позволяют пользователю выбрать тип покрытия и тип поддержки для армирования. Информация в последующих вопросах, например характеристики покрытия, изменяется в зависимости от выбора пользователя.

//The following code is placed in the validation event of the 'Coating types' combo box.
 var coating_spec = this.getField('coating_spec');
 var repair_spec = this.getField('repair_spec');
 var q5_1 = this.getField('q5.1');
 var q5_1_rem = this.getField('q5.1_remark');
 var q5_2 = this.getField('q5.2');
 var q5_2_rem = this.getField('q5.2_remark');
 var q5_3 = this.getField('q5.3');
 var q5_3_rem = this.getField('q5.3_remark');
 if (event.value == 'None'){
  //There is no need to provide coating specifications if there is no coating.
  coating_spec.value = "-";
  repair_spec.value = "-";
  //Questions 5.1,5.2 and 5.3 are not applicable when there is no coating. 
  q5_1.value = 'na';
  q5_1.readonly = true;
  q5_1_rem.readonly = true;
  q5_2.value = 'na';
  q5_2.readonly = true;
  q5_2_rem.readonly = true;
  q5_3.value = 'na';
  q5_3.readonly = true;
  q5_3_rem.readonly = true;
 }else if (event.value == 'Epoxy coated'){
  //Update the specifations for epoxy coated reinforcement.
  coating_spec.value = 'ASTM A775';
  repair_spec.value = 'ASTM A775';
  //All questions 5.1, 5.2 and 5.3 are applicable for epoxy coated reinforcement.
  q5_1.value = 'no';
  q5_1.readonly = false;
  q5_1_rem.readonly = false;
  q5_2.value = 'no';
  q5_2.readonly = false;
  q5_2_rem.readonly = false; 
  q5_3.value = 'no';
  q5_3.readonly = false;
  q5_3_rem.readonly = false;
 }else if(event.value == 'Zinc coated (Galvanized)'){
  //Update the specifations for galvanized reinforcement.
  coating_spec.value = 'ASTM A767';
  repair_spec.value = 'ASTM A780';
  //All questions 5.1, 5.2 and 5.3 are applicable for galvanized reinforcement.
  q5_1.value = 'no';
  q5_1.readonly = false;
  q5_1_rem.readonly = false;
  q5_2.value = 'no';
  q5_2.readonly = false;
  q5_2_rem.readonly = false;
  q5_3.value = 'no';
  q5_3.readonly = false;
  q5_3_rem.readonly = false;
 }

//The following code is placed in the validation event of the 'Reinforcement support types' combo box
 var q7_2 = this.getField('q7.2');
 var q7_2_rem = this.getField('q7.2_remark');
 var q7_3 = this.getField('q7.3');
 var q7_3_rem = this.getField('q7.3_remark');
 var q7_4 = this.getField('q7.4');
 var q7_4_rem = this.getField('q7.4_remark');
 if (event.value == 'Plastic supports'){
  //Questions 7.2, 7.3 and 7.4 are not applicable for plastic supports.
  q7_2.value = 'na';
  q7_2.readonly = true;
  q7_2_rem.readonly = true;
  q7_3.value = 'na';
  q7_3.readonly = true;
  q7_3_rem.readonly = true;
  q7_4.value = 'na';
  q7_4.readonly = true;
  q7_4_rem.readonly = true;
 }else if (event.value == 'Precast concrete supports'){
  //Questions 7.2 and 7.4 are not applicable for precast concrete supports.
  q7_2.value = 'na';
  q7_2.readonly = true;
  q7_2_rem.readonly = true;
  q7_3.value = 'no';
  q7_3.readonly = false;
  q7_3_rem.readonly = false;
  q7_4.value = 'na';
  q7_4.readonly = true;
  q7_4_rem.readonly = true; 
 }else if (event.value == 'Wire supports'){
  //Questions 7.3 is not applicable for wire supports.
  q7_2.value = 'no';
  q7_2.readonly = false;
  q7_2_rem.readonly = false;
  q7_3.value = 'na';
  q7_3.readonly = true;
  q7_3_rem.readonly = true;
  q7_4.value = 'no';
  q7_4.readonly = false;
  q7_4_rem.readonly = false;
 }

Значения полей также можно вычислить с помощью события calculate. Этот сценарий запускается при обновлении любого другого поля в форме. Вопросы 4.2 и 6.2 предоставляют пользователю допустимый допуск для размещения арматуры и покрытия. Эти поля рассчитываются на основе ввода пользователя в предыдущие поля. Для предотвращения несанкционированного доступа эти поля определены как доступные только для чтения.

//The following code is placed in the Calculate event of 'Tolerance for reinforcement placement' field.
 var memb_depth = this.getField('memb_depth');
 //As per ACI 117 if the member depth is less than or equal to 4in the tolerance shall be at most 1/4in,
 //for member depths from 4in to 12in the tolerance shall be at most 3/8in,
 //and for members greater than 12in in thickness the tolerance shall be 1/2in.
 if (memb_depth.value <= 4)
  event.value = '(+/-) 1/4';
 else if (memb_depth.value <= 12)
  event.value = '(+/-) 3/8';
 else 
  event.value = '(+/-) 1/2';

//The following code is placed in the Calculate event of 'Tolerance for cover' field.
 //As per ACI 117 if the member depth is less than or equal to 12in then the cover shall not be reduced
 //by more than 0.375in, otherwise the cover shall not be reduced by more than 0.5in. In no case shall
 //the cover be reduced by a value more than 1/3 the specified cover.
 var cov_tol = 0.0;
 var memb_depth = this.getField('memb_depth');
 var cover = this.getField('cover');
 if (memb_depth.value <= 12)
  cov_tol = 0.375;
 else 
  cov_tol = 0.5;
 if (cov_tol > cover.value/3.0) 
    cov_tol = cover.value/3.0;
 //Round the value to three decimal places.
 event.value = -Math.round(cov_tol*1000)/1000;

Также возможно запустить сценарий, когда поле подписи подписано. Мы можем использовать этот скрипт, чтобы проверить, заполнены ли все обязательные поля. Это гарантирует, что неполные формы не будут отправлены. Мы можем использовать метод resetForm() для очистки подписи, если требуемые условия не выполняются.

//The following code is placed in the Sign event of the contractor 'Checker By' signature field.
 try{
  //If any of the required fields are empty diplay a error message.
  var f = this.getField('title');
  if (f.value == '')
   throw('Title is a required field.'); 
  f = this.getField('check_no');
  if (f.value == '')
   throw('Checklist no. is a required field.');
  f = this.getField('dwg_no');
  if (f.value == '')
   throw('Drawing no. is a required field.');
  f = this.getField('date');
  if (f.value == '')
   throw('Date is a required field.');
  f= this.getField('rev');
  if (f.value == '')
   throw('Rev is a required field.');
  f = this.getField('contractor');
  if (f.value == '')
   throw('Contractor is a required field.');
  f = this.getField('proj_no');
  if (f.value == '')
   throw('Project no. is a required field.');
  //If any question is answered with a 'no', the user has to provide a justification in the 'Remarks' field.
  var q_nos = ['q1.0','q2.0','q3.0','q4.1','q4.2','q5.1','q5.2','q5.3','q6.1','q6.2','q7.1','q7.2','q7.3','q7.4','q8.0','q9.0','q10.0']
  for (var i = 0; i < q_nos.length; i++){
   f = this.getField(q_nos[i]);
   if (f.value == 'no'){
    f = this.getField(q_nos[i] + '_remark');
    if (f.value == ""){
     throw ('For item ' + q_nos[i] + ' you need to provide justifications for your response "No".');
    }
   }
  }
 }catch(e){
  //If the above conditions are not met display a mesage and clear the signature.
  app.alert(e);
  this.resetForm(['Signature1']);
 }

После того, как контролер Подрядчика заполнит и подпишет документ, утверждающий Подрядчик должен подписать документ. За этим следуют контролер и утверждающий клиент, именно в таком порядке. Если какая-либо из предыдущих подписей в этой последовательности отсутствует, отображается сообщение об ошибке. Здесь мы используем метод signatureValidate(), чтобы проверить, подписано ли поле подписи. Этот метод вернет значение 4, если в поле присутствует действительная подпись.

//The following code is placed in the Sign event of the contractor 'Approved By' signature field.
 var f = this.getField('Signature1');
 if (f.signatureValidate() != 4){
   app.alert('Contractor "checked by" signature is not valid!')
   this.resetForm(['Signature2']);
 }
//The following code is placed in the Sign event of the client 'Checked By' signature field.
 var f = this.getField('Signature2');                                                                           
 if (f.signatureValidate() != 4){
   app.alert('Contractor "approve by" signature is not valid!')
   this.resetForm(['Signature3']);
 }
//The following code is placed in the Sign event of the client 'Approved By' signature field.
 var f = this.getField('Signature3');
 if (f.signatureValidate() != 4){
   app.alert('Client "checked by" signature is not valid!')
   this.resetForm(['Signature4']);
 }

Наконец, мы добавляем к кнопке Очистить небольшой скрипт, который сбрасывает ответы на все вопросы до значений по умолчанию и очищает все примечания.

//The following code is placed in the MouseUp event of the 'Clear' button
 var q_nos = ['q1.0','q2.0','q3.0','q4.1','q4.2','q5.1','q5.2','q5.3','q6.1','q6.2','q7.1','q7.2','q7.3','q7.4','q8.0','q9.0','q10.0']
 for (var i = 0; i < q_nos.length; i++){
  f = this.getField(q_nos[i]);
  if (f.readonly == false)
   f.value = 'no';
  f = this.getField(q_nos[i] + '_remark');
  f.value = '';
 }

Валидация — обширная тема; такая короткая статья вряд ли отдала бы должное этой теме. Однако, по опыту автора, потратив немного времени на проверку входных данных, вы сможете обнаружить многие возможные ошибки пользовательского ввода. И значительно улучшит пользовательский опыт.