Программный интерфейс

Показывать:
/**
 * Конструкторы менеджеров данных
 *
 * © Evgeniy Malyarov http://www.oknosoft.ru 2014-2016
 *
 * @module  metadata
 * @submodule meta_mngrs
 * @requires common
 */


/**
 * ### Абстрактный менеджер данных
 * Не используется для создания прикладных объектов, но является базовым классом,
 * от которого унаследованы менеджеры как ссылочных данных, так и объектов с суррогратным ключом и несохраняемых обработок<br />
 * См. так же:
 * - {{#crossLink "EnumManager"}}{{/crossLink}} - менеджер перечислений
 * - {{#crossLink "RefDataManager"}}{{/crossLink}} - абстрактный менеджер ссылочных данных
 * - {{#crossLink "CatManager"}}{{/crossLink}} - менеджер регистров накопления
 * - {{#crossLink "ChartOfCharacteristicManager"}}{{/crossLink}} - менеджер регистров накопления
 * - {{#crossLink "ChartOfAccountManager"}}{{/crossLink}} - менеджер регистров накопления
 * - {{#crossLink "DocManager"}}{{/crossLink}} - менеджер регистров накопления
 * - {{#crossLink "DataProcessorsManager"}}{{/crossLink}} - менеджер обработок
 * - {{#crossLink "RegisterManager"}}{{/crossLink}} - абстрактный менеджер регистра (накопления, сведений и бухгалтерии)
 * - {{#crossLink "InfoRegManager"}}{{/crossLink}} - менеджер регистров сведений
 * - {{#crossLink "LogManager"}}{{/crossLink}} - менеджер журнала регистрации
 * - {{#crossLink "AccumRegManager"}}{{/crossLink}} - менеджер регистров накопления
 * - {{#crossLink "TaskManager"}}{{/crossLink}} - менеджер задач
 * - {{#crossLink "BusinessProcessManager"}}{{/crossLink}} - менеджер бизнес-процессов
 *
 * @class DataManager
 * @constructor
 * @param class_name {string} - имя типа менеджера объекта. например, "doc.calc_order"
 * @menuorder 10
 * @tooltip Менеджер данных
 */
function DataManager(class_name){

  var _meta = _md.get(class_name),

    _events = {

      /**
       * ### После создания
       * Возникает после создания объекта. В обработчике можно установить значения по умолчанию для полей и табличных частей
       * или заполнить объект на основании данных связанного объекта
       *
       * @event after_create
       * @for DataManager
       */
      after_create: [],

      /**
       * ### После чтения объекта с сервера
       * Имеет смысл для объектов с типом кеширования ("doc", "doc_remote", "meta", "e1cib").
       * т.к. структура _DataObj_ может отличаться от прототипа в базе-источнике, в обработчике можно дозаполнить или пересчитать реквизиты прочитанного объекта
       * 
       * @event after_load
       * @for DataManager
       */
      after_load: [],

      /**
       * ### Перед записью
       * Возникает перед записью объекта. В обработчике можно проверить корректность данных, рассчитать итоги и т.д.
       * Запись можно отклонить, если у пользователя недостаточно прав, либо введены некорректные данные
       *
       * @event before_save
       * @for DataManager
       */
      before_save: [],

      /**
       * ### После записи
       *
       * @event after_save
       * @for DataManager
       */
      after_save: [],

      /**
       * ### При изменении реквизита шапки или табличной части
       *
       * @event value_change
       * @for DataManager
       */
      value_change: [],

      /**
       * ### При добавлении строки табличной части
       *
       * @event add_row
       * @for DataManager
       */
      add_row: [],

      /**
       * ### При удалении строки табличной части
       *
       * @event del_row
       * @for DataManager
       */
      del_row: []
    };

  this.__define({

    /**
     * ### Способ кеширования объектов этого менеджера
     *
     * Выполняет две функции:
     * - Указывает, нужно ли сохранять (искать) объекты в локальном кеше или сразу топать на сервер
     * - Указывает, нужно ли запоминать представления ссылок (инверсно).
     * Для кешируемых, представления ссылок запоминать необязательно, т.к. его быстрее вычислить по месту
     * @property cachable
     * @for DataManager
     * @type String - ("ram", "doc", "doc_remote", "meta", "e1cib")
     * @final
     */
    cachable: {
      get: function () {

        // перечисления кешируются всегда
        if(class_name.indexOf("enm.") != -1)
          return "ram";

        // Если в метаданных явно указано правило кеширования, используем его
        if(_meta.cachable)
          return _meta.cachable;

        // документы, отчеты и обработки по умолчанию кешируем в idb, но в память не загружаем
        if(class_name.indexOf("doc.") != -1 || class_name.indexOf("dp.") != -1 || class_name.indexOf("rep.") != -1)
          return "doc";

        // остальные классы по умолчанию кешируем и загружаем в память при старте
        return "ram";

      }
    },


    /**
     * ### Имя типа объектов этого менеджера
     * @property class_name
     * @for DataManager
     * @type String
     * @final
     */
    class_name: {
      value: class_name,
      writable: false
    },

    /**
     * ### Указатель на массив, сопоставленный с таблицей локальной базы данных
     * Фактически - хранилище объектов данного класса
     * @property alatable
     * @for DataManager
     * @type Array
     * @final
     */
    alatable: {
      get : function () {
        return $p.wsql.aladb.tables[this.table_name] ? $p.wsql.aladb.tables[this.table_name].data : []
      }
    },

    /**
     * ### Метаданные объекта
     * указатель на фрагмент глобальных метаданных, относящмйся к текущему объекту
     *
     * @method metadata
     * @for DataManager
     * @return {Object} - объект метаданных
     */
    metadata: {
      value: function(field){
        if(field)
          return _meta.fields[field] || _meta.tabular_sections[field];
        else
          return _meta;
      }
    },

    /**
     * ### Добавляет подписку на события объектов данного менеджера
     * В обработчиках событий можно реализовать бизнес-логику при создании, удалении и изменении объекта.
     * Например, заполнение шапки и табличных частей, пересчет одних полей при изменении других и т.д.
     *
     * @method on
     * @for DataManager
     * @param name {String|Object} - имя события [after_create, after_load, before_save, after_save, value_change, add_row, del_row]
     * @param [method] {Function} - добавляемый метод, если не задан в объекте первым параметром
     *
     * @example
     *
     *     // Обработчик при создании документа
     *     // @this {DataObj} - обработчик вызывается в контексте текущего объекта
     *     $p.doc.nom_prices_setup.on("after_create", function (attr) {
     *       // присваиваем новый номер документа
     *       return this.new_number_doc();
     *     });
     *
     *     // Обработчик события "при изменении свойства" в шапке или табличной части при редактировании в форме объекта
     *     // @this {DataObj} - обработчик вызывается в контексте текущего объекта
     *     $p.doc.nom_prices_setup.on("add_row", function (attr) {
     *       // установим валюту и тип цен по умолчению при добавлении строки
     *       if(attr.tabular_section == "goods"){
     *         attr.row.price_type = this.price_type;
     *         attr.row.currency = this.price_type.price_currency;
     *       }
     *     });
     *
     */
    on: {
      value: function (name, method) {
        if(typeof name == "object"){
          for(var n in name){
            if(name.hasOwnProperty(n))
              _events[n].push(name[n]);
          }
        }else
          _events[name].push(method);
      }
    },

    /**
     * ### Удаляет подписку на событие объектов данного менеджера
     *
     * @method off
     * @for DataManager
     * @param name {String} - имя события [after_create, after_load, before_save, after_save, value_change, add_row, del_row]
     * @param [method] {Function} - удаляемый метод. Если не задан, будут отключены все обработчики событий `name`
     */
    off: {
      value: function (name, method) {

      }
    },

    /**
     * ### Выполняет методы подписки на событие
     * Служебный, внутренний метод, вызываемый формами и обсерверами при создании и изменении объекта данных<br/>
     * Выполняет в цикле все назначенные обработчики текущего события<br/>
     * Если любой из обработчиков вернул `false`, возвращает `false`. Иначе, возвращает массив с результатами всех обработчиков
     *
     * @method handle_event
     * @for DataManager
     * @param obj {DataObj} - объект, в котором произошло событие
     * @param name {String} - имя события
     * @param attr {Object} - дополнительные свойства, передаваемые в обработчик события
     * @return {Boolean|Array.<*>}
     * @private
     */
    handle_event: {
      value: function (obj, name, attr) {
        var res = [], tmp;
        _events[name].forEach(function (method) {
          if(res !== false){
            tmp = method.call(obj, attr);
            if(tmp === false)
              res = tmp;
            else if(tmp)
              res.push(tmp);
          }
        });
        if(res.length){
          if(res.length == 1)
          // если значение единственное - возвращчем его
            return res[0];
          else{
            // если среди значений есть промисы - возвращаем all
            if(res.some(function (v) {return typeof v === "object" && v.then}))
              return Promise.all(res);
            else
              return res;
          }
        }

      }
    },

    /**
     * ### Хранилище объектов данного менеджера
     */
    by_ref: {
      value: {}
    }
  });

}

