Программирование в терминах предметной области
Методология этого процесса представляется достаточно простой и самоочевидной.
Внимательно рассмотрим целевую область на предмет выявления в ней отдельных понятийных сущностей и их возможной наследственной общности, и каждую сущность оформим в виде класса данных. Для каждой пары логически связанных сущностей определим количественный аспект их взаимодействия, и создадим между классами сущностей отношение соответствующего типа. Каждую значащую характеристику сущности декларируем в виде атрибута, и если эта характеристика зависит от других характеристик – свяжем соответствующие атрибуты виртуальными соединителями. Последовательное и методичное исполнение этих рекомендаций позволяет на выходе получить декларативную модель приложения, которая в соответствующей вычислительной среде будет выполнять функции полноценной программы.
От чего хочется сразу предостеречь, так это от попытки создать универсальную модель мира. Реальность относительна. Уж если ожидается решение вполне конкретной и четко поставленной задачи, то предметную область следует рассматривать исключительно с точки зрения потребностей этой задачи. Иными словами, со стороны предполагаемого интерфейса. Для иллюстрации сказанного и в качестве примера использования методологии рассмотрим реализацию бухгалтерского учета.
Ремарка:
Выбор бухгалтерии в качестве предметной области примера обусловлен тем, что в относительно небольшом пространстве классов сконцентрированы самые различные варианты использования функционала модели данных. Цель настоящей статьи – рассмотрение методологии реализации бизнес-логики бухгалтерии, а не ее описание как предмета. Довольно качественное введение в предмет обсуждается здесь.
Примечания:
1. Полный охват данной темы в рамках одной статьи невозможен, да и такая задача не ставится.
2. Автор выражает надежду, что использованная им графическая нотация будет интуитивно понятна.
Основу бухгалтерского учета составляет метод двойной записи. Практический смысл этого метода тривиален: любая сумма денег имеет конкретный источник образования. Для целей автоматизации это означает ровно одно – запись о движении денежных сумм, которую принято именовать Проводка, следует отражать одновременно в двух регистрах учета.
Воспользовавшись служебными прототипами классов, декларируем два класса: класс-событие Проводка и именованный класс Счет. Класс Счет характеризуется Номером (базовый атрибут класса, используемый для именования объекта Счет), и Наименованием, которое поясняет назначение конкретного Счета. Проводка, в дополнение к Дате, характеризуется также Суммой.
Оба класса свяжем множественным отношением. А так как объект Проводка (П) должен адресовать сразу два объекта Счет (С), кратность этого отношения следует увеличить до двух, как показано ниже:
Функциональное назначение каждого элемента атрибута прямой ссылки Проводки строго фиксировано: первый элемент адресует объект Счета, по дебету которого отражается Сумма Проводки, соответственно, второй элемент адресует Счет кредита Проводки. В объекте Счет список обратных ссылок содержит указатели на объекты Проводка, адресовавшие его любым из своих элементов. Отметим, что значение обратной ссылки включает в себя, помимо дескриптора объекта, еще и номер элемента атрибута прямой ссылки, через который ссылка была реализована. Это позволяет отображать Журнал проводок счета как общим списком, так и в виде двусторонней таблицы по общепринятым нормам: дебет слева, кредит справа.
Отдельный Счет может быть детализирован другими однотипными ему Счетами, и количество уровней такой детализации может быть в общем-то произвольным. Для целей само-детализации в классе Счет создается внутреннее рекурсивное отношение. Рекурсия реализуется созданием нового объекта Счет относительно уже существующего объекта. Счетами первого уровня объектной иерархии, образуемой рекурсией, очевидным образом будут считаться объекты, у которых не инициализирован атрибут прямой ссылки рекурсивного отношения. Все прочие объекты класса Счет, принадлежащие второму и последующим уровням иерархии, рассматриваются как Cубсчета.
Для обобщения Проводок в корреспонденцию счетов создается соответствующий класс-проекция Корреспонденция, детали реализации которого подробно рассмотрены здесь. А так как Счет обладает рекурсией, то аналогичное отношение следует создать также и в классе Корреспонденция (К).
При одновременном наличии рекурсивных отношений в некотором классе и ссылающемся на него классе-проекции, конструктор отношений создает связывающие обе рекурсии декларации, которые обеспечивают воспроизведение рекурсии объектов ведущего класса объектами класса-проекции. В зависимости от того, как легли карты, результирующие связи объектов будут выглядеть по разному:
Аналитика представляет собой следующий, после субсчетов, уровень детализации регистров учета, при котором Счет анализируют в разрезе конкретных Объектов Учета. За каждым Счетом закреплен строго определенный вид таких Объектов, что явным образом отражено в наименовании Счета. Если же Объект Учета отсутствует, или перманентно существует в единственном экземпляре, то такой счет не является аналитическим, например счет 80 – Уставный капитал, или 99 – Прибыли и убытки.
Если объединить все разнообразие Объектов Учета под общей вывеской абстрактного именованного класса Субконто (Х), то роль регистра аналитического учета нам обеспечит класс-проекция Аналитика (А):
В данном случае проекция Проводки на Аналитику осуществляется раздельно для для каждой пары одноименных элементов атрибутов прямой ссылки. Если в Проводку не передан один из указателей на Субконто, то соответствующая проекция не будет реализована. Стоит напомнить, что если требуемый по условиям проекции объект аналитики еще не существует, то он будет создан автоматически.
Тут самое время вспомнить, что Аналитика тоже может быть многоуровневой. Так, например, расчеты с отдельным контрагентом осуществляются раздельно по разным Договорам с ним, и в этом случае предметом учета становится детализация Субсчет → Контрагент → Договор. Реализовать эту потребность можно например такой декларацией:
Правда такое решение сложно назвать изящным, так как в Проводку добавляется еще две двойные прямые ссылки. К тому же не к месту вдруг вспомнилось, что в строительной организации, например, анализ расчетов с Заказчиками или Субподрядчиками может включать в себя помимо Договоров также и детализацию по Объектам строительства.
Поэтому, еще раз воспользуемся рекурсивным отношением, на этот раз применительно к классам Субконто и Аналитика. И не забудем отнести Договор и Объект (строительства) к объектам учета, создав соответствующие классы наследованием от класса Субконто.
С целью образовать ссылочную цепочку вида Договор → Объект → Заказчик (как показано на схеме классов), атрибуты унаследованного рекурсивного отношения в каждом из этих классов требуют уточнения ссылочного типа. В результате уточнения типа относительно объекта Заказчик можно будет создать объекты Объект, относительно которых далее можно будет создавать объекты Договор.
После создания рекурсивного отношения в классе Аналитика, отношение проекции, реализованное через этот класс, приобретает свойства рекурсивной проекции. Возникающий при этом нежелательный эффект симметрии проекции убирается де-активацией соединителя, связывающего атрибут прямой ссылки рекурсивного отношения в классе Счет с дополнительным атрибутом, обеспечивающим рекурсию проекции в классе Аналитика.
Результат работы модернизированной таким образом рекурсивной проекции представлен на следующей схеме связи объектов:
Обратим внимание, на приведенной схеме связей объектов в ссылочный атрибут проводки был передан указатель на объект Договор, что и привело к созданию трехуровневой проекции. Но с тем же успехом вместо него можно было передать указатель на объект Объект (или Заказчик), и тогда бы проекция насчитывала всего два уровня (или один, соответственно).
Сразу отметим, что в рассматриваемые виды Субконто, и соответственно аналитику, временно не вошли Расчеты с персоналом по оплате (счет 70), Учет ОС (01,02) и НМА (04,05), а также Учет ТМЦ (10,40,41), которые в силу своей специфики образуют отдельные подзадачи с богатым внутренним функционалом. В этих подзадачах требуемая аналитика присутствует в полном объеме, а формируемые подзадачами Проводки содержат только обобщенные суммы, без дальнейшей детализации.
Как уже упоминалось, каждый отдельный вид (тип) Объекта Учета описывается отдельным классом – создаваемым наследованием от класса Субконто.
Так например, Расчеты с бюджетом включают в себя всевозможные налоги и сборы, каждый из которых учитывается на отдельном субсчете. Но при этом собственно расчеты ведутся строго в разрезе КБК, который и будет в данном случае Объектом Учета, и для которого создается свой собственный класс КБК. Объект класса КБК, помимо собственно значения КБК, хранимого прямо в атрибуте Наименование, обладает также указателем на получателя платежа – источник реквизитов для платежного поручения.
Денежные средства предприятия имеют форму и место обращения. Соответственно, для учета наличных и безналичных средств следует создать классы Касса и Банк (наследники Субконто), и как минимум по одному объекту этих классов. Но кроме этого, деньги в любой их форме подлежат ежедневному учету, который в бухгалтерии формализуется в виде Отчета кассира-операциониста и Выписки банка. Для этих целей нам потребуется еще один независимый класс-событие – Операционный день (ОД), объекты которого будут обобщать Проводки одного календарного дня по Кассе и Банку (на самом деле – по Расчетному счету в Банке). А чтобы не создавать декларации связи отдельно для Кассы и отдельно для Банка, используем естественного промежуточного наследника – класс Денежные средства (ДС):
В данной декларации Проводка проецируется на Операционный день не по значениям указателей, а по значению абсолютного атрибута Дата (D). Механизм реализации такой проекции подробно рассмотрен здесь. И да, если новый рабочий день начать со снятия денег с расчетного счета на выплату заработной платы, то одной Проводкой буду созданы сразу два объекта Операционный день. Один будет принадлежать Банку, и служить Выпиской за этот день, а другой адресует Кассу.
Для того, чтобы не создавать для всех Проводок проекцию на Операционный день, можно было воспользоваться флагами, блокирующими соединители проекции, создав декларации этих флагов в Субконто, и устанавливая требуемые значения в объекта Касса и Банк. Но лучше использовать наследование: так, как показано на схеме связей выше. Создадим класс Аналитика денег (АД), наследника класса Аналитика. В этом классе, а также в классе Денежные средства, переопределим унаследованное отношение с Субконто на прямое взаимное, уточнив тип ссылочных атрибутов. Тогда Проводка, при получении указателя на объекты Касса или Банк, получит вместо Аналитики указатель на Аналитику денег, а при необходимости создать объект создаст его для класса Аналитика денег.
Точно такую же схему связи классов, как для Кассы и Банка, можно использовать и для получения таких регистров учета как Книга покупок и Книга продаж. Для этого по аналогии с классами Денежные средства, Касса, Банк, Операционный день, Аналитика денег следует создать классы НДС, НДС полученный, НДС выставленный, Операционный месяц/квартал, Аналитика НДС.
Среди объектов учета большую однородную группу образуют расчеты с различными представителями внешнего мира – Поставщиками, Покупателями, Заказчиками, Субподрядчиками, Работниками, Подотчетными лицами, Арендаторами, etc. Сами расчеты при этом, как правило, осуществляются в рамках Договора. При этом сам внешний мир представлен двумя категориями действующих лиц: физические и юридические, для которых предыдущий перечень – это Роли, которые эти лица играют во взаиморасчетах. Сами действующие лица во взаимных расчетах участвуют как Контрагент, то есть лица имеют общего абстрактного предка. Таким образом, мы имеем дело с двумя иерархиями, образованными наследованием, между классами которых существуют отношения, декларация которых выглядит так:
Взаимное отношение Роли и Договора (как уже ранее упоминалось – унаследованная у Субконто внутренняя рекурсия) взаимно переопределяется в каждой паре их наследников. Декларации отношений, связывающих Роль с Контрагентом и Договор с Контрагентом, наследуются всеми их потомками, поэтому например Покупателем может быть как Юридическое, так и Физическое лицо. Но при необходимости, унаследованное отношение может быть уточнено. Например в случае, когда принято считать, что Подрядчиком может быть только Юр.лицо.
Представляется логичным, если объект Роли нужного типа создается не вручную, а появляется вследствие определенного взаимодействия с Контрагентом. Например, при вводе Приходной Накладной, Контрагент автоматически квалифицируется как Поставщик ТМЦ, а при создании нового Договора подряда – как Субподрядчик. Такое поведение обеспечивает трансформация обычного класса в класс-проекцию. Если рассматривать класс Роль как часть уникального отношения, связывающего классы Счет и Контрагент, то, для получения классом Роль требуемых свойств проекции, достаточно в свойствах атрибута обратной ссылки Контрагент.{Роль} декларировать дополнительный список, в качестве key-контекста которого выбрать атрибут прямой ссылки Роль.[Счет]. А так как отношение Договор → Роль уже существует, то необходимо также создать back-соединитель, который свяжет созданный список с атрибутом Договор.[Роль]. Для всех последующих отношений с классом Роль, этот соединитель будет создаваться автоматически.
Подотчетным лицом может быть только Работник. Физ.лицо становится Работником (при создании связанного с ним объекта Работник) при заполнении такого документа как Приказ о приеме на работу. Точно таким же образом Юр.лицо образует Поставщика при заполнении Приходной накладной.
И напоследок, ИП (индивидуальный предприниматель) безусловно считается лицом юридическим, но образован он из Физ.лица. Этот факт описывается декларацией унитарного отношения, связывающего Юр.лица с Физ.лицами, через которое Юр.лицо может заимствовать реквизиты лица физического.
После того, как номенклатура регистров учета определена, а их взаимная связь установлена, пришло время рассмотреть декларации, с помощью которых реализуется обобщение сумм Проводок регистрами учета, а также проекция результатов обобщения на ось времени.
Результаты обобщения Сумм Проводок, раздельно отражаемые по дебету и по кредиту Счета, должны быть представлены тремя парами значений: остаток на начало периода, оборот за период, остаток на конец периода. Остатки на начало и конец периода отражаются в "свернутом" виде: соответствующая пара значений (дебета и кредита) уменьшается на величину наименьшего из них, давая "чистый" дебетовый или кредитовый остаток. Выглядеть это должно примерно так, как на приведенном фрагменте так называемой оборотно-сальдовой ведомости:
Результат суммирования оборотов и остатков по всем Счетам для каждой пары дебет-кредит должен дать одинаковое значение. Если это правило соблюдено, то механика двойного счета реализована корректно.
Все выше сказанное в полном объеме также справедливо и для Аналитики счета.
В классах Счет и Аналитика для хранения обобщенных сумм Проводок используется пара атрибутов Дебет и Кредит, а собственно обобщение реализует назначенный им так называемый интегральный функционал из семейства списковых функционалов.
Интегральный функционал, получающий на входе пару ключ-значение, реализует следующую специфику исполнения: после позиционирования по значению ключа на нужный элемент списка (а если такового нет, то он будет создан, с присвоением ему значения предыдущего элемента), численное значение добавляется к значению этого элемента, а также значениям всех последующих (по возрастанию значения ключа) элементов до конца списка.
В классах Счет и Аналитика атрибуты Дебет и Кредит получают свои значения по активным соединителями, декларация которых выглядит так:
В каждом соединителе источником значения ключа (key-контекст) является атрибут Дата (D), а значение Суммы ($) передается через соответствующий элемент двойной ссылки (ref-контекст соединителя).
Тут стоит упомянуть, что если Счет является аналитическим (обладает реальными объектами класса Аналитика), то он формирует свои значения путем тотализации значений, формируемых объектами класса Аналитика. Такой же механизм действует и в отношении объектов Счет и Аналитика, принадлежащих более высоким уровням объектной иерархии, только в этом случае тотализация значений выполняется через рекурсивное отношение.
Для того, чтобы исключить двойную тотализацию сумм, соединители к атрибутам Дебет и Кредит в классе Счет дополнительно используют в качестве lock-контекста атрибуты отношения с классом Аналитика таким образом, что инициализированный элемент ссылки на объект Аналитики блокирует передачу значения.
А для того, чтобы исключить асимметрию двойного счета, создают соединители к служебному атрибуту Del от каждого элемента атрибута прямой ссылки на Счет. Тем самым объект Проводка будет пребывать в логически «удаленном» состоянии до тех пор, пока не будут инициализированы оба указателя. Для управления Проводкой в ручном режиме можно создать логический атрибут Проведено, который затем также связать соединителем с атрибутом Del.
Напоследок упомянем, что в классе Корреспонденция обобщение суммы Проводки осуществляется только в один интегральный атрибут Сумма, а в классе Операционный день сумма Проводки в атрибутах Дебет и Кредит не интегрируется, а просто суммируется, давая сумму оборотов за этот день.
При наличии интегрального списка нет необходимости суммировать множество значений в заданном диапазоне дат. Для определения величины изменения значения в диапазоне между двумя произвольно взятыми элементами интегрального списка достаточно взять разность значений этих элементов. Что и используется для вычисления дебетовых/кредитовых оборотов и остатков Счета/Аналитики в соответствии со следующей декларацией.
В левой части схемы атрибуты д1 и д2 извлекают значения из интегрального списка Дебет, используя в качестве ключей значения атрибутов D1 и D2, устанавливающих даты начала и окончания периода, после чего функционал (сумматор) атрибута дебетовый оборот за период (ДО) вычисляет разность этих значений. Для получения значения оборота по кредиту используется точно такая же декларация. В правой части схемы из значений выборки за период по дебету (д1 и д2) и по кредиту (к1 и к2) формируются значения остатков на начало (Д1 и К1) и окончание периода (Д2 и К2 – на схеме не показаны). Делается это путем так называемой “свертки“ значений.
Приведенная схема нуждается в дополнительных комментариях:
а) Все атрибуты, за исключением статического Дебет, являются динамическими, то есть вычисляют свое значение в момент обращения к ним. Соответственно, все изображенные соединители пассивны.
б) Функционалы "меньше" и "меньше или равно" (принадлежащие семейству списковых функционалов), в целях единообразия изображенные как входящие функционалы атрибутов д1 и д2, на самом деле определены в свойствах исходящих сокетов соединителей. При этом для данного соединителя совершенно непринципиально, для какого из его сокетов: входящего или исходящего, определен key-контекст (на схеме – для входящего). Полученное им значение ключа функционал "меньше" использует для позиционирование на элемент списка, предшествующий элементу, обладающим значением ключа равным или большим полученного. Функционал "меньше или равно" позиционируется на элемент, значение ключа которого равно переданному, или, в отсутствие такового, на логически предшествующий ему элемент.
в) При отображении соединителя его отдельные сокеты изображаются только в том случае, если несут смысловую нагрузку, требующую отдельного пояснения. Один из двух входящих соединителей атрибута ДО содержит изображение кружка, который символизирует наличие у соответствующего сокета (не изображен) установленного флага инверсии. Инверсия входящего сокета изменяет знак операции – в данном случае (для сумматора) с "+" на "–". В этом соединителе с тем же успехом можно было установить флаг инверсии для исходящего сокета, что означало бы инверсию значения.
г) Атрибуты D1 и D2 в классах Счет и Аналитика, значения которых образуют временной интервал, предпочтительнее сразу создавать в формате интервала. Очевидно, что значения D1 и D2 должны быть общими для всех объектов Счет и Аналитика. Эту общность можно обеспечить, например, связав класс Счет со служебным классом Global, где в атрибуте D1-D2 и будет хранится интервал, заданный пользователем. Через действующие отношения классов этот атрибут последовательно связывается пассивными соединителями сначала с атрибутом в классе Счет, а затем с атрибутом в классе Аналитика.
Если атрибуты итоговых оборотов и остатков изначально существуют в качестве элементов массива (перечислимая форма атрибута), то это сильно облегчает их тотализацию в вышестоящих регистрах учета.
На схеме, приведенной выше, передачу сразу шести значений осуществляет всего один, так называемый групповой соединитель. Если соединитель связан с конкретным элементом массива, то его сокет в своих свойствах хранит номер этого элемента. В сокетах группового соединителя этот номер не указан, что означает применимость декларированной связи к каждому элементу массива. Групповой соединитель обеспечивает передачу значения по принципу "элемент в элемент". При этом массивы не обязательно должны быть одной размерности.
Обратим внимание, у итогового атрибута декларирован соединитель на самого себя, но этот соединитель "проходит" через рекурсивное отношение, то есть реальная передача значения будет выполнена в тот же атрибут, но уже в другом объекте.
И еще одна тонкость, но уже бухгалтерская. У обычного Счета один из двух остатков: дебетовый или кредитовый, всегда будет равен нулю (свернутое сальдо). Для Счета, обладающего Аналитикой, это правило не применимо: задолженность Пети передо мной и мой долг Васе не компенсируют друг друга. То есть, на уровне отдельного объекта класса Аналитика остаток всегда будет свернут, но сумма этих остатков в Счете уже может иметь "развернутый" вид.
Теперь самое время ответить на вопрос: каким образом Проводка получает значения Даты, Суммы, и указатели на те или иные регистры учета.
Все операции, отражаемые на регистрах учета, имеют источником конкретный первичный Документ (как правило заверенный надлежащим образом в твердой копии), вид (тип) и реквизиты которого определяют как количество записей в соответствующих регистрах, так и их содержимое этих записей. Исходя из этого, Проводку следует рассматривать как структурный интерфейс к первичному Документу, для реализации которого идеально подходит так называемый интерфейсный тип значения.
Чтобы использовать Проводку как интерфейсный тип, предварительно ее класс объявляют интерфейсным классом. После чего в классах, образующих домен Финансовый Документ, создают требуемое количество атрибутов, которые типизируют классом Проводка, а затем создают соединители, передающие значения из Документов в Проводку. Объем деклараций можно сократить, используя естественное наследование Документов.
В учетной системе Документ – это событие, характеризующееся моментом возникновения. Помимо календарной Даты, у Документа также, как правило, есть Номер, отображаемое Имя (что-то вроде: "Расходный ордер № ... от …"), а также указатель на класс Global – источник всех глобальных и пользовательских значений по умолчанию, включая номер по порядку.
Первичный Документ может быть не обязательно бухгалтерскими (Приказ по кадрам, Заявка поставщику,... ), но у Финансового Документа всегда есть общая Сумма (документа), "Дата от" (входящая), указатели на Счет (С), Субконто (Х), и как минимум одна Проводка. Все множество типов Финансовых Документов можно чисто условно разделить на "внутренние" и "внешние". "Внешними" документами оформляется взаимоотношение с внешним миром, и таких документов большинство. Поэтому есть смысл еще на уровне Финансового Документа декларировать отношение с классом Контрагент (КА). А так как деловые взаимоотношения сплошь и рядом носят договорной характер, то сразу создадим и отношения с классом Договор (Д).
Соответственно, вершина классовой иерархии документов (классы-прототипы изображены пунктиром) может выглядеть, например, так:
Как мы видим, отдельную группу Документ Записей образуют сложные документы, которые включают в себя однородные Записи Документа, каждая из которых адресует отдельный объект Субконто. Для того, чтобы не перегружать Журнал операций Счета/Субсчета совершенно одинаковыми записями, Записи Документа проецируют относительно Счета на класс Обобщение Записей (ОЗ), где и формируется соответствующая Проводка по Счетам.
Под вывеской "Моно" сгруппированы простые документы, содержащие как правило одну Проводку (или максимум две, например: поступил аванс, и надо отдельно провести еще и сумму НДС). Типичный пример таких документов – Платежное Поручение.
У Платежного Поручения есть две части (условно): "откуда" платим, и "куда" платим.
Платежи осуществляются с Расчетного счета в Банке, поэтому для реализации части "откуда" создается декларация отношения с классом Расчетный счет, наследником класса Субконто. Через объект Global осуществляется автоматическое получение указателя на объект Расчетный счет, с которого по умолчанию выполняются платежи, а также "привязка" объектов Расчетный счет к конкретному объекту Счет.
Наличие различных видов "куда" определяет существование трех классов-наследников: обычного Платежного Поручения (ПП), Налогового Поручения (НП), и Переброска Средств (ПС).
Примечание: Наследников можно и не создавать, ограничившись общим Платежным Поручением, но это приведет к излишнему усложнению визуального интерфейса и диалогов, определяющих и "получателя" платежа, и зависимый от "получателя" набор дополнительных реквизитов.
В Налоговом Поручении "получателем" платежа всегда является КБК налога/сбора, поэтому унаследованный у Финансового Документа указатель на Субконто следует переопределить на его наследника – КБК, что позволяет в диалоге выбора отображать только объекты класса КБК.
В классе Переброска Средств делается аналогичное переопределение типа указателя, но уже на класс Расчетный счет.
Обычным Платежным Поручением реализуются все остальные платежи, получателем которых по определению является конкретный Контрагент (КА). Указатель на объект Контрагент определяется путем реализации диалога выбора. Если Договор, в рамках которого осуществляется платеж, известен, то его выбор осуществляется относительно уже известного Контрагента из списка его обратных ссылок. Далее, через указатель на Договор автоматически получают свои значения указатели на Роль и Счет.
Но бывает и так, что нужного Договора нет и не будет (оплата разовой покупки), или будет, но позже ("Маня, срочно оплатить!"). В этом случае надо относительно Контрагента выбрать нужную Роль, в соответствии с характером хозяйственной операции, или создать ее, при отсутствии.
При создании нового объекта Роль возникает следующий момент. Класс Роль обладает наследниками, и вследствие этого классифицируется как прототип. А прототип не может создавать собственных объектов. Поэтому, в ответ на событие Создать, для класса-прототипа модель данных автоматически предлагает диалог выбора, в котором перечислены классы – конечные типы данного прототипа, и создаст объект выбранного класса. Чтобы такой диалог лишний раз не возникал, можно изначально выполнить так называемое уточнение ссылки. Но в данном случае такое уточнение сделать не представляется возможным, так как тип целевой Роли априори может быть любым. Все сказанное справедливо также и для класса Договор.
Передача в Проводку значений Даты и Суммы ПП, а также полученных указателей на Счета и Субконто выполняется тривиальными соединителями. Но есть еще одна деталь. В качестве указателя на Субконто в Проводку можно передать только один указатель: или на Договор, или на Роль Контрагента. Этот вопрос решается блокировкой передаточной функции соединителя. А так как Договор, как следующая ступень детализации, обладает приоритетом, то указатель на Договор используется в качестве lock-контекста к соединителю, передающему в Проводку указатель на Роль.
Также стоит упомянуть, что если указатель на Договор определяется при уже действующем указателе на объект конкретного наследника класса Роль, то и выбираться он будет не относительно объекта Контрагент, а относительно объекта Роль, в соответствии с правилами зависимости отношений. При этом, если требуемый объект Договор отсутствует в перечне диалога выбора, то непосредственно в нем он может быть создан, причем сразу нужного типа, так как отношения парных наследников классов Роль и Договор уточнялись сразу при их создании.
Набор деклараций Платежного Поручения характерен для всех "внешних" документов ("внутренние" – существенно проще). Но есть один "внешний" документ, достойный отдельного упоминания. Это – так называемый Взаимозачет. Взаимозачет бывает двухсторонний – с одним Контрагентом, или трехсторонний, с участием двух Контрагентов. Декларации Взаимозачета выглядят точно так же, как на предыдущей схеме часть "куда" Платежного Поручения, с тем лишь отличием, что кратность всех атрибутов прямой ссылки увеличена до двух (как это было сделано в классе Проводка).
В случае двухстороннего Взаимозачета кратность отношения с Контрагентом остается прежней.
Учету подлежит любая обладающая ценностью (как правило, в стоимостном выражении) сущность, которую следует рассматривать как учетную единицу, обладающую вещественной количественной характеристикой (Единица измерения + Количество). Учетные единицы могут быть как материальными – Товарно-материальные ценности (ТМЦ) и Основные средства (ОС), так и нематериальными (НМА) – объекты права во всех формах. Каждая такая единица учитывается на балансовом Счете, который определяется в зависимости от ее принадлежности к тому или иному Виду. Кроме того, каждая единица также учитывается по своему текущему Местонахождению, которое может с течением времени меняться, так как учетная единица приобретается, выбывает (списывается или продается), а также перемещается с Места на Место. Применительно к ТМЦ все вышесказанное выразим следующей декларацией:
Классы ТМЦ (Т) и Место (М) создаются как обычные именованные классы. Эти два класса связываются классом-проекцией ТМЦ-на-Месте (Т:М), который будет играть роль основного регистра аналитического учета. Этот регистр будет обобщать все записи о движении ТМЦ, используя для организации обобщения и выборки точно такой же набор атрибутов, как в классах Счет и Аналитика. Отличие от обобщения на Счетах состоит лишь в том, что в целях получения оборотно-сальдовой ведомости вида:
обобщению подлежат, помимо значений Сумма, еще и значения Количества (К). А так как указанный набор дает значение оборотов и остатков только для заданного пользователем диапазона дат, в целях получения сиюминутного остатка в набор добавлены атрибуты, просто суммирующие значения движения ТМЦ.
Класс Вид ТМЦ (ВТ) создается наследованием от Субконто (Х). В процессе первичной настройки приложения, объекты этого класса получают из Global указатели на конкретные балансовые Счета/Субсчета (С), на которых учитываются различные виды ТМЦ. В дальнейшем объекты этого класса используются и в качестве справочника, и как объекты аналитического учета, которые для обобщения значений атрибутов Т:М обладают соответствующим набором атрибутов. И точно такое же обобщение реализуется и в самом классе Т, и в классе-проекции С:М.
Если потребуется более детализированный учет по Месту (а-ля Склад→Кладовка→Полка), то в классы М и Т:М можно добавить рекурсивное отношение. Единица измерения ТМЦ может как выбираться из справочника (класс Е), так и быть просто вводимым значением атрибута. Выбор варианта реализации зависит от предпочтений.
Любое изменение состояния ТМЦ возникает вследствие выполнения Операции с ним.
При декларировании отношения класса Операции с регистрами учета ТМЦ следует сразу вспомнить, что есть среди прочих такая операция как Перемещение ТМЦ, в которой надо одновременно адресовать сразу два Места – "откуда" и "куда". То есть, отношения класса Операция с классами Место и ТМЦ-на-Месте следует сразу сделать двойными, что можно с пользой применить для всех остальных операций, а не только для перемещения. Используем первый элемент ссылочного атрибута в классе Операция для оприходования ТМЦ, а второй – для расхода, и обозначим элементы соответственно: "+" и "–".
Во-вторых, следует учесть различие в порядке формирования указателей для приходных и расходных операций. При оприходовании ТМЦ в первую очередь определяется указатель на объект ТМЦ. Делается это в путем выбора сначала Вида ТМЦ, а затем уже конкретного объекта ТМЦ в выбранном Виде, после чего указатель на объект Т:М определяется автоматически. Если нужного ТМЦ в предложенном перечне (список обратных ссылок в объекте Вид) нет, то его можно создать относительно выбранного ранее Вида.
Расходные операции, также как и приходная, осуществляются относительно уже выбранного Места (Склада,Магазина). Но в отличие от прихода, израсходовать можно только тот ТМЦ, который есть в наличии. Поэтому, в расходных операциях к выбору предлагается перечень объектов класса ТМЦ-на-Месте (из списка обратных ссылок выбранного объекта Место), который дополнительно фильтруется по ненулевому значению текущего остатка. После выбора конкретного объекта Т:М, у него заимствуется указатель на собственно ТМЦ.
Приведенные соображения отражены в следующих декларациях (справа – зависимость атрибутов):
Обращает на себя внимание тот факт, что автоматическое проецирование в Операциях (ОТ) реализовано только для одного из двух указателей на ТМЦ-на-Месте (Т:М). Получить нужные зависимости ссылочных атрибутов можно например так: сначала создать комплект обычных отношений, включая проекцию на класс Т:М, а затем увеличить кратность двух отношений с классами М и Т:М, после чего добавить соединитель, связывающий ссылочные атрибуты Т:М.[T] и ОТ.[Т] через ссылку ОТ.[Т:М].[–].
На схеме связи атрибутов, для соединителей, передающих значения атрибутам с интегральным функционалам (К+, К–, S+, S–), не показан key-контекст от атрибута Date (детальный разбор механизма реализации обобщения значений делался ранее). Также обратим внимание, что один из двух входящих соединителей к атрибуту КО (SO) в классе Т:М обладает флагом инверсии, а значит увеличение или уменьшение значения этого атрибута зависит от того, каким из элементов ("+" или "–") атрибута отношения реализована ссылка объекта класса ОТ на объект (объекты) класса Т:М.
Контурными стрелками на схеме обозначен ввод пользователя. Сумма (S), значение которой определяется произведением Количества (К) на Цену (Ц) также может быть введена пользователем, с соответствующей коррекцией первичной Цены. Такое поведение обеспечивает реверс соединителя, детально рассмотренный здесь.
Указатель (-и) на Место класс Операция получает соединителем из класса Накладная. Соответствующие классы создаются наследованием от упомянутых ранее классов Документ Записи и Запись Документов так, как это показано на схеме:
В свою очередь, классы Накладная и Операция являются прототипами для конечных пар классов, реализующих собственно первичные документы, которыми оформляются реальные хозяйственные операции над ТМЦ.
Основных операций над ТМЦ всего четыре: Поступление , Реализация (продажа), Списание (на различные цели, и в том числе на объекты производства), и Перемещение. Декларации соответствующих этим операциям парных классов, образованных наследованием от классов Накладная (Н) и Операция (О), могут выглядеть так:
После создания каждой пары наследников, унаследованное ими у Н и О отношение подлежит двустороннему взаимному переопределению, что позволяет создавать относительно каждого типа Накладной соответствующего ей типа Операцию. Кроме этого добавляется соединитель, передающий указатель на Место, общее для всей Накладной, в каждую ее Операцию. В Накладной на перемещение кратность указателя на Место увеличивается до двух, и для передачи его значения в Операцию используется групповой соединитель.
Обратим внимание, что по своей логике перемещение ТМЦ не должно порождать Проводок. Их и не возникнет, если в объекте Накладная на перемещение никак не определять указатели на Счет/Субконто. Для остальных трех типов Накладной формирование Проводки осуществляется в соответствии со следующей декларацией:
Каждая Операция (О) получает указатель на Счет (С) из Вида ТМЦ (ВТ), после чего Суммы (S) всех операций с одинаковым значением указателя C обобщаются объектами класса Обобщение Записей (ОЗ). Далее, указатель на Счет учета ТМЦ и обобщенная Сумма передается уже в Проводку (П) – составную часть объекта ОЗ, так как показано в следующей декларации.
Но есть одна проблема. В зависимости от характера (типа) Операции указатель на Счет учета ТМЦ надо передать в один из двух элементов (Дебет или Кредит) аналогичного указателя в Проводке. Решается эта проблема следующим образом. Создается два класса-наследника от класса Обобщение Записей, в каждом из которых создается свой соединитель для передачи указателя в дебет или кредит Проводки:
Теперь остается только "уточнить" тип атрибута прямой ссылки унаследованного отношение с классом ОЗ в каждом конкретном классе-наследнике Операции, выбрав требуемого наследника.
Но Счет ТМЦ и Сумма – это только часть значений, необходимых для получения полноценной Проводки. Источником указателей на второй Счет/Субконто, а также Дату, в нашем случае является Накладная. Как и Счет учета ТМЦ, эти значения можно передать в Проводку также через объект Обобщение Записей, предварительно создав в классе ОЗ промежуточные атрибуты. Но есть и более интересное решение.
Если связать отношением Проводку и Финансовый Документ, как на следующей декларации,
то в каждом документе все Проводки будут размещены в одном списке, а во-вторых, единственный соединитель обеспечит передачу значение Даты документа каждой Проводке. Но самое приятное состоит в том, что в каждом конечном классе можно создать соединитель, передающий указатели на Счет/Субконто в нужный элемент (Дебет или Кредит) Проводки через общее унаследованное отношение. Механизм реализации такой передачи детально рассмотрен здесь.
Как известно, операции по покупке и продаже включают в себя НДС. Собственно декларация атрибутов, реализующая формирование значения Сумма НДС достаточно тривиальна, даже с учетом обратного реверса в атрибут Ставка НДС, значение по умолчанию которого заимствуется из Global. Декларация интерфейса с классом Проводка, объект которого можно использовать для отражения Суммы НДС на регистрах учета, была создана еще на уровне Финансового документа. В качестве указателя на первый регистр учета используется уже определенный ранее указатель на объект Субконто Контрагента. Для получения второго указателя необходимо в классе Приходная накладная (Н+) / Накладная на реализацию (НР) создать отношение с классом НДС полученный / НДС выставленный, и из Global заимствовать соединителем значение указателя по умолчанию, заданное при первичной настройке. После чего в каждом из указанных классов Накладной (Н+ / НР) необходимо создать свой комплект соединителей, передающих требуемые значения в Проводку.
Хозяйственные операции с Услугами, оказываемыми сторонними Контрагентами, или предоставляемыми организацией сторонним Контрагентам, в учете выглядят практически так же, как и операции с ТМЦ. С той лишь разницей, что услуги не складируются, а также не перемещаются и не списываются.
В данном случае Единицами учета являются как вещественные объекты – Основные средства (ОС), так и Нематериальные активы (НМА) – права в различной их форме. Эти единицы учета описываются общим классом – Амортизируемое Имущество (АИ), без особой необходимости создания отдельных классов-наследников, так как набор реквизитов вещественных и нематериальных объектов в общем-то идентичен.
Единицы учета принадлежат Объектам учета, которые представлены тремя наследниками класса Субконто: Вид ОС и НМА (ВО), Вид износа (ВИ) и Вид затрат (ВЗ), которые образуют одноименные справочники.
Класс-проекция Износ:Затраты (ИЗ) сводит в общий список объекты АИ с указателями на одинаковые пары объектов учета ВИ и ВЗ.
В течение жизненного цикла объекты АИ приобретаются, вводятся в эксплуатацию и списываются. Все эти операции оформляются соответствующими документами, которые в общем-то аналогичны рассмотренным ранее документам, формализующим движение ТМЦ. Но кроме этого, объекты АИ являются источником амортизационных отчислений, сумма которых рассчитывается ежемесячно, а собственно сам расчет оформляется соответствующим документом. Декларация отношений классов, реализующих периодическое формирование амортизационных отчислений и отнесение их на регистры учета, выглядит так:
Объекты класса Отчетный Месяц (ОМ), обладающего внутренним цепочечным отношением, образуют последовательность Расчетных периодов. Классы Документ Амортизации (ДА) и Запись Амортизации (ЗА) образованы наследованием от классов-прототипов Документ Записей и Запись Документа, по аналогии с Накладными и Операциями в учета ТМЦ. В классах ДА и ЗА унаследованное отношение с Субконто (Х) переопределено на отношение с классами ВИ и ВЗ, которые в свою очередь выступают как источники указателей на класс Счет (С), необходимых для формирования Проводки (П).
Объекты класса Расчет Амортизации (РА) фиксируют для каждой учетной единицы АИ Сумму Амортизации, начисленную в соответствующем периоде. Эта сумма затем обобщается на уровне документов (объектов ДА) и их записей (объектов ЗА), и передается в Проводку.
Для того, чтобы в каждом расчетном периоде автоматически создать логически связанное множество объектов РА→ЗА→ДА по образцу уже существующего множества объектов АИ→ИЗ→ВИ, используется механизм воспроизводства (дублирования) множества, подробно рассмотренный здесь. На приведенной схеме требуемые декларации обеспечивают соединители, попарно связывающие зависимостью соответствующие атрибуты обратной ссылки. То есть, при создании объекта Отчетный Месяц (точнее, при автоматическом получении этим объектом указателя на объект Global), объект ОМ создаст столько объектов Документ Амортизации (ДА), сколько существует Видов Износа (ВИ). В свою очередь каждый создаваемый объект ДА, обладая аналогичной декларацией, создаст множество объектов ЗА, а те в свою очередь – собственные множества объектов РА. При этом механизм воспроизводства множества для каждого создаваемого им объекта автоматически реализует связь с парным ему объектом в дублируемом множестве (горизонтальное отношение на схеме). А для того, чтобы не создавать "пустых" объектов (например, объект АИ списан, или амортизация для него уже не начисляется), в качестве источника воспроизводимого множества соединитель адресует не основной список обратной ссылки, а дополнительный, в декларациях которого в качестве lock-контекста использован логический атрибут "не актуален".
Объект ОМ, "вершину" иерархии объектов, удобно создавать не как обычно, а как продолжение цепочки относительно уже существующего объекта ОМ. Для этого достаточно одному из атрибутов внутреннего отношения назначить функционал Autoset, и далее, для инициации создания объекта, использовать событие Update в адрес этого атрибута.
Как уже упоминалось, объект РА всего лишь фиксирует Сумму Амортизации, расчет которой предварительно осуществляется в объекте АИ, в соответствии со следующей упрощенной декларацией:
где S – стоимость приобретения, SI – первоначальный износ, SO – остаточная стоимость.
Те, кто внимательно читал описание, возможно заметили, что класс Аналитика, столь подробно рассмотренный в разделе "Аналитика счета" и далее по тексту, по сути не несет никакой полезной нагрузки. Если каждый объект в иерархии Субконто будет обладать актуальным указателем на Счет (а это автоматически обеспечивается настройками через Global), то каждому объекту Аналитика будет соответствовать ровно один объект Субконто. Соответственно, если перенести в класс Субконто атрибуты, обеспечивающие сбор, выборку и обобщение сумм Проводок, то класс Аналитика может быть исключен.
Поэтому, вернемся к самому началу рассмотрения предметной области, и предположим, что существует некоторый универсальный Регистр учета, на страницах которого из одной Проводки синхронно делаются две записи:
Регистр (Р) изначально обладает свойством само-детализации (рекурсией), которое наследуется Счетом (С) и Субконто (Х). Счет и детализирующее его Субконто связывает множественное отношение, через которое, как и через рекурсию, обобщаются итоговые значения сумм оборотов и остатков. Обязательность принадлежности каждого Субконто конкретному Счету обеспечивается на уровне каждого наследника Субконто путем первичного задания указателей в классе Global.
Каждым своим ссылочным элементом Проводка адресует или Счет (при отсутствии у него детализации), или Субконто (при наличии таковой). При этом две взаимодействующие между собой объектные иерархии, образованные рекурсиями Счетов и Субконто, с точки зрения Проводки выглядят как одна общая иерархия. Вопрос о том, какой именно указатель передать в Проводку решается на уровне каждого отдельного типа финансового документа.
Закономерный вопрос: зачем же автор, заранее зная результат, тем не менее вставил класс Аналитика в исходное описание? Ну, прежде всего с целью показать несомненную пользу старых добрых инструментов эскизного проектирования – карандаша и бумаги. А во-вторых, " … ты бы никогда не увидел пирамид" – надо было дать пример реализации и исполнения рекурсивной проекции. Ну и не стоит забывать, что вариант с Аналитикой хоть и избыточен, но вполне боеспособен, и использовался для практической реализации учета. Этот вариант прилагается к настоящей статье в качестве живого примера с возможностью изнутри исследовать приложение, созданное в парадигме, вынесенной в заголовок статьи.
Если судить по результату, принимающему типичный вид законченного приложения, то происходящее вполне подпадает под определение "Программирование". Просто выглядит непривычно как-то.