From 2d1a9c8f75adac2ef381f794140d861da851b0de Mon Sep 17 00:00:00 2001 From: nurdotnet <278048682+nurdotnet@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:39:31 +0500 Subject: [PATCH] =?UTF-8?q?fix(moysklad):=20=D0=BD=D0=B5=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B4=D1=83=D0=BC=D1=8B=D0=B2=D0=B0=D1=82=D1=8C=20Kind=3DBoth?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=B8=D0=BC=D0=BF=D0=BE=D1=80=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D1=82=D1=80=D0=B0=D0=B3=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit У MoySklad НЕТ встроенного поля «Поставщик/Покупатель» у контрагентов — эта классификация целиком пользовательская через теги или группы. Импорт ставил Kind=Both дефолтом когда тегов не было, что искажало данные: все 586 контрагентов на stage стали «Оба», хотя в MoySklad ничего такого не было. - CounterpartyKind: добавлен Unspecified=0 как дефолт - ImportCounterpartiesAsync.ResolveKind: возвращает Unspecified когда тегов нет; Both только если в тегах ОБА маркера ("постав" + "покуп"); иначе один из конкретных - UI: dropdown получил опцию «Не указано», лейбл «Оба» переименован в «Поставщик + Покупатель» (точнее) - Существующие данные: SQL UPDATE Kind=3 → Kind=0 на stage (586 строк) и dev (0 строк, локально пусто) Co-Authored-By: Claude Opus 4.7 (1M context) --- src/food-market.domain/Catalog/Enums.cs | 4 ++++ .../Integrations/MoySklad/MoySkladImportService.cs | 13 ++++++++----- src/food-market.web/src/lib/types.ts | 2 +- .../src/pages/CounterpartiesPage.tsx | 8 +++++--- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/food-market.domain/Catalog/Enums.cs b/src/food-market.domain/Catalog/Enums.cs index 7f2a11f..d886eb4 100644 --- a/src/food-market.domain/Catalog/Enums.cs +++ b/src/food-market.domain/Catalog/Enums.cs @@ -2,6 +2,10 @@ namespace foodmarket.Domain.Catalog; public enum CounterpartyKind { + /// Не указано — дефолт для импортированных без явной классификации. + /// MoySklad сам не имеет встроенного поля Supplier/Customer, оно ставится + /// через теги или группы, и часто отсутствует. Не выдумываем за пользователя. + Unspecified = 0, Supplier = 1, Customer = 2, Both = 3, diff --git a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs index 58ef210..cb82666 100644 --- a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs +++ b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs @@ -39,16 +39,19 @@ public async Task ImportCounterpartiesAsync(string token, { var orgId = _tenant.OrganizationId ?? throw new InvalidOperationException("No tenant organization in context."); - // Map MoySklad tag set → local CounterpartyKind. If no tags say otherwise, assume Both. + // MoySklad сам НЕ имеет встроенного "Supplier/Customer" поля у контрагентов. + // Классификация обычно через теги ("Поставщик"/"Покупатель") или группы. Если их нет — + // оставляем Unspecified, не выдумываем за пользователя. static foodmarket.Domain.Catalog.CounterpartyKind ResolveKind(IReadOnlyList? tags) { - if (tags is null || tags.Count == 0) return foodmarket.Domain.Catalog.CounterpartyKind.Both; + 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.Supplier; - if (hasCustomer && !hasSupplier) return foodmarket.Domain.Catalog.CounterpartyKind.Customer; - return foodmarket.Domain.Catalog.CounterpartyKind.Both; + 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; } static foodmarket.Domain.Catalog.CounterpartyType ResolveType(string? companyType) diff --git a/src/food-market.web/src/lib/types.ts b/src/food-market.web/src/lib/types.ts index 3156dc6..f728570 100644 --- a/src/food-market.web/src/lib/types.ts +++ b/src/food-market.web/src/lib/types.ts @@ -6,7 +6,7 @@ export interface PagedResult { totalPages: number } -export const CounterpartyKind = { Supplier: 1, Customer: 2, Both: 3 } as const +export const CounterpartyKind = { Unspecified: 0, Supplier: 1, Customer: 2, Both: 3 } as const export type CounterpartyKind = (typeof CounterpartyKind)[keyof typeof CounterpartyKind] export const CounterpartyType = { LegalEntity: 1, Individual: 2 } as const diff --git a/src/food-market.web/src/pages/CounterpartiesPage.tsx b/src/food-market.web/src/pages/CounterpartiesPage.tsx index e9abbb5..f2e2e19 100644 --- a/src/food-market.web/src/pages/CounterpartiesPage.tsx +++ b/src/food-market.web/src/pages/CounterpartiesPage.tsx @@ -36,7 +36,7 @@ interface Form { } const blankForm: Form = { - name: '', legalName: '', kind: CounterpartyKind.Supplier, type: CounterpartyType.LegalEntity, + name: '', legalName: '', kind: CounterpartyKind.Unspecified, type: CounterpartyType.LegalEntity, bin: '', iin: '', taxNumber: '', countryId: '', address: '', phone: '', email: '', bankName: '', bankAccount: '', bik: '', @@ -44,9 +44,10 @@ const blankForm: Form = { } const kindLabel: Record = { + [CounterpartyKind.Unspecified]: '—', [CounterpartyKind.Supplier]: 'Поставщик', [CounterpartyKind.Customer]: 'Покупатель', - [CounterpartyKind.Both]: 'Оба', + [CounterpartyKind.Both]: 'Поставщик + Покупатель', } export function CounterpartiesPage() { @@ -138,9 +139,10 @@ export function CounterpartiesPage() {