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

Показывать:
/**
 * Дополняет классы {{#crossLink "DataObj"}}{{/crossLink}} и {{#crossLink "DataManager"}}{{/crossLink}} методами чтения,<br />
 * записи и синхронизации через стандартный интерфейс <a href="http://its.1c.ru/db/v83doc#bookmark:dev:TI000001362">OData</a>
 * /a/unf/odata/standard.odata
 *
 * &copy; Evgeniy Malyarov http://www.oknosoft.ru 2014-2016
 *
 * @module  metadata
 * @submodule rest
 * @requires common
 */

/**
 * ### Методы общего назначения для работы с rest
 * 
 * @class Rest
 * @static
 * @menuorder 35
 * @tooltip Работа с rest 1С
 */
function Rest(){

  /**
   * Форматирует период для подстановки в запрос rest
   * @method filter_date
   * @param fld {String} - имя поля для фильтрации по датам
   * @param [dfrom] {Date} - дата начала
   * @param [dtill] {Date} - дата окончания
   * @return {String}
   */
  this.filter_date = function (fld, dfrom, dtill) {
    if(!dfrom)
      dfrom = new Date("2015-01-01");
    var res = fld + " gt datetime'" + $p.moment(dfrom).format($p.moment._masks.iso) + "'";
    if(dtill)
      res += " and " + fld + " lt datetime'" + $p.moment(dtill).format($p.moment._masks.iso) + "'";
    return res;
  };

  /**
   * Преобразует данные, прочитанные из rest-сервиса в json-прототип DataObj
   * @method to_data
   * @param rdata {Object} - фрагмент ответа rest
   * @param mgr {DataManager}
   * @return {Object}
   */
  this.to_data = function (rdata, mgr) {
    var o = {},
      cm = mgr.metadata(),
      mf = cm.fields,
      mts = cm.tabular_sections,
      ts, f, tf, row, syn, synts, vmgr;

    if(mgr instanceof RefDataManager){
      if(rdata.hasOwnProperty("DeletionMark"))
        o._deleted = rdata.DeletionMark;

      if(rdata.hasOwnProperty("DataVersion"))
        ;
      if(rdata.hasOwnProperty("Ref_Key"))
        o.ref = rdata.Ref_Key;

    }else{
      mf = ({})._mixin(cm.dimensions)._mixin(cm.resources)._mixin(cm.attributes);
    }

    if(mgr instanceof DocManager){
      if(rdata.hasOwnProperty("Number"))
        o.number_doc = rdata.Number || rdata.number_doc;
      else if(rdata.hasOwnProperty("number_doc"))
        o.number_doc = rdata.number_doc;
      if(rdata.hasOwnProperty("Date"))
        o.date = rdata.Date;
      else if(rdata.hasOwnProperty("date"))
        o.date = rdata.date;
      if(rdata.hasOwnProperty("Posted"))
        o.posted = rdata.Posted;
      else if(rdata.hasOwnProperty("posted"))
        o.posted = rdata.posted;

    } else {
      if(cm.main_presentation_name){
        if(rdata.hasOwnProperty("Description"))
          o.name = rdata.Description;
        else if(rdata.hasOwnProperty("name"))
          o.name = rdata.name;
      }

      if(cm.code_length){
        if(rdata.hasOwnProperty("Code"))
          o.id = rdata.Code;
        else if(rdata.hasOwnProperty("id"))
          o.id = rdata.id;
      }
    }

    for(f in mf){
      if(rdata.hasOwnProperty(f)){
        o[f] = rdata[f];
      }else{
        syn = _md.syns_1с(f);
        if(syn.indexOf("_Key") == -1 && mf[f].type.is_ref && rdata[syn+"_Key"])
          syn+="_Key";
        if(!rdata.hasOwnProperty(syn))
          continue;
        o[f] = rdata[syn];
      }
    }

    for(ts in mts){
      synts = (ts == "extra_fields" || rdata.hasOwnProperty(ts)) ? ts : _md.syns_1с(ts);
      if(!rdata.hasOwnProperty(synts))
        continue;
      o[ts] = [];
      if(rdata[synts]){
        rdata[synts].sort(function (a, b) {
          return (a.LineNumber || a.row) > (b.LineNumber || b.row);
        });
        rdata[synts].forEach(function (r) {
          row = {};
          for(tf in mts[ts].fields){
            syn = (r.hasOwnProperty(tf) || (ts == "extra_fields" && (tf == "property" || tf == "value"))) ? tf : _md.syns_1с(tf);
            if(syn.indexOf("_Key") == -1 && mts[ts].fields[tf].type.is_ref && r[syn+"_Key"])
              syn+="_Key";
            row[tf] = r[syn];
          }
          o[ts].push(row);
        });
      }
    }

    return o;
  };

  /**
   * Выполняет запрос к rest-сервису и возвращает массив прототипов DataObj
   * @param attr {Object} - параметры запроса
   * @param mgr {DataManager}
   * @return {Promise.<T>}
   */
  this.ajax_to_data = function (attr, mgr) {
    return $p.ajax.get_ex(attr.url, attr)
      .then(function (req) {
        return JSON.parse(req.response);
      })
      .then(function (res) {
        var data = [];
        res.value.forEach(function (rdata) {
          data.push(_rest.to_data(rdata, mgr));
        });
        return data;
      });
  };

  this.build_select = function (attr, mgr) {
    var s, f, syn, type, select_str = "";

    function build_condition(fld, val){

      if(typeof val == "function"){
        f += val(mgr, fld);

      }else{

        syn = _md.syns_1с(fld);
        type = _md.get(mgr.class_name, fld);
        if(type){
          type = type.type;
          if(type.is_ref){
            if(syn.indexOf("_Key") == -1 && type.types.length && type.types[0].indexOf("enm.")==-1)
              syn += "_Key";
          }

          if(type.types.length){

            if(["boolean", "number"].indexOf(typeof val) != -1 )
              f += syn + " eq " + val;

            else if((type.is_ref && typeof val != "object") || val instanceof DataObj)
              f += syn + " eq guid'" + val + "'";

            else if(typeof val == "string")
              f += syn + " eq '" + val + "'";

            else if(typeof val == "object"){
              // TODO: учесть in, not, like
              if(val.hasOwnProperty("like"))
                f += syn + " like '%" + val.like + "%'";

              else if(val.hasOwnProperty("not")){
                f += " not (" + build_condition(fld, val.not) + ") ";
              }

              else if(val.hasOwnProperty("in")){
                f += (syn + " in (") + (type.is_ref ? val.in.map(function(v){return "guid'" + v + "'"}).join(",") : val.in.join(",")) + ") ";
              }
            }
          }
        }
      }
    }

    function build_selection(sel){
      for(var fld in sel){

        if(!f)
          f = "&$filter=";
        else
          f += " and ";

        if(fld == "or" && Array.isArray(sel[fld])){
          var first = true;
          sel[fld].forEach(function (element) {

            if(first){
              f += " ( ";
              first = false;
            }else
              f += " or ";

            var key = Object.keys(element)[0];
            build_condition(key, element[key]);

          });
          f += " ) ";

        }else
          build_condition(fld, sel[fld]);

      }
    }

    if(!attr)
      attr = {};

    // учитываем нужные поля
    if(attr.fields){
      attr.fields.forEach(function(fld){
        if(fld == "ref")
          syn = "Ref_Key";
        else{
          syn = _md.syns_1с(fld);
          type = _md.get(mgr.class_name, fld).type;
          if(type.is_ref){
            if(syn.indexOf("_Key") == -1 && type.types.length && type.types[0].indexOf("enm.")==-1)
              syn += "_Key";
          }
        }
        if(!s)
          s = "&$select=";
        else
          s += ",";
        s += syn;
      });
      select_str += s;
    }

    // учитываем отбор
    // /a/unf/hs/rest/Catalog_Контрагенты?allowedOnly=true&$format=json&$top=30&$select=Ref_Key,Description&$filter=IsFolder eq false and Description like '%б%'
    if(attr.selection){
      if(typeof attr.selection == "function"){

      }else if(Array.isArray(attr.selection))
        attr.selection.forEach(build_selection);

      else
        build_selection(attr.selection);

      if(f)
        select_str += f;
    }


    // для простых запросов используем стандартный odata 1c
    if($p.job_prm.rest &&
      mgr.rest_name.indexOf("Module_") == -1 &&
      mgr.rest_name.indexOf("DataProcessor_") == -1 &&
      mgr.rest_name.indexOf("Report_") == -1 &&
      select_str.indexOf(" like ") == -1 &&
      select_str.indexOf(" in ") == -1 &&
      !mgr.metadata().irest )
      $p.ajax.default_attr(attr, $p.job_prm.rest_url());
    // для сложных отборов либо при явном irest в метаданных, используем наш irest
    else
      $p.ajax.default_attr(attr, $p.job_prm.irest_url());

    // начинаем строить url: только разрешенные + top
    attr.url += mgr.rest_name + "?allowedOnly=true&$format=json&$top=" + (attr.top || 300) + select_str;
    //a/unf/odata/standard.odata/Document_ЗаказПокупателя?allowedOnly=true&$format=json&$select=Ref_Key,DataVersion
  };

  /**
   * Загружает список объектов из rest-сервиса, обрезанный отбором
   * @method load_array
   * @for DataManager
   * @param attr {Object} - параметры запроса
   * @param [attr.selection] {Object} - условия отбора
   * @param [attr.top] {Number} - максимальное число загружаемых записей
   * @param mgr {DataManager}
   * @return {Promise.<T>} - промис с массивом загруженных прототипов DataObj
   * @async
   */
  this.load_array = function (attr, mgr) {

    _rest.build_select(attr, mgr);

    return _rest.ajax_to_data(attr, mgr);
  };

  /**
   * Читает объект из rest-сервиса
   * @return {Promise.<DataObj>} - промис с загруженным объектом
   */
  this.load_obj = function (tObj) {

    var attr = {};
    $p.ajax.default_attr(attr, (!tObj._metadata.irest && $p.job_prm.rest) ? $p.job_prm.rest_url() : $p.job_prm.irest_url());
    attr.url += tObj._manager.rest_name + "(guid'" + tObj.ref + "')?$format=json";

    return $p.ajax.get_ex(attr.url, attr)
      .then(function (req) {
        return JSON.parse(req.response);
      })
      .then(function (res) {
        tObj._mixin(_rest.to_data(res, tObj._manager))._set_loaded();
        return tObj;
      })
      .catch(function (err) {
        if(err.status==404)
          return tObj;
        else
          $p.record_log(err);
      });
  };

  /**
   * Сохраняет объект в базе irest-сервиса (наш http-интерфейс)
   * @param tObj {DataObj} - сохраняемый объект
   * @param attr {Object} - параметры сохранения
   * @param attr.[url] {String} - если не указано, будет использован адрес irest из параметров работы программы
   * @param attr.[username] {String}
   * @param attr.[password] {String}
   * @param attr.[post] {Boolean|undefined} - проведение или отмена проведения или просто запись
   * @param attr.[operational] {Boolean|undefined} - режим проведения документа [Оперативный, Неоперативный]
   * @return {Promise.<T>}
   * @async
   */
  this.save_irest = function (tObj, attr) {

    var post_data = JSON.stringify(tObj),
      prm = (attr.post != undefined ? ",post="+attr.post : "")+
        (attr.operational != undefined ? ",operational="+attr.operational : "");

    $p.ajax.default_attr(attr, $p.job_prm.irest_url());
    attr.url += tObj._manager.rest_name + "(guid'"+tObj.ref+"'"+prm+")";

    return $p.ajax.post_ex(attr.url, post_data, attr)
      .then(function (req) {
        return JSON.parse(req.response);
      })
      .then(function (res) {
        return tObj._mixin(res);
      });
  };

  /**
   * Сохраняет объект в базе rest-сервиса
   * @param tObj {DataObj} - сохраняемый объект
   * @param attr {Object} - параметры сохранения
   * @param attr.[url] {String} - если не указано, будет использован адрес rest из параметров работы программы
   * @param attr.[username] {String}
   * @param attr.[password] {String}
   * @param attr.[post] {Boolean|undefined} - проведение или отмена проведения или просто запись
   * @param attr.[operational] {Boolean} - режим проведения документа [Оперативный, Неоперативный]
   * @return {Promise.<T>}
   * @async
   */
  this.save_rest = function (tObj, attr) {

    var atom = tObj.to_atom(),
      url;

    $p.ajax.default_attr(attr, $p.job_prm.rest_url());
    url = attr.url + tObj._manager.rest_name;

    // проверяем наличие ссылки в базе приёмника
    attr.url = url + "(guid'" + tObj.ref + "')?$format=json&$select=Ref_Key,DeletionMark";

    return $p.ajax.get_ex(attr.url, attr)
      .catch(function (err) {
        if(err.status == 404){
          return {response: JSON.stringify({is_new: true})};
        }else
          return Promise.reject(err);
      })
      .then(function (req) {
        return JSON.parse(req.response);
      })
      .then(function (data) {
        // если объект существует на стороне приемника, выполняем patch, иначе - post
        if(data.is_new)
          return $p.ajax.post_ex(url, atom, attr);
        else
          return $p.ajax.patch_ex(url + "(guid'" + tObj.ref + "')", atom, attr);
      })
      .then(function (req) {
        var data = xmlToJSON.parseString(req.response, {
          mergeCDATA: false, // extract cdata and merge with text
          grokAttr: true, // convert truthy attributes to boolean, etc
          grokText: false, // convert truthy text/attr to boolean, etc
          normalize: true, // collapse multiple spaces to single space
          xmlns: false, // include namespaces as attribute in output
          namespaceKey: '_ns', // tag name for namespace objects
          textKey: '_text', // tag name for text nodes
          valueKey: '_value', // tag name for attribute values
          attrKey: '_attr', // tag for attr groups
          cdataKey: '_cdata', // tag for cdata nodes (ignored if mergeCDATA is true)
          attrsAsObject: false, // if false, key is used as prefix to name, set prefix to '' to merge children and attrs.
          stripAttrPrefix: true, // remove namespace prefixes from attributes
          stripElemPrefix: true, // for elements of same name in diff namespaces, you can enable namespaces and access the nskey property
          childrenAsArray: false // force children into arrays
        });
        if(data.entry && data.entry.content && data.entry.updated){
          var p = data.entry.content.properties, r = {}, v;
          for(var i in p){
            if(i.indexOf("_")==0)
              continue;
            if(v = p[i].element){
              r[i] = [];
              if(Array.isArray(v)){
                for(var n in v){
                  r[i][n] = {};
                  for(var j in v[n])
                    if(j.indexOf("_")!=0)
                      r[i][n][j] = v[n][j]._text === "false" ? false : v[n][j]._text;
                }
              }else{
                r[i][0] = {};
                for(var j in v)
                  if(j.indexOf("_")!=0)
                    r[i][0][j] = v[j]._text === "false" ? false : v[j]._text;
              }
            }else
              r[i] = p[i]._text === "false" ? false : p[i]._text;
          }
          return _rest.to_data(r, tObj._manager);
        }
      })
      .then(function (res) {
        return tObj._mixin(res);
      });

  };
}

