Analysis Patterns 1.0 Help

6. Инвентаризация и учет

Большая часть коммерческих компьютерных систем предназначена для отслеживания движения денег предприятия, т.е. регистрации как они зарабатываются и тратятся. Фундаментальная идея бухгалтерского учета и отслеживания запасов заключается в существовании различных «корзин» с деньгами и товарами. Необходимо фиксировать движение денег и товаров между этими корзинами.

Модели инвентаризации и учета, описанные в этой главе, родились из этой фундаментальной идеи. Они представляют собой основной набор концепций, которые могут использоваться в качестве основы для финансового учета, инвентаризации или управления ресурсами. Паттерны не описывают эти процессы напрямую, они скорее описывают базовые идеи, на основе которых можно строить процессы. В главе 7 описан простой пример, в котором эти идеи используются для выставления счетов за телефонные звонки.

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

Первый шаблон — это счет (6.1). На счете хранятся ценные вещи — товары или деньги, — которые можно добавлять или удалять только с помощью записей (проводок). Они обеспечивают историю всех изменений на счете. Когда мы используем счет для записи истории изменений ценных вещей, важно следить за тем, чтобы предметы не потерялись. Транзакции (6.2) добавляют еще одну степень проверяемости, связывая проводки вместе. В транзакции предметы, снятые с одного счета, должны быть помещены на другой; предметы не могут быть созданы из ниоткуда или уничтожены.

Существует два вида транзакций: Простая (двухуровневая) транзакция — операция перемещает сумму с одного счета на другой. Сложная транзакция может иметь проводки для нескольких счетов, пока транзакция в целом сбалансирована по приходу\расходу, т.е. сумма всех проводок равна нулю.

Счета можно объединять в группы с помощью суммарного счета (6.3), который применяет большинство функций отчетности к группам счетов. Иногда нам нужно сделать записи по счетам, которые не предназначены для поддержания баланса. С этой задачей справляется счет-памятка (6.4).

Счет может содержать фиксированные правила, определяющие порядок перевода сумм между счетами. Правила проводок (6.5) позволяют нам создавать активные сети счетов, которые обновляют друг друга и отражают бизнес-правила. Для этого экземпляры правил проводок должны иметь свои собственные исполняемые методы, и это требование вводит важную концепцию моделирования метода индивидуального экземпляра (6.6). Методы индивидуальных экземпляров могут быть реализованы с помощью: некоторой комбинации одного подтипа, шаблона стратегии, внутреннего оператора case, интерпретатора и параметризованного метода.

Шаблон выполнения правил проводки (6.7) описывает способы запуска правил проводки:

  • во время создания транзакции;

  • путем запроса учетной записи на обработку ее правил;

  • путем запроса на срабатывание правила проводки;

  • или путем запроса учетной записи на обновление, что приводит к срабатыванию связанных счетов обратном порядке (не от «головы» — запрошенного счета, а с низовых узлов (счетов) которые связанны с текущим счетом правилами проводки, т.е. обновлениями).

Чтобы использовать правила проводок со многими счетами, нам нужен способ определения правил проводок для многих счетов (6.8). Один из способов — использовать уровень знаний, в этом случае правила проводок определяются для типов счетов. Другой способ — связать правила проводок с суммарными счетами.

В бухгалтерской системе различным объектам нужны подмножества записей по счету и их остатков, и для того, и для другого требуется шаблон выбора записей (6.9). Этот шаблон полезен, когда мы хотим выбрать объекты из многозначного отображения (различных типов счетов). Доступные опции — вернуть весь набор и позволить клиенту сделать выборку, добавить дополнительные операции к счету или использовать фильтр счета.

Мы можем разделить большие сети правил проводок на группы с помощью шаблона бухгалтерской практики (6.10). В длинных расчетах нам часто приходится возвращаться назад, чтобы понять, почему те или иные операции привели к текущей ситуации; тогда нам нужно использовать схему источников записи (6.11).

В балансовых отчетах и отчетах о прибыли (6.12) различают счета для инвентаризации и для расходных материалов. Люди могут видеть один и тот же счет несколько иначе; например, то, как я вижу свой банковский счет, вероятно, схоже с тем, как его видит мой банк. Один из них является корреспондентским счетом (6.13) другого.

Полученные шаблоны достаточно абстрактны; для применения их в повседневной практике в конкретных случаях требуется специализированная модель счета (6.14). Такие счета разрабатываются путем подтипизации общих шаблонов учета.

Последний шаблон в этой главе описывает учет проводок по нескольким счетам (6.15). Этот шаблон полезен, когда существует более одного способа отразить в отчете следы проводок. Два альтернативных способа — это использование проводок или использование производных счетов. Мы можем использовать производные счета вместо шаблонов учета, если нам нужны отчеты поведения счетов, но не возможности балансировки и аудита.

Эти модели — результат идей, возникших в ходе нескольких проектов. Они возникли в ходе работы над системой обслуживания клиентов для американской коммунальной компании и получили дальнейшее развитие при изучении структуры бухгалтерского учета для международной телекоммуникационной компании. Кроме того, в моделях использован опыт недавней разработки системы расчета заработной платы для крупной американской производственной компании.

Ключевые понятия

Счет, транзакция, проводка, правило проводки

6.1 Счет (Account)

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

Счет похож на раннее рассмотренный шаблон Количество со всеми его атрибутами, но с добавлением записи для каждого изменения его значения, как показано на рисунке 6.1. Баланс, который представляет собой текущее значение счета, является чистым результатом всех проводок, связанных со счетом. Это не означает, что баланс нужно пересчитывать каждый раз, когда он запрашивается. Производные значения можно кэшировать, хотя кэш будет невидим для пользователя счета. Используя записи счета, клиент может определить изменения за определенный период времени и общую сумму пополнений или снятий (см. раздел 6.9). Знак на сумме указывает на характер записи: депозит или снятие средств. Выписка — это список всех записей, которые были сделаны по счету за определенный период времени.

6.1.png

Проводки фиксируют каждое изменение на счете.

Рисунок 6.1. Счет и проводка.

Пример: Я снимаю 100 долларов со своего расчетного счета. Это представлено в виде записи с суммой -$100, прикрепленной к моему расчетному счету.

Пример: Я покупаю в магазине 4 пачки стандартной писчей бумаги. Магазин отражает это как запись на своем счете стандартной писчей бумаги с суммой -4 пачки.

Пример: В январе я использовал 350 кВт-ч электроэнергии. Это отражается в виде записи с суммой 350 кВт-ч на моем счете использования электроэнергии в быту.

Один из способов, которым реализация может вычислить баланс — по коллекции проводок сформировать коллекцию количеств. Для этого в Smalltalk есть специальная операция collect. Опасность заключается в том, что операция collect собирает объекты в коллекцию того же типа, что и исходная. Таким образом, если выполнить collect над набором проводок, то получится набор количеств. Наборы не допускают дублирования, поэтому если у нас есть две записи с одинаковым количеством, то учитывается только количество первой записи, что приведёт к неверному балансу. Для формирования коллекций фундаментальных значений часто лучше использовать пакет, который допускает дубликаты. В C++ эта проблема встречается реже, поскольку операции collect менее распространены и более сложны в использовании; вместо них пользователи C++ используют внешний итератор (шаблон проектирования из GoF), который не имеет такой проблемы. Однако в качестве проверки тестовые примеры всегда должны включать записи с равными суммами (а также записи, у которых все атрибуты равны).

На рисунке 6.1 показаны две временные точки для записи: одна указывает на момент списания (резервирования), а другая — на момент фактического занесения на счет. Это особенно важно, когда происходит ретроактивное списание. Цена списания может измениться между датой списания и датой резервирования, поэтому необходимы обе даты. Нам необходимо знать как историю событий, так и наши знания об этой истории (см. раздел 15.3.1). Временные точки включают время и дату, хотя многие приложения довольствуются только датой.

Пример: Я обедал в кафе Jae's 1 апреля. Компания, обслуживающая кредитную карту, получает уведомление об оплате 4 апреля. Запись имеет дату резервирования 1 апреля и дату фактического списания 4 апреля.

6.2 Транзакции (Transactions)

Использование проводок помогает вести учет изменений на счете. Эти изменения обычно связаны с перемещением какого-либо предмета с одного счета на другой. Когда я снимаю деньги со своего банковского счета, я добавляю их в свой кошелек, или счет наличных. Для многих предметов недостаточно просто записывать их приход и расход; нужно также записывать, откуда они приходят и куда уходят.