DataManager.prototype.__define({

  /**
   * ### Имя семейства объектов данного менеджера
   * Примеры: "справочников", "документов", "регистров сведений"
   * @property family_name
   * @for DataManager
   * @type String
   * @final
   */
  family_name: {
    get : function () {
      return $p.msg["meta_"+this.class_name.split(".")[0]+"_mgr"].replace($p.msg.meta_mgr+" ", "");
    }
  },

  /**
   * ### Имя таблицы объектов этого менеджера в базе alasql
   * @property table_name
   * @type String
   * @final
   */
  table_name: {
    get : function(){
      return this.class_name.replace(".", "_");
    }
  },

  /**
   * ### Найти строки
   * Возвращает массив дата-объектов, обрезанный отбором _selection_<br />
   * Eсли отбор пустой, возвращаются все строки, закешированные в менеджере.
   * Имеет смысл для объектов, у которых _cachable = "ram"_
   * @method find_rows
   * @param selection {Object} - в ключах имена полей, в значениях значения фильтра или объект {like: значение}
   * @param [callback] {Function} - в который передается текущий объект данных на каждой итерации
   * @return {Array}
   */
  find_rows: {
    value: function(selection, callback){
      return $p._find_rows.call(this, this.by_ref, selection, callback);
    }
  },

  /**
   * ### Дополнительные реквизиты
   * Массив дополнителных реквизитов (аналог подсистемы `Свойства` БСП) вычисляется через
   * ПВХ `НазначениеДополнительныхРеквизитов` или справочник `НазначениеСвойствКатегорийОбъектов`
   *
   * @property extra_fields
   * @type Array
   */
  extra_fields: {
    value : function(obj){

      // ищем предопределенный элемент, сответствующий классу данных
      var destinations = $p.cat.destinations || $p.cch.destinations,
        pn = _md.class_name_to_1c(this.class_name).replace(".", "_"),
        res = [];

      if(destinations){
        destinations.find_rows({predefined_name: pn}, function (destination) {
          var ts = destination.extra_fields || destination.ДополнительныеРеквизиты;
          if(ts){
            ts.each(function (row) {
              if(!row._deleted && !row.ПометкаУдаления)
                res.push(row.property || row.Свойство);
            });
          }
          return false;
        })

      }

      return res;
    }
  },

  /**
   * ### Дополнительные свойства
   * Массив дополнителных свойств (аналог подсистемы `Свойства` БСП) вычисляется через
   * ПВХ `НазначениеДополнительныхРеквизитов` или справочник `НазначениеСвойствКатегорийОбъектов`
   *
   * @property extra_properties
   * @type Array
   */
  extra_properties: {
    value : function(obj){
      return [];
    }
  },

  /**
   * ### Имя функции - конструктора объектов или строк табличных частей
   *
   * @method obj_constructor
   * @param ts_name {String}
   * @return {Function}
   */
  obj_constructor: {
    value: function (ts_name) {
      var parts = this.class_name.split("."),
        fn_name = parts[0].charAt(0).toUpperCase() + parts[0].substr(1) + parts[1].charAt(0).toUpperCase() + parts[1].substr(1);

      return ts_name ? fn_name + ts_name.charAt(0).toUpperCase() + ts_name.substr(1) + "Row" : fn_name;

    }
  }

});



/**
 * ### Выводит фрагмент списка объектов данного менеджера, ограниченный фильтром attr в grid
 *
 * @method sync_grid
 * @for DataManager
 * @param grid {dhtmlXGridObject}
 * @param attr {Object}
 */
DataManager.prototype.sync_grid = function(attr, grid){

  var mgr = this;

  function request(){

    if(attr.custom_selection){
      return attr.custom_selection(attr);
      
    }else if(mgr.cachable == "ram"){

      // запрос к alasql
      if(attr.action == "get_tree")
        return $p.wsql.promise(mgr.get_sql_struct(attr), [])
          .then($p.iface.data_to_tree);

      else if(attr.action == "get_selection")
        return $p.wsql.promise(mgr.get_sql_struct(attr), [])
          .then(function(data){
            return $p.iface.data_to_grid.call(mgr, data, attr);
          });

    }else if(mgr.cachable.indexOf("doc") == 0){

      // todo: запрос к pouchdb
      if(attr.action == "get_tree")
        return mgr.pouch_tree(attr);

      else if(attr.action == "get_selection")
        return mgr.pouch_selection(attr);

    } else {

      // запрос к серверу по сети
      if(attr.action == "get_tree")
        return mgr.rest_tree(attr);

      else if(attr.action == "get_selection")
        return mgr.rest_selection(attr);

    }
  }

  function to_grid(res){

    return new Promise(function(resolve, reject) {

      if(typeof res == "string"){

        if(res.substr(0,1) == "{")
          res = JSON.parse(res);

        // загружаем строку в грид
        if(grid && grid.parse){
          grid.xmlFileUrl = "exec";
          grid.parse(res, function(){
            resolve(res);
          }, "xml");
        }else
          resolve(res);

      }else if(grid instanceof dhtmlXTreeView && grid.loadStruct){
        grid.loadStruct(res, function(){
          resolve(res);
        });

      }else
        resolve(res);

    });

  }
  
  // TODO: переделать обработку catch()
  return request()
    .then(to_grid)
    .catch($p.record_log);

};

/**
 * ### Возвращает массив доступных значений для комбобокса
 * @method get_option_list
 * @for DataManager
 * @param val {DataObj|String} - текущее значение
 * @param [selection] {Object} - отбор, который будет наложен на список
 * @param [selection._top] {Number} - ограничивает длину возвращаемого массива
 * @return {Promise.<Array>}
 */
DataManager.prototype.get_option_list = function(val, selection){

  var t = this, l = [], input_by_string, text, sel;

  function check(v){
    if($p.utils.is_equal(v.value, val))
      v.selected = true;
    return v;
  }

  // поиск по строке
  if(selection.presentation && (input_by_string = t.metadata().input_by_string)){
    text = selection.presentation.like;
    delete selection.presentation;
    selection.or = [];
    input_by_string.forEach(function (fld) {
      sel = {};
      sel[fld] = {like: text};
      selection.or.push(sel);
    })
  }

  if(t.cachable == "ram" || (selection && selection._local)) {
    t.find_rows(selection, function (v) {
      l.push(check({text: v.presentation, value: v.ref}));
    });
    return Promise.resolve(l);

  }else if(t.cachable != "e1cib"){
    return t.pouch_find_rows(selection)
      .then(function (data) {
        data.forEach(function (v) {
          l.push(check({
            text: v.presentation,
            value: v.ref}));
        });
        return l;
      });

  }else{
    // для некешируемых выполняем запрос к серверу
    var attr = { selection: selection, top: selection._top},
      is_doc = t instanceof DocManager || t instanceof BusinessProcessManager;
    delete selection._top;

    if(is_doc)
      attr.fields = ["ref", "date", "number_doc"];

    else if(t.metadata().main_presentation_name)
      attr.fields = ["ref", "name"];
    else
      attr.fields = ["ref", "id"];

    return _rest.load_array(attr, t)
      .then(function (data) {
        data.forEach(function (v) {
          l.push(check({
            text: is_doc ? (v.number_doc + " от " + $p.moment(v.date).format($p.moment._masks.ldt)) : (v.name || v.id),
            value: v.ref}));
        });
        return l;
      });
  }
};

/**
 * Заполняет свойства в объекте source в соответствии с реквизитами табчасти
 * @param tabular {String} - имя табчасти
 * @param source {Object}
 */
DataManager.prototype.tabular_captions = function (tabular, source) {

};

/**
 * ### Возаращает строку xml для инициализации PropertyGrid
 * служебный метод, используется {{#crossLink "OHeadFields"}}{{/crossLink}}
 * @method get_property_grid_xml
 * @param oxml {Object} - объект с иерархией полей (входной параметр - правила)
 * @param o {DataObj} - объект данных, из полей и табличных частей которого будут прочитаны значения
 * @param extra_fields {Object} - объект с описанием допреквизитов
 * @param extra_fields.ts {String} - имя табчасти
 * @param extra_fields.title {String} - заголовок в oxml, под которым следует расположить допреквизиты // "Дополнительные реквизиты", "Свойства изделия", "Параметры"
 * @param extra_fields.selection {Object} - отбор, который следует приминить к табчасти допреквизитов
 * @return {String} - XML строка в терминах dhtml.PropertyGrid
 * @private
 */
