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;
/// Архивирована ли организация. Архивные не видны пользователям
/// но данные сохраняются. Из архива можно восстановить или (после 30 дней)
/// удалить навсегда — этим управляет SuperAdmin.
public bool IsArchived { get; set; }
public DateTime? ArchivedAt { get; set; }
/// Account owner (главный владелец, не путать с админами роли).
/// Это конкретный AppUser, который считается «хозяином» организации;
/// SuperAdmin может сменить через отдельный action c reason в audit-log.
public Guid? AccountOwnerUserId { get; set; }
/// Персональный API-токен MoySklad. Храним per-organization чтобы
/// пользователю не нужно было вводить его каждый раз при импорте.
public string? MoySkladToken { get; set; }
/// Chat ID владельца org в Telegram. Привязка делается через
/// deep-link на бота `food-market` — бот шлёт код подтверждения в
/// telegram, юзер вводит код в OrganizationSettings, мы сохраняем
/// chatId. По этому ChatId Hangfire-джоб OwnerDailySummaryJob шлёт
/// ежедневную сводку (выручка вчера, топ-3, low-stock).
/// null — привязки нет; ноль допустимым не считаем.
public long? OwnerTelegramChatId { get; set; }
/// Валюта организации по умолчанию. Если MultiCurrencyEnabled=false,
/// в UI выбор валюты скрыт — всё в этой валюте.
public Guid? DefaultCurrencyId { get; set; }
public Currency? DefaultCurrency { get; set; }
/// Разрешены ли продажи/закупки в нескольких валютах. По умолчанию
/// false — тогда UI не предлагает выбор валюты, всё в DefaultCurrency.
public bool MultiCurrencyEnabled { get; set; }
/// Показывать ли пользователю галку «В том числе НДС» на форме товара.
/// Если false (по умолчанию) — магазин работает с одной ставкой НДС и галка
/// скрыта, все товары считаются с НДС. Если true — можно для отдельных товаров
/// (хлеб, медикаменты) снимать галку.
public bool ShowVatEnabledOnProduct { get; set; }
/// Показывать ли на форме товара и в фильтрах галку «Услуга».
/// Большинство магазинов продают только физические товары — флаг выключен
/// по умолчанию, чтобы не захламлять UI.
public bool ShowServiceOnProduct { get; set; }
/// Показывать ли на форме товара и в фильтрах галку «Маркируемый».
/// Маркировка требуется только в нишевых категориях (алкоголь, лекарства,
/// табак) — по умолчанию выключено.
public bool ShowMarkedOnProduct { get; set; }
/// Показывать ли поля «Минимальный остаток» / «Максимальный остаток»
/// на карточке товара и одноимённую колонку в списке. Нужно в основном
/// торговым сетям со свободным местом на полке — по умолчанию выключено.
public bool ShowMinMaxStock { get; set; }
/// Разрешить ли цены с дробной частью (две цифры после запятой).
/// По умолчанию false — большинству KZ-магазинов хватает круглых тенге;
/// если включено, MoneyInput работает с шагом 0.01 и форматирует с .00,
/// иначе шаг 1 и значения округляются до целого даже при попытке прислать
/// дробное через API.
public bool AllowFractionalPrices { get; set; }
/// Разрешить ли уходить счетам/кассам в минус. По умолчанию выкл.:
/// расход больше остатка (оплата, выплата из смены) отклоняется с 409.
/// Менять может только администратор организации.
public bool AllowNegativeBalance { get; set; }
/// Показывать ли в карточке товара поле «Эталонная цена».
/// Default: true.
public bool ShowReferencePriceOnProduct { get; set; } = true;
/// Показывать ли в карточке товара поле «Страна происхождения».
/// Default: false. Большинству KZ-магазинов это поле не нужно;
/// включать для торговцев импортом или маркируемой продукцией.
public bool ShowCountryOfOriginOnProduct { get; set; }
/// Показывать ли в карточке товара поле «Описание». Default:
/// false. Описания ведут единицы магазинов; обычно текстовая колонка
/// просто захламляет карточку.
public bool ShowDescriptionOnProduct { get; set; }
/// Расширенный режим для организации. По умолчанию false
/// — виден только «простой» набор функций (базовый розничный учёт).
/// Включить может только SuperAdmin через
/// PUT /api/superadmin/organizations/{id}/advanced-mode; в
/// настройках магазина переключателя нет. При выключении данные
/// скрытых модулей не удаляются; при повторном включении всё видно.
/// Серверная защита — атрибут [RequiresAdvancedMode] на
/// контроллерах скрытых модулей.
public bool IsAdvancedMode { get; set; }
// ─ Весовые этикетки (внутримагазинные EAN-13, как в МойСкладе) ─────
/// Разбирать ли весовые этикетки (EAN-13 с префиксом из списков).
/// Default FALSE (у владельца весов с печатью этикеток нет; штрихкоды весовых —
/// обычные коды в карточке). Включается явно; разбор применяется только после
/// проверки точного совпадения штрихкода.
public bool WeightBarcodeEnabled { get; set; } = false;
/// Префиксы (2 цифры) весовых этикеток, где зашит ВЕС. CSV, напр.
/// «20,21,22,23,24». null → дефолт 20–24.
public string? WeightBarcodeWeightPrefixes { get; set; }
/// Префиксы весовых этикеток, где зашита ЦЕНА. CSV, напр.
/// «25,26,27,28,29». null → дефолт 25–29.
public string? WeightBarcodePricePrefixes { get; set; }
/// Длина кода товара (PLU) внутри весовой этикетки. Default 5.
public int WeightBarcodeCodeDigits { get; set; } = 5;
/// Формат кода, который генерируется для НОВОГО весового товара:
/// «ean13» — внутренний EAN-13 (как у штучных), «short» — короткий код
/// «<префикс> + N цифр» (у владельца: 27 + 5 цифр). Default «short».
public string WeightedCodeFormat { get; set; } = "short";
/// Префикс короткого кода весового товара (2 цифры). Default 27 (как у
/// владельца). Следующий свободный N-значный номер добавляется автоматически.
public int WeightedCodePrefix { get; set; } = 27;
/// Сколько цифр в номере после префикса короткого кода весового товара.
/// Default 5 (итого 7 знаков: «27xxxxx»).
public int WeightedCodeDigits { get; set; } = 5;
// ─ Уведомления об отрицательных остатках (идея владельца 08.10) ────────
/// Слать ли уведомления «товары ушли в минус» (центр уведомлений,
/// Telegram, push). Default true. Выключение — только администратор.
public bool NegStockNotifyEnabled { get; set; } = true;
/// Агрегация: не чаще одного уведомления на склад раз в N минут.
/// Default 15. Защита от спама при серии продаж в минус.
public int NegStockNotifyIntervalMinutes { get; set; } = 15;
/// Тихие часы — час начала (0–23, время Asia/Almaty, UTC+5).
/// В интервале [From,To) уведомления копятся, но не шлются. null — тихих
/// часов нет. Если From==To — считается, что тихих часов нет.
public int? NegStockQuietFromHour { get; set; }
/// Тихие часы — час конца (0–23). Интервал может переходить через
/// полночь (напр. From=22, To=8).
public int? NegStockQuietToHour { get; set; }
/// Эскалация: если минус не закрыт за N часов — повторное
/// уведомление владельцу/админам + блок «Просроченные минусы». Default 24.
public int NegStockEscalateHours { get; set; } = 24;
/// Отключённые проверки «Проверки данных» (CSV кодов: no-group,
/// no-sale-price, no-barcode, dup-barcode, dup-name-unit). null/пусто — все включены.
public string? DataQualityDisabledChecks { get; set; }
// ─ Sprint 11: ОФД (фискализация чеков) ────────────────────────────
// Каждая организация выбирает СВОЕГО ОФД-оператора. Креды per-tenant:
// у одной фирмы может быть Webkassa, у другой — Касса24. ApiKey/Secret
// шифруются через DataProtection (purpose=`foodmarket.fiscal`) — в
// открытом виде в API-ответах не возвращаются (только has-* флаги).
/// Тип ОФД-провайдера. 0=None (не фискализировать — дефолт),
/// 1=Mock, 2=Webkassa, 3=Kassa24, 4=OfdSolo. Значения — см.
/// FiscalProviderKind в application-слое.
public int FiscalProvider { get; set; }
/// Зашифрованный ApiKey оператора (base64 через DataProtection,
/// purpose=`foodmarket.fiscal`). Никогда не отдаётся в API-ответах.
public string? FiscalApiKeyEncrypted { get; set; }
/// Зашифрованный ApiSecret оператора (если требуется — у
/// Webkassa, например, токен-логин, у Касса24 — отдельный secret).
public string? FiscalApiSecretEncrypted { get; set; }
/// Уникальный номер кассы у оператора. У Webkassa называется
/// CashboxUniqueNumber, передаётся в каждом чеке.
public string? FiscalCashboxUniqueNumber { get; set; }
/// Опциональный override URL'а оператора (для тестового
/// контура / sandbox'а). null → используется production URL из
/// реализации провайдера.
public string? FiscalApiBaseUrl { get; set; }
// ─ Реестр товаров ─────────────────────────────────────────────────────────
/// Пополняет ли каталог этого магазина общий «Реестр товаров»
/// (фоновое задание берёт новые штрихкоды в реестр). По умолчанию true для
/// зарегистрированных магазинов; демо/автотестовые выключаются.
public bool ContributesToRegistry { get; set; } = true;
/// Статус копии реестра в каталог магазина при регистрации:
/// 0=не было, 1=идёт загрузка, 2=готово, 3=ошибка. Пока 1 — в админке
/// показывается «Каталог из реестра загружается…». Повторно не копируется.
public int RegistryCopyState { get; set; }
public DateTime? RegistryCopiedAt { get; set; }
}