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

Показывать:
/**
 * Глобальные переменные и общие методы фреймворка __metadata.js__ <i>Oknosoft data engine</i>
 *
 * &copy; Evgeniy Malyarov http://www.oknosoft.ru 2014-2016
 *
 * Экспортирует глобальную переменную __$p__ типа {{#crossLink "MetaEngine"}}{{/crossLink}}
 * @module  common
 */


/**
 * Фреймворк добавляет в прототипы _Object_ и _Number_<br />
 * несколько методов - синтаксический сахар для _наследования_ и работы со _свойствами_
 * @class Object
 * @constructor
 */


Object.defineProperties(Object.prototype, {

  /**
   * Синтаксический сахар для defineProperty
   * @method __define
   * @for Object
   */
  __define: {
    value: function( key, descriptor ) {
      if( descriptor ) {
        Object.defineProperty( this, key, descriptor );
      } else {
        Object.defineProperties( this, key );
      }
      return this;
    }
  },

  /**
   * Реализует наследование текущим конструктором свойств и методов конструктора Parent
   * @method _extend
   * @for Object
   * @param Parent {Function}
   */
  _extend: {
    value: function( Parent ) {
      var F = function() { };
      F.prototype = Parent.prototype;
      this.prototype = new F();
      this.prototype.constructor = this;
      this.__define("superclass", {
        value: Parent.prototype,
        enumerable: false
      });
    }
  },

  /**
   * Копирует все свойства из src в текущий объект исключая те, что в цепочке прототипов src до Object
   * @method _mixin
   * @for Object
   * @param src {Object} - источник
   * @return {this}
   */
  _mixin: {
    value: function(src, include, exclude ) {
      var tobj = {}, i, f; // tobj - вспомогательный объект для фильтрации свойств, которые есть у объекта Object и его прототипа
      if(include && include.length){
        for(i = 0; i<include.length; i++){
          f = include[i];
          if(exclude && exclude.indexOf(f)!=-1)
            continue;
          // копируем в dst свойства src, кроме тех, которые унаследованы от Object
          if((typeof tobj[f] == "undefined") || (tobj[f] != src[f]))
            this[f] = src[f];
        }
      }else{
        for(f in src){
          if(exclude && exclude.indexOf(f)!=-1)
            continue;
          // копируем в dst свойства src, кроме тех, которые унаследованы от Object
          if((typeof tobj[f] == "undefined") || (tobj[f] != src[f]))
            this[f] = src[f];
        }
      }
      return this;
    }
  },

  /**
   * Создаёт копию объекта
   * @method _clone
   * @for Object
   * @param src {Object|Array} - исходный объект
   * @param [exclude_propertyes] {Object} - объект, в ключах которого имена свойств, которые не надо копировать
   * @returns {Object|Array} - копия объекта
   */
  _clone: {
    value: function() {
      if(!this || "object" !== typeof this)
        return this;
      var p, v, c = "function" === typeof this.pop ? [] : {};
      for(p in this){
        if (this.hasOwnProperty(p)){
          v = this[p];
          if(v){
            if("function" === typeof v || v instanceof DataObj || v instanceof DataManager || v instanceof Date)
              c[p] = v;

            else if("object" === typeof v)
              c[p] = v._clone();

            else
              c[p] = v;
          } else
            c[p] = v;
        }
      }
      return c;
    }
  }
});

/**
 * Метод округления в прототип числа
 * @method round
 * @for Number
 */
if(!Number.prototype.round)
  Number.prototype.round = function(places) {
    var multiplier = Math.pow(10, places);
    return (Math.round(this * multiplier) / multiplier);
  };

/**
 * Метод дополнения лидирующими нулями в прототип числа
 * @method pad
 * @for Number
 */
if(!Number.prototype.pad)
  Number.prototype.pad = function(size) {
    var s = String(this);
    while (s.length < (size || 2)) {s = "0" + s;}
    return s;
  };

/**
 * Полифил обсервера и нотифаера
 */
if(!Object.observe && !Object.unobserve && !Object.getNotifier){
  Object.prototype.__define({

    /**
     * Подключает наблюдателя
     * @method observe
     * @for Object
     */
    observe: {
      value: function(target, observer) {
        if(!target._observers)
          target.__define({
            _observers: {
              value: [],
              enumerable: false
            },
            _notis: {
              value: [],
              enumerable: false
            }
          });
        target._observers.push(observer);
      },
      enumerable: false
    },

    /**
     * Отключает наблюдателя
     * @method unobserve
     * @for Object
     */
    unobserve: {
      value: function(target, observer) {
        if(!target._observers)
          return;
        for(var i=0; i<target._observers.length; i++){
          if(target._observers[i]===observer){
            target._observers.splice(i, 1);
            break;
          }
        }
      },
      enumerable: false
    },

    /**
     * Возвращает объект нотификатора
     * @method getNotifier
     * @for Object
     */
    getNotifier: {
      value: function(target) {
        var timer;
        return {
          notify: function (noti) {

            if(!target._observers || !noti)
              return;

            if(!noti.object)
              noti.object = target;

            target._notis.push(noti);
            noti = null;

            if(timer)
              clearTimeout(timer);

            timer = setTimeout(function () {
              //TODO: свернуть массив оповещений перед отправкой
              if(target._notis.length){
                target._observers.forEach(function (observer) {
                  observer(target._notis);
                });
                target._notis.length = 0;
              }
              timer = false;

            }, 4);
          }
        }
      },
      enumerable: false
    }

  });
}