DataManager.prototype.get_property_grid_xml = function(oxml, o, extra_fields){

  var t = this, i, j, mf, v, ft, txt, row_id, gd = '<rows>',

    default_oxml = function () {
      if(oxml)
        return;
      mf = t.metadata();

      if(mf.form && mf.form.obj && mf.form.obj.head){
        oxml = mf.form.obj.head;

      }else{
        oxml = {" ": []};

        if(o instanceof CatObj){
          if(mf.code_length)
            oxml[" "].push("id");
          if(mf.main_presentation_name)
            oxml[" "].push("name");
        }else if(o instanceof DocObj){
          oxml[" "].push("number_doc");
          oxml[" "].push("date");
        }

        if(!o.is_folder){
          for(i in mf.fields)
            if(i != "predefined_name" && !mf.fields[i].hide)
              oxml[" "].push(i);
        }

        if(mf.tabular_sections && mf.tabular_sections.extra_fields)
          oxml["Дополнительные реквизиты"] = [];
      }


    },

    txt_by_type = function (fv, mf) {

      if($p.utils.is_data_obj(fv))
        txt = fv.presentation;
      else
        txt = fv;

      if(mf.type.is_ref){
        ;
      } else if(mf.type.date_part) {
        txt = $p.moment(txt).format($p.moment._masks[mf.type.date_part]);

      } else if(mf.type.types[0]=="boolean") {
        txt = txt ? "1" : "0";
      }
    },

    by_type = function(fv){

      ft = _md.control_by_type(mf.type, fv);
      txt_by_type(fv, mf);

    },

    add_xml_row = function(f, tabular){
      if(tabular){
        var pref = f.property || f.param || f.Параметр || f.Свойство,
          pval = f.value != undefined ? f.value : f.Значение;
        if(pref.empty()) {
          row_id = tabular + "|" + "empty";
          ft = "ro";
          txt = "";
          mf = {synonym: "?"};

        }else{
          mf = {synonym: pref.presentation, type: pref.type};
          row_id = tabular + "|" + pref.ref;
          by_type(pval);
          if(ft == "edn")
            ft = "calck";

          if(pref.mandatory)
            ft += '" class="cell_mandatory';
        }

      }else if(typeof f === "object"){
        row_id = f.id;
        mf = extra_fields && extra_fields.metadata && extra_fields.metadata[row_id];
        if(!mf)
          mf = {synonym: f.synonym};
        else if(f.synonym)
          mf.synonym = f.synonym;

        ft = f.type;
        txt = "";
        if(f.hasOwnProperty("txt"))
          txt = f.txt;
        else if((v = o[row_id]) !== undefined)
          txt_by_type(v, mf.type ? mf : _md.get(t.class_name, row_id));

      }else if(extra_fields && extra_fields.metadata && ((mf = extra_fields.metadata[f]) !== undefined)){
        row_id = f;
        by_type(v = o[f]);

      }else if((v = o[f]) !== undefined){
        mf = _md.get(t.class_name, row_id = f);
        by_type(v);

      }else
        return;

      gd += '<row id="' + row_id + '"><cell>' + (mf.synonym || mf.name) +
        '</cell><cell type="' + ft + '">' + txt + '</cell></row>';
    };

  default_oxml();

  for(i in oxml){
    if(i!=" ")
      gd += '<row open="1"><cell>' + i + '</cell>';   // если у блока есть заголовок, формируем блок иначе добавляем поля без иерархии

    for(j in oxml[i])
      add_xml_row(oxml[i][j]);                        // поля, описанные в текущем разделе

    if(extra_fields && i == extra_fields.title && o[extra_fields.ts]){  // строки табчасти o.extra_fields
      var added = false,
        destinations_extra_fields = t.extra_fields(o),
        pnames = "property,param,Свойство,Параметр".split(","),
        //meta_extra_fields = o._metadata.tabular_sections[extra_fields.ts].fields,
        meta_extra_fields = o[extra_fields.ts]._owner._metadata.tabular_sections[o[extra_fields.ts]._name].fields,
        pname;

      // Если в объекте не найдены предопределенные свойства - добавляем
      if(pnames.some(function (name) {
        if(meta_extra_fields[name]){
          pname = name;
          return true;
        }
      })){
        o[extra_fields.ts].forEach(function (row) {
          var index = destinations_extra_fields.indexOf(row[pname]);
          if(index != -1)
            destinations_extra_fields.splice(index, 1);
        });
        destinations_extra_fields.forEach(function (property) {
          var row = o[extra_fields.ts].add();
          row[pname] = property;
        });
      };

      // Добавляем строки в oxml с учетом отбора, который мог быть задан в extra_fields.selection
      o[extra_fields.ts].find_rows(extra_fields.selection, function (row) {
        add_xml_row(row, extra_fields.ts);

      });
      //if(!added)
      //  add_xml_row({param: $p.cch.properties.get("", false)}, "params"); // fake-строка, если в табчасти нет допреквизитов

    }

    if(i!=" ") gd += '</row>';                          // если блок был открыт - закрываем
  }
  gd += '</rows>';
  return gd;
};



/**
 * Печатает объект
 * @method print
 * @param ref {DataObj|String} - guid ссылки на объект
 * @param model {String|DataObj.cst.formulas} - идентификатор команды печати
 * @param [wnd] {dhtmlXWindows} - окно, из которого вызываем печать
 */
DataManager.prototype.print = function(ref, model, wnd){

  function tune_wnd_print(wnd_print){
    if(wnd && wnd.progressOff)
      wnd.progressOff();
    if(wnd_print)
      wnd_print.focus();
  }

  if(wnd && wnd.progressOn)
    wnd.progressOn();

  setTimeout(tune_wnd_print, 3000);

  // если _printing_plates содержит ссылку на обрабочтик печати, используем его
  if(this._printing_plates[model] instanceof DataObj)
    model = this._printing_plates[model];  
  
  // если существует локальный обработчик, используем его
  if(model instanceof DataObj && model.execute){

    if(ref instanceof DataObj)
      return model.execute(ref)
        .then(tune_wnd_print);
    else
      return this.get(ref, true, true)
        .then(model.execute.bind(model))
        .then(tune_wnd_print);

  }else{
    
    // иначе - печатаем средствами 1С или иного сервера
    var rattr = {};
    $p.ajax.default_attr(rattr, $p.job_prm.irest_url());
    rattr.url += this.rest_name + "(guid'" + $p.utils.fix_guid(ref) + "')" +
      "/Print(model=" + model + ", browser_uid=" + $p.wsql.get_user_param("browser_uid") +")";

    return $p.ajax.get_and_show_blob(rattr.url, rattr, "get")
      .then(tune_wnd_print);
  }

};

/**
 * Возвращает промис со структурой печатных форм объекта
 * @return {Promise.<Object>}
 */
DataManager.prototype.printing_plates = function(){
  var rattr = {}, t = this;

  if(!t._printing_plates){
    if(t.metadata().printing_plates)
      t._printing_plates = t.metadata().printing_plates;

    else if(t.metadata().cachable == "ram" || (t.metadata().cachable && t.metadata().cachable.indexOf("doc") == 0)){
      t._printing_plates = {};
    }
  }

  if(!t._printing_plates && $p.ajax.authorized){
    $p.ajax.default_attr(rattr, $p.job_prm.irest_url());
    rattr.url += t.rest_name + "/Print()";
    return $p.ajax.get_ex(rattr.url, rattr)
      .then(function (req) {
        t._printing_plates = JSON.parse(req.response);
        return t._printing_plates;
      })
      .catch(function () {
      })
      .then(function (pp) {
        return pp || (t._printing_plates = {});
      });
  }

  return Promise.resolve(t._printing_plates);

};



/**
 * ### Aбстрактный менеджер ссылочных данных
 * От него унаследованы менеджеры документов, справочников, планов видов характеристик и планов счетов
 *
 * @class RefDataManager
 * @extends DataManager
 * @constructor
 * @param class_name {string} - имя типа менеджера объекта
 */
function RefDataManager(class_name) {
  
  RefDataManager.superclass.constructor.call(this, class_name);
  
}
RefDataManager._extend(DataManager);

