Інтеграція Хорошоп
магазин Урожай
Користувач
Останнє оновлення:
9/1/2026 5:30:00 PM
119
<ArrayOfDocument address="urozhaj.com.ua" docFormatDescription="https://github.com/amukan/AndriyCo.Shopdesk.Containers.git" hash="/puFEUxhy23WmbCTLKsOYpdJG30=" originalFileName="7386.tcudoc" software="Shopserver Horoshop import" wpGuid="278763b6-c879-4a84-9bae-6b8930424df0" wpName="Урожай">
<Document>
<AdditionalInfo>Імпорт доставленого замовлення Хорошоп WebSiteId=5</AdditionalInfo>
<AgentId>0</AgentId>
<Amount>440.00</Amount>
<AmountPaid>0.00</AmountPaid>
<Bias>0</Bias>
<BonusFranch>0.00</BonusFranch>
<BonusOther>0.00</BonusOther>
<BonusPaid>0.00</BonusPaid>
<BonusPaymentRecordId>0</BonusPaymentRecordId>
<BotCustomerAuthenticationSessionId>0</BotCustomerAuthenticationSessionId>
<ContractorId>1170</ContractorId>
<ContractorName>Роздрібний покупець оплата при отриманні</ContractorName>
<CurrencyId>0</CurrencyId>
<CurrencyRate>0</CurrencyRate>
<DateOfApprove>2026-08-31 10:56:58</DateOfApprove>
<DateOfCreate>2026-08-31 10:56:58</DateOfCreate>
<DeliveryPointId>0</DeliveryPointId>
<DepartmentId>1062</DepartmentId>
<DepartmentName>1Склад для ринку та дрогобича</DepartmentName>
<Detail>
<DocumentDetail>
<AmountToPay>440.00</AmountToPay>
<BarcodeId>0</BarcodeId>
<BonusGeneratePercent>0</BonusGeneratePercent>
<BonusSum>0.00</BonusSum>
<CalculatedBonus>0.00</CalculatedBonus>
<CurrentQuantity>0.000</CurrentQuantity>
<Discount>0.00</Discount>
<Discounts></Discounts>
<DocumentDetailGuid>{3c64d7c5-e1db-45e2-8bb6-3cd61e12fe0c}</DocumentDetailGuid>
<DocumentId>0</DocumentId>
<FranchGoodId>847</FranchGoodId>
<GoodId>847</GoodId>
<GoodsCategoryId>17</GoodsCategoryId>
<GoodsItemName>Родентицид Щелкунчик зерно 315г</GoodsItemName>
<GoodsItemType>1</GoodsItemType>
<GoodsUomId>2</GoodsUomId>
<GoodsUomName>шт</GoodsUomName>
<Id>0</Id>
<InventoryRecordDate>2026-08-29 17:00:35</InventoryRecordDate>
<InventoryRecordId>14679</InventoryRecordId>
<MaxAllowedDiscountPercent>0</MaxAllowedDiscountPercent>
<MaxAllowedPrice>0.00</MaxAllowedPrice>
<MinAllowedPrice>22.00</MinAllowedPrice>
<MoneySum>440.00</MoneySum>
<PrimaryPrice>22</PrimaryPrice>
<PurchasePrice>0</PurchasePrice>
<Quantity>20.000</Quantity>
<QuantityDifference>0.000</QuantityDifference>
<QuantityInPack>0.000</QuantityInPack>
<QuantityPack>0.000</QuantityPack>
<SalePrice>22</SalePrice>
<SalePriceAfterRevaluation>0.00</SalePriceAfterRevaluation>
<SourceDocumentDetailGuid p5:nil="true" xmlns:p5="http://www.w3.org/2001/XMLSchema-instance"></SourceDocumentDetailGuid>
<TopDocumentDetailGuid p5:nil="true" xmlns:p5="http://www.w3.org/2001/XMLSchema-instance"></TopDocumentDetailGuid>
<TopGoodId>0</TopGoodId>
<TaxGroupId p5:nil="true" xmlns:p5="http://www.w3.org/2001/XMLSchema-instance"></TaxGroupId>
</DocumentDetail>
</Detail>
<DocumentGuid>{cf51ce23-04c5-4321-bfe2-21e035af6c4a}</DocumentGuid>
<DocumentNumber>7386</DocumentNumber>
<DocumentType>1</DocumentType>
<EditPermissions>0</EditPermissions>
<FiscalRegisterId>0</FiscalRegisterId>
<FranchiseContractorId>0</FranchiseContractorId>
<FranchiseeId>0</FranchiseeId>
<GiftCertificateSumma>0.00</GiftCertificateSumma>
<Id>7386</Id>
<IsFiscal>0</IsFiscal>
<NumberOfUtensils>0</NumberOfUtensils>
<PaymentMethod>0</PaymentMethod>
<PromoCodes></PromoCodes>
<RoundingSum>0.00</RoundingSum>
<SaleChannel>2</SaleChannel>
<SessionDocumentNumber>7386</SessionDocumentNumber>
<SourceDocumentGuid p3:nil="true" xmlns:p3="http://www.w3.org/2001/XMLSchema-instance"></SourceDocumentGuid>
<SourceDocumentId>0</SourceDocumentId>
<Status>1</Status>
<SupportingDocument>Хорошоп order_id 7386</SupportingDocument>
<TimeToIssue p3:nil="true" xmlns:p3="http://www.w3.org/2001/XMLSchema-instance"></TimeToIssue>
<TopDocumentGuid p3:nil="true" xmlns:p3="http://www.w3.org/2001/XMLSchema-instance"></TopDocumentGuid>
<TopDocumentId>0</TopDocumentId>
<TransactionTypeId>0</TransactionTypeId>
<UserId>0</UserId>
</Document>
</ArrayOfDocument>
Ось завантажене замовлення на сервері, але основне воно завантажується коли вже виконане, в тцу не відображається Коментарів (14)
Andriy Kravchenko
Admin, Writer, File Uploader
8/31/2026 9:55:45 PM
Перше, що нам треба вирішити, як нам забирати цей документ з сайту Хорошопа - як замовлення від покупця, чи як видаткову накладну. Я вважаю, що все ж таки, як замовлення від покупця, бо це й документ відображає саме подію замовлення. Видаткова накладна повинна створюватись на подію вже фізичного відвантаження товару покупцю.
В TCU документ не з'явився, бо намагався імпортуватись, як видаткова накладна, і для сайту Хорошоп я не зробив прив'язку до статті руху.
Чому документ не проімпортований до TCU легко побачити в переліку документів, або в загальному, або в обліковому записі сайту Хорошопа на відповідній вкладинці. Зараз займусь виправленням цих речей. Тема нова, лише розробляється, тому можуть бути певні проблеми.

