Command Palette

Search for a command to run...

Примеры хуков 1С:Предприятие

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

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


Не импортировать услуги

В 1С услуги ведутся в той же номенклатуре, что и товары, но в магазине им не место, потому что у них нет ни остатка, ни карточки. Обработчик убирает из пачки товары, у которых реквизит «ВидНоменклатуры» равен «Услуга». Запросов к базе нет.

src/workflows/hooks/onec-catalog.ts
1import { StepResponse } from "@medusajs/framework/workflows-sdk"
2import { importOnecProductsWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"
3import { readRequisite } from "@gorgo-store/medusa-erp-1c/hooks"
4
5importOnecProductsWorkflow.hooks.setProducts(async ({ products }) => {
6 return new StepResponse({
7 products: products.filter(({ source }) => readRequisite(source, "ВидНоменклатуры") !== "Услуга"),
8 })
9})

Убранная услуга не создаётся в Medusa. Если она попала в магазин раньше, обработчик её не удалит, потому что убранный товар импорт не трогает вовсе.


Реквизиты и свойства в поля товара

Полное наименование, вес и материал 1С хранит в реквизитах и свойствах товара, а у товара Medusa для них есть поля , и . Обмен их не заполняет, и обработчик переносит значения сам. Запросов к базе нет.

src/workflows/hooks/onec-catalog.ts
1import { StepResponse } from "@medusajs/framework/workflows-sdk"
2import { importOnecProductsWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"
3import { readProperty, readRequisite } from "@gorgo-store/medusa-erp-1c/hooks"
4
5importOnecProductsWorkflow.hooks.setProducts(async ({ products, properties }) => {
6 return new StepResponse({
7 products: products.map(({ source, existing, product }) => {
8 const fullName = readRequisite(source, "Полное наименование")
9 const weight = Number.parseFloat((readRequisite(source, "Вес") ?? "").replace(",", "."))
10 const [material] = readProperty(source, properties, "Материал")
11
12 return {
13 source,
14 existing,
15 product: {
16 ...product,
17 ...(fullName ? { subtitle: fullName } : {}),
18 ...(Number.isFinite(weight) ? { weight } : {}),
19 ...(material ? { material } : {}),
20 },
21 }
22 }),
23 })
24})

Поле попадает в товар, только когда 1С прислала значение. Ключ со значением импорт счёл бы изменившимся и стёр бы значение, заданное в Medusa Admin. Вес 1С может прислать с десятичной запятой, поэтому перед разбором она заменяется точкой.


Черновик без картинок

Товар без единой картинки выглядит на витрине пустой карточкой. Обработчик оставляет такой товар черновиком, пока картинка не появится. Запросов к базе нет.

src/workflows/hooks/onec-catalog.ts
1import { StepResponse } from "@medusajs/framework/workflows-sdk"
2import { importOnecProductsWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"
3
4importOnecProductsWorkflow.hooks.setProducts(async ({ products }) => {
5 return new StepResponse({
6 products: products.map((item) => {
7 const images = item.product.images ?? item.existing?.images ?? []
8
9 return images.length ? item : { ...item, product: { ...item.product, status: "draft" } }
10 }),
11 })
12})

Обмен публикует товар на каждом импорте, так что товар сам станет опубликованным в обмене, где 1С пришлёт ему картинку. Обратная сторона в том, что товар без картинок, опубликованный вручную, следующий обмен снова переведёт в черновик.


Не перезаписывать правки из Medusa Admin

Название и описание товара часто правят в Medusa Admin, а следующий обмен вернул бы значения из 1С. Обработчик удаляет эти ключи у существующих товаров, и импорт оставляет поля как есть. Запросов к базе нет.

src/workflows/hooks/onec-catalog.ts
1import { StepResponse } from "@medusajs/framework/workflows-sdk"
2import { importOnecProductsWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"
3
4importOnecProductsWorkflow.hooks.setProducts(async ({ products }) => {
5 return new StepResponse({
6 products: products.map((item) => {
7 if (!item.existing) {
8 return item
9 }
10
11 const { title: _title, description: _description, ...product } = item.product
12
13 return { ...item, product }
14 }),
15 })
16})

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


Бренд в коллекцию

Бренд товара 1С хранит свойством «Бренд», а в Medusa его удобно держать коллекцией. Обработчик ищет коллекции с названиями брендов пачки и ставит товару найденную. Один запрос к базе на пачку.

src/workflows/hooks/onec-catalog.ts
1import { StepResponse } from "@medusajs/framework/workflows-sdk"
2import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
3import { importOnecProductsWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"
4import { readProperty } from "@gorgo-store/medusa-erp-1c/hooks"
5
6importOnecProductsWorkflow.hooks.setProducts(async ({ products, properties }, { container }) => {
7 const brandByOnecId = new Map(products.map(({ source }) => [source.id, readProperty(source, properties, "Бренд")[0]]))
8 const titles = [...new Set([...brandByOnecId.values()].filter((title): title is string => Boolean(title)))]
9
10 if (!titles.length) {
11 return new StepResponse({ products })
12 }
13
14 const query = container.resolve(ContainerRegistrationKeys.QUERY)
15 const { data: collections } = await query.graph({
16 entity: "product_collection",
17 fields: ["id", "title"],
18 filters: { title: titles },
19 })
20 const collectionIdByTitle = new Map(collections.map((collection) => [collection.title, collection.id]))
21
22 return new StepResponse({
23 products: products.map((item) => {
24 const collectionId = collectionIdByTitle.get(brandByOnecId.get(item.source.id) ?? "")
25
26 return collectionId ? { ...item, product: { ...item.product, collection_id: collectionId } } : item
27 }),
28 })
29})

Коллекции обработчик только ищет, а заводите их вы, в Medusa Admin. Товар, для бренда которого коллекции нет, остаётся без изменений. Создавать коллекции из обработчика можно, но тогда ему нужна функция отката. Её описывают правила для обработчиков.


Оптовая цена для группы покупателей

Тип цен «Оптовая цена» должен действовать только для покупателей из группы «Опт». Обработчик включает его прайс-лист и ставит ему правило группы, так что гость по-прежнему видит розничную цену. Тип прайс-листа , чтобы покупатель из группы платил ровно цену из 1С, а не меньшую из оптовой и розничной. Один запрос к базе на пачку, и только когда в пачке есть этот тип цен.

src/workflows/hooks/onec-prices.ts
1import { StepResponse } from "@medusajs/framework/workflows-sdk"
2import { ContainerRegistrationKeys, PriceListStatus, PriceListType } from "@medusajs/framework/utils"
3import { importOnecPricesWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"
4
5const WHOLESALE_PRICE_TYPE_NAME = "Оптовая цена"
6const WHOLESALE_GROUP_NAME = "Опт"
7
8importOnecPricesWorkflow.hooks.setPriceLists(async ({ priceLists }, { container }) => {
9 if (!priceLists.some(({ source }) => source.name === WHOLESALE_PRICE_TYPE_NAME)) {
10 return new StepResponse({ priceLists })
11 }
12
13 const query = container.resolve(ContainerRegistrationKeys.QUERY)
14 const { data: groups } = await query.graph({
15 entity: "customer_group",
16 fields: ["id"],
17 filters: { name: WHOLESALE_GROUP_NAME },
18 })
19 const groupId = groups[0]?.id
20
21 if (!groupId) {
22 return new StepResponse({ priceLists })
23 }
24
25 return new StepResponse({
26 priceLists: priceLists.map((item) =>
27 item.source.name === WHOLESALE_PRICE_TYPE_NAME
28 ? {
29 ...item,
30 priceList: {
31 ...item.priceList,
32 status: PriceListStatus.ACTIVE,
33 type: PriceListType.OVERRIDE,
34 rules: { "customer.groups.id": [groupId] },
35 },
36 }
37 : item,
38 ),
39 })
40})

Группу «Опт» заведите в Medusa Admin заранее. Без неё прайс-лист остаётся черновиком, как без обработчика. Статус, тип и правила этого прайс-листа обработчик ставит на каждом обмене, поэтому их изменения в Medusa Admin следующий обмен вернёт.


Не выгружать тестовые заказы

Заказы, которые команда оформляет для проверки витрины, не должны попадать в 1С. Обработчик убирает из выгрузки заказы с адресов . Запросов к базе нет.

src/workflows/hooks/onec-orders.ts
1import { StepResponse } from "@medusajs/framework/workflows-sdk"
2import { exportOnecOrdersWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"
3
4exportOnecOrdersWorkflow.hooks.setOrders(async ({ orders }) => {
5 return new StepResponse({
6 orders: orders.filter(({ source }) => !source.email?.endsWith("@test.invalid")),
7 })
8})

Убранный заказ не получает отметку о выгрузке. Он попадает в хук при каждой выгрузке, и обработчик убирает его снова.


Источник заказа и комментарий покупателя

Менеджеру в 1С полезно видеть, откуда пришёл заказ и что написал покупатель. Отдельного поля для комментария у заказа Medusa нет, поэтому витрина записывает его в . Обработчик переносит комментарий в документ и добавляет реквизит «Источник». Запросов к базе нет.

src/workflows/hooks/onec-orders.ts
1import { StepResponse } from "@medusajs/framework/workflows-sdk"
2import { exportOnecOrdersWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"
3
4exportOnecOrdersWorkflow.hooks.setOrders(async ({ orders }) => {
5 return new StepResponse({
6 orders: orders.map(({ source, document }) => {
7 const comment = source.metadata?.comment
8
9 return {
10 source,
11 document: {
12 ...document,
13 ...(typeof comment === "string" && comment ? { comment } : {}),
14 requisites: [...document.requisites, { name: "Источник", value: "Интернет-магазин" }],
15 },
16 }
17 }),
18 })
19})

Сохранить комментарий в заказа должна витрина. Реквизит «Источник» попадёт в документ, а что с ним сделает 1С, зависит от вашей конфигурации.


Контрагент-юрлицо и способ доставки

Покупатель из группы «Юрлица» должен уйти в 1С контрагентом с ИНН и КПП, а менеджеру нужен способ доставки. ИНН и КПП хранятся в покупателя, способ доставки в заказе, и обработчик получает всё это одним запросом на всю выгрузку.

src/workflows/hooks/onec-orders.ts
1import { StepResponse } from "@medusajs/framework/workflows-sdk"
2import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
3import { exportOnecOrdersWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"
4
5const COMPANY_GROUP_NAME = "Юрлица"
6
7exportOnecOrdersWorkflow.hooks.setOrders(async ({ orders }, { container }) => {
8 if (!orders.length) {
9 return new StepResponse({ orders })
10 }
11
12 const query = container.resolve(ContainerRegistrationKeys.QUERY)
13 const { data } = await query.graph({
14 entity: "order",
15 fields: ["id", "customer.metadata", "customer.groups.name", "shipping_methods.name"],
16 filters: { id: orders.map(({ source }) => source.id) },
17 })
18 const extraById = new Map(data.map((order) => [order.id, order]))
19
20 return new StepResponse({
21 orders: orders.map(({ source, document }) => {
22 const extra = extraById.get(source.id)
23 const isCompany = extra?.customer?.groups?.some((group: { name?: string } | null) => group?.name === COMPANY_GROUP_NAME)
24 const { inn, kpp } = (extra?.customer?.metadata ?? {}) as { inn?: string; kpp?: string }
25 const delivery = extra?.shipping_methods?.[0]?.name
26
27 return {
28 source,
29 document: {
30 ...document,
31 counterparties: document.counterparties.map((counterparty) =>
32 isCompany && inn ? { ...counterparty, inn, ...(kpp ? { kpp } : {}) } : counterparty,
33 ),
34 requisites: delivery
35 ? [...document.requisites, { name: "Способ доставки", value: delivery }]
36 : document.requisites,
37 },
38 }
39 }),
40 })
41})

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


Версия схемы для старой конфигурации 1С

Плагин пишет в корень файла , а старая конфигурация 1С может ждать свою версию, например 2.05. В модели товара и документа версии схемы нет, поэтому обработчик правит готовый XML через , у выгрузки каталога и у выгрузки заказов. Запросов к базе нет.

src/workflows/hooks/onec-payload.ts
1import { StepResponse } from "@medusajs/framework/workflows-sdk"
2import { exportOnecCatalogWorkflow, exportOnecOrdersWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"
3
4const OLD_SCHEMA_VERSION = "2.05"
5
6function withSchemaVersion(payload: string): string {
7 return payload.replace(/ВерсияСхемы="[^"]*"/, `ВерсияСхемы="${OLD_SCHEMA_VERSION}"`)
8}
9
10exportOnecCatalogWorkflow.hooks.setPayload(async ({ payload }) => {
11 return new StepResponse({ payload: withSchemaVersion(payload) })
12})
13
14exportOnecOrdersWorkflow.hooks.setPayload(async ({ payload }) => {
15 return new StepResponse({ payload: withSchemaVersion(payload) })
16})

Обработчик меняет только номер версии, а устройство файла остаётся тем, что пишет плагин. Если ваша конфигурация отличается и устройством, правьте XML в том же обработчике. Правка строки ничего не знает об устройстве XML, поэтому всё, что выражается через , надёжнее делать там.


Несколько правок в одном обработчике

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

src/workflows/hooks/onec-catalog.ts
1import { StepResponse } from "@medusajs/framework/workflows-sdk"
2import { importOnecProductsWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"
3import { readRequisite, type ProductToImport } from "@gorgo-store/medusa-erp-1c/hooks"
4
5function skipServices(products: ProductToImport[]): ProductToImport[] {
6 return products.filter(({ source }) => readRequisite(source, "ВидНоменклатуры") !== "Услуга")
7}
8
9function keepManagerEdits(products: ProductToImport[]): ProductToImport[] {
10 return products.map((item) => {
11 if (!item.existing) {
12 return item
13 }
14
15 const { title: _title, description: _description, ...product } = item.product
16
17 return { ...item, product }
18 })
19}
20
21importOnecProductsWorkflow.hooks.setProducts(async ({ products }) => {
22 return new StepResponse({ products: keepManagerEdits(skipServices(products)) })
23})

Функции, которым нужна база, делайте асинхронными и вызывайте через . Тогда у каждой по-прежнему один запрос на пачку.


Материалы

Изменено 18 октября 2026 г.·Редактировать страницу