RefDataManager.prototype.__define({

  /**
   * Помещает элемент ссылочных данных в локальную коллекцию
   * @method push
   * @param o {DataObj}
   * @param [new_ref] {String} - новое значение ссылки объекта
   */
  push: {
    value: function(o, new_ref){
      if(new_ref && (new_ref != o.ref)){
        delete this.by_ref[o.ref];
        this.by_ref[new_ref] = o;
      }else
        this.by_ref[o.ref] = o;
    }
  },

  /**
   * Выполняет перебор элементов локальной коллекции
   * @method each
   * @param fn {Function} - функция, вызываемая для каждого элемента локальной коллекции
   */
  each: {
    value:   function(fn){
      for(var i in this.by_ref){
        if(!i || i == $p.utils.blank.guid)
          continue;
        if(fn.call(this, this.by_ref[i]) == true)
          break;
      }
    }
  },

  /**
   * Синоним для each()
   */
  forEach: {
    value: function (fn) {
      return this.each.call(this, fn);
    }
  },

  /**
   * Возвращает объект по ссылке (читает из датабазы или локального кеша) если идентификатор пуст, создаёт новый объект
   * @method get
   * @param ref {String|Object} - ссылочный идентификатор
   * @param [force_promise] {Boolean} - Если истина, возвращает промис, даже для локальных объектов. Если ложь, ищет только в локальном кеше
   * @param [do_not_create] {Boolean} - Не создавать новый. Например, когда поиск элемента выполняется из конструктора
   * @return {DataObj|Promise.<DataObj>}
   */
  get: {
    value: function(ref, force_promise, do_not_create){

      var o = this.by_ref[ref] || this.by_ref[(ref = $p.utils.fix_guid(ref))];

      if(!o){
        if(do_not_create && !force_promise)
          return;
        else
          o = new $p[this.obj_constructor()](ref, this, true);
      }

      if(force_promise === false)
        return o;

      else if(force_promise === undefined && ref === $p.utils.blank.guid)
        return o;

      if(o.is_new()){
        return o.load();  // читаем из 1С или иного сервера

      }else if(force_promise)
        return Promise.resolve(o);

      else
        return o;
    }
  },

  /**
   * ### Создаёт новый объект типа объектов текущего менеджера
   * Для кешируемых объектов, все действия происходят на клиенте<br />
   * Для некешируемых, выполняется обращение к серверу для получения guid и значений реквизитов по умолчанию
   *
   * @method create
   * @param [attr] {Object} - значениями полей этого объекта будет заполнен создаваемый объект
   * @param [fill_default] {Boolean} - признак, надо ли заполнять (инициализировать) создаваемый объект значениями полей по умолчанию
   * @return {Promise.<*>}
   */
  create: {
    value: function(attr, fill_default){

      if(!attr || typeof attr != "object")
        attr = {};
      if(!attr.ref || !$p.utils.is_guid(attr.ref) || $p.utils.is_empty_guid(attr.ref))
        attr.ref = $p.utils.generate_guid();

      var o = this.by_ref[attr.ref];
      if(!o){

        o = new $p[this.obj_constructor()](attr, this);

        if(!fill_default && attr.ref && attr.presentation && Object.keys(attr).length == 2){
          // заглушка ссылки объекта

        }else{

          if(o instanceof DocObj && o.date == $p.utils.blank.date)
            o.date = new Date();

          // Триггер после создания
          var after_create_res = this.handle_event(o, "after_create");

          if(after_create_res === false)
            return Promise.resolve(o);

          else if(typeof after_create_res === "object" && after_create_res.then)
            return after_create_res;

          // выполняем обработчик после создания объекта и стандартные действия, если их не запретил обработчик
          if(this.cachable == "e1cib" && fill_default){
            var rattr = {};
            $p.ajax.default_attr(rattr, $p.job_prm.irest_url());
            rattr.url += this.rest_name + "/Create()";
            return $p.ajax.get_ex(rattr.url, rattr)
              .then(function (req) {
                return o._mixin(JSON.parse(req.response), undefined, ["ref"]);
              });
          }

        }
      }

      return Promise.resolve(o);
    }
  },

  /**
   * Удаляет объект из alasql и локального кеша
   * @method unload_obj
   * @param ref
   */
  unload_obj: {
    value: function(ref) {
      delete this.by_ref[ref];
      this.alatable.some(function (o, i, a) {
        if(o.ref == ref){
          a.splice(i, 1);
          return true;
        }
      });
    }
  },

  /**
   * Находит первый элемент, в любом поле которого есть искомое значение
   * @method find
   * @param val {*} - значение для поиска
   * @param columns {String|Array} - колонки, в которых искать
   * @return {DataObj}
   */
  find: {
    value: function(val, columns){
      return $p._find(this.by_ref, val, columns);
    }
  },

  /**
   * сохраняет массив объектов в менеджере
   * @method load_array
   * @param aattr {Array} - массив объектов для трансформации в объекты ссылочного типа
   * @param forse {Boolean} - перезаполнять объект
   * @async
   */
  load_array: {
    value: function(aattr, forse){

      var ref, obj, res = [];

      for(var i=0; i<aattr.length; i++){

        ref = $p.utils.fix_guid(aattr[i]);
        obj = this.by_ref[ref];

        if(!obj){
          obj = new $p[this.obj_constructor()](aattr[i], this);
          if(forse)
            obj._set_loaded();

        }else if(obj.is_new() || forse){
          obj._mixin(aattr[i]);
          obj._set_loaded();
        }

        res.push(obj);
      }
      return res;
    }
  },

  /**
   * Находит перую папку в пределах подчинения владельцу
   * @method first_folder
   * @param owner {DataObj|String}
   * @return {DataObj} - ссылка найденной папки или пустая ссылка
   */
  first_folder: {
    value: function(owner){
      for(var i in this.by_ref){
        var o = this.by_ref[i];
        if(o.is_folder && (!owner || $p.utils.is_equal(owner, o.owner)))
          return o;
      }
      return this.get();
    }
  },
  
  /**
   * Возаращает массив запросов для создания таблиц объекта и его табличных частей
   * @method get_sql_struct
   * @param attr {Object}
   * @param attr.action {String} - [create_table, drop, insert, update, replace, select, delete]
   * @return {Object|String}
   */
  get_sql_struct: {
    value: function(attr){
      var t = this,
        cmd = t.metadata(),
        res = {}, f, f0, trunc_index = 0,
        action = attr && attr.action ? attr.action : "create_table";


      function sql_selection(){

        var ignore_parent = !attr.parent,
          parent = attr.parent || $p.utils.blank.guid,
          owner,
          initial_value = attr.initial_value || $p.utils.blank.guid,
          filter = attr.filter || "",
          set_parent = $p.utils.blank.guid;

        function list_flds(){
          var flds = [], s = "_t_.ref, _t_.`_deleted`";

          if(cmd.form && cmd.form.selection){
            cmd.form.selection.fields.forEach(function (fld) {
              flds.push(fld);
            });

          }else if(t instanceof DocManager){
            flds.push("posted");
            flds.push("date");
            flds.push("number_doc");

          }else{

            if(cmd["hierarchical"] && cmd["group_hierarchy"])
              flds.push("is_folder");
            else
              flds.push("0 as is_folder");

            if(t instanceof ChartOfAccountManager){
              flds.push("id");
              flds.push("name as presentation");

            }else if(cmd["main_presentation_name"])
              flds.push("name as presentation");

            else{
              if(cmd["code_length"])
                flds.push("id as presentation");
              else
                flds.push("'...' as presentation");
            }

            if(cmd["has_owners"])
              flds.push("owner");

            if(cmd["code_length"])
              flds.push("id");

          }

          flds.forEach(function(fld){
            if(fld.indexOf(" as ") != -1)
              s += ", " + fld;
            else
              s += _md.sql_mask(fld, true);
          });
          return s;

        }

        function join_flds(){

          var s = "", parts;

          if(cmd.form && cmd.form.selection){
            for(var i in cmd.form.selection.fields){
              if(cmd.form.selection.fields[i].indexOf(" as ") == -1 || cmd.form.selection.fields[i].indexOf("_t_.") != -1)
                continue;
              parts = cmd.form.selection.fields[i].split(" as ");
              parts[0] = parts[0].split(".");
              if(parts[0].length > 1){
                if(s)
                  s+= "\n";
                s+= "left outer join " + parts[0][0] + " on " + parts[0][0] + ".ref = _t_." + parts[1];
              }
            }
          }
          return s;
        }

        function where_flds(){

          var s;

          if(t instanceof ChartOfAccountManager){
            s = " WHERE (" + (filter ? 0 : 1);

          }else if(cmd["hierarchical"]){
            if(cmd["has_owners"])
              s = " WHERE (" + (ignore_parent || filter ? 1 : 0) + " OR _t_.parent = '" + parent + "') AND (" +
                (owner == $p.utils.blank.guid ? 1 : 0) + " OR _t_.owner = '" + owner + "') AND (" + (filter ? 0 : 1);
            else
              s = " WHERE (" + (ignore_parent || filter ? 1 : 0) + " OR _t_.parent = '" + parent + "') AND (" + (filter ? 0 : 1);

          }else{
            if(cmd["has_owners"])
              s = " WHERE (" + (owner == $p.utils.blank.guid ? 1 : 0) + " OR _t_.owner = '" + owner + "') AND (" + (filter ? 0 : 1);
            else
              s = " WHERE (" + (filter ? 0 : 1);
          }

          if(t.sql_selection_where_flds){
            s += t.sql_selection_where_flds(filter);

          }else if(t instanceof DocManager)
            s += " OR _t_.number_doc LIKE '" + filter + "'";

          else{
            if(cmd["main_presentation_name"] || t instanceof ChartOfAccountManager)
              s += " OR _t_.name LIKE '" + filter + "'";

            if(cmd["code_length"])
              s += " OR _t_.id LIKE '" + filter + "'";
          }

          s += ") AND (_t_.ref != '" + $p.utils.blank.guid + "')";


          // допфильтры форм и связей параметров выбора
          if(attr.selection){
            if(typeof attr.selection == "function"){

            }else
              attr.selection.forEach(function(sel){
                for(var key in sel){

                  if(typeof sel[key] == "function"){
                    s += "\n AND " + sel[key](t, key) + " ";

                  }else if(cmd.fields.hasOwnProperty(key)){
                    if(sel[key] === true)
                      s += "\n AND _t_." + key + " ";

                    else if(sel[key] === false)
                      s += "\n AND (not _t_." + key + ") ";

                    else if(typeof sel[key] == "object"){

                      if($p.utils.is_data_obj(sel[key]))
                        s += "\n AND (_t_." + key + " = '" + sel[key] + "') ";

                      else{
                        var keys = Object.keys(sel[key]),
                          val = sel[key][keys[0]],
                          mf = cmd.fields[key],
                          vmgr;

                        if(mf && mf.type.is_ref){
                          vmgr = _md.value_mgr({}, key, mf.type, true, val);
                        }

                        if(keys[0] == "not")
                          s += "\n AND (not _t_." + key + " = '" + val + "') ";

                        else
                          s += "\n AND (_t_." + key + " = '" + val + "') ";
                      }

                    }else if(typeof sel[key] == "string")
                      s += "\n AND (_t_." + key + " = '" + sel[key] + "') ";

                    else
                      s += "\n AND (_t_." + key + " = " + sel[key] + ") ";

                  } else if(key=="is_folder" && cmd.hierarchical && cmd.group_hierarchy){
                    //if(sel[key])
                    //  s += "\n AND _t_." + key + " ";
                    //else
                    //  s += "\n AND (not _t_." + key + ") ";
                  }
                }
              });
          }

          return s;
        }

        function order_flds(){

          if(t instanceof ChartOfAccountManager){
            return "ORDER BY id";

          }else if(cmd["hierarchical"]){
            if(cmd["group_hierarchy"])
              return "ORDER BY _t_.is_folder desc, is_initial_value, presentation";
            else
              return "ORDER BY _t_.parent desc, is_initial_value, presentation";
          }else
            return "ORDER BY is_initial_value, presentation";
        }

        function selection_prms(){

          // т.к. в процессе установки может потребоваться получение объектов, код асинхронный
          function on_parent(o){

            // ссылка родителя для иерархических справочников
            if(o){
              set_parent = (attr.set_parent = o.parent.ref);
              parent = set_parent;
              ignore_parent = false;
            }else if(!filter && !ignore_parent){
              ;

            }

            // строка фильтра
            if(filter && filter.indexOf("%") == -1)
              filter = "%" + filter + "%";

          }

          // установим владельца
          if(cmd["has_owners"]){
            owner = attr.owner;
            if(attr.selection && typeof attr.selection != "function"){
              attr.selection.forEach(function(sel){
                if(sel.owner){
                  owner = typeof sel.owner == "object" ?  sel.owner.valueOf() : sel.owner;
                  delete sel.owner;
                }
              });
            }
            if(!owner)
              owner = $p.utils.blank.guid;
          }

          // ссылка родителя во взаимосвязи с начальным значением выбора
          if(initial_value !=  $p.utils.blank.guid && ignore_parent){
            if(cmd["hierarchical"]){
              on_parent(t.get(initial_value, false))
            }else
              on_parent();
          }else
            on_parent();

        }

        selection_prms();

        var sql;
        if(t.sql_selection_list_flds)
          sql = t.sql_selection_list_flds(initial_value);
        else
          sql = ("SELECT %2, case when _t_.ref = '" + initial_value +
          "' then 0 else 1 end as is_initial_value FROM `" + t.table_name + "` AS _t_ %j %3 %4 LIMIT 300")
            .replace("%2", list_flds())
            .replace("%j", join_flds())
          ;

        return sql.replace("%3", where_flds()).replace("%4", order_flds());

      }

      function sql_create(){

        var sql = "CREATE TABLE IF NOT EXISTS ";

        if(attr && attr.postgres){
          sql += t.table_name+" (ref uuid PRIMARY KEY NOT NULL, _deleted boolean";

          if(t instanceof DocManager)
            sql += ", posted boolean, date timestamp with time zone, number_doc character(11)";
          else{
            if(cmd.code_length)
              sql += ", id character("+cmd.code_length+")";
            sql += ", name character varying(50), is_folder boolean";
          }

          for(f in cmd.fields){
            if(f.length > 30){
              if(cmd.fields[f].short_name)
                f0 = cmd.fields[f].short_name;
              else{
                trunc_index++;
                f0 = f[0] + trunc_index + f.substr(f.length-27);
              }
            }else
              f0 = f;
            sql += ", " + f0 + _md.sql_type(t, f, cmd.fields[f].type, true) + _md.sql_composite(cmd.fields, f, f0, true);
          }

          for(f in cmd["tabular_sections"])
            sql += ", " + "ts_" + f + " JSON";

        }else{
          sql += "`"+t.table_name+"` (ref CHAR PRIMARY KEY NOT NULL, `_deleted` BOOLEAN";

          if(t instanceof DocManager)
            sql += ", posted boolean, date Date, number_doc CHAR";
          else
            sql += ", id CHAR, name CHAR, is_folder BOOLEAN";

          for(f in cmd.fields)
            sql += _md.sql_mask(f) + _md.sql_type(t, f, cmd.fields[f].type)+ _md.sql_composite(cmd.fields, f);

          for(f in cmd["tabular_sections"])
            sql += ", " + "`ts_" + f + "` JSON";
        }

        sql += ")";

        return sql;
      }

      function sql_update(){
        // "INSERT OR REPLACE INTO user_params (prm_name, prm_value) VALUES (?, ?);
        var fields = ["ref", "_deleted"],
          sql = "INSERT INTO `"+t.table_name+"` (ref, `_deleted`",
          values = "(?";

        if(t.class_name.substr(0, 3)=="cat"){
          sql += ", id, name, is_folder";
          fields.push("id");
          fields.push("name");
          fields.push("is_folder");

        }else if(t.class_name.substr(0, 3)=="doc"){
          sql += ", posted, date, number_doc";
          fields.push("posted");
          fields.push("date");
          fields.push("number_doc");

        }
        for(f in cmd.fields){
          sql += _md.sql_mask(f);
          fields.push(f);
        }
        for(f in cmd["tabular_sections"]){
          sql += ", `ts_" + f + "`";
          fields.push("ts_" + f);
        }
        sql += ") VALUES ";
        for(f = 1; f<fields.length; f++){
          values += ", ?";
        }
        values += ")";
        sql += values;

        return {sql: sql, fields: fields, values: values};
      }


      if(action == "create_table")
        res = sql_create();

      else if(["insert", "update", "replace"].indexOf(action) != -1)
        res[t.table_name] = sql_update();

      else if(action == "select")
        res = "SELECT * FROM `"+t.table_name+"` WHERE ref = ?";

      else if(action == "select_all")
        res = "SELECT * FROM `"+t.table_name+"`";

      else if(action == "delete")
        res = "DELETE FROM `"+t.table_name+"` WHERE ref = ?";

      else if(action == "drop")
        res = "DROP TABLE IF EXISTS `"+t.table_name+"`";

      else if(action == "get_tree"){
        if(!attr.filter || attr.filter.is_folder)
          res = "SELECT ref, parent, name as presentation FROM `" + t.table_name + "` WHERE is_folder order by parent, name";
        else
          res = "SELECT ref, parent, name as presentation FROM `" + t.table_name + "` order by parent, name";
      }

      else if(action == "get_selection")
        res = sql_selection();

      return res;
    }
  },

  /**
   * ШапкаТаблицыПоИмениКласса
   */
  caption_flds: {
    value: function(attr){

      var _meta = attr.metadata || this.metadata(),
        str_def = "<column id=\"%1\" width=\"%2\" type=\"%3\" align=\"%4\" sort=\"%5\">%6</column>",
        acols = [],  s = "";

      if(_meta.form && _meta.form.selection){
        acols = _meta.form.selection.cols;

      }else if(this instanceof DocManager){
        acols.push(new Col_struct("date", "160", "ro", "left", "server", "Дата"));
        acols.push(new Col_struct("number_doc", "140", "ro", "left", "server", "Номер"));

        if(_meta.fields.note)
          acols.push(new Col_struct("note", "*", "ro", "left", "server", _meta.fields.note.synonym));

        if(_meta.fields.responsible)
          acols.push(new Col_struct("responsible", "*", "ro", "left", "server", _meta.fields.responsible.synonym));


      }else if(this instanceof ChartOfAccountManager){
        acols.push(new Col_struct("id", "140", "ro", "left", "server", "Код"));
        acols.push(new Col_struct("presentation", "*", "ro", "left", "server", "Наименование"));

      }else{

        acols.push(new Col_struct("presentation", "*", "ro", "left", "server", "Наименование"));
        //if(_meta.has_owners){
        //  acols.push(new Col_struct("owner", "*", "ro", "left", "server", _meta.fields.owner.synonym));
        //}

      }

      if(attr.get_header && acols.length){
        s = "<head>";
        for(var col in acols){
          s += str_def.replace("%1", acols[col].id).replace("%2", acols[col].width).replace("%3", acols[col].type)
            .replace("%4", acols[col].align).replace("%5", acols[col].sort).replace("%6", acols[col].caption);
        }
        s += "</head>";
      }

      return {head: s, acols: acols};
    }
  },

  /**
   * Догружает с сервера объекты, которых нет в локальном кеше
   * @method load_cached_server_array
   * @param list {Array} - массив строк ссылок или объектов со свойством ref
   * @param alt_rest_name {String} - альтернативный rest_name для загрузки с сервера
   * @return {Promise}
   */
  load_cached_server_array: {
    value: function (list, alt_rest_name) {

      var query = [], obj,
        t = this,
        mgr = alt_rest_name ? {class_name: t.class_name, rest_name: alt_rest_name} : t,
        check_loaded = !alt_rest_name;

      list.forEach(function (o) {
        obj = t.get(o.ref || o, false, true);
        if(!obj || (check_loaded && obj.is_new()))
          query.push(o.ref || o);
      });
      if(query.length){

        var attr = {
          url: "",
          selection: {ref: {in: query}}
        };
        if(check_loaded)
          attr.fields = ["ref"];

        $p.rest.build_select(attr, mgr);
        //if(dhx4.isIE)
        //  attr.url = encodeURI(attr.url);

        return $p.ajax.get_ex(attr.url, attr)
          .then(function (req) {
            var data = JSON.parse(req.response);

            if(check_loaded)
              data = data.value;
            else{
              data = data.data;
              for(var i in data){
                if(!data[i].ref && data[i].id)
                  data[i].ref = data[i].id;
                if(data[i].Код){
                  data[i].id = data[i].Код;
                  delete data[i].Код;
                }
                data[i]._not_set_loaded = true;
              }
            }

            t.load_array(data);
            return(list);
          });

      }else
        return Promise.resolve(list);
    }
  },

  /**
   * Возаращает предопределенный элемент по имени предопределенных данных
   * @method predefined
   * @param name {String} - имя предопределенного
   * @return {DataObj}
   */
  predefined: {
    value: function(name){

      if(!this._predefined)
        this._predefined = {};

      if(!this._predefined[name]){

        this._predefined[name] = this.get();

        this.find_rows({predefined_name: name}, function (el) {
          this._predefined[name] = el;
          return false;
        });
      }

      return this._predefined[name];
    }
  }

});