магазин Урожай
Користувач
8/31/2026 10:35:21 PM
Andriy Kravchenko
Admin, Writer, File Uploader
9/1/2026 3:09:16 AM
Опис вивантажується. Надайте приклад товару, який є на шопсервері, і по якому повинен вивантажитись опис, а його на хорошопі немає. Я відслюдкую весь ланцюг передачі даних.
Кількість передається.
"
але тут виникає проблема автоматизації, якщо товар замовили, кількість не спишеться з залишку поки не буде проведена видаткова накладна (так що можливий повторний продаж, товару якого не має)"
З кількістю як раз все складно. Бо маємо розсинхронізацію в часі.
Скажіть, чи увімкнутий у вас зараз на хорошопі "
Облік залишків"?
магазин Урожай
Користувач
9/1/2026 10:47:14 AM
описи є на хорошопі, а в тцу не має описів.
магазин Урожай
Користувач
9/1/2026 10:49:59 AM
мені краще щоб не вивантажувались описи, або не замінялись, я використовував тцу як програму обліку, достатньо було залишків і цін
магазин Урожай
Користувач
9/1/2026 11:05:20 AM
включив облік залишків запустив інтеграцію, кількість не підтягнулась, описи товарів в хорошоп пропали
Andriy Kravchenko
Admin, Writer, File Uploader
9/1/2026 3:57:04 PM
Я додав параметр "Вивантажувати опис товарів", також додав прив'язку документів до статей руху при імпорті
Що стосується оновлення кількостей.
Це проблема. Вона полягає в тому, що ви можете продавати одночасно товар в магазині через касу, і через сайт. Також у вас може відбутись прихід в обліковій системі, і буде оновлено залишок на хорошопі без урахування його продажів.
Наприклад
Було 10 штук
На сайті продали 3, і сайт зараз відображає 7.
В той же час, облікова система ще бачить 10 штук, і відбувається прихід 5 штук. Облікова система в цей момент відображає 15 штук.
Оновлені залишки приходять з облікової системи до касового сервера і проходять далі до хорошопа, і відображається 15 штук, хоча повинно бути 12.
Поки що не проглядається простий механізм узгодження залишків, які змінюються одночасно в декількох системах.
магазин Урожай
Користувач
9/1/2026 4:11:42 PM
потрібно щоб шопсервер підтягував нові замовлення з сайту, а не завершені на сайті, імпорт завершених замовлень в облікову систему не має сенсу, так як я веду облік по складі, відповідно вручну набрані видаткові накладні по замовленнях
Andriy Kravchenko
Admin, Writer, File Uploader
9/1/2026 4:54:32 PM
в якому вигляді ви хочете "піддягувати нові замовлення"? у вигляді замовлень від покупця, чи у вигляді видаткових накладних?
магазин Урожай
Користувач
9/1/2026 5:24:24 PM
можуть одразу бути у видаткових
Andriy Kravchenko
Admin, Writer, File Uploader
9/1/2026 5:40:24 PM
якщо це нове замовлення, яке ще не відвантажилось, а ми його одразу імпортуємо до TCU як видаткову накладну, постає питання, чи проводити її?
магазин Урожай
Користувач
9/1/2026 5:49:07 PM
так як в мене склад тільки привязаний до інтернет магазину і переміщень на інші магазини, офлайн склад не продає, для мене могла б проводитись, але не знаю як в інших буде організовано і як кому буде зручніше
Andriy Kravchenko
Admin, Writer, File Uploader
9/1/2026 6:39:36 PM
Справа в тому, що я зараз не вирішую ваш частний випадок. Мені потрібно побудувати систему, де на сайті, як на вітрині, представлені товари, які фізично можуть бути розташовані на різних складах. І замовлення покупця треба розкласти на дікльіка видаткових накладних, кожна з яких буде відвантажувати лише ті товари, що є на конкретному складі.
Я спеціально віддав цю проблему до ChatGPT щоб він максимально чітко описав проблематику.
Можете почитати, якщо цікаво.
Так, тут справді є суттєва зміна, і клієнт правильно звернув на неї увагу.
В актуальній документації catalog/import Хорошоп розділяє два режими обліку залишків.
Якщо «Облік залишків на складах» вимкнений, працює звичайний:
"presence": "В наявності"Причому документація прямо зазначає, що products[i].presence можна передавати лише коли складський облік вимкнений. Notion
А якщо «Облік залишків на складах» увімкнений, Хорошоп очікує вже не quantity, а масив:
"residues": [
{
"warehouse": "Основний склад",
"quantity": 15
}
]де:
products[i].residues— залишки по складах;products[i].residues[n].warehouse— саме «Назва для синхронізації» складу в Хорошопі;products[i].residues[n].quantity— кількість на цьому складі. Notion
І це в документації винесено як «Нові параметри з версії 3.15.15». Notion
Що це означає для нашого коду
Зараз ми робимо:
Quantity = link.TotalQuantity,
Presence = link.IsAvailable ? "В наявності" : "Немає в наявності",Для сайту, де клієнт увімкнув складський облік Хорошопа, це вже не та модель. Його слова:
залишки не підтягнулись
цілком пояснюються цим.
Ба більше, в актуальному переліку параметрів catalog/import, який я зараз переглянув, я взагалі не бачу старого products[i].quantity як рекомендованого поля залишку. Для складського режиму Хорошоп чітко описує residues. Notion
Нам треба вирішити, як представити склад Хорошопа
На першому етапі я б не будував складне зіставлення кількох складів Shopserver ↔ кілька складів Хорошопа.
У нас уже є:
link.TotalQuantityтобто сумарний доступний залишок по підрозділах, які працюють із цим сайтом.
Тому я б додав до WebSite ще одне налаштування, наприклад:
public string? WarehouseSynchronizationName { get; private set; }У UI:
Назва складу для синхронізації
Клієнт бере цю назву з налаштувань складу Хорошопа, з поля «Назва для синхронізації».
І тоді формуємо:
"residues": [
{
"warehouse": "Shopserver",
"quantity": 18
}
]де 18 — наш link.TotalQuantity.
DTO приблизно такий
public sealed class HoroshopProductResidue
{
[JsonPropertyName("warehouse")]
public string Warehouse { get; set; }
[JsonPropertyName("quantity")]
public double Quantity { get; set; }
}і:
[JsonPropertyName("residues")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public HoroshopProductResidue[]? Residues { get; set; }А в CreateActiveProduct:
Residues = new[]
{
new HoroshopProductResidue
{
Warehouse = changes.WarehouseSynchronizationName,
Quantity = link.TotalQuantity
}
},При цьому Presence для такого режиму взагалі не треба передавати, бо документація прямо каже, що воно для режиму без складського обліку. Notion
Тобто клієнт фактично знайшов нам важливий нюанс API. І я думаю, тепер стає зрозуміло, чому після ввімкнення «Обліку залишків» у нього описи синхронізувалися, а кількість — ні: Хорошоп переключився на іншу модель залишків, а ми продовжили відправляти стару.
Я б наступним кроком саме це й виправив: додав назву складу синхронізації й перейшов для складського режиму на products[i].residues.
тут виникає проблема під час отримання замовлень
Якщо ми передамо окремо залишки в розрізі кожної точки (складу), яку вказали в обліковому записі хорошопа, то потім нам треба формувати декілька видаткових накладних з одного замовлення. Бо клієнт набирає в замовлення різні товари, які фактично знаходяться на різних складах. Цікаво, як себе поведе хорошоп в цьому випадку. Особливо цікаво, коли один і той самий товар опиняється на декількох складах. Як він вирішує, звідки товар брати?
Так, це справді потенційно неприємне місце. І в документації, яку я зараз переглянув, я не знайшов опису алгоритму, за яким Хорошоп розподіляє один товар між кількома складами під час оформлення замовлення. Документація catalog/import лише каже, що можна передати масив residues із warehouse та quantity по кожному складу. Notion
У orders/get Хорошоп повертає склад замовлення як частину даних замовлення, а при additionalData=true може повертати додаткову інформацію, зокрема склади відправника/одержувача для доставки. Але це не те саме, що сказати: «цей товар списати зі складу А, а цей — зі складу Б». В описі товарних рядків замовлення я не бачу явного warehouse-id для кожної позиції. Notion
Тому я б поки не припускав, що Хорошоп сам розкладе одне замовлення по наших складах так, як нам потрібно.
Особливо небезпечний сценарій саме той, який ти описав:
Товар X:
Склад A = 2
Склад B = 5
Покупець замовив 4Хорошоп цілком може просто бачити сумарно 7 доступних одиниць і дозволити купити 4. Але з API замовлення ми можемо отримати лише:
Товар X, кількість 4і не отримати інформацію:
2 зі складу A
2 зі складу BТоді Shopserver уже сам мусить вирішувати, як сформувати рух.
А якщо в одному замовленні:
Товар A є тільки на складі 1
Товар B є тільки на складі 2то одна видаткова накладна на один DepartmentId взагалі не відображає реальність. Доведеться або:
- створювати кілька
SalesInvoiceв одному контейнері — по одному на кожен склад; - або мати якийсь один «web-склад» і вже внутрішньо переміщати туди товар;
- або взагалі не передавати Хорошопу розріз складів, а працювати з одним агрегованим складом.
І от третій варіант мені зараз здається найчистішим для нашої інтеграції.
Тобто в Хорошопі створити умовний склад, наприклад:
Shopserverі передавати туди:
quantity = link.TotalQuantityТоді Хорошоп бачить один логічний склад, а наша внутрішня логіка розподілу по Department залишається всередині Shopserver/TCU.
Це дуже схоже на те, як ми вже абстрагуємо платіжні методи: web-платформі не обов'язково знати всю внутрішню бухгалтерську структуру.
Але перед тим як остаточно це вирішувати, я б поставив підтримці Хорошопа одне дуже конкретне питання:
Якщо для одного товару через
products[i].residuesпередано залишки на кількох складах, а покупець замовляє кількість, яка має бути набрана з декількох складів, чи зберігається в замовленні інформація, з якого саме складу і в якій кількості було зарезервовано товар? Якщо так — у якому полі це повертається черезorders/get?
Оце їхня відповідь нам фактично визначить архітектуру. Якщо вони скажуть «ні, у замовленні склад по позиції не повертається», тоді я б узагалі не експортував кілька наших фізичних складів у Хорошоп, а використовував один логічний склад Shopserver.
в нас зараз є проблемний момент, ось він
private async Task<Department> GetPrimaryDepartment(long webSiteId, CancellationToken cancellationToken)
{
Department department = await (from link in _dictionaryContext.WebSiteDepartmentLinks
join dep in _dictionaryContext.Departments on link.DepartmentId equals dep.DepartmentId
where link.CompanyId == _dictionaryContext.CompanyId && dep.CompanyId == _dictionaryContext.CompanyId && link.WebSiteId == webSiteId && !link.IsDeleted && !dep.IsClosed
orderby dep.DepartmentName
select dep).FirstOrDefaultAsync(cancellationToken);
return department ?? throw new InvalidOperationException("Для сайту Хорошоп не прив'язано жодного активного підрозділу/складу.");
}Дивись, ми агрегуємо товари з різних торгових точок (складів) нашої системи, і відправляємо ці товари на хорошоп.
Але потім, коли повертається замовлення від хорошопу, ми все ж таки повинні розбити це замовлення на декілька, по тих точках, з яких це замовлення зібралось на сайті.
Як розбивати? Єдиний вихід - по кількості та наявності. Але тут питання, чи не розбиває сам хорошоп?
Так, це зараз ключова проблема нашої моделі, і твій GetPrimaryDepartment() фактично її маскує: ми агрегуємо залишки по кількох Department, а при поверненні замовлення довільно кладемо всю накладну на один перший склад.
І я перевірив актуальну документацію Хорошопа. В catalog/import дійсно можна передавати залишки по кількох складах через products[i].residues[n].warehouse + quantity. Notion Але в orders/get я не бачу поля, яке б повертало розподіл кожної товарної позиції замовлення по складах. Є additionalData, де можуть бути дані про склад відправника/одержувача доставки, але це не те саме, що «цей товар 2 шт зі складу A і 1 шт зі складу B». Notion
Тому поки що я б виходив із того, що Хорошоп сам для нас потрібний розподіл назад не повертає.
Тоді при імпорті чека Shopserver має сам виконати allocation.
Наприклад, сайт агрегує два підрозділи:
Склад A:
товар X = 2
товар Y = 5
Склад B:
товар X = 4
товар Z = 3Покупець замовив:
X = 5
Y = 1
Z = 2Shopserver може розкласти це так:
SalesInvoice #1, Department A
X = 2
Y = 1
SalesInvoice #2, Department B
X = 3
Z = 2Обидві накладні лежать в одному контейнері одного чека Хорошопа.
Це дуже добре лягає на нашу модель ContainerDto, бо контейнер і так може містити кілька документів.
Але алгоритм розподілу треба визначити детерміновано. Я б для першої версії зробив максимально простий:
- Беремо всі активні
WebSiteDepartmentLinkцього сайту. - Для кожної позиції дивимося поточні
InventoryRecord. - Йдемо по складах у стабільному порядку.
- Забираємо товар із першого складу, де він є, до вичерпання залишку.
- Якщо треба — продовжуємо з наступного складу.
- Потім групуємо отримані рядки за
DepartmentIdі формуємо по однійSalesInvoiceна кожний склад.
Наприклад:
потрібно X = 7
A має 3 → беремо 3
B має 10 → беремо 4Далі дві накладні.
Але тут є одна дуже важлива проблема: наш поточний InventoryRecord.Quantity може вже не відповідати тому залишку, який був у момент оформлення замовлення Хорошопа. Між замовленням і статусом 6/3 товар могли продати локально.
Тому розкладати заднім числом по поточних залишках — це best effort, а не абсолютна істина.
Саме тому я б поки не передавав у Хорошоп фізичні склади Shopserver окремо. Якщо Хорошоп не повертає складову позиції замовлення назад, ми створимо собі складну задачу без інформації для її точного розв'язання.
Практичніший варіант для першої версії:
в Хорошопі один логічний склад "Shopserver"
quantity = TotalQuantity по всіх WebSiteDepartmentLinksА при імпорті Shopserver сам розкладає чек по своїх складах за поточними доступними залишками.
Тобто warehouse в Хорошопі — це не фізичний Department, а агрегований канал доступності товару.
Саме тому я б зараз не міняв catalog/import на передачу кожного Department окремо. Спершу варто або отримати від підтримки Хорошопа підтвердження, що orders/get повертає розподіл позицій по складах, або прийняти модель одного логічного складу. За поточною документацією такого розподілу я не бачу.
магазин Урожай
Користувач
9/1/2026 7:54:40 PM
дуже цікаво прочитати
магазин Урожай
Користувач
Останнє оновлення:
9/1/2026 5:30:00 PM
отримав від підтримки хорошопу про кількість як передати
- products[i].residues - залишки товару на складах. Працює тільки якщо на сайті ввімкнено облік залишків на складах
- products[i].residues[n].warehouse - Назва для синхронізації складу (значення властивості "Назва для синхронізації" у складі)
- products[i].residues[n].quantity - кількість товару на складі
Коментарів (5)
Andriy Kravchenko
Admin, Writer, File Uploader
9/1/2026 7:22:40 PM
Дивіться, у вас зараз на сайті вимкнено облік товарних залишків
Думаю, його варто увімкнути

магазин Урожай
Користувач
9/1/2026 7:56:46 PM
включив облік товарних залишків, пройшла синхронізація, товари не в наявності, кількості не має
Andriy Kravchenko
Admin, Writer, File Uploader
9/1/2026 8:10:10 PM
Зараз я перевірю, чи дійсно все вивантажується, як вони вимагають, зробив логування. Через 10 хвилин побачимо.
Andriy Kravchenko
Admin, Writer, File Uploader
9/1/2026 8:32:16 PM
Ситуація дивна. Одночасно для товару "Немає в наявності", а нижче "Наявність: В наявності"

магазин Урожай
Користувач
9/1/2026 8:42:37 PM
Відповідь техпідтримки хорошопу