Перейти к содержанию

Фабрика документов

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

Электронный документ

Электронный документ — файл, который содержит юридически-значимое действие пайщика. Он создан по общему кооперативному стандарту, может быть подписан и передан.

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

Полная форма документа включает в себя "сырые" данные, доступные для отображения или скачивания в виде PDF-файла. Это тот вид документа, который мы привыкли видеть, и который содержит все данные, которые в нем должны быть - все заполненные поля, доступные для чтения, и т.д..

Интерфейс данных полного документа:

interface GeneratedDocument {
  binary: <string>; // Бинарное содержимое документа (base64)
  full_title: <string>; // Полное название документа
  hash: <string>; // Хэш документа
  html: <string>; // HTML содержимое документа
  meta: <ModelTypes["JSON"]>; // Метаданные документа
}

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

Интерфейс данных подписанного документа:

interface SignedBlockchainDocument {
  doc_hash: <string>; // Хэш содержимого документа
  hash: <string>; // Общий хэш (doc_hash + meta_hash)
  meta: <string>; // Метаинформация документа (в формате JSON-строки)
  meta_hash: <string>; // Хэш мета-данных
  signatures: <ModelTypes["SignatureInfo"][]>; // Вектор подписей
  version: <string>; // Версия стандарта документа
}

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

Для передачи подписей в документах используется следующий интерфейс:

interface SignatureInfoInput {
  id: <number>; // Идентификатор номера подписи
  meta: <string>; // Мета-данные подписи
  public_key: <string>; // Публичный ключ
  signature: <string>; // Подпись хэша
  signed_at: <string>; // Время подписания
  signed_hash: <string>; // Подписанный хэш
  signer: <string>; // Аккаунт подписавшего
}

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

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

Подпись документа

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

подпись документа

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

Далее мы видим цифровую подпись. Как мы ранее говорили, она накладывается на контрольную сумму документа с помощью приватного ключа пайщика. А поскольку мы обладаем именем аккаунта пайщика, то мы всегда можем извлечь публичный ключ и произвести математическую сверку корректности подписи и ее принадлежности к указанному аккаунту. Что мы и делаем, когда показываем идентификатор статуса подписи - "Верифицирована".

Глубокая сверка

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

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

Фабрика документов

Итак, что же такое фабрика документов?

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

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

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

Мы не будем в данном разделе подробно останавливаться на принципах генерации документов из шаблов, т.к. это продукт стандартов кооперативной экономики и данный материал будет описан на соответствующих страницах сайта https://coopenomics.world.

Реестр шаблонов