Как показано на рисунке 6.2, операция помогает четко связать снятие средств с одного счета с пополнением другого. Подход, основанный на двойной записи, отражает базовый принцип бухгалтерского учета, согласно которому деньги (или что-либо другое, что мы должны учитывать) никогда не создаются и не уничтожаются, они просто перемещаются с одного счета на другой.

6.2.png

Рисунок 6.2. Транзакция с двумя проводками.

Пример: Я использую свою кредитную карту, чтобы заплатить Boston Airlines 500 долларов за авиабилет. Это операция со счета кредитной карты на счет Boston Airlines с суммой 500 долларов. Позже я проведу операцию с моего расчетного счета на счет кредитной карты, чтобы свести баланс счета кредитной карты к нулю.

Пример: Компания Aroma Coffee Makers (ACM) перевозит 5 тонн аравийского мокко из Нью-Йорка в Бостон. Это транзакция со счета в Нью-Йорке на счет в Бостоне с суммой 5 тонн.

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

6.2.1 Сложные транзакции (Multilegged Transactions)

Из рисунка 6.2 следует, что каждая транзакция состоит из одного снятия и одного соответствующего пополнения. На самом деле в одной транзакции может быть много снятий и вкладов. Скажем, я получаю $3000 от Megabank и $2000 от Total Telecommunications. Я решаю положить оба чека на свой расчетный счет. В моей банковской выписке будет указан кредит в размере $5000. Обратите внимание, что, хотя на мой банковский счет поступили два чека, в выписке указана одна запись. Эта транзакция представлена сложной моделью транзакций, показанной на рисунке 6.3. Минимальная размерность связи от транзакции к проводке теперь 2. Главным правилом является то, что проводки должны быть сбалансированы по отношению ко всей транзакции, но соответствие между отдельными проводками не требуется. Таким образом, я могу смоделировать ситуацию с моим банковским счетом с помощью транзакции, состоящей из трех проводок: [счет: расчетный счет, сумма: $5000], [счет: Megabank, сумма: ($3000)], [счет: Total Telecommunications, сумма: ($2000)]. Эта операция отвечает за то, чтобы деньги не создавались и не уничтожались.

6.3.png

Они обеспечивают большую гибкость при заключении сделок, чем двуногая модель.

Рисунок 6.3. Сложные транзакции.

Пример: Компания Aroma Coffee Makers вывозит 5 тонн кофе «Ява» из Нью-Йорка и отправляет 2 тонны в Бостон и 3 тонны в Вашингтон. Это одна операция с тремя записями: [счет: Нью-Йорк, -5 тонн], [счет: Бостон, 2 тонны], [счет: Вашингтон, 3 тонны].

Простая модель транзакции — это частный случай сложной модели транзакции, в которой она имеет только две проводки. В некоторых приложениях преобладает простая модель, и мы имеем схему, похожую на рис. 6.4. В других приложениях может быть большое количество сложных транзакций. Я бы рекомендовал использовать подход со сложными транзакциями, потому что он обеспечивает большую гибкость. Простые транзакции могут быть легко созданы специальной операцией создания сложных транзакций, что является полезным удобством. Остальная часть этого обсуждения предполагает сложную модель транзакций.

6.4.png

Эту модель можно встретить там, где все транзакции являются простыми. Она обладает теми же возможностями, что и рисунок 6.2. Однако я бы предпочел использовать рисунок 6.2, поскольку его легче перенести на модель сложных транзакций.

Рисунок 6.4. Модель простой транзакции, в которой не используются проводки.