var _rest = $p.rest = new Rest();


/**
 * ### Имя объектов этого менеджера для запросов к rest-серверу
 * Идентификатор формируется по принципу: ПрефиксИмени_ИмяОбъектаКонфигурации_СуффиксИмени
 * - Справочник _Catalog_
 * - Документ _Document_
 * - Журнал документов _DocumentJournal_
 * - Константа _Constant_
 * - План обмена _ExchangePlan_
 * - План счетов _ChartOfAccounts_
 * - План видов расчета _ChartOfCalculationTypes_
 * - План видов характеристик _ChartOfCharacteristicTypes_
 * - Регистр сведений _InformationRegister_
 * - Регистр накопления _AccumulationRegister_
 * - Регистр расчета _CalculationRegister_
 * - Регистр бухгалтерии _AccountingRegister_
 * - Бизнес-процесс _BusinessProcess_
 * - Задача _Task_
 * - Обработка _DataProcessor_
 * - Отчет _Report_
 * - Общий модуль _Module_
 * - Внешняя обработка _ExternalDataProcessor_
 * - Внешний отчет _ExternalReport_
 *
 * @property rest_name
 * @for DataManager
 * @type String
 * @final
 */
DataManager.prototype.__define("rest_name", {
  get : function(){
    var fp = this.class_name.split("."),
      csyn = {
        cat: "Catalog",
        doc: "Document",
        ireg: "InformationRegister",
        areg: "AccumulationRegister",
        cch: "ChartOfCharacteristicTypes",
        cacc: "ChartOfAccounts",
        tsk: "Task",
        bp: "BusinessProcess"
      };
    return csyn[fp[0]] + "_" + _md.syns_1с(fp[1]);
  },
  enumerable : false
});