/**
 * ### Абстрактный менеджер обработок
 * Экземпляры объектов этого класса создаются при выполнении конструктора {{#crossLink "Meta"}}{{/crossLink}}
 * в соответствии с описанием метаданных конфигурации и помещаются в коллекцию {{#crossLink "DataProcessors"}}{{/crossLink}}
 *
 * @class DataProcessorsManager
 * @extends DataManager
 * @param class_name {string} - имя типа менеджера объекта
 * @constructor
 */
function DataProcessorsManager(class_name){

  DataProcessorsManager.superclass.constructor.call(this, class_name);

}
DataProcessorsManager._extend(DataManager);

DataProcessorsManager.prototype.__define({

  /**
   * Создаёт экземпляр объекта обработки
   * @method
   * @return {DataProcessorObj}
   */
  create: {
    value: function(){
      return new $p[this.obj_constructor()]({}, this);
    }
  },

  /**
   * fake-метод, не имеет смысла для обработок, т.к. они не кешируются в alasql. Добавлен, чтобы не ругалась форма обхекта при закрытии
   * @method unload_obj
   * @param ref
   */
  unload_obj: {
    value: function() {  }
  }
});



/**
 * ### Абстрактный менеджер перечисления
 * Экземпляры объектов этого класса создаются при выполнении конструктора {{#crossLink "Meta"}}{{/crossLink}}
 * в соответствии с описанием метаданных конфигурации и помещаются в коллекцию {{#crossLink "Enumerations"}}{{/crossLink}}
 *
 * @class EnumManager
 * @extends RefDataManager
 * @param class_name {string} - имя типа менеджера объекта. например, "enm.open_types"
 * @constructor
 */
function EnumManager(class_name) {

  EnumManager.superclass.constructor.call(this, class_name);

  var a = $p.md.get(class_name);
  for(var i in a)
    new EnumObj(a[i], this);

}
EnumManager._extend(RefDataManager);