Если вы теряетесь в нотации некоторых диаграмм, то обратитесь к полному описанию в приложении А или к его сокращенной версии в приложении C

    Взаимообязательства между транзакцией и записью создают проблему курицы и яйца. Я не могу создать проводку без создания транзакции из-за ограничений. Точно так же я не могу создать транзакцию без проводки, потому что транзакция аналогично ограничена.

    Одно из решений — предоставить транзакции операцию создания, которая принимает список частично определенных проводок или даже список массивов с соответствующими аргументами. Операция создания проводки была бы приватной, но доступной для создания транзакции. Тогда создание транзакции будет единственным местом, которое может создавать проводки. Очевидно, что во время выполнения этой операции создания объекты будут нарушать свои ограничения. Однако правило работы с ограничениями заключается в том, что открытые операции должны завершаться с выполнением всех ограничений [5]. Если обнародовать только процедуру создания транзакции, то это правило может быть выполнено.

    6.3 Сводный счет (Summary Account)

    В системе счетов часто бывает полезно группировать счета вместе. Например, я могу объединить свои счета Total Telecommunications и Megabank в счет доходов от бизнеса. Аналогично я хочу отнести арендную плату и питание к личным расходам, а командировочные и офисные расходы — к деловым расходам. Такую структуру можно поддерживать с помощью простой иерархии детальных и сводных счетов, как показано на рисунке 6.5.

    6.5.png

    Сводный счет может состоять как из сводных, так и из детальных счетов. Это образует иерархию, в которой детальные счета являются листьями (пример составного счета [1]). Записи сводного счета получаются из записей компонентов счета рекурсивным способом.

    Рисунок 6.5. Сводные и детальные счета.

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

    Пример: У меня есть суммарный счет для авиаперевозок с детализированными счетами для авиаперевозок Megabank и Total Telecommunications авиаперевозок.

    Пример: Компания Aroma Coffee Makers имеет общий счет для кофе сорта Ява с подробными счетами для каждого склада. Таким образом, компания может узнать общее количество кофе сорта Ява, которым она владеет.

    Обратите внимание, что отношения между компонентами должны быть отмечены, чтобы показать, что это иерархия. Кардинальности недостаточно, чтобы обеспечить это ограничение. В этой структуре не должно быть циклов.

    Разделение между суммарными и детальными счетами довольно распространено в бухгалтерском учете, но оно не является абсолютно необходимым. В модели на рисунке 6.6 это различие устранено. В этом случае запись может быть сделана на любой счет, а все счета могут быть размещены в иерархической структуре. Этого можно достичь предоставив два отображения от счета к проводке: одно, чтобы показать, какие проводки размещены на данном уровне, и другое, чтобы сложить проводки на субсчетах. Первое будет обновляемым, второе — производным, не обновляемым и используемым для баланса, отчетов и других функций, которые в модели на рисунке 6.5 были у супертипа.

    6.6.png

    Мы можем использовать эту модель для создания проводок~~~~ по суммарным счетам.

    Рисунок 6.6 Иерархия счетов без разделения суммарных и детальных счетов.

    До сих пор мы придерживались условностей, согласно которым счета должны быть расположены в иерархии, а проводки должны быть отнесены только к одному счету. Мы будем придерживаться этих предположений некоторое время, но позже в разделе 6.15 мы рассмотрим некоторые альтернативные возможности.

    6.4 Счет-памятка (Memo Account)

    Бенджамин Франклин однажды сказал: «В этом мире ничего существующего наверняка, кроме смерти и налогов». Мы не можем избавиться от боли, связанной с уплатой налогов, но я считаю, что эта боль несколько уменьшается, если избегать сюрпризов в своей налоговой декларации. Каждый раз, когда я зарабатываю деньги, часть из них идет на счет налоговых обязательств. В этом случае мне известно, какая часть денег действительно принадлежит мне и сколько я должен государству.

    Заметьте, что при таком подходе никакие реальные деньги не перемещаются. Нет никакого платежа с моего расчетного счета до тех пор, пока я не заплачу налог. Более того, в моей налоговой категории объединены налоги штата и федеральные налоги. Когда я действительно заплачу, то проведу операции с моего расчетного счета на счета федерального налога и налога штата. В процессе этого мне нужно уменьшить свой счет налоговых обязательств на те же суммы, но опять же никакие деньги не перемещаются между реальными счетами (расчетный счет, федеральный налог, налог штата) и этим счетом налоговых обязательств. Этот счет служит для меня памяткой о том, сколько денег я должен заплатить в виде налогов, поэтому он называется счетом памятки.

    На счете-памятке хранятся денежные суммы, но не реальные деньги. Важно, чтобы реальные деньги не перетекали с такого счета или на него. Поэтому в моем примере с налогами, когда я перевожу деньги с доходного счета на расчетный счет, я одновременно делаю проводку на счете-памятке налоговых обязательств. Он становится еще одним подтипом счета, и я должен убедиться, что операции не перемещают деньги между этим и другими счетами. Это можно сделать, убедившись, что ограничение баланса транзакции исключает счета-памятку из расчетов.

    При использовании транзакций всегда нужно помнить и гарантировать, что новые деньги не создаются и не уничтожаются. Это означает, что при создании проводки на счету налоговых обязательств где-то должна быть сделана балансирующая запись. Поскольку бывает трудно понять, какой счет будет разумным местом для этой записи, бухгалтеры часто создают контрсчет. Таким образом, у счета налоговых обязательств будет контрарный счет налоговых обязательств, который выступает в качестве другого конца всех записей по счету налоговых обязательств, будь то снятие или внесение средств. Такой подход можно использовать и в обычной модели, но это не является строго необходимым. Если ограничение проверки баланса игнорирует счета-памятки, то допускаются односторонние записи по ним. Контрарный счет всегда может быть создан автоматически. Такой подход означает, что нижняя граница отображения от транзакции к записи может быть уменьшена до 1.

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

    Конечно, если мы не используем транзакции, мы не столкнемся с проблемами баланса и сможем без проблем создавать проводки, но опасность заключается в том, что реальные деньги могут легче просочиться на счета-памятки (или в воздух).

    6.5 Правила проводок (Posting Rules)

    С помощью счета-памятки я могу сделать проводку по счету налоговых обязательств, но мне все равно придется не забыть сделать это. Поскольку я всегда вношу 45% от каждого комиссионного дохода на счет налоговых обязательств, компьютерная система должна делать это за меня автоматически.

    Необходимо правило, которое следит за конкретным счетом и, увидев проводку, создает другую проводку согласно установленным правилам. Простой пример такого правила показан на рисунке 6.7. Правило проводки описывается путем указания счета в качестве триггера. Любая проводка на триггерном счете приводит к созданию новой проводки, которая представляет собой значение исходной проводки с некоторым множителем.

    6.7.png

    Для каждой проводки на триггерном счете мы делаем проводку на целевом счете в размере величины триггерной проводки, умноженной на коэффициент.

    Рисунок 6.7. Простая структура для размещения правил, умножающих на коэффициент.

    Пример: Мое налоговое обязательство может быть обработано правилом проводки, в котором счет комиссионного дохода является триггером, счет налогового обязательства — выходом (целью), а множитель равен 0,45.

    Умножение на скаляр позволяет решить ряд полезных ситуаций для правила проводки, но процесс может легко усложниться. Рассмотрим растущий подоходный налог: Первые 300 фунтов стерлингов не облагаются налогом, следующие 2500 фунтов стерлингов облагаются 20% налогом, остальные — 40%. Простого скалярного множителя уже недостаточно. Мы хотим, чтобы правила проводки могли иметь произвольный алгоритм, что обеспечит нам максимальную гибкость.

    Чтобы придать правилам проводки такую гибкость, мы должны связать расчет с каждым экземпляром правила проводки, поскольку каждое правило будет по-своему рассчитывать сумму новой записи. Концептуально это означает, что каждый экземпляр правила проводки должен иметь свой собственный метод для выполнения вычисления, как показано на рис. 6.8. Эта гибкая нотация скрывает существенную проблему. Основные объектные системы позволяют варьировать поведение за счет полиморфизма и наследования, но эта система основана на классах: Поведение меняется в зависимости от класса объекта. Мы же хотим, чтобы поведение менялось для каждого отдельного экземпляра, а это требует шаблона индивидуальных методов экземпляра, как обсуждалось в разделе 6.6. (Я обсуждаю аналогичную проблему в разделе 9.2.)

    6.8.png

    Эта нотация говорит о том, что каждый экземпляр правила проводки имеет свой собственный метод расчета.

    Рисунок 6.8. Правила проводки с методами вычисления значений для записей.

    6.5.1 Обратимость

    Важным свойством правил проводки является то, что они должны быть обратимыми. Обычно мы не можем удалить неправильную проводку, потому что она либо стала частью платежа, либо появилась в чеке. Единственный способ устранить ее последствия — ввести обратную запись, которая представляет собой идентичную, но противоположную по знакам проводку. Таким образом, любое правило проводки должно гарантировать, что две идентичные, но противоположные по знаку записи будут помещены на триггерный счет и полностью аннулируют друг друга при дальнейшей обработке. Мы можем проверить реверс, вставив такие противоположные пары в процедуры для правила проводки и убедившись, что их выходные суммы также равны и противоположны.

    6.5.2 Отказ от транзакций

    На некоторых счетах почти все операции формируются на основе правил проводок. Входные счета используются для записи первоначальных записей из внешнего мира. Все дальнейшие проводки по счетам формируются на основе правил проводок. Риск отсутствия транзакций снижается, поскольку все проводки предсказуемы на основе начальных записей и правил проводок. Ответственность за проверку того, что ничего не утекает, переносится с оперативного использования системы на разработку правил проводок. Если мы удалим транзакции, то все равно будет полезно сохранять информацию о причинно-следственных связях между проводками. В целом я предпочитаю сохранять транзакции, потому что они облегчают аудит небольшой ценой дополнительных расходов. Если вы не используете транзакции, вам все равно понадобится какой-то механизм аудита.

    6.6 Индивидуальный метод экземпляра (Individual Instance Method)

    Концептуальная модель должна представлять ситуацию как можно более естественно для удобства эксперта в данной области. Мы должны максимально снизить зависимость от конкретной среды реализации. Компьютерный дизайн должен отражать человеческое мышление, а не наоборот. Эта философия отражена в диаграмме, показанной на рисунке 6.8. Определив концептуальную модельную конструкцию, мы должны придумать общий способ ее реализации. Следовательно, вопрос заключается не в том, «Как нам поместить вычисления в отдельные правила проводки?», а в том, «Как нам прикрепить методы к экземплярам?». Это следует трансформационному подходу, который обсуждается в главе 14. Мы хотим иметь несколько способов реализации модели на рисунке 6.9, которая скрывается за одним интерфейсом. Это следует главному принципу проектирования на основе шаблонов: Модель должна определять интерфейс классов. Мы должны иметь возможность менять реализации без изменения интерфейса.

    6.9.png

    Рисунок 6.9. Использование классов-синглтонов для реализации отдельных методов экземпляра.

    6.6.1 Реализация с помощью класса-одиночки

    Естественный способ варьировать поведение — использовать полиморфную операцию, основанную на дочерних классах. Самый простой способ сделать это — создать подкласс правила проводки для каждого экземпляра правила проводки, создав таким образом несколько классов-синглтонов. В этом случае все стандартные методы и свойства хранятся в правиле проводки, а подтипы просто реализуют различные методы calculateFor().

    Основная проблема такого подхода заключается в том, что подтипы являются довольно искусственными. Они существуют только потому, что мы не можем варьировать CalculateValue по экземплярам. Эта искусственность делает подход менее чем идеальным. Другая проблема заключается в том, что такой подход приводит к появлению множества классов, что заставляет некоторых людей чувствовать себя довольно неловко. Классы не представляют собой особенно большой проблемы, потому что классы и малы, и очень ограничены. Методы вычислений можно использовать совместно, манипулируя иерархией классов. Однако операция обработки правила проводки также может стать жертвой полиморфизма, и два полиморфизма могут не совпадать.

    6.6.2 Реализация с помощью шаблона стратегии

    На первый взгляд реализация паттерна стратегии [1], показанная на рисунке 6.10, очень похожа на паттерн, использующий синглтоны. Основное отличие заключается в том, что на рисунке 6.10 подтипирование выполняется на отдельном объекте метода или стратегии. Правило проводки проще, потому что отпадает весь вопрос выбора метода. Правило проводки просто знает, что оно может попросить объект метода выполнить вычисление.

    6.10.png

    Рисунок 6.10. Использование паттерна «стратегия» для реализации отдельных методов экземпляра.

    На рисунке 6.11 показаны взаимодействия, происходящие в одном из примеров. Счет просит правило проводки обработать его. Правило проводки получает все проводки, которые не были обработаны этим правилом (см. раздел 6.7.2). Для каждой из этих проводок оно вызывает свой метод, чтобы вычислить значение новой проводки. Методу может потребоваться дополнительная информация; например, налоговые ставки часто различаются в зависимости от того, состоит человек в браке или нет. Он передает результат обратно в правило проводки, которое затем создает новую проводку.

    6.11.png

    Метод получает всю необходимую информацию, обращаясь к введенной проводке.

    Рисунок 6.11. Диаграмма взаимодействия при использовании паттерна стратегии.

    Следует подчеркнуть, что этот объект метода не является «свободной подпрограммой», как в функциональных конструкциях (или некоторых ОО-подходах). Метод инкапсулирован внутри правила проводки, поскольку только оно может ссылаться на него и использовать его.

    Методы правил проводки могут совместно использоваться объектами. Примером такого метода является метод фиксированного налога, который применяет фиксированную ставку налога с некоторыми стандартными вычетами. Если метод одинаков для нескольких видов налогов, а меняется только ставка налога, то можно разработать метод, который запрашивает правило проводок для своей фиксированной ставки, но в остальном позволяет повторно использовать обработку. Этот метод можно рассматривать как нечто среднее между индивидуальным объектом метода и реализацией параметризованного метода (см. раздел 6.6.4).

    Разновидностью этого подхода в Smalltalk является использование блока в качестве метода. Таким образом мы избавляемся от необходимости создавать новый класс метода и устраняем подклассы класса метода. Блоки элегантны в использовании, но могут быть очень сложны в отладке: Если в коде блока возникает ошибка, бывает трудно понять, что происходит. Однако если блок простой, такой подход может работать очень хорошо.

    6.6.3 Реализация с внутренним оператором case

    Столкнувшись с необходимостью создавать подклассы только для работы с одним полиморфным методом, мы можем задаться вопросом, зачем нам это нужно. Вместо этого мы можем иметь ряд частных операций для правила проводки: computeFederalTax, computeMassTax, computeSalesCommision и так далее. Тогда единственный computeFor() для правила проводок будет содержать простой оператор case, который выбирает, какой частный метод использовать в зависимости от того, какой экземпляр является получателем, как показано на рисунке 6.12.

    6.12.png

    Это не является нарушением объектно-ориентированных принципов, если оператор case инкапсулирован внутри правила проводки.

    Рисунок 6.12. Использование внутреннего оператора case для метода отдельного экземпляра.

    + — обозначает публичный метод
    - — обозначает приватный метод

      Разработчики объектов обычно отмахиваются от идеи использования подобных операторов case, но в данной ситуации это очень полезно. Модификация этой реализации означает добавление новой приватной операции и добавление пункта в оператор case. Это не сильно отличается от новых подклассов, требуемых при реализации стратегии или синглтона. Если количество методов велико, то мы имеем большой (но простой) оператор case или большое количество подклассов. Таким образом, это компромисс между управлением большим количеством классов синглтонов и необходимостью изменять оператор case при каждом новом правиле проводки.

      6.6.4 Реализация с помощью параметризованного метода

      Стратегия параметризованных методов использует один метод в правиле проводки и обрабатывает различное поведение с помощью параметров самого правила проводки или связанных классов. Например, если все проводки используют единый процент, то правило проводки может содержать этот процент, и достаточно одного метода, который вычитает этот процент, как показано на рисунке 6.13. Если в некоторых правилах проводок установлены разные проценты для женатых и незамужних, то в правиле проводок можно указать ставку для женатых и незамужних, и метод будет запрашивать у сотрудника семейное положение, а затем использовать соответствующую ставку.

      6.13.png

      Рисунок 6.13. Использование параметризованного правила постинга.

      Эта стратегия работает, если все вариации вычислений можно уловить, изменяя несколько параметров. В таких случаях мы должны моделировать именно так. Индивидуальные методы экземпляра применяются только в том случае, если ситуация сложнее. Это одна из возможных реализаций, потому что в некоторых случаях мы можем сочетать параметризацию с другой техникой.

      6.6.5 Реализация с помощью интерпретатора

      Если метод простой, то мы можем хранить его в виде строки на простом языке и создать для него интерпретатор. Каждый экземпляр метода хранит свою конкретную строку, а класс метода может интерпретировать эту строку (возможно, используя паттерн интерпретатора [1]).

      Хорошими кандидатами для такой реализации являются методы, использующие простые формулы, в которых применяются арифметические операторы, круглые скобки и пара простых функций. Если язык прост, создать интерпретатор не составит труда. Единственное ограничение — это то, что может быть выражено на таком языке.

      6.6.6 Выбор реализации

      Все реализации работают хорошо и могут быть скрыты за одним методом. Если есть возможность, я использую параметризованный метод. Мой следующий выбор — использовать реализацию параметризованного метода в сочетании с одним из других паттернов, чтобы посмотреть, смогу ли я найти смесь, которая использует только несколько вариантных методов для обработки больших вариаций и множество параметров для обработки меньших вариаций. Если нужно всего несколько вариантных методов, то хорошо работают либо синглтоны, либо внутренний оператор case. Если вариантов много, то лучше всего использовать паттерн стратегии. В целом шаблон стратегии не намного хуже синглтонов или внутренних операторов case, но может быть немного сложнее для понимания на первый взгляд. Если метод может быть выражен на простом языке, например, в виде арифметической формулы, то интерпретатор — хорошая идея. По мере распространения паттернов «банды четырех» сочетание паттерна стратегии и параметризованного метода станет преобладающим выбором.

      Все четыре описанные выше стратегии показывают, как можно решить проблему индивидуальных методов экземпляра. Можно сказать, что модель, показанная на рисунке 6.8, является аналитическим утверждением спецификации, и разработчики могут выбрать любую стратегию, которая лучше всего подходит для условий реализации. Это работает до тех пор, пока для каждой стратегии существует общий интерфейс. Принцип одной модели анализа, определяющей единый интерфейс, который может быть реализован многими способами, лежит в основе подхода к использованию шаблонов проектирования для разработки.

      Многие моделисты предпочли бы другой способ моделирования проблемы, чем тот, что показан на рисунке 6.8. Они могут предпочесть выражение, более близкое к одной из других стратегий. Они все еще могут отделить анализ от реализации, если заменят другую реализацию на тот же интерфейс. Другие моделисты предпочтут моделировать в той же форме, что и реализация. В этой ситуации они обменивают независимость от реализации на большую бесшовность между моделью анализа и реализацией.

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

      6.7 Запуск правил проводки (Posting Rule Execution)

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

      6.7.1 Ранний запуск

      При таком подходе правила проводки срабатывают, как только на триггерном счете появляется подходящая проводка. Это можно сделать двумя способами. Один из них — возложить эту ответственность на методы создания транзакций или проводок, как показано на рисунке 6.14. Создание транзакции приводит к тому, что на счетах появляется несколько проводок. Каждая проводка записи вызывает поиск правил проводки, которые используют этот счет в качестве триггера. Затем каждое из этих правил проводки запускается.

      6.14.png

      Рисунок 6.14. Диаграмма событий, показывающая, как создание транзакции может запустить правила проводки.

      Если вы теряетесь в нотации некоторых диаграмм, то обратитесь к полному описанию в приложении А или к его сокращенной версии в приложении C

        Поиск и отмена правил проводки могут быть выполнены либо во время создания транзакции, либо в методах создания отдельных записей, как показано на рис. 6.15. Последний вариант является более точным представлением этого процесса.

        6.15.png

        Рисунок 6.15. Диаграмма взаимодействия для отмены правил проводки при создании записи.

        Второй подход заключается в том, чтобы сделать правила проводок наблюдателями своего триггерного счета. Когда правило настроено, оно регистрируется в триггерном счете. Когда проводка прикрепляется к счету, он сообщает всем наблюдателям, что произошло достойное внимания событие. Затем правило проводки опрашивает счет, чтобы выяснить, что произошло, и обнаруживает новую проводку. Затем оно генерирует соответствующую новую проводку в, например, счет-памятку. Преимущество этой схемы в том, что транзакция больше не должна активировать правило проводки. Наблюдатель — очень полезный механизм, но я склонен использовать его только в тех случаях, когда необходимо обеспечить видимость исключительно от наблюдателя к наблюдаемому, особенно когда они лежат в разных пакетах. Я не люблю использовать наблюдателей, когда в этом нет необходимости, потому что слишком большое их количество затрудняет отладку. Не думаю, что я поместил бы правила проводок в отдельный пакет, чтобы не было необходимости использовать наблюдателя.

        6.7.2 Запуск на основе счета

        Запуск на основе счета переносит ответственность за запуск с транзакций на счет. Проводки могут быть добавлены в счет без выполнения каких-либо правил проводки. В определенный момент счету дается команда обработать себя, а затем он запускает свои правила исходящей проводки для всех записей, которые поступили с момента последней обработки, как показано на рисунке 6.16.

        6.16.png

        Обозначение X указывает на то, что для каждой комбинации правила проводки и необработанной проводки выполняется операция правила запуска проводки.

        Рисунок 6.16. Периодический запуск счетов на обработку.

        Такой способ требует, чтобы счет отслеживал необработанные проводки. Это можно сделать, организовав отдельную коллекцию для необработанных проводок (сохраняя проводки в списке и отслеживая последнюю проводку для обработки), или записывая момент времени последней обработки и возвращая записи, которые были внесены после этого времени (используя свойство when booked).

        Запуск на основе счета можно использовать в периодической системе учета, где счета обрабатываются раз в день. В этом случае необходимо следить за тем, чтобы счета обрабатывались в правильном порядке. Сначала должны обрабатываться счета без зависимостей, и двигаясь к счетам которые используют большее количество правил проводок. Эти зависимости можно определить автоматически, просмотрев правила проводок.

        6.7.3 Запуск на основе правил проводки

        При запуске на основе правил проводки, должен существовать внешний актор, который прямо укажет на необходимость запуска. Такой актор просматривает что подается на вход, чтобы определить новые проводки. В этом смысле, такой подход очень похож на запуск на основе счета со всеми плюсами и минусами. Главное отличие заключается в том, что, поскольку у счета может быть много правил проводки, ответственность за принятие решения о том, какие записи не были обработаны, переходит от счета к правилу проводки. Это обычно усложняет ситуацию, поэтому я предпочитаю запуск на основе счета.

        6.7.4 Запуск в обратном порядке зависимостей

        Запуск в обратном порядке зависимостей является вариацией запуска на основе счета. Т.е. счета не просто запускают свои правила, но и инициируют запуск правил для всех счетов от которых конкретный счет зависит. При таком подходе можно получать свежий статус любого счета.

        Мы можем начать этот процесс, запросив у счета его проводки, как показано на рис. 6.17. Сначала счет приводит себя в актуальное состояние. Затем использует правила проводок, чтобы определить, какие счета являются источником для этих правил и которые повлияют на изначальный счет. Эти счета в свою очередь начинают приводить себя в актуальное состояние, что является рекурсивным процессом, как показано на рисунках 6.18 и 6.19. Весь граф счетов приводится в актуальное состояние, если начать актуализацию с вершины графа.

        6.17.png

        Рисунок 6.17 Запрос детального счета на обработку проводок с запуском в обратном порядке зависимостей.

        6.18.png

        Операция приведения счета в актуальное состояние вызывается рекурсивно на каждом счете, который является источником для обрабатываемого счета.

        Рисунок 6.18. Метод приведения учетной записи в актуальное состояние.

        6.19.png

        Рисунок 6.19. Диаграмма взаимодействия для обновления учетной записи при увольнении на основе учетной записи.

        6.7.5 Сравнение подходов к запуску

        Основными соображениями при выборе подхода к запуску являются: время, затрачиваемое на выполнение правила проводки (решение о необходимости запуска), и момент, в который мы хотим отлавливать ошибки. Ранний запуск позволяет нам получать ошибки сразу же после их обнаружения. Это дает нам больше времени на поиск причины ошибок. Однако это заставляет нас выполнять все вычисления при создании проводок. Обработка на основе счетов и на основе обратной зависимости дают нам больше гибкости в выборе времени вычислений. Если мы обрабатываем счета пакетным методом, мы можем считывать все проводки из хранилища, а затем запускать правила проводок по своему усмотрению, возможно, в течение ночи. Чем раньше мы выполним запуск, тем быстрее найдем возможные ошибки.

        Выбор между запуском на основе счета и на основе обратных зависимостей счета в действительности сводится к тому, хотим ли мы бороться с дополнительной сложностью построения логики на основе обратных зависимостей. Обратная зависимость более неудобна в построении, чем основанная на счете, но после создания ее легче использовать. Поэтому я бы использовал запуск на основе счета для простых структур счетов и обратную зависимость для сложных структур счетов. В целом, мне не нравится «быстрый запуск», потому что он не такой гибкий. Можно получить все преимущества быстрого запуска, обеспечив срабатывание правила проводки при добавлении проводок (но не в процессе их создания). Хотя это и дополнительный шаг, но я могу контролировать его и управлять им. Быстрый запуск в базовом исполнении не дает мне такого выбора. Если у меня так много вычислительных мощностей, что правила проводок ничего не стоят, то это все не имеет значения.

        Ничто вас не останавливает от комбинирования подходов к запуску правил в одной системе. Доходные счета могут использовать быстрый запуск в пару слоев счетов активов, а затем использовать обратную зависимость для остальной части системы обработки счетов. Однако использование более чем одной схемы запуска делает систему более сложной и запутанной, поэтому я не смешиваю их, если у меня нет веских причин.

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

        6.8 Правила проводок для многих счетов

        До сих пор я рассматривал частный пример себя и своего собственного плана счетов. Нам нужно расширить этот подход, чтобы работать с большим количеством людей. Мы хотим, чтобы правила проводок были последовательными, чтобы, например, единое правило проводок по федеральному налогу можно было использовать для определения обязательств по федеральному налогу для всех людей.

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

        Это можно сделать двумя способами. Первый — использовать понятие уровня знаний и операционного уровня (см. раздел 2.5). Мы устанавливаем правила проводок на уровне знаний и связываем их с типами счетов, как показано на рисунке 6.20. Таким образом, у нас будут типы счетов для комиссионных доходов, доходов до налогообложения, чистых доходов и так далее. Записи, которые появляются на счетах, проверяют правила проводки по типу счета, добавляя неявный уровень к видам выражений, рассмотренных выше.

        6.20.png

        Это вводит уровень знаний, на котором могут быть определены правила размещения.

        Рисунок 6.20. Использование типов счетов.

        Пример: Всем сотрудникам начисляется 1 день отпуска за каждые 18 отработанных дней. Это можно представить в виде правила проводки с триггером для типа счета «Отработанные дни» и целевым счетом типа «Начисленный отпуск». Этот метод гарантирует, что баланс счета начисленных отпусков составляет 1/18 баланса отработанных дней. Каждый раз, когда срабатывает счет сотрудника, он ищет правила проводок, определенные для его типа счета в соответствии с типом используемого срабатывания.

        Однако разделение на знания и операции, хотя оно и привлекательно, не является единственным способом решения этой ситуации. Второй подход заключается в использовании сводных счетов. Правило проводки, определенное на сводном счете, активируется, когда любая проводка помещается на любой дочерний счет сводного счета (или на сам счет, если проводка по сводному счету разрешена). Аналогичным образом можно определить целевой счет для сводного счета с объяснением, что это вызовет запись на соответствующем дочернем счете.

        Пример: Пусть имеются суммарные счета для отработанных дней и начисленного отпуска. Правило проводки такое же, как и в примере выше. Вместо проверки типа счета для правил проводки проверяются суммарные счета.

        Выбор между этими двумя методами зависит от степени различия между счетом и типом счета. Если все правила проводок определяются типом счета, а проводки делаются по счетам, то разделение по принципу «знание/операция» вполне оправдано. Однако иногда такая ситуация не возникает. Проводки могут быть сделаны на более общем уровне, возможно, для указания общей платы компании (для этого потребуется модель, показанная на рис. 6.6). Аналогично, правила проводок могут варьироваться для каждого отдельного платежа. Например, это потребуется для поддержки вычетов по автокредиту. В таких ситуациях лучше не делать разделение.

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

        В любом случае правило проводки должно определить, как сделать правильную выходную проводку. Во многих из приведенных выше примеров правило проводки просто ищет счет для того же сотрудника, что и триггерная проводка. Однако возможны и более сложные ситуации. Рассмотрим ситуацию, в которой выплата гонорара младшему консультанту приводит к тому, что процент от гонорара отражается на счете служебных проводок для менеджера этого консультанта. В этом случае правилу проводки необходимо указать, как найти счастливого менеджера.

        Один из способов решения этой проблемы — предоставить второй метод для поиска соответствующего целевого счета, как показано на рис. 6.21. Этот второй метод запрашивает у исходной записи ее сотрудника, а затем у этого сотрудника — его менеджера. Это обеспечивает наибольшую степень гибкости, но ценой второго «метода объекта», который должен быть реализован, как предложено в разделе 6.6.

        6.21.png

        Для поиска целевых счетов и расчета стоимости операции используются отдельные методы.

        Рисунок 6.21. Использование метода поиска счета.

        Это намекает на другую проблему. При использовании общих правил проводок не все сотрудники могут подпадать под действие правила проводок. Например, правила проводки могут быть настроены для обработки налогов каждого штата. Однако правило проводки для налога штата Иллинойс должно срабатывать только в том случае, если сотрудник является резидентом Иллинойса. Таким образом, предлагается третий метод, который используется для выражения условия применимости, как показано на рисунках 6.22 и 6.23.

        6.22.png

        Рисунок 6.22. Диаграмма событий, показывающая использование методов поиска счета и условия приемлемости, добавленная на рисунок 6.14.

        Если вы теряетесь в нотации некоторых диаграмм, то обратитесь к полному описанию в приложении А или к его сокращенной версии в приложении C

          6.23.png

          Рисунок 6.23. Добавление условия приемлемости к приведенным выше правилам.

          6.9 Выбор проводок (Choosing Entries)

          Во многих ситуациях правило проводки должно выбрать некоторое подмножество проводок из своего триггерного счета. Может потребоваться просмотреть все проводки с определенной даты, сальдо всех проводок, списанных в июле, или проводки опасных товаров (для которых используется некоторый подтип проводок). Существует три способа выполнения выборки: получение всех проводок и последующая выборка, предоставление метода выборки, и использование фильтра.

          Первая техника является самой простой: Счет возвращает все проводки, а клиент обрабатывает эту коллекцию для выбора нужных. Это не требует от счета никаких дополнительных действий, вся ответственность на клиенте. Если многим клиентам нужно выполнить одинаковый выбор, может возникнуть много дублирования. Если записей много, то передача набора может оказаться накладной, особенно если набор нужно скопировать. Помните, что аккаунт никогда не должен передавать незащищенную ссылку на свой собственный способ хранения записей (см. раздел 14.1). Использование такого подхода к записям также означает, что клиент отвечает за суммирование записей для получения баланса.

          Если многие клиенты запрашивают схожий тип отбора, например, записи за определенный период времени, то в аккаунт можно добавить дополнительное поведение для удовлетворения этой потребности (например, entriesChargedDuring (TimePeriod)). Преимущество этого способа заключается в том, что он избавляет всех клиентов от необходимости повторять один и тот же процесс выбора. Мы можем сэкономить клиентам еще больше усилий, предоставив метод, который выдает баланс за определенный период времени (например, balanceChargedDuring (TimePeriod)). Проблема с этим решением заключается в том, что если таких отборов много, интерфейс счета становится очень большим.

          Фильтр (см. раздел 9.2) — это объект, в котором заключен запрос. Если использовать этот шаблон здесь, то получится фильтр аккаунта. Фильтр аккаунта включает в себя различные операции для задания условий запроса. После того как фильтр настроен, он применяется к учетной записи для получения ответа, как показано на рисунке 6.24. Счет использует фильтр для выбора подмножества записей, буквально беря каждую из своих записей и проверяя ее с помощью метода isIncluded фильтра. Он может применять свои частные знания о том, как хранятся записи, чтобы оптимизировать этот процесс. При таком подходе счет может поддерживать большинство вариантов записей с помощью entriesUsing (AccountFilter) и выдавать соответствующие остатки с помощью balanceUsing (anAccountFilter). Обратите внимание, что если подтипы записей имеют дополнительные характеристики, которые используются в качестве основы для отбора, то для каждого типа записей могут потребоваться подтипы фильтра счета.

          6.24.png

          Рисунок 6.24. Схема взаимодействия при использовании фильтра учетных записей.

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

          6.10 Бухгалтерская практика (Accounting Practice)

          Когда мы сталкиваемся с большой сетью счетов со множеством правил проводок, сеть становится слишком большой, чтобы с ней справиться. Для упрощения работы нам нужен способ разбить сеть на части. Рассмотрим процедуру выставления счетов в коммунальной службе. Они выставляют счета различным типам клиентов с помощью различных процессов выставления счетов. Это можно представить как сеть счетов. Для каждого типа клиентов действуют свои правила, и они могут работать с несколько иной сетью счетов.

          Определенная сеть счетов — это практика бухгалтерского учета. Концептуально, практика учета — это просто набор правил проводки, как показано на рис. 6.25. Суть в том, что каждому типу клиентов назначается своя практика учета для обработки счетов.

          6.25.png

          Они используются для объединения правил отправки сообщений в логические группы.

          Рисунок 6.25. Практика бухгалтерского учета.

          Пример: Энергокомпания делит своих потребителей на обычных и приоритетных. Категория «приоритетные» предназначена для тех, кто, по мнению штата, должен получать минимальные тарифы. Обычные потребители делятся на три различные тарифные сетки в зависимости от района проживания. Для этого используются четыре метода учета: один для приоритетных и один для каждого из трех районов.

          Пример: В ACM работает много профсоюзных работников, и каждый профсоюз ведет переговоры о разных сделках. В ACM существует практика оплаты труда для каждого профсоюза.

          Одно и то же правило проводки может существовать в нескольких практиках. Это часто случается, когда в разных практиках требуется одинаковое поведение. Необходимо понимать разницу между копированием правила из одной практики (что приводит к появлению двух одинаковых правил) и наличием одного и того же правила в нескольких практиках. Наличие правила в более чем одной практике учета подразумевает, что при изменении правила оно меняется для всех использующих его практик. Копии позволяют изменять одну копию без изменения остальных.

          Практики учета назначаются некоторому пользовательскому объекту таким образом, чтобы у каждого пользователя была одна практика учета. Таким образом, каждый клиент энергокомпании или сотрудник компании использует определенную практику учета. Это назначение может быть сделано вручную или определено правилом.

          Пример: В ACM практика оплаты труда назначается работнику на основе его профсоюза.

          Вместо использования бухгалтерской практики можно использовать правило проводки, которое разделяет проводки в зависимости от атрибутов сотрудника. Вместо того чтобы использовать условно уникальную практику для каждого профсоюза, можно использовать только одну общую практику. Первое правило проводки из практики определяет профсоюз сотрудника, для которого делается проводка, и создает проводку по соответствующему профсоюзному счету (пример такого правила разделения проводок см. в разделе 7.6).

          Я предпочитаю использовать отдельные практики, если проблема совсем сложная, при условии, что мы можем закрепить практику за пользователем на определенный период времени. Любые разделения, которые всегда меняются от проводки к проводке (например, разделение на вечер и день, рассмотренное в разделе 7.6), должны иметь правило проводки для их обработки. Если пользователь меняет свою практику учета, мы можем использовать историческое отображение (см. раздел 15.3), чтобы сохранить запись об этих изменениях.

          Когда на разных этапах обработки имеются логически разделенные группы правил проводки, мы можем разделить правила на различные типы практик и дать пользователю практику из каждого типа. На рисунке 6.26 бухгалтерская практика может иметь пользователей, которые, в общем случае, могут быть любыми объектами. В конкретной модели, конечно, пользователями будут клиенты, сотрудники и т. п. У каждого пользователя есть по одной бухгалтерской практике каждого типа — ограничение, которое обеспечивается маппингом по ключу (см. раздел 15.2).

          6.26.png

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

          Рисунок 6.26 Тип бухгалтерской практики.

          Пример: У коммунального предприятия есть несколько практик выставления счетов бытовым потребителям, но все они облагаются налогом одинаково. Мы можем решить эту проблему с помощью раздельной практики начисления и налогообложения. Все частные клиенты имеют одинаковую практику налогообложения, хотя у них разные практики начисления.

          Логическим завершением этой дискуссии является рассмотрение бухгалтерских практик и правил составления проводок как частей одного и того же комплекса. Это позволит создавать композиции практик для многих уровней. Пока что я не вижу в этом большой необходимости, поэтому не стал исследовать этот вопрос дальше.

          6.11 Источники проводок (Sources of an Entry)

          Часто бывает важно знать, почему та или иная проводка находится в своем текущем виде. Например, если клиент звонит и спрашивает о конкретной проводке, текущая модель может дать нам достаточно много информации о том, как проводка была создана. Мы можем определить состояние счета на тот момент, посмотрев на даты других проводок. Мы также можем определить, по какому правилу проводки она была рассчитана. Модель, показанная на рисунке 6.27, может обрабатывать такие запросы клиентов, заставляя каждую транзакцию помнить, какое правило проводки ее создало и какие проводки были использованы в качестве входных для транзакции. (Если вы не используете транзакции, то ассоциация идет от проводки к проводке).

          Пример: Я получил 2000 долларов за работу для ACM, которую я зарегистрировал как операцию по перечислению комиссионного дохода на расчетный счет. Мое правило проводки создало отдельную операцию на моем счете налоговых обязательств. Создателем этой транзакции было правило проводки 45%, а источником для этой транзакции было снятие средств со счета комиссионных доходов.

          6.27.png

          При этом записывается полный путь вычислений для каждой проводки в обоих направлениях.

          Рисунок 6.27. Источники для транзакции.

          Используя этот шаблон, мы можем сформировать цепочку проводок и операций во всей структуре учета. Каждая запись может определять все причины и следствия путем рекурсивного использования отображений источников и следствий.

          6.12 Балансовый счет и приходы\расходы (Balance Sheet and Income Statement)

          При использовании счетов для описания системы стоит различать счета баланса и отчета о прибылях и убытках, как показано на рис. 6.28. Мой расчетный счет — это счет активов, а счет моей кредитной карты — счет пассивов. Они отражают деньги, которые у меня есть (или, в случае с кредитной картой, которых у меня нет) в любой период времени. Они отображаются в моем балансовом отчете. Счета доходов и расходов отражают, откуда приходят или куда уходят деньги. У меня есть счет доходов для моего работодателя, другой счет доходов для процентов от моих сбережений, счет расходов для путешествий, другой для еды и так далее. Остатки на счетах доходов и расходов не отражают деньги, которыми я располагаю в данный момент, а лишь классифицируют, откуда они приходят и куда уходят.

          6.28.png

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

          Рисунок 6.28. Счета активов, доходов и расходов.

          Счета обычно используются по схеме, в которой предметы попадают в мир через счет доходов, проходят через несколько счетов активов и ликвидируются через счет расходов. Любые активы, которые сохраняются системой, хранятся на определенных счетах активов, но многие счета активов — это просто перевалочные пункты, предназначенные для регулярной балансировки. Пассивные счета почти всегда предназначены для балансирования в определенный момент (который может быть далеко в будущем для долгосрочного долга, такого как ипотека).

          Пример: Я покупаю билет у Boston Airlines по своей кредитной карте. Мой кредитный счет — это пассивный счет, а счет Boston Airlines — расходный счет. Оба счета классифицированы мной, и я являюсь владельцем счета кредитной карты (это мое обязательство).

          Пример: ACM покупает 3 тонны явы у индонезийских импортеров кофе. ACM открывает счет доходов для индонезийских импортеров кофе, чтобы отразить перевод 3 тонн явы от индонезийских импортеров кофе на нью-йоркский счет ACM. Нью-йоркский счет — это счет активов, принадлежащий ACM.

          На этом этапе я могу быстро объяснить, почему я избегаю терминов «дебет» и «кредит». Это общеизвестные термины, которые применяются к счетам, но я проигнорировал их в пользу терминов «от», «до», «депозит» и «вывод». Причина в том, что дебет и кредит не используются последовательно в смысле депозита и изъятия. Для счетов отчета о прибылях и убытках кредиты увеличивают счет, а дебеты уменьшают его, что вполне понятно для обывателя. Однако для балансовых счетов дебет увеличивает активы (то есть является депозитом), а кредит уменьшает активы. Это может показаться странным для людей далеких от бухгалтерии, но это обычная бухгалтерская конвенция. Таким образом, я избегаю дебета и кредита, отчасти потому, что они могут запутать читателей, а отчасти потому, что мы работаем с более абстрактной моделью, чем обычная финансовая бухгалтерия.

          6.13 Корреспондентский счет (Corresponding Account)

          Хотя счета доходов и расходов являются внешними — деньги не мои — это мои счета с выбранной мной классификацией. Посмотрим как банк видит мои счета и работает с ними. У меня есть расчетный счет, который является активом в моей личной системе счетов. У банка есть счет в его системе счетов, который выглядит удивительно похожим. Банк является классификатором банковского счета, но я владею активами на нем. Мы могли бы считать этот счет таким же, как и счет в моей системе счетов, но это не сработает. Я могу сделать запись о снятии денег в банкомате 1 марта, то есть в день, когда я снял деньги. Банк публикует ту же запись о снятии денег 2 марта, потому что это следующий рабочий день в банке. Эти два счета относятся к одному и тому же активу, но они не являются одним и тем же, поскольку их проводки различаются. Лучше считать эти два счета корреспондентскими.

          Предполагается, что корреспондентские счета должны каким-то образом совпадать, и обычно в какой-то момент их выверяют. Так происходит, когда я сверяю чековую книжку (свой счет) с банковской выпиской (счет банка). Процесс сверки может быть точным, а может допускать некоторые неточности, например, небольшие различия в датах.

          Рисунок 6.29 иллюстрирует эту ситуацию. Владельцы есть только у балансовых счетов. У счетов отчета о прибылях и убытках нет активов, поэтому вопрос о владении не стоит. У всех счетов есть классификатор, указывающий, кто создает и управляет счетами. Тут я использовал шаблон «Группа» (см. раздел 2.1). Корреспондентские отношения обладают парой особых свойств: симметричностью и транзитивностью [3]. Во-первых, они симметричны: если счет x является корреспондентом счета y, то счет y должен быть корреспондентом счета x. Обычно по умолчанию для ассоциаций принято считать, что они асимметричны. Транзитивность указывает, что если счет y является корреспондентом счета x и счет z является корреспондентом счета y, то счет z является корреспондентом счета x.

          6.29.png

          Рисунок 6.29. Корреспондирующие счета.

          6.14 Специализированная модель счетов (Specialized Account Model)

          Я привел несколько примеров, показывающих, что эта модель может быть использована в качестве основы как для финансового учета, так и для отслеживания запасов. В моделях учета обычно используется дочерний тип, чтобы предоставить информацию для конкретной области. Например, рассмотрим управление запасами — проблему, подходящую для использования счетов. Мы можем сформировать счет для каждой комбинации вида товара и его местоположения (и дать ему менее бухгалтерское название, например, холдинг).

          Таким образом, если мы отслеживаем перемещение бутылок виски Macallans, Talisker и Laphroig между Лондоном, Парижем и Амстердамом, у нас будет девять холдингов (счетов активов, таких как London-Macallans, London-Talisker, Paris-Talisker и так далее). Всякий раз, когда мы перемещаем товары из одного места в другое, мы создаем трансфер (транзакцию), чтобы управлять этим перемещением. Как и в случае с деньгами, трансферы должны быть сбалансированы. Кроме того, вид объекта должен быть одинаковым на протяжении всего перемещения. На рисунке 6.30 показано такое расширение модели счета.

          6.30.png

          Такая специализация необходима для использования модели учета в конкретной области.

          Рисунок 6.30. Специализация модели счета для поддержки инвентаризации.

          Этот подход также может быть использован для отслеживания заказов, как входящих, так и исходящих. У каждого поставщика будет свой счет доходов, возможно, даже несколько, если важно местоположение поставщика. Аналогично, каждый заказчик получил бы счет расходов. Мы можем отслеживать заказы двумя способами: можем разрешить подтипы передачи, либо заказанные, либо фактические, или мы можем предоставить другой набор холдингов для заказов, так что у нас будут, например, London-Talisker-Ordered и London-Talisker-Actual. Когда заказ размещается, мы переводим средства из заказанного холдинга поставщика в заказанный холдинг в том месте, куда мы хотим его доставить. Когда заказ будет доставлен, мы осуществим перевод между заказанным холдингом и фактическим холдингом в нашем месте. Это точно так же, как использование счета дебиторской задолженности в финансовых книгах.

          Чтобы получить общую картину, мы можем использовать сводные холдинги. Сводное хранение всех заказанных запасов может дать общую заказанную позицию, а сводное хранение Talisker — общее количество Talisker во всех местах.

          6.15 Бронирование проводок на нескольких счетах (Booking Entries to Multiple Accounts)

          Частая проблема при работе со счетами — это когда есть несколько мест, где можно забронировать проводку. Например, предположим, я заплатил 500 долларов за авиабилет для участия в конференции OOPSLA. Заносить ли мне эту сумму на счет OOPSLA (чтобы можно было подсчитать, во сколько мне обошлось участие в OOPSLA) или на счет авиаперелета (чтобы можно было подсчитать, сколько я потратил на авиаперелет)? Существует несколько способов решения этой проблемы, которые иллюстрируют некоторые полезные моменты использования счетов, а также показывают более сложные структуры счетов, чем просто иерархии счетов, упомянутые ранее.

          Типичный счет консультанта иллюстрирует эту проблему. Допустим, я провожу три дня консультаций для ACM. За работу я беру с них 6000 долларов. Кроме того, у меня возникают некоторые расходы: 500 долларов на авиабилеты, 250 долларов на гостиницу, 150 долларов на аренду автомобиля и 100 долларов на питание. Как мне это учесть, или, точнее, как мне вести учет, если у меня есть приличная система бухгалтерского учета? Очевидно, что мне нужен счет для ACM, чтобы я мог отправить им счет. Однако одного счета недостаточно. Мне интересно посмотреть, сколько я зарабатываю на разных клиентах. Когда я провожу этот анализ, я не хочу видеть расходы, потому что они не являются заработанными деньгами. Точно так же при оценке налоговых обязательств я не должен учитывать расходы. Это говорит о том, что я могу использовать отдельные счета для комиссий ACM и расходов ACM. Тогда мой счет ACM будет формироваться суммарным счетом по этим двум счетам, как показано на рис. 6.31. Проблема в том, что мне нужен отдельный счет для всех заработанных комиссий. Этот счет будет включать счета для комиссий ACM, комиссий Megabank и комиссий других клиентов. Это также работает в качестве сводного счета, но нарушает иерархическое ограничение на рисунках 6.5 и 6.6. Поэтому мне нужно изменить модель, чтобы позволить детальному счету иметь несколько суммарных счетов в качестве родителей, как показано на рисунке 6.32.

          6.31.png

          Элементы с жирными границами — это детальные счета, обобщенные, как показано стрелками.

          Рисунок 6.31. Типичная структура счета оплаты/расходов.

          6.32.png

          Эта диаграмма заменяет иерархию на рисунке 6.5 направленным ациклическим графом.

          Рисунок 6.32. Разрешение нескольких суммарных учетных записей.

          Модель на рис. 6.32 позволяет счетам образовывать направленный ациклический граф. Таким образом, у счета может быть много родителей, но мы избегаем циклов (счет не может быть своим дедушкой или бабушкой). Такая структура позволяет использовать несколько суммарных счетов.

          Однако здесь есть небольшая сложность, которую мы должны учитывать. Что произойдет, если у меня будет структура счета, показанная на рисунке 6.33? Счет X суммирует ACM и комиссионные, поэтому счет комиссионных ACM учитывается дважды.

          6.33.png

          Если используется несколько сводных счетов, кто-то может определить сводный счет, который имеет перекрывающиеся детальные счета.

          Рисунок 6.33. Структура счета, в которой видна проблема.

          Согласно модели на рис. 6.32, мы все равно получим правильный баланс для X. Баланс определяется на производном наборе проводок. Наборы не допускают дублирования, поэтому все записи в «ACM оплаты» появятся в X только один раз, что даст нам правильный баланс для X. Однако этот баланс не будет равен сумме балансов для «оплат» и ACM, что может привести к путанице. Если эта путаница является проблемой, нам нужно ограничение на отношения компонентов, которое не позволит нам выбирать компоненты, имеющие какое-либо перекрытие. Это разумное ограничение, поскольку трудно придумать пример, в котором такой счет, как X, был бы полезен. Определение такого рода счета, скорее всего, является результатом случайности, чем замысла.

          6.15.1 Использование счетов-памяток

          Модель хорошо работает при таком представлении, но следует рассмотреть некоторые дополнительные детали. Может возникнуть необходимость в более подробной разбивке расходов. Налоговое законодательство может потребовать разделения расходов на проезд, проживание и питание (например, ACM-авиаперелет, ACM-проживание, Megabank-авиаперелет и так далее). Это можно сделать, разбив каждый счет расходов на детальные счета, но это может стать сложным в управлении из-за всех сложных комбинированных счетов. Стоит изучить другие варианты.

          Один из вариантов — использовать записи на счетах-памятках. Так, 500 долларов за билет для посещения штаб-квартиры ACM будут депонированы как на счет расходов ACM, так и на счет авиаперевозок. Этот метод устраняет необходимость в счете ACM-авиаперелеты, но требует дополнительных проводок. Правило проводки может в некоторой степени решить эту проблему, но нам все равно нужно какое-то заявление о том, какой счет расходов необходим. Это можно сделать с помощью создания специальной транзакции расходов, которая принимает параметры от счета, до счета и тип расхода счет-памятка.

          Выбор того, какой из счетов (счет расходов ACM или счет авиаперевозок) должен быть счетом-памяткой, зависит от последующего использования счета. Если счет расходов ACM используется для отслеживания оплаты счетов, а счет авиаперевозок — только для налоговой отчетности, то лучше использовать счет авиаперевозок в качестве счета-памятки. Существует определенная доля произвольности в выборе счетов, через которые проходит основной поток денег, по сравнению с теми, которые работают со счетами-памятками.

          6.15.2 Производные счета

          Другой подход заключается в использовании производной учетной записи, как на рисунке 6.34. В этом случае записи задаются путем предоставления производному счету фильтра (см. раздел 6.9), который выбирает подходящие записи. Для работы производного реквизита необходимо что-то, на чем можно основывать его наследование. Хорошо подойдет подтип проводки, поддерживающий категорию расходов, и тогда учетная запись, в которой критерием принадлежности является категория расходов = стоимость авиабилета, создаст нужную информацию.

          6.34.png

          Рисунок 6.34 Внедрение производных счетов.

          Можно пойти дальше с этим подходом. Почему бы вообще не отказаться от использования счетов и просто иметь что-то вроде рисунка 6.35? Тогда мы сможем выяснить, что происходит, просто сделав запрос по расходам.

          6.35.png

          Производный счет все еще позволяет нам использовать все возможности отчетности, но мы теряем возможности отслеживания.

          Рисунок 6.35 Расходы, определенные для отказа от модели учета.

          Этот вопрос помогает определить, почему счета полезны и почему производные счета ценны. Счета лучше всего работают в относительно статичных структурах, где необходимо отслеживать сложные движения активов. Если движения просты, например, просто назначить расход на авиабилеты, то счета не нужны. Однако рассмотрим ситуацию, когда за одну поездку я посещаю и Megabank, и ACM и отношу две трети стоимости авиабилета на счет одного и одну треть — на счет другого. Это тот тип сложной транзакции, с которым хорошо справляются счета. Однако в модели на рис. 6.35 с этим есть реальная проблема. Как разделить простой платеж таким образом? Обратите внимание, что у модели на рисунке 6.35 есть еще одна проблема: в ней не сказано, откуда берутся деньги. Я мог бы добавить к ней ассоциацию кредитной карты, но тогда расход будет очень похож на транзакцию с двумя ногами.

          Использование атрибутов для производных счетов эффективно, когда структура счета не очень статична. Если существует множество возможных разрезов информации, то производный счет позволяет легко вычислять их, используя те же средства отчетности, что и счета. Однако у них есть только средства отчетности. По производным счетам нельзя делать проводки и, следовательно, их нельзя использовать для отслеживания притока и оттока активов.

          Поэтому всякий раз, когда мы пытаемся представить аспект проводки, у нас есть выбор между атрибутом проводки и новым уровнем счета. Решение зависит от того, какая часть поведения счета вам нужна. Если это просто сторона отчетности, мы можем использовать атрибут и вывести счет, когда это необходимо. В противном случае потребуется новый уровень счетов.

          Дополнительное чтение

          Я могу порекомендовать еще несколько источников информации о бухгалтерском учете, которые дают несколько иной взгляд от представленного в этой главе. В книге Хэйя [2] есть глава, посвященная бухгалтерскому учету. Его основные понятия о счетах и операциях во многом совпадают с моими, хотя он ничего не говорит о правилах проводки. Он гораздо глубже рассматривает типы счетов, которые присутствуют в корпорациях. Он также обсуждает общие операции, которые используются в корпорациях, и то, как они вписываются в эту модель учета. Он также представляет уровень знаний по этим типам счетов и операций.

          В Иллинойском университете в Урбана-Шампейн ведется активная работа по созданию системы бухгалтерского учета [4]. Этот подход сильно отличается от подхода Хэя и меня. Он начинается с того, что информация в счете-фактуре (например) рассматривается как высокоуровневая «транзакция» по высокоуровневому счету. Затем эта «транзакция» может быть разбита на «транзакции» более низкого уровня против счетов более низкого уровня. Они используют слово «транзакция» совсем не так, как я: Они не следуют принципу сохранения. Высокоуровневой «транзакцией» может быть счет-фактура со всеми его статьями. Система сосредоточена на разбиении этого понятия на «транзакции» более низкого уровня, такие как сами статьи. Таким образом, фреймворк предназначен для разбиения группы записей на составляющие их элементы, а не для моего подхода к сети счетов и переносов между ними.

          Ссылки

          1. Gamma, E., R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.

          2. Hay, D. Data Model Patterns: Conventions of Thought. New York, NY: Dorset House, 1996.

          3. Langer, S.K. An Introduction to Symbolic Logic, Third Edition. New York, NY: Dover, 1967.

          4. Keefer, P.D. An Object-Oriented Framework for Accounting Systems. University of Illinois at Urbana-Champaign ftp://st.cs.uiuc.edu/pub/Smalltalk/st80_vw/accounts/thesis.ps, 1994.

          5. Meyer, B. “Applying ‘Design by Contract,’” In IEEE Computer, 25, 10 (1992), pp. 40–51.

          Last modified: 16 January 2025