⟨/⟩ 50_Domain/Cards/Template/DocumentKind.cs

242 строк · в начало

using System;
using System.Collections.Generic;
using Ban.Sdaid.Notation.Documents;

namespace Ban.Sdaid.Icd.Domain.Cards.Template
{
    /// <summary>Свойство ядра карты, заполняемое из значения атрибута.</summary>
    public enum CardCoreField
    {
        /// <summary>Атрибут в ядро не попадает.</summary>
        None,

        /// <summary>Шифр хранения.</summary>
        Shelf,

        /// <summary>Инвентарный номер.</summary>
        InventoryNumber,

        /// <summary>Заглавие.</summary>
        Title,

        /// <summary>Автор.</summary>
        Author,

        /// <summary>Год издания.</summary>
        Year,
    }

    /// <summary>Вид документа — описание того, что бывает: состав атрибутов и допустимые вложенные виды.</summary>
    [DomainEntity(Origin = DomainDataOrigin.Reference)]
    public class DocumentKind
    {
        /// <summary>Идентификатор вида документа.</summary>
        public Guid DocId { get; init; }

        /// <summary>Вид, в состав которого входит этот; пуст у вида верхнего уровня.</summary>
        public Guid? ParentId { get; init; }

        /// <summary>Код вида: по нему на него ссылаются документы карты, формы и выгрузка.</summary>
        public string DocKind { get; init; } = string.Empty;

        /// <summary>Наименование вида: ИК издания, ИК рукописи, эксперт, замер pH.</summary>
        public string Name { get; init; } = string.Empty;

        /// <summary>Виды документов, входящие в состав этого вида.</summary>
        [Relation(Kind = RelationKind.Composition)]
        public IReadOnlyList<DocumentKind> NestedKinds { get; init; } = new DocumentKind[0];

        /// <summary>Атрибуты, входящие в состав документов этого вида.</summary>
        [Relation(Kind = RelationKind.Composition)]
        public IReadOnlyList<DocumentKindAttribute> Attributes { get; init; } = new DocumentKindAttribute[0];
    }

    /// <summary>Атрибут вида документа — поле в составе вида: код, наименование, тип значения.</summary>
    [DomainEntity(Origin = DomainDataOrigin.Reference)]
    public class DocumentKindAttribute
    {
        /// <summary>Вид документа, в состав которого входит атрибут.</summary>
        [Relation]
        public DocumentKind DocumentKind { get; init; } = null!;

        /// <summary>Код атрибута — устойчивое имя, по которому его опознают форма и выгрузка.</summary>
        public string Code { get; init; } = string.Empty;

        /// <summary>Каноническое наименование атрибута.</summary>
        public string Name { get; init; } = string.Empty;

        /// <summary>
        /// Тип значения: чем заполняется поле и в каких границах. Именованный и переиспользуемый —
        /// единственное, что виды документов делят между собой.
        /// </summary>
        [Relation]
        public AttributeValueType ValueType { get; init; } = null!;

        /// <summary>Обязателен ли атрибут для документов этого вида.</summary>
        public bool IsRequired { get; init; }

        /// <summary>Порядок атрибута в составе вида.</summary>
        public int Order { get; init; }

        /// <summary>Свойство ядра карты, которое заполняется значением этого атрибута.</summary>
        [Relation]
        public CardCoreField CoreField { get; init; }
    }

    /// <summary>Описание доменной сущности <see cref="DocumentKind"/>.</summary>
    public class DocumentKindSpec : IDomainEntityDocument
    {
        public static string _refName = "вид документа";

        public Type Entity => typeof(DocumentKind);

        public string Name => "Вид документа";

        public string Description =>
            @"Описание того, что бывает: из каких атрибутов состоит документ этого вида и документы каких видов могут в него входить.";

        public string Version => "0.1";
        public string Status => "draft";

        public string[] Comments => new[]
        {
            "2026-08-19. Заведён по варианту 5 решения о структуре данных; заместил конфигурацию документа.",
        };

        public static string S1_Purpose = """
            ## Назначение

            Вид документа отвечает на вопрос «что хранится», и только на него. Как это напечатано,
            на каком листе и с какой подписью — знание формы документа, а не вида.

            Виды заводятся не только на информационные карты. Эксперт, замер величины pH, автор,
            переписчик, владелец, биотест — тоже виды документов: у них есть состав атрибутов
            и они существуют в данных независимо от того, сколько строк отвела под них бумага.
            """;

        public static string S2_Tree = """
            ## Дерево видов

            Вид входит в состав другого вида — так вложение описано **на уровне описания**:
            «в ИК издания входит эксперт, а в эксперта — ничего». Это парная конструкция к дереву
            документов карты: там — то, что заполнено, здесь — то, что бывает.

            Так описываются повторяющиеся группы бумажной формы: состав экспертов, замеры величины
            pH, авторы и переписчики рукописи, владельцы, записи биотеста.

            Таблицы с заранее известными строками вложенными видами **не** являются. «Владельческие
            пометы» и «Оформление экземпляра» на форме на рукопись выглядят как таблицы, но набор
            строк задан бланком — это пары атрибутов, а не список.

            **Кратность отдельно не задаётся.** Вложение и означает «сколько угодно»: экспертов
            у карты столько, сколько их было; примечаний — сколько кусков текста нашлось на бумаге.
            Ограничивать число сверху нечем и незачем — бумага уже заполнена, и Сервис её
            переписывает, а не проверяет на соответствие норме.
            """;