DataManager.prototype.rest_tree = function (attr) {

  var t = this,
    cmd = t.metadata(),
    flds = [], ares = [], o, ro, syn, mf;

  $p.ajax.default_attr(attr, (!cmd.irest && $p.job_prm.rest) ? $p.job_prm.rest_url() : $p.job_prm.irest_url());
  attr.url += this.rest_name + "?allowedOnly=true&$format=json&$top=1000&$select=Ref_Key,DeletionMark,Parent_Key,Description&$filter=IsFolder eq true";

  return $p.ajax.get_ex(attr.url, attr)
    .then(function (req) {
      return JSON.parse(req.response);
    })
    .then(function (res) {
      for(var i = 0; i < res.value.length; i++) {
        ro = res.value[i];
        o = {
          ref: ro["Ref_Key"],
          _deleted: ro["DeletionMark"],
          parent: ro["Parent_Key"],
          presentation: ro["Description"]
        };
        ares.push(o);
      }
      return $p.iface.data_to_tree(ares);
    });

};

DataManager.prototype.rest_selection = function (attr) {

  if(attr.action == "get_tree")
    return this.rest_tree(attr);

  var t = this,
    cmd = t.metadata(),
    flds = [],
    ares = [], o, ro, syn, mf,
    select,
    filter_added;

  select = (function(){

    var s = "$select=Ref_Key,DeletionMark";

    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(t instanceof TaskManager){
      flds.push("name as presentation");
      flds.push("date");
      flds.push("number_doc");
      flds.push("completed");

    }else if(t instanceof BusinessProcessManager){
      flds.push("date");
      flds.push("number_doc");
      flds.push("started");
      flds.push("finished");

    }else{

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

      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){
      var parts;
      if(fld.indexOf(" as ") != -1){
        parts = fld.split(" as ")[0].split(".");
        if(parts.length == 1)
          fld = parts[0];
        else if(parts[0] != "_t_")
          return;
        else
          fld = parts[1]
      }
      if(fld == "0")
        return;
      syn = _md.syns_1с(fld);
      if(_md.get(t.class_name, fld).type.is_ref){
        if(syn.indexOf("_Key") == -1 && _md.get(t.class_name, fld).type.types.length && _md.get(t.class_name, fld).type.types[0].indexOf("enm.")==-1)
          syn += "_Key";
      }

      s += "," + syn;
    });

    flds.push("ref");
    flds.push("_deleted");

    return s;

  })();


  $p.ajax.default_attr(attr, (!cmd.irest && $p.job_prm.rest) ? $p.job_prm.rest_url() : $p.job_prm.irest_url());
  attr.url += (cmd.irest && cmd.irest.selection ? cmd.irest.selection : this.rest_name) + "?allowedOnly=true&$format=json&$top=1000&" + select;

  if(_md.get(t.class_name, "date") && (attr.date_from || attr.date_till)){
    attr.url += "&$filter=" + _rest.filter_date("Date", attr.date_from, attr.date_till);
    filter_added = true;
  }

  if(cmd["hierarchical"] && attr.parent){
    attr.url += filter_added ? " and " : "&$filter=";
    attr.url += "Parent_Key eq guid'" + attr.parent + "'";
    filter_added = true;
  }

  if(cmd["has_owners"] && attr.owner){
    attr.url += filter_added ? " and " : "&$filter=";
    attr.url += "Owner_Key eq guid'" + attr.owner + "'";
    filter_added = true;
  }

  if(attr.filter){
    attr.url += filter_added ? " and " : "&$filter=";
    attr.url += "$filter eq '" + attr.filter + "'";
    filter_added = true;
  }

  return $p.ajax.get_ex(attr.url, attr)
    .then(function (req) {
      return JSON.parse(req.response);
    })
    .then(function (res) {
      for(var i = 0; i < res.value.length; i++) {
        ro = res.value[i];
        o = {};
        flds.forEach(function (fld) {

          var fldsyn;

          if(fld == "ref") {
            o[fld] = ro["Ref_Key"];
            return;
          }else if(fld.indexOf(" as ") != -1){
            fldsyn = fld.split(" as ")[1];
            fld = fld.split(" as ")[0].split(".");
            fld = fld[fld.length-1];
          }else
            fldsyn = fld;

          syn = _md.syns_1с(fld);
          mf = _md.get(t.class_name, fld);
          if(mf){
            if(syn.indexOf("_Key") == -1 && mf.type.is_ref && mf.type.types.length && mf.type.types[0].indexOf("enm.")==-1)
              syn += "_Key";

            if(mf.type.date_part)
              o[fldsyn] = $p.moment(ro[syn]).format($p.moment._masks[mf.type.date_part]);

            else if(mf.type.is_ref){
              if(!ro[syn] || ro[syn] == $p.utils.blank.guid)
                o[fldsyn] = "";
              else{
                var mgr  = _md.value_mgr(o, fld, mf.type, false, ro[syn]);
                if(mgr)
                  o[fldsyn] = mgr.get(ro[syn]).presentation;
                else
                  o[fldsyn] = "";
              }
            }else
              o[fldsyn] = ro[syn];
          }
        });
        ares.push(o);
      }
      return $p.iface.data_to_grid.call(t, ares, attr);
    });

};