EnumManager.prototype.__define({

  get: {
    value: function(ref){

      if(ref instanceof EnumObj)
        return ref;

      else if(!ref || ref == $p.utils.blank.guid)
        ref = "_";

      var o = this[ref];
      if(!o)
        o = new EnumObj({name: ref}, this);

      return o;
    }
  },

  push: {
    value: function(o, new_ref){
      this.__define(new_ref, {
        value : o
      });
    }
  },

  each: {
    value: function (fn) {
      this.alatable.forEach(function (v) {
        if(v.ref && v.ref != "_" && v.ref != $p.utils.blank.guid)
          fn.call(this[v.ref]);
      }.bind(this));
    }
  }
});

/**
 * Bозаращает массив запросов для создания таблиц объекта и его табличных частей
 * @param attr {Object}
 * @param attr.action {String} - [create_table, drop, insert, update, replace, select, delete]
 * @return {Object|String}
 */
EnumManager.prototype.get_sql_struct = function(attr){

  var res = "CREATE TABLE IF NOT EXISTS ",
    action = attr && attr.action ? attr.action : "create_table";

  if(attr && attr.postgres){
    if(action == "create_table")
      res += this.table_name+
        " (ref character varying(255) PRIMARY KEY NOT NULL, sequence INT, synonym character varying(255))";
    else if(["insert", "update", "replace"].indexOf(action) != -1){
      res = {};
      res[this.table_name] = {
        sql: "INSERT INTO "+this.table_name+" (ref, sequence, synonym) VALUES ($1, $2, $3)",
        fields: ["ref", "sequence", "synonym"],
        values: "($1, $2, $3)"
      };

    }else if(action == "delete")
      res = "DELETE FROM "+this.table_name+" WHERE ref = $1";

  }else {
    if(action == "create_table")
      res += "`"+this.table_name+
        "` (ref CHAR PRIMARY KEY NOT NULL, sequence INT, synonym CHAR)";

    else if(["insert", "update", "replace"].indexOf(action) != -1){
      res = {};
      res[this.table_name] = {
        sql: "INSERT INTO `"+this.table_name+"` (ref, sequence, synonym) VALUES (?, ?, ?)",
        fields: ["ref", "sequence", "synonym"],
        values: "(?, ?, ?)"
      };

    }else if(action == "delete")
      res = "DELETE FROM `"+this.table_name+"` WHERE ref = ?";
  }



  return res;

};

/**
 * Возвращает массив доступных значений для комбобокса
 * @method get_option_list
 * @param val {DataObj|String}
 * @param [selection] {Object}
 * @param [selection._top] {Number}
 * @return {Promise.<Array>}
 */
EnumManager.prototype.get_option_list = function(val, selection){
  var l = [], synonym = "", sref;

  function check(v){
    if($p.utils.is_equal(v.value, val))
      v.selected = true;
    return v;
  }

  if(selection){
    for(var i in selection){
      if(i.substr(0,1)=="_")
        continue;
      else if(i == "ref"){
        sref = selection[i].hasOwnProperty("in") ? selection[i].in : selection[i];
      }
      else
        synonym = selection[i];
    }
  }

  if(typeof synonym == "object"){
    if(synonym.like)
      synonym = synonym.like;
    else
      synonym = "";
  }
  synonym = synonym.toLowerCase();

  this.alatable.forEach(function (v) {
    if(synonym){
      if(!v.synonym || v.synonym.toLowerCase().indexOf(synonym) == -1)
        return;
    }
    if(sref){
      if(Array.isArray(sref)){
        if(!sref.some(function (sv) {
            return sv.name == v.ref || sv.ref == v.ref || sv == v.ref;
          }))
          return;
      }else{
        if(sref.name != v.ref && sref.ref != v.ref && sref != v.ref)
          return;
      }
    }
    l.push(check({text: v.synonym || "", value: v.ref}));
  });
  return Promise.resolve(l);
};


/**
 * ### Абстрактный менеджер регистра (накопления, сведений и бухгалтерии)
 *
 * @class RegisterManager
 * @extends DataManager
 * @constructor
 * @param class_name {string} - имя типа менеджера объекта. например, "ireg.prices"
 */
function RegisterManager(class_name){

  RegisterManager.superclass.constructor.call(this, class_name);

  /**
   * Помещает элемент ссылочных данных в локальную коллекцию
   * @method push
   * @param o {RegisterRow}
   * @param [new_ref] {String} - новое значение ссылки объекта
   */
  this.push = function(o, new_ref){
    if(new_ref && (new_ref != o.ref)){
      delete this.by_ref[o.ref];
      this.by_ref[new_ref] = o;
    }else
      this.by_ref[o.ref] = o;
  };

  /**
   * Возвращает массив записей c заданным отбором либо запись по ключу
   * @method get
   * @for InfoRegManager
   * @param attr {Object} - объект {key:value...}
   * @param force_promise {Boolesn} - возаращять промис, а не массив
   * @param return_row {Boolesn} - возвращать запись, а не массив
   * @return {*}
   */
  this.get = function(attr, force_promise, return_row){

    if(!attr)
      attr = {};
    else if(typeof attr == "string")
      attr = {ref: attr};
    
    if(attr.ref && return_row)
      return force_promise ? Promise.resolve(this.by_ref[attr.ref]) : this.by_ref[attr.ref];
    
    attr.action = "select";

    var arr = $p.wsql.alasql(this.get_sql_struct(attr), attr._values),
      res;

    delete attr.action;
    delete attr._values;

    if(arr.length){
      if(return_row)
        res = this.by_ref[this.get_ref(arr[0])];
      else{
        res = [];
        for(var i in arr)
          res.push(this.by_ref[this.get_ref(arr[i])]);
      }
    }
    
    return force_promise ? Promise.resolve(res) : res;
  };

  /**
   * Удаляет объект из alasql и локального кеша
   * @method unload_obj
   * @param ref
   */
  this.unload_obj = function(ref) {
    delete this.by_ref[ref];
    this.alatable.some(function (o, i, a) {
      if(o.ref == ref){
        a.splice(i, 1);
        return true;
      }
    });
  };

  /**
   * сохраняет массив объектов в менеджере
   * @method load_array
   * @param aattr {array} - массив объектов для трансформации в объекты ссылочного типа
   * @param forse {Boolean} - перезаполнять объект
   * @async
   */
  this.load_array = function(aattr, forse){

    var ref, obj, res = [];

    for(var i=0; i<aattr.length; i++){

      ref = this.get_ref(aattr[i]);
      obj = this.by_ref[ref];

      if(!obj && !aattr[i]._deleted){
        obj = new $p[this.obj_constructor()](aattr[i], this);
        if(forse)
          obj._set_loaded();

      }else if(obj && aattr[i]._deleted){
        obj.unload();
        continue;

      }else if(obj.is_new() || forse){
        obj._mixin(aattr[i]);
        obj._set_loaded();
      }

      res.push(obj);
    }
    return res;
  };

}
RegisterManager._extend(DataManager);

