GithubHelp home page GithubHelp logo

wmakeev / moysklad Goto Github PK

View Code? Open in Web Editor NEW
35.0 9.0 7.0 749 KB

Библиотека для работы с API сервиса МойСклад

License: MIT License

JavaScript 98.16% TypeScript 1.84%
moysklad rest client wms warehouse crm javascript nodejs

moysklad's Introduction

moysklad

moysklad

npm Travis Code Climate coverage Code Climate maintainability Code Climate tech-debt

Библиотека для взаимодействия с JSON API сервиса МойСклад для node.js и браузера.

ВНИМАНИЕ! Библиотека находится в стадии разработки и становления. API может меняться. Перед обновлением минорной версии смотрите историю изменений.

Содержание

Особенности

Библиотека представляет максимально простой и прозрачный интерфейс к существующим методам API МойСклад, не абстрагирует разработчика от API и не выполняет никаких внутренних преобразований отправляемых и получаемых данных.

Основная задача библиотеки - упростить ряд рутинных задач:

  • формирование строки запроса (передача параметров, заголовков и фильтрация)
  • обработка ошибок
  • явное преобразование даты в формат МойСклад и обратно в Date
  • базовые TypeScript тайпинги для подсказок по API библиотеки

Важно отметить, что библиотека не поможет вам разобраться с API МойСклад, но лишь упростит работу с ним.

Установка

Поддерживаются версии Node.js >=14

$ npm install moysklad

Для Node.js до 18 версии, дополнительно нужно установить библиотеку для Fetch API и явно указать модуль с соответствующим интерфейсом при создании экземпляра библиотеки

$ npm install undici

undici.fetch

const { fetch } = require('undici')

const Moysklad = require('moysklad')

const moysklad = Moysklad({ fetch })

Для работы с библиотекой в браузере или для Node.js c 18 версии, установка дополнительного модуля не требуется.

Использование

const Moysklad = require('moysklad')

// Для инициализации экземпляра библиотеки указывать ключевое слово new не нужно
const ms = Moysklad({ login, password })

ms.GET('entity/customerorder', {
  filter: {
    applicable: true,
    state: {
      name: 'Отгружен'
    },
    sum: { $gt: 1000000, $lt: 2000000 }
  },
  limit: 10,
  order: 'moment,desc',
  expand: 'agent'
}).then(({ meta, rows }) => {
  console.log(
    `Последние ${meta.limit} из ${meta.size} проведенных заказов ` +
      `в статусе "Отгружен" на сумму от 10000 до 20000 руб`
  )

  // Выводим имя заказа, имя контрагента и сумму заказа для всех позиций
  rows.forEach(row => {
    console.log(`${row.name} ${row.agent.name} ${row.sum / 100}`)
  })
})

С другими примерами использования можно ознакомиться в папке examples

Параметры инициализации

Все параметры опциональные (имеют значения по умолчанию)

