Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| de55c3aa3e | |||
| 32fc211006 | |||
| 24663bdaac | |||
| c3a433b591 | |||
| ca877ab294 | |||
| 4d6a03a142 | |||
| c185b0167f | |||
| 07f35ea084 | |||
| 4a13cd26c0 | |||
| 59a8b9b6ee | |||
| eb720a88ef | |||
| 58b8654989 | |||
| 138d7ab350 | |||
| b2adb56535 | |||
| 1f486ce48a | |||
| 7ca2695121 | |||
| bbfc592357 | |||
| 18646f8353 | |||
| e932260847 | |||
| b42394bfb4 | |||
| ff8f60e4b2 | |||
| abcd34f23d |
@@ -0,0 +1,174 @@
|
||||
import { describe, it } from 'vitest'
|
||||
|
||||
/**
|
||||
* Расширение Capital Test под эпик «Благорост» (Story 1.11).
|
||||
*
|
||||
* Существующий capital.test.ts (~2650 строк) покрывает базовые happy-paths.
|
||||
* Новые ветви Story 1.1–1.10 описаны здесь отдельно, чтобы не раздувать
|
||||
* монолитный файл. it.todo() — каркас под последующее наполнение helpers'ами
|
||||
* по аналогии с processDebt / processFundProgram / etc.
|
||||
*
|
||||
* Helpers, которые потребуются (часть из них появится в Эпиках 2–5):
|
||||
* - processDebtDeclinePay (gateway → debtpaydcln)
|
||||
* - processSettleDebt (settledebt деньгами)
|
||||
* - processMarkOverdue (markdebtoverd batch)
|
||||
* - processTopupProgramExpense (chairman → topupprogexp)
|
||||
* - processProgramExpense (createpgexp → apprvpgexp → authpgexp → pgexppay)
|
||||
* - processDeclineProgramExpense (declpgexp на разных фазах)
|
||||
* - processRequestRole / processApproveRole / processDeclineRole
|
||||
* - processInviteRole / processAcceptInvite / processDeclineInvite
|
||||
* - processRequestRateUpdate
|
||||
*/
|
||||
|
||||
describe('CAPITAL — расширения «Благорост» (Эпик 1)', () => {
|
||||
|
||||
describe('A — Займы', () => {
|
||||
it.todo(
|
||||
'Story 1.1: debtpaydcln возвращает PAY_PENDING → AUTHORIZED с last_pay_error и не удаляет запись'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.1: debtpayretry повторно отправляет outpay из AUTHORIZED без новой авторизации совета'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.1: debtpayretry отвергается если last_pay_error пустой (платёж ещё не пробовали)'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.2: settledebt на полную сумму → SETTLED, проводка REPAY 30000, contributors.debt_amount уменьшается'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.2: settledebt доступен и для OVERDUE, не только PAID'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.2: settledebt отвергается при amount ≠ debt.amount'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.3: debtpaycnfrm проставляет due_at = now + 1 год (фиксированный срок, решение 2026-05-19)'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.13: closedebt — компонент cancelled → автоматическое закрытие долга: o.cap.crtnma (Дт 04 / Кт 08, TRANSFER GENERATOR_FUND → NMA, создание НМА из коммитов-обеспечения) + o.cap.dbtwrf (Дт 80 / Кт 58, BURN LOAN_ISSUED, write-off долга); сегмент уменьшен на сумму займа; долг → WRITEOFF; без подписи пайщика и без сбора совета (решение 2026-05-19)'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.13: closedebt отвергается, если родительский компонент не в статусе cancelled'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.13: closedebt отвергается, если долг не в статусе paid/overdue'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.13: closedebt — глобальный contributors.debt_amount пайщика уменьшен; запись в loan.debts удалена; loan.summaries.total опущен'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.3: markdebtoverd переводит PAID → OVERDUE для долгов с now > due_at; PAID с due_at в будущем не трогает'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.3: markdebtoverd обрабатывает не более batch_limit (25) записей за вызов'
|
||||
)
|
||||
})
|
||||
|
||||
describe('A2 — Централизованный учёт займов (loan-контракт + счётчик сегмента)', () => {
|
||||
it.todo(
|
||||
'Story 1.12: loan-контракт изолирован — loan.debts хранит только (coopname, username, debt_hash, amount, repaid_at) + source_contract; project_hash отсутствует'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.12: debtpaycnfrm создаёт запись в loan.debts через inline createdebt и поднимает loan.summaries.total пайщика'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.12: settledebt в capital удаляет запись из loan.debts через inline settledebt и опускает loan.summaries.total'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.12: debt в loan.debts помечается source_contract = вызвавший контракт (capital для capital→loan)'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.12: debtpaycnfrm увеличивает active_debts_count на сегменте (binary_extension), settledebt — уменьшает'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.12: signact2 с N≤10 займами на проекте — все погашаются inline-actions в loan; active_debts_count = 0'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.12: signact2 с >10 займами на проекте (active_debts_count > 10) — fail сразу по счётчику, без обхода byprojhash'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.12: loan.createdebt от аккаунта вне contracts_whitelist — fail (только capital/marketplace/...)'
|
||||
)
|
||||
})
|
||||
|
||||
describe('B — Расходы программы', () => {
|
||||
it.todo(
|
||||
'Story 1.4: exppaycnfrm после complete_expense делает Ledger2 PAY_EXPENSE (BLAGOROST_FUND → SOV_EXPENSES)'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.5/1.6: topupprogexp пополняет program_expense_pool из global_available_invest_pool'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.5: createpgexp резервирует amount; вторая createpgexp на превышение — fail'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.5: cycle createpgexp → apprvpgexp → authpgexp → pgexppay = paid + delete + Ledger2 PAY_EXPENSE'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.5: declpgexp на любом из {created, approved, authorized} возвращает reserved → pool, удаляет запись'
|
||||
)
|
||||
})
|
||||
|
||||
describe('D — Двухуровневые допуски и approved-ставка (бездокументарная схема)', () => {
|
||||
it.todo(
|
||||
'Story 1.7: requestrole без statement создаёт rolerequests со статусом PENDING; approverole без master_decision фиксирует approved_rate'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.7: requestrole с role вне Role::{CREATOR, AUTHOR, MASTER} — fail "Допустимые роли заявки..."'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.7: requestrateu создаёт запись с role = Role::NONE и request_type = RequestType::RATE_UPDATE'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.8: createcmmt с approved_rate=1200 и creator_hours=4 → generation_amounts на 4800, не на 6000 (глобальная)'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.7: editcontrib с новой глобальной ставкой не меняет approved_rate_per_hour на сегменте'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.7: inviterole без statement → acceptinvite применяет инвайт-ставку к сегменту; declinvite оставляет сегмент без approved-rate'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.7: declinerole оставляет запись со статусом DECLINED + reason, сегмент не трогает'
|
||||
)
|
||||
|
||||
it.todo(
|
||||
'Story 1.7: requestrateu + approverole с новой ставкой обновляет approved_rate в сегменте; role.apply_role_to_project не вызывается'
|
||||
)
|
||||
})
|
||||
|
||||
describe('E — Event ridges (Story 1.10)', () => {
|
||||
it.todo(
|
||||
'Все новые actions вызывают require_recipient для (автор события, обратная сторона, председатель)'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* @brief Закрывает долг созданием НМА кооператива из коммитов-обеспечения.
|
||||
*
|
||||
* Срабатывает, когда родительский компонент проекта явно перешёл в `cancelled`:
|
||||
* проект до акта-2 не дойдёт, возврат через `signact2` невозможен.
|
||||
* Коммиты-обеспечение, накопленные пайщиком на сегменте проекта-компонента
|
||||
* (`w.cap.gen`), формируют НМА кооператива на отдельном кошельке `w.cap.nma`,
|
||||
* а обязательство пайщика по займу списывается (write-off). Никаких санкций
|
||||
* к пайщику не применяется, совет не собирается, подпись пайщика не требуется.
|
||||
*
|
||||
* Двухоперационная свёртка ledger2:
|
||||
* - o.cap.crtnma — TRANSFER w.cap.gen → w.cap.nma, Дт 04 / Кт 08.
|
||||
* Зеркалит o.cap.accept, но без участия пайщика: НМА из
|
||||
* коммитов-обеспечения долга идут в имущество кооператива,
|
||||
* а не на share программы пайщика.
|
||||
* - o.cap.dbtwrf — BURN w.cap.loan, Дт 80 / Кт 58.
|
||||
* Списание долга (write-off). Зеркало o.cap.repay, но share
|
||||
* пайщика не пополняется — долг закрыт имущественно.
|
||||
*
|
||||
* Решение 2026-05-19 (Игорь Смуров / Алексей Муравьёв): срок займа фиксированный
|
||||
* (1 год от выплаты), но при явной отмене компонента закрытие происходит
|
||||
* досрочно — без ожидания due_at.
|
||||
*
|
||||
* @param coopname Наименование кооператива (scope)
|
||||
* @param debt_hash Хеш закрываемого займа
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*
|
||||
* @note Авторизация только от @p coopname. Action идёт из backend кооператива,
|
||||
* отслеживающего переход компонента в `cancelled`.
|
||||
*/
|
||||
void capital::closedebt(name coopname, checksum256 debt_hash) {
|
||||
require_auth(coopname);
|
||||
|
||||
// 1. Получить долг
|
||||
auto exist_debt = Capital::Debts::get_debt_or_fail(coopname, debt_hash);
|
||||
|
||||
// 2. Долг должен быть активным (выплачен, не погашен)
|
||||
eosio::check(exist_debt.status == Capital::Debts::Status::PAID
|
||||
|| exist_debt.status == Capital::Debts::Status::OVERDUE,
|
||||
"Закрытие долга допустимо для активного (paid) или просроченного (overdue) займа");
|
||||
|
||||
// 3. Guard на статус родительского компонента: только `cancelled` (отменён).
|
||||
// Если компонент завершён нормально (`finalized`/`result`) — возврат идёт
|
||||
// через signact2 → o.cap.repay, не сюда.
|
||||
auto project = Capital::Projects::get_project_or_fail(coopname, exist_debt.project_hash);
|
||||
eosio::check(project.status == Capital::Projects::Status::CANCELLED,
|
||||
"Закрытие долга через НМА допустимо только при cancelled статусе компонента проекта");
|
||||
|
||||
// 4. Сегмент пайщика на компоненте
|
||||
auto exist_segment = Capital::Segments::get_segment_or_fail(coopname,
|
||||
exist_debt.project_hash, exist_debt.username, "Сегмент пайщика не найден");
|
||||
|
||||
// 5. Контрибьютор для обновления глобального долга
|
||||
auto contributor = Capital::Contributors::get_contributor(coopname, exist_debt.username);
|
||||
eosio::check(contributor.has_value(), "Контрибьютор не найден");
|
||||
|
||||
auto memo = Capital::Memo::get_debt_memo(exist_debt.username);
|
||||
|
||||
// 6. o.cap.crtnma: TRANSFER GENERATOR_FUND → NMA, Дт 04 / Кт 08.
|
||||
// Из коммитов-обеспечения долга создаётся НМА кооператива.
|
||||
Ledger2::apply(_capital, coopname, operations::capital::CREATE_NMA,
|
||||
exist_debt.amount, exist_debt.username, debt_hash, memo);
|
||||
|
||||
// 7. o.cap.dbtwrf: BURN LOAN_ISSUED, Дт 80 / Кт 58.
|
||||
// Финансовое вложение кооператива в пайщика списано (write-off); share пайщика
|
||||
// не пополняется — долг закрыт имущественно.
|
||||
Ledger2::apply(_capital, coopname, operations::capital::DEBT_WRITEOFF,
|
||||
exist_debt.amount, exist_debt.username, debt_hash, memo);
|
||||
|
||||
// 8. Обновить агрегаты сегмента: -debt_amount, -active_debts_count.
|
||||
Capital::Segments::decrease_debt_amount(coopname, exist_segment.id, exist_debt.amount);
|
||||
Capital::Segments::decrease_active_debts_count(coopname, exist_debt.project_hash, exist_debt.username);
|
||||
|
||||
// 9. Уменьшить used_for_compensation в проекте (зеркало createdebt).
|
||||
Capital::Projects::subtract_used_for_compensation(coopname, project.id, exist_debt.amount);
|
||||
|
||||
// 10. Глобальный долг контрибьютора уменьшаем (как в settledebt).
|
||||
Capital::Contributors::decrease_debt_amount(coopname, contributor->id, exist_debt.amount);
|
||||
|
||||
// 11. Запись долга → WRITEOFF (для аудита).
|
||||
Capital::Debts::mark_writeoff(coopname, exist_debt.id);
|
||||
|
||||
// 12. Централизованный реестр в loan-контракте: уменьшаем сводную, удаляем запись.
|
||||
Loan::settle_debt(_capital, coopname, exist_debt.username, debt_hash, exist_debt.amount);
|
||||
|
||||
// 13. Уведомить пайщика и кооператив.
|
||||
require_recipient(exist_debt.username);
|
||||
require_recipient(coopname);
|
||||
}
|
||||
@@ -19,11 +19,11 @@ void capital::debtauthcnfr(eosio::name coopname, checksum256 debt_hash, document
|
||||
|
||||
// Получаем долг
|
||||
auto exist_debt = Capital::Debts::get_debt_or_fail(coopname, debt_hash);
|
||||
|
||||
// Обновляем статус долга
|
||||
Capital::Debts::update_debt_status(coopname, exist_debt.id, Capital::Debts::Status::AUTHORIZED,
|
||||
_capital, decision);
|
||||
|
||||
|
||||
// Фиксируем решение совета и переводим долг в pay_pending —
|
||||
// outpay уходит в gateway, дальше debtpaycnfrm (успех) или debtpaydcln (отказ).
|
||||
Capital::Debts::start_pay(coopname, exist_debt.id, decision, _capital);
|
||||
|
||||
// создаём объект исходящего платежа в gateway с коллбэком после обработки
|
||||
::Gateway::create_outcome(
|
||||
_capital,
|
||||
|
||||
@@ -18,12 +18,11 @@ void capital::debtpaycnfrm(name coopname, checksum256 debt_hash) {
|
||||
auto contributor = Capital::Contributors::get_contributor(coopname, exist_debt.username);
|
||||
eosio::check(contributor.has_value(), "Контрибьютор не найден");
|
||||
|
||||
// Проверяем что долг в статусе 'authorized' (готов к выплате)
|
||||
eosio::check(exist_debt.status == Capital::Debts::Status::AUTHORIZED,
|
||||
"Долг должен быть в статусе 'authorized' для подтверждения оплаты");
|
||||
|
||||
// Обновляем статус долга на PAID
|
||||
Capital::Debts::update_debt_status(coopname, exist_debt.id, Capital::Debts::Status::PAID, _gateway);
|
||||
// Переводим долг в PAID + проставляем due_at = now + 1 год (срок погашения,
|
||||
// решение 2026-05-19 — срок зафиксирован, не привязан к календарному
|
||||
// сроку проекта).
|
||||
// confirm_paid внутри проверяет статус pay_pending и атомарно сбрасывает last_pay_error.
|
||||
Capital::Debts::confirm_paid(coopname, exist_debt.id, _gateway);
|
||||
|
||||
// Выдача пайщику беспроцентного займа: Dr 58 / Cr 51, ISSUE LOAN_ISSUED (4051).
|
||||
// Семантика момента — деньги ушли пайщику, у кооператива появилось финансовое
|
||||
@@ -34,5 +33,17 @@ void capital::debtpaycnfrm(name coopname, checksum256 debt_hash) {
|
||||
|
||||
// Увеличиваем долг contributor (теперь долг активен и должен быть погашен через внесение результата)
|
||||
Capital::Contributors::increase_debt_amount(coopname, contributor->id, exist_debt.amount);
|
||||
|
||||
};
|
||||
|
||||
// Локальный счётчик займов пайщика на проекте этого сегмента (+1).
|
||||
// Используется в signact2 как лимит «10 займов на проект» без обхода byprojhash.
|
||||
Capital::Segments::increase_active_debts_count(coopname, exist_debt.project_hash, exist_debt.username);
|
||||
|
||||
// Централизованный учёт займа в контракте loan — loan-контракт независим от capital,
|
||||
// знает только (coopname, username, debt_hash, amount, repaid_at) + source_contract
|
||||
// (заполняется самим loan::createdebt из payer-авторизации). Локальная привязка к
|
||||
// проекту/программе остаётся в Capital::Debts (поле project_hash).
|
||||
// due_at уже проставлен в confirm_paid, читаем обновлённое значение.
|
||||
auto fresh_debt = Capital::Debts::get_debt_or_fail(coopname, debt_hash);
|
||||
Loan::create_debt(_capital, coopname, exist_debt.username,
|
||||
debt_hash, fresh_debt.due_at, exist_debt.amount);
|
||||
};
|
||||
@@ -1,23 +1,27 @@
|
||||
/**
|
||||
* @brief Отклоняет оплату долга
|
||||
* Отклоняет оплату долга и удаляет его из базы данных:
|
||||
* - Получает долг
|
||||
* - Удаляет долг из базы данных с указанием причины
|
||||
* @param coopname Наименование кооператива
|
||||
* @param debt_hash Хеш долга для отклонения оплаты
|
||||
* @param reason Причина отклонения оплаты долга
|
||||
* @brief Отклоняет оплату долга по реквизитам (gateway возвращает ошибку перечисления).
|
||||
*
|
||||
* Решение совета уже принято в @ref capital::debtauthcnfr, долг НЕ удаляется и НЕ откатывается
|
||||
* в approved. Запись остаётся, статус возвращается из @c pay_pending в @c authorized
|
||||
* с заполненным полем @c last_pay_error = @p reason. Из @c authorized доступна повторная
|
||||
* отправка в gateway без новой авторизации совета.
|
||||
*
|
||||
* @param coopname Наименование кооператива
|
||||
* @param debt_hash Хеш долга, для которого пришёл отказ от gateway
|
||||
* @param reason Текстовая причина отказа (заполняется gateway-оператором)
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
|
||||
*
|
||||
* @note Авторизация требуется от аккаунта: @p _gateway
|
||||
*/
|
||||
void capital::debtpaydcln(name coopname, checksum256 debt_hash, std::string reason) {
|
||||
require_auth(_gateway);
|
||||
|
||||
//TODO: отказ в оплате по реквизитам не должен приводить к удалению и откату т.к. решение о займе уже принято
|
||||
// // Получаем долг
|
||||
// auto exist_debt = Capital::Debts::get_debt_or_fail(coopname, debt_hash);
|
||||
|
||||
// // Удаляем долг
|
||||
// Capital::Debts::delete_debt(coopname, debt_hash);
|
||||
};
|
||||
|
||||
auto exist_debt = Capital::Debts::get_debt_or_fail(coopname, debt_hash);
|
||||
|
||||
Capital::Debts::mark_pay_declined(coopname, exist_debt.id, reason, _capital);
|
||||
|
||||
// event ridge: должник и кооператив получают уведомление об отказе оплаты.
|
||||
require_recipient(exist_debt.username);
|
||||
require_recipient(coopname);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @brief Повторная отправка платежа по займу в gateway после предыдущего отказа.
|
||||
*
|
||||
* Допустимо только из AUTHORIZED с непустым last_pay_error (то есть после debtpaydcln).
|
||||
* Использует уже сохранённое решение совета — новой авторизации не требуется.
|
||||
* Переводит займ в PAY_PENDING, очищает last_pay_error и создаёт новый outpay.
|
||||
*
|
||||
* @param coopname Кооператив
|
||||
* @param debt_hash Хеш займа для повторной отправки
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*
|
||||
* @note Авторизация требуется от @p coopname (председатель/backend). Пайщик подпись не ставит.
|
||||
*/
|
||||
void capital::debtpayretry(eosio::name coopname, checksum256 debt_hash) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto exist_debt = Capital::Debts::get_debt_or_fail(coopname, debt_hash);
|
||||
eosio::check(exist_debt.status == Capital::Debts::Status::AUTHORIZED,
|
||||
"Повторная отправка платежа возможна только из статуса authorized");
|
||||
eosio::check(!exist_debt.last_pay_error.empty(),
|
||||
"Повторная отправка платежа возможна только после предыдущего отказа (debtpaydcln)");
|
||||
|
||||
Capital::Debts::start_pay(coopname, exist_debt.id, exist_debt.authorization, _capital);
|
||||
|
||||
::Gateway::create_outcome(
|
||||
_capital,
|
||||
coopname,
|
||||
exist_debt.username,
|
||||
exist_debt.debt_hash,
|
||||
exist_debt.amount,
|
||||
_capital,
|
||||
Names::Capital::CONFIRM_DEBT_PAYMENT,
|
||||
Names::Capital::DECLINE_DEBT
|
||||
);
|
||||
|
||||
// event ridge: должник и председатель видят повторную отправку.
|
||||
require_recipient(exist_debt.username);
|
||||
require_recipient(coopname);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @brief Помечает просроченные займы статусом OVERDUE.
|
||||
*
|
||||
* Долг считается просроченным, когда current_time_point > debt.due_at
|
||||
* (поле due_at проставляется в debtpaycnfrm на 1 год от даты выплаты;
|
||||
* срок зафиксирован — решение 2026-05-19).
|
||||
* Из OVERDUE долг можно так же закрыть settledebt или signact2 как из PAID.
|
||||
*
|
||||
* Action рассчитан на запуск из backend через @Cron (см. Эпик 4) — раз в сутки.
|
||||
* Один вызов помечает не более 25 записей чтобы уложиться в 0.5-секундный
|
||||
* лимит транзакции EOSIO; backend повторяет до тех пор, пока action не
|
||||
* помечает 0 — это сигнал, что в текущем scope больше нечего переводить.
|
||||
*
|
||||
* @param coopname Кооператив (scope таблицы debts)
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*
|
||||
* @note Авторизация требуется от аккаунта: @p coopname (председатель / системный).
|
||||
*/
|
||||
void capital::markdebtoverd(name coopname) {
|
||||
require_auth(coopname);
|
||||
|
||||
constexpr uint32_t MAX_PER_BATCH = 25;
|
||||
Capital::Debts::sweep_overdue(coopname, MAX_PER_BATCH);
|
||||
}
|
||||
@@ -1,22 +1,57 @@
|
||||
/**
|
||||
* @brief Погашает долг участника
|
||||
* Погашает долг участника в системе кооператива:
|
||||
* - Проверяет подлинность заявления о погашении
|
||||
* - Обрабатывает погашение долга (TODO: реализация)
|
||||
* @param coopname Наименование кооператива
|
||||
* @param username Наименование пользователя-должника
|
||||
* @param amount Сумма погашения долга
|
||||
* @param statement Заявление о погашении долга
|
||||
* @brief Погашает активный заём пайщика.
|
||||
*
|
||||
* Прямой канал погашения (без сдачи результата): сумма, ровно равная остатку долга,
|
||||
* закрывает заём. В ledger2 фиксируется операция REPAY (Dr 80 / Cr 58),
|
||||
* глобальный долг пайщика в contributors.debt_amount уменьшается, запись долга
|
||||
* переходит в SETTLED.
|
||||
*
|
||||
* Альтернативный путь закрытия — через подписание акта-2 по результату (см. signact2.cpp),
|
||||
* там та же операция REPAY вызывается на сумму result->debt_amount.
|
||||
*
|
||||
* @param coopname Наименование кооператива
|
||||
* @param debt_hash Хеш погашаемого долга
|
||||
* @param amount Сумма погашения (должна совпасть с debt.amount — полное закрытие)
|
||||
* @param statement Документ-заявление на погашение (SettleDebtStatement)
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
|
||||
* @note Авторизация требуется от аккаунта: @p coopname
|
||||
*
|
||||
* @note Авторизация только от @p coopname. Пайщик транзакции не подписывает — backend кооператива
|
||||
* подставляет себя.
|
||||
*/
|
||||
void capital::settledebt(name coopname, name username, eosio::asset amount, document2 statement){
|
||||
void capital::settledebt(name coopname, checksum256 debt_hash, eosio::asset amount, document2 statement) {
|
||||
require_auth(coopname);
|
||||
//TODO: ?
|
||||
// // Получаем долг
|
||||
// auto exist_debt = Capital::Debts::get_debt_or_fail(coopname, debt_hash);
|
||||
|
||||
|
||||
}
|
||||
|
||||
auto exist_debt = Capital::Debts::get_debt_or_fail(coopname, debt_hash);
|
||||
|
||||
eosio::check(exist_debt.status == Capital::Debts::Status::PAID
|
||||
|| exist_debt.status == Capital::Debts::Status::OVERDUE,
|
||||
"Погашение допустимо для активного (paid) или просроченного (overdue) займа");
|
||||
|
||||
eosio::check(amount.is_valid(), "Сумма погашения некорректна");
|
||||
eosio::check(amount.symbol == exist_debt.amount.symbol,
|
||||
"Символ суммы погашения не совпадает с символом долга");
|
||||
eosio::check(amount == exist_debt.amount,
|
||||
"Сумма погашения должна полностью покрывать остаток долга");
|
||||
|
||||
auto contributor = Capital::Contributors::get_contributor(coopname, exist_debt.username);
|
||||
eosio::check(contributor.has_value(), "Контрибьютор не найден");
|
||||
|
||||
// Возврат займа пайщиком: Dr 80 / Cr 58 — закрываем финансовое вложение в участника.
|
||||
auto memo = Capital::Memo::get_settle_debt_memo(exist_debt.username, debt_hash);
|
||||
Ledger2::apply(_capital, coopname, operations::capital::REPAY, amount,
|
||||
exist_debt.username, debt_hash, memo);
|
||||
|
||||
Capital::Contributors::decrease_debt_amount(coopname, contributor->id, amount);
|
||||
Capital::Debts::mark_settled(coopname, exist_debt.id, memo, _capital);
|
||||
|
||||
// Локальный счётчик займов пайщика на сегменте этого проекта (-1).
|
||||
Capital::Segments::decrease_active_debts_count(coopname, exist_debt.project_hash, exist_debt.username);
|
||||
|
||||
// Централизованный учёт: уменьшаем сводную и удаляем запись из loan.debts через inline action.
|
||||
Loan::settle_debt(_capital, coopname, exist_debt.username, debt_hash, amount);
|
||||
|
||||
// event ridge: и должник, и председатель видят факт погашения.
|
||||
require_recipient(exist_debt.username);
|
||||
require_recipient(coopname);
|
||||
}
|
||||
|
||||
+10
-3
@@ -28,9 +28,16 @@ void capital::exppaycnfrm(eosio::name coopname, checksum256 expense_hash) {
|
||||
|
||||
// Обновляем used_expense_pool в проекте
|
||||
Capital::Projects::complete_expense(coopname, project.id, expense.amount);
|
||||
|
||||
|
||||
// Списываем сумму расхода с программного фонда в пул хозрасходов:
|
||||
// TRANSFER BLAGOROST_FUND → SOV_EXPENSES (без Dr/Cr — оба под счётом 80).
|
||||
// Бухгалтерская проводка Dr 26 / Cr 51 на расчётном фиксируется gateway отдельно.
|
||||
auto memo = Capital::Memo::get_expense_pay_memo(expense_hash);
|
||||
Ledger2::apply(_capital, coopname,
|
||||
operations::capital::PAY_EXPENSE,
|
||||
expense.amount, expense.username,
|
||||
expense_hash, memo);
|
||||
|
||||
// Удаляем запись расхода
|
||||
Capital::Expenses::delete_expense(coopname, expense.id);
|
||||
|
||||
//TODO: здесь должна быть проводка по фонду
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @brief Одобряет программный расход председателем и отправляет в совет.
|
||||
*
|
||||
* @param coopname Кооператив
|
||||
* @param approver Председатель (или доверенный)
|
||||
* @param expense_hash Хеш расхода
|
||||
* @param approved_statement Принятая записка (с подписью председателя)
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*
|
||||
* @note Авторизация требуется от @p coopname.
|
||||
*/
|
||||
void capital::apprvpgexp(name coopname, name approver, checksum256 expense_hash,
|
||||
document2 approved_statement) {
|
||||
require_auth(coopname);
|
||||
|
||||
verify_document_or_fail(approved_statement);
|
||||
|
||||
auto expense = Capital::Expenses::get_program_expense_or_fail(coopname, expense_hash);
|
||||
eosio::check(expense.status == Capital::Expenses::Status::CREATED,
|
||||
"Программный расход должен быть в статусе 'created'");
|
||||
|
||||
Capital::Expenses::set_program_approved(coopname, expense.id, approved_statement);
|
||||
|
||||
::Soviet::create_agenda(
|
||||
_capital,
|
||||
coopname,
|
||||
expense.username,
|
||||
Names::SovietActions::CAPITAL_RESOLVE_PROGRAM_EXPENSE,
|
||||
expense_hash,
|
||||
_capital,
|
||||
Names::Capital::AUTHORIZE_PROGRAM_EXPENSE,
|
||||
Names::Capital::DECLINE_PROGRAM_EXPENSE,
|
||||
expense.expense_statement,
|
||||
std::string("")
|
||||
);
|
||||
|
||||
// event ridge: автор расхода и approver (председатель/доверенный) уведомляются.
|
||||
require_recipient(expense.username);
|
||||
require_recipient(approver);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @brief Авторизует программный расход советом и создаёт исходящий платёж в gateway.
|
||||
*
|
||||
* @param coopname Кооператив
|
||||
* @param expense_hash Хеш расхода
|
||||
* @param authorization Документ-решение совета
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*
|
||||
* @note Авторизация требуется от @p _soviet.
|
||||
*/
|
||||
void capital::authpgexp(name coopname, checksum256 expense_hash, document2 authorization) {
|
||||
require_auth(_soviet);
|
||||
|
||||
auto expense = Capital::Expenses::get_program_expense_or_fail(coopname, expense_hash);
|
||||
eosio::check(expense.status == Capital::Expenses::Status::APPROVED,
|
||||
"Программный расход должен быть в статусе 'approved'");
|
||||
|
||||
Capital::Expenses::set_program_authorized(coopname, expense.id, authorization);
|
||||
|
||||
Action::send<createoutpay_interface>(
|
||||
_gateway,
|
||||
Names::External::CREATE_OUTPAY,
|
||||
_capital,
|
||||
coopname,
|
||||
expense.username,
|
||||
expense.expense_hash,
|
||||
expense.amount,
|
||||
_capital,
|
||||
Names::Capital::CONFIRM_PROGRAM_EXPENSE_PAYMENT,
|
||||
Names::Capital::DECLINE_PROGRAM_EXPENSE
|
||||
);
|
||||
|
||||
// event ridge: автор расхода и председатель уведомляются о решении совета.
|
||||
require_recipient(expense.username);
|
||||
require_recipient(coopname);
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @brief Создаёт целевой программный расход и резервирует средства в state.
|
||||
*
|
||||
* Программный расход — целевое списание программы «Благорост», не привязанное к проекту.
|
||||
* Источник средств — пул @c program_expense_pool, наполняемый через @ref capital::topupprogexp.
|
||||
*
|
||||
* @param coopname Кооператив
|
||||
* @param expense_hash Хеш расхода (анкер процесса p.cap.expns)
|
||||
* @param creator Инициатор (председатель / казначей; авторизуется через coopname)
|
||||
* @param amount Сумма
|
||||
* @param description Описание (для отчётности)
|
||||
* @param statement Документ-заявление (ProgramExpenseStatement)
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*
|
||||
* @note Авторизация требуется от @p coopname.
|
||||
*/
|
||||
void capital::createpgexp(name coopname, checksum256 expense_hash, name creator,
|
||||
eosio::asset amount, std::string description, document2 statement) {
|
||||
require_auth(coopname);
|
||||
|
||||
verify_document_or_fail(statement);
|
||||
Wallet::validate_asset(amount);
|
||||
eosio::check(amount.amount > 0, "Сумма программного расхода должна быть положительной");
|
||||
|
||||
auto exist = Capital::get_program_expense(coopname, expense_hash);
|
||||
eosio::check(!exist.has_value(), "Программный расход с указанным хэшем уже существует");
|
||||
|
||||
// Резервируем средства в пуле программных расходов сразу при создании,
|
||||
// чтобы две параллельные заявки не превысили доступный пул.
|
||||
Capital::State::reserve_program_expense(coopname, amount);
|
||||
|
||||
Capital::Expenses::create_program_expense(coopname, expense_hash, creator,
|
||||
amount, description, statement);
|
||||
|
||||
// event ridge: создатель расхода и председатель видят появление заявки.
|
||||
require_recipient(creator);
|
||||
require_recipient(coopname);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @brief Отклоняет программный расход на любом этапе до подтверждения оплаты.
|
||||
*
|
||||
* Допустимо из created (отказ председателя), approved (отказ совета через callback)
|
||||
* и authorized (отказ gateway по реквизитам). Возвращает зарезервированную сумму
|
||||
* обратно в program_expense_pool и удаляет запись.
|
||||
*
|
||||
* @param coopname Кооператив
|
||||
* @param expense_hash Хеш расхода
|
||||
* @param reason Причина отказа
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*
|
||||
* @note Авторизация требуется от @p coopname (если отказ инициирован председателем),
|
||||
* @p _soviet (если совет отказал) или @p _gateway (если выпуск платежа провалился).
|
||||
*/
|
||||
void capital::declpgexp(name coopname, checksum256 expense_hash, std::string reason) {
|
||||
eosio::check(
|
||||
eosio::has_auth(coopname) || eosio::has_auth(_soviet) || eosio::has_auth(_gateway),
|
||||
"Отклонить программный расход может только председатель, совет или gateway"
|
||||
);
|
||||
|
||||
auto expense = Capital::Expenses::get_program_expense_or_fail(coopname, expense_hash);
|
||||
eosio::check(
|
||||
expense.status == Capital::Expenses::Status::CREATED ||
|
||||
expense.status == Capital::Expenses::Status::APPROVED ||
|
||||
expense.status == Capital::Expenses::Status::AUTHORIZED,
|
||||
"Программный расход уже завершён (paid/declined)"
|
||||
);
|
||||
|
||||
Capital::State::release_program_expense(coopname, expense.amount);
|
||||
Capital::Expenses::delete_program_expense(coopname, expense.id);
|
||||
|
||||
// event ridge: автор расхода и председатель уведомляются об отклонении.
|
||||
require_recipient(expense.username);
|
||||
require_recipient(coopname);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @brief Подтверждает оплату программного расхода (callback от gateway).
|
||||
*
|
||||
* Снимает резерв с @c program_expense_reserved, делает ledger2-проводку
|
||||
* @ref operations::capital::PAY_EXPENSE (TRANSFER BLAGOROST_FUND → SOV_EXPENSES)
|
||||
* и удаляет запись расхода — финал процесса @c p.cap.expns.
|
||||
*
|
||||
* @param coopname Кооператив
|
||||
* @param expense_hash Хеш расхода
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*
|
||||
* @note Авторизация требуется от @p _gateway.
|
||||
*/
|
||||
void capital::pgexppay(name coopname, checksum256 expense_hash) {
|
||||
require_auth(_gateway);
|
||||
|
||||
auto expense = Capital::Expenses::get_program_expense_or_fail(coopname, expense_hash);
|
||||
eosio::check(expense.status == Capital::Expenses::Status::AUTHORIZED,
|
||||
"Программный расход должен быть в статусе 'authorized' для подтверждения оплаты");
|
||||
|
||||
// Окончательное списание из резерва (деньги ушли gateway).
|
||||
Capital::State::consume_program_expense(coopname, expense.amount);
|
||||
|
||||
// Бухгалтерская/кошельковая проводка.
|
||||
auto memo = Capital::Memo::get_expense_pay_memo(expense_hash);
|
||||
Ledger2::apply(_capital, coopname,
|
||||
operations::capital::PAY_EXPENSE,
|
||||
expense.amount, expense.username,
|
||||
expense_hash, memo);
|
||||
|
||||
// Запись больше не нужна как ожидающая — закрываем процесс.
|
||||
Capital::Expenses::delete_program_expense(coopname, expense.id);
|
||||
|
||||
// event ridge: автор расхода и председатель уведомляются о факте оплаты.
|
||||
require_recipient(expense.username);
|
||||
require_recipient(coopname);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @brief Пополняет пул программных расходов из общего инвестиционного пула программы.
|
||||
*
|
||||
* Программные расходы — это целевые списания «Благорост», не аллоцированные ни в один проект
|
||||
* (хозяйственные расходы программы, оплата инфраструктуры программы и т.п.). Прежде чем
|
||||
* создавать запрос на программный расход, председатель пополняет дедикейтнутый пул
|
||||
* @c program_expense_pool из общего @c global_available_invest_pool — деньги остаются
|
||||
* на том же ledger2-кошельке @c BLAGOROST_FUND, меняется только разбивка по назначению.
|
||||
*
|
||||
* @param coopname Кооператив (scope state)
|
||||
* @param amount Сумма пополнения (символ строго совпадает с программой)
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*
|
||||
* @note Авторизация требуется от аккаунта: @p coopname (председатель).
|
||||
*/
|
||||
void capital::topupprogexp(name coopname, eosio::asset amount) {
|
||||
require_auth(coopname);
|
||||
|
||||
Capital::State::topup_program_expense_pool(coopname, amount);
|
||||
}
|
||||
@@ -47,11 +47,22 @@ void capital::createcmmt(eosio::name coopname, eosio::name username, checksum256
|
||||
eosio::check(creator_hours > 0, "Только положительная сумма часов создателя");
|
||||
eosio::check(creator_hours <= 1000000, "Превышено максимальное количество часов (1 млн)");
|
||||
|
||||
// Если на сегменте уже утверждена мастером approved-ставка (Story 1.7/1.8) — берём её,
|
||||
// иначе fallback на глобальную rate_per_hour из contributors. Это позволяет мастеру
|
||||
// компонента дифференцировать ставку по проекту без изменения договора УХД.
|
||||
auto segment_opt = Capital::Segments::get_segment(coopname, project_hash, username);
|
||||
eosio::asset effective_rate = contributor->rate_per_hour;
|
||||
if (segment_opt.has_value()
|
||||
&& segment_opt->approved_rate_per_hour.has_value()
|
||||
&& segment_opt->approved_rate_per_hour->amount > 0) {
|
||||
effective_rate = *segment_opt->approved_rate_per_hour;
|
||||
}
|
||||
|
||||
// считаем сумму фактических затрат создателя на основе ставки в час и потраченного времени
|
||||
eosio::asset creator_base = contributor -> rate_per_hour * creator_hours;
|
||||
|
||||
eosio::asset creator_base = effective_rate * creator_hours;
|
||||
|
||||
// Вычисляем фактическое изменение сумм генерации
|
||||
auto delta_amounts = Capital::Core::Generation::calculate_fact_generation_amounts(contributor -> rate_per_hour, creator_hours);
|
||||
auto delta_amounts = Capital::Core::Generation::calculate_fact_generation_amounts(effective_rate, creator_hours);
|
||||
|
||||
// Создаем коммит без отправки на аппрув председателю
|
||||
// Одобрение осуществляется мастером проекта через approvecmmt
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @brief Кандидат принимает инвайт от мастера: ставка/часы из заявки становятся
|
||||
* approved-параметрами сегмента.
|
||||
*
|
||||
* @param coopname Кооператив
|
||||
* @param request_hash Хеш инвайт-заявки
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*
|
||||
* @note Авторизация требуется от @p coopname (backend подставляет кандидата).
|
||||
*/
|
||||
void capital::acceptinvite(name coopname, checksum256 request_hash) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto req = Capital::RoleRequests::get_role_request_or_fail(coopname, request_hash);
|
||||
eosio::check(req.direction == Capital::RoleRequests::Direction::INVITE,
|
||||
"Принять можно только инвайт");
|
||||
|
||||
Capital::RoleRequests::approve(coopname, req.id, req.rate_per_hour, req.hours_per_day);
|
||||
Capital::Segments::set_approved_rate(
|
||||
coopname, req.project_hash, req.username, req.rate_per_hour, req.hours_per_day
|
||||
);
|
||||
|
||||
// Подключаем кандидата к проекту по приглашённой роли.
|
||||
if (req.request_type == Capital::RoleRequests::RequestType::ROLE) {
|
||||
Capital::RoleRequests::apply_role_to_project(
|
||||
coopname, req.project_hash, req.username, req.role
|
||||
);
|
||||
}
|
||||
|
||||
// event ridge: кандидат и мастер видят принятие инвайта.
|
||||
require_recipient(req.username);
|
||||
require_recipient(req.master);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @brief Мастер компонента одобряет заявку на L2-допуск либо обновление approved-ставки.
|
||||
*
|
||||
* Фиксирует approved-ставку и норматив часов в сегменте через
|
||||
* @ref Capital::Segments::set_approved_rate — далее `createcmmt` берёт их.
|
||||
* Сегмент должен уже существовать (создаётся при подписании приложения договора УХД
|
||||
* на этот проект через apprvappndx — L1-допуск).
|
||||
*
|
||||
* Бездокументарная схема: решение фиксируется только подписью транзакции
|
||||
* (require_auth coopname), документ-решение мастера не передаётся.
|
||||
*
|
||||
* @param coopname Кооператив
|
||||
* @param request_hash Хеш заявки (requestrole либо requestrateu)
|
||||
* @param approved_rate Утверждённая мастером ставка часа (≠ запрошенной — допустимо)
|
||||
* @param approved_hours Утверждённая норма часов
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*/
|
||||
void capital::approverole(name coopname, checksum256 request_hash,
|
||||
eosio::asset approved_rate, uint64_t approved_hours) {
|
||||
require_auth(coopname);
|
||||
|
||||
Wallet::validate_asset(approved_rate);
|
||||
eosio::check(approved_rate.amount > 0, "Утверждённая ставка должна быть положительной");
|
||||
eosio::check(approved_hours > 0 && approved_hours <= 8, "Норма часов — от 1 до 8");
|
||||
|
||||
auto req = Capital::RoleRequests::get_role_request_or_fail(coopname, request_hash);
|
||||
|
||||
Capital::RoleRequests::approve(coopname, req.id, approved_rate, approved_hours);
|
||||
|
||||
Capital::Segments::set_approved_rate(
|
||||
coopname, req.project_hash, req.username, approved_rate, approved_hours
|
||||
);
|
||||
|
||||
// Только для ROLE-заявок: подключаем участника к проекту по заявленной роли.
|
||||
// RATE_UPDATE роль не меняет, только ставку — inline action не нужен.
|
||||
if (req.request_type == Capital::RoleRequests::RequestType::ROLE) {
|
||||
Capital::RoleRequests::apply_role_to_project(
|
||||
coopname, req.project_hash, req.username, req.role
|
||||
);
|
||||
}
|
||||
|
||||
// event ridge: заявитель и мастер компонента видят решение.
|
||||
require_recipient(req.username);
|
||||
require_recipient(req.master);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @brief Мастер компонента отклоняет заявку на L2-допуск / на обновление approved-ставки.
|
||||
* Запись остаётся в таблице со статусом DECLINED + причиной — для аудита.
|
||||
*
|
||||
* @param coopname Кооператив
|
||||
* @param request_hash Хеш заявки
|
||||
* @param reason Причина отказа
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*/
|
||||
void capital::declinerole(name coopname, checksum256 request_hash, std::string reason) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto req = Capital::RoleRequests::get_role_request_or_fail(coopname, request_hash);
|
||||
Capital::RoleRequests::decline(coopname, req.id, reason);
|
||||
|
||||
// event ridge: заявитель и мастер компонента видят отказ.
|
||||
require_recipient(req.username);
|
||||
require_recipient(req.master);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @brief Кандидат отказывается от инвайта мастера.
|
||||
*
|
||||
* @param coopname Кооператив
|
||||
* @param request_hash Хеш инвайт-заявки
|
||||
* @param reason Причина отказа
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*/
|
||||
void capital::declinvite(name coopname, checksum256 request_hash, std::string reason) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto req = Capital::RoleRequests::get_role_request_or_fail(coopname, request_hash);
|
||||
eosio::check(req.direction == Capital::RoleRequests::Direction::INVITE,
|
||||
"Отказаться можно только от инвайта");
|
||||
|
||||
Capital::RoleRequests::decline(coopname, req.id, reason);
|
||||
|
||||
// event ridge: кандидат и мастер видят отказ от инвайта.
|
||||
require_recipient(req.username);
|
||||
require_recipient(req.master);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @brief Мастер компонента (или председатель — для роли master при пустом слоте мастера)
|
||||
* приглашает кандидата на роль. Обратный поток к requestrole.
|
||||
* Кандидат далее acceptinvite (с применением приглашённой ставки) либо declinvite.
|
||||
*
|
||||
* Бездокументарная схема: приглашение и принятие/отказ оформляются только
|
||||
* подписью транзакции (require_auth coopname). Юридический документ-инвайт не нужен.
|
||||
*
|
||||
* Ставка часа в приглашении — это ставка, на которую кандидат соглашается через
|
||||
* acceptinvite. Не путать с contributors.rate_per_hour (личная глобальная ставка).
|
||||
*
|
||||
* @param coopname Кооператив
|
||||
* @param request_hash Хеш заявки (анкер; уникален в scope coopname)
|
||||
* @param project_hash Проект / компонент
|
||||
* @param candidate Приглашаемый пайщик (потенциальный участник)
|
||||
* @param master Мастер-приглашающий (или координатор/председатель для роли master)
|
||||
* @param role Предлагаемая роль: Role::CREATOR | Role::AUTHOR | Role::MASTER
|
||||
* @param rate_per_hour Предлагаемая ставка часа
|
||||
* @param hours_per_day Предлагаемая норма часов в день
|
||||
* @param description Текст приглашения (может быть пустым)
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*/
|
||||
void capital::inviterole(name coopname, checksum256 request_hash, checksum256 project_hash,
|
||||
name candidate, name master, name role,
|
||||
eosio::asset rate_per_hour, uint64_t hours_per_day,
|
||||
std::string description) {
|
||||
require_auth(coopname);
|
||||
|
||||
Capital::RoleRequests::validate_role_or_fail(role);
|
||||
Wallet::validate_asset(rate_per_hour);
|
||||
eosio::check(rate_per_hour.amount > 0, "Ставка часа должна быть положительной");
|
||||
eosio::check(hours_per_day > 0 && hours_per_day <= 8, "Норма часов — от 1 до 8");
|
||||
|
||||
Capital::RoleRequests::create(
|
||||
coopname, request_hash, project_hash, candidate, master, role,
|
||||
rate_per_hour, hours_per_day,
|
||||
Capital::RoleRequests::Direction::INVITE,
|
||||
Capital::RoleRequests::RequestType::ROLE,
|
||||
description
|
||||
);
|
||||
|
||||
// event ridge: кандидат и мастер видят инвайт.
|
||||
require_recipient(candidate);
|
||||
require_recipient(master);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @brief Заявка пайщика на обновление approved-ставки на сегменте.
|
||||
* Тот же flow что и requestrole, но через RequestType::RATE_UPDATE.
|
||||
* approverole с новой ставкой → обновление сегмента; declinerole — без изменений.
|
||||
*
|
||||
* Бездокументарная схема: заявление-документ не передаётся, утверждение
|
||||
* фиксируется только транзакцией. role в записи остаётся Role::NONE
|
||||
* (валидация роли для RATE_UPDATE не применяется).
|
||||
*
|
||||
* @param coopname Кооператив
|
||||
* @param request_hash Хеш заявки
|
||||
* @param project_hash Проект / компонент
|
||||
* @param username Заявитель
|
||||
* @param master Мастер компонента (ожидаемый одобряющий)
|
||||
* @param new_rate Желаемая новая ставка
|
||||
* @param new_hours Желаемая новая норма часов
|
||||
* @param description Текст заявки на обновление ставки (может быть пустым)
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*/
|
||||
void capital::requestrateu(name coopname, checksum256 request_hash, checksum256 project_hash,
|
||||
name username, name master,
|
||||
eosio::asset new_rate, uint64_t new_hours,
|
||||
std::string description) {
|
||||
require_auth(coopname);
|
||||
|
||||
Wallet::validate_asset(new_rate);
|
||||
eosio::check(new_rate.amount > 0, "Новая ставка должна быть положительной");
|
||||
eosio::check(new_hours > 0 && new_hours <= 8, "Норма часов — от 1 до 8");
|
||||
|
||||
Capital::RoleRequests::create(
|
||||
coopname, request_hash, project_hash, username, master,
|
||||
Capital::RoleRequests::Role::NONE,
|
||||
new_rate, new_hours,
|
||||
Capital::RoleRequests::Direction::REQUEST,
|
||||
Capital::RoleRequests::RequestType::RATE_UPDATE,
|
||||
description
|
||||
);
|
||||
|
||||
// event ridge: заявитель и мастер компонента видят запрос на обновление ставки.
|
||||
require_recipient(username);
|
||||
require_recipient(master);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @brief Заявка пайщика на L2-допуск (роль creator/author/master) на компоненте.
|
||||
*
|
||||
* Создаёт запись в `rolerequests` со статусом PENDING. Дальше мастер компонента
|
||||
* (или председатель — для роли master, когда мастера в проекте ещё нет)
|
||||
* решает: approverole (с фиксацией approved-ставки в сегменте) или declinerole.
|
||||
*
|
||||
* Бездокументарная схема: подача и решение оформляются только подписью транзакции
|
||||
* (require_auth coopname). Юридическое заявление при приёме на роль не требуется —
|
||||
* допуск даётся подписью действия на стороне кооператива.
|
||||
*
|
||||
* Ставка часа подаётся явно — это ставка пайщика именно под этот проект.
|
||||
* По дефолту фронт подставляет contributors.rate_per_hour, но пайщик может изменить.
|
||||
*
|
||||
* @param coopname Кооператив
|
||||
* @param request_hash Хеш заявки (анкер для approverole/declinerole)
|
||||
* @param project_hash Проект (или компонент — на уровне хэша одно)
|
||||
* @param username Заявитель
|
||||
* @param master Мастер компонента (ожидаемый одобряющий)
|
||||
* @param role Запрашиваемая роль: Role::CREATOR | Role::AUTHOR | Role::MASTER
|
||||
* @param rate_per_hour Заявленная ставка часа под этот проект
|
||||
* @param hours_per_day Заявленная норма часов в день
|
||||
* @param description Текст заявки (может быть пустым)
|
||||
* @ingroup public_actions
|
||||
* @ingroup public_capital_actions
|
||||
*/
|
||||
void capital::requestrole(name coopname, checksum256 request_hash, checksum256 project_hash,
|
||||
name username, name master, name role,
|
||||
eosio::asset rate_per_hour, uint64_t hours_per_day,
|
||||
std::string description) {
|
||||
require_auth(coopname);
|
||||
|
||||
Capital::RoleRequests::validate_role_or_fail(role);
|
||||
Wallet::validate_asset(rate_per_hour);
|
||||
eosio::check(rate_per_hour.amount > 0, "Ставка часа должна быть положительной");
|
||||
eosio::check(hours_per_day > 0 && hours_per_day <= 8, "Норма часов в день — от 1 до 8");
|
||||
|
||||
Capital::RoleRequests::create(
|
||||
coopname, request_hash, project_hash, username, master, role,
|
||||
rate_per_hour, hours_per_day,
|
||||
Capital::RoleRequests::Direction::REQUEST,
|
||||
Capital::RoleRequests::RequestType::ROLE,
|
||||
description
|
||||
);
|
||||
|
||||
// event ridge: заявитель и мастер компонента видят новую заявку.
|
||||
require_recipient(username);
|
||||
require_recipient(master);
|
||||
}
|
||||
@@ -14,21 +14,20 @@
|
||||
*/
|
||||
void capital::startvoting(name coopname, checksum256 project_hash) {
|
||||
require_auth(coopname);
|
||||
print("z");
|
||||
|
||||
// Проверяем существование проекта и получаем его
|
||||
auto project = Capital::Projects::get_project_or_fail(coopname, project_hash);
|
||||
print("a");
|
||||
|
||||
// Проверяем что проект авторизован советом
|
||||
//eosio::check(project.is_authorized, "Проект не авторизован советом");
|
||||
|
||||
// Проверяем, что проект в статусе "active"
|
||||
eosio::check(project.status == Capital::Projects::Status::ACTIVE, "Проект должен быть в статусе 'active'");
|
||||
eosio::check(project.counts.total_commits > 0, "Проект без коммитов не может быть поставлен на голосование");
|
||||
print("b");
|
||||
|
||||
// Обновляем статус проекта на "voting"
|
||||
Capital::Projects::update_status(coopname, project.id, Capital::Projects::Status::VOTING);
|
||||
print("c");
|
||||
|
||||
// Инициализируем голосование по методу Водянова
|
||||
Capital::Core::Voting::initialize_project_voting(coopname, project.id);
|
||||
print("d");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,10 +61,41 @@ void capital::signact2(eosio::name coopname, eosio::name chairman, checksum256 r
|
||||
}
|
||||
|
||||
// Возврат беспроцентного займа пайщика: Dr 80 / Cr 58, TRANSFER LOAN_ISSUED → SHARE_FUND_PAY.
|
||||
// Семантика: пайщик погасил ссуду результатом (интеллектуальным взносом),
|
||||
// Семантика: пайщик погасил ссуду результатом (паевой взнос интеллектуальным результатом),
|
||||
// у кооперативa уменьшилось фин. вложение 58, деньги вернулись на расчётный.
|
||||
if (result -> debt_amount.amount > 0){
|
||||
Ledger2::apply(_capital, coopname, operations::capital::REPAY, result -> debt_amount, result -> username, result_hash, memo);
|
||||
|
||||
// Лимит «10 займов на проект в одном паевом взносе» проверяем по счётчику
|
||||
// active_debts_count на сегменте (binary_extension) — без обхода byprojhash.
|
||||
// Сам счётчик инкрементируется в debtpaycnfrm и декрементируется в mark_settled.
|
||||
uint32_t active_cnt = Capital::Segments::get_active_debts_count(coopname, result->project_hash, result->username);
|
||||
eosio::check(active_cnt <= 10,
|
||||
"Превышен лимит займов на проект для одного паевого взноса (10)");
|
||||
|
||||
// Перебираем долги пайщика по проекту, но не больше известного счётчика —
|
||||
// ранний выход сразу после нужного числа активных записей.
|
||||
Capital::Debts::debts_index debts(_capital, coopname.value);
|
||||
auto byproj = debts.get_index<"byprojhash"_n>();
|
||||
std::vector<std::tuple<uint64_t, checksum256, eosio::asset>> to_settle;
|
||||
to_settle.reserve(active_cnt);
|
||||
for (auto it = byproj.find(result->project_hash);
|
||||
it != byproj.end() && it->project_hash == result->project_hash
|
||||
&& to_settle.size() < active_cnt; ++it) {
|
||||
if (it->username == result->username
|
||||
&& (it->status == Capital::Debts::Status::PAID
|
||||
|| it->status == Capital::Debts::Status::OVERDUE)) {
|
||||
to_settle.emplace_back(it->id, it->debt_hash, it->amount);
|
||||
}
|
||||
}
|
||||
for (auto& tup : to_settle) {
|
||||
auto id = std::get<0>(tup);
|
||||
auto dh = std::get<1>(tup);
|
||||
auto amt = std::get<2>(tup);
|
||||
Loan::settle_debt(_capital, coopname, result->username, dh, amt);
|
||||
Capital::Debts::mark_settled(coopname, id, memo, _capital);
|
||||
Capital::Segments::decrease_active_debts_count(coopname, result->project_hash, result->username);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем накопительные показатели контрибьютора на основе его ролей в сегменте
|
||||
|
||||
@@ -92,9 +92,29 @@
|
||||
#include "app/debt_managment/create_debt/debtauthcnfr.cpp"
|
||||
#include "app/debt_managment/create_debt/debtpaycnfrm.cpp"
|
||||
#include "app/debt_managment/create_debt/debtpaydcln.cpp"
|
||||
#include "app/debt_managment/create_debt/debtpayretry.cpp"
|
||||
#include "app/debt_managment/create_debt/declinedebt.cpp"
|
||||
#include "app/debt_managment/create_debt/markdebtoverd.cpp"
|
||||
#include "app/debt_managment/create_debt/closedebt.cpp"
|
||||
#include "app/debt_managment/settle_debt/settledebt.cpp"
|
||||
|
||||
// Управление программными расходами
|
||||
#include "app/expense_managment/program_expenses/topupprogexp.cpp"
|
||||
#include "app/expense_managment/program_expenses/createpgexp.cpp"
|
||||
#include "app/expense_managment/program_expenses/apprvpgexp.cpp"
|
||||
#include "app/expense_managment/program_expenses/authpgexp.cpp"
|
||||
#include "app/expense_managment/program_expenses/pgexppay.cpp"
|
||||
#include "app/expense_managment/program_expenses/declpgexp.cpp"
|
||||
|
||||
// L2-роли (Story 1.7)
|
||||
#include "app/participation_management/roles/requestrole.cpp"
|
||||
#include "app/participation_management/roles/approverole.cpp"
|
||||
#include "app/participation_management/roles/declinerole.cpp"
|
||||
#include "app/participation_management/roles/inviterole.cpp"
|
||||
#include "app/participation_management/roles/acceptinvite.cpp"
|
||||
#include "app/participation_management/roles/declinvite.cpp"
|
||||
#include "app/participation_management/roles/requestrateu.cpp"
|
||||
|
||||
// Управление расходами
|
||||
#include "app/expense_managment/create_expense/approveexpns.cpp"
|
||||
#include "app/expense_managment/create_expense/capauthexpns.cpp"
|
||||
|
||||
@@ -315,11 +315,73 @@ public:
|
||||
|
||||
[[eosio::action]]
|
||||
void debtpaydcln(name coopname, checksum256 debt_hash, std::string reason);
|
||||
|
||||
|
||||
[[eosio::action]]
|
||||
void debtpayretry(eosio::name coopname, checksum256 debt_hash);
|
||||
|
||||
[[eosio::action]]
|
||||
void declinedebt(name coopname, eosio::name username, checksum256 debt_hash, std::string reason);
|
||||
|
||||
[[eosio::action]]
|
||||
void settledebt(name coopname, name username, eosio::asset amount, document2 statement);
|
||||
void settledebt(name coopname, checksum256 debt_hash, eosio::asset amount, document2 statement);
|
||||
|
||||
[[eosio::action]]
|
||||
void markdebtoverd(name coopname);
|
||||
|
||||
[[eosio::action]]
|
||||
void closedebt(name coopname, checksum256 debt_hash);
|
||||
|
||||
[[eosio::action]]
|
||||
void topupprogexp(name coopname, eosio::asset amount);
|
||||
|
||||
[[eosio::action]]
|
||||
void createpgexp(name coopname, checksum256 expense_hash, name creator,
|
||||
eosio::asset amount, std::string description, document2 statement);
|
||||
|
||||
[[eosio::action]]
|
||||
void apprvpgexp(name coopname, name approver, checksum256 expense_hash,
|
||||
document2 approved_statement);
|
||||
|
||||
[[eosio::action]]
|
||||
void authpgexp(name coopname, checksum256 expense_hash, document2 authorization);
|
||||
|
||||
[[eosio::action]]
|
||||
void pgexppay(name coopname, checksum256 expense_hash);
|
||||
|
||||
[[eosio::action]]
|
||||
void declpgexp(name coopname, checksum256 expense_hash, std::string reason);
|
||||
|
||||
// L2-роли и approved-ставки сегмента (Story 1.7) — бездокументарная схема,
|
||||
// решения фиксируются только подписью транзакции (require_auth coopname).
|
||||
[[eosio::action]]
|
||||
void requestrole(name coopname, checksum256 request_hash, checksum256 project_hash,
|
||||
name username, name master, name role,
|
||||
eosio::asset rate_per_hour, uint64_t hours_per_day,
|
||||
std::string description);
|
||||
|
||||
[[eosio::action]]
|
||||
void approverole(name coopname, checksum256 request_hash,
|
||||
eosio::asset approved_rate, uint64_t approved_hours);
|
||||
|
||||
[[eosio::action]]
|
||||
void declinerole(name coopname, checksum256 request_hash, std::string reason);
|
||||
|
||||
[[eosio::action]]
|
||||
void inviterole(name coopname, checksum256 request_hash, checksum256 project_hash,
|
||||
name candidate, name master, name role,
|
||||
eosio::asset rate_per_hour, uint64_t hours_per_day,
|
||||
std::string description);
|
||||
|
||||
[[eosio::action]]
|
||||
void acceptinvite(name coopname, checksum256 request_hash);
|
||||
|
||||
[[eosio::action]]
|
||||
void declinvite(name coopname, checksum256 request_hash, std::string reason);
|
||||
|
||||
[[eosio::action]]
|
||||
void requestrateu(name coopname, checksum256 request_hash, checksum256 project_hash,
|
||||
name username, name master,
|
||||
eosio::asset new_rate, uint64_t new_hours,
|
||||
std::string description);
|
||||
|
||||
};
|
||||
|
||||
@@ -15,10 +15,14 @@ namespace Capital::Debts {
|
||||
|
||||
*/
|
||||
namespace Status {
|
||||
constexpr name CREATED = "created"_n; ///< Долг создан
|
||||
constexpr name APPROVED = "approved"_n; ///< Долг одобрен
|
||||
constexpr name AUTHORIZED = "authorized"_n; ///< Долг авторизован советом
|
||||
constexpr name PAID = "paid"_n; ///< Долг выплачен (готов к использованию)
|
||||
constexpr name CREATED = "created"_n; ///< Долг создан
|
||||
constexpr name APPROVED = "approved"_n; ///< Долг одобрен председателем
|
||||
constexpr name AUTHORIZED = "authorized"_n; ///< Долг авторизован советом, ожидает повторной отправки на оплату
|
||||
constexpr name PAY_PENDING = "paypending"_n; ///< Долг отправлен в gateway на оплату (исходящий платёж создан, ждём debtpaycnfrm/debtpaydcln)
|
||||
constexpr name PAID = "paid"_n; ///< Долг выплачен пайщику, активный (ждёт погашения)
|
||||
constexpr name OVERDUE = "overdue"_n; ///< Долг просрочен (now > due_at) — выставляется action markdebtoverd
|
||||
constexpr name SETTLED = "settled"_n; ///< Долг полностью погашен (деньгами через settledebt либо результатом через signact2)
|
||||
constexpr name WRITEOFF = "writeoff"_n; ///< Долг списан (write-off) за счёт созданного НМА из коммитов-обеспечения (через closedebt при отмене родительского компонента; решение 2026-05-19)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,7 +46,9 @@ struct [[eosio::table, eosio::contract(CAPITAL)]] debt {
|
||||
document2 approved_statement; ///< Одобренное заявление
|
||||
document2 authorization; ///< Авторизация совета
|
||||
std::string memo; ///< Примечание
|
||||
|
||||
std::string last_pay_error; ///< Последняя ошибка outpay от gateway (заполняется debtpaydcln; пусто = успех или ещё не отправлен)
|
||||
time_point_sec due_at; ///< Срок погашения (current_time_point + 1 год при переходе в paid; решение 2026-05-19); now > due_at → overdue
|
||||
|
||||
uint64_t primary_key() const { return id; } ///< Первичный ключ (1)
|
||||
|
||||
uint64_t by_username() const { return username.value; } ///< Индекс по имени пользователя (2)
|
||||
@@ -145,6 +151,179 @@ inline void update_debt_status(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Переводит долг в PAY_PENDING — фиксирует решение совета и факт отправки в gateway.
|
||||
* Допустимо из APPROVED (первая отправка после авторизации совета) или из AUTHORIZED
|
||||
* (повторная отправка после предыдущего отказа gateway). Очищает last_pay_error.
|
||||
*/
|
||||
inline void start_pay(
|
||||
eosio::name coopname,
|
||||
uint64_t debt_id,
|
||||
const document2 &authorization,
|
||||
eosio::name payer = name{}
|
||||
) {
|
||||
if (payer == name{}) payer = coopname;
|
||||
|
||||
debts_index debts(_capital, coopname.value);
|
||||
auto debt = debts.find(debt_id);
|
||||
eosio::check(debt != debts.end(), "Долг не найден");
|
||||
eosio::check(debt->status == Status::APPROVED || debt->status == Status::AUTHORIZED,
|
||||
"Старт оплаты допустим только из approved/authorized");
|
||||
|
||||
debts.modify(debt, payer, [&](auto &d) {
|
||||
d.status = Status::PAY_PENDING;
|
||||
d.authorization = authorization;
|
||||
d.last_pay_error = "";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Возвращает долг из PAY_PENDING в AUTHORIZED с записью причины ошибки оплаты.
|
||||
* Долг не удаляется — решение совета уже принято, доступна повторная отправка в gateway.
|
||||
*/
|
||||
inline void mark_pay_declined(
|
||||
eosio::name coopname,
|
||||
uint64_t debt_id,
|
||||
const std::string &reason,
|
||||
eosio::name payer = name{}
|
||||
) {
|
||||
if (payer == name{}) payer = coopname;
|
||||
|
||||
debts_index debts(_capital, coopname.value);
|
||||
auto debt = debts.find(debt_id);
|
||||
eosio::check(debt != debts.end(), "Долг не найден");
|
||||
eosio::check(debt->status == Status::PAY_PENDING,
|
||||
"Отказ оплаты допустим только из статуса pay_pending");
|
||||
|
||||
debts.modify(debt, payer, [&](auto &d) {
|
||||
d.status = Status::AUTHORIZED;
|
||||
d.last_pay_error = reason;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Кол-во секунд в стандартном сроке погашения займа (1 год = 365 дней).
|
||||
*
|
||||
* Решение 2026-05-19 (Игорь Смуров / Алексей Муравьёв): срок займа
|
||||
* фиксированный — год, не привязан к календарному сроку проекта. Если
|
||||
* родительский компонент явно перешёл в `cancelled` раньше — долг закрывается
|
||||
* досрочно через создание НМА кооператива из коммитов-обеспечения и списание
|
||||
* долга (см. action `capital::closedebt`, операции o.cap.crtnma + o.cap.dbtwrf).
|
||||
*/
|
||||
constexpr uint32_t DEFAULT_DEBT_TERM_SECONDS = 365 * 24 * 60 * 60;
|
||||
|
||||
/**
|
||||
* @brief Переводит долг в PAID, фиксирует дату выплаты и проставляет due_at.
|
||||
*
|
||||
* Используется в debtpaycnfrm после успешной outpay из gateway. due_at рассчитывается
|
||||
* от current_time_point на год вперёд; долги с now > due_at и status == PAID
|
||||
* далее перейдут в OVERDUE через markdebtoverd.
|
||||
*/
|
||||
inline void confirm_paid(
|
||||
eosio::name coopname,
|
||||
uint64_t debt_id,
|
||||
eosio::name payer = name{}
|
||||
) {
|
||||
if (payer == name{}) payer = coopname;
|
||||
|
||||
debts_index debts(_capital, coopname.value);
|
||||
auto debt = debts.find(debt_id);
|
||||
eosio::check(debt != debts.end(), "Долг не найден");
|
||||
eosio::check(debt->status == Status::PAY_PENDING,
|
||||
"Подтверждение оплаты допустимо только из pay_pending");
|
||||
|
||||
auto now = eosio::current_time_point();
|
||||
debts.modify(debt, payer, [&](auto &d) {
|
||||
d.status = Status::PAID;
|
||||
d.due_at = eosio::time_point_sec(now.sec_since_epoch() + DEFAULT_DEBT_TERM_SECONDS);
|
||||
d.last_pay_error = "";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Помечает долги PAID с now > due_at как OVERDUE в пределах одного batch.
|
||||
*
|
||||
* @param coopname Кооператив (scope)
|
||||
* @param batch_limit Максимальное число записей за вызов (защита от 30-сек лимита транзакции)
|
||||
* @return Сколько долгов помечено за этот вызов; backend-cron повторяет до 0.
|
||||
*/
|
||||
inline uint32_t sweep_overdue(
|
||||
eosio::name coopname,
|
||||
uint32_t batch_limit
|
||||
) {
|
||||
debts_index debts(_capital, coopname.value);
|
||||
auto now = eosio::current_time_point();
|
||||
auto now_sec = eosio::time_point_sec(now.sec_since_epoch());
|
||||
|
||||
uint32_t marked = 0;
|
||||
for (auto it = debts.begin(); it != debts.end() && marked < batch_limit; ++it) {
|
||||
if (it->status == Status::PAID && it->due_at != time_point_sec() && now_sec > it->due_at) {
|
||||
debts.modify(it, coopname, [&](auto &d) {
|
||||
d.status = Status::OVERDUE;
|
||||
});
|
||||
++marked;
|
||||
}
|
||||
}
|
||||
return marked;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Закрывает долг как погашенный — переводит в SETTLED, сохраняет документ-основание
|
||||
* в поле memo (если передан) и фиксирует дату погашения.
|
||||
*
|
||||
* Деньги и проводка REPAY к этому моменту уже должны быть применены в Ledger2,
|
||||
* а contributors.debt_amount — уменьшен. Функция отвечает только за состояние записи.
|
||||
*/
|
||||
inline void mark_settled(
|
||||
eosio::name coopname,
|
||||
uint64_t debt_id,
|
||||
const std::string &settle_memo = "",
|
||||
eosio::name payer = name{}
|
||||
) {
|
||||
if (payer == name{}) payer = coopname;
|
||||
|
||||
debts_index debts(_capital, coopname.value);
|
||||
auto debt = debts.find(debt_id);
|
||||
eosio::check(debt != debts.end(), "Долг не найден");
|
||||
eosio::check(debt->status == Status::PAID || debt->status == Status::OVERDUE,
|
||||
"Погашение допустимо только из статуса paid или overdue");
|
||||
|
||||
debts.modify(debt, payer, [&](auto &d) {
|
||||
d.status = Status::SETTLED;
|
||||
d.repaid_at = eosio::current_time_point();
|
||||
if (!settle_memo.empty()) {
|
||||
d.memo = settle_memo;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Закрывает долг как списанный — переводит в WRITEOFF.
|
||||
*
|
||||
* Применяется в closedebt после применения o.cap.crtnma + o.cap.dbtwrf и
|
||||
* декремента сегмента. Семантически зеркалит mark_settled, но не пишет
|
||||
* memo о погашении: долг закрыт имущественно через созданное НМА из
|
||||
* коммитов-обеспечения, без зачисления на share пайщика. Решение 2026-05-19.
|
||||
*/
|
||||
inline void mark_writeoff(
|
||||
eosio::name coopname,
|
||||
uint64_t debt_id,
|
||||
eosio::name payer = name{}
|
||||
) {
|
||||
if (payer == name{}) payer = coopname;
|
||||
|
||||
debts_index debts(_capital, coopname.value);
|
||||
auto debt = debts.find(debt_id);
|
||||
eosio::check(debt != debts.end(), "Долг не найден");
|
||||
eosio::check(debt->status == Status::PAID || debt->status == Status::OVERDUE,
|
||||
"Списание долга допустимо только из статуса paid или overdue");
|
||||
|
||||
debts.modify(debt, payer, [&](auto &d) {
|
||||
d.status = Status::WRITEOFF;
|
||||
d.repaid_at = eosio::current_time_point();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Удаляет долг
|
||||
*/
|
||||
|
||||
@@ -178,8 +178,116 @@ namespace Capital::Expenses {
|
||||
Capital::expense_index expenses(_capital, coopname.value);
|
||||
auto expense = expenses.find(expense_id);
|
||||
eosio::check(expense != expenses.end(), "Расход не найден");
|
||||
|
||||
|
||||
expenses.erase(expense);
|
||||
}
|
||||
|
||||
} // namespace Capital::Expenses
|
||||
|
||||
namespace Capital {
|
||||
|
||||
/**
|
||||
* @brief Таблица программных расходов — целевые списания «Благорост»,
|
||||
* не привязанные к конкретному проекту.
|
||||
* @par Область памяти (scope): coopname
|
||||
* @par Имя таблицы (table): progexpenses
|
||||
*/
|
||||
struct [[eosio::table, eosio::contract(CAPITAL)]] program_expense {
|
||||
uint64_t id; ///< Внутренний ключ
|
||||
name coopname; ///< Имя кооператива
|
||||
name username; ///< Инициатор расхода (председатель / казначей)
|
||||
|
||||
name status = Expenses::Status::CREATED; ///< Статус (CREATED/APPROVED/AUTHORIZED/PAID/DECLINED — общий enum)
|
||||
checksum256 expense_hash; ///< Хеш расхода — анкер процесса p.cap.expns
|
||||
uint64_t fund_id; ///< Идентификатор фонда списания
|
||||
eosio::asset amount; ///< Сумма расхода
|
||||
std::string description; ///< Описание (для отчётности)
|
||||
|
||||
document2 expense_statement; ///< Служебная записка (ProgramExpenseStatement)
|
||||
document2 approved_statement; ///< Записка, принятая председателем
|
||||
document2 authorization; ///< Решение совета
|
||||
|
||||
time_point_sec spended_at = current_time_point();
|
||||
|
||||
uint64_t primary_key() const { return id; }
|
||||
uint64_t by_username() const { return username.value; }
|
||||
checksum256 by_expense_hash() const { return expense_hash; }
|
||||
};
|
||||
|
||||
typedef eosio::multi_index<
|
||||
"progexpenses"_n, program_expense,
|
||||
indexed_by<"byusername"_n, const_mem_fun<program_expense, uint64_t, &program_expense::by_username>>,
|
||||
indexed_by<"byhash"_n, const_mem_fun<program_expense, checksum256, &program_expense::by_expense_hash>>
|
||||
> program_expense_index;
|
||||
|
||||
inline std::optional<program_expense> get_program_expense(eosio::name coopname, const checksum256 &hash) {
|
||||
program_expense_index t(_capital, coopname.value);
|
||||
auto idx = t.get_index<"byhash"_n>();
|
||||
auto itr = idx.find(hash);
|
||||
if (itr == idx.end()) return std::nullopt;
|
||||
return program_expense(*itr);
|
||||
}
|
||||
|
||||
} // namespace Capital
|
||||
|
||||
namespace Capital::Expenses {
|
||||
|
||||
inline program_expense get_program_expense_or_fail(eosio::name coopname, const checksum256 &expense_hash) {
|
||||
auto v = Capital::get_program_expense(coopname, expense_hash);
|
||||
eosio::check(v.has_value(), "Программный расход с указанным хэшем не найден");
|
||||
return v.value();
|
||||
}
|
||||
|
||||
inline void create_program_expense(
|
||||
eosio::name coopname,
|
||||
const checksum256 &expense_hash,
|
||||
eosio::name username,
|
||||
const eosio::asset &amount,
|
||||
const std::string &description,
|
||||
const document2 &statement
|
||||
) {
|
||||
Capital::program_expense_index t(_capital, coopname.value);
|
||||
uint64_t id = get_global_id_in_scope(_capital, coopname, "progexpenses"_n);
|
||||
|
||||
t.emplace(coopname, [&](auto &e) {
|
||||
e.id = id;
|
||||
e.coopname = coopname;
|
||||
e.username = username;
|
||||
e.expense_hash = expense_hash;
|
||||
e.fund_id = Ledger::accounts::GENERAL_EXPENSES;
|
||||
e.status = Status::CREATED;
|
||||
e.spended_at = current_time_point();
|
||||
e.expense_statement = statement;
|
||||
e.amount = amount;
|
||||
e.description = description;
|
||||
});
|
||||
}
|
||||
|
||||
inline void set_program_approved(eosio::name coopname, uint64_t id, const document2 &approved_statement) {
|
||||
Capital::program_expense_index t(_capital, coopname.value);
|
||||
auto itr = t.find(id);
|
||||
eosio::check(itr != t.end(), "Программный расход не найден");
|
||||
t.modify(itr, coopname, [&](auto &e) {
|
||||
e.status = Status::APPROVED;
|
||||
e.approved_statement = approved_statement;
|
||||
});
|
||||
}
|
||||
|
||||
inline void set_program_authorized(eosio::name coopname, uint64_t id, const document2 &authorization) {
|
||||
Capital::program_expense_index t(_capital, coopname.value);
|
||||
auto itr = t.find(id);
|
||||
eosio::check(itr != t.end(), "Программный расход не найден");
|
||||
t.modify(itr, coopname, [&](auto &e) {
|
||||
e.status = Status::AUTHORIZED;
|
||||
e.authorization = authorization;
|
||||
});
|
||||
}
|
||||
|
||||
inline void delete_program_expense(eosio::name coopname, uint64_t id) {
|
||||
Capital::program_expense_index t(_capital, coopname.value);
|
||||
auto itr = t.find(id);
|
||||
eosio::check(itr != t.end(), "Программный расход не найден");
|
||||
t.erase(itr);
|
||||
}
|
||||
|
||||
} // namespace Capital::Expenses
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <eosio/binary_extension.hpp>
|
||||
|
||||
using namespace eosio;
|
||||
using std::string;
|
||||
|
||||
@@ -40,7 +42,11 @@ namespace Capital {
|
||||
double program_membership_cumulative_reward_per_share; ///< Накопительное вознаграждение на долю в членских взносах
|
||||
|
||||
config config; ///< Управляемая конфигурация контракта
|
||||
|
||||
|
||||
// binary_extension: таблица state уже в продакшене, новые поля только хвостом.
|
||||
eosio::binary_extension<asset> program_expense_pool; ///< Доступная сумма программы под целевые расходы (без аллокации в проекты)
|
||||
eosio::binary_extension<asset> program_expense_reserved; ///< Сумма, зарезервированная под approved/authorized программные расходы
|
||||
|
||||
uint64_t primary_key() const { return coopname.value; } ///< Первичный ключ (1)
|
||||
};
|
||||
|
||||
@@ -78,7 +84,7 @@ inline global_state get_global_state(name coopname) {
|
||||
|
||||
/**
|
||||
* @brief Проверяет что контракт инициализирован.
|
||||
*
|
||||
*
|
||||
* @param coopname Имя кооператива.
|
||||
*/
|
||||
inline void is_initialized(name coopname) {
|
||||
@@ -86,6 +92,83 @@ inline void is_initialized(name coopname) {
|
||||
auto itr = global_state_inst.find(coopname.value);
|
||||
eosio::check(itr != global_state_inst.end(), "Контракт не инициализирован");
|
||||
}
|
||||
|
||||
/// Lazy-default для binary_extension<asset>: пустое значение = 0 в _root_govern_symbol.
|
||||
inline asset _read_pool(const eosio::binary_extension<asset> &field) {
|
||||
return field.has_value() ? *field : asset(0, _root_govern_symbol);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Пополняет пул программных расходов на @p amount из общего пула инвестиций программы.
|
||||
* Вызывается из action topupprogexp (председатель).
|
||||
*/
|
||||
inline void topup_program_expense_pool(eosio::name coopname, const asset &amount) {
|
||||
global_state_table global_state_inst(_capital, _capital.value);
|
||||
auto itr = global_state_inst.find(coopname.value);
|
||||
eosio::check(itr != global_state_inst.end(), "Контракт не инициализирован");
|
||||
eosio::check(amount.is_valid() && amount.amount > 0, "Сумма пополнения должна быть положительной");
|
||||
eosio::check(itr->global_available_invest_pool >= amount,
|
||||
"Недостаточно свободных инвестиций программы для пополнения пула расходов");
|
||||
|
||||
global_state_inst.modify(itr, _capital, [&](auto &s) {
|
||||
s.global_available_invest_pool -= amount;
|
||||
s.program_expense_pool.emplace(_read_pool(s.program_expense_pool) + amount);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Резервирует средства под approved/authorized программный расход:
|
||||
* program_expense_pool -= amount, program_expense_reserved += amount.
|
||||
*/
|
||||
inline void reserve_program_expense(eosio::name coopname, const asset &amount) {
|
||||
global_state_table global_state_inst(_capital, _capital.value);
|
||||
auto itr = global_state_inst.find(coopname.value);
|
||||
eosio::check(itr != global_state_inst.end(), "Контракт не инициализирован");
|
||||
eosio::check(amount.is_valid() && amount.amount > 0, "Сумма резерва должна быть положительной");
|
||||
eosio::check(_read_pool(itr->program_expense_pool) >= amount,
|
||||
"Недостаточно средств в пуле программных расходов");
|
||||
|
||||
global_state_inst.modify(itr, _capital, [&](auto &s) {
|
||||
s.program_expense_pool.emplace(_read_pool(s.program_expense_pool) - amount);
|
||||
s.program_expense_reserved.emplace(_read_pool(s.program_expense_reserved) + amount);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Освобождает зарезервированное при отклонении программного расхода:
|
||||
* program_expense_reserved -= amount, program_expense_pool += amount.
|
||||
*/
|
||||
inline void release_program_expense(eosio::name coopname, const asset &amount) {
|
||||
global_state_table global_state_inst(_capital, _capital.value);
|
||||
auto itr = global_state_inst.find(coopname.value);
|
||||
eosio::check(itr != global_state_inst.end(), "Контракт не инициализирован");
|
||||
eosio::check(amount.is_valid() && amount.amount > 0, "Сумма освобождения должна быть положительной");
|
||||
eosio::check(_read_pool(itr->program_expense_reserved) >= amount,
|
||||
"Зарезервированных программных расходов меньше указанной суммы");
|
||||
|
||||
global_state_inst.modify(itr, _capital, [&](auto &s) {
|
||||
s.program_expense_reserved.emplace(_read_pool(s.program_expense_reserved) - amount);
|
||||
s.program_expense_pool.emplace(_read_pool(s.program_expense_pool) + amount);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Окончательное списание программного расхода после exppaycnfrm:
|
||||
* program_expense_reserved -= amount. Деньги уже ушли gateway,
|
||||
* ledger2-проводка PAY_EXPENSE сделана в action подтверждения.
|
||||
*/
|
||||
inline void consume_program_expense(eosio::name coopname, const asset &amount) {
|
||||
global_state_table global_state_inst(_capital, _capital.value);
|
||||
auto itr = global_state_inst.find(coopname.value);
|
||||
eosio::check(itr != global_state_inst.end(), "Контракт не инициализирован");
|
||||
eosio::check(amount.is_valid() && amount.amount > 0, "Сумма списания должна быть положительной");
|
||||
eosio::check(_read_pool(itr->program_expense_reserved) >= amount,
|
||||
"Зарезервированных программных расходов меньше указанной суммы");
|
||||
|
||||
global_state_inst.modify(itr, _capital, [&](auto &s) {
|
||||
s.program_expense_reserved.emplace(_read_pool(s.program_expense_reserved) - amount);
|
||||
});
|
||||
}
|
||||
}// namespace State
|
||||
|
||||
}// namespace Capital
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "projects.hpp"
|
||||
#include "results.hpp"
|
||||
#include "debts.hpp"
|
||||
#include "role_requests.hpp"
|
||||
#include "wallets.hpp"
|
||||
#include "plan_pool.hpp"
|
||||
#include "fact_pool.hpp"
|
||||
|
||||
@@ -87,4 +87,12 @@ namespace Capital::Memo {
|
||||
return "Внесение имущества в программу Благорост: " + checksum256_to_hex(property_hash);
|
||||
}
|
||||
|
||||
inline std::string get_settle_debt_memo(eosio::name username, checksum256 debt_hash) {
|
||||
return "Погашение займа участником " + username.to_string() + " (заём " + checksum256_to_hex(debt_hash) + ")";
|
||||
}
|
||||
|
||||
inline std::string get_expense_pay_memo(checksum256 expense_hash) {
|
||||
return "Оплата целевого расхода программы «Благорост»: " + checksum256_to_hex(expense_hash);
|
||||
}
|
||||
|
||||
} // namespace Capital::Memo
|
||||
@@ -4,17 +4,23 @@ using namespace eosio;
|
||||
using std::string;
|
||||
|
||||
namespace Capital {
|
||||
//TODO: УДАЛИТЬ после 07.2026 т.к. не используем
|
||||
/**
|
||||
* @brief Таблица программных инвестиций (progrinvests): ранее использовалась для заявок на зачёт с этапом одобрения советом; при текущей логике createpinv строки не создаёт, таблица сохранена для совместимости и чтения исторических записей.
|
||||
* @deprecated description: не используется
|
||||
* @brief Таблица программных инвестиций (progrinvests): ранее использовалась
|
||||
* для заявок на зачёт с этапом одобрения советом. При текущей логике
|
||||
* @ref capital::createpinv строки не создаёт, таблица сохранена для
|
||||
* совместимости и чтения исторических записей.
|
||||
*
|
||||
* @deprecated Запланировано к удалению после 2026-07. Не использовать в новом
|
||||
* коде. Чтение через scope coopname допустимо до этой даты для
|
||||
* ретроспективных отчётов; писать через emplace/modify нельзя.
|
||||
*
|
||||
* @ingroup public_tables
|
||||
* @ingroup public_capital_tables
|
||||
|
||||
*
|
||||
* @par Область памяти (scope): coopname
|
||||
* @par Имя таблицы (table): progrinvests
|
||||
* @par Имя таблицы (table): progrinvests
|
||||
*/
|
||||
struct [[eosio::table, eosio::contract(CAPITAL)]] program_invest {
|
||||
struct [[deprecated("remove after 2026-07"), eosio::table, eosio::contract(CAPITAL)]] program_invest {
|
||||
uint64_t id; ///< ID программной инвестиции (внутренний ключ)
|
||||
eosio::name coopname; ///< Имя кооператива
|
||||
eosio::name username; ///< Имя инвестора
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
#pragma once
|
||||
|
||||
#include <eosio/eosio.hpp>
|
||||
#include <eosio/asset.hpp>
|
||||
|
||||
using namespace eosio;
|
||||
using std::string;
|
||||
|
||||
namespace Capital::RoleRequests {
|
||||
|
||||
/**
|
||||
* @brief Статусы заявки на роль / обновление approved-ставки.
|
||||
*/
|
||||
namespace Status {
|
||||
constexpr name PENDING = "pending"_n;
|
||||
constexpr name APPROVED = "approved"_n;
|
||||
constexpr name DECLINED = "declined"_n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Направление: пайщик просит сам (REQUEST) или мастер инвайтит кандидата (INVITE).
|
||||
*/
|
||||
namespace Direction {
|
||||
constexpr name REQUEST = "request"_n;
|
||||
constexpr name INVITE = "invite"_n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Тип заявки: «дай мне роль/допуск L2» или «обнови мне approved-ставку».
|
||||
*/
|
||||
namespace RequestType {
|
||||
constexpr name ROLE = "role"_n;
|
||||
constexpr name RATE_UPDATE = "rateupdate"_n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Допустимые роли заявки на L2-допуск.
|
||||
* Контрибьюторы и инвесторы — L1, через role_requests не идут.
|
||||
*/
|
||||
namespace Role {
|
||||
constexpr name CREATOR = "creator"_n;
|
||||
constexpr name AUTHOR = "author"_n;
|
||||
constexpr name MASTER = "master"_n;
|
||||
constexpr name NONE = name{}; ///< Для RATE_UPDATE: поле role не значимо
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Очередь заявок на L2-допуск/инвайт и на обновление approved-ставки.
|
||||
*
|
||||
* Бездокументарная схема: подача и решение оформляются только транзакцией
|
||||
* блокчейна (require_auth coopname). Юридическое заявление при приёме на роль
|
||||
* не требуется — допуск даётся через подпись действия на стороне кооператива.
|
||||
*
|
||||
* @par Область памяти (scope): coopname
|
||||
* @par Имя таблицы (table): rolerequests
|
||||
*/
|
||||
struct [[eosio::table, eosio::contract(CAPITAL)]] role_request {
|
||||
uint64_t id; ///< Внутренний ключ
|
||||
eosio::name coopname; ///< Кооператив
|
||||
checksum256 request_hash; ///< Хеш заявки (анкер)
|
||||
checksum256 project_hash; ///< Проект / компонент
|
||||
eosio::name username; ///< Заявитель (REQUEST) или кандидат (INVITE)
|
||||
eosio::name master; ///< Мастер компонента (одобряющий) — при invite на роль master это координатор/председатель
|
||||
eosio::name role = Role::NONE; ///< Role::CREATOR | Role::AUTHOR | Role::MASTER (для RequestType::RATE_UPDATE — Role::NONE)
|
||||
eosio::asset rate_per_hour = asset(0, _root_govern_symbol); ///< Заявленная пайщиком/мастером ставка часа (всегда явная, не из contributors)
|
||||
uint64_t hours_per_day = 0; ///< Заявленная норма часов
|
||||
eosio::asset approved_rate = asset(0, _root_govern_symbol); ///< Утверждённая ставка (≠ requested при approve)
|
||||
uint64_t approved_hours = 0; ///< Утверждённая норма часов
|
||||
eosio::name direction = Direction::REQUEST; ///< request | invite
|
||||
eosio::name request_type = RequestType::ROLE; ///< role | rateupdate
|
||||
eosio::name status = Status::PENDING; ///< pending | approved | declined
|
||||
std::string description; ///< Текст заявки / приглашения (может быть пустым)
|
||||
std::string decline_reason; ///< Причина отказа (если DECLINED)
|
||||
time_point_sec created_at = current_time_point();
|
||||
|
||||
uint64_t primary_key() const { return id; }
|
||||
checksum256 by_request_hash() const { return request_hash; }
|
||||
uint128_t by_project_user() const {
|
||||
return combine_checksum_ids(project_hash, username);
|
||||
}
|
||||
};
|
||||
|
||||
typedef eosio::multi_index<
|
||||
"rolerequests"_n, role_request,
|
||||
indexed_by<"byhash"_n, const_mem_fun<role_request, checksum256, &role_request::by_request_hash>>,
|
||||
indexed_by<"byprojuser"_n, const_mem_fun<role_request, uint128_t, &role_request::by_project_user>>
|
||||
> role_requests_index;
|
||||
|
||||
inline std::optional<role_request> get_role_request(eosio::name coopname, const checksum256 &request_hash) {
|
||||
role_requests_index t(_capital, coopname.value);
|
||||
auto idx = t.get_index<"byhash"_n>();
|
||||
auto itr = idx.find(request_hash);
|
||||
if (itr == idx.end()) return std::nullopt;
|
||||
return role_request(*itr);
|
||||
}
|
||||
|
||||
inline role_request get_role_request_or_fail(eosio::name coopname, const checksum256 &request_hash) {
|
||||
auto v = get_role_request(coopname, request_hash);
|
||||
eosio::check(v.has_value(), "Заявка на роль не найдена");
|
||||
return v.value();
|
||||
}
|
||||
|
||||
inline void create(
|
||||
eosio::name coopname,
|
||||
const checksum256 &request_hash,
|
||||
const checksum256 &project_hash,
|
||||
eosio::name username,
|
||||
eosio::name master,
|
||||
eosio::name role,
|
||||
const eosio::asset &rate_per_hour,
|
||||
uint64_t hours_per_day,
|
||||
eosio::name direction,
|
||||
eosio::name request_type,
|
||||
const std::string &description
|
||||
) {
|
||||
role_requests_index t(_capital, coopname.value);
|
||||
eosio::check(!get_role_request(coopname, request_hash).has_value(),
|
||||
"Заявка с указанным хэшем уже существует");
|
||||
|
||||
uint64_t id = get_global_id_in_scope(_capital, coopname, "rolerequests"_n);
|
||||
t.emplace(coopname, [&](auto &r) {
|
||||
r.id = id;
|
||||
r.coopname = coopname;
|
||||
r.request_hash = request_hash;
|
||||
r.project_hash = project_hash;
|
||||
r.username = username;
|
||||
r.master = master;
|
||||
r.role = role;
|
||||
r.rate_per_hour = rate_per_hour;
|
||||
r.hours_per_day = hours_per_day;
|
||||
r.direction = direction;
|
||||
r.request_type = request_type;
|
||||
r.status = Status::PENDING;
|
||||
r.description = description;
|
||||
r.created_at = current_time_point();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Проверяет, что role входит в допустимый набор Role::{CREATOR, AUTHOR, MASTER}.
|
||||
* Контрибьюторы и инвесторы — это L1-роли и через role_requests не идут.
|
||||
* Не применяется к заявкам RequestType::RATE_UPDATE (там role = Role::NONE).
|
||||
*/
|
||||
inline void validate_role_or_fail(eosio::name role) {
|
||||
eosio::check(
|
||||
role == Role::CREATOR || role == Role::AUTHOR || role == Role::MASTER,
|
||||
"Допустимые роли заявки: creator, author, master"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Применяет заявленную роль к проекту через inline action:
|
||||
* Role::AUTHOR → addauthor, Role::MASTER → setmaster, Role::CREATOR → no-op
|
||||
* (creator фиксируется фактически при первом createcmmt).
|
||||
*
|
||||
* Вызывается из approverole/acceptinvite после фиксации решения, чтобы
|
||||
* заявленная при подаче роль немедленно отражалась в реестре участников.
|
||||
* Inline action идёт под permission coopname — председатель/backend подписывает.
|
||||
*/
|
||||
inline void apply_role_to_project(
|
||||
eosio::name coopname,
|
||||
const checksum256 &project_hash,
|
||||
eosio::name username,
|
||||
eosio::name role
|
||||
) {
|
||||
if (role == Role::AUTHOR) {
|
||||
eosio::action(
|
||||
eosio::permission_level{coopname, "active"_n},
|
||||
_capital,
|
||||
"addauthor"_n,
|
||||
std::make_tuple(coopname, project_hash, username)
|
||||
).send();
|
||||
} else if (role == Role::MASTER) {
|
||||
eosio::action(
|
||||
eosio::permission_level{coopname, "active"_n},
|
||||
_capital,
|
||||
"setmaster"_n,
|
||||
std::make_tuple(coopname, project_hash, username)
|
||||
).send();
|
||||
}
|
||||
}
|
||||
|
||||
inline void approve(
|
||||
eosio::name coopname,
|
||||
uint64_t request_id,
|
||||
const eosio::asset &approved_rate,
|
||||
uint64_t approved_hours
|
||||
) {
|
||||
role_requests_index t(_capital, coopname.value);
|
||||
auto itr = t.find(request_id);
|
||||
eosio::check(itr != t.end(), "Заявка не найдена");
|
||||
eosio::check(itr->status == Status::PENDING, "Заявка уже обработана");
|
||||
t.modify(itr, coopname, [&](auto &r) {
|
||||
r.status = Status::APPROVED;
|
||||
r.approved_rate = approved_rate;
|
||||
r.approved_hours = approved_hours;
|
||||
});
|
||||
}
|
||||
|
||||
inline void decline(eosio::name coopname, uint64_t request_id, const std::string &reason) {
|
||||
role_requests_index t(_capital, coopname.value);
|
||||
auto itr = t.find(request_id);
|
||||
eosio::check(itr != t.end(), "Заявка не найдена");
|
||||
eosio::check(itr->status == Status::PENDING, "Заявка уже обработана");
|
||||
t.modify(itr, coopname, [&](auto &r) {
|
||||
r.status = Status::DECLINED;
|
||||
r.decline_reason = reason;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Capital::RoleRequests
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <eosio/eosio.hpp>
|
||||
#include <eosio/asset.hpp>
|
||||
#include <eosio/binary_extension.hpp>
|
||||
|
||||
using namespace eosio;
|
||||
|
||||
@@ -112,7 +113,19 @@ namespace Capital::Segments {
|
||||
eosio::asset available_for_program = asset(0, _root_govern_symbol); ///< Доступная сумма для конвертации в программу (intellectual_cost - debt_amount)
|
||||
eosio::asset available_for_wallet = asset(0, _root_govern_symbol); ///< Доступная сумма для конвертации в кошелек (provisional_amount - debt_amount)
|
||||
double share_percent = 0.0; ///< Доля участника в результате (intellectual_cost / fact.total * 100)
|
||||
|
||||
|
||||
// Допуски L2 — утверждаются мастером компонента/сегмента через approverole (Story 1.7).
|
||||
// Используются в createcmmt вместо глобального contributors.rate_per_hour, если значение установлено.
|
||||
// binary_extension: таблица segments уже в продакшене, расширение схемы только хвостом.
|
||||
eosio::binary_extension<eosio::asset> approved_rate_per_hour; ///< Утверждённая мастером ставка часа на этом сегменте
|
||||
eosio::binary_extension<uint64_t> approved_hours_per_day; ///< Утверждённое мастером количество часов в день на этом сегменте
|
||||
|
||||
// Счётчик активных (PAID/OVERDUE) займов пайщика на проекте этого сегмента.
|
||||
// Инкрементируется в Capital::Debts::confirm_paid, декрементируется в mark_settled.
|
||||
// Используется как лимит «10 займов на проект в одном паевом взносе» (signact2),
|
||||
// чтобы не перебирать byprojhash и не упираться в 30-сек лимит транзакции.
|
||||
eosio::binary_extension<uint32_t> active_debts_count;
|
||||
|
||||
uint64_t primary_key() const { return id; } ///< Первичный ключ (1)
|
||||
|
||||
checksum256 by_project_hash() const { return project_hash; } ///< Индекс по хэшу проекта (2)
|
||||
@@ -156,6 +169,30 @@ namespace Capital::Segments {
|
||||
return segment(*segment_itr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Фиксирует утверждённую мастером ставку часа и норматив часов в день на сегменте.
|
||||
* Вызывается из action approverole (Story 1.7). createcmmt далее берёт эту
|
||||
* ставку для расчёта generation_amounts вместо глобальной из contributors.
|
||||
*/
|
||||
inline void set_approved_rate(
|
||||
eosio::name coopname,
|
||||
const checksum256 &project_hash,
|
||||
eosio::name username,
|
||||
const eosio::asset &rate_per_hour,
|
||||
uint64_t hours_per_day
|
||||
) {
|
||||
segments_index segments(_capital, coopname.value);
|
||||
auto idx = segments.get_index<"byprojuser"_n>();
|
||||
auto rkey = combine_checksum_ids(project_hash, username);
|
||||
auto it = idx.find(rkey);
|
||||
eosio::check(it != idx.end(), "Сегмент участника на этом проекте не найден");
|
||||
|
||||
idx.modify(it, coopname, [&](auto &s) {
|
||||
s.approved_rate_per_hour.emplace(rate_per_hour);
|
||||
s.approved_hours_per_day.emplace(hours_per_day);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Подсчитывает количество авторов в проекте.
|
||||
@@ -418,14 +455,57 @@ inline void increase_debt_amount(eosio::name coopname, uint64_t segment_id, eosi
|
||||
inline void decrease_debt_amount(eosio::name coopname, uint64_t segment_id, eosio::asset amount) {
|
||||
Capital::Segments::segments_index segments(_capital, coopname.value);
|
||||
auto segment = segments.find(segment_id);
|
||||
|
||||
|
||||
eosio::check(segment->debt_amount >= amount, "Пайщик не может погасить долг больше, чем должен");
|
||||
|
||||
|
||||
segments.modify(segment, coopname, [&](auto &s) {
|
||||
s.debt_amount -= amount;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Увеличивает счётчик активных займов пайщика на сегменте на +1.
|
||||
* Вызывается в Capital::Debts::confirm_paid после перехода долга в PAID.
|
||||
*/
|
||||
inline void increase_active_debts_count(eosio::name coopname, const checksum256 &project_hash, eosio::name username) {
|
||||
segments_index segments(_capital, coopname.value);
|
||||
auto idx = segments.get_index<"byprojuser"_n>();
|
||||
auto rkey = combine_checksum_ids(project_hash, username);
|
||||
auto it = idx.find(rkey);
|
||||
eosio::check(it != idx.end(), "Сегмент участника на этом проекте не найден");
|
||||
idx.modify(it, coopname, [&](auto &s) {
|
||||
uint32_t cur = s.active_debts_count.has_value() ? *s.active_debts_count : 0;
|
||||
s.active_debts_count.emplace(cur + 1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Уменьшает счётчик активных займов пайщика на сегменте на -1.
|
||||
* Вызывается в Capital::Debts::mark_settled (settledebt и signact2-погашение).
|
||||
*/
|
||||
inline void decrease_active_debts_count(eosio::name coopname, const checksum256 &project_hash, eosio::name username) {
|
||||
segments_index segments(_capital, coopname.value);
|
||||
auto idx = segments.get_index<"byprojuser"_n>();
|
||||
auto rkey = combine_checksum_ids(project_hash, username);
|
||||
auto it = idx.find(rkey);
|
||||
eosio::check(it != idx.end(), "Сегмент участника на этом проекте не найден");
|
||||
idx.modify(it, coopname, [&](auto &s) {
|
||||
uint32_t cur = s.active_debts_count.has_value() ? *s.active_debts_count : 0;
|
||||
eosio::check(cur > 0, "Счётчик активных займов уже на нуле");
|
||||
s.active_debts_count.emplace(cur - 1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Текущее значение счётчика активных займов пайщика на сегменте.
|
||||
* Используется в signact2 как лимит обхода долгов проекта (≤10).
|
||||
*/
|
||||
inline uint32_t get_active_debts_count(eosio::name coopname, const checksum256 &project_hash, eosio::name username) {
|
||||
auto seg = get_segment(coopname, project_hash, username);
|
||||
if (!seg.has_value()) return 0;
|
||||
return seg->active_debts_count.has_value() ? *seg->active_debts_count : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Удаляет сегмент участника
|
||||
* @param coopname Имя кооператива
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Стандарт «Займ пайщику» — выдача беспроцентного целевого займа из паевого
|
||||
# фонда кооператива и его возврат через акт приёма-передачи проекта.
|
||||
#
|
||||
# Многоактовый процесс с авторизацией советом. Две операции:
|
||||
# • o.cap.lend — выдача займа на момент авторизации (ISSUE → w.cap.loan, Дт 58 / Кт 51)
|
||||
# • o.cap.repay — возврат займа из акта-2 (TRANSFER w.cap.loan → w.wal.share, Дт 80 / Кт 58)
|
||||
#
|
||||
# Источники правды в коде:
|
||||
# • cpp/capital/capital.hpp — actions
|
||||
# • cpp/capital/src/debt/{createdebt,approvedebt,debtauthcnfr,debtpaycnfrm,declinedebt,debtpaydcln}.cpp
|
||||
# • cpp/lib/core/ledger2/operations.hpp — o.cap.lend, o.cap.repay
|
||||
# • cpp/lib/core/ledger2/processes.hpp — processes::capital::DEBT
|
||||
# Стандарт «Займ пайщику»
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# ── Секция 1. Паспорт ───────────────────────────────────────────────────────
|
||||
@@ -20,103 +9,104 @@ title: Выдача займа пайщику
|
||||
slug: debt
|
||||
status: proposed
|
||||
contract: capital
|
||||
summary: >
|
||||
Пайщик получает из паевого фонда кооператива беспроцентный целевой заём
|
||||
на срок проекта; возврат происходит при сдаче акта-2 проекта.
|
||||
purpose: >
|
||||
«Заём пайщику» — кооператив выдаёт пайщику беспроцентный целевой заём
|
||||
из паевого фонда на срок проекта. Заявку последовательно одобряют
|
||||
председатель и совет, выплата уходит пайщику через кассира. Возврат
|
||||
происходит автоматически при сдаче акта приёма-передачи проекта —
|
||||
отдельная заявка на возврат не нужна.
|
||||
Пайщик получает из паевого фонда кооператива беспроцентный целевой заём
|
||||
сроком на один год. Решение о выдаче принимает совет кооператива. Заём
|
||||
возвращается автоматически при сдаче результата работ по проекту. Если же
|
||||
проект до сдачи не доведён и был отменён, долг закрывается за счёт
|
||||
имущества, которое пайщик ранее передал кооперативу в обеспечение по этому
|
||||
займу. Никаких санкций к пайщику в случае невозврата не применяется.
|
||||
roles:
|
||||
- contributor
|
||||
- chairman
|
||||
- soviet
|
||||
- gateway_operator
|
||||
- Пайщик
|
||||
- Председатель
|
||||
- Совет
|
||||
- Кассир
|
||||
|
||||
# ── Секция 2. Действия контракта ────────────────────────────────────────────
|
||||
actions:
|
||||
- name: capital::createdebt
|
||||
human: Подать заявление
|
||||
actor: contributor
|
||||
role: opener
|
||||
actor: Пайщик
|
||||
purpose: >
|
||||
Пайщик создаёт заявку на беспроцентный целевой заём. Контракт фиксирует
|
||||
запись долга в статусе `created`, обновляет сегмент CRPS пайщика на
|
||||
сумму займа.
|
||||
Пайщик подаёт заявление на беспроцентный целевой заём.
|
||||
- name: capital::approvedebt
|
||||
human: Одобрить решением председателя
|
||||
actor: chairman
|
||||
role: progress
|
||||
human: Одобрить
|
||||
actor: Председатель
|
||||
purpose: >
|
||||
Председатель добавляет к заявке документ-одобрение займа. Статус
|
||||
заявки → `approved`.
|
||||
Председатель одобряет заявление пайщика и выносит его на утверждение
|
||||
советом.
|
||||
- name: capital::debtauthcnfr
|
||||
human: Авторизовать выплату
|
||||
actor: soviet
|
||||
role: progress
|
||||
actor: Совет
|
||||
purpose: >
|
||||
Совет авторизует выплату займа. На этом этапе применяется ledger2-
|
||||
операция o.cap.lend и Gateway получает поручение на исходящий платёж.
|
||||
Статус → `authorized`.
|
||||
Совет утверждает выдачу займа и поручает кассиру выполнить выплату.
|
||||
- name: capital::debtpaycnfrm
|
||||
human: Подтвердить выплату
|
||||
actor: gateway_operator
|
||||
role: closer
|
||||
actor: Кассир
|
||||
purpose: >
|
||||
Gateway подтверждает зачисление займа на счёт пайщика. Статус → `paid`.
|
||||
Это закрывающее действие: запись долга сохраняется до возврата
|
||||
(операция o.cap.repay сработает уже на акте-2 в процессе РИД).
|
||||
Кассир подтверждает зачисление денежных средств пайщику. С этого
|
||||
момента отсчитывается срок возврата — один год.
|
||||
- name: capital::declinedebt
|
||||
human: Отклонить
|
||||
actor: chairman
|
||||
role: reject
|
||||
actor: Председатель
|
||||
purpose: >
|
||||
Председатель или совет отклоняет заявку на заём. Сегмент CRPS откатывается,
|
||||
запись долга удаляется, операции в ledger2 не создаются.
|
||||
Председатель или совет отклоняет заявление о выдаче займа.
|
||||
- name: capital::debtpaydcln
|
||||
human: Отклонить выплату
|
||||
actor: gateway_operator
|
||||
role: reject
|
||||
actor: Кассир
|
||||
purpose: >
|
||||
Gateway отклонил исходящий платёж по займу (тех. ошибка, возврат).
|
||||
Запись долга удаляется.
|
||||
Кассир сообщает, что выплата не прошла (технический сбой, возврат
|
||||
средств банком). Заявление снимается.
|
||||
- name: capital::closedebt
|
||||
human: Закрыть займ
|
||||
actor: Председатель совета
|
||||
purpose: >
|
||||
Если проект, под который был выдан заём, отменён до сдачи результата —
|
||||
долг закрывается за счёт имущества, ранее переданного пайщиком
|
||||
кооперативу в обеспечение по этому займу. Делается автоматически:
|
||||
без сбора совета и без подписи пайщика.
|
||||
|
||||
# ── Секция 3. Граф состояний ────────────────────────────────────────────────
|
||||
entity: capital::debt
|
||||
entity_human: Долг пайщика
|
||||
entity_source: cpp/capital/src/debt/
|
||||
|
||||
states:
|
||||
- name: created
|
||||
human: Заявление подано
|
||||
description: >
|
||||
Заявка на заём создана. Сегмент CRPS пайщика обновлён на запрошенную сумму.
|
||||
Пайщик подал заявление на заём. Документ ждёт решения председателя.
|
||||
kind: normal
|
||||
- name: approved
|
||||
human: Председатель одобрил
|
||||
human: Одобрено председателем
|
||||
description: >
|
||||
К заявке добавлен документ-одобрение председателя. Заявка ждёт авторизации
|
||||
советом.
|
||||
Председатель одобрил заявление. Документ ушёл на утверждение совету.
|
||||
kind: normal
|
||||
- name: authorized
|
||||
human: Совет авторизовал
|
||||
human: Утверждено советом
|
||||
description: >
|
||||
Совет авторизовал заём, в ledger2 применена операция o.cap.lend
|
||||
(Дт 58 / Кт 51), Gateway получил поручение на исходящий платёж.
|
||||
Совет утвердил выдачу займа. Кассиру поручено выполнить выплату.
|
||||
kind: normal
|
||||
- name: paid
|
||||
human: Заём выплачен
|
||||
description: >
|
||||
Gateway подтвердил зачисление займа пайщику. Запись долга остаётся
|
||||
в реестре до момента возврата через акт приёма-передачи проекта.
|
||||
kind: final
|
||||
- name: removed
|
||||
human: Отклонено
|
||||
Кассир подтвердил зачисление средств пайщику. С этого дня
|
||||
отсчитывается срок возврата — один год. Возврат произойдёт
|
||||
автоматически вместе со сдачей результата работ по проекту.
|
||||
kind: normal
|
||||
- name: writeoff
|
||||
human: Списано через имущество-обеспечение
|
||||
description: >
|
||||
Заявка отклонена на одном из этапов; запись удалена, операции
|
||||
o.cap.lend / o.cap.repay не создавались.
|
||||
Проект, под который был выдан заём, отменён до сдачи результата.
|
||||
Долг закрыт автоматически за счёт имущества, переданного пайщиком
|
||||
кооперативу в обеспечение по этому займу. Запись о займе остаётся
|
||||
в реестре с этой пометкой для последующего аудита; никаких претензий
|
||||
к пайщику не предъявляется.
|
||||
kind: normal
|
||||
- name: removed
|
||||
human: Заявление отклонено
|
||||
description: >
|
||||
Заявление снято на одном из этапов согласования — председателем,
|
||||
советом или кассиром.
|
||||
kind: virtual
|
||||
virtual: true
|
||||
|
||||
@@ -124,129 +114,141 @@ transitions:
|
||||
- from: "∅"
|
||||
to: created
|
||||
action: capital::createdebt
|
||||
actor: contributor
|
||||
actor: Пайщик
|
||||
guards:
|
||||
- Пайщик имеет статус active и участвует в проекте.
|
||||
- Сегмент CRPS пайщика актуален.
|
||||
- provisional_amount ≥ запрошенная сумма займа.
|
||||
- Заявление подписано ЭЦП.
|
||||
- Пайщик — активный участник кооператива.
|
||||
- Пайщик участвует в проекте, под который запрашивается заём.
|
||||
- На сегменте пайщика хватает доступной суммы для запроса.
|
||||
- Заявление подписано пайщиком.
|
||||
|
||||
- from: created
|
||||
to: approved
|
||||
action: capital::approvedebt
|
||||
actor: chairman
|
||||
actor: Председатель
|
||||
guards:
|
||||
- Документ одобрения председателя подписан.
|
||||
- Председатель подписал решение об одобрении.
|
||||
|
||||
- from: approved
|
||||
to: authorized
|
||||
action: capital::debtauthcnfr
|
||||
actor: soviet
|
||||
ledger_code: o.cap.lend
|
||||
operations:
|
||||
- o.cap.lend
|
||||
actor: Совет
|
||||
guards:
|
||||
- Документ авторизации совета подписан.
|
||||
- Совет принял решение о выдаче займа.
|
||||
|
||||
- from: authorized
|
||||
to: paid
|
||||
action: capital::debtpaycnfrm
|
||||
actor: gateway_operator
|
||||
actor: Кассир
|
||||
guards:
|
||||
- Gateway подтвердил зачисление займа пайщику.
|
||||
- Кассир подтвердил зачисление средств пайщику.
|
||||
|
||||
- from: created
|
||||
to: removed
|
||||
action: capital::declinedebt
|
||||
actor: chairman
|
||||
actor: Председатель
|
||||
guards:
|
||||
- Председатель или совет отклонили заявку.
|
||||
- Председатель или совет отклонили заявление.
|
||||
|
||||
- from: approved
|
||||
to: removed
|
||||
action: capital::declinedebt
|
||||
actor: soviet
|
||||
actor: Совет
|
||||
guards:
|
||||
- Совет отклонил заявку.
|
||||
- Совет отклонил заявление.
|
||||
|
||||
- from: authorized
|
||||
to: removed
|
||||
action: capital::debtpaydcln
|
||||
actor: gateway_operator
|
||||
actor: Кассир
|
||||
guards:
|
||||
- Платёж не прошёл / отклонён Gateway.
|
||||
- Платёж не прошёл — банк отклонил или вернул перевод.
|
||||
|
||||
- from: paid
|
||||
to: writeoff
|
||||
action: capital::closedebt
|
||||
actor: Председатель совета
|
||||
guards:
|
||||
- Проект, под который был выдан заём, отменён.
|
||||
- У пайщика на сегменте есть имущество, переданное в обеспечение.
|
||||
|
||||
# ── Секция 4. Сценарий ──────────────────────────────────────────────────────
|
||||
scenario:
|
||||
steps:
|
||||
- step: 1
|
||||
title: Подача заявления на заём
|
||||
actor: contributor
|
||||
title: Подача заявления
|
||||
actor: Пайщик
|
||||
action: capital::createdebt
|
||||
description: >
|
||||
Пайщик подаёт заявление на беспроцентный целевой заём.
|
||||
Контракт создаёт запись долга со статусом `created` и обновляет
|
||||
сегмент CRPS на запрошенную сумму.
|
||||
|
||||
- step: 2
|
||||
title: Одобрение председателем
|
||||
actor: chairman
|
||||
actor: Председатель
|
||||
action: capital::approvedebt
|
||||
description: >
|
||||
Председатель добавляет к заявке документ-одобрение. Статус
|
||||
заявки → `approved`.
|
||||
Председатель рассматривает заявление и при согласии одобряет его,
|
||||
вынося на утверждение совету.
|
||||
|
||||
- step: 3
|
||||
title: Авторизация советом и выдача займа
|
||||
actor: soviet
|
||||
title: Утверждение советом и поручение на выплату
|
||||
actor: Совет
|
||||
action: capital::debtauthcnfr
|
||||
description: >
|
||||
Совет авторизует выплату. В ledger2 применяется операция o.cap.lend
|
||||
(ISSUE → w.cap.loan, Дт 58 / Кт 51), Gateway получает поручение на
|
||||
исходящий платёж. Статус → `authorized`.
|
||||
Совет утверждает выдачу займа. Кассиру передаётся поручение на
|
||||
перечисление средств пайщику.
|
||||
|
||||
- step: 4
|
||||
title: Подтверждение выплаты
|
||||
actor: gateway_operator
|
||||
actor: Кассир
|
||||
action: capital::debtpaycnfrm
|
||||
description: >
|
||||
Gateway подтверждает зачисление средств пайщику. Статус → `paid`.
|
||||
Запись долга остаётся в реестре до момента возврата через акт-2
|
||||
проекта (операция o.cap.repay сработает в процессе p.cap.rid).
|
||||
Кассир выполняет перевод и подтверждает зачисление средств пайщику.
|
||||
С этого дня отсчитывается срок возврата — один год.
|
||||
|
||||
alternatives:
|
||||
- branch: Отказ председателя или совета
|
||||
at_step: 2
|
||||
action: capital::declinedebt
|
||||
actor: chairman
|
||||
actor: Председатель
|
||||
description: >
|
||||
Заявка отклонена. Сегмент CRPS откатывается, запись долга удаляется,
|
||||
операции в ledger2 не создаются.
|
||||
- branch: Отказ Gateway
|
||||
Заявление отклонено на этапе согласования. Деньги пайщику не
|
||||
перечисляются.
|
||||
- branch: Не прошёл платёж
|
||||
at_step: 4
|
||||
action: capital::debtpaydcln
|
||||
actor: gateway_operator
|
||||
actor: Кассир
|
||||
description: >
|
||||
Платёж не прошёл / отклонён. Запись долга удаляется.
|
||||
Банк отклонил или вернул перевод. Заявление снимается; пайщик может
|
||||
подать его заново.
|
||||
- branch: Невозврат — закрытие через имущество-обеспечение
|
||||
at_step: 4
|
||||
action: capital::closedebt
|
||||
actor: Председатель совета
|
||||
description: >
|
||||
Если проект, под который был выдан заём, отменён до сдачи
|
||||
результата — долг закрывается автоматически за счёт имущества,
|
||||
переданного пайщиком кооперативу в обеспечение по этому займу.
|
||||
Сбора совета не требуется, подпись пайщика не требуется,
|
||||
претензий к пайщику не предъявляется.
|
||||
|
||||
# ── Секция 5. Документы и подписи ───────────────────────────────────────────
|
||||
documents:
|
||||
- step: 1
|
||||
- action: capital::createdebt
|
||||
title: Заявление на получение займа
|
||||
registry_id: 1050
|
||||
signed_by: [contributor]
|
||||
signed_by: [Пайщик]
|
||||
stored_in: debts.statement
|
||||
|
||||
- step: 2
|
||||
- action: capital::approvedebt
|
||||
title: Решение совета о предоставлении займа (одобрение председателем)
|
||||
registry_id: 1051
|
||||
signed_by: [chairman]
|
||||
signed_by: [Председатель]
|
||||
stored_in: debts.approved_statement
|
||||
|
||||
- step: 3
|
||||
title: Решение совета о предоставлении займа (авторизация совета)
|
||||
- action: capital::debtauthcnfr
|
||||
title: Решение совета о предоставлении займа (утверждение советом)
|
||||
registry_id: 1051
|
||||
signed_by: [soviet]
|
||||
signed_by: [Совет]
|
||||
stored_in: debts.authorization
|
||||
|
||||
# ── Секция 6. Операции ──────────────────────────────────────────────────────
|
||||
@@ -254,32 +256,65 @@ operations:
|
||||
- ledger_code: o.cap.lend
|
||||
human_name: Выдача пайщику беспроцентного займа
|
||||
wallet_op: ISSUE
|
||||
wallet_from: ''
|
||||
wallet_to: w.cap.loan # Выданные пайщикам беспроцентные займы
|
||||
debit: 58 # Финансовые вложения
|
||||
credit: 51 # Расчётный счёт
|
||||
debit: 58
|
||||
credit: 51
|
||||
wallet_from: null
|
||||
wallet_to: w.cap.loan
|
||||
amount_ref: debt.amount
|
||||
triggered_by: capital::debtauthcnfr
|
||||
description: >
|
||||
Выдача беспроцентного займа пайщику. Деньги уходят с расчётного счёта
|
||||
кооператива (Кт 51) и появляются как финансовое вложение Дт 58.
|
||||
Кошелёк «Выданные пайщикам беспроцентные займы» (w.cap.loan) фиксирует
|
||||
обязательство пайщика перед кооперативом.
|
||||
Кооператив перечисляет пайщику беспроцентный целевой заём. На стороне
|
||||
кооператива деньги уходят с расчётного счёта и фиксируются как
|
||||
финансовое вложение в пайщика-должника.
|
||||
|
||||
- ledger_code: o.cap.repay
|
||||
human_name: Возврат беспроцентного займа пайщика по акту-2
|
||||
human_name: Возврат беспроцентного займа пайщика при сдаче результата
|
||||
wallet_op: TRANSFER
|
||||
wallet_from: w.cap.loan # Выданные пайщикам беспроцентные займы
|
||||
wallet_to: w.wal.share # ЦПП «Цифровой Кошелёк» — паевые взносы деньгами
|
||||
debit: 80 # Паевой фонд (складочный капитал)
|
||||
credit: 58 # Финансовые вложения
|
||||
debit: 80
|
||||
credit: 58
|
||||
wallet_from: w.cap.loan
|
||||
wallet_to: w.wal.share
|
||||
user_wallet: w.wal.share
|
||||
user_ref: debt.username
|
||||
available_delta: +debt.amount
|
||||
blocked_delta: null
|
||||
amount_ref: debt.amount
|
||||
triggered_by: capital::debtpaycnfrm
|
||||
description: >
|
||||
Возврат займа в момент сдачи акта-2 проекта (происходит как часть
|
||||
процесса «Приём РИД» — p.cap.rid). Финансовое вложение закрывается
|
||||
(Кт 58), сумма зачитывается в паевой фонд (Дт 80) и появляется как
|
||||
доступный остаток на SHARE_FUND_PAY пайщика.
|
||||
Происходит в момент сдачи результата работ по проекту
|
||||
(подписанный акт приёма-передачи). Долг пайщика гасится, а сумма
|
||||
займа зачитывается ему как паевой взнос — появляется как свободный
|
||||
остаток на личном паевом кошельке пайщика.
|
||||
|
||||
- ledger_code: o.cap.crtnma
|
||||
human_name: Создание имущества кооператива из обеспечения по займу
|
||||
wallet_op: TRANSFER
|
||||
debit: 4
|
||||
credit: 8
|
||||
wallet_from: w.cap.gen
|
||||
wallet_to: w.cap.nma
|
||||
amount_ref: debt.amount
|
||||
triggered_by: capital::closedebt
|
||||
description: >
|
||||
Срабатывает при невозврате: проект, под который был выдан заём,
|
||||
отменён до сдачи результата. Имущество, ранее переданное пайщиком
|
||||
кооперативу в обеспечение, переходит в собственность кооператива
|
||||
как нематериальный актив.
|
||||
|
||||
- ledger_code: o.cap.dbtwrf
|
||||
human_name: Списание долга за счёт имущества-обеспечения
|
||||
wallet_op: BURN
|
||||
debit: 80
|
||||
credit: 58
|
||||
wallet_from: w.cap.loan
|
||||
wallet_to: null
|
||||
amount_ref: debt.amount
|
||||
triggered_by: capital::closedebt
|
||||
description: >
|
||||
Парная операция к созданию имущества из обеспечения: обязательство
|
||||
пайщика по займу закрывается. На паевой кошелёк пайщика ничего не
|
||||
зачисляется — долг закрыт имущественно, через переданное в
|
||||
обеспечение имущество.
|
||||
|
||||
# ── Секция 7. Связи ─────────────────────────────────────────────────────────
|
||||
related:
|
||||
@@ -287,11 +322,20 @@ related:
|
||||
id: public_capital_rid_process
|
||||
relation: triggers
|
||||
note: >
|
||||
Возврат займа (o.cap.repay) технически происходит в процессе
|
||||
«Приём РИД» при подписании акта-2 проекта.
|
||||
Возврат займа физически происходит при сдаче результата работ по
|
||||
проекту (подписанный акт приёма-передачи) — в процессе «Приём
|
||||
результата интеллектуальной деятельности».
|
||||
|
||||
- process_type: p.reg.accept
|
||||
id: public_registrator_accept_process
|
||||
relation: provides
|
||||
note: >
|
||||
Заём может получить только активный пайщик кооператива.
|
||||
|
||||
- process_type: p.cap.rid
|
||||
id: public_capital_rid_process
|
||||
relation: affects
|
||||
note: >
|
||||
Закрытие долга через имущество-обеспечение уменьшает на сегменте
|
||||
пайщика ту часть, которая раньше была передана в обеспечение и
|
||||
теперь стала собственностью кооператива.
|
||||
|
||||
@@ -77,9 +77,12 @@ namespace operations {
|
||||
inline constexpr eosio::name DROP_PREIMP = "o.cap.drppre"_n; ///< Закрытие пред-импорт-учёта при переходе на электронный учёт (Dr 80 / Cr 04, BURN PREIMP_FUND). Вызывается из capital::importcontr перед o.cap.import.
|
||||
inline constexpr eosio::name LEND = "o.cap.lend"_n; ///< Выдача беспроцентного займа пайщику (Dr 58 / Cr 51, ISSUE LOAN_ISSUED).
|
||||
inline constexpr eosio::name REPAY = "o.cap.repay"_n; ///< Возврат займа пайщика по акту-2 (Dr 80 / Cr 58, TRANSFER LOAN_ISSUED → SHARE_FUND_PAY).
|
||||
inline constexpr eosio::name CREATE_NMA = "o.cap.crtnma"_n; ///< Создать НМА кооператива из коммитов-обеспечения долга при невозврате займа (Dr 04 / Cr 08, TRANSFER GENERATOR_FUND → NMA). Решение 2026-05-19.
|
||||
inline constexpr eosio::name DEBT_WRITEOFF = "o.cap.dbtwrf"_n; ///< Списание долга (write-off) за счёт созданного НМА (Dr 80 / Cr 58, BURN LOAN_ISSUED). Зеркало REPAY без зачисления на share пайщика — долг закрыт имущественно. Решение 2026-05-19.
|
||||
inline constexpr eosio::name WITHDRAW_FROM_CAPITAL = "o.cap.wthcap"_n; ///< Возврат паевого из ЦПП «Благорост» в кошелёк пайщика (TRANSFER BLAGOROST_FUND → SHARE_FUND_PAY, без Dr/Cr).
|
||||
inline constexpr eosio::name CONVERT_TO_SHARE = "o.cap.cnvshr"_n; ///< Конвертация сегмента: РИД → главный кошелёк (TRANSFER GENERATOR_FUND → SHARE_FUND_PAY, без Dr/Cr — бухпроводка уже была сделана в ACCEPT_RID).
|
||||
inline constexpr eosio::name CONVERT_TO_BLAGO = "o.cap.cnvbl"_n; ///< Конвертация сегмента: РИД → ЦПП «Благорост» (TRANSFER GENERATOR_FUND → BLAGOROST_FUND, без Dr/Cr — бухпроводка уже была сделана в ACCEPT_RID).
|
||||
inline constexpr eosio::name PAY_EXPENSE = "o.cap.expns"_n; ///< Оплата целевого расхода программы (Dr 86 / Cr 51, TRANSFER BLAGOROST_FUND → SOV_EXPENSES). Списание целевого финансирования на расчётный счёт.
|
||||
}
|
||||
|
||||
// marketplace
|
||||
@@ -252,6 +255,28 @@ static constexpr OperationRegistryEntry OPERATION_REGISTRY[] = {
|
||||
ledger2_accounts::SHARE_FUND, ledger2_accounts::FINANCIAL_INVESTMENTS,
|
||||
"Возврат беспроцентного займа пайщика по акту-2" },
|
||||
|
||||
// 11a. Создать НМА кооператива из коммитов-обеспечения долга при невозврате:
|
||||
// Dr 04 / Cr 08, TRANSFER GENERATOR_FUND → NMA.
|
||||
// Срабатывает автоматически из `capital::closedebt`, когда родительский
|
||||
// компонент перешёл в `cancelled` (решение 2026-05-19 — без сбора совета,
|
||||
// без подписи пайщика). РИД-коммиты, накопленные на GENERATOR_FUND пайщика
|
||||
// как обеспечение долга, формируют НМА кооператива на отдельном кошельке
|
||||
// `w.cap.nma`. Пара по 04 зеркалит `o.cap.accept`, но без участия пайщика —
|
||||
// сегмент пайщика уменьшается на сумму операции.
|
||||
{ operations::capital::CREATE_NMA, processes::capital::DEBT, WalletOp::TRANSFER,
|
||||
ledger2_wallets::GENERATOR_FUND, ledger2_wallets::NMA,
|
||||
ledger2_accounts::INTANGIBLE_ASSETS, ledger2_accounts::NON_CURRENT_INVESTMENTS,
|
||||
"Создание НМА кооператива из коммитов-обеспечения долга" },
|
||||
|
||||
// 11b. Списание долга (write-off) за счёт созданного НМА: Dr 80 / Cr 58, BURN LOAN_ISSUED.
|
||||
// Парная к `o.cap.crtnma` операция, закрывает обязательство пайщика по займу.
|
||||
// Зеркало `o.cap.repay`, но без зачисления на share пайщика (долг закрыт
|
||||
// имущественно — через НМА из коммитов-обеспечения). Решение 2026-05-19.
|
||||
{ operations::capital::DEBT_WRITEOFF, processes::capital::DEBT, WalletOp::BURN,
|
||||
ledger2_wallets::LOAN_ISSUED, eosio::name{},
|
||||
ledger2_accounts::SHARE_FUND, ledger2_accounts::FINANCIAL_INVESTMENTS,
|
||||
"Списание долга (write-off) за счёт созданного НМА" },
|
||||
|
||||
// 12. Подтверждение поставки: Dr 51 / Cr 80, ISSUE SHARE_FUND_PAY
|
||||
{ operations::marketplace::CONFIRM_SUPPLY, processes::marketplace::REQUEST, WalletOp::ISSUE, eosio::name{}, ledger2_wallets::SHARE_FUND_PAY,
|
||||
ledger2_accounts::BANK_ACCOUNT, ledger2_accounts::SHARE_FUND,
|
||||
@@ -305,6 +330,16 @@ static constexpr OperationRegistryEntry OPERATION_REGISTRY[] = {
|
||||
0, 0,
|
||||
"Конвертация сегмента: РИД → ЦПП «Благорост»" },
|
||||
|
||||
// 20. Оплата целевого расхода программы: Dr 86 / Cr 51, TRANSFER BLAGOROST_FUND → SOV_EXPENSES.
|
||||
// Закрывает TODO в exppaycnfrm.cpp: списывает сумму расхода из программного фонда
|
||||
// «Благорост» в пул «Хозяйственные расходы из числа целевого финансирования»;
|
||||
// одновременно фиксируется бухпроводка по уменьшению целевого финансирования (86)
|
||||
// и списанию с расчётного счёта (51).
|
||||
{ operations::capital::PAY_EXPENSE, processes::capital::EXPENSE, WalletOp::TRANSFER,
|
||||
ledger2_wallets::BLAGOROST_FUND, ledger2_wallets::SOV_EXPENSES,
|
||||
ledger2_accounts::TARGET_RECEIPTS, ledger2_accounts::BANK_ACCOUNT,
|
||||
"Оплата целевого расхода по программе «Благорост»" },
|
||||
|
||||
// ----- Миграционные (o.mig.*) — вызываются только из migrate.cpp -----
|
||||
|
||||
// 15. Миграция: минимальный паевой: Dr 51 / Cr 80, ISSUE MIN_SHARE_FUND
|
||||
|
||||
@@ -48,11 +48,12 @@ namespace processes {
|
||||
namespace capital {
|
||||
inline constexpr eosio::name IMPORT = "p.cap.import"_n; ///< Оффлайн-импорт пайщика Благорост (o.cap.drppre (опц.) + o.cap.import).
|
||||
inline constexpr eosio::name INVEST = "p.cap.invest"_n; ///< Инвестиция в ЦПП Благорост (wallet-only 2001→9001).
|
||||
inline constexpr eosio::name DEBT = "p.cap.debt"_n; ///< Займ пайщику (o.cap.lend + o.cap.repay).
|
||||
inline constexpr eosio::name DEBT = "p.cap.debt"_n; ///< Займ пайщику (o.cap.lend + o.cap.repay; альтернативная ветка невозврата — o.cap.crtnma + o.cap.dbtwrf, решение 2026-05-19).
|
||||
inline constexpr eosio::name RID = "p.cap.rid"_n; ///< Приём РИД в паевой фонд: o.cap.commit (коммиты) + o.cap.accept (акт-2) + опц. o.cap.repay + o.cap.cnvshr/o.cap.cnvbl (финальная конвертация сегмента). Анкер процесса — result_hash.
|
||||
inline constexpr eosio::name PROPERTY = "p.cap.prop"_n; ///< Приём имущественного паевого взноса (одноактовый).
|
||||
inline constexpr eosio::name PREIMP = "p.cap.preimp"_n; ///< Первичный учёт РИД-взноса до перехода на электронный учёт (одноактовый, anchor = preimp register hash).
|
||||
inline constexpr eosio::name WTHCAP = "p.cap.wthcap"_n; ///< Возврат паевого из ЦПП «Благорост» в кошелёк пайщика (одноактовый).
|
||||
inline constexpr eosio::name EXPENSE = "p.cap.expns"_n; ///< Целевой расход программы (создание → одобрение председателя → авторизация совета → outpay → подтверждение оплаты). Anchor — expense_hash.
|
||||
}
|
||||
|
||||
// marketplace
|
||||
|
||||
@@ -63,6 +63,7 @@ struct ledger2_wallets {
|
||||
static constexpr eosio::name BLAGOROST_FUND = "w.cap.blago"_n; ///< Благорост — единый агрегированный кошелёк программы (USER_SHARED; ADR-009)
|
||||
static constexpr eosio::name GENERATOR_FUND = "w.cap.gen"_n; ///< Генератор — единый агрегированный кошелёк программы (USER_SHARED; ADR-009)
|
||||
static constexpr eosio::name PREIMP_FUND = "w.cap.preimp"_n; ///< Первичный учёт РИД-взносов до перехода на электронный учёт (USER_SHARED; o.cap.preimp / o.cap.drppre)
|
||||
static constexpr eosio::name NMA = "w.cap.nma"_n; ///< НМА кооператива — собственное имущество, перешедшее из коммитов-обеспечения при невозврате займа (COOPERATIVE; решение 2026-05-19)
|
||||
|
||||
// marketplace — выплаты
|
||||
static constexpr eosio::name SUPPLIER_PAYMENTS = "w.mkt.payout"_n; ///< Выплаты поставщикам (sink RECEIVE_CONFIRM, COOPERATIVE)
|
||||
@@ -94,7 +95,7 @@ struct Ledger2WalletMeta {
|
||||
WalletKind kind;
|
||||
};
|
||||
|
||||
inline constexpr std::array<Ledger2WalletMeta, 14> LEDGER2_WALLET_REGISTRY = {{
|
||||
inline constexpr std::array<Ledger2WalletMeta, 15> LEDGER2_WALLET_REGISTRY = {{
|
||||
// USER_SHARED (6) — L3-разрез по пайщику
|
||||
{ ledger2_wallets::MIN_SHARE_FUND, "Минимальный паевой взнос", WalletKind::USER_SHARED },
|
||||
{ ledger2_wallets::SHARE_FUND_PAY, "Паевой взнос пайщика", WalletKind::USER_SHARED },
|
||||
@@ -103,7 +104,7 @@ inline constexpr std::array<Ledger2WalletMeta, 14> LEDGER2_WALLET_REGISTRY = {{
|
||||
{ ledger2_wallets::GENERATOR_FUND, "ЦПП «Генератор» — единый кошелёк программы у пайщика", WalletKind::USER_SHARED },
|
||||
{ ledger2_wallets::PREIMP_FUND, "Первичный учёт РИД-взносов до перехода на электронный учёт", WalletKind::USER_SHARED },
|
||||
|
||||
// COOPERATIVE (8) — единый кооперативный баланс, без L3
|
||||
// COOPERATIVE (9) — единый кооперативный баланс, без L3
|
||||
{ ledger2_wallets::ENTRANCE_FEES, "Вступительные взносы", WalletKind::COOPERATIVE },
|
||||
{ ledger2_wallets::WITHDRAWALS_SINK, "Возвраты паевых взносов пайщикам", WalletKind::COOPERATIVE },
|
||||
{ ledger2_wallets::INFRA_FEES, "Членские взносы за инфраструктуру кооп. платформы", WalletKind::COOPERATIVE },
|
||||
@@ -112,6 +113,7 @@ inline constexpr std::array<Ledger2WalletMeta, 14> LEDGER2_WALLET_REGISTRY = {{
|
||||
{ ledger2_wallets::MIN_SHARE_USED, "Использованные минимальные паевые взносы", WalletKind::COOPERATIVE },
|
||||
{ ledger2_wallets::LOAN_ISSUED, "Выданные пайщикам беспроцентные займы", WalletKind::COOPERATIVE },
|
||||
{ ledger2_wallets::SUPPLIER_PAYMENTS, "Выплаты поставщикам", WalletKind::COOPERATIVE },
|
||||
{ ledger2_wallets::NMA, "НМА кооператива (из коммитов-обеспечения по займам)", WalletKind::COOPERATIVE },
|
||||
}};
|
||||
|
||||
static constexpr size_t LEDGER2_WALLET_REGISTRY_SIZE = LEDGER2_WALLET_REGISTRY.size();
|
||||
|
||||
@@ -50,6 +50,12 @@ namespace Names {
|
||||
// Коллбэки для расходов
|
||||
constexpr eosio::name AUTHORIZE_EXPENSE = "capauthexpns"_n;
|
||||
constexpr eosio::name DECLINE_EXPENSE = "capdeclexpns"_n;
|
||||
|
||||
// Коллбэки для программных расходов
|
||||
// (CREATE_PROGRAM_EXPENSE объявлен ниже в блоке "акцептов" — общий для обоих контекстов)
|
||||
constexpr eosio::name AUTHORIZE_PROGRAM_EXPENSE = "authpgexp"_n;
|
||||
constexpr eosio::name DECLINE_PROGRAM_EXPENSE = "declpgexp"_n;
|
||||
constexpr eosio::name CONFIRM_PROGRAM_EXPENSE_PAYMENT = "pgexppay"_n;
|
||||
|
||||
// Коллбэки для долгов
|
||||
constexpr eosio::name AUTHORIZE_DEBT = "debtauthcnfr"_n;
|
||||
@@ -91,6 +97,7 @@ namespace Names {
|
||||
constexpr eosio::name CAPITAL_WITHDRAW_FROM_PROGRAM = "capwthdrprog"_n;
|
||||
constexpr eosio::name CAPITAL_WITHDRAW_FROM_PROJECT = "capwthdrproj"_n;
|
||||
constexpr eosio::name CAPITAL_RESOLVE_EXPENSE = "capresexpns"_n;
|
||||
constexpr eosio::name CAPITAL_RESOLVE_PROGRAM_EXPENSE = "caprespgexp"_n;
|
||||
constexpr eosio::name CREATE_RESULT = "createresult"_n;
|
||||
constexpr eosio::name CREATE_DEBT = "createdebt"_n;
|
||||
constexpr eosio::name CREATE_PROJECT = "createprj"_n;
|
||||
@@ -120,6 +127,7 @@ namespace Names {
|
||||
constexpr eosio::name CREATE_INVESTMENT = "createinvest"_n; // акцепт инвестиции
|
||||
constexpr eosio::name CREATE_PROGRAM_INVESTMENT = "createpinv"_n; // акцепт программной инвестиции
|
||||
constexpr eosio::name CREATE_EXPENSE = "createexpnse"_n; // акцепт расхода
|
||||
constexpr eosio::name CREATE_PROGRAM_EXPENSE = "createpgexp"_n; // акцепт программного расхода
|
||||
constexpr eosio::name CREATE_WITHDRAW_1 = "createwthd1"_n; // акцепт возврата из задания
|
||||
constexpr eosio::name CREATE_WITHDRAW_2 = "createwthd2"_n; // акцепт возврата из проекта
|
||||
constexpr eosio::name CREATE_WITHDRAW_3 = "createwthd3"_n; // акцепт возврата из программы
|
||||
|
||||
@@ -19,6 +19,11 @@ struct [[eosio::table, eosio::contract(LOAN)]] debt {
|
||||
time_point_sec created_at;
|
||||
time_point_sec repaid_at;
|
||||
|
||||
// Контракт-источник, инициировавший выдачу займа (capital / marketplace / ...).
|
||||
// loan не знает доменного контекста (проекта, программы) — это ответственность
|
||||
// вызывающего контракта; ему же принадлежит и локальная привязка debt_hash.
|
||||
name source_contract;
|
||||
|
||||
uint64_t primary_key() const { return id; }
|
||||
uint64_t by_username() const { return username.value; }
|
||||
checksum256 by_debt_hash() const { return debt_hash; }
|
||||
|
||||
@@ -2,10 +2,13 @@ using namespace Loan;
|
||||
|
||||
/**
|
||||
* @brief Создание долгового обязательства.
|
||||
* Создает новое долговое обязательство для пользователя с указанным сроком погашения
|
||||
* Создает новое долговое обязательство пайщика. loan-контракт независим от
|
||||
* прикладных контрактов — здесь нет привязки к проекту/программе/компоненту.
|
||||
* Локальный контекст (project_hash и т.п.) хранится на стороне вызывающего
|
||||
* контракта в его собственной таблице долгов.
|
||||
* @param coopname Наименование кооператива
|
||||
* @param username Имя пользователя-должника
|
||||
* @param debt_hash Хэш долгового обязательства
|
||||
* @param debt_hash Хэш долгового обязательства (идентификатор)
|
||||
* @param repaid_at Срок погашения долга
|
||||
* @param quantity Сумма долга
|
||||
* @ingroup public_actions
|
||||
@@ -33,6 +36,7 @@ void loan::createdebt(name coopname, name username, checksum256 debt_hash, time_
|
||||
d.amount = quantity;
|
||||
d.created_at = time_point_sec(current_time_point());
|
||||
d.repaid_at = repaid_at;
|
||||
d.source_contract = payer;
|
||||
});
|
||||
|
||||
Loan::summaries_index summaries(_loan, coopname.value);
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import { SignedDigitalDocumentInputDTO } from '~/application/document/dto/signed
|
||||
type ExcludeCommonProps<T> = Omit<T, 'coopname' | 'username' | 'registry_id' | 'appendix_hash'>;
|
||||
|
||||
// интерфейс параметров для генерации
|
||||
type action = Cooperative.Registry.GenerationMoneyInvestStatement.Action;
|
||||
type action = Cooperative.Registry.GenerationConvertStatement.Action;
|
||||
|
||||
@InputType(`BaseGenerationConvertStatementMetaDocumentInput`)
|
||||
class BaseGenerationConvertStatementMetaDocumentInputDTO implements ExcludeCommonProps<action> {
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ export class DistributionManagementService {
|
||||
const document = await this.documentInteractor.generateDocument({
|
||||
data: {
|
||||
...enrichedData,
|
||||
registry_id: Cooperative.Registry.GenerationMoneyInvestStatement.registry_id,
|
||||
registry_id: Cooperative.Registry.GenerationConvertStatement.registry_id,
|
||||
},
|
||||
options,
|
||||
});
|
||||
|
||||
+2
-2
@@ -45,7 +45,7 @@ export class DistributionManagementInteractor {
|
||||
async prepareGenerationConvertStatementData(
|
||||
data: GenerationConvertStatementGenerateDocumentInputDTO,
|
||||
currentUser: MonoAccountDomainInterface
|
||||
): Promise<Cooperative.Registry.GenerationMoneyInvestStatement.Action> {
|
||||
): Promise<Cooperative.Registry.GenerationConvertStatement.Action> {
|
||||
const projectHash = data.project_hash;
|
||||
if (!projectHash) {
|
||||
throw new Error('project_hash обязателен для генерации заявления о конвертации');
|
||||
@@ -63,6 +63,6 @@ export class DistributionManagementInteractor {
|
||||
return {
|
||||
...data,
|
||||
appendix_hash: userAppendix.appendix_hash,
|
||||
} as Cooperative.Registry.GenerationMoneyInvestStatement.Action;
|
||||
} as Cooperative.Registry.GenerationConvertStatement.Action;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user