/**
 * Для совместимости со старыми модулями, публикуем $p глобально
 * Кроме этой переменной, metadata.js ничего не экспортирует
 */
var $p = new MetaEngine();


/**
 * ### Metadata.js - проект с открытым кодом
 * Приглашаем к сотрудничеству всех желающих. Будем благодарны за любую помощь
 *
 * ### Почему Metadata.js?
 * Библиотека предназначена для разработки бизнес-ориентированных и учетных offline-first браузерных приложений
 * и содержит JavaScript реализацию [Объектной модели 1С](http://v8.1cru/overview/Platform.htm).
 * Библиотека эмулирует наиболее востребованные классы API 1С внутри браузера или Node.js, дополняя их средствами автономной работы и обработки данных на клиенте.
 *
 * ### Для кого?
 * Для разработчиков мобильных и браузерных приложений, которым близка парадигма 1С _на базе бизнес-объектов: документов и справочников_,
 * но которым тесно в рамках традиционной платформы 1С.<br />
 * Metadata.js предоставляет программисту:
 * - высокоуровневые [data-объекты](http://www.oknosoft.ru/upzp/apidocs/classes/DataObj.html), схожие по функциональности с документами, регистрами и справочниками платформы 1С
 * - инструменты декларативного описания метаданных и автогенерации интерфейса, схожие по функциональности с метаданными и формами платформы 1С
 * - средства событийно-целостной репликации и эффективные классы обработки данных, не имеющие прямых аналогов в 1С
 *
 * ### Контекст metadata.js
 * [metadata.js](https://github.com/oknosoft/metadata.js), экспортирует в глобальную область видимости переменную __$p__ типа {{#crossLink "MetaEngine"}}{{/crossLink}}
 *
 * @class MetaEngine
 * @static
 * @menuorder 00
 * @tooltip Контекст metadata.js
 */