Параметр Значение по умолчанию Описание
fetch глобальный fetch Функция с интерфейсом Fetch API. Если глобальный fetch не найден, то будет выброшена ошибка при попытке осуществить http запрос. Начиная с Node.js 18 fetch является частью стандратной библиотеки.
endpoint "https://api.moysklad.ru/api" Точка досупа к API (хост точки доступа можно указать через переменную окружения MOYSKLAD_HOST, по умолчанию api.moysklad.ru)
api "remap" Раздел API (можно задать через переменную окружения MOYSKLAD_API)
apiVersion "1.2" Версия API (можно задать через переменную окружения MOYSKLAD_{NAME}_API_VERSION, где {NAME} - название API в верхнем регистре, напр. MOYSKLAD_REMAP_API_VERSION)
token undefined Токен доступа к API (см. Аутентификация)
login undefined Логин для доступа к API (см. Аутентификация)
password undefined Пароль для доступа к API (см. Аутентификация)
emitter undefined экземляр EventEmitter для передачи событий библиотеки
userAgent moysklad/{ver} (+https://github.com/wmakeev/moysklad), где {ver} - текущая версия библиотеки Содержимое заголовка "User-Agent" при выполнении запроса. Удобно использовать для контроля изменений через API на вкладке "Аудит". Можно задать через переменную окружения MOYSKLAD_USER_AGENT.

Явное задание параметра переопределяет значение заданное в соотв. переменной окружения.

Пример использования:

const Moysklad = require('moysklad')

// Явное указание используемой версии API
const moysklad = Moysklad({ apiVersion: '1.2' })

Аутентификация

Есть несколько способов передачи параметров аутентификации:

  1. Напрямую при инициализации экземпляра

    // Аутентификация по логину и паролю
    const moysklad = Moysklad({ login, password })
    // Аутентификация по токену
    const moysklad = Moysklad({ token })
  2. Через глобальные переменные или переменные окружения

    Если параметры аутентификации не указаны при инициализации клиента,

    const moysklad = Moysklad()

    то будет проведен поиск параметров в следующем порядке:

    1. Переменная окружения process.env.MOYSKLAD_TOKEN
    2. Переменные окружения process.env.MOYSKLAD_LOGIN и process.env.MOYSKLAD_PASSWORD
    3. Глобальная переменная window.MOYSKLAD_TOKEN
    4. Глобальные переменные window.MOYSKLAD_LOGIN и window.MOYSKLAD_PASSWORD
    5. Глобальная переменная global.MOYSKLAD_TOKEN
    6. Глобальные переменные global.MOYSKLAD_LOGIN и global.MOYSKLAD_PASSWORD

Статические методы

getTimeString

Преобразует локальную дату в строку в формате API МойСклад в часовом поясе Москвы

Moysklad.getTimeString(date: Date, includeMs?: boolean): string

Параметры:

date - дата

includeMs - если true, то в дату будут включены миллисекунды

Пример использования:

const date = new Date('2017-02-01T07:10:11.123Z')
const timeString = Moysklad.getTimeString(date, true)

assert.equal(timeString, '2017-02-01 10:10:11.123')

parseTimeString

Преобразует строку с датой в формате API МойСклад в объект даты (с учетом локального часового пояса и часового пояса API МойСклад)

Moysklad.parseTimeString(date: string) : Date

Параметры:

date - дата в формате МойСклад (напр. 2017-04-08 13:33:00.123)

Пример использования:

const parsedDate = Moysklad.parseTimeString('2017-04-08 13:33:00.123')

assert.equal(parsedDate.toISOString(), '2017-04-08T10:33:00.123Z')

parseUrl (статический метод)

Разбор url на составные компоненты

Аналогичен parseUrl методу экземпляра, за тем исключением, что на вход принимает только строку в формате href МойСклад.

buildFilter

Возвращает строку фильтра по объекту QueryFilter (см. filter)

Moysklad.buildFilter({ name: { $st: 'foo' } })
// 'code=123;name~=foo'

buildQuery

Формирует строку с параметрами запроса по объекту Query (см. query)

Moysklad.buildQuery({
  filter: { name: 'foo' },
  limit: 100,
  foo: 'bar'
})

// 'filter=name%3Dfoo&limit=100&foo=bar'

Методы экземпляра

GET

GET запрос

ms.GET(path: string, query?: object, options?: object): Promise

Параметры:

path - url ресурса

query - параметры запроса

options - опции запроса

Пример использования:

const productsCollection = await ms.GET('entity/product', { limit: 50 })

const order = await ms.GET(`entity/customerorder/${orderId}`, {
  expand: 'positions'
})

POST

POST запрос

ms.POST(
  path: string,
  payload?: object | Array<object>,
  query?: object,
  options?: object
): Promise

Параметры:

path - url ресурса

payload - объект или коллекция объектов (будет преобразовано в строку методом JSON.stringify)

query - параметры запроса

options - опции запроса

Пример использования:

const newProduct = await ms.POST('entity/product', { name: 'Новый товар' })

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

Если такое поведение не является предпочтительным, то можно обрабатывать ошибки при массовом обновлении/создании объектов вручную (см. muteCollectionErrors в параметрах запроса):

const updated = await ms.POST('entity/supply', supplyList, null, {
  muteCollectionErrors: true
})

const errors = updated
  .filter(item => item.errors)
  .map(item => item.errors[0].error)

if (errors.length) {
  console.log('Есть ошибки:', errors.join(', '))
}

const supplyHrefs = updated
  .filter(item => !item.errors)
  .map(item => item.meta.href)

PUT

PUT запрос

ms.PUT(
  path: string | string[],
  payload?: object,
  query?: object,
  options?: object
) : Promise

Параметры:

path - url ресурса

payload - обнвляемый объект (будет преобразован в строку методом JSON.stringify)

query - параметры запроса

options - опции запроса

Пример использования:

const updatedProduct = await ms.PUT(`entity/product/${id}`, product)

DELETE

DELETE запрос

ms.DELETE(path: string, options?: object): Promise

Параметры:

path - url ресурса

options - опции запроса

Метод DELETE возвращает undefined при успешном запросе.

Пример использования:

await ms.DELETE(`entity/product/${product.id}`)

getOptions

Возвращает опции переданные в момент инициализации экземпляра библиотеки

Пример использования:

const options = {
  login: 'login',
  password: 'password'
}

const ms = Moysklad(options)

const msOptions = ms.getOptions()

assert.ok(msOptions !== options)
assert.equal(msOptions.login, 'login')
assert.equal(msOptions.password, 'password')

buildUrl

Формирует url запроса

ms.buildUrl(url: string, query?: object): string

Параметры:

url - полный url (должен соответствовать настройкам)

path - url ресурса

query - параметры запроса

Пример использования:

const url = ms.buildUrl(
  'https://api.moysklad.ru/api/remap/1.2/entity/customerorder?expand=positions',
  { limit: 100 }
)

assert.equal(
  url,
  'https://api.moysklad.ru/api/remap/1.2/entity/customerorder?expand=positions&limit=100'
)
const url = ms.buildUrl('entity/customerorder', { expand: 'positions' })

assert.equal(
  url,
  'https://api.moysklad.ru/api/remap/1.2/entity/customerorder?expand=positions'
)

Можно безопасно дублировать символы /, лишние знаки будут исключены из результирующего url

const positionUrl = `/positions/${posId}/`

const url = ms.buildUrl(`entity/customerorder/` + positionUrl)

assert.equal(url, `https://api.moysklad.ru/api/remap/1.2/entity/customerorder/positions/${posId}`)

parseUrl

Разбор url на составные компоненты

ms.parseUrl(url: string): {
  endpoint: string
  api: string
  apiVersion: string
  path: Array<string>
  query: object
}

Параметры:

url - url ресурса

Пример использования:

const parsedUri = ms.parseUrl('https://api.moysklad.ru/api/remap/1.2/entity/customerorder?expand=positions')

assert.deepEqual(parsedUri, {
  endpoint: 'https://api.moysklad.ru/api',
  api: 'remap'
  apiVersion: '1.2',
  path: ['entity', 'customerorder'],
  query: {
    expand: 'positions'
  }
})

fetchUrl

Выполнить запрос по указанному url

ms.fetchUrl(url: string, options?: object): Promise

Параметры:

url - url ресурса

options - опции запроса

Пример использования:

const url = `https://api.moysklad.ru/api/remap/1.2/entity/customerorder/eb7bcc22-ae8d-11e3-9e32-002590a28eca`

const patch = { applicable: false }

const updatedOrder = await ms.fetchUrl(url, {
  method: 'PUT',
  body: JSON.stringify(patch)
})

Основные аргументы

path

Строка.

Примеры:

Url запроса можно указать полностью

ms.GET(
  `https://api.moysklad.ru/api/remap/1.2/entity/customerorder/${ORDER_ID}/positions/${POSITION_ID}?expand=assortment`
)

Но гораздо удобнее указывать путь только после версии API и выносить параметры запроса в параметры метода. Полный url будет сгенерирован автоматически, согласно настройкам экземпляра.

Ниже пример аналогичного запроса:

ms.GET(`entity/customerorder/${ORDER_ID}/positions/${POSITION_ID}`, {
  expand: 'assortment'
})

Можно безопасно дублировать символы /, лишние знаки будут сключены из результирующего url

const positionUrl = `/positions/${posId}`

ms.GET(`entity/customerorder/` + positionUrl)

query

querystring

Все поля объекта запроса преобразуются в соответствующую строку запроса url. Некоторые поля могут подвергаться преобразованию (напр. поля filter и order).

Поле объекта запроса должно иметь тип: string, number, boolean, null или undefined, любое другое значение вызовет ошибку.

const query = {
  str: 'some string',
  num: 1,
  bool: true,
  nil: null, // будет добавлено в строку запроса с пустым значением
  nothing: undefined, // поле будет пропущено
  arr: ['str', 1, true, null, undefined]
}

// https://api.moysklad.ru/api/remap/1.2/entity/demand?str=some%20string&num=1&bool=true&nil=&arr=str&arr=1&arr=true&arr=
ms.GET('entity/demand', query)
filter

Если поле filter объект, то вложенные поля filter преобразуются в параметры фильтра в строке запроса в соответствии со следующими правилами:

  • string, number, boolean не проходят дополнительных преобразований (key=value)
  • null преобразуется в пустую строку (key=)
  • Date преобразуется в строку методом getTimeString (key=YYYY-MM-DD HH:mm:ss)
  • object интерпретируется как набор селекторов или вложенных полей (см. пример ниже)

Пример фильтра:

const query = {
  filter: {
    name: '00001',
    code: [1, 2, '03'],
    foo: new Date(2000, 0, 1),
    state: {
      name: 'Оформлен'
    },
    moment: {
      $gt: new Date(2000, 0, 1),
      $lte: new Date(2001, 0, 2, 10, 0, 15, 123)
    },
    bar: {
      baz: 1,
      $exists: true
    }
  }
}

соответствует следующему значению поля filter в запросе (даты переданы в часовом поясе +5):

bar!=;bar.baz=1;code=03;code=1;code=2;foo=1999-12-31 22:00:00;moment<=2001-01-02 08:00:15.123;moment>1999-12-31 22:00:00;name=00001;state.name=Оформлен

Для построения фильтра можно использовать селекторы в стиле Mongo (как в примере выше).

Подробное описание всех возможных селекторов:

Селектор Фильтр МойСклад Описание
key: { $eq: value } key=value равно
key: { $ne: value } key!=value не равно
key: { $gt: value } key>value больше
key: { $gte: value } key>=value больше или равно
key: { $lt: value } key<value меньше
key: { $lte: value } key<=value меньше или равно
key: { $st: value } key~=value начинается со строки
key: { $et: value } key=~value заканчивается строкой
key: { $contains: value } key~value содержит строку
key: { $in: [..] } или key: [..] key=value1;key=value2;... входит в
key: { $nin: [..] } key!=value1;key!=value2;... не входит в
key: { $exists: true } key!= наличие значения (не null)
key: { $exists: false } key= пустое значение (null)
key: { $all: [{..}, ..] } объединение условий
key: { $not: {..} } отрицание условия

На один ключ можно использовать несколько селекторов.

Подробнее с правилами фильтрации можно ознакомится в документации МойСклад:

order

Если поле order массив, то произойдет преобразование записи из формы массива в строку.

Примеры:

  • ['name']'name'
  • [['code','desc']]'code,desc'
  • ['name', ['code','desc']]'name;code,desc'
  • ['name,desc', ['code','asc'], ['moment']]'name,desc;code,asc;moment'

👉 examples/query.js

expand и limit

Если указано значение expand, но не указан limit, то в поле limit по умолчанию будет подставлено значение 100. Это важно, т.к. в версии API remap 1.2 expand не работает, если не указан limit.

options (параметры запроса)

Все опции переданные в объекте options (за исключением описанных ниже) передаются напрямую в опции метода fetch (Fetch API) при осуществлении запроса.

С опциями fetch API можно ознакомиться по этой ссылке

Опции специфичные для библиотеки moysklad (не передаются в fetch):

Поле Тип Описание
rawResponse boolean Если true, то метод вернет результат в виде объекта Response. Код и содержимое ответа не проверяется на ошибки.
rawRedirect boolean Если true и код запроса 3xx (редирект), то метод вернет Response. В обратном случае, будет выброшена ошибка MoyskladRequestError. Опция нужна для явного указания того, что вы ожидаете получить редирект при опции запроса redirect не равной follow.
muteApiErrors boolean Если true, то все ошибки API будут проигнорированы (метод не будет генерировать ошибку если код ответа сервера не в диапазоне 200-299 и тело ответа содержит описание ошибки МойСклад). Опция не затрагивает прочие ошибки, которые не возвращают JSON ответ.
muteCollectionErrors boolean Если true, то все ошибки внутри коллекций при массовом обновлении сущностей будут проигнорированы.
precision boolean Если true, то в запрос будет включен заголовок X-Lognex-Precision со значением true (отключение округления цен и себестоимости до копеек).
webHookDisable boolean Если true, то в запрос будет включен заголовок X-Lognex-WebHook-Disable со значением true (отключить уведомления вебхуков в контексте данного запроса).
downloadExpirationSeconds number Устанавливает значение для заголовока X-Lognex-Download-Expiration-Seconds (подробнее см. Ссылки на файлы)
Примеры
  • Формирование заполненного шаблона печатной формы и получение ссылки для загрузки (examples/download-print-form.js):

    const path = require('node:path')
    const { writeFile } = require('node:fs/promises')
    const { fetch } = require('undici')
    
    /** @type {import('..')} */
    const Moysklad = require('moysklad')
    
    const TEMPLATE_ID = '8a686b8a-9e4a-11e5-7a69-97110004af3e'
    const DEMAND_ID = '13abf361-e9c6-45ea-a940-df70289a7f95'
    
    async function downloadPrintForm() {
      const ms = Moysklad({ fetch })
    
      const body = {
        template: {
          meta: {
            href: ms.buildUrl(
              `entity/demand/metadata/customtemplate/${TEMPLATE_ID}`
            ),
            type: 'customtemplate',
            mediaType: 'application/json'
          }
        },
        extension: 'pdf'
      }
    
      /** @type {import('undici').Response} */
      const response = await ms.POST(
        `entity/demand/${DEMAND_ID}/export`,
        body,
        null,
        // вернуть результат запроса с редиректом без предварительного разбора
        { rawRedirect: true }
      )
    
      const location = response.headers.get('location')
    
      console.log(location)
      // 'https://print-prod.moysklad.ru/temp/.../00123.pdf'
    
      const formResponse = await fetch(location)
    
      // TODO undici возвращает веб-стримы (апгрейдится до 18-й ноды пока рановато)
    
      const blob = await formResponse.blob()
    
      const buffer = Buffer.from(await blob.arrayBuffer())
    
      await writeFile(path.join(process.cwd(), '__temp/form.pdf'), buffer)
    }
    
    downloadPrintForm()
  • Указание кастомного заголовка

    const ms = Moysklad({ fetch: require('node-fetch') })
    
    const folder = {
      meta: {
        type: 'productfolder',
        href: ms.buildUrl(`entity/productfolder/${FOLDER_ID}`)
      },
      description: 'Новое описание группы товаров'
    }
    
    // Указываем кастомный заголовок X-Lognex-WebHook-Disable для PUT запроса
    const updatedFolder = await ms.PUT(
      `entity/productfolder/${FOLDER_ID}`,
      folder,
      null,
      {
        // вместо этого можно использовать webHookDisable: true
        headers: {
          'X-Lognex-WebHook-Disable': true
        }
      }
    )
    
    assert.equal(updatedFolder.description, folder.description)
  • Автоматический редирект

    Идентификаторы товаров в приложении МойСклад отличаются от идентификаторов в API. Поэтому, при запросе товара по id из приложения, будет выполнен редирект на другой href.

    const ms = Moysklad({ fetch })
    
    // https://api.moysklad.ru/app/#good/edit?id=cb277549-34f4-4029-b9de-7b37e8e25a54
    const PRODUCT_UI_ID = 'cb277549-34f4-4029-b9de-7b37e8e25a54'
    
    // Error: 308 Permanent Redirect
    await ms.fetchUrl(ms.buildUrl(`entity/product/${PRODUCT_UI_ID}`))
    
    // Указана опция redirect
    const product = await ms.fetchUrl(
      ms.buildUrl(`entity/product/${PRODUCT_UI_ID}`),
      { redirect: 'follow' }
    )
    
    assert.ok(product) // OK

События

Событие Передаваемый объект Момент наступления
request { requestId, url, options } Отправлен http запрос
response { requestId, url, options, response } Получен ответ на запрос
response:body { requestId, url, options, response, body } Загружено тело ответа
error Error, { requestId } Ошибка при выполнении запроса

Пример использования:

const { fetch } = require('undici')
const { EventEmitter } = require('events')
const Moysklad = require('moysklad')

/** @type {Moysklad.MoyskladEmitter} */
const emitter = new EventEmitter()

const ms = Moysklad({ fetch, emitter })

emitter
  .on('request', ({ requestId, url, options }) => {
    console.log(`${requestId} ${options.method} ${url}`)
  })
  .on('error', (err, { requestId }) => {
    console.log(requestId, err)
  })

ms.GET('entity/customerorder', { limit: 1 }).then(res => {
  console.log('Order name: ' + res.rows[0].name)
})

Более подробный пример смотрите в examples/events.js.

Работа с ошибками

В рамках работы с библиотекой выделены следующие виды ошибок:

Название ошибки Класс ошибки Описание
1 Ошибка библиотеки MoyskladError Напр. не верно указаны параметры одного из методов.
2 Ошибка запроса MoyskladRequestError Ответ получен с кодом ошибки, тело ответа НЕ содержит JSON с описанием ошибки в формате МойСклад.
3 Ошибка API МойСклад MoyskladApiError Ответ получен с кодом ошибки, тело ответа содержит JSON с описанием ошибки в формате МойСклад.
4 Ошибка в коллекции MoyskladCollectionError Ошибка в одном из элементов внутри коллекции.
5 Неявный редирект MoyskladUnexpectedRedirectError Ошибка возникает когда запрос вернул перенапраление (код 3xx) и явно не указана опция запроса rawRedirect (опция redirect не равна follow)

Библиотека дает возможность указать параметры запроса muteApiErrors и muteCollectionErrors для игнорирования ошибок API п.3 и п.4 соответственно.

Ошибки глобального fetch модуля или переданного при инициализации экземпляра не перехватываются внутри библиотеки. Т.е. все описанные выше ошибки, связанные с выполнением запроса, формируются уже после анализа полученного ответа.

MoyskladError

Внутренняя ошибка библиотеки не связанная с выполнением запроса к API

Наследует класс Error

Примеры

Код с ошибкой:

await ms.GET('entity/product', {
  filter: 123
})

Структура ошибки:

{
  "name": "MoyskladError",
  "message": "Поле filter запроса должно быть строкой или объектом"
}

MoyskladRequestError

Ошибка при выполнении запроса

Наследует класс MoyskladError

Примеры

Код с ошибкой:

const ms = Moysklad({ fetch, api: 'foo', apiVersion: '0' })

await ms.GET('foo/bar')

Структура ошибки:

{
  "name": "MoyskladRequestError",
  "message": "404 Not Found",
  "url": "https://api.moysklad.ru/api/foo/0/foo/bar",
  "status": 404,
  "statusText": "Not Found"
}

MoyskladApiError

Ошибка API МойСклад

Наследует класс MoyskladRequestError

Ошибка формируется в случае, если API помимо HTTP кода ошибки, так же вернуло стандартное описание ошибки МойСклад в формате JSON. В обратном случае (ответ не содержит JSON с ошибкой) будет выброшена ошибка MoyskladRequestError

Примеры

Код с ошибкой:

await ms.GET('entity/product2')

Структура ошибки:

{
  "name": "MoyskladApiError",
  "message": "Неизвестный тип: 'product2' (https://dev.moysklad.ru/doc/api/remap/1.2/#error_1005)",
  "url": "https://api.moysklad.ru/api/remap/1.2/entity/product2",
  "status": 412,
  "statusText": "Precondition Failed",
  "code": 1005,
  "moreInfo": "https://dev.moysklad.ru/doc/api/remap/1.2/#error_1005",
  "errors": [
    {
      "error": "Неизвестный тип: 'product2'",
      "code": 1005,
      "moreInfo": "https://dev.moysklad.ru/doc/api/remap/1.2/#error_1005"
    }
  ]
}

Можно игнорировать ошибку API, указав muteApiErrors:true в опциях запроса.

const rawError1 = await ms.GET('entity/product2', null, {
  muteApiErrors: true
})

console.log(rawError1.errors[0].error)
// Неизвестный тип: 'product2'

MoyskladCollectionError

Ошибка в коллекции при массовом создании/изменении сущностей

Наследует класс MoyskladApiError

Ошибка выбрасывается когда возвращаемая коллекция содержит хотя бы одну ошибку.

Например, когда при массовом обновлении нескольких объектов часть из них не были обновлены, то API вернет массив с результатами в части которых будет указана ошибка.

Примеры Код с ошибкой:
await ms.POST('entity/product', [
  { foo: 'bar' },
  {
    meta: {
      type: 'product',
      href: ms.buildUrl(`entity/product/${uuidFromApi}`)
    },
    weight: 42
  },
  { name: 123 }
])

Структура ошибки:

{
  "name": "MoyskladCollectionError",
  "message": "Ошибка сохранения объекта: поле 'name' не может быть пустым или отсутствовать (https://dev.moysklad.ru/doc/api/remap/1.2/#error_3000)",
  "url": "https://api.moysklad.ru/api/remap/1.2/entity/product",
  "status": 400,
  "statusText": "Bad Request",
  "code": 3000,
  "moreInfo": "https://dev.moysklad.ru/doc/api/remap/1.2/#error_3000",
  "line": 1,
  "column": 3,
  "errors": [
    {
      "error": "Ошибка сохранения объекта: поле 'name' не может быть пустым или отсутствовать",
      "code": 3000,
      "parameter": "name",
      "moreInfo": "https://dev.moysklad.ru/doc/api/remap/1.2/#error_3000",
      "line": 1,
      "column": 3
    },
    {
      "error": "Ошибка формата: значение поля 'name' не соответствует типу строка",
      "code": 2016,
      "moreInfo": "https://dev.moysklad.ru/doc/api/remap/1.2/#error_2016",
      "line": 1,
      "column": 169
    }
  ],
  "errorsIndexes": [
    [
      0,
      [
        {
          "error": "Ошибка сохранения объекта: поле 'name' не может быть пустым или отсутствовать",
          "code": 3000,
          "parameter": "name",
          "moreInfo": "https://dev.moysklad.ru/doc/api/remap/1.2/#error_3000",
          "line": 1,
          "column": 3
        }
      ]
    ],
    [
      2,
      [
        {
          "error": "Ошибка формата: значение поля 'name' не соответствует типу строка",
          "code": 2016,
          "moreInfo": "https://dev.moysklad.ru/doc/api/remap/1.2/#error_2016",
          "line": 1,
          "column": 169
        }
      ]
    ]
  ]
}

Можно игнорировать ошибки в коллекции, указав muteCollectionErrors:true в опциях запроса.

const result2 = await ms.POST(
  'entity/product',
  [
    { foo: 'bar' },
    {
      meta: {
        type: 'product',
        href: ms.buildUrl(`entity/product/${uuidFromApi}`)
      },
      weight: 42
    },
    { name: 123 }
  ],
  null,
  {
    muteCollectionErrors: true
  }
)

const collItemError = result2.find(it => it.errors)

if (collItemError) {
  console.log(collItemError.errors[0].error)
  // Ошибка сохранения объекта: поле 'name' не может быть пустым или отсутствовать
}

MoyskladUnexpectedRedirectError

Ошибка если запрос вернул перенапраление (код 3xx), когда явно не указана опция запроса rawRedirect и опция redirect не равна follow

Наследует класс MoyskladRequestError

Примеры
/** id товара из приложения МойСклад */
const uuidFromApp = 'cb277549-34f4-4029-b9de-7b37e8e25a54'

/** id товара из API (отличается от id из приложения) */
let uuidFromApi

const getProduct = id => ms.GET(`entity/product/${id}`)

try {
  await getProduct(uuidFromApp)
} catch (err) {
  if (err instanceof Moysklad.MoyskladUnexpectedRedirectError) {
    uuidFromApi = ms.parseUrl(err.location).path.pop()
    await getProduct(uuidFromApi)
  } else {
    throw err
  }
}

Можно обработать перенаправление без перехвата ошибки:

const { fetch, Response } = require('undici')

const ms = Moysklad({ fetch })

let product = await ms.GET(`entity/product/${uuidFromApp}`, null, {
  rawRedirect: true
})

if (product instanceof Response) {
  uuidFromApi = ms.parseUrl(product.headers.get('location')).path.pop()

  product = await ms.GET(`entity/product/${uuidFromApi}`)
}

console.log(product.id === uuidFromApp) // false

Или использовать автоматическое пренаправление, указав значение follow в опции redirect:

const product = await ms.GET(`entity/product/${uuidFromApp}`, null, {
  redirect: 'follow'
})

console.log(product.id === uuidFromApp) // false

История изменений

CHANGELOG.md

Планы развития

Планируется немного переработанная версия библиотеки в другом репозитории и npm пакете. Без концептуальных изменений, но с убранным легаси кодом.

  • Переписать на TypeScript
  • Добавить новый метод для формирования объекта запроса
  • Убрать всё легаси (в том числе то, что тянет лишние зависимости - "have2" и "stampit")
  • Более развернутая документация с автогенерацией части описаний методов

TODO

Свалка мыслей по развитию библиотеки - TODO.md

moysklad's People

Contributors

alexander-mart avatar wmakeev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

moysklad's Issues

В отчете Прибыльность не работает фильтрация по дате (json API 1.2)

Запрос

momentFrom = '2021-01-01 00:00:00'
momentTo = '2021-01-31 23:59:59'

url = f'https://online.moysklad.ru/api/remap/1.2/report/profit/byproduct?limit=1000&filter=momentFrom={momentFrom}&momentTo={momentTo}'
requests.get(url, auth=auth, headers=headers).json()

Возвращает:
{'errors': [{'error': "Ошибка фильтрации: Неизвестное поле фильтрации 'momentFrom'",
'code': 1034, 'moreInfo': 'https://dev.moysklad.ru/doc/api/remap/1.2/#error_1034'}]}

В API 1.1 такой же запрос работает и возвращает первые 100 позиций отчёта (т.е. максимальный limit равен 100).

Есть ли генератор meta на основе entity_path и entity_id?

В модуле есть методы buildFilter и buildQuery. Есть ли в модуле что-то вроде этого?

Либо такой метод не нужен, т. к. для построения тела запроса есть какие-то другие инструменты самого МС API 🤔

export const build_entity_meta = (api_endpoint, entity_name, entity_uuid) => ({
  href        : `${api_endpoint}/entity/${entity_name}/${entity_uuid}`,
  metadataHref: `${api_endpoint}/entity/${entity_name}/metadata`,
  type        : entity_name,
  mediaType   : 'application/json'
})

Параметр для отключения вебхука в контексте запроса

Большое спасибо за библиотеку :)