RegisterManager.prototype.__define({

  /**
   * Возаращает запросов для создания таблиц или извлечения данных
   * @method get_sql_struct
   * @for RegisterManager
   * @param attr {Object}
   * @param attr.action {String} - [create_table, drop, insert, update, replace, select, delete]
   * @return {Object|String}
   */
  get_sql_struct: {
    value: function(attr) {
      var t = this,
        cmd = t.metadata(),
        res = {}, f,
        action = attr && attr.action ? attr.action : "create_table";

      function sql_selection(){

        var filter = attr.filter || "";

        function list_flds(){
          var flds = [], s = "_t_.ref";

          if(cmd.form && cmd.form.selection){
            cmd.form.selection.fields.forEach(function (fld) {
              flds.push(fld);
            });

          }else{

            for(var f in cmd["dimensions"]){
              flds.push(f);
            }
          }

          flds.forEach(function(fld){
            if(fld.indexOf(" as ") != -1)
              s += ", " + fld;
            else
              s += _md.sql_mask(fld, true);
          });
          return s;

        }

        function join_flds(){

          var s = "", parts;

          if(cmd.form && cmd.form.selection){
            for(var i in cmd.form.selection.fields){
              if(cmd.form.selection.fields[i].indexOf(" as ") == -1 || cmd.form.selection.fields[i].indexOf("_t_.") != -1)
                continue;
              parts = cmd.form.selection.fields[i].split(" as ");
              parts[0] = parts[0].split(".");
              if(parts[0].length > 1){
                if(s)
                  s+= "\n";
                s+= "left outer join " + parts[0][0] + " on " + parts[0][0] + ".ref = _t_." + parts[1];
              }
            }
          }
          return s;
        }

        function where_flds(){

          var s = " WHERE (" + (filter ? 0 : 1);

          if(t.sql_selection_where_flds){
            s += t.sql_selection_where_flds(filter);

          }

          s += ")";


          // допфильтры форм и связей параметров выбора
          if(attr.selection){
            if(typeof attr.selection == "function"){

            }else
              attr.selection.forEach(function(sel){
                for(var key in sel){

                  if(typeof sel[key] == "function"){
                    s += "\n AND " + sel[key](t, key) + " ";

                  }else if(cmd.fields.hasOwnProperty(key)){
                    if(sel[key] === true)
                      s += "\n AND _t_." + key + " ";

                    else if(sel[key] === false)
                      s += "\n AND (not _t_." + key + ") ";

                    else if(typeof sel[key] == "object"){

                      if($p.utils.is_data_obj(sel[key]))
                        s += "\n AND (_t_." + key + " = '" + sel[key] + "') ";

                      else{
                        var keys = Object.keys(sel[key]),
                          val = sel[key][keys[0]],
                          mf = cmd.fields[key],
                          vmgr;

                        if(mf && mf.type.is_ref){
                          vmgr = _md.value_mgr({}, key, mf.type, true, val);
                        }

                        if(keys[0] == "not")
                          s += "\n AND (not _t_." + key + " = '" + val + "') ";

                        else
                          s += "\n AND (_t_." + key + " = '" + val + "') ";
                      }

                    }else if(typeof sel[key] == "string")
                      s += "\n AND (_t_." + key + " = '" + sel[key] + "') ";

                    else
                      s += "\n AND (_t_." + key + " = " + sel[key] + ") ";

                  } else if(key=="is_folder" && cmd.hierarchical && cmd.group_hierarchy){
                    //if(sel[key])
                    //  s += "\n AND _t_." + key + " ";
                    //else
                    //  s += "\n AND (not _t_." + key + ") ";
                  }
                }
              });
          }

          return s;
        }

        function order_flds(){

          return "";
        }

        // строка фильтра
        if(filter && filter.indexOf("%") == -1)
          filter = "%" + filter + "%";

        var sql;
        if(t.sql_selection_list_flds)
          sql = t.sql_selection_list_flds();
        else
          sql = ("SELECT %2 FROM `" + t.table_name + "` AS _t_ %j %3 %4 LIMIT 300")
            .replace("%2", list_flds())
            .replace("%j", join_flds())
          ;

        return sql.replace("%3", where_flds()).replace("%4", order_flds());

      }

      function sql_create(){

        var sql = "CREATE TABLE IF NOT EXISTS ",
          first_field = true;

        if(attr && attr.postgres){
          sql += t.table_name+" (";

          if(cmd.splitted){
            sql += "zone integer";
            first_field = false;
          }

          for(f in cmd.dimensions){
            if(first_field){
              sql += f;
              first_field = false;
            }else
              sql += ", " + f;
            sql += _md.sql_type(t, f, cmd.dimensions[f].type, true) + _md.sql_composite(cmd.dimensions, f, "", true);
          }

          for(f in cmd.resources)
            sql += ", " + f + _md.sql_type(t, f, cmd.resources[f].type, true) + _md.sql_composite(cmd.resources, f, "", true);

          for(f in cmd.attributes)
            sql += ", " + f + _md.sql_type(t, f, cmd.attributes[f].type, true) + _md.sql_composite(cmd.attributes, f, "", true);

          sql += ", PRIMARY KEY (";
          first_field = true;
          if(cmd.splitted){
            sql += "zone";
            first_field = false;
          }
          for(f in cmd["dimensions"]){
            if(first_field){
              sql += f;
              first_field = false;
            }else
              sql += ", " + f;
          }

        }else{
          sql += "`"+t.table_name+"` (ref CHAR PRIMARY KEY NOT NULL, `_deleted` BOOLEAN";

          //sql += _md.sql_mask(f) + _md.sql_type(t, f, cmd.dimensions[f].type) + _md.sql_composite(cmd.dimensions, f);

          for(f in cmd.dimensions)
            sql += _md.sql_mask(f) + _md.sql_type(t, f, cmd.dimensions[f].type);

          for(f in cmd.resources)
            sql += _md.sql_mask(f) + _md.sql_type(t, f, cmd.resources[f].type);

          for(f in cmd.attributes)
            sql += _md.sql_mask(f) + _md.sql_type(t, f, cmd.attributes[f].type);

          // sql += ", PRIMARY KEY (";
          // first_field = true;
          // for(f in cmd["dimensions"]){
          //   if(first_field){
          //     sql += "`" + f + "`";
          //     first_field = false;
          //   }else
          //     sql += _md.sql_mask(f);
          // }
        }

        sql += ")";

        return sql;
      }

      function sql_update(){
        // "INSERT OR REPLACE INTO user_params (prm_name, prm_value) VALUES (?, ?);
        var sql = "INSERT OR REPLACE INTO `"+t.table_name+"` (",
          fields = [],
          first_field = true;

        for(f in cmd.dimensions){
          if(first_field){
            sql += f;
            first_field = false;
          }else
            sql += ", " + f;
          fields.push(f);
        }
        for(f in cmd.resources){
          sql += ", " + f;
          fields.push(f);
        }
        for(f in cmd.attributes){
          sql += ", " + f;
          fields.push(f);
        }

        sql += ") VALUES (?";
        for(f = 1; f<fields.length; f++){
          sql += ", ?";
        }
        sql += ")";

        return {sql: sql, fields: fields};
      }

      function sql_select(){
        var sql = "SELECT * FROM `"+t.table_name+"` WHERE ",
          first_field = true;
        attr._values = [];

        for(var f in cmd["dimensions"]){

          if(first_field)
            first_field = false;
          else
            sql += " and ";

          sql += "`" + f + "`" + "=?";
          attr._values.push(attr[f]);
        }

        if(first_field)
          sql += "1";

        return sql;
      }


      if(action == "create_table")
        res = sql_create();

      else if(action in {insert:"", update:"", replace:""})
        res[t.table_name] = sql_update();

      else if(action == "select")
        res = sql_select();

      else if(action == "select_all")
        res = sql_select();

      else if(action == "delete")
        res = "DELETE FROM `"+t.table_name+"` WHERE ref = ?";

      else if(action == "drop")
        res = "DROP TABLE IF EXISTS `"+t.table_name+"`";

      else if(action == "get_selection")
        res = sql_selection();

      return res;
    }
  },

  get_ref: {
    value: function(attr){

      if(attr instanceof RegisterRow)
        attr = attr._obj;

      if(attr.ref)
        return attr.ref;

      var key = "",
        dimensions = this.metadata().dimensions;

      for(var j in dimensions){
        key += (key ? "¶" : "");
        if(dimensions[j].type.is_ref)
          key += $p.utils.fix_guid(attr[j]);

        else if(!attr[j] && dimensions[j].type.digits)
          key += "0";

        else if(dimensions[j].date_part)
          key += $p.moment(attr[j] || $p.utils.blank.date).format($p.moment.defaultFormatUtc);

        else if(attr[j]!=undefined)
          key += String(attr[j]);

        else
          key += "$";
      }
      return key;
    }
  },

  caption_flds: {
    value: function(attr){

      var _meta = attr.metadata || this.metadata(),
        str_def = "<column id=\"%1\" width=\"%2\" type=\"%3\" align=\"%4\" sort=\"%5\">%6</column>",
        acols = [],  s = "";

      if(_meta.form && _meta.form.selection){
        acols = _meta.form.selection.cols;

      }else{

        for(var f in _meta["dimensions"]){
          acols.push(new Col_struct(f, "*", "ro", "left", "server", _meta["dimensions"][f].synonym));
        }
      }

      if(attr.get_header && acols.length){
        s = "<head>";
        for(var col in acols){
          s += str_def.replace("%1", acols[col].id).replace("%2", acols[col].width).replace("%3", acols[col].type)
            .replace("%4", acols[col].align).replace("%5", acols[col].sort).replace("%6", acols[col].caption);
        }
        s += "</head>";
      }

      return {head: s, acols: acols};
    }
  },

  create: {
    value: function(attr){

      if(!attr || typeof attr != "object")
        attr = {};


      var o = this.by_ref[attr.ref];
      if(!o){

        o = new $p[this.obj_constructor()](attr, this);

        // Триггер после создания
        var after_create_res = this.handle_event(o, "after_create");

        if(after_create_res === false)
          return Promise.resolve(o);

        else if(typeof after_create_res === "object" && after_create_res.then)
          return after_create_res;
      }

      return Promise.resolve(o);
    }
  }
});



/**
 * ### Абстрактный менеджер регистра сведений
 * Экземпляры объектов этого класса создаются при выполнении конструктора {{#crossLink "Meta"}}{{/crossLink}}
 * в соответствии с описанием метаданных конфигурации и помещаются в коллекцию {{#crossLink "InfoRegs"}}{{/crossLink}}
 *
 * @class InfoRegManager
 * @extends RegisterManager
 * @constructor
 * @param class_name {string} - имя типа менеджера объекта. например, "ireg.prices"
 */
function InfoRegManager(class_name){

  InfoRegManager.superclass.constructor.call(this, class_name);

}
InfoRegManager._extend(RegisterManager);

/**
 * Возаращает массив записей - срез первых значений по ключам отбора
 * @method slice_first
 * @for InfoRegManager
 * @param filter {Object} - отбор + период
 */
InfoRegManager.prototype.slice_first = function(filter){

};

/**
 * Возаращает массив записей - срез последних значений по ключам отбора
 * @method slice_last
 * @for InfoRegManager
 * @param filter {Object} - отбор + период
 */
InfoRegManager.prototype.slice_last = function(filter){

};


/**
 * ### Журнал событий
 * Хранит и накапливает события сеанса<br />
 * Является наследником регистра сведений
 * @extends InfoRegManager
 * @class LogManager
 * @static
 */
