Search for a command to run...
На этой странице собраны обработчики для частых задач обмена с 1С. Код примеров взят из тестов плагина, которые проверяют каждый из них на настоящем обмене. Описание входных данных и результата каждого хука находится на странице Справочник хуков.
Каждый пример регистрирует свой обработчик, а Medusa разрешает только один обработчик на хук. Чтобы применить несколько примеров к одному хуку, объедините их, как в разделе Несколько правок в одном обработчике.
В 1С услуги ведутся в той же номенклатуре, что и товары, но в магазине им не место, потому что у них нет ни остатка, ни карточки. Обработчик убирает из пачки товары, у которых реквизит «ВидНоменклатуры» равен «Услуга». Запросов к базе нет.
src/workflows/hooks/onec-catalog.ts1import { StepResponse } from "@medusajs/framework/workflows-sdk"2import { importOnecProductsWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"3import { readRequisite } from "@gorgo-store/medusa-erp-1c/hooks"45importOnecProductsWorkflow.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.ts1import { 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"45importOnecProductsWorkflow.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, "Материал")1112 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.ts1import { StepResponse } from "@medusajs/framework/workflows-sdk"2import { importOnecProductsWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"34importOnecProductsWorkflow.hooks.setProducts(async ({ products }) => {5 return new StepResponse({6 products: products.map((item) => {7 const images = item.product.images ?? item.existing?.images ?? []89 return images.length ? item : { ...item, product: { ...item.product, status: "draft" } }10 }),11 })12})
Обмен публикует товар на каждом импорте, так что товар сам станет опубликованным в обмене, где 1С пришлёт ему картинку. Обратная сторона в том, что товар без картинок, опубликованный вручную, следующий обмен снова переведёт в черновик.
Название и описание товара часто правят в Medusa Admin, а следующий обмен вернул бы значения из 1С. Обработчик удаляет эти ключи у существующих товаров, и импорт оставляет поля как есть. Запросов к базе нет.
src/workflows/hooks/onec-catalog.ts1import { StepResponse } from "@medusajs/framework/workflows-sdk"2import { importOnecProductsWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"34importOnecProductsWorkflow.hooks.setProducts(async ({ products }) => {5 return new StepResponse({6 products: products.map((item) => {7 if (!item.existing) {8 return item9 }1011 const { title: _title, description: _description, ...product } = item.product1213 return { ...item, product }14 }),15 })16})
Новый товар получает название и описание из 1С, потому что без названия его не создать. После этого правки названия и описания в 1С до магазина не доходят.
Бренд товара 1С хранит свойством «Бренд», а в Medusa его удобно держать коллекцией. Обработчик ищет коллекции с названиями брендов пачки и ставит товару найденную. Один запрос к базе на пачку.
src/workflows/hooks/onec-catalog.ts1import { 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"56importOnecProductsWorkflow.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)))]910 if (!titles.length) {11 return new StepResponse({ products })12 }1314 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]))2122 return new StepResponse({23 products: products.map((item) => {24 const collectionId = collectionIdByTitle.get(brandByOnecId.get(item.source.id) ?? "")2526 return collectionId ? { ...item, product: { ...item.product, collection_id: collectionId } } : item27 }),28 })29})
Коллекции обработчик только ищет, а заводите их вы, в Medusa Admin. Товар, для бренда которого коллекции нет, остаётся без изменений. Создавать коллекции из обработчика можно, но тогда ему нужна функция отката. Её описывают правила для обработчиков.
Тип цен «Оптовая цена» должен действовать только для покупателей из группы «Опт». Обработчик включает его прайс-лист и ставит ему правило группы, так что гость по-прежнему видит розничную цену. Тип прайс-листа , чтобы покупатель из группы платил ровно цену из 1С, а не меньшую из оптовой и розничной. Один запрос к базе на пачку, и только когда в пачке есть этот тип цен.
src/workflows/hooks/onec-prices.ts1import { StepResponse } from "@medusajs/framework/workflows-sdk"2import { ContainerRegistrationKeys, PriceListStatus, PriceListType } from "@medusajs/framework/utils"3import { importOnecPricesWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"45const WHOLESALE_PRICE_TYPE_NAME = "Оптовая цена"6const WHOLESALE_GROUP_NAME = "Опт"78importOnecPricesWorkflow.hooks.setPriceLists(async ({ priceLists }, { container }) => {9 if (!priceLists.some(({ source }) => source.name === WHOLESALE_PRICE_TYPE_NAME)) {10 return new StepResponse({ priceLists })11 }1213 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]?.id2021 if (!groupId) {22 return new StepResponse({ priceLists })23 }2425 return new StepResponse({26 priceLists: priceLists.map((item) =>27 item.source.name === WHOLESALE_PRICE_TYPE_NAME28 ? {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.ts1import { StepResponse } from "@medusajs/framework/workflows-sdk"2import { exportOnecOrdersWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"34exportOnecOrdersWorkflow.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.ts1import { StepResponse } from "@medusajs/framework/workflows-sdk"2import { exportOnecOrdersWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"34exportOnecOrdersWorkflow.hooks.setOrders(async ({ orders }) => {5 return new StepResponse({6 orders: orders.map(({ source, document }) => {7 const comment = source.metadata?.comment89 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.ts1import { StepResponse } from "@medusajs/framework/workflows-sdk"2import { ContainerRegistrationKeys } from "@medusajs/framework/utils"3import { exportOnecOrdersWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"45const COMPANY_GROUP_NAME = "Юрлица"67exportOnecOrdersWorkflow.hooks.setOrders(async ({ orders }, { container }) => {8 if (!orders.length) {9 return new StepResponse({ orders })10 }1112 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]))1920 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]?.name2627 return {28 source,29 document: {30 ...document,31 counterparties: document.counterparties.map((counterparty) =>32 isCompany && inn ? { ...counterparty, inn, ...(kpp ? { kpp } : {}) } : counterparty,33 ),34 requisites: delivery35 ? [...document.requisites, { name: "Способ доставки", value: delivery }]36 : document.requisites,37 },38 }39 }),40 })41})
Запрос идёт через связь заказа с покупателем, поэтому в базе это три SQL-запроса, но на всю выгрузку, а не на заказ. ИНН и КПП плагин выводит после имени контрагента и перед адресом. На настоящей конфигурации 1С это место ещё не проверялось, поэтому убедитесь, что ваша читает их оттуда.
Плагин пишет в корень файла , а старая конфигурация 1С может ждать свою версию, например 2.05. В модели товара и документа версии схемы нет, поэтому обработчик правит готовый XML через , у выгрузки каталога и у выгрузки заказов. Запросов к базе нет.
src/workflows/hooks/onec-payload.ts1import { StepResponse } from "@medusajs/framework/workflows-sdk"2import { exportOnecCatalogWorkflow, exportOnecOrdersWorkflow } from "@gorgo-store/medusa-erp-1c/workflows"34const OLD_SCHEMA_VERSION = "2.05"56function withSchemaVersion(payload: string): string {7 return payload.replace(/ВерсияСхемы="[^"]*"/, `ВерсияСхемы="${OLD_SCHEMA_VERSION}"`)8}910exportOnecCatalogWorkflow.hooks.setPayload(async ({ payload }) => {11 return new StepResponse({ payload: withSchemaVersion(payload) })12})1314exportOnecOrdersWorkflow.hooks.setPayload(async ({ payload }) => {15 return new StepResponse({ payload: withSchemaVersion(payload) })16})
Обработчик меняет только номер версии, а устройство файла остаётся тем, что пишет плагин. Если ваша конфигурация отличается и устройством, правьте XML в том же обработчике. Правка строки ничего не знает об устройстве XML, поэтому всё, что выражается через , надёжнее делать там.
Medusa разрешает один обработчик на хук, поэтому правки проекта для одного хука собираются в одном обработчике. Удобно держать каждую правку отдельной функцией и вызывать их по очереди. Отбор ставьте первым, чтобы остальные правки не тратили время на убранные элементы.
src/workflows/hooks/onec-catalog.ts1import { 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"45function skipServices(products: ProductToImport[]): ProductToImport[] {6 return products.filter(({ source }) => readRequisite(source, "ВидНоменклатуры") !== "Услуга")7}89function keepManagerEdits(products: ProductToImport[]): ProductToImport[] {10 return products.map((item) => {11 if (!item.existing) {12 return item13 }1415 const { title: _title, description: _description, ...product } = item.product1617 return { ...item, product }18 })19}2021importOnecProductsWorkflow.hooks.setProducts(async ({ products }) => {22 return new StepResponse({ products: keepManagerEdits(skipServices(products)) })23})
Функции, которым нужна база, делайте асинхронными и вызывайте через . Тогда у каждой по-прежнему один запрос на пачку.