Из апи 1.1:

ЗАГОЛОВОК ВРЕМЕННОГО ОТКЛЮЧЕНИЯ ЧЕРЕЗ API 
Через JSON API или POS API при запросах можно отключить уведомления вебхуков в контексте данного запроса.
Для этого нужно указать заголовок X-Lognex-WebHook-Disable с произвольным значением.

Требуется такой параметр для функций ms.GET/POST/... для передачи в запросе данного заголовка (X-Lognex-WebHook-Disable с произвольным значением).

Могу и сам сделать пр с такими правками, вопрос как лучше передавать такое в функции. Например, ms.GET("...", { ..., disableWebhook: true }

P.S. нашёл способ передать этот заголовок: ms.PUT("entity/productFolder/" + id, category, null, { headers: { "X-Lognex-WebHook-Disable" : "1" } });. Может, теперь и не очень актуально, но всё равно документированная возможность была бы полезна

Разделить на модули

Еще больше разделить библиотеку на модули. К примеру, часто не требуется реализация слоя запросов к серверу, а логика построения url запроса нужна.

Скачивание файлов из доп полей товара

Добрый день!
Очень полезная библиотека, много времени сэкономил, спасибо большое что занимаетесь развитием этого дела!
Не могли бы вы помочь, не могу разобраться как формировать ссылку на скачивание файла который лежит в доп поле товара

product = {
...params,
 attributes: [

        {
            "id" : "3333",
            "name" : "Документация 1",
            "value" : "ремонт"
        },
  ]
}

Спасибо!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.