From 26a76e5aea63f037207b2d54c263946575729154 Mon Sep 17 00:00:00 2001 From: nurdotnet <278048682+nurdotnet@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:51:23 +0500 Subject: [PATCH] =?UTF-8?q?fix(moysklad):=20=D1=83=D0=B1=D0=B8=D1=80=D0=B0?= =?UTF-8?q?=D0=B5=D0=BC=20=D0=B2=D1=8B=D0=B4=D1=83=D0=BC=D0=BA=D1=83=20Kin?= =?UTF-8?q?d=20=D0=BF=D0=BE=D0=BB=D0=BD=D0=BE=D1=81=D1=82=D1=8C=D1=8E=20?= =?UTF-8?q?=E2=80=94=20=D1=83=20MoySklad=20=D1=8D=D1=82=D0=BE=D0=B3=D0=BE?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BB=D1=8F=20=D0=BD=D0=B5=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проверил через API под реальным токеном (entity/counterparty?expand=group,tags): у MoySklad **нет** поля «Поставщик/Покупатель» у контрагентов вообще. Есть только: - group (группа доступа сотрудников, у всех "Основной") - tags (произвольные ярлыки, у большинства пусто) - state (пользовательская цепочка статусов) - companyType (legal/individual/entrepreneur — это наш Type) Один и тот же контрагент может быть поставщиком в одной приёмке и покупателем в другом чеке — классификация контекстная, не атрибут сущности. Изменения: - ImportCounterpartiesAsync.ResolveKind теперь ВСЕГДА возвращает Unspecified. Никаких эвристик по тегам — просто null для Kind. - useSuppliers хук теперь useCounterparties — возвращает ВСЕХ контрагентов, не фильтрует по Kind. Селекторы поставщика в Supply/RetailSale показывают всех. Пользователь сам выбирает кто поставщик в этом конкретном документе. - Создание контрагента в UI: дефолт Kind = Unspecified, не Supplier. Поле Kind в нашей модели остаётся для пользователей которые сами хотят классифицировать. Но импорт его не трогает. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../MoySklad/MoySkladImportService.cs | 20 +++++++------------ src/food-market.web/src/lib/useLookups.ts | 10 +++++----- .../src/pages/CounterpartiesPage.tsx | 2 ++ 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs index cb82666..2953411 100644 --- a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs +++ b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs @@ -39,20 +39,14 @@ public async Task ImportCounterpartiesAsync(string token, { var orgId = _tenant.OrganizationId ?? throw new InvalidOperationException("No tenant organization in context."); - // MoySklad сам НЕ имеет встроенного "Supplier/Customer" поля у контрагентов. - // Классификация обычно через теги ("Поставщик"/"Покупатель") или группы. Если их нет — - // оставляем Unspecified, не выдумываем за пользователя. + // MoySklad НЕ имеет поля "Поставщик/Покупатель" у контрагентов вообще — это + // не наша выдумка, проверено через API: counterparty entity содержит только + // group (группа доступа), tags (произвольные), state (пользовательская цепочка + // статусов), companyType (legal/individual/entrepreneur). Никакой role/kind. + // Поэтому при импорте ВСЕГДА ставим Unspecified — пользователь сам решит. + // Параметр tags оставлен ради совместимости сигнатуры, не используется. static foodmarket.Domain.Catalog.CounterpartyKind ResolveKind(IReadOnlyList? tags) - { - if (tags is null || tags.Count == 0) return foodmarket.Domain.Catalog.CounterpartyKind.Unspecified; - var lower = tags.Select(t => t.ToLowerInvariant()).ToList(); - var hasSupplier = lower.Any(t => t.Contains("постав")); - var hasCustomer = lower.Any(t => t.Contains("покуп") || t.Contains("клиент")); - if (hasSupplier && hasCustomer) return foodmarket.Domain.Catalog.CounterpartyKind.Both; - if (hasSupplier) return foodmarket.Domain.Catalog.CounterpartyKind.Supplier; - if (hasCustomer) return foodmarket.Domain.Catalog.CounterpartyKind.Customer; - return foodmarket.Domain.Catalog.CounterpartyKind.Unspecified; - } + => foodmarket.Domain.Catalog.CounterpartyKind.Unspecified; static foodmarket.Domain.Catalog.CounterpartyType ResolveType(string? companyType) => companyType switch diff --git a/src/food-market.web/src/lib/useLookups.ts b/src/food-market.web/src/lib/useLookups.ts index e978bbf..9d715f8 100644 --- a/src/food-market.web/src/lib/useLookups.ts +++ b/src/food-market.web/src/lib/useLookups.ts @@ -20,8 +20,8 @@ export const useCountries = () => useLookup('countries', '/api/catalog/ export const useCurrencies = () => useLookup('currencies', '/api/catalog/currencies') export const useStores = () => useLookup('stores', '/api/catalog/stores') export const usePriceTypes = () => useLookup('price-types', '/api/catalog/price-types') -export const useSuppliers = () => useQuery({ - queryKey: ['lookup:suppliers'], - queryFn: async () => (await api.get>('/api/catalog/counterparties?pageSize=500&kind=1')).data.items, - staleTime: 5 * 60 * 1000, -}) +// MoySklad-style: контрагент один, может быть и поставщиком, и покупателем +// в разных документах. Не фильтруем по Kind — пользователь сам выбирает. +export const useCounterparties = () => useLookup('counterparties', '/api/catalog/counterparties') +// Алиас для обратной совместимости со старым кодом форм Supply/RetailSale. +export const useSuppliers = useCounterparties diff --git a/src/food-market.web/src/pages/CounterpartiesPage.tsx b/src/food-market.web/src/pages/CounterpartiesPage.tsx index f2e2e19..dcf16ac 100644 --- a/src/food-market.web/src/pages/CounterpartiesPage.tsx +++ b/src/food-market.web/src/pages/CounterpartiesPage.tsx @@ -36,6 +36,8 @@ interface Form { } const blankForm: Form = { + // Kind по умолчанию Unspecified — MoySklad не имеет такого поля у контрагентов, + // не выдумываем за пользователя. Пусть выберет вручную если нужно. name: '', legalName: '', kind: CounterpartyKind.Unspecified, type: CounterpartyType.LegalEntity, bin: '', iin: '', taxNumber: '', countryId: '', address: '', phone: '', email: '',