function LogManager(){

  LogManager.superclass.constructor.call(this, "ireg.$log");

  var smax;

  this.__define({

    /**
     * Добавляет запись в журнал
     * @param msg {String|Object|Error} - текст + класс события
     * @param [msg.obj] {Object} - дополнительный json объект
     */
    record: {
      value: function(msg){

        if(msg instanceof Error){
          if(console)
            console.log(msg);
          msg = {
            class: "error",
            note: msg.toString()
          }
        }else if(typeof msg == "object" && !msg.class && !msg.obj){
          msg = {
            class: "obj",
            obj: msg,
            note: msg.note
          };
        }else if(typeof msg != "object")
          msg = {note: msg};

        msg.date = Date.now() + $p.wsql.time_diff;

        // уникальность ключа
        if(!smax)
          smax = alasql.compile("select MAX(`sequence`) as `sequence` from `ireg_$log` where `date` = ?");
        var res = smax([msg.date]);
        if(!res.length || res[0].sequence === undefined)
          msg.sequence = 0;
        else
          msg.sequence = parseInt(res[0].sequence) + 1;

        // класс сообщения
        if(!msg.class)
          msg.class = "note";

        $p.wsql.alasql("insert into `ireg_$log` (`ref`, `date`, `sequence`, `class`, `note`, `obj`) values (?,?,?,?,?,?)",
          [msg.date + "¶" + msg.sequence, msg.date, msg.sequence, msg.class, msg.note, msg.obj ? JSON.stringify(msg.obj) : ""]);

      }
    },

    /**
     * Сбрасывает события на сервер
     * @method backup
     * @param [dfrom] {Date}
     * @param [dtill] {Date}
     */
    backup: {
      value: function(dfrom, dtill){

      }
    },

    /**
     * Восстанавливает события из архива на сервере
     * @method restore
     * @param [dfrom] {Date}
     * @param [dtill] {Date}
     */
    restore: {
      value: function(dfrom, dtill){

      }
    },

    /**
     * Стирает события в указанном диапазоне дат
     * @method clear
     * @param [dfrom] {Date}
     * @param [dtill] {Date}
     */
    clear: {
      value: function(dfrom, dtill){

      }
    },

    show: {
      value: function (pwnd) {

      }
    },

    get: {
      value: function (ref, force_promise, do_not_create) {

        if(typeof ref == "object")
          ref = ref.ref || "";

        if(!this.by_ref[ref]){

          if(force_promise === false)
            return undefined;

          var parts = ref.split("¶");
          $p.wsql.alasql("select * from `ireg_$log` where date=" + parts[0] + " and sequence=" + parts[1]).forEach(function (row) {
            new RegisterRow(row, this);
          }.bind(this));
        }

        return force_promise ? Promise.resolve(this.by_ref[ref]) : this.by_ref[ref];
      }
    },

    get_sql_struct: {
      value: function(attr){

        if(attr && attr.action == "get_selection"){
          var sql = "select * from `ireg_$log`";
          if(attr.date_from){
            if (attr.date_till)
              sql += " where `date` >= ? and `date` <= ?";
            else
              sql += " where `date` >= ?";
          }else if (attr.date_till)
            sql += " where `date` <= ?";

          return sql;

        }else
          return LogManager.superclass.get_sql_struct.call(this, attr);
      }
    },

    caption_flds: {
      value: function (attr) {

        var str_def = "<column id=\"%1\" width=\"%2\" type=\"%3\" align=\"%4\" sort=\"%5\">%6</column>",
          acols = [], s = "";


        acols.push(new Col_struct("date", "200", "ro", "left", "server", "Дата"));
        acols.push(new Col_struct("class", "100", "ro", "left", "server", "Класс"));
        acols.push(new Col_struct("note", "*", "ro", "left", "server", "Событие"));

        if(attr.get_header){
          s = "<head>";
          for(var col in acols){
            s += str_def.replace("%1", acols[col].id).replace("%2", acols[col].width).replace("%3", acols[col].type)
              .replace("%4", acols[col].align).replace("%5", acols[col].sort).replace("%6", acols[col].caption);
          }
          s += "</head>";
        }

        return {head: s, acols: acols};
      }
    },

    data_to_grid: {
      value: function (data, attr) {
        var xml = "<?xml version='1.0' encoding='UTF-8'?><rows total_count='%1' pos='%2' set_parent='%3'>"
            .replace("%1", data.length).replace("%2", attr.start)
            .replace("%3", attr.set_parent || "" ),
          caption = this.caption_flds(attr);

        // при первом обращении к методу добавляем описание колонок
        xml += caption.head;

        data.forEach(function(r){
          xml += "<row id=\"" + r.ref + "\"><cell>" +
            $p.moment(r.date - $p.wsql.time_diff).format("DD.MM.YYYY HH:mm:ss") + "." + r.sequence + "</cell>" +
            "<cell>" + (r.class || "") + "</cell><cell>" + (r.note || "") + "</cell></row>";
        });

        return xml + "</rows>";
      }
    }
  });

}
LogManager._extend(InfoRegManager);



/**
 * ### Абстрактный менеджер регистра накопления
 * Экземпляры объектов этого класса создаются при выполнении конструктора {{#crossLink "Meta"}}{{/crossLink}}
 * в соответствии с описанием метаданных конфигурации и помещаются в коллекцию {{#crossLink "AccumRegs"}}{{/crossLink}}
 *
 * @class AccumRegManager
 * @extends RegisterManager
 * @constructor
 * @param class_name {string} - имя типа менеджера объекта. например, "areg.goods_on_stores"
 */
function AccumRegManager(class_name){

  AccumRegManager.superclass.constructor.call(this, class_name);
}
AccumRegManager._extend(RegisterManager);




/**
 * ### Абстрактный менеджер справочника
 * Экземпляры объектов этого класса создаются при выполнении конструктора {{#crossLink "Meta"}}{{/crossLink}}
 * в соответствии с описанием метаданных конфигурации и помещаются в коллекцию {{#crossLink "Catalogs"}}{{/crossLink}}
 *
 * @class CatManager
 * @extends RefDataManager
 * @constructor
 * @param class_name {string}
 */
function CatManager(class_name) {

  CatManager.superclass.constructor.call(this, class_name);

  // реквизиты по метаданным
  if(this.metadata().hierarchical && this.metadata().group_hierarchy){

    /**
     * ### Признак "это группа"
     * @property is_folder
     * @for CatObj
     * @type {Boolean}
     */
    $p[this.obj_constructor()].prototype.__define("is_folder", {
      get : function(){ return this._obj.is_folder || false},
      set : function(v){ this._obj.is_folder = $p.utils.fix_boolean(v)},
      enumerable: true,
      configurable: true
    });
  }

}
CatManager._extend(RefDataManager);

/**
 * Возвращает объект по наименованию
 * @method by_name
 * @param name {String|Object} - искомое наименование
 * @return {DataObj}
 */
CatManager.prototype.by_name = function(name){

  var o;

  this.find_rows({name: name}, function (obj) {
    o = obj;
    return false;
  });

  if(!o)
    o = this.get();

  return o;
};

/**
 * Возвращает объект по коду
 * @method by_id
 * @param id {String|Object} - искомый код
 * @return {DataObj}
 */
CatManager.prototype.by_id = function(id){

  var o;

  this.find_rows({id: id}, function (obj) {
    o = obj;
    return false;
  });

  if(!o)
    o = this.get();

  return o;
};

/**
 * Для иерархических кешируемых справочников возвращает путь элемента
 * @param ref {String|CatObj} - ссылка или объект данных
 * @return {string} - строка пути элемента
 */
CatManager.prototype.path = function(ref){
  var res = [], tobj;

  if(ref instanceof DataObj)
    tobj = ref;
  else
    tobj = this.get(ref, false, true);
  if(tobj)
    res.push({ref: tobj.ref, presentation: tobj.presentation});

  if(tobj && this.metadata().hierarchical){
    while(true){
      tobj = tobj.parent;
      if(tobj.empty())
        break;
      res.push({ref: tobj.ref, presentation: tobj.presentation});
    }
  }
  return res;
};



/**
 * ### Абстрактный менеджер плана видов характеристик
 * Экземпляры объектов этого класса создаются при выполнении конструктора {{#crossLink "Meta"}}{{/crossLink}}
 * в соответствии с описанием метаданных конфигурации и помещаются в коллекцию {{#crossLink "ChartsOfCharacteristics"}}{{/crossLink}}
 *
 * @class ChartOfCharacteristicManager
 * @extends CatManager
 * @constructor
 * @param class_name {string}
 */
function ChartOfCharacteristicManager(class_name){

  ChartOfCharacteristicManager.superclass.constructor.call(this, class_name);

}
ChartOfCharacteristicManager._extend(CatManager);


/**
 * ### Абстрактный менеджер плана счетов
 * Экземпляры объектов этого класса создаются при выполнении конструктора {{#crossLink "Meta"}}{{/crossLink}}
 * в соответствии с описанием метаданных конфигурации и помещаются в коллекцию {{#crossLink "ChartsOfAccounts"}}{{/crossLink}}
 *
 * @class ChartOfAccountManager
 * @extends CatManager
 * @constructor
 * @param class_name {string}
 */
function ChartOfAccountManager(class_name){

  ChartOfAccountManager.superclass.constructor.call(this, class_name);

}
ChartOfAccountManager._extend(CatManager);


/**
 * ### Абстрактный менеджер документов
 * Экземпляры объектов этого класса создаются при выполнении конструктора {{#crossLink "Meta"}}{{/crossLink}}
 * в соответствии с описанием метаданных конфигурации и помещаются в коллекцию {{#crossLink "Documents"}}{{/crossLink}}
 *
 * @class DocManager
 * @extends RefDataManager
 * @constructor
 * @param class_name {string}
 */
function DocManager(class_name) {


  DocManager.superclass.constructor.call(this, class_name);

}
DocManager._extend(RefDataManager);

/**
 * ### Абстрактный менеджер задач
 * Экземпляры объектов этого класса создаются при выполнении конструктора {{#crossLink "Meta"}}{{/crossLink}}
 * в соответствии с описанием метаданных конфигурации и помещаются в коллекцию {{#crossLink "Tasks"}}{{/crossLink}}
 *
 * @class TaskManager
 * @extends CatManager
 * @constructor
 * @param class_name {string}
 */
function TaskManager(class_name){

  TaskManager.superclass.constructor.call(this, class_name);

}
TaskManager._extend(CatManager);

/**
 * ### Абстрактный менеджер бизнес-процессов
 * Экземпляры объектов этого класса создаются при выполнении конструктора {{#crossLink "Meta"}}{{/crossLink}}
 * в соответствии с описанием метаданных конфигурации и помещаются в коллекцию {{#crossLink "BusinessProcesses"}}{{/crossLink}}
 *
 * @class BusinessProcessManager
 * @extends CatManager
 * @constructor
 * @param class_name {string}
 */
function BusinessProcessManager(class_name){

  BusinessProcessManager.superclass.constructor.call(this, class_name);

}
BusinessProcessManager._extend(CatManager);