InfoRegManager.prototype.rest_slice_last = function(selection){

  if(!selection.period)
    selection.period = $p.utils.date_add_day(new Date(), 1);

  var t = this,
    cmd = t.metadata(),
    period = "Period=datetime'" + $p.moment(selection.period).format($p.moment._masks.iso) + "'",
    condition = "";

  for(var fld in cmd.dimensions){

    if(selection[fld] === undefined)
      continue;

    var syn = _md.syns_1с(fld),
      mf = cmd.dimensions[fld];

    if(syn.indexOf("_Key") == -1 && mf.type.is_ref && mf.type.types.length && mf.type.types[0].indexOf("enm.")==-1){
      syn += "_Key";
      if(condition)
        condition+= " and ";
      condition+= syn+" eq guid'"+selection[fld].ref+"'";
    }else{
      if(condition)
        condition+= " and ";

      if(mf.type.digits)
        condition+= syn+" eq "+$p.utils.fix_number(selection[fld]);

      else if(mf.type.date_part)
        condition+= syn+" eq datetime'"+ $p.moment(selection[fld]).format($p.moment._masks.iso) +"'";

      else
        condition+= syn+" eq '"+selection[fld]+"'";
    }

  }

  if(condition)
    period+= ",Condition='"+condition+"'";

  $p.ajax.default_attr(selection, $p.job_prm.rest_url());
  selection.url += this.rest_name + "/SliceLast(%sl)?allowedOnly=true&$format=json&$top=1000".replace("%sl", period);

  return _rest.ajax_to_data(selection, t)
    .then(function (data) {
      return t.load_array(data);
    });
};