id Название SDK GraphQL
1 Согласие с условиями ЦПП «ЦИФРОВОЙ КОШЕЛЕК» 🛠️ SDK: Mutations.Agreements.GenerateWalletAgreement 🔗 GraphQL API: Mutation.generateWalletAgreement
2 Согласие с условиями положения о простой электронной подписи 🛠️ SDK: Mutations.Agreements.GenerateSignatureAgreement 🔗 GraphQL API: Mutation.generateSignatureAgreement
3 Согласие с условиями политики обработки конфиденциальных данных 🛠️ SDK: Mutations.Agreements.GeneratePrivacyAgreement 🔗 GraphQL API: Mutation.generatePrivacyAgreement
4 Согласие с условиями пользовательского соглашения 🛠️ SDK: Mutations.Agreements.GenerateUserAgreement 🔗 GraphQL API: Mutation.generateUserAgreement
50 Пользовательское соглашение (оферта) о присоединении к платформе "Кооперативная Экономика"
51 Заявление о конвертации паевого взноса в членский взнос 🛠️ SDK: Mutations.Provider.GenerateConvertToAxonStatement 🔗 GraphQL API: Mutation.generateConvertToAxonStatement
100 Заявление на вступление в кооператив 🛠️ SDK: Mutations.Participants.GenerateParticipantApplication 🔗 GraphQL API: Mutation.generateParticipantApplication
101 Заявление пайщика о выборе кооперативного участка 🛠️ SDK: Mutations.Branches.GenerateSelectBranchDocument 🔗 GraphQL API: Mutation.generateSelectBranchDocument
300 Предложение повестки дня общего собрания 🛠️ SDK: Mutations.Meet.GenerateAnnualGeneralMeetAgendaDocument 🔗 GraphQL API: Mutation.generateAnnualGeneralMeetAgendaDocument
301 Решение совета о созыве общего собрания 🛠️ SDK: Mutations.Meet.GenerateSovietDecisionOnAnnualMeetDocument 🔗 GraphQL API: Mutation.generateSovietDecisionOnAnnualMeetDocument
302 Уведомление о проведении общего собрания 🛠️ SDK: Mutations.Meet.GenerateAnnualGeneralMeetNotificationDocument 🔗 GraphQL API: Mutation.generateAnnualGeneralMeetNotificationDocument
303 Заявление с бюллетенем для голосования на общем собрании 🛠️ SDK: Mutations.Meet.GenerateBallotForAnnualGeneralMeetDocument 🔗 GraphQL API: Mutation.generateBallotForAnnualGeneralMeetDocument
304 Протокол решения общего собрания 🛠️ SDK: Mutations.Meet.GenerateAnnualGeneralMeetDecisionDocument 🔗 GraphQL API: Mutation.generateAnnualGeneralMeetDecisionDocument
501 Решение совета о приёме пайщика в кооператив 🛠️ SDK: Mutations.Participants.GenerateParticipantApplicationDecision 🔗 GraphQL API: Mutation.generateParticipantApplicationDecision
599 Предложение повестки дня собрания совета 🛠️ SDK: Mutations.FreeDecisions.GenerateProjectOfFreeDecision 🔗 GraphQL API: Mutation.generateProjectOfFreeDecision
600 Протокола решения совета 🛠️ SDK: Mutations.FreeDecisions.GenerateFreeDecision 🔗 GraphQL API: Mutation.generateFreeDecision
699 Согласие с условиями ЦПП «СОСЕДИ»
700 Заявление на паевый взнос имуществом 🛠️ SDK: Mutations.Cooplace.GenerateAssetContributionStatement 🔗 GraphQL API: Mutation.generateAssetContributionStatement
701 Протокол решения совета о форме и стоимости имущества 🛠️ SDK: Mutations.Cooplace.GenerateAssetContributionDecision 🔗 GraphQL API: Mutation.generateAssetContributionDecision
702 Акт приёма-передачи имущества 🛠️ SDK: Mutations.Cooplace.GenerateAssetContributionAct 🔗 GraphQL API: Mutation.generateAssetContributionAct
800 Заявление на возврат паевого взноса имуществом 🛠️ SDK: Mutations.Cooplace.GenerateReturnByAssetStatement 🔗 GraphQL API: Mutation.generateReturnByAssetStatement
801 Протокол решения совета о возврате паевого взноса имуществом по соглашению новации 🛠️ SDK: Mutations.Cooplace.GenerateReturnByAssetDecision 🔗 GraphQL API: Mutation.generateReturnByAssetDecision
802 Акт приёмки-передачи имущества 🛠️ SDK: Mutations.Cooplace.GenerateReturnByAssetAct 🔗 GraphQL API: Mutation.generateReturnByAssetAct
900 Заявление на возврат паевого взноса денежными средствами 🛠️ SDK: Mutations.Wallet.GenerateReturnByMoneyStatementDocument 🔗 GraphQL API: Mutation.generateReturnByMoneyStatementDocument
901 Решение совета о возврате паевого взноса 🛠️ SDK: Mutations.Wallet.GenerateReturnByMoneyDecisionDocument 🔗 GraphQL API: Mutation.generateReturnByMoneyDecisionDocument
1000 Оферта по капитализации 🛠️ SDK: Mutations.Capital.GenerateCapitalizationAgreement 🔗 GraphQL API: Mutation.capitalGenerateCapitalizationAgreement
1001 Договор участия в хозяйственной деятельности 🛠️ SDK: Mutations.Capital.GenerateGenerationContract 🔗 GraphQL API: Mutation.capitalGenerateGenerationContract
1002 Приложение к договору участия ⚠️ 🛠️ SDK: Mutations.Capital.GenerateAppendixGenerationContract не найден 🔗 GraphQL API: Mutation.capitalGenerateAppendixGenerationContract
1010 Заявление о расходах 🛠️ SDK: Mutations.Capital.GenerateExpenseStatement 🔗 GraphQL API: Mutation.capitalGenerateExpenseStatement
1011 Решение совета о расходах 🛠️ SDK: Mutations.Capital.GenerateExpenseDecision 🔗 GraphQL API: Mutation.capitalGenerateExpenseDecision
1020 Заявление об инвестировании денежных средств в генерацию 🛠️ SDK: Mutations.Capital.GenerateGenerationMoneyInvestStatement 🔗 GraphQL API: Mutation.capitalGenerateGenerationMoneyInvestStatement
1025 Заявление о возврате неиспользованных средств генерации 🛠️ SDK: Mutations.Capital.GenerateGenerationMoneyReturnUnusedStatement 🔗 GraphQL API: Mutation.capitalGenerateGenerationMoneyReturnUnusedStatement
1030 Заявление об инвестировании средств в капитализацию 🛠️ SDK: Mutations.Capital.GenerateCapitalizationMoneyInvestStatement 🔗 GraphQL API: Mutation.capitalGenerateCapitalizationMoneyInvestStatement
1040 Заявление на внесение результата интеллектуальной деятельности 🛠️ SDK: Mutations.Capital.GenerateResultContributionStatement 🔗 GraphQL API: Mutation.capitalGenerateResultContributionStatement
1041 Решение совета об инвестициях по результатам 🛠️ SDK: Mutations.Capital.GenerateResultContributionDecision 🔗 GraphQL API: Mutation.capitalGenerateResultContributionDecision
1042 Акт приема-передачи результата интеллектуальной деятельности 🛠️ SDK: Mutations.Capital.GenerateResultContributionAct 🔗 GraphQL API: Mutation.capitalGenerateResultContributionAct
1050 Заявление на получение займа 🛠️ SDK: Mutations.Capital.GenerateGetLoanStatement 🔗 GraphQL API: Mutation.capitalGenerateGetLoanStatement
1051 Решение совета о предоставлении займа 🛠️ SDK: Mutations.Capital.GenerateGetLoanDecision 🔗 GraphQL API: Mutation.capitalGenerateGetLoanDecision
1060 Заявление об инвестировании имущества в генерацию 🛠️ SDK: Mutations.Capital.GenerateGenerationPropertyInvestStatement 🔗 GraphQL API: Mutation.capitalGenerateGenerationPropertyInvestStatement
1061 Решение совета об инвестировании имущества в генерацию 🛠️ SDK: Mutations.Capital.GenerateGenerationPropertyInvestDecision 🔗 GraphQL API: Mutation.capitalGenerateGenerationPropertyInvestDecision
1062 Акт приема-передачи имущества в генерацию 🛠️ SDK: Mutations.Capital.GenerateGenerationPropertyInvestAct 🔗 GraphQL API: Mutation.capitalGenerateGenerationPropertyInvestAct
1070 Заявление об инвестировании имущества в капитализацию 🛠️ SDK: Mutations.Capital.GenerateCapitalizationPropertyInvestStatement 🔗 GraphQL API: Mutation.capitalGenerateCapitalizationPropertyInvestStatement
1071 Решение совета об инвестировании имущества в капитализацию 🛠️ SDK: Mutations.Capital.GenerateCapitalizationPropertyInvestDecision 🔗 GraphQL API: Mutation.capitalGenerateCapitalizationPropertyInvestDecision
1072 Акт приема-передачи имущества в капитализацию 🛠️ SDK: Mutations.Capital.GenerateCapitalizationPropertyInvestAct 🔗 GraphQL API: Mutation.capitalGenerateCapitalizationPropertyInvestAct
1080 Заявление о конвертации средств генерации в основной кошелек 🛠️ SDK: Mutations.Capital.GenerateGenerationToMainWalletConvertStatement 🔗 GraphQL API: Mutation.capitalGenerateGenerationToMainWalletConvertStatement
1081 Заявление о конвертации средств генерации в проект 🛠️ SDK: Mutations.Capital.GenerateGenerationToProjectConvertStatement 🔗 GraphQL API: Mutation.capitalGenerateGenerationToProjectConvertStatement
1082 Заявление о конвертации средств генерации в капитализацию 🛠️ SDK: Mutations.Capital.GenerateGenerationToCapitalizationConvertStatement 🔗 GraphQL API: Mutation.capitalGenerateGenerationToCapitalizationConvertStatement
1090 Заявление о конвертации средств капитализации в основной кошелек 🛠️ SDK: Mutations.Capital.GenerateCapitalizationToMainWalletConvertStatement 🔗 GraphQL API: Mutation.capitalGenerateCapitalizationToMainWalletConvertStatement