function MetaEngine() {

  this.__define({

    version: {
      value: "PACKAGE_VERSION",
      writable: false
    },

    toString: {
      value: function(){
        return "Oknosoft data engine. v:" + this.version;
      },
      writable: false
    },

    /**
     * ### Коллекция вспомогательных методов
     *
     * @property utils
     * @type Utils
     * @final
     */
    utils: {
      value: new Utils()
    },

    /**
     * ### Буфер для строковых и двоичных данных, внедряемых в скрипт
     * В этой структуре живут, например, sql текст инициализации таблиц, xml-строки форм и менюшек и т.д.
     *
     * @property injected_data
     * @type Object
     * @final
     */
    injected_data: {
      value: {},
      writable: false
    },

    /**
     * Наша promise-реализация ajax
     *
     * @property ajax
     * @type Ajax
     * @final
     */
    ajax: {
      value: new Ajax(),
      writable: false
    },

    /**
     * Сообщения пользователю и строки нитернационализации
     * @property msg
     * @type Messages
     * @final
     */
    msg: {
      value: new Messages(),
      writable: false
    },

    /**
     * Интерфейс к данным в LocalStorage, AlaSQL и IndexedDB
     * @property wsql
     * @type WSQL
     * @final
     */
    wsql: {
      value: new WSQL(),
      writable: false
    },

    /**
     * Обработчики событий приложения
     * Подробнее см. модули {{#crossLinkModule "events"}}{{/crossLinkModule}} и {{#crossLinkModule "events.ui"}}{{/crossLinkModule}}
     * @property eve
     * @type AppEvents
     * @final
     */
    eve: {
      value: new AppEvents(),
      writable: false
    },

    /**
     * Aes для шифрования - дешифрования данных
     *
     * @property aes
     * @type Aes
     * @final
     */
    aes: {
      value: new Aes("metadata.js"),
      writable: false
    },

    /**
     * ### Moment для операций с интервалами и датами
     *
     * @property moment
     * @type Function
     * @final
     */
    moment: {
      get: function () { return this.utils.moment; }
    },

    /**
     * ### Подмешивает в объект свойства с иерархией объекта patch
     * В отличии от `_mixin`, не замещает, а дополняет одноименные свойства
     *
     * @method _patch
     * @param obj {Object}
     * @param patch {Object}
     * @return {Object} - исходный объект с подмешанными свойствами
     */
    _patch: {
      value: function (obj, patch) {
        for(var area in patch){

          if(typeof patch[area] == "object"){
            if(obj[area] && typeof obj[area] == "object")
              $p._patch(obj[area], patch[area]);
            else
              obj[area] = patch[area];
          }else
            obj[area] = patch[area];
        }
        return obj;
      }
    },

    /**
     * Абстрактный поиск значения в коллекции
     * @method _find
     * @param a {Array}
     * @param val {DataObj|String}
     * @param val {Array|String} - имена полей, в которых искать
     * @return {*}
     * @private
     */
    _find: {
      value: function(a, val, columns){
        //TODO переписать с учетом html5 свойств массивов
        var o, i, finded;
        if(typeof val != "object"){
          for(i in a){ // ищем по всем полям объекта
            o = a[i];
            for(var j in o){
              if(typeof o[j] !== "function" && $p.utils.is_equal(o[j], val))
                return o;
            }
          }
        }else{
          for(i in a){ // ищем по ключам из val
            o = a[i];
            finded = true;
            for(var j in val){
              if(typeof o[j] !== "function" && !$p.utils.is_equal(o[j], val[j])){
                finded = false;
                break;
              }
            }
            if(finded)
              return o;
          }
        }
      }
    },

    /**
     * Выясняет, удовлетворяет ли объект `o` условию `selection`
     * @method _selection
     * @param o {Object}
     * @param [selection]
     * @private
     */
    _selection: {
      value: function (o, selection) {

        var ok = true, j, sel, is_obj;

        if(selection){
          // если отбор является функцией, выполняем её, передав контекст
          if(typeof selection == "function")
            ok = selection.call(this, o);

          else{
            // бежим по всем свойствам `selection`
            for(j in selection){

              sel = selection[j];
              is_obj = typeof(sel) === "object";

              // пропускаем служебные свойства
              if(j.substr(0, 1) == "_")
                continue;

              // если свойство отбора является функцией, выполняем её, передав контекст
              else if(typeof sel == "function"){
                ok = sel.call(this, o, j);
                if(!ok)
                  break;

                // если свойство отбора является объектом `or`, выполняем Array.some() TODO: здесь напрашивается рекурсия
              }else if(j == "or" && Array.isArray(sel)){
                ok = sel.some(function (element) {
                  var key = Object.keys(element)[0];
                  if(element[key].hasOwnProperty("like"))
                    return o[key] && o[key].toLowerCase().indexOf(element[key].like.toLowerCase())!=-1;
                  else
                    return $p.utils.is_equal(o[key], element[key]);
                });
                if(!ok)
                  break;

                // если свойство отбора является объектом `like`, сравниваем подстроку
              }else if(is_obj && sel.hasOwnProperty("like")){
                if(!o[j] || o[j].toLowerCase().indexOf(sel.like.toLowerCase())==-1){
                  ok = false;
                  break;
                }

                // если свойство отбора является объектом `not`, сравниваем на неравенство
              }else if(is_obj && sel.hasOwnProperty("not")){
                if($p.utils.is_equal(o[j], sel.not)){
                  ok = false;
                  break;
                }

                // если свойство отбора является объектом `in`, выполняем Array.some()
              }else if(is_obj && sel.hasOwnProperty("in")){
                ok = sel.in.some(function(element) {
                  return $p.utils.is_equal(element, o[j]);
                });
                if(!ok)
                  break;

                // если свойство отбора является объектом `lt`, сравниваем на _меньше_
              }else if(is_obj && sel.hasOwnProperty("lt")){
                ok = o[j] < sel.lt;
                if(!ok)
                  break;

                // если свойство отбора является объектом `gt`, сравниваем на _больше_
              }else if(is_obj && sel.hasOwnProperty("gt")){
                ok = o[j] > sel.gt;
                if(!ok)
                  break;

                // если свойство отбора является объектом `between`, сравниваем на _вхождение_
              }else if(is_obj && sel.hasOwnProperty("between")){
                var tmp = o[j];
                if(typeof tmp != "number")
                  tmp = $p.utils.fix_date(o[j]);
                ok = (tmp >= sel.between[0]) && (tmp <= sel.between[1]);
                if(!ok)
                  break;

              }else if(!$p.utils.is_equal(o[j], sel)){
                ok = false;
                break;
              }
            }
          }
        }

        return ok;
      }
    },

    /**
     * ### Поиск массива значений в коллекции
     * Кроме стандартного поиска по равенству значений,
     * поддержаны операторы `in`, `not` и `like` и фильтрация через внешнюю функцию
     * @method _find_rows
     * @param arr {Array}
     * @param selection {Object|function} - в ключах имена полей, в значениях значения фильтра или объект {like: "значение"} или {not: значение}
     * @param callback {Function}
     * @return {Array}
     * @private
     */
    _find_rows: {
      value: function(arr, selection, callback){

        var o, res = [], top, count = 0;

        if(selection){
          if(selection._top){
            top = selection._top;
            delete selection._top;
          }else
            top = 300;
        }

        for(var i in arr){
          o = arr[i];

          // выполняем колбэк с элементом и пополняем итоговый массив
          if($p._selection.call(this, o, selection)){
            if(callback){
              if(callback.call(this, o) === false)
                break;
            }else
              res.push(o);

            // ограничиваем кол-во возвращаемых элементов
            if(top) {
              count++;
              if (count >= top)
                break;
            }
          }

        }
        return res;
      }
    },

    /**
     * ### Подключает обработчики событий
     *
     * @method on
     * @param name {String|Object} - имя события
     * @param fn {Function} - функция - обработчик
     * @returns {*}
     */
    on: {
      value: function (name, fn) {
        if(typeof name == "object"){
          for(var n in name){
            if(!name[n]._evnts)
              name[n]._evnts = [];
            name[n]._evnts.push(this.eve.attachEvent(n, name[n]));
          }
        }else
          return this.eve.attachEvent(name, fn);
      }
    },

    /**
     * ### Отключает обработчики событий
     *
     * @method off
     * @param id {String|Number|Function}
     */
    off: {
      value: function (id) {
        if(typeof id == "function" && id._evnts){
          id._evnts.forEach(function (id) {
            $p.eve.detachEvent(id);
          });
        }else if(!id)
          $p.eve.detachAllEvents();
        else
          $p.eve.detachEvent(id);
      }
    },

    /**
     * ### Запись журнала регистрации
     *
     * @method record_log
     * @param err
     */
    record_log: {
      value: function (err) {
        if($p.ireg && $p.ireg.$log)
          $p.ireg.$log.record(err);
        console.log(err);
      }
    },

    /**
     * ### Mетаданные конфигурации
     * @property md
     * @type Meta
     * @static
     */
    md: {
      value: new Meta()
    },

    /**
     * Коллекция менеджеров перечислений
     * @property enm
     * @type Enumerations
     * @static
     */
    enm: {
      value: new (
        /**
         * ### Коллекция менеджеров перечислений
         * - Состав коллекции определяется метаданными используемой конфигурации
         * - Тип элементов коллекции: {{#crossLink "EnumManager"}}{{/crossLink}}
         *
         * @class Enumerations
         * @static
         */
          function Enumerations(){
          this.toString = function(){return $p.msg.meta_enn_mgr};
        })
    },

    /**
     * Коллекция менеджеров справочников
     * @property cat
     * @type Catalogs
     * @static
     */
    cat: {
      value:   new (
        /**
         * ### Коллекция менеджеров справочников
         * - Состав коллекции определяется метаданными используемой конфигурации
         * - Тип элементов коллекции: {{#crossLink "CatManager"}}{{/crossLink}}
         *
         * @class Catalogs
         * @static
         */
          function Catalogs(){
          this.toString = function(){return $p.msg.meta_cat_mgr};
        }
      )
    },

    /**
     * Коллекция менеджеров документов
     * @property doc
     * @type Documents
     * @static
     */
    doc: {
      value:   new (
        /**
         * ### Коллекция менеджеров документов
         * - Состав коллекции определяется метаданными используемой конфигурации
         * - Тип элементов коллекции: {{#crossLink "DocManager"}}{{/crossLink}}
         *
         * @class Documents
         * @static
         */
          function Documents(){
          this.toString = function(){return $p.msg.meta_doc_mgr};
        })
    },

    /**
     * Коллекция менеджеров регистров сведений
     * @property ireg
     * @type InfoRegs
     * @static
     */
    ireg: {
      value:   new (
        /**
         * ### Коллекция менеджеров регистров сведений
         * - Состав коллекции определяется метаданными используемой конфигурации
         * - Тип элементов коллекции: {{#crossLink "InfoRegManager"}}{{/crossLink}}
         *
         * @class InfoRegs
         * @static
         */
          function InfoRegs(){
          this.toString = function(){return $p.msg.meta_ireg_mgr};
        })
    },

    /**
     * Коллекция менеджеров регистров накопления
     * @property areg
     * @type AccumRegs
     * @static
     */
    areg: {
      value:   new (
        /**
         * ### Коллекция менеджеров регистров накопления
         * - Состав коллекции определяется метаданными используемой конфигурации
         * - Тип элементов коллекции: {{#crossLink "RegisterManager"}}{{/crossLink}}
         *
         * @class AccumRegs
         * @static
         */
          function AccumRegs(){
          this.toString = function(){return $p.msg.meta_areg_mgr};
        })
    },

    /**
     * Коллекция менеджеров регистров бухгалтерии
     * @property aссreg
     * @type AccountsRegs
     * @static
     */
    aссreg: {
      value:   new (
        /**
         * ### Коллекция менеджеров регистров бухгалтерии
         * - Состав коллекции определяется метаданными используемой конфигурации
         * - Тип элементов коллекции: {{#crossLink "RegisterManager"}}{{/crossLink}}
         *
         * @class AccountsRegs
         * @static
         */
          function AccountsRegs(){
          this.toString = function(){return $p.msg.meta_accreg_mgr};
        })
    },

    /**
     * Коллекция менеджеров обработок
     * @property dp
     * @type DataProcessors
     * @static
     */
    dp: {
      value:   new (
        /**
         * ### Коллекция менеджеров обработок
         * - Состав коллекции определяется метаданными используемой конфигурации
         * - Тип элементов коллекции: {{#crossLink "DataProcessorsManager"}}{{/crossLink}}
         *
         * @class DataProcessors
         * @static
         */
          function DataProcessors(){
          this.toString = function(){return $p.msg.meta_dp_mgr};
        })
    },

    /**
     * Коллекция менеджеров отчетов
     * @property rep
     * @type Reports
     * @static
     */
    rep: {
      value: new (
        /**
         * ### Коллекция менеджеров отчетов
         * - Состав коллекции определяется метаданными используемой конфигурации
         * - Тип элементов коллекции: {{#crossLink "DataProcessorsManager"}}{{/crossLink}}
         *
         * @class Reports
         * @static
         */
          function Reports(){
          this.toString = function(){return $p.msg.meta_reports_mgr};
        })
    },

    /**
     * Коллекция менеджеров планов счетов
     * @property cacc
     * @type ChartsOfAccounts
     * @static
     */
    cacc: {
      value:   new (

        /**
         * ### Коллекция менеджеров планов счетов
         * - Состав коллекции определяется метаданными используемой конфигурации
         * - Тип элементов коллекции: {{#crossLink "ChartOfAccountManager"}}{{/crossLink}}
         *
         * @class ChartsOfAccounts
         * @static
         */
          function ChartsOfAccounts(){
          this.toString = function(){return $p.msg.meta_charts_of_accounts_mgr};
        })
    },

    /**
     * Коллекция менеджеров планов видов характеристик
     * @property cch
     * @type ChartsOfCharacteristics
     * @static
     */
    cch: {
      value: new (

        /**
         * ### Коллекция менеджеров планов видов характеристик
         * - Состав коллекции определяется метаданными используемой конфигурации
         * - Тип элементов коллекции: {{#crossLink "ChartOfCharacteristicManager"}}{{/crossLink}}
         *
         * @class ChartsOfCharacteristics
         * @static
         */
          function ChartsOfCharacteristics(){
          this.toString = function(){return $p.msg.meta_charts_of_characteristic_mgr};
        })
    },

    /**
     * Коллекция менеджеров задач
     * @property tsk
     * @type Tasks
     * @static
     */
    tsk: {
      value:   new (

        /**
         * ### Коллекция менеджеров задач
         * - Состав коллекции определяется метаданными используемой конфигурации
         * - Тип элементов коллекции: {{#crossLink "TaskManager"}}{{/crossLink}}
         *
         * @class Tasks
         * @static
         */
          function Tasks(){
          this.toString = function(){return $p.msg.meta_task_mgr};
        })
    },

    /**
     * Коллекция менеджеров бизнес-процессов
     * @property bp
     * @type Tasks
     * @static
     */
    bp: {
      value:   new (

        /**
         * ### Коллекция бизнес-процессов
         * - Состав коллекции определяется метаданными используемой конфигурации
         * - Тип элементов коллекции: {{#crossLink "BusinessProcessManager"}}{{/crossLink}}
         *
         * @class BusinessProcesses
         * @static
         */
          function BusinessProcesses(){
          this.toString = function(){return $p.msg.meta_bp_mgr};
        })
    },

    DataManager: {
      value: DataManager
    },

    RefDataManager: {
      value: RefDataManager
    },

    DataProcessorsManager: {
      value: DataProcessorsManager
    },

    EnumManager: {
      value: EnumManager
    },

    RegisterManager: {
      value: RegisterManager
    },

    InfoRegManager: {
      value: InfoRegManager
    },

    InfoRegManager: {
      value: InfoRegManager
    },

    LogManager: {
      value: LogManager
    },

    AccumRegManager: {
      value: AccumRegManager
    },

    CatManager: {
      value: CatManager
    },

    ChartOfCharacteristicManager: {
      value: ChartOfCharacteristicManager
    },

    ChartOfAccountManager: {
      value: ChartOfAccountManager
    },

    DocManager: {
      value: DocManager
    },

    TaskManager: {
      value: TaskManager
    },

    BusinessProcessManager: {
      value: BusinessProcessManager
    },

    DataObj: {
      value: DataObj
    },

    CatObj: {
      value: CatObj
    },

    DocObj: {
      value: DocObj
    },

    DataProcessorObj: {
      value: DataProcessorObj
    },

    TaskObj: {
      value: TaskObj
    },

    BusinessProcessObj: {
      value: BusinessProcessObj
    },

    EnumObj: {
      value: EnumObj
    },

    RegisterRow: {
      value: RegisterRow
    },

    TabularSection: {
      value: TabularSection
    },

    TabularSectionRow: {
      value: TabularSectionRow
    }

  });
}