/**
 * Сериализует объект данных в формат xml/atom (например, для rest-сервиса 1С)
 * @param [ex_meta] {Object} - метаданные внешней по отношению к текущей базы (например, для записи в *УНФ* объекта *Заказа дилера*).
 * Если указано, вывод ограничен полями, доступными во внешней базе + используются синонимы внешней базы
 */
DataObj.prototype.to_atom = function (ex_meta) {

  var res = '<entry><category term="StandardODATA.%n" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>\
        \n<title type="text"/><updated>%d</updated><author/><summary/><content type="application/xml">\
        \n<m:properties xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">\
      %p\
      \n</m:properties></content></entry>'
    .replace('%n', this._manager.rest_name)
    .replace('%d', $p.moment().format($p.moment.defaultFormatUtc)),

    prop = '\n<d:Ref_Key>' + this.ref + '</d:Ref_Key>' +
      '\n<d:DeletionMark>' + this._deleted + '</d:DeletionMark>',
      // '\n<d:DataVersion>' + this.data_version + '</d:DataVersion>',

    f, mf, fts, ts, mts, pname, v;

  function fields_to_atom(obj){
    var meta_fields = obj._metadata.fields,
      prefix = obj instanceof TabularSectionRow ? '\n\t<d:' : '\n<d:';

    for(f in meta_fields){
      mf = meta_fields[f];
      pname = _md.syns_1с(f);
      v = obj[f];
      if(v instanceof EnumObj)
        v = v.empty() ? "" : v.name;

      else if(v instanceof DataObj){
        if(pname.indexOf("_Key") == -1)
          pname+= '_Key';
        v = v.ref;

      }else if(mf.type.date_part){
        if(v.getFullYear() < 1000)
          v = '0001-01-01T00:00:00Z';
        else
          v = $p.moment(v).format($p.moment.defaultFormatUtc);

      }else if(v == undefined)
        continue;


      prop+= prefix + pname + '>' + v + '</d:' + pname + '>';
    }
  }

  if(this instanceof DocObj){
    prop+= '\n<d:Date>' + $p.moment(this.date).format($p.moment.defaultFormatUtc) + '</d:Date>';
    prop+= '\n<d:Number>' + this.number_doc + '</d:Number>';

  } else {

    if(this._metadata.main_presentation_name)
      prop+= '\n<d:Description>' + this.name + '</d:Description>';

    if(this._metadata.code_length)
      prop+= '\n<d:Code>' + this.id + '</d:Code>';

    if(this._metadata.hierarchical && this._metadata.group_hierarchy)
      prop+= '\n<d:IsFolder>' + this.is_folder + '</d:IsFolder>';

  }

  fields_to_atom(this);

  for(fts in this._metadata.tabular_sections) {

    mts = this._metadata.tabular_sections[fts];
    //if(mts.hide)
    //  continue;

    pname = 'StandardODATA.' + this._manager.rest_name + '_' + _md.syns_1с(fts) + '_RowType';
    ts = this[fts];
    if(ts.count()){
      prop+= '\n<d:' + _md.syns_1с(fts) + ' m:type="Collection(' + pname + ')">';

      ts.each(function (row) {
        prop+= '\n\t<d:element m:type="' + pname + '">';
        prop+= '\n\t<d:LineNumber>' + row.row + '</d:LineNumber>';
        fields_to_atom(row);
        prop+= '\n\t</d:element>';
      });

      prop+= '\n</d:' + _md.syns_1с(fts) + '>';

    }else
      prop+= '\n<d:' + _md.syns_1с(fts) + ' m:type="Collection(' + pname + ')" />';
  }

  return res.replace('%p', prop);

  //<d:DeletionMark>false</d:DeletionMark>
  //<d:Ref_Key>213d87ad-33d5-11de-b58f-00055d80a2b8</d:Ref_Key>
  //<d:IsFolder>false</d:IsFolder>
  //<d:Description>Новая папка</d:Description>
  //<d:Запасы m:type="Collection(StandardODATA.Document_ЗаказПокупателя_Запасы_RowType)">
  //  <d:element m:type="StandardODATA.Document_ЗаказПокупателя_Запасы_RowType">
  //    <d:LineNumber>1</d:LineNumber>
  //    <d:Номенклатура_Key>6ebf3bf7-3565-11de-b591-00055d80a2b9</d:Номенклатура_Key>
  //    <d:ТипНоменклатурыЗапас>true</d:ТипНоменклатурыЗапас>
  //    <d:Характеристика_Key>00000000-0000-0000-0000-000000000000</d:Характеристика_Key>
  //    <d:ПроцентАвтоматическойСкидки>0</d:ПроцентАвтоматическойСкидки>
  //    <d:СуммаАвтоматическойСкидки>0</d:СуммаАвтоматическойСкидки>
  //    <d:КлючСвязи>0</d:КлючСвязи>
  //  </d:element>
  //</d:Запасы>
  //<d:МатериалыЗаказчика m:type="Collection(StandardODATA.Document_ЗаказПокупателя_МатериалыЗаказчика_RowType)"/>
};