        public static string S3_Core = """
            ## Отображение в ядро карты

            Атрибут вида задаёт, какое свойство ядра карты он заполняет. Отображение живёт здесь,
            потому что зависит от вида: у ИК рукописи заглавие берётся из унифицированного
            заглавия, у ИК издания — из единственного поля «Заглавие».

            Ядро — проекция для поиска и показа; источник истины остаётся за значениями атрибутов
            документов карты.
            """;

        public static string S4_Invariants = """
            ## Инварианты

            - Код вида уникален и не меняется: по нему на вид ссылаются документы карты и формы.
            - Обязательность атрибута — свойство состава, а не формы: атрибут может быть
              обязательным, даже если на конкретном бланке для него нет клетки.
            - Одно свойство ядра заполняется не более чем одним атрибутом вида.
            - Вложение не зацикливается: вид не может входить сам в себя ни прямо, ни через цепочку.
            """;

        public static string S99_Related = $"""
            ## Связанные артефакты

            - {nameof(Ban.Sdaid.Icd.Arch.Adr.ADR_006_CardDataStructure)} — решение о структуре данных информационной карты
            """;
    }

    /// <summary>Описание доменной сущности <see cref="DocumentKindAttribute"/>.</summary>
    public class DocumentKindAttributeSpec : IDomainEntityDocument
    {
        public static string _refName = "атрибут вида документа";

        public Type Entity => typeof(DocumentKindAttribute);

        public string Name => "Атрибут вида документа";

        public string Description =>
            @"Поле в составе вида документа: код, каноническое наименование, тип значения, обязательность, порядок и участие в ядре карты.";

        public string Version => "0.3";
        public string Status => "draft";

        public string[] Comments => new[]
        {
            "2026-08-27. Принял код, наименование и тип значения упразднённого словаря атрибутов: атрибут принадлежит виду документа.",
        };

        public static string S1_Purpose = """
            ## Назначение

            Атрибут вида документа — **поле в составе вида**, а не связка между видом и чем-то
            ещё. Он отвечает и на вопрос «что это за поле по смыслу» (код, каноническое
            наименование), и на вопрос «чем оно заполняется и обязательно ли» (тип значения,
            обязательность).

            Атрибут принадлежит виду и никому больше. Общего словаря атрибутов над видами нет:
            он давал переиспользование заодно с общими ограничениями, и правка ради одного вида
            молча меняла поведение остальных. Переиспользуется теперь тип значения — он именован
            и выбирается осознанно.

            Атрибут вида — узловая сущность модели: на него ссылаются и поле формы, и значение
            в пакете ввода, и атрибут документа карты. Благодаря этому поле бланка не может
            сослаться на атрибут, которого в составе этого вида нет: ограничение обеспечено самой
            связью, а не проверкой.

            Поэтому одинаковое по смыслу поле в двух видах — дата и в карте, и в листе
            экспертизы — это два разных атрибута. Совпадение кодов между видами полезно при
            заведении, но ничем не обеспечено и ни к чему не обязывает.
            """;

        public static string S2_Invariants = """
            ## Инварианты

            - Код уникален в составе своего вида и не меняется: по нему опознаются значения
              в уже созданных картах и строится выгрузка в АБИС.
            - Атрибут не знает, на каком листе он напечатан и с какой подписью: это знание формы.
            - Две заполняемые позиции на бланке — всегда два атрибута, даже если подписаны
              одинаково и заполняются одинаковыми значениями.
            - Одно свойство ядра карты заполняется не более чем одним атрибутом вида.
            """;

        public static string S3_Granularity = """
            ## Дробность атрибутов

            Атрибут соответствует **одной заполняемой позиции** на форме, а не смысловой группе.
            Правило одно и покрывает оба случая, которые иначе хотелось бы решать по-разному:

            - матрица «Задано / Выполнено» в разделе «Консервация» даёт по два атрибута
              на мероприятие — отдельно «задано» и отдельно «выполнено»: на бумаге это две
              независимые клетки, заполняемые в разное время и разными людьми;
            - парные колонки «1 и 2» на форме на рукопись тоже дают по два атрибута — основа
              переплёта и основа футляра: это разные объекты описания, а не одно поле, заполняемое
              дважды.

            Состав вида от этого длиннее, зато значение опознаётся **кодом атрибута** и не зависит
            от того, в каком разделе бланка поле напечатано. Иначе смысл значения пришлось бы
            восстанавливать по пути в дереве разделов — и при любой перевёрстке бланка он бы поехал.

            Одно и то же поле, подписанное на разных редакциях бланка по-разному (`Кол-во стр.`
            и `Количество страниц`), остаётся **одним** атрибутом: подпись живёт в поле формы.
            Поэтому карты разных редакций сравнимы между собой.
            """;
    }
}