/**
 * ### Коллекция вспомогательных методов
 * @class Utils
 * @static
 * @menuorder 35
 * @tooltip Вспомогательные методы
 */
function Utils() {

  /**
   * ### Moment для операций с интервалами и датами
   *
   * @property moment
   * @type Function
   * @final
   */
  this.moment = typeof moment == "function" ? moment : require('moment');
  this.moment._masks = {
    date:       "DD.MM.YY",
    date_time:  "DD.MM.YYYY HH:mm",
    ldt:        "DD MMMM YYYY, HH:mm",
    iso:        "YYYY-MM-DDTHH:mm:ss"
  };


  /**
   * ### Приводит значение к типу Дата
   *
   * @method fix_date
   * @param str {String|Number|Date} - приводиме значение
   * @param [strict=false] {Boolean} - если истина и значение не приводится к дате, возвращать пустую дату
   * @return {Date|*}
   */
  this.fix_date = function(str, strict){

    if(str instanceof Date)
      return str;
    else{
      var m = this.moment(str, ["DD-MM-YYYY", "DD-MM-YYYY HH:mm", "DD-MM-YYYY HH:mm:ss", "DD-MM-YY HH:mm", "YYYYDDMMHHmmss", this.moment.ISO_8601]);
      return m.isValid() ? m.toDate() : (strict ? this.blank.date : str);
    }
  };

  /**
   * ### Извлекает guid из строки или ссылки или объекта
   *
   * @method fix_guid
   * @param ref {*} - значение, из которого надо извлечь идентификатор
   * @param generate {Boolean} - указывает, генерировать ли новый guid для пустого значения
   * @return {String}
   */
  this.fix_guid = function(ref, generate){

    if(ref && typeof ref == "string"){

    } else if(ref instanceof DataObj)
      return ref.ref;

    else if(ref && typeof ref == "object"){
      if(ref.presentation){
        if(ref.ref)
          return ref.ref;
        else if(ref.name)
          return ref.name;
      }
      else
        ref = (typeof ref.ref == "object" && ref.ref.hasOwnProperty("ref")) ?  ref.ref.ref : ref.ref;
    }

    if(this.is_guid(ref) || generate === false)
      return ref;

    else if(generate)
      return this.generate_guid();

    else
      return this.blank.guid;
  };

  /**
   * ### Приводит значение к типу Число
   *
   * @method fix_number
   * @param str {*} - приводиме значение
   * @param [strict=false] {Boolean} - конвертировать NaN в 0
   * @return {Number}
   */
  this.fix_number = function(str, strict){
    var v = parseFloat(str);
    if(!isNaN(v))
      return v;
    else if(strict)
      return 0;
    else
      return str;
  };

  /**
   * ### Приводит значение к типу Булево
   *
   * @method fix_boolean
   * @param str {String}
   * @return {boolean}
   */
  this.fix_boolean = function(str){
    if(typeof str === "string")
      return !(!str || str.toLowerCase() == "false");
    else
      return !!str;
  };

  /**
   * ### Пустые значения даты и уникального идентификатора
   *
   * @property blank
   * @type Blank
   * @final
   */
  this.blank = {
    date: this.fix_date("0001-01-01T00:00:00"),
    guid: "00000000-0000-0000-0000-000000000000",
    by_type: function(mtype){
      var v;
      if(mtype.is_ref)
        v = this.guid;
      else if(mtype.date_part)
        v = this.date;
      else if(mtype["digits"])
        v = 0;
      else if(mtype.types && mtype.types[0]=="boolean")
        v = false;
      else
        v = "";
      return v;
    }
  };

  /**
   * ### Приводит тип значения v к типу метаданных
   *
   * @method fetch_type
   * @param str {*} - значение (обычно, строка, полученная из html поля ввода)
   * @param mtype {Object} - поле type объекта метаданных (field.type)
   * @return {*}
   */
  this.fetch_type = function(str, mtype){
    var v = str;
    if(mtype.is_ref)
      v = this.fix_guid(str);
    else if(mtype.date_part)
      v = this.fix_date(str, true);
    else if(mtype["digits"])
      v = this.fix_number(str, true);
    else if(mtype.types[0]=="boolean")
      v = this.fix_boolean(str);
    return v;
  };

  /**
   * ### Добавляет days дней к дате
   *
   * @method date_add_day
   * @param date {Date} - исходная дата
   * @param days {Number} - число дней, добавляемых к дате (может быть отрицательным)
   * @return {Date}
   */
  this.date_add_day = function(date, days, reset_time){
    var newDt = new Date(date);
    newDt.setDate(date.getDate() + days);
    if(reset_time)
      newDt.setHours(0,-newDt.getTimezoneOffset(),0,0);
    return newDt;
  }

  /**
   * ### Генерирует новый guid
   *
   * @method generate_guid
   * @return {String}
   */
  this.generate_guid = function(){
    var d = new Date().getTime();
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = (d + Math.random()*16)%16 | 0;
      d = Math.floor(d/16);
      return (c=='x' ? r : (r&0x7|0x8)).toString(16);
    });
  };

  /**
   * ### Проверяет, является ли значение guid-ом
   *
   * @method is_guid
   * @param v {*} - проверяемое значение
   * @return {Boolean} - true, если значение соответствует регурярному выражению guid
   */
  this.is_guid = function(v){
    if(typeof v !== "string" || v.length < 36)
      return false;
    else if(v.length > 36)
      v = v.substr(0, 36);
    return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(v)
  };

  /**
   * ### Проверяет, является ли значение пустым идентификатором
   *
   * @method is_empty_guid
   * @param v {*} - проверяемое значение
   * @return {Boolean} - true, если v эквивалентен пустому guid
   */
  this.is_empty_guid = function (v) {
    return !v || v === this.blank.guid;
  };

  /**
   * ### Проверяет, является ли значенние Data-объектным типом
   *
   * @method is_data_obj
   * @param v {*} - проверяемое значение
   * @return {Boolean} - true, если значение является ссылкой
   */
  this.is_data_obj = function(v){
    return v && v instanceof DataObj;
  };

  /**
   * ### Проверяет, является ли значенние менеджером объектов данных
   *
   * @method is_data_mgr
   * @param v {*} - проверяемое значение
   * @return {Boolean} - true, если значение является ссылкой
   */
  this.is_data_mgr = function(v){
    return v && v instanceof DataManager;
  };

  /**
   * ### Сравнивает на равенство ссылочные типы и примитивные значения
   *
   * @method is_equal
   * @param v1 {DataObj|String}
   * @param v2 {DataObj|String}
   * @return {boolean} - true, если значенния эквивалентны
   */
  this.is_equal = function(v1, v2){

    if(v1 == v2)
      return true;
    else if(typeof v1 === typeof v2)
      return false;

    return (this.fix_guid(v1, false) == this.fix_guid(v2, false));
  };

  /**
   * ### Читает данные из блоба
   * Возвращает промис с прочитанными данными
   *
   * @param blob {Blob}
   * @param [type] {String} - если type == "data_url", в промисе будет возвращен DataURL, а не текст
   * @return {Promise}
   */
  this.blob_as_text = function (blob, type) {

    return new Promise(function(resolve, reject){
      var reader = new FileReader();
      reader.onload = function(event){
        resolve(reader.result);
      };
      reader.onerror = function(err){
        reject(err);
      };
      if(type == "data_url")
        reader.readAsDataURL(blob);
      else
        reader.readAsText(blob);
    });

  };

}