Для разработчиков

Генерация и подпись

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

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

import { Mutations } from '@coopenomics/sdk';

const variables: Mutations.Participants.GenerateParticipantApplication.IInput = {
  data: {
    block_num?: <null | number>; // Номер блока, на котором был создан документ
    braname: <string>; // Имя аккаунта кооперативного участка
    coopname: <string>; // Название кооператива, связанное с документом
    created_at?: <null | string>; // Дата и время создания документа
    generator?: <null | string>; // Имя генератора, использованного для создания документа
    lang?: <null | string>; // Язык документа
    links?: <null | string[]>; // Ссылки, связанные с документом
    signature?: <null | string>; // Изображение собственноручной подписи (base-64)
    skip_save: <boolean>; // Флаг пропуска сохранения документа (используется для предварительной генерации и демонстрации пользователю)
    timezone?: <null | string>; // Часовой пояс, в котором был создан документ
    title?: <null | string>; // Название документа
    username: <string>; // Имя пользователя, создавшего документ
    version?: <null | string>; // Версия генератора, использованного для создания документа
  };
  options?: {
    lang?: <null | string>; // Язык документа
    skip_save?: <null | boolean>; // Пропустить сохранение
  };
};

const { [Mutations.Participants.GenerateParticipantApplication.name]: result } = await client.Mutation(
  Mutations.Participants.GenerateParticipantApplication.mutation,
  { variables }
);
Результат
interface IOutput {
  generateParticipantApplication: {
    binary: <string>; // Бинарное содержимое документа (base64)
    full_title: <string>; // Полное название документа
    hash: <string>; // Хэш документа
    html: <string>; // HTML содержимое документа
    meta: <unknown>; // Метаданные документа
  };
}

Используем полученный документ в result для создания подписи:

import { Mutations, Classes } from '@coopenomics/sdk'

// 2. Подписываем документ с помощью SDK-класса Document
const signer = new Classes.Document('WIF_PRIVATE_KEY')
const signed = await signer.signDocument(document, 'user1', 1) //1 - это порядковый номер подписи
Результат
interface SignedBlockchainDocument {
  doc_hash: <string>; // Хэш содержимого документа
  hash: <string>; // Общий хэш (doc_hash + meta_hash)
  meta: <string>; // Метаинформация документа (в формате JSON-строки)
  meta_hash: <string>; // Хэш мета-данных
  signatures: <ModelTypes["SignatureInfo"][]>; // Вектор подписей
  version: <string>; // Версия стандарта документа
}

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

Важно

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

Мультиподпись

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

const signed = await signer.signDocument(document, 'user2', 2)