food-market/src/food-market.domain/Organizations/Organization.cs
nns 5173eaa07c feat(catalog): штрихкоды товара — добавление + весовые этикетки
- 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>
2026-10-08 05:39:20 +05:00

149 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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; }
}