/**
 * ### Наша promise-реализация ajax
 * - Поддерживает basic http авторизацию
 * - Позволяет установить перед отправкой запроса специфические заголовки
 * - Поддерживает получение и отправку данных с типом `blob`
 * - Позволяет отправлять запросы типа `get`, `post`, `put`, `patch`, `delete`
 *
 * @class Ajax
 * @static
 * @menuorder 31
 * @tooltip Работа с http
 */
function Ajax() {


  function _call(method, url, post_data, auth, before_send) {

    // Возвращаем новое Обещание
    return new Promise(function(resolve, reject) {

      // внутри Node, используем request
      if(typeof window == "undefined" && auth && auth.request){

        auth.request({
            url: encodeURI(url),
            headers : {
              "Authorization": auth.auth
            }
          },
          function (error, response, body) {
            if(error)
              reject(error);

            else if(response.statusCode != 200)
              reject({
                message: response.statusMessage,
                description: body,
                status: response.statusCode
              });

            else
              resolve({response: body});
          }
        );

      }else {

        // делаем привычные для XHR вещи
        var req = new XMLHttpRequest();

        if(window.dhx4 && window.dhx4.isIE)
          url = encodeURI(url);

        if(auth){
          var username, password;
          if(typeof auth == "object" && auth.username && auth.hasOwnProperty("password")){
            username = auth.username;
            password = auth.password;
            
          }else{
            if($p.ajax.username && $p.ajax.authorized){
              username = $p.ajax.username;
              password = $p.aes.Ctr.decrypt($p.ajax.password);
              
            }else{
              username = $p.wsql.get_user_param("user_name");
              password = $p.aes.Ctr.decrypt($p.wsql.get_user_param("user_pwd"));
              
              if(!username && $p.job_prm && $p.job_prm.guest_name){
                username = $p.job_prm.guest_name;
                password = $p.aes.Ctr.decrypt($p.job_prm.guest_pwd);
              }
            }
          }
          req.open(method, url, true, username, password);
          req.withCredentials = true;
          req.setRequestHeader("Authorization", "Basic " +
            btoa(unescape(encodeURIComponent(username + ":" + password))));
        }else
          req.open(method, url, true);

        if(before_send)
          before_send.call(this, req);

        if (method != "GET") {
          if(!this.hide_headers && !auth.hide_headers){
            req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
          }
        } else {
          post_data = null;
        }

        req.onload = function() {
          // Этот кусок вызовется даже при 404’ой ошибке
          // поэтому проверяем статусы ответа
          if (req.status == 200 && (req.response instanceof Blob || req.response.substr(0,9)!=="<!DOCTYPE")) {
            // Завершаем Обещание с текстом ответа
            if(req.responseURL == undefined)
              req.responseURL = url;
            resolve(req);
          }
          else {
            // Обламываемся, и передаём статус ошибки
            // что бы облегчить отладку и поддержку
            if(req.response)
              reject({
                message: req.statusText,
                description: req.response,
                status: req.status
              });
            else
              reject(Error(req.statusText));
          }
        };

        // отлавливаем ошибки сети
        req.onerror = function() {
          reject(Error("Network Error"));
        };

        // Делаем запрос
        req.send(post_data);
      }

    });

  }

  /**
   * имя пользователя для авторизации на сервере
   * @property username
   * @type String
   */
  this.username = "";

  /**
   * пароль пользователя для авторизации на сервере
   * @property password
   * @type String
   */
  this.password = "";

  /**
   * На этапе отладки считаем всех пользователей полноправными
   * @type {boolean}
   */
  this.root = true;

  /**
   * признак авторизованности на сервере
   * @property authorized
   * @type Boolean
   */
  this.authorized = false;

  /**
   * Выполняет асинхронный get запрос
   * @method get
   * @param url {String}
   * @return {Promise.<T>}
   * @async
   */
  this.get = function(url) {
    return _call.call(this, "GET", url);
  };

  /**
   * Выполняет асинхронный post запрос
   * @method post
   * @param url {String}
   * @param postData {String} - данные для отправки на сервер
   * @return {Promise.<T>}
   * @async
   */
  this.post = function(url, postData) {
    if (arguments.length == 1) {
      postData = "";
    } else if (arguments.length == 2 && (typeof(postData) == "function")) {
      onLoad = postData;
      postData = "";
    } else {
      postData = String(postData);
    }
    return _call.call(this, "POST", url, postData);
  };

  /**
   * Выполняет асинхронный get запрос с авторизацией и возможностью установить заголовки http
   * @method get_ex
   * @param url {String}
   * @param auth {Boolean}
   * @param beforeSend {Function} - callback перед отправкой запроса на сервер
   * @return {Promise.<T>}
   * @async
   */
  this.get_ex = function(url, auth, beforeSend){
    return _call.call(this, "GET", url, null, auth, beforeSend);

  };

  /**
   * Выполняет асинхронный post запрос с авторизацией и возможностью установить заголовки http
   * @method post_ex
   * @param url {String}
   * @param postData {String} - данные для отправки на сервер
   * @param auth {Boolean}
   * @param beforeSend {Function} - callback перед отправкой запроса на сервер
   * @return {Promise.<T>}
   * @async
   */
  this.post_ex = function(url, postData, auth, beforeSend){
    return _call.call(this, "POST", url, postData, auth, beforeSend);
  };

  /**
   * Выполняет асинхронный put запрос с авторизацией и возможностью установить заголовки http
   * @method put_ex
   * @param url {String}
   * @param postData {String} - данные для отправки на сервер
   * @param auth {Boolean}
   * @param beforeSend {Function} - callback перед отправкой запроса на сервер
   * @return {Promise.<T>}
   * @async
   */
  this.put_ex = function(url, postData, auth, beforeSend){
    return _call.call(this, "PUT", url, postData, auth, beforeSend);
  };

  /**
   * Выполняет асинхронный patch запрос с авторизацией и возможностью установить заголовки http
   * @method patch_ex
   * @param url {String}
   * @param postData {String} - данные для отправки на сервер
   * @param auth {Boolean}
   * @param beforeSend {Function} - callback перед отправкой запроса на сервер
   * @return {Promise.<T>}
   * @async
   */
  this.patch_ex = function(url, postData, auth, beforeSend){
    return _call.call(this, "PATCH", url, postData, auth, beforeSend);
  };

  /**
   * Выполняет асинхронный delete запрос с авторизацией и возможностью установить заголовки http
   * @method delete_ex
   * @param url {String}
   * @param auth {Boolean}
   * @param beforeSend {Function} - callback перед отправкой запроса на сервер
   * @return {Promise.<T>}
   * @async
   */
  this.delete_ex = function(url, auth, beforeSend){
    return _call.call(this, "DELETE", url, null, auth, beforeSend);

  };

  /**
   * Получает с сервера двоичные данные (pdf отчета или картинку или произвольный файл) и показывает его в новом окне, используя data-url
   * @method get_and_show_blob
   * @param url {String} - адрес, по которому будет произведен запрос
   * @param post_data {Object|String} - данные запроса
   * @param [method] {String}
   * @async
   */
  this.get_and_show_blob = function(url, post_data, method){

    var params = "menubar=no,toolbar=no,location=no,status=no,directories=no,resizable=yes,scrollbars=yes",
      wnd_print;

    function show_blob(req){
      url = window.URL.createObjectURL(req.response);
      wnd_print = window.open(url, "wnd_print", params);
      wnd_print.onload = function(e) {
        window.URL.revokeObjectURL(url);
      };
      return wnd_print;
    }

    if(!method || (typeof method == "string" && method.toLowerCase().indexOf("post")!=-1))
      return this.post_ex(url,
        typeof post_data == "object" ? JSON.stringify(post_data) : post_data,
        true,
        function(xhr){
          xhr.responseType = "blob";
        })
        .then(show_blob);
    else
      return this.get_ex(url, true, function(xhr){
          xhr.responseType = "blob";
        })
        .then(show_blob);
  };

  /**
   * Получает с сервера двоичные данные (pdf отчета или картинку или произвольный файл) и показывает диалог сохранения в файл
   * @method get_and_save_blob
   * @param url {String} - адрес, по которому будет произведен запрос
   * @param post_data {Object|String} - данные запроса
   * @param file_name {String} - имя файла для сохранения
   * @return {Promise.<T>}
   */
  this.get_and_save_blob = function(url, post_data, file_name){

    return this.post_ex(url,
      typeof post_data == "object" ? JSON.stringify(post_data) : post_data, true, function(xhr){
        xhr.responseType = "blob";
      })
      .then(function(req){
        saveAs(req.response, file_name);
      });
  };

  this.default_attr = function (attr, url) {
    if(!attr.url)
      attr.url = url;
    if(!attr.username)
      attr.username = this.username;
    if(!attr.password)
      attr.password = this.password;
    attr.hide_headers = true;

    if($p.job_prm["1c"]){
      attr.auth = $p.job_prm["1c"].auth;
      attr.request = $p.job_prm["1c"].request;
    }
  }

}