- POST /api/catalog/products/{id}/barcodes {code, kind}: добавить штрихкод к
товару, уникальность в организации, 409 «штрихкод уже у товара X» (+название),
идемпотентно при повторе тому же товару, автоопределение типа по длине.
- Весовые этикетки (внутримагазинные EAN-13, как в МойСкладе): общий парсер
food-market.shared/Barcodes/WeightedBarcode (настраиваемые префиксы веса/цены,
длина PLU); касса pos.core делегирует туда. Настройки организации
(WeightBarcode*: enabled/префиксы/длина кода) + миграция Phase35.
- GET /api/catalog/products/by-barcode/{value} разбирает весовую этикетку →
товар + количество (вес) / встроенная цена; обычный ШК — как прежде.
- GET/PUT /api/organization/settings отдаёт/принимает настройки (блок весовых
применяется только если передан — round-trip-safe для текущей веб-формы).
- Офлайн-каталог ProductSyncDto: явный isWeighed (packaging==2) для мобильного/кассы.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
149 lines
10 KiB
C#
149 lines
10 KiB
C#
using foodmarket.Domain.Catalog;
|
||
using foodmarket.Domain.Common;
|
||
|
||
namespace foodmarket.Domain.Organizations;
|
||
|
||
public class Organization : Entity
|
||
{
|
||
public string Name { get; set; } = null!;
|
||
public string CountryCode { get; set; } = "KZ";
|
||
public string? Bin { get; set; }
|
||
public string? Address { get; set; }
|
||
public string? Phone { get; set; }
|
||
public string? Email { get; set; }
|
||
public bool IsActive { get; set; } = true;
|
||
|
||
/// <summary>Архивирована ли организация. Архивные не видны пользователям
|
||
/// но данные сохраняются. Из архива можно восстановить или (после 30 дней)
|
||
/// удалить навсегда — этим управляет SuperAdmin.</summary>
|
||
public bool IsArchived { get; set; }
|
||
public DateTime? ArchivedAt { get; set; }
|
||
|
||
/// <summary>Account owner (главный владелец, не путать с админами роли).
|
||
/// Это конкретный AppUser, который считается «хозяином» организации;
|
||
/// SuperAdmin может сменить через отдельный action c reason в audit-log.</summary>
|
||
public Guid? AccountOwnerUserId { get; set; }
|
||
|
||
/// <summary>Персональный API-токен MoySklad. Храним per-organization чтобы
|
||
/// пользователю не нужно было вводить его каждый раз при импорте.</summary>
|
||
public string? MoySkladToken { get; set; }
|
||
|
||
/// <summary>Chat ID владельца org в Telegram. Привязка делается через
|
||
/// deep-link на бота `food-market` — бот шлёт код подтверждения в
|
||
/// telegram, юзер вводит код в OrganizationSettings, мы сохраняем
|
||
/// chatId. По этому ChatId Hangfire-джоб OwnerDailySummaryJob шлёт
|
||
/// ежедневную сводку (выручка вчера, топ-3, low-stock).
|
||
/// null — привязки нет; ноль допустимым не считаем.</summary>
|
||
public long? OwnerTelegramChatId { get; set; }
|
||
|
||
/// <summary>Валюта организации по умолчанию. Если MultiCurrencyEnabled=false,
|
||
/// в UI выбор валюты скрыт — всё в этой валюте.</summary>
|
||
public Guid? DefaultCurrencyId { get; set; }
|
||
public Currency? DefaultCurrency { get; set; }
|
||
|
||
/// <summary>Разрешены ли продажи/закупки в нескольких валютах. По умолчанию
|
||
/// false — тогда UI не предлагает выбор валюты, всё в DefaultCurrency.</summary>
|
||
public bool MultiCurrencyEnabled { get; set; }
|
||
|
||
/// <summary>Показывать ли пользователю галку «В том числе НДС» на форме товара.
|
||
/// Если false (по умолчанию) — магазин работает с одной ставкой НДС и галка
|
||
/// скрыта, все товары считаются с НДС. Если true — можно для отдельных товаров
|
||
/// (хлеб, медикаменты) снимать галку.</summary>
|
||
public bool ShowVatEnabledOnProduct { get; set; }
|
||
|
||
/// <summary>Показывать ли на форме товара и в фильтрах галку «Услуга».
|
||
/// Большинство магазинов продают только физические товары — флаг выключен
|
||
/// по умолчанию, чтобы не захламлять UI.</summary>
|
||
public bool ShowServiceOnProduct { get; set; }
|
||
|
||
/// <summary>Показывать ли на форме товара и в фильтрах галку «Маркируемый».
|
||
/// Маркировка требуется только в нишевых категориях (алкоголь, лекарства,
|
||
/// табак) — по умолчанию выключено.</summary>
|
||
public bool ShowMarkedOnProduct { get; set; }
|
||
|
||
/// <summary>Показывать ли поля «Минимальный остаток» / «Максимальный остаток»
|
||
/// на карточке товара и одноимённую колонку в списке. Нужно в основном
|
||
/// торговым сетям со свободным местом на полке — по умолчанию выключено.</summary>
|
||
public bool ShowMinMaxStock { get; set; }
|
||
|
||
/// <summary>Разрешить ли цены с дробной частью (две цифры после запятой).
|
||
/// По умолчанию false — большинству KZ-магазинов хватает круглых тенге;
|
||
/// если включено, MoneyInput работает с шагом 0.01 и форматирует с .00,
|
||
/// иначе шаг 1 и значения округляются до целого даже при попытке прислать
|
||
/// дробное через API.</summary>
|
||
public bool AllowFractionalPrices { get; set; }
|
||
|
||
/// <summary>Разрешить ли уходить счетам/кассам в минус. По умолчанию выкл.:
|
||
/// расход больше остатка (оплата, выплата из смены) отклоняется с 409.
|
||
/// Менять может только администратор организации.</summary>
|
||
public bool AllowNegativeBalance { get; set; }
|
||
|
||
/// <summary>Показывать ли в карточке товара поле «Эталонная цена».
|
||
/// Default: true.</summary>
|
||
public bool ShowReferencePriceOnProduct { get; set; } = true;
|
||
|
||
/// <summary>Показывать ли в карточке товара поле «Страна происхождения».
|
||
/// Default: false. Большинству KZ-магазинов это поле не нужно;
|
||
/// включать для торговцев импортом или маркируемой продукцией.</summary>
|
||
public bool ShowCountryOfOriginOnProduct { get; set; }
|
||
|
||
/// <summary>Показывать ли в карточке товара поле «Описание». Default:
|
||
/// false. Описания ведут единицы магазинов; обычно текстовая колонка
|
||
/// просто захламляет карточку.</summary>
|
||
public bool ShowDescriptionOnProduct { get; set; }
|
||
|
||
/// <summary>Расширенный режим для организации. По умолчанию <c>false</c>
|
||
/// — виден только «простой» набор функций (базовый розничный учёт).
|
||
/// Включить может только SuperAdmin через
|
||
/// <c>PUT /api/superadmin/organizations/{id}/advanced-mode</c>; в
|
||
/// настройках магазина переключателя нет. При выключении данные
|
||
/// скрытых модулей не удаляются; при повторном включении всё видно.
|
||
/// Серверная защита — атрибут <c>[RequiresAdvancedMode]</c> на
|
||
/// контроллерах скрытых модулей.</summary>
|
||
public bool IsAdvancedMode { get; set; }
|
||
|
||
// ─ Весовые этикетки (внутримагазинные EAN-13, как в МойСкладе) ─────
|
||
/// <summary>Разбирать ли весовые этикетки (EAN-13 с префиксом из списков).
|
||
/// Default true.</summary>
|
||
public bool WeightBarcodeEnabled { get; set; } = true;
|
||
|
||
/// <summary>Префиксы (2 цифры) весовых этикеток, где зашит ВЕС. CSV, напр.
|
||
/// «20,21,22,23,24». null → дефолт 20–24.</summary>
|
||
public string? WeightBarcodeWeightPrefixes { get; set; }
|
||
|
||
/// <summary>Префиксы весовых этикеток, где зашита ЦЕНА. CSV, напр.
|
||
/// «25,26,27,28,29». null → дефолт 25–29.</summary>
|
||
public string? WeightBarcodePricePrefixes { get; set; }
|
||
|
||
/// <summary>Длина кода товара (PLU) внутри весовой этикетки. Default 5.</summary>
|
||
public int WeightBarcodeCodeDigits { get; set; } = 5;
|
||
|
||
// ─ Sprint 11: ОФД (фискализация чеков) ────────────────────────────
|
||
// Каждая организация выбирает СВОЕГО ОФД-оператора. Креды per-tenant:
|
||
// у одной фирмы может быть Webkassa, у другой — Касса24. ApiKey/Secret
|
||
// шифруются через DataProtection (purpose=`foodmarket.fiscal`) — в
|
||
// открытом виде в API-ответах не возвращаются (только has-* флаги).
|
||
|
||
/// <summary>Тип ОФД-провайдера. 0=None (не фискализировать — дефолт),
|
||
/// 1=Mock, 2=Webkassa, 3=Kassa24, 4=OfdSolo. Значения — см.
|
||
/// <c>FiscalProviderKind</c> в application-слое.</summary>
|
||
public int FiscalProvider { get; set; }
|
||
|
||
/// <summary>Зашифрованный ApiKey оператора (base64 через DataProtection,
|
||
/// purpose=`foodmarket.fiscal`). Никогда не отдаётся в API-ответах.</summary>
|
||
public string? FiscalApiKeyEncrypted { get; set; }
|
||
|
||
/// <summary>Зашифрованный ApiSecret оператора (если требуется — у
|
||
/// Webkassa, например, токен-логин, у Касса24 — отдельный secret).</summary>
|
||
public string? FiscalApiSecretEncrypted { get; set; }
|
||
|
||
/// <summary>Уникальный номер кассы у оператора. У Webkassa называется
|
||
/// <c>CashboxUniqueNumber</c>, передаётся в каждом чеке.</summary>
|
||
public string? FiscalCashboxUniqueNumber { get; set; }
|
||
|
||
/// <summary>Опциональный override URL'а оператора (для тестового
|
||
/// контура / sandbox'а). null → используется production URL из
|
||
/// реализации провайдера.</summary>
|
||
public string? FiscalApiBaseUrl { get; set; }
|
||
}
|