Compare commits

...

10 Commits

Author SHA1 Message Date
Alex Ant 64c86de033 transport delivery 2025-07-25 16:14:04 +05:00
Alex Ant 4ea4298cb1 Merge branch 'dev' into marketplace 2025-07-23 21:10:20 +05:00
Alex Ant 3dc0592d72 first step 2025-07-23 21:06:38 +05:00
Alex Ant 7ad2806059 success compilation 2025-07-23 18:58:15 +05:00
Alex Ant 725b804481 ledger and marketplace contracts 2025-07-23 13:27:52 +05:00
Alex Ant 1080a29ab9 Merge branch 'dev' into marketplace 2025-07-21 11:14:56 +05:00
Alex Ant 4eb6888b1f bug fix on unit count and request entity fix 2025-07-20 21:19:22 +05:00
Alex Ant 48c41937e5 contract refactoring #1 and DTOs 2025-07-19 15:16:43 +05:00
Alex Ant b6771e43a9 marketplace plugin initialization 2025-07-17 15:19:46 +05:00
Alex Ant 507e694477 release v2025.7.15-1 2025-07-15 18:24:45 +05:00
200 changed files with 13924 additions and 1747 deletions
+22
View File
@@ -1,3 +1,25 @@
# v2025.7.15-1
Релиз закрывает собой большой цикл фундаментальной разработки, предоставляя кооперативам мобильные PWA-приложения, которые быстро и легко устанавливаются на все телефоны и компьютеры через добавление на домашний экран. После добавления на домашний экран приложение запрашивает доступ к системным уведомлениям и становится способным оповещать пайщиков о самом важном через PUSH-сообщения.
Также, данный релиз содержит прототип виджета, который позволит разработчикам в несколько строк кода отображать на своих сайтах визуальный интерфейс цифрового кооператива MONO и безопасно взаимодействовать с ним без необходимости тяжелой разработки. Реализация данного виджета открывает дорогу на все существующие платформы-конструкторы, такие как Tilda, Wordpress, Joomla, а также, на любые сайты, где страницы с мини-приложениями кооперативной экономики встроятся "нативно", - без необходимости для пользователя переходить куда-либо и разрывать опыт.
✨ Новые функции
- [#293](https://github.com/coopenomics/mono/issues/293): Разработан прототип виджета MONO для быстрого запуска
- [#291](https://github.com/coopenomics/mono/issues/291): Реализован и настроен центр уведомлений
- [#290](https://github.com/coopenomics/mono/issues/290): Создан пакет фабрики оповещений (email, push, inApp)
- [#289](https://github.com/coopenomics/mono/issues/289): Настроены push-уведомления для всех поддерживаемых устройств и браузеров
- [#288](https://github.com/coopenomics/mono/issues/288): Добавлено окно для запроса разрешения на PUSH уведомления
- [#287](https://github.com/coopenomics/mono/issues/287): Создана PWA-конфигурация для приложения MONO
🐛 Исправления ошибок
- [#292](https://github.com/coopenomics/mono/issues/292): Исправлен баг "Неизвестный подписант" для организаций
#releases
---
# v2025.7.1-1
В этом релизе реализованы механизмы возврата паевого взноса пайщика и инструменты управления этим процессом для членов совета. Также внесены визуальные доработки интерфейсов для улучшения восприятия информации.
BIN
View File
Binary file not shown.
@@ -0,0 +1,4 @@
---
alwaysApply: true
---
Длинна типа name не должна превышать 12 символов и не должна содержать специальных знаков.
+1
View File
@@ -30,6 +30,7 @@ macro(add_contract_build name)
endmacro()
# Добавляем все контракты
add_contract_build(ledger)
add_contract_build(meet)
add_contract_build(loan)
add_contract_build(wallet)
Binary file not shown.
+1
View File
@@ -6,6 +6,7 @@
#include "src/addaccum.cpp"
#include "src/addcirculate.cpp"
#include "src/addexpense.cpp"
#include "src/accumfee.cpp"
#include "src/addinitial.cpp"
#include "src/authorize.cpp"
#include "src/delfund.cpp"
+5 -2
View File
@@ -44,10 +44,10 @@ class [[eosio::contract(FUND)]] fund : public eosio::contract {
eosio::name type, uint64_t fund_id);
// атомарные транзакции фондового кошелька
// паевый фонд
// паевой фонд
[[eosio::action]] void addcirculate(
eosio::name coopname,
eosio::asset quantity); /// < добавить сумму в паевый фонд
eosio::asset quantity); /// < добавить сумму в паевой фонд
[[eosio::action]] void subcirculate(
eosio::name coopname,
eosio::asset quantity,
@@ -65,6 +65,9 @@ class [[eosio::contract(FUND)]] fund : public eosio::contract {
[[eosio::action]] void addexpense(eosio::name coopname, uint64_t fund_id,
eosio::asset quantity);
// добавить членский взнос на накопительный счет кооператива для дальнейшего управления
[[eosio::action]] void accumfee(eosio::name coopname, eosio::asset quantity);
//счет вступительных взносов
[[eosio::action]] void addinitial(eosio::name coopname,
eosio::asset quantity);
@@ -0,0 +1,23 @@
/**
* @brief Атомарный метод добавления членского взноса на накопительный счет кооператива.
* Минует процесс spreadamount и добавляет средства напрямую на accumulative_expense_account
* для дальнейшего управления кооперативом (распределение по фондам или использование на расходы).
*
* @param coopname Имя кооператива
* @param quantity Сумма членского взноса
*/
[[eosio::action]] void fund::accumfee(eosio::name coopname, eosio::asset quantity) {
name payer = check_auth_and_get_payer_or_fail(contracts_whitelist);
auto cooperative = get_cooperative_or_fail(coopname);
coopwallet_index coopwallet(_fund, coopname.value);
auto wal = coopwallet.find(0);
eosio::check(wal != coopwallet.end(), "Кошелёк кооператива не найден");
// Добавление членского взноса на накопительный счет кооператива
coopwallet.modify(wal, payer, [&](auto &w) {
w.accumulative_expense_account.available += quantity;
});
}
@@ -1,8 +1,8 @@
// атомарные транзакции фондового кошелька
// паевый счет
// паевой счет
[[eosio::action]] void fund::addcirculate(
eosio::name coopname,
eosio::asset quantity) /// < добавить сумму в паевый фонд
eosio::asset quantity) /// < добавить сумму в паевой фонд
{
name payer = check_auth_and_get_payer_or_fail(contracts_whitelist);
@@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.5)
# Проект
project(ledger)
find_package(cdt)
# Добавление контракта
add_contract(ledger ledger ledger.cpp)
target_compile_definitions(ledger PUBLIC IS_TESTNET=${IS_TESTNET})
@@ -0,0 +1,13 @@
#include "ledger.hpp"
#include <ctime>
#include <eosio/transaction.hpp>
#include "src/initialize/init.cpp"
#include "src/common/add.cpp"
#include "src/common/sub.cpp"
#include "src/common/transfer.cpp"
#include "src/writeoff/create.cpp"
#include "src/writeoff/auth.cpp"
#include "src/writeoff/complete.cpp"
#include "src/writeoff/decline.cpp"
#include "src/migrate/migrate.cpp"
@@ -0,0 +1,46 @@
#include <eosio/asset.hpp>
#include <eosio/contract.hpp>
#include <eosio/crypto.hpp>
#include <eosio/eosio.hpp>
#include <eosio/multi_index.hpp>
#include <eosio/system.hpp>
#include <eosio/time.hpp>
#include "../lib/common.hpp"
using namespace eosio;
using namespace Ledger;
/**
* @ingroup public_contracts
* @brief Контракт ledger для управления бухгалтерской книгой кооператива
* @details Обеспечивает ведение счетов согласно стандартам кооперативной отчетности
*/
class [[eosio::contract(LEDGER)]] ledger : public eosio::contract {
public:
ledger(eosio::name receiver, eosio::name code,
eosio::datastream<const char *> ds)
: eosio::contract(receiver, code, ds) {}
void apply(uint64_t receiver, uint64_t code, uint64_t action);
[[eosio::action]] void migrate();
// Инициализация счетов
[[eosio::action]] void init(eosio::name coopname);
// Операции со счетами (common процесс)
[[eosio::action]] void add(eosio::name coopname, uint64_t account_id, eosio::asset quantity, std::string comment);
[[eosio::action]] void sub(eosio::name coopname, uint64_t account_id, eosio::asset quantity, std::string comment);
[[eosio::action]] void transfer(eosio::name coopname, uint64_t from_account_id, uint64_t to_account_id, eosio::asset quantity, std::string comment);
// Операции списания через совет (writeoff процесс)
[[eosio::action]] void create(eosio::name coopname, eosio::name username, uint64_t account_id, eosio::asset quantity, std::string reason, document2 document, checksum256 writeoff_hash);
[[eosio::action]] void auth(eosio::name coopname, checksum256 writeoff_hash);
// Коллбэки от gateway
[[eosio::action]] void complete(eosio::name coopname, checksum256 writeoff_hash);
[[eosio::action]] void decline(eosio::name coopname, checksum256 writeoff_hash, std::string reason);
};
@@ -0,0 +1,26 @@
/**
* @brief Пополнение счета
* @param coopname - имя кооператива
* @param account_id - идентификатор счета
* @param quantity - сумма пополнения
* @param comment - комментарий к операции
*/
[[eosio::action]]
void ledger::add(eosio::name coopname, uint64_t account_id, eosio::asset quantity, std::string comment) {
if (!has_auth(coopname)) {
check_auth_and_get_payer_or_fail(contracts_whitelist);
}
eosio::check(quantity.is_valid(), "Некорректная сумма");
eosio::check(quantity.amount > 0, "Сумма должна быть положительной");
eosio::check(quantity.symbol == _root_govern_symbol, "Некорректный символ валюты");
laccounts_index accounts(_ledger, coopname.value);
auto account_iter = accounts.find(account_id);
eosio::check(account_iter != accounts.end(), "Счет не найден");
// Обновляем баланс счета
accounts.modify(account_iter, coopname, [&](auto& acc) {
acc.allocation += quantity;
});
}
@@ -0,0 +1,31 @@
/**
* @brief Списание со счета
* @param coopname - имя кооператива
* @param account_id - идентификатор счета
* @param quantity - сумма списания
* @param comment - комментарий к операции
*/
[[eosio::action]]
void ledger::sub(eosio::name coopname, uint64_t account_id, eosio::asset quantity, std::string comment) {
if (!has_auth(coopname)) {
check_auth_and_get_payer_or_fail(contracts_whitelist);
}
eosio::check(quantity.is_valid(), "Некорректная сумма");
eosio::check(quantity.amount > 0, "Сумма должна быть положительной");
eosio::check(quantity.symbol == _root_govern_symbol, "Некорректный символ валюты");
laccounts_index accounts(_ledger, coopname.value);
auto account_iter = accounts.find(account_id);
eosio::check(account_iter != accounts.end(), "Счет не найден");
// Проверяем достаточность средств
eosio::check(account_iter->allocation >= quantity, "Недостаточно средств для списания");
// Обновляем баланс счета
accounts.modify(account_iter, coopname, [&](auto& acc) {
acc.allocation -= quantity;
acc.writeoff += quantity;
});
}
@@ -0,0 +1,40 @@
/**
* @brief Перевод средств между счетами
* @param coopname - имя кооператива
* @param from_account_id - идентификатор счета списания
* @param to_account_id - идентификатор счета зачисления
* @param quantity - сумма перевода
* @param comment - комментарий к операции
*/
[[eosio::action]]
void ledger::transfer(eosio::name coopname, uint64_t from_account_id, uint64_t to_account_id, eosio::asset quantity, std::string comment) {
if (!has_auth(coopname)) {
check_auth_and_get_payer_or_fail(contracts_whitelist);
}
eosio::check(quantity.is_valid(), "Некорректная сумма");
eosio::check(quantity.amount > 0, "Сумма должна быть положительной");
eosio::check(quantity.symbol == _root_govern_symbol, "Некорректный символ валюты");
eosio::check(from_account_id != to_account_id, "Счета списания и зачисления должны отличаться");
laccounts_index accounts(_ledger, coopname.value);
auto from_account_iter = accounts.find(from_account_id);
eosio::check(from_account_iter != accounts.end(), "Счет списания не найден");
auto to_account_iter = accounts.find(to_account_id);
eosio::check(to_account_iter != accounts.end(), "Счет зачисления не найден");
// Проверяем достаточность средств
eosio::check(from_account_iter->allocation >= quantity, "Недостаточно средств для перевода");
// Списываем с источника
accounts.modify(from_account_iter, coopname, [&](auto& acc) {
acc.allocation -= quantity;
});
// Зачисляем на получателя
accounts.modify(to_account_iter, coopname, [&](auto& acc) {
acc.allocation += quantity;
});
}
@@ -0,0 +1,32 @@
/**
* @brief Инициализация всех счетов кооператива согласно плану счетов
* @param coopname - имя кооператива
*/
[[eosio::action]]
void ledger::init(eosio::name coopname) {
require_auth(coopname);
// Проверяем что кооператив существует
cooperatives2_index coops(_registrator, _registrator.value);
auto coop_iter = coops.find(coopname.value);
eosio::check(coop_iter != coops.end(), "Кооператив не найден");
laccounts_index accounts(_ledger, coopname.value);
// Проверяем что счета еще не инициализированы
auto existing = accounts.begin();
eosio::check(existing == accounts.end(), "Счета уже инициализированы для данного кооператива");
// Инициализируем все счета из карты с нулевыми значениями
for (const auto& account_data : ACCOUNT_MAP) {
uint64_t id = std::get<0>(account_data);
std::string name = std::get<1>(account_data);
accounts.emplace(coopname, [&](auto& acc) {
acc.id = id;
acc.name = name;
acc.allocation = eosio::asset(0, _root_govern_symbol);
acc.writeoff = eosio::asset(0, _root_govern_symbol);
});
}
}
@@ -0,0 +1,65 @@
/**
* @brief Миграция данных из контракта fund
* Переносит данные из фондов fund в счета ledger
*/
void ledger::migrate(){
require_auth(_ledger);
// Получаем все кооперативы
cooperatives2_index coops(_registrator, _registrator.value);
for (auto coop_iter = coops.begin(); coop_iter != coops.end(); ++coop_iter) {
eosio::name coopname = coop_iter->username;
// Проверяем что счета еще не инициализированы
laccounts_index accounts(_ledger, coopname.value);
auto existing = accounts.begin();
if (existing != accounts.end()) {
continue; // Пропускаем уже инициализированные кооперативы
}
// Инициализируем все счета с нулевыми значениями
for (const auto& account_data : ACCOUNT_MAP) {
uint64_t id = std::get<0>(account_data);
std::string name = std::get<1>(account_data);
accounts.emplace(_ledger, [&](auto& acc) {
acc.id = id;
acc.name = name;
acc.allocation = eosio::asset(0, _root_govern_symbol);
acc.writeoff = eosio::asset(0, _root_govern_symbol);
});
}
// Мигрируем данные из fund контракта
// Читаем кошелек кооператива
coopwallet_index coopwallets(_fund, coopname.value);
auto wallet_iter = coopwallets.find(0); // ID всегда 0
if (wallet_iter != coopwallets.end()) {
// Паевой фонд (circulating_account) -> счет 80
auto account_iter = accounts.find(80);
if (account_iter != accounts.end()) {
accounts.modify(account_iter, _ledger, [&](auto& acc) {
acc.allocation = wallet_iter->circulating_account.available;
});
}
// Вступительные взносы (initial_account) -> счет 801
account_iter = accounts.find(801);
if (account_iter != accounts.end()) {
accounts.modify(account_iter, _ledger, [&](auto& acc) {
acc.allocation = wallet_iter->initial_account.available;
});
}
// Накопительный счет членских взносов (accumulative_expense_account) -> счет 86
account_iter = accounts.find(86);
if (account_iter != accounts.end()) {
accounts.modify(account_iter, _ledger, [&](auto& acc) {
acc.allocation = wallet_iter->accumulative_expense_account.available;
});
}
}
}
}
@@ -0,0 +1,37 @@
/**
* @brief Авторизация операции после положительного решения совета
* @param coopname - имя кооператива
* @param writeoff_hash - хэш операции списания
*/
[[eosio::action]]
void ledger::auth(eosio::name coopname, checksum256 writeoff_hash) {
require_auth(_soviet);
auto writeoff_opt = get_writeoff_by_hash(writeoff_hash);
eosio::check(writeoff_opt.has_value(), "Операция не найдена");
auto writeoff = writeoff_opt.value();
eosio::check(writeoff.coopname == coopname, "Неверный кооператив");
eosio::check(writeoff.status == "pending"_n, "Операция уже обработана");
// Обновляем статус операции
writeoffs_index writeoffs(_ledger, _ledger.value);
auto writeoff_iter = writeoffs.find(writeoff.id);
writeoffs.modify(writeoff_iter, _soviet, [&](auto& w) {
w.status = "approved"_n;
});
// Отправляем запрос на создание исходящего платежа в gateway через интерфейс
Action::send<createoutpay_interface>(
_gateway,
"createoutpay"_n,
_ledger,
coopname,
writeoff.username,
writeoff_hash,
writeoff.quantity,
_ledger, // callback_contract
"complete"_n, // confirm_callback
"decline"_n // decline_callback
);
}
@@ -0,0 +1,33 @@
/**
* @brief Коллбэк при успешном завершении платежа
* @param coopname - имя кооператива
* @param writeoff_hash - хэш операции списания
*/
[[eosio::action]]
void ledger::complete(eosio::name coopname, checksum256 writeoff_hash) {
require_auth(_gateway);
auto writeoff_opt = get_writeoff_by_hash(writeoff_hash);
eosio::check(writeoff_opt.has_value(), "Операция не найдена");
auto writeoff = writeoff_opt.value();
eosio::check(writeoff.coopname == coopname, "Неверный кооператив");
eosio::check(writeoff.status == "approved"_n, "Операция не одобрена");
// Обновляем статус операции
writeoffs_index writeoffs(_ledger, _ledger.value);
auto writeoff_iter = writeoffs.find(writeoff.id);
writeoffs.modify(writeoff_iter, _gateway, [&](auto& w) {
w.status = "paid"_n;
});
// Списываем средства со счета
laccounts_index accounts(_ledger, coopname.value);
auto account_iter = accounts.find(writeoff.account_id);
eosio::check(account_iter != accounts.end(), "Счет не найден");
accounts.modify(account_iter, _gateway, [&](auto& acc) {
acc.allocation -= writeoff.quantity;
acc.writeoff += writeoff.quantity;
});
}
@@ -0,0 +1,68 @@
/**
* @brief Создание заявления в совет для списания средств
* @param coopname - имя кооператива
* @param username - инициатор заявления
* @param account_id - идентификатор счета для списания
* @param quantity - сумма списания
* @param reason - обоснование списания
* @param document - документ обоснования
* @param writeoff_hash - внешний хэш операции списания
*/
[[eosio::action]]
void ledger::create(eosio::name coopname, eosio::name username, uint64_t account_id, eosio::asset quantity, std::string reason, document2 document, checksum256 writeoff_hash) {
require_auth(username);
eosio::check(quantity.is_valid(), "Некорректная сумма");
eosio::check(quantity.amount > 0, "Сумма должна быть положительной");
eosio::check(quantity.symbol == _root_govern_symbol, "Некорректный символ валюты");
eosio::check(reason.length() > 0, "Обоснование должно быть от 1 символа");
// Проверяем что кооператив существует
cooperatives2_index coops(_registrator, _registrator.value);
auto coop_iter = coops.find(coopname.value);
eosio::check(coop_iter != coops.end(), "Кооператив не найден");
// Проверяем что счет существует
laccounts_index accounts(_ledger, coopname.value);
auto account_iter = accounts.find(account_id);
eosio::check(account_iter != accounts.end(), "Счет не найден");
// Проверяем достаточность средств
eosio::check(account_iter -> allocation >= quantity, "Недостаточно средств на счету");
// Проверяем уникальность writeoff_hash
auto existing_writeoff = get_writeoff_by_hash(writeoff_hash);
eosio::check(!existing_writeoff.has_value(), "Операция с таким хэшем уже существует");
// Создаем операцию ожидающую решения совета
auto operation_id = get_global_id_in_scope(_ledger, _ledger, "writeoffs"_n);
writeoffs_index writeoffs(_ledger, _ledger.value);
writeoffs.emplace(username, [&](auto& op) {
op.id = operation_id;
op.coopname = coopname;
op.username = username;
op.account_id = account_id;
op.quantity = quantity;
op.reason = reason;
op.document = document;
op.writeoff_hash = writeoff_hash;
op.status = "pending"_n;
});
// Создаем агенду в совете через Action::send
Action::send<createagenda_interface>(
_soviet,
"createagenda"_n,
username,
coopname,
username,
get_valid_soviet_action("ledgerwthd"_n), // тип решения
writeoff_hash,
_ledger, // callback_contract
"auth"_n, // confirm_callback
"decline"_n, // decline_callback
document,
reason
);
}
@@ -0,0 +1,22 @@
/**
* @brief Коллбэк при отклонении платежа или решения совета
* @param coopname - имя кооператива
* @param writeoff_hash - хэш операции списания
* @param reason - причина отклонения
*/
[[eosio::action]]
void ledger::decline(eosio::name coopname, checksum256 writeoff_hash, std::string reason) {
// Может быть вызван от _soviet (отклонение решения) или _gateway (отклонение платежа)
eosio::check(has_auth(_soviet) || has_auth(_gateway), "Недостаточно прав для отклонения операции");
auto writeoff_opt = get_writeoff_by_hash(writeoff_hash);
eosio::check(writeoff_opt.has_value(), "Операция не найдена");
auto writeoff = writeoff_opt.value();
eosio::check(writeoff.coopname == coopname, "Неверный кооператив");
// Обновляем статус операции
writeoffs_index writeoffs(_ledger, _ledger.value);
auto writeoff_iter = writeoffs.find(writeoff.id);
writeoffs.erase(writeoff_iter);
}
+6 -6
View File
@@ -164,10 +164,10 @@ struct org_data {
std::string announce; ///< Анонс
std::string description; ///< Описание
eosio::asset initial; ///< Вступительный взнос физического лица / ип
eosio::asset minimum; ///< Минимальный паевый взнос физического лица / ип
eosio::asset minimum; ///< Минимальный паевой взнос физического лица / ип
eosio::asset org_initial; ///< Вступительный взнос юридического лица
eosio::asset org_minimum; ///< Минимальный паевый взнос юридического лица
eosio::asset org_minimum; ///< Минимальный паевой взнос юридического лица
};
@@ -192,11 +192,11 @@ struct [[eosio::table, eosio::contract(REGISTRATOR)]] cooperative {
eosio::asset registration; ///< Регистрационный взнос физического лица / ип
eosio::asset initial; ///< Вступительный членский взнос физического лица / ип
eosio::asset minimum; ///< Минимальный паевый взнос физического лица / ип
eosio::asset minimum; ///< Минимальный паевой взнос физического лица / ип
eosio::binary_extension<eosio::asset> org_registration; ///< Регистрационный взнос юридического лица
eosio::binary_extension<eosio::asset> org_initial; ///< Вступительный членский взнос юридического лица
eosio::binary_extension<eosio::asset> org_minimum; ///< Минимальный паевый взнос юридического лица
eosio::binary_extension<eosio::asset> org_minimum; ///< Минимальный паевой взнос юридического лица
eosio::binary_extension<eosio::name> status; ///< Статус процесса подключения
eosio::binary_extension<eosio::time_point_sec> created_at; ///< Дата поступления заявки на подключение (pending | ... | active | blocked)
@@ -303,11 +303,11 @@ struct [[eosio::table, eosio::contract(REGISTRATOR)]] cooperative2 {
eosio::asset registration; ///< Регистрационный взнос физического лица / ип
eosio::asset initial; ///< Вступительный членский взнос физического лица / ип
eosio::asset minimum; ///< Минимальный паевый взнос физического лица / ип
eosio::asset minimum; ///< Минимальный паевой взнос физического лица / ип
eosio::binary_extension<eosio::asset> org_registration; ///< Регистрационный взнос юридического лица
eosio::binary_extension<eosio::asset> org_initial; ///< Вступительный членский взнос юридического лица
eosio::binary_extension<eosio::asset> org_minimum; ///< Минимальный паевый взнос юридического лица
eosio::binary_extension<eosio::asset> org_minimum; ///< Минимальный паевой взнос юридического лица
eosio::binary_extension<eosio::name> status; ///< Статус процесса подключения
eosio::binary_extension<eosio::time_point_sec> created_at; ///< Дата поступления заявки на подключение (pending | ... | active | blocked)
+15
View File
@@ -76,6 +76,21 @@ struct [[eosio::table, eosio::contract(BRANCH)]] coobranch {
bool is_account_in_trusted(const eosio::name& account) const {
return std::find(trusted.begin(), trusted.end(), account) != trusted.end();
}
/**
* @brief Проверяет права доступа пользователя к данному кооперативному участку
* @param username Имя пользователя для проверки
* @return true если пользователь имеет права доступа, false в противном случае
*/
bool is_user_authorized(const eosio::name& username) const {
// Проверяем является ли пользователь доверенным лицом (trustee)
if (trustee == username) {
return true;
}
// Проверяем находится ли пользователь в списке доверенных (trusted)
return is_account_in_trusted(username);
}
};
typedef eosio::multi_index<"branches"_n, coobranch,
+1 -153
View File
@@ -1,157 +1,5 @@
#pragma once
/**
\ingroup public_tables
\brief Параметры заявки на обмен.
*
* Эта структура предоставляет набор данных, которые требуются для создания или обновления заявки на обмен в контракте "marketplace".
*
* @details Следующие параметры определяют заявку:
* - username: Имя пользователя, инициирующего или обновляющего заявку.
* - parent_id: Идентификатор родительской заявки (если есть).
* - coopname: Имя кооператива.
* - units: Количество частей (штук) товара или услуги для обмена.
* - unit_cost: Цена за единицу (штуку) товара или услуги, выраженная в определенном формате asset.
* - data: Содержит дополнительные данные, специфичные для заявки (например, условия обмена).
* - meta: Метаданные, предоставляющие дополнительную информацию о заявке (например, описание товара или условия обмена).
*/
struct exchange_params {
eosio::name username; /*!< Имя пользователя, инициирующего или обновляющего заявку */
uint64_t parent_id; /*!< Идентификатор родительской заявки */
uint64_t program_id; /*!< Идентификатор программы */
eosio::name coopname; /*!< Имя кооператива */
uint64_t units; /*!< Количество частей (штук) товара или услуги */
eosio::asset unit_cost; /*!< Цена за единицу (штуку) товара или услуги */
uint64_t product_lifecycle_secs; /*!< Время жизни продукта, заявляемое поставщиком */
std::optional<document2> document; /*!< Сопутствующий подписанный документ на взнос или возврат взноса */
std::string data; /*!< Дополнительные данные, специфичные для заявки */
std::string meta; /*!< Метаданные о заявке */
};
/**
* @brief Таблица обменов для контракта "marketplace"
* @ingroup public_tables
* Эта таблица используется для хранения информации об обменных заявках в системе.
*
* @param id Идентификатор обмена, используемый как первичный ключ
* @param parent_id Идентификатор родительской заявки (если есть)
* @param type Тип обмена (покупка, продажа и т.д.)
* @param status Статус обмена (например, "опубликовано", "на модерации" и т.д.)
* @param username Имя пользователя, создавшего заявку
* @param token_contract Имя контракта токена
* @param unit_cost Цена за единицу товара в заявке
* @param remain_units Оставшееся количество товара
* @param blocked_units Заблокированное количество товара
* @param delivered_units Количество доставленного товара
* @param data Дополнительные данные, связанные с заявкой
* @param meta Метаданные заявки
*
* Дополнительные индексы по status, type и parent_id позволяют искать заявки по этим полям.
*
* Пример использования:
* @code
* requests_index exchange(_me, coopname.value);
* auto exchange_order = exchange.find(id);
* @endcode
*/
struct [[eosio::table, eosio::contract(MARKETPLACE)]] request {
uint64_t id; /*!< идентификатор обмена */
uint64_t parent_id; /*!< идентификатор родительской заявки */
uint64_t program_id; /*!< идентификатор программы */
eosio::name coopname; /*!< имя аккаунта кооператива */
eosio::name type; /*!< тип обмена */
eosio::name status; /*!< статус обмена */
eosio::name username; /*!< имя аккаунта владельца заявки */
eosio::name parent_username; /*!< имя аккаунта владельца объявления */
eosio::name token_contract; /*!< имя контракта токена */
eosio::asset unit_cost;/*!< себестоимость единицы товара от поставщика */
eosio::asset supplier_amount; /*!< сумма взноса поставщика */
eosio::asset total_cost; /*!< сумма взноса заказчика */
eosio::asset membership_fee; /*!< членский взнос заказчика */
uint64_t remain_units; /*!< оставшееся количество товара */
uint64_t blocked_units; /*!< заблокированное количество товара */
uint64_t delivered_units; /*!< количество доставленного товара */
std::string data; /*!< дополнительные данные */
std::string meta; /*!< метаданные заявки */
eosio::name money_contributor;
eosio::name product_contributor;
document2 contribute_product_statement;
document2 return_product_statement;
uint64_t contribution_product_decision_id;
document2 contribution_product_authorization;
uint64_t return_product_decision_id;
document2 return_product_authorization;
document2 product_contribution_act_validation;
document2 product_contribution_act;
document2 product_recieve_act;
document2 product_recieve_act_validation;
uint64_t product_lifecycle_secs;
uint64_t cancellation_fee; //up to 100
eosio::asset cancellation_fee_amount;
eosio::time_point_sec created_at;
eosio::time_point_sec accepted_at;
eosio::time_point_sec supplied_at;
eosio::time_point_sec delivered_at;
eosio::time_point_sec recieved_at;
eosio::time_point_sec completed_at;
eosio::time_point_sec declined_at;
eosio::time_point_sec disputed_at;
eosio::time_point_sec canceled_at;
// eosio::time_point_sec expired_at;
eosio::time_point_sec warranty_delay_until;
eosio::time_point_sec deadline_for_receipt;
bool is_warranty_return = false;
uint64_t warranty_return_id;
uint64_t primary_key() const { return id; } /*!< return id - primary_key */
uint64_t by_coop() const {return coopname.value;} /*!< кооператив */
uint64_t by_status() const { return status.value; } /*!< индекс по статусу */
uint64_t by_program() const { return program_id; } /*!< индекс по программе */
uint64_t by_type() const { return type.value; } /*!< индекс по типу */
uint64_t by_parent() const { return parent_id; } /*!< индекс по родительскому ID */
uint64_t by_username() const { return username.value;} /*!< индекс по имени аккаунта */
uint64_t by_parent_username() const { return parent_username.value;} /*!< индекс по имени аккаунта владельца объявления */
uint64_t by_created() const { return created_at.sec_since_epoch();}
uint64_t by_completed() const { return completed_at.sec_since_epoch();}
uint64_t by_declined() const { return declined_at.sec_since_epoch();}
uint64_t by_canceled() const { return canceled_at.sec_since_epoch();}
uint64_t by_warranty_id() const { return warranty_return_id;}
// uint64_t by_expired() const { return expired_at.sec_since_epoch();}
};
typedef eosio::multi_index<
"requests"_n, request,
eosio::indexed_by<"bycoop"_n, eosio::const_mem_fun<request, uint64_t, &request::by_coop>>,
eosio::indexed_by<"bystatus"_n, eosio::const_mem_fun<request, uint64_t, &request::by_status>>,
eosio::indexed_by<"bytype"_n, eosio::const_mem_fun<request, uint64_t, &request::by_type>>,
eosio::indexed_by<"byprogram"_n, eosio::const_mem_fun<request, uint64_t, &request::by_program>>,
eosio::indexed_by<"byparent"_n, eosio::const_mem_fun<request, uint64_t, &request::by_parent>>,
eosio::indexed_by<"byusername"_n, eosio::const_mem_fun<request, uint64_t, &request::by_username>>,
eosio::indexed_by<"bypausername"_n, eosio::const_mem_fun<request, uint64_t, &request::by_parent_username>>,
eosio::indexed_by<"bycreated"_n, eosio::const_mem_fun<request, uint64_t, &request::by_created>>,
eosio::indexed_by<"bycompleted"_n, eosio::const_mem_fun<request, uint64_t, &request::by_completed>>,
eosio::indexed_by<"bydeclined"_n, eosio::const_mem_fun<request, uint64_t, &request::by_declined>>,
eosio::indexed_by<"bycanceled"_n, eosio::const_mem_fun<request, uint64_t, &request::by_canceled>>,
eosio::indexed_by<"bywarrantyid"_n, eosio::const_mem_fun<request, uint64_t, &request::by_warranty_id>>
// eosio::indexed_by<"byexpired"_n, eosio::const_mem_fun<request, uint64_t, &request::by_expired>>
>
requests_index; /*!< Тип мультииндекса для таблицы обменов */
// Структуры exchange_params и request перенесены в shared_marketplace.hpp
+3 -1
View File
@@ -3,7 +3,6 @@
#include "utils.hpp"
#include "drafts.hpp"
#include "accounts.hpp"
#include "changes.hpp"
#include "coops.hpp"
#include "counts.hpp"
#include "permissions.hpp"
@@ -17,10 +16,13 @@
#include "shared_actions.hpp"
#include "shared_soviet.hpp"
#include "shared_wallet.hpp"
#include "shared_fund.hpp"
#include "shared_gateway.hpp"
#include "shared_ledger.hpp"
#include "shared_approver.hpp"
#include "shared_registrator.hpp"
#include "shared_loan.hpp"
#include "shared_branch.hpp"
#include "shared_marketplace.hpp"
#include "approves.hpp"
#include "debts.hpp"
+15 -3
View File
@@ -93,7 +93,7 @@ static constexpr eosio::name _result_action = "result"_n;
//program_types
static constexpr eosio::name _wallet_program = "wallet"_n;
static constexpr eosio::name _sosedi_program = "cooplace"_n;
static constexpr eosio::name _marketplace_program = "marketplace"_n;
static constexpr eosio::name _source_program = "source"_n;
static constexpr eosio::name _capital_program = "capital"_n;
@@ -117,11 +117,18 @@ static const std::set<eosio::name> soviet_actions = {
//WALLET
"createwthd"_n, //создать заявление на возврат паевого взноса
//LEDGER
"ledgerwthd"_n, //заявление на списание со счета через ledger
//MARKETPLACE
"authoffs2c"_n, //заявление на взнос имуществом
"authoffc2r"_n, //заявление на возврат паевого взноса имуществом
};
//program_ids
static constexpr uint64_t _wallet_program_id = 1;
static constexpr uint64_t _sosedi_program_id = 2;
static constexpr uint64_t _marketplace_program_id = 2;
static constexpr uint64_t _capital_program_id = 3;
static constexpr int64_t REWARD_SCALE = 100000000; ///< Масштабный коэффициент для вознаграждений (1e8).
@@ -143,7 +150,10 @@ static constexpr int64_t REWARD_SCALE = 100000000; ///< Масштабный к
#define CAPITAL "capital"
#define WALLET "wallet"
#define LOAN "loan"
#define LEDGER "ledger"
#define MEET "meet"
#define LEDGER "ledger"
/**
* @ingroup public_consts
* @{
@@ -164,6 +174,7 @@ static constexpr int64_t REWARD_SCALE = 100000000; ///< Масштабный к
static constexpr eosio::name _branch = "branch"_n;
static constexpr eosio::name _loan = "loan"_n;
static constexpr eosio::name _meet = "meet"_n;
static constexpr eosio::name _ledger = "ledger"_n;
static constexpr eosio::name _power_account = "eosio.power"_n;
static constexpr eosio::name _saving_account = "eosio.saving"_n;
@@ -188,7 +199,8 @@ static constexpr int64_t REWARD_SCALE = 100000000; ///< Масштабный к
"eosio.msig"_n,
"eosio.wrap"_n,
"eosio.power"_n,
"eosio.saving"_n
"eosio.saving"_n,
"ledger"_n
// Добавьте остальные стандартные или пользовательские контракты по необходимости
};
+1 -1
View File
@@ -165,7 +165,7 @@ struct [[eosio::table, eosio::contract(SOVIET)]] participant {
eosio::name status; //accepted | blocked
bool is_initial = true; ///< флаг, указывающий, внесен ли регистрационный взнос.
bool is_minimum = true; ///< флаг, указывающий, внесен ли минимальный паевый взнос.
bool is_minimum = true; ///< флаг, указывающий, внесен ли минимальный паевой взнос.
bool has_vote = true; ///< флаг, указывающий, имеет ли член право голоса.
eosio::binary_extension<eosio::name> type; ///< individual | entrepreneur | organization
+2
View File
@@ -30,6 +30,8 @@ struct document2 {
// Версия стандарта документа
std::string version;
// uint64_t template_id; ///< было бы удобно но этого нет
// Хэши
eosio::checksum256 hash; // общий хэш (doc_hash + meta_hash)
eosio::checksum256 doc_hash; // хэш содержимого документа
+5 -5
View File
@@ -6,7 +6,7 @@
struct [[eosio::table, eosio::contract(SOVIET)]] program {
uint64_t id; /*!< идентификатор обмена */
uint64_t draft_id; ///< Ссылка на шаблон условий
eosio::name program_type; /*!< тип кооперативной программы (wallet | market | ...) */
eosio::name program_type; /*!< тип кооперативной программы (wallet | market | capital) */
eosio::name coopname; /*!< имя аккаунта кооператива */
bool is_active;
@@ -17,9 +17,9 @@ struct [[eosio::table, eosio::contract(SOVIET)]] program {
std::string images;
std::string meta;
eosio::name calculation_type; /*!< тип настройки платежей по программе ( absolute | relative | free ) */
uint64_t membership_percent_fee; /*!< процент комиссии со взноса */
eosio::asset fixed_membership_contribution; /*!< Членский взнос */
eosio::name calculation_type; /*!< LEGACY тип настройки платежей по программе ( absolute | relative | free ) */
uint64_t membership_percent_fee; /*!< LEGACY процент комиссии со взноса */
eosio::asset fixed_membership_contribution; /*!< LEGACY Членский взнос */
eosio::time_point_sec start_at; /*!< Время открытия */
eosio::time_point_sec expired_at; /*!< Временное ограничение */
@@ -120,7 +120,7 @@ struct ProgramInfo {
static const std::map<eosio::name, ProgramInfo> program_map = {
{_wallet_program, {1, 1}}, // program_id = 1, draft_id = 1
{_sosedi_program, {2, 699}}, // program_id = 2, draft_id = 699
{_marketplace_program, {2, 699}}, // program_id = 2, draft_id = 699
{_source_program, {3, 0}}, // program_id = 3, draft_id = 0
{_capital_program, {4, 1000}} // program_id = 4, draft_id = 1000
};
@@ -0,0 +1,63 @@
namespace Fund {
/**
* @brief Добавляет средства в циркулирующий фонд кооператива
* @param contract Контракт-отправитель (обычно _marketplace)
* @param coopname Имя кооператива
* @param amount Сумма для добавления
*/
inline void add_circulating_funds(eosio::name contract, eosio::name coopname, eosio::asset amount) {
action(
permission_level{ contract, "active"_n },
_fund,
"addcirculate"_n,
std::make_tuple(coopname, amount)
).send();
}
/**
* @brief Списывает средства из циркулирующего фонда кооператива
* @param contract Контракт-отправитель (обычно _marketplace)
* @param coopname Имя кооператива
* @param amount Сумма для списания
* @param skip_available_check Пропустить проверку доступных средств
*/
inline void sub_circulating_funds(eosio::name contract, eosio::name coopname, eosio::asset amount, bool skip_available_check = false) {
action(
permission_level{ contract, "active"_n },
_fund,
"subcirculate"_n,
std::make_tuple(coopname, amount, skip_available_check)
).send();
}
/**
* @brief Добавляет средства на счёт вступительных взносов кооператива
* @param contract Контракт-отправитель (обычно _registrator)
* @param coopname Имя кооператива
* @param amount Сумма для добавления
*/
inline void add_initial_funds(eosio::name contract, eosio::name coopname, eosio::asset amount) {
action(
permission_level{ contract, "active"_n },
_fund,
"addinitial"_n,
std::make_tuple(coopname, amount)
).send();
}
// /**
// * @brief Добавляет членский взнос на накопительный счет кооператива для дальнейшего управления
// * @param contract Контракт-отправитель
// * @param coopname Имя кооператива
// * @param amount Сумма членского взноса
// */
// inline void accumulate_fee(eosio::name contract, eosio::name coopname, eosio::asset amount) {
// action(
// permission_level{ contract, "active"_n },
// _fund,
// "accumfee"_n,
// std::make_tuple(coopname, amount)
// ).send();
// }
}
@@ -0,0 +1,252 @@
#pragma once
/**
* @brief Валидные действия ledger для интеграции с другими контрактами
*/
namespace Ledger {
static const std::set<eosio::name> ledger_actions = {
"add"_n, // пополнение счета
"sub"_n, // списание со счета
"transfer"_n, // перевод между счетами
"create"_n, // создание заявления на списание
"auth"_n, // авторизация списания
"complete"_n, // завершение операции
"decline"_n // отклонение операции
};
inline eosio::name get_valid_ledger_action(const eosio::name& action) {
eosio::check(ledger_actions.contains(action), "Недопустимое имя действия ledger");
return action;
}
/**
* @brief Проверка корректности символа валюты для операций ledger
* @param amount - проверяемая сумма
*/
inline void check_ledger_symbol(const eosio::asset& amount) {
eosio::check(amount.symbol == _root_govern_symbol, "Некорректный символ валюты для операций ledger");
}
/**
* @brief Проверка положительности суммы для операций ledger
* @param amount - проверяемая сумма
*/
inline void check_positive_amount(const eosio::asset& amount) {
eosio::check(amount.is_valid(), "Некорректная сумма");
eosio::check(amount.amount > 0, "Сумма должна быть положительной");
}
/**
* @brief Структура бухгалтерского счета
*/
struct [[eosio::table, eosio::contract(LEDGER)]] laccount {
uint64_t id; ///< Идентификатор счета
std::string name; ///< Название счета
eosio::asset allocation; ///< Аллоцированные средства
eosio::asset writeoff; ///< Списанные средства
uint64_t primary_key() const { return id; }
};
/**
* @brief Типы индексов для таблиц ledger
*/
typedef eosio::multi_index<"accounts"_n, laccount> laccounts_index;
/**
* @brief Интерфейс для пополнения счета ledger из других контрактов
* @param coopname - имя кооператива
* @param account_id - ID счета
* @param quantity - сумма пополнения
* @param comment - комментарий
*/
inline void add(eosio::name coopname, uint64_t account_id, eosio::asset quantity, std::string comment) {
eosio::action(
eosio::permission_level{coopname, "active"_n},
_ledger,
"add"_n,
std::make_tuple(coopname, account_id, quantity, comment)
).send();
}
/**
* @brief Интерфейс для списания со счета ledger из других контрактов
* @param coopname - имя кооператива
* @param account_id - ID счета
* @param quantity - сумма списания
* @param comment - комментарий
*/
inline void sub(eosio::name coopname, uint64_t account_id, eosio::asset quantity, std::string comment) {
eosio::action(
eosio::permission_level{coopname, "active"_n},
_ledger,
"sub"_n,
std::make_tuple(coopname, account_id, quantity, comment)
).send();
}
/**
* @brief Интерфейс для перевода между счетами ledger из других контрактов
* @param coopname - имя кооператива
* @param from_account_id - ID счета списания
* @param to_account_id - ID счета зачисления
* @param quantity - сумма перевода
* @param comment - комментарий
*/
inline void transfer(eosio::name coopname, uint64_t from_account_id, uint64_t to_account_id, eosio::asset quantity, std::string comment) {
eosio::action(
eosio::permission_level{coopname, "active"_n},
_ledger,
"transfer"_n,
std::make_tuple(coopname, from_account_id, to_account_id, quantity, comment)
).send();
}
/**
* @brief Структура для операций ожидающих решения совета
*/
struct [[eosio::table, eosio::contract(LEDGER)]] writeoff_op {
uint64_t id; ///< Идентификатор операции
eosio::name coopname; ///< Имя кооператива
eosio::name username; ///< Инициатор операции
uint64_t account_id; ///< Счет для операции
eosio::asset quantity; ///< Сумма операции
std::string reason; ///< Обоснование
document2 document; ///< Документ обоснования
checksum256 writeoff_hash; ///< Хэш операции списания
eosio::name status; ///< Статус: pending, approved, declined, paid
uint64_t primary_key() const { return id; }
uint64_t by_coop() const { return coopname.value; }
checksum256 by_hash() const { return writeoff_hash; }
};
typedef eosio::multi_index<"writeoffs"_n, writeoff_op,
eosio::indexed_by<"bycoop"_n, eosio::const_mem_fun<writeoff_op, uint64_t, &writeoff_op::by_coop>>,
eosio::indexed_by<"byhash"_n, eosio::const_mem_fun<writeoff_op, checksum256, &writeoff_op::by_hash>>
> writeoffs_index;
/**
* @brief Получает операцию списания по хэшу
* @param writeoff_hash Хэш операции списания
* @return std::optional<writeoff_op> - найденная операция или std::nullopt, если её нет
*/
inline std::optional<writeoff_op> get_writeoff_by_hash(const checksum256 &writeoff_hash) {
writeoffs_index writeoffs(_ledger, _ledger.value);
auto hash_idx = writeoffs.get_index<"byhash"_n>();
auto op_iter = hash_idx.find(writeoff_hash);
if (op_iter == hash_idx.end()) {
return std::nullopt;
}
return *op_iter;
}
/**
* @brief Константы счетов для использования в других контрактах
*/
namespace accounts {
// Денежные средства
static constexpr uint64_t CASH = 50; // Касса
static constexpr uint64_t BANK_ACCOUNT = 51; // Расчетный счет
// Расчеты с пайщиками
static constexpr uint64_t MEMBER_SETTLEMENTS = 75; // Расчеты с пайщиками по внесению/возврату паевых взносов
static constexpr uint64_t MEMBER_DEBT = 751; // Задолженность пайщиков по внесению взносов в паевой фонд
static constexpr uint64_t INCOME_ACCRUALS = 752; // Начисление доходов участникам от предпринимательской деятельности ПО (кооперативные выплаты)
// Паевой фонд
static constexpr uint64_t SHARE_FUND = 80; // Паевой фонд (складочный капитал)
// Расчеты с пайщиками (дебиторами и кредиторами)
static constexpr uint64_t MEMBER_FEES = 761; // По членским взносам
static constexpr uint64_t PROPERTY_TRANSFER = 762; // По передаче имущества для некоммерческой деятельности
static constexpr uint64_t OTHER_SETTLEMENTS = 763; // Другие расчеты
// Расчеты по займам
static constexpr uint64_t LOANS_ISSUED = 583; // Расчеты по выданным займам
static constexpr uint64_t LOAN_INTEREST = 911; // Внесение процентов за пользование займами
// Финансовые вложения из средств ПО
static constexpr uint64_t SHARES_AND_STAKES = 581; // Доли, паи и акции в организациях, где участвует ПО
static constexpr uint64_t SECURITIES = 582; // Облигации (государственные ценные бумаги)
// Запасы, затраты, расчеты, собственные средства
static constexpr uint64_t FIXED_ASSETS = 1; // Основные средства
static constexpr uint64_t INTANGIBLE_ASSETS = 4; // Нематериальные активы
static constexpr uint64_t MATERIALS_GOODS = 10; // Материалы, товары
static constexpr uint64_t MAIN_PRODUCTION = 20; // Основное производство
static constexpr uint64_t NON_PROFIT_ACTIVITY = 201; // Некоммерческая деятельность
static constexpr uint64_t GENERAL_EXPENSES = 26; // Общехозяйственные расходы (содержание ПО)
static constexpr uint64_t RESERVES = 63; // Резервы по сомнительным долгам
static constexpr uint64_t LONG_TERM_LOANS = 67; // Расчеты по долгосрочным кредитам и займам
static constexpr uint64_t TAXES_FEES = 68; // Расчеты с бюджетом по налогам и сборам
static constexpr uint64_t SOCIAL_INSURANCE = 69; // Расчеты по социальному страхованию и обеспечению
static constexpr uint64_t SALARY = 70; // Заработная плата
static constexpr uint64_t ACCOUNTABLE_PERSONS = 71; // Расчеты с подотчетными лицами
static constexpr uint64_t ADDITIONAL_CAPITAL = 83; // Добавочный капитал
static constexpr uint64_t FUNDS_PO_1 = 831; // Фонды ПО (вариант пополнения фондов ПО)
static constexpr uint64_t CURRENT_YEAR_PROFIT = 841; // Нераспределенная прибыль (убыток) отчетного года
static constexpr uint64_t PREVIOUS_YEARS_PROFIT = 842; // Нераспределенная прибыль (непокрытый убыток) прошлых лет
static constexpr uint64_t FUNDS_PO_2 = 843; // Фонды ПО (вариант пополнения фондов ПО)
static constexpr uint64_t TARGET_RECEIPTS = 86; // Целевые поступления
static constexpr uint64_t ENTRANCE_FEES = 861; // Вступительные взносы
static constexpr uint64_t RESERVE_FUND = 862; // Резервный фонд
static constexpr uint64_t INDIVISIBLE_FUND = 863; // Неделимый фонд
static constexpr uint64_t ECONOMIC_ACTIVITY_FUND = 864; // Фонд обеспечения хозяйственной деятельности
static constexpr uint64_t MUTUAL_SECURITY_FUND = 865; // Фонд взаимного обеспечения
static constexpr uint64_t DEVELOPMENT_FUND = 866; // Фонд развития потребительской кооперации
static constexpr uint64_t OTHER_INCOME_EXPENSES = 91; // Прочие доходы и расходы
static constexpr uint64_t FUTURE_EXPENSES_RESERVE = 96; // Резерв предстоящих расходов
static constexpr uint64_t FREE_RECEIPT = 981; // Безвозмездное получение имущества
}
/**
* @brief Карта счетов для инициализации бухгалтерской книги
*/
static const std::vector<std::tuple<uint64_t, std::string>> ACCOUNT_MAP = {
{accounts::FIXED_ASSETS, "Основные средства"},
{accounts::INTANGIBLE_ASSETS, "Нематериальные активы"},
{accounts::MATERIALS_GOODS, "Материалы, товары"},
{accounts::MAIN_PRODUCTION, "Основное производство"},
{accounts::NON_PROFIT_ACTIVITY, "Некоммерческая деятельность"},
{accounts::GENERAL_EXPENSES, "Общехозяйственные расходы"},
{accounts::CASH, "Касса"},
{accounts::BANK_ACCOUNT, "Расчетный счет"},
{accounts::RESERVES, "Резервы по сомнительным долгам"},
{accounts::LONG_TERM_LOANS, "Расчеты по долгосрочным кредитам и займам"},
{accounts::TAXES_FEES, "Расчеты с бюджетом по налогам и сборам"},
{accounts::SOCIAL_INSURANCE, "Расчеты по социальному страхованию и обеспечению"},
{accounts::SALARY, "Заработная плата"},
{accounts::ACCOUNTABLE_PERSONS, "Расчеты с подотчетными лицами"},
{accounts::MEMBER_SETTLEMENTS, "Расчеты с пайщиками по внесению/возврату паевых взносов"},
{accounts::SHARE_FUND, "Паевой фонд (складочный капитал)"},
{accounts::ADDITIONAL_CAPITAL, "Добавочный капитал"},
{accounts::TARGET_RECEIPTS, "Целевые поступления"},
{accounts::OTHER_INCOME_EXPENSES, "Прочие доходы и расходы"},
{accounts::FUTURE_EXPENSES_RESERVE, "Резерв предстоящих расходов"},
{accounts::SHARES_AND_STAKES, "Доли, паи и акции в организациях"},
{accounts::SECURITIES, "Облигации (государственные ценные бумаги)"},
{accounts::LOANS_ISSUED, "Расчеты по выданным займам"},
{accounts::MEMBER_DEBT, "Задолженность пайщиков по внесению взносов в паевой фонд"},
{accounts::INCOME_ACCRUALS, "Начисление доходов участникам от предпринимательской деятельности ПО"},
{accounts::MEMBER_FEES, "По членским взносам"},
{accounts::PROPERTY_TRANSFER, "По передаче имущества для некоммерческой деятельности"},
{accounts::OTHER_SETTLEMENTS, "Другие расчеты"},
{accounts::ENTRANCE_FEES, "Вступительные взносы"},
{accounts::FUNDS_PO_1, "Фонды ПО (вариант пополнения фондов ПО)"},
{accounts::CURRENT_YEAR_PROFIT, "Нераспределенная прибыль (убыток) отчетного года"},
{accounts::PREVIOUS_YEARS_PROFIT, "Нераспределенная прибыль (непокрытый убыток) прошлых лет"},
{accounts::FUNDS_PO_2, "Фонды ПО (вариант пополнения фондов ПО)"},
{accounts::RESERVE_FUND, "Резервный фонд"},
{accounts::INDIVISIBLE_FUND, "Неделимый фонд"},
{accounts::ECONOMIC_ACTIVITY_FUND, "Фонд обеспечения хозяйственной деятельности"},
{accounts::MUTUAL_SECURITY_FUND, "Фонд взаимного обеспечения"},
{accounts::DEVELOPMENT_FUND, "Фонд развития потребительской кооперации"},
{accounts::LOAN_INTEREST, "Внесение процентов за пользование займами"},
{accounts::FREE_RECEIPT, "Безвозмездное получение имущества"}
};
}
@@ -0,0 +1,195 @@
#pragma once
// Сигнатуры для callback действий marketplace
#define AUTH_SIGNATURE eosio::name coopname, checksum256 request_hash, document2 authorization
using auth_interface = void(AUTH_SIGNATURE);
namespace Marketplace {
/**
* @brief Сегмент поставки - унифицированная структура для работы с документами
* @ingroup public_tables
*
* Сегмент представляет собой путь поставки от пайщика в кооператив или из кооператива до пайщика
*
* Типы сегментов:
* - "contribute" - сегмент поставки имущества в кооператив
* - "return" - сегмент возврата имущества из кооператива
* - "wreturn" - сегмент возврата по гарантии (от заказчика в кооператив)
* - "wsupply" - сегмент выдачи по гарантии (из кооператива поставщику)
*/
struct [[eosio::table, eosio::contract(MARKETPLACE)]] segment {
uint64_t id; /*!< идентификатор сегмента */
uint64_t request_id; /*!< идентификатор заявки, к которой относится сегмент */
eosio::name type; /*!< тип сегмента: "contribute", "return", "wreturn", "wsupply" */
eosio::name status; /*!< статус сегмента */
document2 convert_in; /*!< заявление на конвертацию из кошелька */
document2 statement; /*!< заявление/документ на взнос или возврат */
uint64_t decision_id; /*!< идентификатор решения */
document2 authorization; /*!< документ авторизации */
document2 act1; /*!< акт приёма-передачи */
document2 act2; /*!< акт приёма-передачи */
document2 convert_out; /*!< заявление на конвертацию в кошелек */
// Документы транспортировки
document2 transport_act_1; /*!< акт товарно-транспортной накладной - передача имущества на транспортировку */
document2 transport_act_2; /*!< акт товарно-транспортной накладной - прием имущества водителем */
document2 transport_act_3; /*!< акт товарно-транспортной накладной - доставка до КУ получателя */
document2 transport_act_4; /*!< акт товарно-транспортной накладной - прием имущества председателем КУ */
eosio::name coopactor; /*!< представитель кооператива, который действует в этом сегменте */
eosio::name username; /*!< пользователь, который действует в этом сегменте */
eosio::name driver_username; /*!< водитель-пайщик, который принимает имущество на транспортировку */
eosio::name receive_from_driver_coopactor; /*!< председатель КУ, который принимает имущество от водителя */
eosio::time_point_sec created_at;
eosio::time_point_sec updated_at;
uint64_t primary_key() const { return id; }
uint64_t by_request() const { return request_id; }
uint64_t by_type() const { return type.value; }
uint64_t by_status() const { return status.value; }
};
typedef eosio::multi_index<
"segments"_n, segment,
eosio::indexed_by<"byrequest"_n, eosio::const_mem_fun<segment, uint64_t, &segment::by_request>>,
eosio::indexed_by<"bytype"_n, eosio::const_mem_fun<segment, uint64_t, &segment::by_type>>,
eosio::indexed_by<"bystatus"_n, eosio::const_mem_fun<segment, uint64_t, &segment::by_status>>
> segments_index;
/**
* @brief Таблица обменов для контракта "marketplace"
* @ingroup public_tables
*/
struct [[eosio::table, eosio::contract(MARKETPLACE)]] request {
uint64_t id; /*!< идентификатор обмена */
checksum256 hash; /*!< хэш заявки */
uint64_t parent_id; /*!< идентификатор родительской заявки */
checksum256 parent_hash; /*!< хэш родительской заявки */
eosio::name coopname; /*!< имя аккаунта кооператива */
eosio::name type; /*!< тип обмена */
eosio::name status; /*!< статус обмена */
eosio::name username; /*!< имя аккаунта владельца заявки */
eosio::name parent_username; /*!< имя аккаунта владельца объявления */
eosio::name braname; /*!< имя кооперативного участка */
eosio::name token_contract; /*!< имя контракта токена */
eosio::asset unit_cost;/*!< себестоимость единицы товара от поставщика */
eosio::asset base_cost; /*!< базовая стоимость заявки */
eosio::asset membership_fee_amount; /*!< членский взнос заказчика */
eosio::asset total_cost; /*!< общая сумма заявки */
uint64_t remaining_units; /*!< оставшееся количество единиц товара */
uint64_t blocked_units; /*!< заблокированное количество единиц товара */
uint64_t supplied_units; /*!< количество доставленных и принятых единиц товара */
std::string meta; /*!< метаданные заявки */
eosio::name money_contributor; /*!< имя аккаунта, который вносит средства */
eosio::name product_contributor; /*!< имя аккаунта, который передаёт товар */
uint64_t product_lifecycle_secs; // жизненный цикл продукта
uint64_t warranty_period_secs; // гарантийный срок в секундах
eosio::asset cancellation_fee_amount; // сумма штрафа за отмену заявки
eosio::time_point_sec warranty_delay_until;
eosio::time_point_sec deadline_for_receipt;
bool is_warranty_return = false;
uint64_t warranty_return_id;
eosio::time_point_sec created_at;
eosio::time_point_sec accepted_at;
eosio::time_point_sec supplied_at;
eosio::time_point_sec delivered_at;
eosio::time_point_sec received_at;
eosio::time_point_sec completed_at;
eosio::time_point_sec declined_at;
eosio::time_point_sec disputed_at;
eosio::time_point_sec canceled_at;
uint64_t primary_key() const { return id; }
uint64_t by_coop() const {return coopname.value;}
uint64_t by_status() const { return status.value; }
uint64_t by_type() const { return type.value; }
uint64_t by_parent() const { return parent_id; }
checksum256 by_hash() const { return hash; }
checksum256 by_parent_hash() const { return parent_hash; }
uint64_t by_username() const { return username.value;}
uint64_t by_parent_username() const { return parent_username.value;}
uint64_t by_created() const { return created_at.sec_since_epoch();}
uint64_t by_completed() const { return completed_at.sec_since_epoch();}
uint64_t by_declined() const { return declined_at.sec_since_epoch();}
uint64_t by_canceled() const { return canceled_at.sec_since_epoch();}
uint64_t by_warranty_id() const { return warranty_return_id;}
};
typedef eosio::multi_index<
"requests"_n, request,
eosio::indexed_by<"bycoop"_n, eosio::const_mem_fun<request, uint64_t, &request::by_coop>>,
eosio::indexed_by<"bystatus"_n, eosio::const_mem_fun<request, uint64_t, &request::by_status>>,
eosio::indexed_by<"bytype"_n, eosio::const_mem_fun<request, uint64_t, &request::by_type>>,
eosio::indexed_by<"byparent"_n, eosio::const_mem_fun<request, uint64_t, &request::by_parent>>,
eosio::indexed_by<"byhash"_n, eosio::const_mem_fun<request, checksum256, &request::by_hash>>,
eosio::indexed_by<"byparenthash"_n, eosio::const_mem_fun<request, checksum256, &request::by_parent_hash>>,
eosio::indexed_by<"byusername"_n, eosio::const_mem_fun<request, uint64_t, &request::by_username>>,
eosio::indexed_by<"bypausername"_n, eosio::const_mem_fun<request, uint64_t, &request::by_parent_username>>,
eosio::indexed_by<"bycreated"_n, eosio::const_mem_fun<request, uint64_t, &request::by_created>>,
eosio::indexed_by<"bycompleted"_n, eosio::const_mem_fun<request, uint64_t, &request::by_completed>>,
eosio::indexed_by<"bydeclined"_n, eosio::const_mem_fun<request, uint64_t, &request::by_declined>>,
eosio::indexed_by<"bycanceled"_n, eosio::const_mem_fun<request, uint64_t, &request::by_canceled>>,
eosio::indexed_by<"bywarrantyid"_n, eosio::const_mem_fun<request, uint64_t, &request::by_warranty_id>>
> requests_index;
static const std::set<eosio::name> marketplace_callback_actions = {
"authoffs2c"_n, // авторизация сегмента contribute для направления OFFER → ORDER
"authoffc2r"_n, // авторизация сегмента return для направления OFFER → ORDER
"authordcont"_n, // авторизация сегмента contribute для направления ORDER → OFFER
"authordret"_n, // авторизация сегмента return для направления ORDER → OFFER
"declineacc"_n, // отклонение принятия заявки
};
inline eosio::name get_valid_marketplace_action(const eosio::name& action) {
eosio::check(marketplace_callback_actions.contains(action), "Недопустимое имя действия marketplace");
return action;
}
// Вспомогательные функции для поиска заявок по хэшу
static std::optional<request> get_request_by_hash(eosio::name coopname, checksum256 request_hash) {
requests_index requests(_marketplace, coopname.value);
auto idx = requests.get_index<"byhash"_n>();
auto req = idx.find(request_hash);
if (req != idx.end()) {
return *req;
}
return std::nullopt;
}
static request get_request_by_hash_or_fail(eosio::name coopname, checksum256 request_hash, const std::string& error_msg = "Заявка не найдена по хэшу") {
auto request_opt = get_request_by_hash(coopname, request_hash);
eosio::check(request_opt.has_value(), error_msg);
return *request_opt;
}
static std::vector<request> get_requests_by_parent_hash(eosio::name coopname, checksum256 parent_hash) {
requests_index requests(_marketplace, coopname.value);
auto idx = requests.get_index<"byparenthash"_n>();
auto lower = idx.lower_bound(parent_hash);
auto upper = idx.upper_bound(parent_hash);
std::vector<request> result;
for (auto itr = lower; itr != upper; ++itr) {
result.push_back(*itr);
}
return result;
}
} // namespace Marketplace
@@ -123,6 +123,33 @@ namespace Wallet {
}
/**
* @brief Добавляет членский взнос в программу для дальнейшего списания
* @param contract Контракт-отправитель
* @param coopname Имя кооператива
* @param username Имя пользователя
* @param program_id ID программы
* @param amount Сумма членского взноса
* @param memo Мемо для операции
*/
inline void add_member_fee(eosio::name contract, eosio::name coopname, eosio::name username, uint64_t program_id, eosio::asset amount, std::string memo) {
// Сначала добавляем в программе для учета
action(
permission_level{ contract, "active"_n },
_soviet,
"addmemberfee"_n,
std::make_tuple(coopname, username, program_id, amount, memo)
).send();
// Затем добавляем на накопительный счет кооператива для дальнейшего управления
action(
permission_level{ contract, "active"_n },
_fund,
"accumfee"_n,
std::make_tuple(coopname, amount)
).send();
}
/**
* @ingroup public_tables
* @brief Таблица `deposits` отслеживает депозиты в контракте WALLET.
+11
View File
@@ -57,4 +57,15 @@ uint64_t generate() {
}
return seed;
}
std::string checksum256_to_hex(const eosio::checksum256& hash) {
auto hash_bytes = hash.extract_as_byte_array();
std::string hash_hex;
for (const auto& byte : hash_bytes) {
char hex_char[3];
sprintf(hex_char, "%02x", byte);
hash_hex += hex_char;
}
return hash_hex;
}
+2 -11
View File
@@ -11,17 +11,8 @@
### Структуры
- `balance`: Описывает баланс пользователя.
- `exchange`: Описывает информацию об обмене.
- `exchange_params`: Параметры обмена, включая имя пользователя, контракт, количество единиц, цену за штуку и дополнительные данные и метаданные.
### Действия
Контракт включает в себя следующие действия:
- **Действия обмена**: `offer`, `order`, `complete`, `decline`, `accept`.
- **Административные действия**: `moderate`, `prohibit`, `unpublish`, `publish`.
- **Действия авторизации**: `authorize`.
- **Прочие действия**: `newid` (используется для возврата информации из контракта), `update`, `addpieces`.
- `requests`: Описывает информацию об обмене.
- `requests_params`: Параметры обмена, включая имя пользователя, контракт, количество единиц, цену за штуку и дополнительные данные и метаданные.
## Использование
@@ -1,36 +1,46 @@
#include "marketplace.hpp"
#include <eosio/transaction.hpp>
#include "src/accept.cpp"
#include "src/addunits.cpp"
#include "src/authorize.cpp"
#include "src/cancel.cpp"
#include "src/change.cpp"
#include "src/complete.cpp"
#include "src/decline.cpp"
#include "src/delivered.cpp"
#include "src/dispute.cpp"
#include "src/moderate.cpp"
#include "src/offer.cpp"
#include "src/order.cpp"
#include "src/prohibit.cpp"
#include "src/publish.cpp"
#include "src/recieve.cpp"
#include "src/recievecnfrm.cpp"
#include "src/supply.cpp"
#include "src/supplycnfrm.cpp"
#include "src/unpublish.cpp"
#include "src/update.cpp"
/**
* @brief Пустой метод регистрации нового идентификатора
* @ingroup public_actions
* Этот метод используется для возврата информации из контракта.
* @param id идентификатор
* @param type тип идентификатора
*/
[[eosio::action]] void marketplace::newid(uint64_t id, eosio::name type) {
require_auth(_marketplace);
};
// Процесс поставки OFFER → ORDER (deliver_on_offer)
#include "src/deliver_on_offer/parentoffer.cpp"
#include "src/deliver_on_offer/childorder.cpp"
#include "src/deliver_on_offer/acceptoff.cpp"
#include "src/deliver_on_offer/authoffs2c.cpp"
#include "src/deliver_on_offer/authoffc2r.cpp"
#include "src/deliver_on_offer/declineacc.cpp"
#include "src/deliver_on_offer/supplyoff.cpp"
#include "src/deliver_on_offer/supplcnfoff.cpp"
#include "src/deliver_on_offer/deliveroff1.cpp"
#include "src/deliver_on_offer/deliveroff2.cpp"
#include "src/deliver_on_offer/deliveroff3.cpp"
#include "src/deliver_on_offer/deliveroff4.cpp"
#include "src/deliver_on_offer/recvoff.cpp"
#include "src/deliver_on_offer/recvcnfoff.cpp"
#include "src/deliver_on_offer/completeoff.cpp"
#include "src/deliver_on_offer/declineoff.cpp"
#include "src/deliver_on_offer/cancel.cpp"
// Процесс поставки ORDER → OFFER (deliver_on_order)
// #include "src/deliver_on_order/parentorder.cpp"
// #include "src/deliver_on_order/childoffer.cpp"
// #include "src/deliver_on_order/acceptord.cpp"
// #include "src/deliver_on_order/authords2c.cpp"
// #include "src/deliver_on_order/authordc2r.cpp"
// #include "src/deliver_on_order/supplyord.cpp"
// #include "src/deliver_on_order/supplcnford.cpp"
// #include "src/deliver_on_order/deliverord.cpp"
// #include "src/deliver_on_order/recvord.cpp"
// #include "src/deliver_on_order/recvcnford.cpp"
// #include "src/deliver_on_order/completeord.cpp"
// #include "src/deliver_on_order/declineord.cpp"
// Диспуты
#include "src/dispute_on_offer/dispute.cpp"
#include "src/dispute_on_offer/wauthorize.cpp"
#include "src/dispute_on_offer/wreturn.cpp"
#include "src/dispute_on_offer/woffer.cpp"
#include "src/dispute_on_offer/waccept.cpp"
[[eosio::action]] void marketplace::migrate(){
@@ -8,6 +8,16 @@
#include "../lib/common.hpp"
using namespace Marketplace;
// Инициализация статических констант
const std::vector<eosio::name> valid_segment_types = {
"s2c"_n, // supplier to coop (поставщик → кооператив)
"c2r"_n, // coop to receiver (кооператив → получатель)
"wreturn"_n, // warranty return (гарантийный возврат)
"wsupply"_n // warranty supply (гарантийная поставка)
};
/**
* \ingroup public_contracts
* @brief Класс `marketplace` предоставляет функционал кооперативного маркетплейса, позволяя пользователям
@@ -20,7 +30,20 @@
* - Модерация и управление публикацией заявок на обмен.
* - Административные функции, такие как создание идентификаторов и авторизация операций.
*
* ## Направления процессов поставки:
*
* ### OFFER → ORDER (deliver_on_offer)
* - Parent: имущественный паевой взнос (поставщик создает предложение)
* - Child: денежный паевой взнос (заказчик создает встречную заявку)
* - При accept заказчик подает: заявление на конвертацию + заявление на возврат паевого взноса
*
* ### ORDER → OFFER (deliver_on_order)
* - Parent: денежный паевой взнос (заказчик создает заказ)
* - Child: имущественный паевой взнос (поставщик создает встречную заявку)
* - При accept поставщик подает: заявление на взнос имуществом + заявление на конвертацию
*
* \note Контракт маркетплейса является центральной точкой экономической активности на платформе.
* \note Каждое направление имеет свой набор методов с префиксами "offer" и "order" соответственно.
*/
class [[eosio::contract(MARKETPLACE)]] marketplace : public eosio::contract {
@@ -29,54 +52,202 @@ public:
eosio::datastream<const char *> ds)
: eosio::contract(receiver, code, ds) {}
void apply(uint64_t receiver, uint64_t code, uint64_t action);
[[eosio::action]] void migrate();
// Утилитарные функции для работы с сегментами
static segment get_segment(eosio::name coopname, uint64_t segment_id) {
segments_index segments(_marketplace, coopname.value);
auto seg = segments.find(segment_id);
eosio::check(seg != segments.end(), "Сегмент не найден");
return *seg;
}
static segment get_segment_by_request_and_type(eosio::name coopname, uint64_t request_id, eosio::name type) {
segments_index segments(_marketplace, coopname.value);
auto idx = segments.get_index<"byrequest"_n>();
auto lower = idx.lower_bound(request_id);
auto upper = idx.upper_bound(request_id);
for (auto itr = lower; itr != upper; ++itr) {
if (itr->type == type) {
return *itr;
}
}
eosio::check(false, "Сегмент не найден для заявки и типа");
return segment{}; // никогда не достигнется
}
static uint64_t create_segment(eosio::name coopname, uint64_t request_id, eosio::name type) {
segments_index segments(_marketplace, coopname.value);
uint64_t id = get_global_id(_marketplace, "segment"_n);
segments.emplace(_marketplace, [&](auto &s) {
s.id = id;
s.request_id = request_id;
s.type = type;
s.status = "created"_n;
s.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
s.updated_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
});
return id;
}
static uint64_t create_segment(eosio::name coopname, uint64_t request_id, eosio::name type, eosio::name username) {
segments_index segments(_marketplace, coopname.value);
uint64_t id = get_global_id(_marketplace, "segment"_n);
segments.emplace(_marketplace, [&](auto &s) {
s.id = id;
s.request_id = request_id;
s.type = type;
s.status = "created"_n;
s.username = username;
s.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
s.updated_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
});
return id;
}
static void update_segment(eosio::name coopname, uint64_t segment_id, std::function<void(segment&)> updater) {
segments_index segments(_marketplace, coopname.value);
auto seg = segments.find(segment_id);
eosio::check(seg != segments.end(), "Сегмент не найден");
segments.modify(seg, _marketplace, [&](auto &s) {
updater(s);
s.updated_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
});
}
static void update_segment_by_request_and_type(eosio::name coopname, uint64_t request_id, eosio::name type, std::function<void(segment&)> updater) {
segments_index segments(_marketplace, coopname.value);
auto idx = segments.get_index<"byrequest"_n>();
auto lower = idx.lower_bound(request_id);
auto upper = idx.upper_bound(request_id);
for (auto itr = lower; itr != upper; ++itr) {
if (itr->type == type) {
idx.modify(itr, _marketplace, [&](auto &s) {
updater(s);
s.updated_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
});
return;
}
}
eosio::check(false, "Сегмент не найден для обновления");
}
static void delete_segments_by_request(eosio::name coopname, uint64_t request_id) {
segments_index segments(_marketplace, coopname.value);
auto idx = segments.get_index<"byrequest"_n>();
auto lower = idx.lower_bound(request_id);
auto upper = idx.upper_bound(request_id);
// Создаем вектор для хранения итераторов, чтобы избежать инвалидации
std::vector<segments_index::const_iterator> to_delete;
for (auto itr = lower; itr != upper; ++itr) {
to_delete.push_back(segments.find(itr->id));
}
// Удаляем все найденные сегменты
for (auto itr : to_delete) {
segments.erase(itr);
}
}
// Функция валидации имени сегмента
static eosio::name valid_segment(const std::string& segment_name) {
eosio::name segment_type(segment_name);
// Проверяем, что сегмент входит в список допустимых
bool is_valid = false;
for (const auto& valid_type : valid_segment_types) {
if (valid_type == segment_type) {
is_valid = true;
break;
}
}
eosio::check(is_valid, "Недопустимый тип сегмента: " + segment_name);
return segment_type;
}
// Проверка корректности единиц товара (инварианты)
static void check_units_invariant(const request& req, const std::string& operation) {
// Проверяем, что количество единиц не отрицательное
eosio::check(req.remaining_units >= 0,
"Инвариант нарушен в операции " + operation + ": remaining_units не может быть отрицательным");
eosio::check(req.blocked_units >= 0,
"Инвариант нарушен в операции " + operation + ": blocked_units не может быть отрицательным");
eosio::check(req.supplied_units >= 0,
"Инвариант нарушен в операции " + operation + ": supplied_units не может быть отрицательным");
}
// ... определение методов контракта ...
// Действия для создания заявок
[[eosio::action]] void parentoffer(eosio::name coopname, eosio::name braname, eosio::name username, checksum256 hash, uint64_t units, eosio::asset unit_cost, uint32_t product_lifecycle_secs, uint32_t warranty_period_secs, eosio::asset membership_fee_amount, eosio::asset cancellation_fee_amount, std::string meta);
[[eosio::action]] void childorder(eosio::name coopname, eosio::name braname, eosio::name username, checksum256 hash, checksum256 parent_hash, uint64_t units, eosio::asset unit_cost, document2 statement, document2 convert_in, std::string meta);
//marketplace.cpp
[[eosio::action]] void newid(uint64_t id, eosio::name type);
//soviet.cpp
[[eosio::action]] void authorize(eosio::name coopname, uint64_t exchange_id, uint64_t contribution_product_decision_id, document2 contribution_product_authorization, uint64_t return_product_decision_id, document2 return_product_authorization);
// [[eosio::action]] void parentorder(eosio::name coopname, eosio::name braname, eosio::name username, checksum256 hash, uint64_t units, eosio::asset unit_cost, eosio::asset membership_fee_amount, eosio::asset cancellation_fee_amount, document2 convert_in, std::string meta);
// [[eosio::action]] void childoffer(eosio::name coopname, eosio::name braname, eosio::name username, checksum256 hash, checksum256 parent_hash, uint64_t units, eosio::asset unit_cost, uint32_t product_lifecycle_secs, document2 document, std::string meta);
static void cancel_parent(eosio::name coopname, eosio::name username, checksum256 request_hash);
static void cancel_child(eosio::name coopname, eosio::name username, checksum256 request_hash);
//change.cpp
[[eosio::action]] void offer(const exchange_params& params);
[[eosio::action]] void order(const exchange_params& params);
// Статические методы для отклонения заявок
static void decline_child_request(eosio::name coopname, const request& child_change);
static void decline_parent_request(eosio::name coopname, const request& parent_change);
static void decline_parent_and_children(eosio::name coopname, const request& parent_change);
static void decline_child_simple(eosio::name coopname, const request& child_change);
static void create(eosio::name type, const exchange_params& params);
static void create_parent(eosio::name type, const exchange_params& params);
static void create_child(eosio::name type, const exchange_params& params);
static void cancel_parent(eosio::name coopname, eosio::name username, uint64_t exchange_id);
static void cancel_child(eosio::name coopname, eosio::name username, uint64_t exchange_id);
// Методы для направления OFFER → ORDER (deliver_on_offer)
[[eosio::action]] void acceptoff(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 convert_out, document2 return_document);
[[eosio::action]] void authoffs2c(eosio::name coopname, checksum256 request_hash, document2 authorization);
[[eosio::action]] void authoffc2r(eosio::name coopname, checksum256 request_hash, document2 authorization);
[[eosio::action]] void declineacc(eosio::name coopname, checksum256 hash, std::string reason);
[[eosio::action]] void supplyoff(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 act1);
[[eosio::action]] void supplcnfoff(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 act2);
[[eosio::action]] void deliveroff1(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 transport_act_1);
[[eosio::action]] void deliveroff2(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 transport_act_2);
[[eosio::action]] void deliveroff3(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 transport_act_3);
[[eosio::action]] void deliveroff4(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 transport_act_4);
[[eosio::action]] void recvoff(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document);
[[eosio::action]] void recvcnfoff(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document);
[[eosio::action]] void completeoff(eosio::name coopname, eosio::name username, checksum256 request_hash);
[[eosio::action]] void declineoff(eosio::name coopname, eosio::name username, checksum256 request_hash, std::string meta);
[[eosio::action]] void accept(eosio::name coopname, eosio::name username, uint64_t exchange_id, document2 document);
[[eosio::action]] void decline(eosio::name coopname, eosio::name username, uint64_t exchange_id, std::string meta);
// Методы для направления ORDER → OFFER (deliver_on_order)
// [[eosio::action]] void acceptord(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 contribution_document, document2 conversion_document);
// [[eosio::action]] void authords2c(eosio::name coopname, checksum256 request_hash, document2 authorization);
// [[eosio::action]] void authordc2r(eosio::name coopname, checksum256 request_hash, document2 authorization);
// [[eosio::action]] void supplyord(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document);
// [[eosio::action]] void supplcnford(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document);
// [[eosio::action]] void deliverord(eosio::name coopname, eosio::name username, checksum256 request_hash);
// [[eosio::action]] void recvord(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document);
// [[eosio::action]] void recvcnford(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document);
// [[eosio::action]] void completeord(eosio::name coopname, eosio::name username, checksum256 request_hash);
// [[eosio::action]] void declineord(eosio::name coopname, eosio::name username, checksum256 request_hash, std::string meta);
[[eosio::action]] void supplycnfrm(eosio::name coopname, eosio::name username, uint64_t exchange_id, document2 document);
[[eosio::action]] void supply(eosio::name coopname, eosio::name username, uint64_t exchange_id, document2 document);
[[eosio::action]] void delivered(eosio::name coopname, eosio::name username, uint64_t exchange_id);
[[eosio::action]] void recieve(eosio::name coopname, eosio::name username, uint64_t exchange_id, document2 document);
[[eosio::action]] void recievecnfrm(eosio::name coopname, eosio::name username, uint64_t exchange_id, document2 document);
[[eosio::action]] void dispute(eosio::name coopname, eosio::name username, uint64_t exchange_id, document2 document);
[[eosio::action]] void complete(eosio::name coopname, eosio::name username, uint64_t exchange_id);
[[eosio::action]] void cancel(eosio::name coopname, eosio::name username, uint64_t exchange_id);
[[eosio::action]] void update(eosio::name coopname, eosio::name username, uint64_t exchange_id, uint64_t remain_units, eosio::asset unit_cost, std::string data, std::string meta);
[[eosio::action]] void addunits(eosio::name coopname, eosio::name username, uint64_t exchange_id, uint64_t units);
//admins.cpp
[[eosio::action]] void moderate(eosio::name coopname, eosio::name username, uint64_t exchange_id, uint64_t cancellation_fee);
[[eosio::action]] void prohibit(eosio::name coopname, eosio::name username, uint64_t exchange_id, std::string meta);
[[eosio::action]] void unpublish(eosio::name coopname, eosio::name username, uint64_t exchange_id);
[[eosio::action]] void publish(eosio::name coopname, eosio::name username, uint64_t exchange_id);
// Диспуты
[[eosio::action]] void dispute(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document);
[[eosio::action]] void cancel(eosio::name coopname, eosio::name username, checksum256 request_hash);
// Методы для работы с диспутом (гарантийный возврат)
[[eosio::action]] void wauthorize(eosio::name coopname, checksum256 request_hash, uint64_t wreturn_decision_id, document2 wreturn_authorization, uint64_t wsupply_decision_id, document2 wsupply_authorization);
[[eosio::action]] void wreturn(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document);
[[eosio::action]] void woffer(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document);
[[eosio::action]] void waccept(eosio::name coopname, eosio::name username, checksum256 request_hash, bool accept, document2 document);
struct [[eosio::table, eosio::contract("marketplace")]] balances : balances_base {};
struct [[eosio::table, eosio::contract("marketplace")]] counts : counts_base {};
};
@@ -1,55 +0,0 @@
/**
\ingroup public_actions
\brief Подтверждение готовности выполнить заявку.
@details Данный метод позволяет пользователю, который получил предложение по своей заявке, подтвердить свою готовность его принять и выполнить. При этом формируется пакет документов, который отправляется в совет на утверждение.
@param username Имя пользователя, подтверждающего готовность выполнить предложение.
@param exchange_id ID предложения, которое следует подтвердить.
@note Авторизация требуется от аккаунта: @p username
*/
[[eosio::action]] void marketplace::accept(eosio::name coopname, eosio::name username, uint64_t exchange_id, document2 document) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Заявка не найдена");
eosio::check(change -> status == "published"_n, "Только заявка в статусе ожидания может быть принята");
auto parent_change = exchange.find(change -> parent_id);
eosio::check(parent_change != exchange.end(), "Родительская заявка не найдена");
eosio::check(parent_change -> username == username, "Недостаточно прав доступа");
eosio::check(parent_change -> remain_units >= change -> remain_units, "Недостаточно объектов для поставки");
// Проверяем подпись документа
verify_document_or_fail(document);
exchange.modify(parent_change, _marketplace, [&](auto &i) {
i.remain_units -= change -> remain_units;
i.supplier_amount = (parent_change -> remain_units - change -> remain_units ) * parent_change -> unit_cost;
i.blocked_units += change -> remain_units;
});
exchange.modify(change, _marketplace, [&](auto &o){
o.status = "accepted"_n;
o.blocked_units += change -> remain_units;
o.remain_units = 0;
o.accepted_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
if (change -> type == "order"_n) {
o.contribute_product_statement = document;
} else if (change -> type == "offer"_n) {
o.return_product_statement = document;
};
});
action(
permission_level{ _marketplace, "active"_n},
_soviet,
_change_action,
std::make_tuple(change -> coopname, username, change -> username, exchange_id, change -> money_contributor, change -> product_contributor)
).send();
}
@@ -1,28 +0,0 @@
/**
\ingroup public_actions
\brief Добавление единиц товара к заявке.
@details Метод позволяет владельцу заявки дополнительно увеличить количество товара, доступное для обмена в рамках указанной заявки.
Используется, когда у продавца появляется дополнительное количество товара, которое он хочет добавить к существующей заявке.
@param username Имя пользователя, инициировавшего добавление.
@param exchange_id Идентификатор заявки, к которой добавляются единицы товара.
@param units Количество новых единиц товара, которые следует добавить к заявке.
@note Авторизация требуется от аккаунта: @p username
*/
[[eosio::action]] void marketplace::addunits(eosio::name coopname, eosio::name username, uint64_t exchange_id, uint64_t units) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change -> username == username, "У вас нет прав на редактирование данной заявки");
eosio::check(change -> parent_id == 0, "Нельзя отредактировать количество единиц во встречной заявке. Отмените её и пересоздайте");
exchange.modify(change, _marketplace, [&](auto &c){
c.remain_units += units;
c.supplier_amount = (change -> remain_units + units) * change -> unit_cost;
});
};
@@ -1,32 +0,0 @@
/**
\ingroup public_actions
\brief Авторизация обмена советом.
@details Метод используется для подтверждения согласия совета на заявленный обмен.
Обычно этот метод вызывается после прохождения определенного процесса голосования или принятия решения советом.
Авторизованный обмен считается утвержденным и может быть выполнен.
@param exchange_id Идентификатор заявки на обмен, которую следует авторизовать.
@note Авторизация требуется от аккаунта: @p _soviet
*/
[[eosio::action]] void marketplace::authorize(eosio::name coopname, uint64_t exchange_id, uint64_t contribution_product_decision_id, document2 contribution_product_authorization, uint64_t return_product_decision_id, document2 return_product_authorization) {
require_auth(_soviet);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Ордер не найден");
exchange.modify(change, _soviet, [&](auto &o) {
o.status = "authorized"_n;
o.contribution_product_decision_id = contribution_product_decision_id;
o.contribution_product_authorization = contribution_product_authorization;
o.return_product_decision_id = return_product_decision_id;
o.return_product_authorization = return_product_authorization;
});
};
@@ -1,27 +0,0 @@
/**
\ingroup public_actions
\brief Отмена заявки и возврат токенов.
@details Позволяет пользователю отменить родительскую или дочернюю заявку, а также обеспечивает возврат токенов владельцу (если применимо). При отмене проверяется наличие заявки и её текущий статус.
@param username Имя пользователя, инициировавшего отмену.
@param exchange_id Идентификатор заявки для отмены.
@note Авторизация требуется от аккаунта: @p username
*/
[[eosio::action]] void marketplace::cancel(eosio::name coopname, eosio::name username, uint64_t exchange_id) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Заявка не найдена");
//TODO перенести под проверку пользователя и вообще сценарий отмены проверить полностью
eosio::check(change -> status != "accepted"_n, "Заявка не может быть отменена сейчас");
if (change -> parent_id == 0) {
marketplace::cancel_parent(coopname, username, exchange_id);
} else {
marketplace::cancel_child(coopname, username, exchange_id);
};
}
@@ -1,325 +0,0 @@
// /**
// * @mainpage Описание системы заявок
// *
// * В системе имеются два типа заявок: order (заказ) и offer (предложение), а также два уровня заявок: родительская (base) и встречная (quote).
// * Используется одна таблица заявок: exchange(_marketplace, _marketplace). Встречная заявка всегда должна быть противополжного типа к родительской.
// *
// * <b>Группы заявок:</b>
// *
// * <b>1. Группа предложений от родителя:</b>
// * - Parent: type == 'offer' && (parent_id == 0) => имущественный паевый взнос
// * - Child: type == 'order' && (parent_id > 0) => денежный паевый взнос
// *
// * <b>2. Группа заказа от родителя:</b>
// * - Parent: type == "order" && (parent_id == 0) => денежный паевый взнос
// * - Child: type == "offer" && (parent_id > 0) => имущественный паевый взнос
// *
// * <b>Обозначения:</b>
// * - order => денежный паевый взнос
// * - offer => имущественный паевый взнос
// */
/**
* @brief Создание заявки на обмен
*
* @param type Тип заявки
* @param params Параметры заявки
*
* Общая функция для создания как родительских, так и дочерних заявок.
*/
void marketplace::create (eosio::name type, const exchange_params& params) {
cooperatives2_index coops(_registrator, _registrator.value);
auto coop = coops.find(params.coopname.value);
eosio::check(coop != coops.end() && coop -> is_coop(), "Кооператив не найден");
eosio::check(params.unit_cost.symbol == coop -> initial.symbol, "Неверный символ токен");
eosio::check(params.units > 0, "Количество единиц в заявке должно быть больше нуля");
eosio::check(params.unit_cost.amount >= 0, "Цена не может быть отрицательной");
if (params.parent_id == 0) {
marketplace::create_parent(type, params);
} else {
marketplace::create_child(type, params);
};
};
/**
* @brief Создание родительской заявки
*
* @param type Тип заявки
* @param params Параметры заявки
*
* Специализированная функция для создания родительской заявки.
*/
void marketplace::create_parent(eosio::name type, const exchange_params& params) {
eosio::check(type == "offer"_n, "В родительском заявке может быть только предложение");
requests_index exchange(_marketplace, params.coopname.value);
uint64_t id = get_global_id(_marketplace, "exchange"_n);
eosio::check(params.parent_id == 0, "Родительская заявка создаётся без указания родителя");
cooperatives2_index coops(_registrator, _registrator.value);
auto coop = coops.find(params.coopname.value);
eosio::check(coop != coops.end(), "Кооператив не найден");
eosio::check(coop -> is_coop() == true, "Организация - не кооператив");
participants_index participants(_soviet, params.coopname.value);
auto participant = participants.find(params.username.value);
eosio::check(participant != participants.end(), "Вы не являетесь членом указанного кооператива");
auto program = get_program_or_fail(params.coopname, params.program_id);
//срок гарантийного возврата должен быть установлен
eosio::check(params.product_lifecycle_secs > 0, "Гарантийный срок возврата для имущества должен быть установлен");
exchange.emplace(_marketplace, [&](auto &i) {
i.id = id;
i.type = type;
i.program_id = params.program_id;
i.username = params.username;
i.coopname = params.coopname;
i.status = "moderation"_n;
i.remain_units = params.units;
i.unit_cost = params.unit_cost;
i.supplier_amount = params.unit_cost * params.units;
i.membership_fee = asset(0, coop -> initial.symbol);
i.total_cost = asset(0, coop -> initial.symbol);
i.product_lifecycle_secs = params.product_lifecycle_secs;
i.data = params.data;
i.meta = params.meta;
i.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
i.cancellation_fee_amount = asset(0, params.unit_cost.symbol);
});
action(
permission_level{ _marketplace, "active"_n},
_marketplace,
"newid"_n,
std::make_tuple(id, type)
).send();
};
/**
* @brief Создание дочерней заявки
*
* @param type Тип заявки
* @param params Параметры заявки
*
* Специализированная функция для создания дочерних заявок, связанных с родительской заявкой.
*/
void marketplace::create_child(eosio::name type, const exchange_params& params) {
eosio::check(type == "order"_n, "В дочерней заявки может быть только заказ");
requests_index exchange(_marketplace, params.coopname.value);
auto parent_change = exchange.find(params.parent_id);
eosio::check(parent_change != exchange.end(), "Заявка не обнаружена");
eosio::check(parent_change -> status == "published"_n, "Заявка не опубликована или не прошла модерацию");
cooperatives2_index coops(_registrator, _registrator.value);
auto coop = coops.find(params.coopname.value);
eosio::check(coop != coops.end(), "Кооператив не найден");
eosio::check(coop -> is_coop() == true, "Организация - не кооператив");
eosio::check(parent_change -> unit_cost.amount == params.unit_cost.amount, "Торги запрещены");
eosio::check(params.parent_id > 0, "Встречная заявка создаётся с указанием родителя");
eosio::check(params.document.has_value(), "Документ должен быть приложен к транзакции");
//проводим проверку подписи документа
verify_document_or_fail(*params.document);
uint64_t id = get_global_id(_marketplace, "exchange"_n);
auto program = get_program_or_fail(params.coopname, params.program_id);
eosio::check(parent_change -> program_id == params.program_id, "Целевые программы должны совпадать");
participants_index participants(_soviet, params.coopname.value);
auto participant = participants.find(params.username.value);
eosio::check(participant != participants.end(), "Вы не являетесь членом указанного кооператива");
uint64_t product_lifecycle_secs = 0;
eosio::asset membership_fee;
eosio::asset supplier_amount = params.unit_cost * params.units;
if (program.calculation_type == "absolute"_n) {
membership_fee = program.fixed_membership_contribution;
} else if (program.calculation_type == "relative"_n) {
membership_fee = supplier_amount * HUNDR_PERCENTS / program.membership_percent_fee;
} else if (program.calculation_type == "free"_n) {
membership_fee = asset(0, _root_govern_symbol);
};
eosio::asset total_cost = params.unit_cost * params.units + membership_fee;
//Специальные проверки
if (type == "offer"_n) {
//родительская заявка должна быть противоположного типа
eosio::check(parent_change -> type == "order"_n, "Неверный тип родительской заявки");
} else if(type == "order"_n) {
//родительская заявка должна быть противоположного типа
eosio::check(parent_change -> type == "offer"_n, "Неверный тип родительской заявки");
std::string memo = "Начало поставки по программе №" + std::to_string(params.program_id) + " с ID: " + std::to_string(id);
//Для блокировки средств необходимо их иметь на ЦПП, т.е. предварительно необходимо сконвертировать их с ЦПП кошелька
action(
permission_level{ _marketplace, "active"_n},
_soviet,
"blockbal"_n,
std::make_tuple(params.coopname, params.username, params.program_id, total_cost, memo)
).send();
}
exchange.emplace(_marketplace, [&](auto &i) {
i.id = id;
i.parent_id = params.parent_id;
i.parent_username = parent_change -> username;
i.type = type;
i.program_id = parent_change -> program_id;
i.coopname = params.coopname;
i.username = params.username;
i.status = "published"_n;
i.remain_units = params.units;
i.unit_cost = params.unit_cost;
i.membership_fee = membership_fee;
i.supplier_amount = supplier_amount;
i.total_cost = total_cost;
// не дублируем информацию
// i.data = params.data;
// i.meta = params.meta;
i.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
i.cancellation_fee_amount = asset(0, params.unit_cost.symbol);
if (type == "order"_n) {
print("on create child order");
i.return_product_statement = *params.document;
i.money_contributor = params.username;
i.product_contributor = parent_change -> username;
i.product_lifecycle_secs = parent_change -> product_lifecycle_secs;
} else if (type == "offer"_n) {
print("on create child offer");
i.contribute_product_statement = *params.document;
i.money_contributor = parent_change -> username;
i.product_contributor = params.username;
i.product_lifecycle_secs = params.product_lifecycle_secs;
};
});
action(
permission_level{ _marketplace, "active"_n},
_marketplace,
"newid"_n,
std::make_tuple(id, type)
).send();
};
/**
* @brief Отмена родительской заявки.
*
* Вызывается из `cancel`, если заявка является родительской.
* Выполняется проверка, что заявка не имеет заблокированных единиц товара, и удаляется из хранилища.
*
* @param username Имя пользователя, осуществляющего отмену заявки.
* @param exchange_id ID родительской заявки, которую нужно отменить.
*/
void marketplace::cancel_parent(eosio::name coopname, eosio::name username, uint64_t exchange_id) {
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
//Удаление, если заблокированных объектов на поставке - нет.
eosio::check(change -> remain_units + change -> blocked_units == 0, "Заявка не может быть отменена из-за наличия заблокированных единиц товара");
exchange.erase(change);
};
/**
* @brief Отмена дочерней заявки.
*
* Вызывается из `cancel`, если заявка является дочерней.
* Обновляет количество оставшихся и заблокированных единиц товара в родительской заявке и удаляет дочернюю заявку из хранилища.
* В зависимости от статуса и типа заявки возможен возврат токенов "покупателю".
*
* @param username Имя пользователя, осуществляющего отмену заявки.
* @param exchange_id ID дочерней заявки, которую нужно отменить.
*/
void marketplace::cancel_child(eosio::name coopname, eosio::name username, uint64_t exchange_id) {
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
auto parent_change = exchange.find(change -> parent_id);
eosio::asset quantity = change -> unit_cost * change -> blocked_units;
// оповещаем совет об отмене и разблокируем средства
if (change -> type == "order"_n) {
std::string memo = "Отмена поставки по программе №" + std::to_string(change -> program_id) + " с ID: " + std::to_string(change -> id);
action(
permission_level{ _marketplace, "active"_n},
_soviet,
"unblockbal"_n,
std::make_tuple(coopname, change -> money_contributor, change -> program_id, change -> total_cost, memo)
).send();
};
if (change -> status == "authorized"_n) {
//возвращаем единицы товара в родительскую заявку
exchange.modify(parent_change, _marketplace, [&](auto &e) {
e.remain_units += change -> blocked_units;
e.blocked_units -= change -> blocked_units;
e.supplier_amount += change -> supplier_amount;
});
exchange.modify(change, _marketplace, [&](auto &c){
c.status = "canceled"_n;
c.canceled_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
});
//удаляем дочернюю заявку
// exchange.erase(change);
} else if (change -> status == "published"_n) {
exchange.modify(change, _marketplace, [&](auto &c){
c.status = "canceled"_n;
c.canceled_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
});
//удаляем дочернюю заявку
// exchange.erase(change);
} else {
//TODO здесь должно быть допустимо, но для каждого статуса по-своему
eosio::check(false, "Заявка находится в недопустимом статусе для отмены");
}
}
@@ -1,91 +0,0 @@
/**
\ingroup public_actions
\brief Подписание акта о приёме-передаче имущества.
* @details После успешного получения товара, получатель подписывает акт о приёме-передаче, что свидетельствует о юридическом завершении сделки. Этот акт делает пакет документов по данной сделке полным. После проведения ряда проверок, обновляются статусы и количество объектов в основной заявке и предложении. Если все объекты основной заявки обработаны, заявка удаляется из публикации. В зависимости от типа предложения, может осуществляться перевод токенов.
* @param username Имя пользователя-получателя товара.
* @param exchange_id ID предложения, под которым следует подписать акт.
* @note Авторизация требуется от аккаунта: @p username
*/
[[eosio::action]] void marketplace::complete(eosio::name coopname, eosio::name username, uint64_t exchange_id) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Заявка не найдена");
eosio::check(change -> parent_id > 0, "У указанной заявки нет встречной заявки");
auto parent_change = exchange.find(change -> parent_id);
eosio::check(parent_change != exchange.end(), "Родительская заявка не найдена");
// eosio::check(change -> username == username, "Вы не можете подтвердить исполнение заявки");
eosio::check(change -> status == "recieved2"_n, "Заявка находится в неверном статусе для утверждения обмена");
eosio::check(change -> warranty_delay_until.sec_since_epoch() < eosio::current_time_point().sec_since_epoch(), "Время гарантийной задержки еще не истекло");
exchange.modify(parent_change, _marketplace, [&](auto &i) {
i.delivered_units += change -> blocked_units;
i.blocked_units -= change -> blocked_units;
if (i.blocked_units + parent_change -> remain_units == 0) {
i.status = "unpublished"_n; //снимаем родительскую заявку с публикации, если она исполнена
};
});
exchange.modify(change, _marketplace, [&](auto &o) {
o.status = "completed"_n;
o.delivered_units += change -> blocked_units;
o.blocked_units = 0;
o.completed_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
});
auto program = get_program_or_fail(coopname, change -> program_id);
std::string memo = "Успешное завершение поставки имущества по программе №" + std::to_string(change -> program_id) + " с ID: " + std::to_string(change -> id);
//Заказчику разблокируем баланс ЦПП кооплекса и списываем его
action(
permission_level{ _marketplace, "active"_n},
_soviet,
"unblockbal"_n,
std::make_tuple(coopname, change -> money_contributor, change -> program_id, change -> total_cost, memo)
).send();
action(
permission_level{ _marketplace, "active"_n},
_soviet,
"subbal"_n,
std::make_tuple(coopname, change -> money_contributor, change -> program_id, change -> total_cost, false, memo)
).send();
//Поставщику разблокируем средства в программе кооплейса
action(
permission_level{ _marketplace, "active"_n},
_soviet,
"unblockbal"_n,
std::make_tuple(coopname, change -> product_contributor, change -> program_id, change -> supplier_amount, memo)
).send();
if (change -> membership_fee.amount > 0) {
//распределяем членские взносы по фондам
action(
permission_level{ _marketplace, "active"_n},
_fund,
"spreadamount"_n,
std::make_tuple(coopname, change -> membership_fee)
).send();
// отмечаем распределение членских взносов в программе и кошельке пользователя
action(
permission_level{ _marketplace, "active"_n},
_soviet,
"addmemberfee"_n,
std::make_tuple(coopname, change -> money_contributor, change -> program_id, change -> membership_fee, memo)
).send();
}
}
@@ -1,48 +0,0 @@
/**
\ingroup public_actions
\brief Отказ от предложения.
* @details Этот метод позволяет пользователю отклонить предложение, представленное к его заявке.
* Выполняются следующие проверки:
* - Существование предложения с указанным ID.
* - Существование основной заявки.
* - Предложение находится в статусе "ожидание".
*
* Если отклонено предложение к заявке типа "order", осуществляется возврат токенов пользователю, которому были заблокированы токены при создании предложения.
*
* @param username Имя пользователя, отклоняющего предложение.
* @param exchange_id ID предложения, которое следует отклонить.
* @param meta Дополнительные метаданные, связанные с отказом.
*
* @note Авторизация требуется от аккаунта: @p username
*/
[[eosio::action]] void marketplace::decline(eosio::name coopname, eosio::name username, uint64_t exchange_id, std::string meta) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
auto parent_change = exchange.find(change -> parent_id);
eosio::check(change != exchange.end(), "Заявка не найдена");
eosio::check(parent_change != exchange.end(), "Родительская заявка не найдена");
eosio::check(change -> status == "published"_n, "Только заявка в статусе ожидания может быть отклонена");
exchange.modify(change, coopname, [&](auto &o){
o.status = "declined"_n;
o.declined_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
o.meta = meta;
});
if (change -> type == "order"_n) {
std::string memo = "Отказ в поставке по программе №" + std::to_string(change -> program_id) + " с ID: " + std::to_string(change -> id);
action(
permission_level{ _marketplace, "active"_n},
_soviet,
"unblockbal"_n,
std::make_tuple(coopname, change -> money_contributor, change -> program_id, change -> total_cost, memo)
).send();
};
}
@@ -0,0 +1,116 @@
/**
\ingroup public_actions
\brief Принятие встречной заявки поставщиком в направлении deliver_on_offer (acceptoff).
@details Поставщик принимает встречную заявку (child order) на поставку имущества, создает сегменты и подготавливает заявления для совета.
Процесс: OFFER (parent) → ORDER (child)
- Parent: имущественный паевой взнос (поставщик)
- Child: денежный паевой взнос (заказчик)
@param coopname Имя кооператива
@param username Имя поставщика
@param request_hash Хэш встречной заявки (order)
@param conversion_document Заявление на конвертацию
@param product_contribution_statement Заявление на возврат
@note Авторизация требуется от аккаунта: @p coopname
**/
[[eosio::action]] void marketplace::acceptoff(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 convert_out, document2 product_contribution_statement) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "active"_n, "Только активная заявка может быть принята");
eosio::check(change.type == "order"_n, "Метод offeraccept применим только к встречным заявкам типа order");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть типа offer");
eosio::check(parent_change.username == username, "Недостаточно прав доступа");
eosio::check(parent_change.remaining_units >= change.remaining_units, "Недостаточно объектов для поставки");
// Проверяем подписи документов
verify_document_or_fail(convert_out);
verify_document_or_fail(product_contribution_statement);
// Обновляем родительскую заявку (offer)
auto parent_itr = requests.find(parent_change.id);
eosio::check(parent_itr != requests.end(), "Родительская заявка не найдена для обновления");
requests.modify(parent_itr, _marketplace, [&](auto &i) {
i.remaining_units -= change.remaining_units;
i.base_cost = (parent_change.remaining_units - change.remaining_units) * parent_change.unit_cost;
i.blocked_units += change.remaining_units;
});
// Проверяем инварианты родительской заявки
auto updated_parent = requests.find(parent_change.id);
marketplace::check_units_invariant(*updated_parent, "offeraccept_parent_update");
// Обновляем встречную заявку (order)
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &o){
o.status = "accepted"_n;
o.blocked_units += change.remaining_units;
o.remaining_units = 0;
o.accepted_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "offeraccept_child_update");
// Сохраняем заявление на конвертацию в contribute сегменте
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.convert_out = convert_out;
s.statement = product_contribution_statement;
s.status = "statement"_n;
});
// Извлекаем заявления из сегментов
auto s2c_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"));
auto c2r_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("c2r"));
// Используем хэш встречной заявки как идентификатор пакета решений
checksum256 agenda_hash = change.hash;
// Отправляем заявление на конвертацию (из contribute сегмента) в совет
Action::send<createagenda_interface>(
_soviet,
"createagenda"_n,
_marketplace,
coopname,
username,
get_valid_soviet_action("authoffs2c"_n),
agenda_hash,
_marketplace,
Marketplace::get_valid_marketplace_action("authoffs2c"_n),
"declineacc"_n,
s2c_segment.statement,
std::string("")
);
// Отправляем заявление на возврат (из return сегмента) в совет
Action::send<createagenda_interface>(
_soviet,
"createagenda"_n,
_marketplace,
coopname,
username,
get_valid_soviet_action("authoffc2r"_n),
agenda_hash,
_marketplace,
Marketplace::get_valid_marketplace_action("authoffc2r"_n),
"declineacc"_n,
c2r_segment.statement,
std::string("")
);
}
@@ -0,0 +1,50 @@
/**
\ingroup public_actions
\brief Авторизация сегмента "return" для направления deliver_on_offer (authoffc2r).
@details Метод используется для подтверждения согласия совета на заявление на возврат в направлении OFFER → ORDER.
Заявка становится авторизованной только когда оба сегмента (contribute и return) получили авторизацию.
@param coopname Имя кооператива
@param request_hash Хэш встречной заявки на обмен (order)
@param authorization Документ авторизации
@note Авторизация требуется от аккаунта: @p _soviet
*/
[[eosio::action]] void marketplace::authoffc2r(eosio::name coopname, checksum256 request_hash, document2 authorization) {
require_auth(_soviet);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.type == "order"_n, "Метод authoffc2r применим только к заявкам типа order");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть типа offer");
// Обновляем return сегмент
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("c2r"), [&](auto &s) {
s.authorization = authorization;
s.status = "authorized"_n;
});
// Проверяем статусы обоих сегментов
auto s2c_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"));
auto c2r_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("c2r"));
// Если оба сегмента авторизованы, авторизуем заявку
if (s2c_segment.status == "authorized"_n && c2r_segment.status == "authorized"_n) {
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _soviet, [&](auto &o) {
o.status = "authorized"_n;
});
}
}
@@ -0,0 +1,49 @@
/**
\ingroup public_actions
\brief Авторизация сегмента "contribute" для направления deliver_on_offer (authoffs2c).
@details Метод используется для подтверждения согласия совета на заявление на конвертацию в направлении OFFER → ORDER.
Заявка становится авторизованной только когда оба сегмента (contribute и return) получили авторизацию.
@param coopname Имя кооператива
@param request_hash Хэш встречной заявки на обмен (order)
@param authorization Документ авторизации
@note Авторизация требуется от аккаунта: @p _soviet
*/
[[eosio::action]] void marketplace::authoffs2c(eosio::name coopname, checksum256 request_hash, document2 authorization) {
require_auth(_soviet);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.type == "order"_n, "Метод authoffs2c применим только к заявкам типа order");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть типа offer");
// Обновляем contribute сегмент
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.authorization = authorization;
s.status = "authorized"_n;
});
// Проверяем статусы обоих сегментов
auto s2c_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"));
auto c2r_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("c2r"));
// Если оба сегмента авторизованы, авторизуем заявку
if (s2c_segment.status == "authorized"_n && c2r_segment.status == "authorized"_n) {
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _soviet, [&](auto &o) {
o.status = "authorized"_n;
});
}
}
@@ -0,0 +1,207 @@
/**
\ingroup public_actions
\brief Отмена заявки с учетом комиссии за отмену.
@details Позволяет пользователю отменить родительскую или дочернюю заявку с учетом следующих правил:
- Поставщик не может отменить заявку после поставки имущества (статусы supplied1+)
- Заказчик может отменить с вычетом комиссии за отмену (cancellation_fee_amount)
- При отмене возвращаются заблокированные единицы и средства
@param coopname Имя кооператива
@param username Имя пользователя, инициирующего отмену
@param request_hash Хэш заявки для отмены
@note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::cancel(eosio::name coopname, eosio::name username, checksum256 request_hash) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
// Проверяем права доступа на отмену заявки
eosio::check(change.username == username, "Только владелец заявки может её отменить");
// Проверяем что заявка может быть отменена
eosio::check(change.status != "completed"_n && change.status != "canceled"_n && change.status != "declined"_n,
"Заявка уже завершена или отменена");
if (change.parent_id == 0) {
marketplace::cancel_parent(coopname, username, request_hash);
} else {
marketplace::cancel_child(coopname, username, request_hash);
}
}
/**
* @brief Отмена родительской заявки.
*/
void marketplace::cancel_parent(eosio::name coopname, eosio::name username, checksum256 request_hash) {
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
// Родительская заявка может быть отменена только если нет заблокированных единиц
eosio::check(change.blocked_units == 0, "Заявка не может быть отменена из-за наличия заблокированных единиц товара");
// Для родительского заказа разблокируем средства
if (change.type == "order"_n && change.total_cost.amount > 0) {
eosio::asset refund_amount = change.total_cost;
// Если заказчик отменяет заявку, вычитаем комиссию за отмену
if (change.money_contributor == username && change.cancellation_fee_amount.amount > 0) {
refund_amount -= change.cancellation_fee_amount;
}
std::string memo = "Отмена родительской заявки по программе №" + std::to_string(_marketplace_program_id) + " с ID: " + std::to_string(change.id);
if (refund_amount.amount > 0) {
// Списываем средства с ЦПП маркетплейса
Wallet::sub_blocked_funds(_marketplace, coopname, change.money_contributor, refund_amount, _marketplace_program, memo);
// Начисляем средства на ЦПП Цифровой Кошелёк
Wallet::add_available_funds(_marketplace, coopname, change.money_contributor, refund_amount, _wallet_program, memo);
}
// Если была комиссия, списываем её и направляем в фонд членских взносов
if (change.money_contributor == username && change.cancellation_fee_amount.amount > 0) {
std::string fee_memo = "Комиссия за отмену родительской заявки по программе №" + std::to_string(_marketplace_program_id) + " с ID: " + std::to_string(change.id);
// Списываем комиссию с ЦПП маркетплейса
Wallet::sub_blocked_funds(_marketplace, coopname, change.money_contributor, change.cancellation_fee_amount, _marketplace_program, fee_memo);
// Направляем комиссию в фонд членских взносов
Wallet::add_member_fee(_marketplace, coopname, username, _marketplace_program_id, change.cancellation_fee_amount, fee_memo);
}
}
// Удаляем сегменты родительской заявки
marketplace::delete_segments_by_request(coopname, change.id);
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Заявка не найдена для удаления");
requests.erase(change_itr);
}
/**
* @brief Отмена дочерней заявки с учетом комиссии за отмену.
*/
void marketplace::cancel_child(eosio::name coopname, eosio::name username, checksum256 request_hash) {
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
// Проверяем права на отмену в зависимости от типа заявки и статуса
if (change.type == "offer"_n) {
// Поставщик не может отменить заявку после поставки имущества
eosio::check(change.status != "supplied1"_n && change.status != "supplied2"_n &&
change.status != "delivered"_n && change.status != "received1"_n &&
change.status != "received2"_n,
"Поставщик не может отменить заявку после поставки имущества");
}
// Возвращаем заблокированные единицы в родительскую заявку
if (change.blocked_units > 0) {
auto parent_itr = requests.find(parent_change.id);
eosio::check(parent_itr != requests.end(), "Родительская заявка не найдена для обновления");
requests.modify(parent_itr, _marketplace, [&](auto &e) {
e.remaining_units += change.blocked_units;
e.blocked_units -= change.blocked_units;
e.base_cost = e.remaining_units * e.unit_cost;
});
}
// Разблокируем средства если это заказ
if (change.type == "order"_n && change.total_cost.amount > 0) {
eosio::asset refund_amount = change.total_cost;
// Если заказчик отменяет заявку, вычитаем комиссию за отмену
if (change.money_contributor == username && change.cancellation_fee_amount.amount > 0) {
refund_amount -= change.cancellation_fee_amount;
}
std::string memo = "Отмена заявки по программе №" + std::to_string(_marketplace_program_id) + " с ID: " + std::to_string(change.id);
if (refund_amount.amount > 0) {
// Списываем средства с ЦПП маркетплейса
Wallet::sub_blocked_funds(_marketplace, coopname, change.money_contributor, refund_amount, _marketplace_program, memo);
// Начисляем средства на ЦПП Цифровой Кошелёк
Wallet::add_available_funds(_marketplace, coopname, change.money_contributor, refund_amount, _wallet_program, memo);
}
// Если была комиссия, списываем её и направляем в фонд членских взносов
if (change.money_contributor == username && change.cancellation_fee_amount.amount > 0) {
std::string fee_memo = "Комиссия за отмену заявки по программе №" + std::to_string(_marketplace_program_id) + " с ID: " + std::to_string(change.id);
// Списываем комиссию с ЦПП маркетплейса
Wallet::sub_blocked_funds(_marketplace, coopname, change.money_contributor, change.cancellation_fee_amount, _marketplace_program, fee_memo);
// Направляем комиссию в фонд членских взносов
Wallet::add_member_fee(_marketplace, coopname, username, _marketplace_program_id, change.cancellation_fee_amount, fee_memo);
}
}
// Удаляем сегменты дочерней заявки
marketplace::delete_segments_by_request(coopname, change.id);
// Проверяем равенство количества единиц для определения стратегии удаления
auto updated_parent = requests.find(parent_change.id);
bool units_equal = false;
if (updated_parent != requests.end()) {
units_equal = (change.remaining_units == updated_parent->remaining_units);
}
// Удаляем дочернюю заявку
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Заявка не найдена для удаления");
requests.erase(change_itr);
// Если количество единиц равно - удаляем также родительскую заявку
if (units_equal && updated_parent != requests.end()) {
// Для родительского заказа тоже разблокируем средства если есть
if (parent_change.type == "order"_n && parent_change.total_cost.amount > 0 && parent_change.parent_id == 0) {
eosio::asset parent_refund_amount = parent_change.total_cost;
// Если заказчик отменяет заявку, вычитаем комиссию за отмену
if (parent_change.money_contributor == username && parent_change.cancellation_fee_amount.amount > 0) {
parent_refund_amount -= parent_change.cancellation_fee_amount;
}
std::string parent_memo = "Отмена родительской заявки при отмене дочерней с равными единицами по программе №" + std::to_string(_marketplace_program_id) + " с ID: " + std::to_string(parent_change.id);
if (parent_refund_amount.amount > 0) {
// Списываем средства с ЦПП маркетплейса
Wallet::sub_blocked_funds(_marketplace, coopname, parent_change.money_contributor, parent_refund_amount, _marketplace_program, parent_memo);
// Начисляем средства на ЦПП Цифровой Кошелёк
Wallet::add_available_funds(_marketplace, coopname, parent_change.money_contributor, parent_refund_amount, _wallet_program, parent_memo);
}
// Если была комиссия, списываем её и направляем в фонд членских взносов
if (parent_change.money_contributor == username && parent_change.cancellation_fee_amount.amount > 0) {
std::string parent_fee_memo = "Комиссия за отмену родительской заявки при отмене дочерней по программе №" + std::to_string(_marketplace_program_id) + " с ID: " + std::to_string(parent_change.id);
// Списываем комиссию с ЦПП маркетплейса
Wallet::sub_blocked_funds(_marketplace, coopname, parent_change.money_contributor, parent_change.cancellation_fee_amount, _marketplace_program, parent_fee_memo);
// Направляем комиссию в фонд членских взносов
Wallet::add_member_fee(_marketplace, coopname, username, _marketplace_program_id, parent_change.cancellation_fee_amount, parent_fee_memo);
}
}
// Удаляем сегменты родительской заявки
marketplace::delete_segments_by_request(coopname, parent_change.id);
requests.erase(updated_parent);
}
}
@@ -0,0 +1,110 @@
/**
\ingroup public_actions
\brief Создать дочернюю заявку на денежный паевой взнос (в ответ на родительское предложение).
*
* Данный метод позволяет пользователю создать встречную заявку на денежный паевой взнос.
*
* @param coopname Имя кооператива
* @param username Имя пользователя, инициирующего заявку
* @param hash Хэш заявки (уникальный идентификатор)
* @param parent_hash Хэш родительской заявки
* @param units Количество частей (штук) товара или услуги
* @param unit_cost Цена за единицу (штуку) товара или услуги
* @param braname Имя кооперативного участка откуда будет забираться товар
* @param statement Заявление на возврат паевого взноса имуществом
* @param convert_in Заявление на конвертацию из кошелька в маркетплейс
* @param meta Метаданные о заявке
*
* @note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::childorder(eosio::name coopname, eosio::name braname, eosio::name username, checksum256 hash, checksum256 parent_hash, uint64_t units, eosio::asset unit_cost, document2 statement, document2 convert_in, std::string meta) {
require_auth(coopname);
// Проверяем, что заявка с таким хэшем не существует
auto existing_request = get_request_by_hash(coopname, hash);
eosio::check(!existing_request.has_value(), "Заявка с таким хэшем уже существует");
auto coop = get_cooperative_or_fail(coopname);
eosio::check(unit_cost.symbol == coop.initial.symbol, "Неверный символ токена");
eosio::check(units > 0, "Количество единиц в заявке должно быть больше нуля");
eosio::check(unit_cost.amount >= 0, "Цена не может быть отрицательной");
// Проверяем существование кооперативного участка
get_branch_or_fail(coopname, braname);
// Находим родительскую заявку по хэшу
auto parent_change = get_request_by_hash_or_fail(coopname, parent_hash, "Родительская заявка не найдена");
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть предложением");
eosio::check(parent_change.unit_cost.amount == unit_cost.amount, "Торги запрещены. Стоимость единицы не совпадает с родительской заявкой");
//проводим проверку подписи документа
verify_document_or_fail(statement);
verify_document_or_fail(convert_in);
// Используем константу marketplace program_id
auto program = get_program_or_fail(coopname, _marketplace_program_id);
// Проверяем, что пользователь является пайщиком кооператива
get_participant_or_fail(coopname, username);
// Берем членский взнос и комиссию за отмену из родительского предложения
eosio::asset membership_fee_amount = parent_change.membership_fee_amount;
eosio::asset cancellation_fee_amount = parent_change.cancellation_fee_amount;
eosio::asset base_cost = unit_cost * units;
eosio::asset total_cost = base_cost + membership_fee_amount;
// Проверяем что комиссия за отмену не превышает общую стоимость
eosio::check(cancellation_fee_amount <= total_cost, "Комиссия за отмену не может превышать общую стоимость заказа");
requests_index requests(_marketplace, coopname.value);
uint64_t request_id = get_global_id(_marketplace, "requests"_n);
std::string memo = "Начало поставки по программе №" + std::to_string(_marketplace_program_id) + " с ID: " + std::to_string(request_id);
requests.emplace(_marketplace, [&](auto &i) {
i.id = request_id;
i.hash = hash;
i.parent_id = parent_change.id;
i.parent_hash = parent_hash;
i.parent_username = parent_change.username;
i.type = "order"_n;
i.coopname = coopname;
i.username = username;
i.status = "active"_n;
i.remaining_units = units;
i.unit_cost = unit_cost;
i.membership_fee_amount = membership_fee_amount;
i.base_cost = base_cost;
i.total_cost = total_cost;
i.product_lifecycle_secs = parent_change.product_lifecycle_secs;
i.warranty_period_secs = parent_change.warranty_period_secs;
i.money_contributor = username;
i.product_contributor = parent_change.username;
i.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
i.cancellation_fee_amount = cancellation_fee_amount;
i.braname = braname;
});
// Создаем сегмент для дочерней заявки поставки из кооператива - заказчику
marketplace::create_segment(coopname, request_id, marketplace::valid_segment("c2r"), username);
// Сохраняем заявление на конвертацию и заявление на возврат в contribute сегменте
marketplace::update_segment_by_request_and_type(coopname, request_id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.convert_in = convert_in;
s.statement = statement;
s.status = "statement"_n;
});
//Для блокировки средств необходимо их иметь на ЦПП, т.е. предварительно необходимо сконвертировать их с ЦПП кошелька
// Списываем средства с ЦПП цифрового кошелька
std::string convert_memo = "Конвертация средств из ЦПП 'Цифровой Кошелёк' в ЦПП 'Маркетплейс' для заказа №" + std::to_string(request_id);
Wallet::sub_available_funds(_marketplace, coopname, username, total_cost, _wallet_program, convert_memo);
// Добавляем средства на ЦПП маркетплейса
Wallet::add_available_funds(_marketplace, coopname, username, total_cost, _marketplace_program, convert_memo);
// Блокируем средства на программе маркетплейса
Wallet::block_funds(_marketplace, coopname, username, total_cost, _marketplace_program, memo);
};
@@ -0,0 +1,59 @@
/**
\ingroup public_actions
\brief Завершение поставки в направлении deliver_on_offer (completeoff).
@details После истечения гарантийной задержки завершает поставку и переводит единицы из заблокированных в доставленные.
Процесс: OFFER (parent) → ORDER (child)
- Завершение происходит после истечения warranty_delay
@param coopname Имя кооператива
@param username Имя пользователя
@param request_hash Хэш встречной заявки (order)
@note Авторизация требуется от аккаунта: @p coopname
**/
[[eosio::action]] void marketplace::completeoff(eosio::name coopname, eosio::name username, checksum256 request_hash) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.type == "order"_n, "Метод completeoff применим только к заявкам типа order");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть типа offer");
eosio::check(change.status == "received2"_n, "Завершение возможно только в статусе received2");
eosio::check(change.warranty_delay_until.sec_since_epoch() < eosio::current_time_point().sec_since_epoch(), "Время гарантийной задержки еще не истекло");
// Удаляем родительскую заявку (offer)
auto parent_itr = requests.find(parent_change.id);
requests.erase(parent_itr);
// Удаляем встречную заявку (order)
auto change_itr = requests.find(change.id);
requests.erase(change_itr);
// Обрабатываем членские взносы (увеличиваем счёт кооператива согласно ТЗ)
if (change.membership_fee_amount.amount > 0) {
std::string member_fee_memo = "Членский взнос по программе №" + std::to_string(_marketplace_program_id) + " с идентификатором: " + checksum256_to_hex(change.hash);
Wallet::add_member_fee(_marketplace, coopname, change.product_contributor, _marketplace_program_id, change.membership_fee_amount, member_fee_memo);
}
// Удаляем все сегменты, связанные с заявкой
marketplace::delete_segments_by_request(coopname, change.id);
std::string memo = "Возврат паевого взноса по программе №" + std::to_string(_marketplace_program_id) + " с идентификатором: " + checksum256_to_hex(change.hash) + "в ЦПП 'Цифровой Кошелёк'";
// Поставщику списываем средства в программе маркетплейса (base_cost согласно ТЗ)
Wallet::sub_blocked_funds(_marketplace, coopname, change.product_contributor, change.base_cost, _marketplace_program, memo);
// Поставщику начисляем средства в ЦПП Цифровой Кошелек (base_cost согласно ТЗ)
Wallet::add_available_funds(_marketplace, coopname, change.product_contributor, change.base_cost, _wallet_program, memo);
}
@@ -0,0 +1,195 @@
/**
\ingroup public_actions
\brief Отклонение принятия заявки советом (declineacc).
@details Данный метод вызывается советом когда заявление на конвертацию, возврат или взнос отклоняется.
При отклонении отменяется дочерняя заявка, а родительская отменяется только если количество единиц товара равно.
@param coopname Имя кооператива
@param request_hash Хэш заявки, которая должна быть отменена
@param reason Причина отклонения
@note Авторизация требуется от аккаунта: @p _soviet
*/
[[eosio::action]] void marketplace::declineacc(eosio::name coopname, checksum256 request_hash, std::string reason) {
require_auth(_soviet);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
// Если заявка не найдена, ничего не делаем (возможно уже удалена)
if (!change_opt.has_value()) {
print("Заявка уже удалена или не найдена. Кооператив: ", coopname, ", причина: ", reason);
return;
}
auto change = change_opt.value();
// Логирование отклонения
print("Отклонение заявления советом. Заявка ID: ", change.id, ", причина: ", reason);
// Если это дочерняя заявка, отменяем её и проверяем нужно ли отменять родительскую
if (change.parent_id > 0) {
decline_child_request(coopname, change);
} else {
// Если это родительская заявка, ищем все дочерние и отменяем их
decline_parent_and_children(coopname, change);
}
}
/**
* @brief Отмена дочерней заявки при отклонении с проверкой равенства единиц
*/
void marketplace::decline_child_request(eosio::name coopname, const request& child_change) {
requests_index requests(_marketplace, coopname.value);
// Проверяем, что заявка все еще существует
auto child_itr = requests.find(child_change.id);
if (child_itr == requests.end()) {
return; // Заявка уже удалена
}
// Разблокируем средства если это заказ
if (child_change.type == "order"_n && child_change.total_cost.amount > 0) {
std::string memo = "Отклонение заявления советом по программе №" + std::to_string(_marketplace_program_id) + " с ID: " + std::to_string(child_change.id);
// Списываем средства с ЦПП маркетплейса
Wallet::sub_blocked_funds(_marketplace, coopname, child_change.money_contributor, child_change.total_cost, _marketplace_program, memo);
// Начисляем средства на ЦПП Цифровой Кошелёк
Wallet::add_available_funds(_marketplace, coopname, child_change.money_contributor, child_change.total_cost, _wallet_program, memo);
}
// Получаем родительскую заявку
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, child_change.parent_hash);
bool should_cancel_parent = false;
if (parent_change_opt.has_value()) {
auto parent_itr = requests.find(parent_change_opt.value().id);
if (parent_itr != requests.end()) {
// Возвращаем заблокированные единицы в родительскую заявку
if (child_change.blocked_units > 0) {
requests.modify(parent_itr, _marketplace, [&](auto &e) {
e.remaining_units += child_change.blocked_units;
e.blocked_units -= child_change.blocked_units;
e.base_cost = e.remaining_units * e.unit_cost;
});
// Получаем обновленную родительскую заявку для проверки единиц
auto updated_parent = requests.find(parent_change_opt.value().id);
// Проверяем равенство количества единиц для определения стратегии удаления
// Сравниваем remaining_units дочерней заявки с remaining_units обновленной родительской
bool units_equal = (child_change.remaining_units == updated_parent->remaining_units);
if (units_equal) {
// Если количество единиц равно - отмечаем для удаления родительской заявки
should_cancel_parent = true;
}
} else {
// Если заблокированных единиц нет, сравниваем remaining_units как есть
bool units_equal = (child_change.remaining_units == parent_itr->remaining_units);
if (units_equal) {
should_cancel_parent = true;
}
}
}
}
// Удаляем сегменты дочерней заявки
marketplace::delete_segments_by_request(coopname, child_change.id);
// Удаляем дочернюю заявку
requests.erase(child_itr);
// Удаляем родительскую заявку если количество единиц равно
if (should_cancel_parent && parent_change_opt.has_value()) {
// Используем метод decline_parent_request для консистентности
decline_parent_request(coopname, parent_change_opt.value());
}
}
/**
* @brief Отмена родительской заявки при отклонении
*/
void marketplace::decline_parent_request(eosio::name coopname, const request& parent_change) {
requests_index requests(_marketplace, coopname.value);
// Проверяем, что заявка все еще существует
auto parent_itr = requests.find(parent_change.id);
if (parent_itr == requests.end()) {
return; // Заявка уже удалена
}
// Разблокируем средства если это родительский заказ
if (parent_change.type == "order"_n && parent_change.total_cost.amount > 0 && parent_change.parent_id == 0) {
std::string memo = "Отклонение заявления советом по программе №" + std::to_string(_marketplace_program_id) + " с идентификатором: " + checksum256_to_hex(parent_change.hash);
// Списываем средства с ЦПП маркетплейса
Wallet::sub_blocked_funds(_marketplace, coopname, parent_change.username, parent_change.total_cost, _marketplace_program, memo);
// Начисляем средства на ЦПП Цифровой Кошелёк
Wallet::add_available_funds(_marketplace, coopname, parent_change.username, parent_change.total_cost, _wallet_program, memo);
}
// Удаляем родительскую заявку
requests.erase(parent_itr);
}
/**
* @brief Отмена родительской заявки и всех её дочерних заявок
*/
void marketplace::decline_parent_and_children(eosio::name coopname, const request& parent_change) {
requests_index requests(_marketplace, coopname.value);
// Ищем все дочерние заявки и отменяем их
auto parent_index = requests.get_index<"byparent"_n>();
auto children_begin = parent_index.lower_bound(parent_change.id);
auto children_end = parent_index.upper_bound(parent_change.id);
// Сначала отменяем всех детей
std::vector<uint64_t> children_to_cancel;
for (auto itr = children_begin; itr != children_end; ++itr) {
children_to_cancel.push_back(itr->id);
}
for (uint64_t child_id : children_to_cancel) {
auto child_itr = requests.find(child_id);
if (child_itr != requests.end()) {
// Для дочерних заявок используем упрощенную логику без проверки единиц
// так как мы отменяем родительскую заявку в любом случае
decline_child_simple(coopname, *child_itr);
}
}
// Затем отменяем родительскую заявку
decline_parent_request(coopname, parent_change);
}
/**
* @brief Упрощенная отмена дочерней заявки без проверки единиц (используется при отмене родительской)
*/
void marketplace::decline_child_simple(eosio::name coopname, const request& child_change) {
requests_index requests(_marketplace, coopname.value);
// Проверяем, что заявка все еще существует
auto child_itr = requests.find(child_change.id);
if (child_itr == requests.end()) {
return; // Заявка уже удалена
}
// Разблокируем средства если это заказ
if (child_change.type == "order"_n && child_change.total_cost.amount > 0) {
std::string memo = "Отклонение заявления советом по программе №" + std::to_string(_marketplace_program_id) + " с идентификатором: " + checksum256_to_hex(child_change.hash);
// Списываем средства с ЦПП маркетплейса
Wallet::sub_blocked_funds(_marketplace, coopname, child_change.money_contributor, child_change.total_cost, _marketplace_program, memo);
// Начисляем средства на ЦПП Цифровой Кошелёк
Wallet::add_available_funds(_marketplace, coopname, child_change.money_contributor, child_change.total_cost, _wallet_program, memo);
}
// Удаляем сегменты дочерней заявки
marketplace::delete_segments_by_request(coopname, child_change.id);
// Удаляем дочернюю заявку
requests.erase(child_itr);
}
@@ -0,0 +1,80 @@
/**
\ingroup public_actions
\brief Отклонение встречной заявки в направлении deliver_on_offer (declineoff).
@details Позволяет поставщику отклонить встречную заявку (child order) на поставку имущества.
Процесс: OFFER (parent) → ORDER (child)
- Поставщик отклоняет встречную заявку
@param coopname Имя кооператива
@param username Имя поставщика
@param request_hash Хэш встречной заявки (order)
@param meta Причина отклонения
@note Авторизация требуется от аккаунта: @p coopname
**/
[[eosio::action]] void marketplace::declineoff(eosio::name coopname, eosio::name username, checksum256 request_hash, std::string meta) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.type == "order"_n, "Метод offerdecline применим только к заявкам типа order");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть типа offer");
eosio::check(change.status == "active"_n, "Только активная заявка может быть отклонена");
// Если заявка была принята (blocked_units > 0), возвращаем единицы товара в родительскую заявку
if (change.blocked_units > 0) {
auto parent_itr = requests.find(parent_change.id);
eosio::check(parent_itr != requests.end(), "Родительская заявка не найдена для обновления");
requests.modify(parent_itr, _marketplace, [&](auto &p) {
p.remaining_units += change.blocked_units;
p.blocked_units -= change.blocked_units;
p.base_cost = p.remaining_units * p.unit_cost;
});
// Проверяем инварианты родительской заявки
auto updated_parent = requests.find(parent_change.id);
marketplace::check_units_invariant(*updated_parent, "offerdecline_parent_update");
}
// Разблокируем средства заказчика
if (change.total_cost.amount > 0) {
std::string memo = "Отклонение встречной заявки поставщиком по программе №" + std::to_string(_marketplace_program_id) + " с ID: " + std::to_string(change.id);
// Списываем средства с ЦПП маркетплейса
Wallet::sub_blocked_funds(_marketplace, coopname, change.money_contributor, change.total_cost, _marketplace_program, memo);
// Начисляем средства на ЦПП Цифровой Кошелёк
Wallet::add_available_funds(_marketplace, coopname, change.money_contributor, change.total_cost, _wallet_program, memo);
}
// Удаляем сегменты встречной заявки
marketplace::delete_segments_by_request(coopname, change.id);
// Проверяем равенство количества единиц для определения стратегии удаления
auto updated_parent = requests.find(parent_change.id);
bool units_equal = false;
if (updated_parent != requests.end()) {
units_equal = (change.remaining_units == updated_parent->remaining_units);
}
// Удаляем встречную заявку
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для удаления");
requests.erase(change_itr);
// Если количество единиц равно - удаляем также родительскую заявку
if (units_equal && updated_parent != requests.end()) {
requests.erase(updated_parent);
}
}
@@ -0,0 +1,60 @@
/**
\ingroup public_actions
\brief Начало транспортировки имущества в направлении deliver_on_offer (deliveroff1).
@details Председатель или уполномоченный КУ передает имущество на транспортировку.
Процесс: OFFER (parent) → ORDER (child)
- Этап 1 из 4: Передача имущества на транспортировку
@param coopname Имя кооператива
@param username Имя председателя или уполномоченного КУ
@param request_hash Хэш встречной заявки (order)
@param transport_act_1 Акт передачи имущества на транспортировку
@note Авторизация требуется от аккаунта: @p coopname
**/
[[eosio::action]] void marketplace::deliveroff1(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 transport_act_1) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "supplied2"_n, "Только поставка с подтверждением может быть отправлена на транспортировку");
eosio::check(change.type == "order"_n, "Метод deliveroff1 применим только к заявкам типа order");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть типа offer");
auto branch = get_branch_or_fail(coopname, change.braname);
//Проверяем права доступа на КУ (председатель или доверенное лицо)
eosio::check(branch.is_user_authorized(username), "Недостаточно прав доступа для передачи имущества на транспортировку");
// Проверяем подпись документа
verify_document_or_fail(transport_act_1, { username });
// Обновляем статус встречной заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &o) {
o.status = "delivered1"_n;
o.supplied_units = o.blocked_units;
});
// Сохраняем акт передачи на транспортировку в сегменте s2c
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.transport_act_1 = transport_act_1;
s.coopactor = username; // Представитель кооператива, который передал имущество на транспортировку
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "deliveroff1_child_update");
}
@@ -0,0 +1,57 @@
/**
\ingroup public_actions
\brief Прием имущества водителем для транспортировки в направлении deliver_on_offer (deliveroff2).
@details Водитель (пайщик) принимает имущество в транспортировку.
Процесс: OFFER (parent) → ORDER (child)
- Этап 2 из 4: Прием имущества водителем
@param coopname Имя кооператива
@param username Имя водителя (пайщик)
@param request_hash Хэш встречной заявки (order)
@param transport_act_2 Акт приема имущества водителем
@note Авторизация требуется от аккаунта: @p coopname
**/
[[eosio::action]] void marketplace::deliveroff2(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 transport_act_2) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "delivered1"_n, "Прием в транспортировку возможен только в статусе delivered1");
eosio::check(change.type == "order"_n, "Метод deliveroff2 применим только к заявкам типа order");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть типа offer");
// Проверяем, что пользователь является пайщиком кооператива
get_participant_or_fail(coopname, username);
// Проверяем подпись документа
verify_document_or_fail(transport_act_2, { username });
// Обновляем статус встречной заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &o) {
o.status = "delivered2"_n;
});
// Сохраняем акт приема водителем в сегменте s2c
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.transport_act_2 = transport_act_2;
s.driver_username = username; // Водитель, который принял имущество
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "deliveroff2_child_update");
}
@@ -0,0 +1,57 @@
/**
\ingroup public_actions
\brief Доставка имущества до КУ водителем в направлении deliver_on_offer (deliveroff3).
@details Водитель (пайщик) доставляет имущество до кооперативного участка получателя.
Процесс: OFFER (parent) → ORDER (child)
- Этап 3 из 4: Доставка до КУ водителем
@param coopname Имя кооператива
@param username Имя водителя (пайщик)
@param request_hash Хэш встречной заявки (order)
@param transport_act_3 Акт доставки до КУ
@note Авторизация требуется от аккаунта: @p coopname
**/
[[eosio::action]] void marketplace::deliveroff3(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 transport_act_3) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "delivered2"_n, "Доставка до КУ возможна только в статусе delivered2");
eosio::check(change.type == "order"_n, "Метод deliveroff3 применим только к заявкам типа order");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть типа offer");
// Проверяем, что пользователь является пайщиком кооператива
get_participant_or_fail(coopname, username);
// Проверяем подпись документа
verify_document_or_fail(transport_act_3, { username });
// Обновляем статус встречной заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &o) {
o.status = "delivered3"_n;
o.delivered_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
});
// Сохраняем акт доставки до КУ в сегменте s2c
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.transport_act_3 = transport_act_3;
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "deliveroff3_child_update");
}
@@ -0,0 +1,60 @@
/**
\ingroup public_actions
\brief Прием имущества председателем КУ в направлении deliver_on_offer (deliveroff4).
@details Председатель кооперативного участка принимает доставленное имущество.
Процесс: OFFER (parent) → ORDER (child)
- Этап 4 из 4: Прием имущества председателем КУ
@param coopname Имя кооператива
@param username Имя председателя КУ
@param request_hash Хэш встречной заявки (order)
@param transport_act_4 Акт приема имущества председателем КУ
@note Авторизация требуется от аккаунта: @p coopname
**/
[[eosio::action]] void marketplace::deliveroff4(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 transport_act_4) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "delivered3"_n, "Прием имущества председателем возможен только в статусе delivered3");
eosio::check(change.type == "order"_n, "Метод deliveroff4 применим только к заявкам типа order");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть типа offer");
auto branch = get_branch_or_fail(coopname, change.braname);
//Проверяем права доступа на КУ (председатель или доверенное лицо)
eosio::check(branch.is_user_authorized(username), "Недостаточно прав доступа для приема имущества");
// Проверяем подпись документа
verify_document_or_fail(transport_act_4, { username });
// Обновляем статус встречной заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &o) {
o.status = "delivered4"_n;
o.deadline_for_receipt = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch() + 3 * 24 * 60 * 60); // 3 дня на получение
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "deliveroff4_child_update");
// Сохраняем акт приема председателем КУ в сегменте s2c
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.transport_act_4 = transport_act_4;
s.receive_from_driver_coopactor = username; // Председатель КУ, который принял имущество
});
}
@@ -0,0 +1,83 @@
/**
\ingroup public_actions
\brief Создать родительскую заявку на имущественный паевой взнос.
*
* Данный метод позволяет пользователю создать родительскую заявку на имущественный паевой взнос.
*
* @param coopname Имя кооператива
* @param username Имя пользователя, инициирующего заявку
* @param hash Хэш заявки (уникальный идентификатор)
* @param units Количество частей (штук) товара или услуги
* @param unit_cost Цена за единицу (штуку) товара или услуги
* @param product_lifecycle_secs Время жизни продукта, заявляемое поставщиком
* @param warranty_period_secs Гарантийный срок в секундах
* @param membership_fee_amount Сумма членского взноса
* @param cancellation_fee_amount Сумма комиссии за отмену заявки
* @param braname Имя кооперативного участка куда будет поставляться товар
* @param meta Метаданные о заявке
*
* @note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::parentoffer(eosio::name coopname, eosio::name braname, eosio::name username, checksum256 hash, uint64_t units, eosio::asset unit_cost, uint32_t product_lifecycle_secs, uint32_t warranty_period_secs, eosio::asset membership_fee_amount, eosio::asset cancellation_fee_amount, std::string meta) {
require_auth(coopname);
// Проверяем, что заявка с таким хэшем не существует
auto existing_request = get_request_by_hash(coopname, hash);
eosio::check(!existing_request.has_value(), "Заявка с таким хэшем уже существует");
// Проверяем, что символ токена совпадает с символом токена кооператива
auto coop = get_cooperative_or_fail(coopname);
eosio::check(unit_cost.symbol == coop.initial.symbol, "Неверный символ токена");
eosio::check(membership_fee_amount.symbol == coop.initial.symbol, "Неверный символ токена для членского взноса");
eosio::check(cancellation_fee_amount.symbol == coop.initial.symbol, "Неверный символ токена для комиссии отмены");
eosio::check(units > 0, "Количество единиц в заявке должно быть больше нуля");
eosio::check(unit_cost.amount >= 0, "Цена не может быть отрицательной");
eosio::check(membership_fee_amount.amount >= 0, "Членский взнос не может быть отрицательным");
eosio::check(cancellation_fee_amount.amount >= 0, "Комиссия за отмену не может быть отрицательной");
// Проверяем, что пользователь является пайщиком кооператива
get_participant_or_fail(coopname, username);
// Проверяем существование кооперативного участка
get_branch_or_fail(coopname, braname);
// Проверяем существование программы маркетплейса
auto program = get_program_or_fail(coopname, _marketplace_program_id);
// Гарантийный срок возврата должен быть установлен для предложений
eosio::check(product_lifecycle_secs > 0, "Гарантийный срок возврата для имущества должен быть установлен");
eosio::check(warranty_period_secs > 0, "Гарантийный срок должен быть больше нуля");
// Для предложения рассчитываем общую стоимость включая членский взнос
eosio::asset base_cost = unit_cost * units;
eosio::asset total_cost = base_cost + membership_fee_amount;
// Проверяем что комиссия за отмену не превышает общую стоимость
eosio::check(cancellation_fee_amount <= total_cost, "Комиссия за отмену не может превышать общую стоимость предложения");
requests_index requests(_marketplace, coopname.value);
uint64_t request_id = get_global_id(_marketplace, "requests"_n);
requests.emplace(_marketplace, [&](auto &i) {
i.id = request_id;
i.hash = hash;
i.type = "offer"_n;
i.username = username;
i.coopname = coopname;
i.status = "active"_n;
i.remaining_units = units;
i.unit_cost = unit_cost;
i.base_cost = base_cost;
i.membership_fee_amount = membership_fee_amount;
i.total_cost = total_cost;
i.product_lifecycle_secs = product_lifecycle_secs;
i.warranty_period_secs = warranty_period_secs;
i.meta = meta;
i.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
i.cancellation_fee_amount = cancellation_fee_amount;
i.braname = braname;
});
// Создаем сегмент для родительской заявки поставки от пайщика в кооператив
marketplace::create_segment(coopname, request_id, marketplace::valid_segment("s2c"), username);
};
@@ -0,0 +1,64 @@
/**
\ingroup public_actions
\brief Подтверждение получения имущества председателем в направлении deliver_on_offer (recvcnfoff).
@details Председатель совета подтверждает получение имущества заказчиком и переводит статус на received2.
Процесс: OFFER (parent) → ORDER (child)
- Председатель подтверждает получение имущества заказчиком
@param coopname Имя кооператива
@param username Имя председателя
@param request_hash Хэш встречной заявки (order)
@param document Акт подтверждения получения
@note Авторизация требуется от аккаунта: @p coopname
**/
[[eosio::action]] void marketplace::recvcnfoff(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 act2) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.type == "order"_n, "Метод offerrecievecnfrm применим только к заявкам типа order");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть типа offer");
eosio::check(change.status == "received1"_n, "Подтверждение получения возможно только в статусе received1");
eosio::check(username == change.money_contributor, "Недостаточно прав доступа для подтверждения получения");
// Проверяем подпись документа
verify_document_or_fail(act2, { username });
// Обновляем статус встречной заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &ch) {
ch.status = "received2"_n;
ch.received_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
ch.warranty_delay_until = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch() + change.warranty_period_secs);
});
// Сохраняем акт получения в return сегменте
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("c2r"), [&](auto &s) {
s.act2 = act2;
s.status = "received2"_n;
});
// Уменьшаем паевой фонд на сумму возвращаемой стоимости имущества заказчику
Fund::sub_circulating_funds(_marketplace, coopname, change.total_cost, false);
std::string memo = "Возврат паевого взноса по программе №" + std::to_string(_marketplace_program_id) + " с идентификатором: " + checksum256_to_hex(change.hash);
// Списываем заблокированный баланс заказчика в ЦПП Маркетплейс (base_cost согласно ТЗ)
Wallet::sub_blocked_funds(_marketplace, coopname, change.money_contributor, change.base_cost, _marketplace_program, memo);
}
@@ -0,0 +1,58 @@
/**
\ingroup public_actions
\brief Подпись акта получения имущества заказчиком в направлении deliver_on_offer (recvoff).
@details Заказчик подписывает акт получения имущества от поставщика.
Процесс: OFFER (parent) → ORDER (child)
- Заказчик (автор child order) получает имущество от поставщика
@param coopname Имя кооператива
@param username Имя заказчика
@param request_hash Хэш встречной заявки (order)
@param document Акт получения имущества
@note Авторизация требуется от аккаунта: @p coopname
**/
[[eosio::action]] void marketplace::recvoff(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 act1) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.type == "order"_n, "Метод offerrecieve применим только к заявкам типа order");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть типа offer");
eosio::check(change.status == "delivered4"_n, "Имущество может быть получено только в статусе delivered4");
eosio::check(change.deadline_for_receipt.sec_since_epoch() >= eosio::current_time_point().sec_since_epoch(), "Время на получение имущества истекло");
auto branch = get_branch_or_fail(coopname, change.braname);
//Проверяем права доступа на КУ (председатель или доверенное лицо)
eosio::check(branch.is_user_authorized(username), "Недостаточно прав доступа для выдачи имущества");
// Проверяем подпись документа от председателя или доверенного
verify_document_or_fail(act1, { username });
// Обновляем статус и устанавливаем гарантийную задержку
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
requests.modify(change_itr, coopname, [&](auto &ch){
ch.status = "received1"_n;
});
// Сохраняем акт получения в return сегменте
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("c2r"), [&](auto &s) {
s.act1 = act1;
s.status = "received1"_n;
s.coopactor = username; // Представитель кооператива, который выдал имущество
});
}
@@ -0,0 +1,74 @@
/**
\ingroup public_actions
\brief Подтверждение поставки председателем в направлении deliver_on_offer (supplcnfoff).
@details Председатель совета подтверждает поставку имущества и переводит статус заявки на "доставлено".
Процесс: OFFER (parent) → ORDER (child)
- Председатель подтверждает факт поставки имущества
@param coopname Имя кооператива
@param username Имя председателя
@param request_hash Хэш встречной заявки (order)
@param document Акт подтверждения поставки
@note Авторизация требуется от аккаунта: @p coopname
**/
[[eosio::action]] void marketplace::supplcnfoff(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 act2) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.type == "order"_n, "Метод offersupplycnfrm применим только к заявкам типа order");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть типа offer");
eosio::check(change.status == "supplied1"_n, "Подтверждение поставки возможно только в статусе supplied1");
auto branch = get_branch_or_fail(coopname, change.braname);
//Проверяем права доступа на КУ (председатель или доверенное лицо)
eosio::check(branch.is_user_authorized(username), "Недостаточно прав доступа для приёма имущества");
// Проверяем подписи документа
verify_document_or_fail(act2, { username, change.product_contributor });
// Обновляем статус встречной заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &o) {
o.status = "supplied2"_n;
o.supplied_units = o.blocked_units;
// delivered_at и deadline_for_receipt устанавливаются в delivercnfoff согласно ТЗ
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "offersupplycnfrm_child_update");
// Сохраняем акт подтверждения поставки в contribute сегменте
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.act2 = act2;
s.status = "supplied2"_n;
s.coopactor = username; // Представитель кооператива, который принял имущество
});
// Пополняем паевой фонд
Fund::add_circulating_funds(_marketplace, coopname, change.total_cost);
// Пополняем кошелёк поставщика (base_cost, а не total_cost согласно ТЗ)
std::string memo = "Имущественный паевой взнос по заявке №" + checksum256_to_hex(change.hash);
Wallet::add_available_funds(_marketplace, coopname, change.product_contributor, change.base_cost, _marketplace_program, memo);
// Блокируем средства на программе маркетплейса
Wallet::block_funds(_marketplace, coopname, change.product_contributor, change.base_cost, _marketplace_program, memo);
}
@@ -0,0 +1,56 @@
/**
\ingroup public_actions
\brief Поставка имущества в направлении deliver_on_offer (supplyoff).
@details Поставщик поставляет имущество по встречной заявке и предоставляет акт поставки.
Процесс: OFFER (parent) → ORDER (child)
- Поставщик (автор parent offer) поставляет имущество заказчику
@param coopname Имя кооператива
@param username Имя поставщика
@param request_hash Хэш встречной заявки (order)
@param document Акт поставки имущества
@note Авторизация требуется от аккаунта: @p coopname
**/
[[eosio::action]] void marketplace::supplyoff(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 act1) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "authorized"_n, "Только авторизованная заявка может быть поставлена");
eosio::check(change.type == "order"_n, "Метод offersupply применим только к встречным заявкам типа order");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "offer"_n, "Родительская заявка должна быть типа offer");
eosio::check(change.product_contributor == username, "Недостаточно прав доступа передачи имущества");
//проводим проверку подписи документа
verify_document_or_fail(act1, { username });
// Обновляем статус встречной заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &o) {
o.status = "supplied1"_n;
o.supplied_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "offersupply_child_update");
// Сохраняем акт поставки в contribute сегменте
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.act1 = act1;
s.status = "supplied1"_n;
});
}
@@ -0,0 +1,120 @@
/**
\ingroup public_actions
\brief Подтверждение готовности выполнить заказ в направлении deliver_on_order (acceptord).
@details Данный метод позволяет поставщику, который получил заказ от заказчика, подтвердить свою готовность принять его и создать встречную заявку типа "offer". При этом поставщик подает два документа: заявление на взнос имуществом и заявление на конвертацию в цифровой кошелек.
Процесс: ORDER (parent) → OFFER (child)
- Parent: денежный паевой взнос (заказчик)
- Child: имущественный паевой взнос (поставщик)
@param coopname Имя кооператива
@param username Имя поставщика, подтверждающего готовность выполнить заказ
@param request_hash Хэш заявки, которую следует принять
@param contribution_document Заявление на взнос имуществом
@param conversion_document Заявление на конвертацию в цифровой кошелек
@note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::acceptord(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 contribution_document, document2 conversion_document) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "active"_n, "Только активная заявка может быть принята");
eosio::check(change.type == "offer"_n, "Метод orderaccept применим только к встречным заявкам типа offer");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "order"_n, "Родительская заявка должна быть типа order");
eosio::check(parent_change.username == username, "Недостаточно прав доступа");
eosio::check(parent_change.remaining_units >= change.remaining_units, "Недостаточно объектов для поставки");
// Проверяем подписи документов
verify_document_or_fail(contribution_document);
verify_document_or_fail(conversion_document);
// Обновляем родительскую заявку (order)
auto parent_itr = requests.find(parent_change.id);
eosio::check(parent_itr != requests.end(), "Родительская заявка не найдена для обновления");
requests.modify(parent_itr, _marketplace, [&](auto &i) {
i.remaining_units -= change.remaining_units;
i.base_cost = (parent_change.remaining_units - change.remaining_units) * parent_change.unit_cost;
i.blocked_units += change.remaining_units;
});
// Проверяем инварианты родительской заявки
auto updated_parent = requests.find(parent_change.id);
marketplace::check_units_invariant(*updated_parent, "orderaccept_parent_update");
// Обновляем встречную заявку (offer)
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &o){
o.status = "accepted"_n;
o.blocked_units += change.remaining_units;
o.remaining_units = 0;
o.accepted_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "orderaccept_child_update");
// Сохраняем заявление на конвертацию в contribute сегменте
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.statement = conversion_document;
s.status = "statement"_n;
});
// Сохраняем заявление на возврат в return сегменте
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("c2r"), [&](auto &s) {
s.statement = contribution_document;
s.status = "statement"_n;
});
// Извлекаем заявления из сегментов
auto s2c_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"));
auto c2r_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("c2r"));
// Используем хэш встречной заявки как идентификатор пакета решений
checksum256 agenda_hash = change.hash;
// Отправляем заявление на взнос имуществом (из contribute сегмента) в совет
Action::send<createagenda_interface>(
_soviet,
"createagenda"_n,
_marketplace,
coopname,
username,
get_valid_soviet_action("authords2c"_n),
agenda_hash,
_marketplace,
Marketplace::get_valid_marketplace_action("authords2c"_n),
"declineacc"_n,
s2c_segment.statement,
std::string("")
);
// Отправляем заявление на конвертацию (из return сегмента) в совет
Action::send<createagenda_interface>(
_soviet,
"createagenda"_n,
_marketplace,
coopname,
username,
get_valid_soviet_action("authordc2r"_n),
agenda_hash,
_marketplace,
Marketplace::get_valid_marketplace_action("authordc2r"_n),
"declineacc"_n,
c2r_segment.statement,
std::string("")
);
}
@@ -0,0 +1,49 @@
/**
\ingroup public_actions
\brief Авторизация сегмента "c2r" для направления deliver_on_order (authordc2r).
@details Метод используется для подтверждения согласия совета на заявление на конвертацию в направлении ORDER → OFFER.
Заявка становится авторизованной только когда оба сегмента (contribute и return) получили авторизацию.
@param coopname Имя кооператива
@param request_hash Хэш встречной заявки на обмен (offer)
@param authorization Документ авторизации
@note Авторизация требуется от аккаунта: @p _soviet
*/
[[eosio::action]] void marketplace::authordc2r(eosio::name coopname, checksum256 request_hash, document2 authorization) {
require_auth(_soviet);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.type == "offer"_n, "Метод authordc2r применим только к заявкам типа offer");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "order"_n, "Родительская заявка должна быть типа order");
// Обновляем return сегмент
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("c2r"), [&](auto &s) {
s.authorization = authorization;
s.status = "authorized"_n;
});
// Проверяем статусы обоих сегментов
auto s2c_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"));
auto c2r_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("c2r"));
// Если оба сегмента авторизованы, авторизуем заявку
if (s2c_segment.status == "authorized"_n && c2r_segment.status == "authorized"_n) {
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _soviet, [&](auto &o) {
o.status = "authorized"_n;
});
}
}
@@ -0,0 +1,49 @@
/**
\ingroup public_actions
\brief Авторизация сегмента "s2c" для направления deliver_on_order (authords2c).
@details Метод используется для подтверждения согласия совета на заявление на взнос имуществом в направлении ORDER → OFFER.
Заявка становится авторизованной только когда оба сегмента (contribute и return) получили авторизацию.
@param coopname Имя кооператива
@param request_hash Хэш встречной заявки на обмен (offer)
@param authorization Документ авторизации
@note Авторизация требуется от аккаунта: @p _soviet
*/
[[eosio::action]] void marketplace::authords2c(eosio::name coopname, checksum256 request_hash, document2 authorization) {
require_auth(_soviet);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.type == "offer"_n, "Метод authords2c применим только к заявкам типа offer");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "order"_n, "Родительская заявка должна быть типа order");
// Обновляем contribute сегмент
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.authorization = authorization;
s.status = "authorized"_n;
});
// Проверяем статусы обоих сегментов
auto s2c_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"));
auto c2r_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("c2r"));
// Если оба сегмента авторизованы, авторизуем заявку
if (s2c_segment.status == "authorized"_n && c2r_segment.status == "authorized"_n) {
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _soviet, [&](auto &o) {
o.status = "authorized"_n;
});
}
}
@@ -0,0 +1,92 @@
/**
\ingroup public_actions
\brief Создать дочернюю заявку на имущественный паевой взнос (в ответ на родительский заказ).
*
* Данный метод позволяет пользователю создать встречную заявку на имущественный паевой взнос.
*
* @param coopname Имя кооператива
* @param username Имя пользователя, инициирующего заявку
* @param hash Хэш заявки (уникальный идентификатор)
* @param parent_hash Хэш родительской заявки
* @param units Количество частей (штук) товара или услуги
* @param unit_cost Цена за единицу (штуку) товара или услуги
* @param product_lifecycle_secs Время жизни продукта, заявляемое поставщиком
* @param braname Имя кооперативного участка куда будет поставляться товар
* @param document Сопутствующий подписанный документ на взнос или возврат взноса
* @param meta Метаданные о заявке
*
* @note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::childoffer(eosio::name coopname, eosio::name braname, eosio::name username, checksum256 hash, checksum256 parent_hash, uint64_t units, eosio::asset unit_cost, uint32_t product_lifecycle_secs, document2 document, std::string meta) {
require_auth(coopname);
// Проверяем, что заявка с таким хэшем не существует
auto existing_request = get_request_by_hash(coopname, hash);
eosio::check(!existing_request.has_value(), "Заявка с таким хэшем уже существует");
cooperatives2_index coops(_registrator, _registrator.value);
auto coop = coops.find(coopname.value);
eosio::check(coop != coops.end() && coop -> is_coop(), "Кооператив не найден");
eosio::check(unit_cost.symbol == coop -> initial.symbol, "Неверный символ токена");
eosio::check(units > 0, "Количество единиц в заявке должно быть больше нуля");
eosio::check(unit_cost.amount >= 0, "Цена не может быть отрицательной");
// Проверяем существование кооперативного участка
get_branch_or_fail(coopname, braname);
// Находим родительскую заявку по хэшу
auto parent_change = get_request_by_hash_or_fail(coopname, parent_hash, "Родительская заявка не найдена");
eosio::check(parent_change.type == "order"_n, "Родительская заявка должна быть заказом");
eosio::check(parent_change.unit_cost.amount == unit_cost.amount, "Торги запрещены");
eosio::check(product_lifecycle_secs > 0, "Гарантийный срок возврата для имущества должен быть установлен");
//проводим проверку подписи документа
verify_document_or_fail(document);
// Используем константу marketplace program_id
auto program = get_program_or_fail(coopname, _marketplace_program_id);
participants_index participants(_soviet, coopname.value);
auto participant = participants.find(username.value);
eosio::check(participant != participants.end(), "Вы не являетесь членом указанного кооператива");
// Берем членский взнос и комиссию за отмену из родительского заказа
eosio::asset membership_fee_amount = parent_change.membership_fee_amount;
eosio::asset cancellation_fee_amount = parent_change.cancellation_fee_amount;
eosio::asset base_cost = unit_cost * units;
eosio::asset total_cost = base_cost + membership_fee_amount;
// Проверяем что комиссия за отмену не превышает общую стоимость
eosio::check(cancellation_fee_amount <= total_cost, "Комиссия за отмену не может превышать общую стоимость заказа");
requests_index requests(_marketplace, coopname.value);
uint64_t request_id = get_global_id(_marketplace, "requests"_n);
requests.emplace(_marketplace, [&](auto &i) {
i.id = request_id;
i.hash = hash;
i.parent_id = parent_change.id;
i.parent_hash = parent_hash;
i.parent_username = parent_change.username;
i.type = "offer"_n;
i.coopname = coopname;
i.username = username;
i.status = "active"_n;
i.remaining_units = units;
i.unit_cost = unit_cost;
i.membership_fee_amount = membership_fee_amount;
i.base_cost = base_cost;
i.total_cost = total_cost;
i.product_lifecycle_secs = product_lifecycle_secs;
i.money_contributor = parent_change.username;
i.product_contributor = username;
i.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
i.cancellation_fee_amount = cancellation_fee_amount;
i.braname = braname;
});
// Создаем сегмент для дочерней заявки поставки от пайщика в кооператив
marketplace::create_segment(coopname, request_id, marketplace::valid_segment("s2c"), username);
};
@@ -0,0 +1,66 @@
/**
\ingroup public_actions
\brief Завершение процесса поставки в направлении deliver_on_order (completeord).
@details После истечения гарантийного срока и успешного получения товара заказчиком, процесс поставки завершается. Обновляются статусы, количество объектов, происходят финансовые операции.
Процесс: ORDER (parent) → OFFER (child)
- Финализация денежного и имущественного паевых взносов
@param coopname Имя кооператива
@param username Имя пользователя, инициирующего завершение
@param request_hash Хэш встречной заявки (offer)
@note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::completeord(eosio::name coopname, eosio::name username, checksum256 request_hash) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.type == "offer"_n, "Метод completeord применим только к заявкам типа offer");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "order"_n, "Родительская заявка должна быть типа order");
eosio::check(change.status == "recconfirmed"_n, "Завершение возможно только в статусе recconfirmed");
// Обновляем родительскую заявку (order)
auto parent_itr = requests.find(parent_change.id);
requests.erase(parent_itr);
// Обновляем встречную заявку (offer)
auto change_itr = requests.find(change.id);
requests.erase(change_itr);
auto program = get_program_or_fail(coopname, _marketplace_program_id);
std::string memo = "Успешное завершение поставки имущества по программе №" + std::to_string(_marketplace_program_id) + " с ID: " + std::to_string(change.id);
// Заказчику разблокируем баланс и списываем его
Wallet::sub_blocked_funds(_marketplace, coopname, change.money_contributor, change.total_cost, _marketplace_program, memo);
// Поставщику разблокируем средства в программе
Wallet::unblock_funds(_marketplace, coopname, change.product_contributor, change.base_cost, _marketplace_program, memo);
// Обрабатываем членские взносы
if (change.membership_fee_amount.amount > 0) {
// Сохраняем членский взнос в программе для дальнейшего списания
Wallet::add_member_fee(_marketplace, coopname, change.money_contributor, _marketplace_program_id, change.membership_fee_amount, memo);
}
// Извлекаем сегменты для завершения
auto return_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("return"));
auto contribution_segment = marketplace::get_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("contribute"));
// Удаляем сегменты встречной заявки
marketplace::delete_segments_by_request(coopname, change.id);
// Удаляем встречную заявку
requests.erase(change);
}
@@ -0,0 +1,80 @@
/**
\ingroup public_actions
\brief Отказ от заказа в направлении deliver_on_order (declineord).
@details Этот метод позволяет заказчику отклонить встречную заявку поставщика типа offer.
Процесс: ORDER (parent) → OFFER (child)
- Заказчик отклоняет заявку поставщика
@param coopname Имя кооператива
@param username Имя заказчика, отклоняющего заявку
@param request_hash Хэш встречной заявки (offer), которую следует отклонить
@param meta Дополнительные метаданные, связанные с отказом
@note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::declineord(eosio::name coopname, eosio::name username, checksum256 request_hash, std::string meta) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.type == "offer"_n, "Метод orderdecline применим только к заявкам типа offer");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "order"_n, "Родительская заявка должна быть типа order");
eosio::check(change.status == "active"_n, "Только активная заявка может быть отклонена");
// Если заявка была принята (blocked_units > 0), возвращаем единицы товара в родительскую заявку
if (change.blocked_units > 0) {
auto parent_itr = requests.find(parent_change.id);
eosio::check(parent_itr != requests.end(), "Родительская заявка не найдена для обновления");
requests.modify(parent_itr, _marketplace, [&](auto &e) {
e.remaining_units += change.blocked_units;
e.blocked_units -= change.blocked_units;
e.base_cost = e.remaining_units * e.unit_cost;
});
// Проверяем инварианты после изменения
auto updated_parent = requests.find(parent_change.id);
marketplace::check_units_invariant(*updated_parent, "orderdecline_parent_update");
}
// Обновляем встречную заявку
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, coopname, [&](auto &o){
o.status = "declined"_n;
o.declined_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
o.meta = meta;
o.blocked_units = 0; // Обнуляем заблокированные единицы в дочерней заявке
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "orderdecline_child_update");
// Примечание: В процессе ORDER → OFFER разблокировка средств не требуется,
// так как средства блокируются у заказчика при создании родительской заявки order,
// а не при создании встречной заявки offer
// Проверяем равенство количества единиц для определения стратегии удаления
auto updated_parent = requests.find(parent_change.id);
bool units_equal = (change.remaining_units == updated_parent->remaining_units);
if (units_equal) {
// Если количество единиц равно - удаляем обе заявки
requests.erase(updated_parent);
requests.erase(updated_change);
} else {
// Иначе удаляем только встречную заявку
requests.erase(updated_change);
}
}
@@ -0,0 +1,49 @@
/**
\ingroup public_actions
\brief Доставка товара для направления deliver_on_order (deliverord).
@details Данный метод используется для фиксации доставки имущества от поставщика к заказчику.
Встречная заявка должна быть предварительно в статусе "поставлено".
Процесс: ORDER (parent) → OFFER (child)
- Parent: денежный паевой взнос (заказчик)
- Child: имущественный паевой взнос (поставщик)
@param coopname Имя кооператива
@param username Имя председателя совета
@param request_hash Хэш встречной заявки (offer)
@note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::deliverord(eosio::name coopname, eosio::name username, checksum256 request_hash) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "supplied"_n, "Только поставленная заявка может быть доставлена");
eosio::check(change.type == "offer"_n, "Метод orderdeliver применим только к заявкам типа offer");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "order"_n, "Родительская заявка должна быть типа order");
// Обновляем статус встречной заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &o) {
o.status = "delivered"_n;
o.delivered_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
o.supplied_units = o.blocked_units;
o.deadline_for_receipt = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch() + 3 * 24 * 60 * 60); // 3 дня на подтверждение
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "orderdeliver_update");
}
@@ -0,0 +1,99 @@
/**
\ingroup public_actions
\brief Создать родительскую заявку на денежный паевой взнос.
*
* Данный метод позволяет пользователю создать родительскую заявку на денежный паевой взнос.
*
* @param coopname Имя кооператива
* @param username Имя пользователя, инициирующего заявку
* @param hash Хэш заявки (уникальный идентификатор)
* @param units Количество частей (штук) товара или услуги
* @param unit_cost Цена за единицу (штуку) товара или услуги
* @param membership_fee_amount Сумма членского взноса
* @param cancellation_fee_amount Сумма комиссии за отмену заявки
* @param braname Имя кооперативного участка откуда будет забираться товар
* @param convert_in Заявление на конвертацию из кошелька в маркетплейс
* @param meta Метаданные о заявке
*
* @note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::parentorder(eosio::name coopname, eosio::name braname, eosio::name username, checksum256 hash, uint64_t units, eosio::asset unit_cost, eosio::asset membership_fee_amount, eosio::asset cancellation_fee_amount, document2 convert_in, std::string meta) {
require_auth(coopname);
// Проверяем, что заявка с таким хэшем не существует
auto existing_request = get_request_by_hash(coopname, hash);
eosio::check(!existing_request.has_value(), "Заявка с таким хэшем уже существует");
cooperatives2_index coops(_registrator, _registrator.value);
auto coop = coops.find(coopname.value);
eosio::check(coop != coops.end() && coop -> is_coop(), "Кооператив не найден");
eosio::check(unit_cost.symbol == coop -> initial.symbol, "Неверный символ токена");
eosio::check(membership_fee_amount.symbol == coop -> initial.symbol, "Неверный символ токена для членского взноса");
eosio::check(cancellation_fee_amount.symbol == coop -> initial.symbol, "Неверный символ токена для комиссии отмены");
eosio::check(units > 0, "Количество единиц в заявке должно быть больше нуля");
eosio::check(unit_cost.amount >= 0, "Цена не может быть отрицательной");
eosio::check(membership_fee_amount.amount >= 0, "Членский взнос не может быть отрицательным");
eosio::check(cancellation_fee_amount.amount >= 0, "Комиссия за отмену не может быть отрицательной");
// Проверяем существование кооперативного участка
get_branch_or_fail(coopname, braname);
requests_index requests(_marketplace, coopname.value);
uint64_t request_id = get_global_id(_marketplace, "requests"_n);
participants_index participants(_soviet, coopname.value);
auto participant = participants.find(username.value);
eosio::check(participant != participants.end(), "Вы не являетесь членом указанного кооператива");
auto program = get_program_or_fail(coopname, _marketplace_program_id);
// Для заказа рассчитываем общую стоимость включая членский взнос
eosio::asset base_cost = unit_cost * units;
eosio::asset total_cost = base_cost + membership_fee_amount;
// Проверяем что комиссия за отмену не превышает общую стоимость
eosio::check(cancellation_fee_amount <= total_cost, "Комиссия за отмену не может превышать общую стоимость заказа");
// Проверяем подпись документа на конвертацию
verify_document_or_fail(convert_in);
requests.emplace(_marketplace, [&](auto &i) {
i.id = request_id;
i.hash = hash;
i.type = "order"_n;
i.username = username;
i.coopname = coopname;
i.status = "active"_n;
i.remaining_units = units;
i.unit_cost = unit_cost;
i.base_cost = base_cost;
i.total_cost = total_cost;
i.membership_fee_amount = membership_fee_amount;
i.product_lifecycle_secs = 0; // Не используется для заказов
i.meta = meta;
i.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
i.cancellation_fee_amount = cancellation_fee_amount;
i.braname = braname;
});
// Создаем сегмент для родительской заявки поставки из кооператива - заказчику
marketplace::create_segment(coopname, request_id, marketplace::valid_segment("c2r"), username);
// Сохраняем заявление на конвертацию в contribute сегменте
marketplace::update_segment_by_request_and_type(coopname, request_id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.convert_in = convert_in;
s.status = "convertin"_n;
});
// Списываем средства с ЦПП цифрового кошелька
std::string convert_memo = "Конвертация средств из ЦПП 'Цифровой Кошелёк' в ЦПП 'Маркетплейс' для заказа №" + std::to_string(request_id);
Wallet::sub_available_funds(_marketplace, coopname, username, total_cost, _wallet_program, convert_memo);
// Добавляем средства на ЦПП маркетплейса
Wallet::add_available_funds(_marketplace, coopname, username, total_cost, _marketplace_program, convert_memo);
// Блокируем средства на программе маркетплейса
std::string memo = "Создание заказа по программе №" + std::to_string(_marketplace_program_id) + " с ID: " + std::to_string(request_id);
Wallet::block_funds(_marketplace, coopname, username, total_cost, _marketplace_program, memo);
};
@@ -0,0 +1,62 @@
/**
\ingroup public_actions
\brief Подтверждение получения товара заказчиком для направления deliver_on_order (recvcnford).
@details Председатель совета подтверждает получение имущества заказчиком на основе предоставленного документа.
Встречная заявка должна быть предварительно в статусе "получено".
Процесс: ORDER (parent) → OFFER (child)
- Parent: денежный паевой взнос (заказчик)
- Child: имущественный паевой взнос (поставщик)
@param coopname Имя кооператива
@param username Имя председателя совета
@param request_hash Хэш встречной заявки (offer)
@param document Документ подтверждения получения
@note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::recvcnford(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "received1"_n, "Подтверждение получения возможно только после получения");
eosio::check(change.type == "offer"_n, "Метод recvcnford применим только к заявкам типа offer");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "order"_n, "Родительская заявка должна быть типа order");
auto branch = get_branch_or_fail(coopname, change.braname);
eosio::check(change.money_contributor == username, "Недостаточно прав доступа для получения имущества");
//проводим проверку подписи документа
verify_document_or_fail(document);
// Уменьшаем паевой фонд на сумму возвращаемой стоимости имущества заказчику
Fund::sub_circulating_funds(_marketplace, coopname, parent_change.total_cost, false);
// Обновляем статус встречной заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &o) {
o.status = "received2"_n;
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "orderrecconfirm_update");
// Обновляем return сегмент документом подтверждения
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("c2r"), [&](auto &s) {
s.act2 = document;
s.status = "received2"_n;
});
}
@@ -0,0 +1,62 @@
/**
\ingroup public_actions
\brief Получение товара для направления deliver_on_order (recvord).
@details Данный метод позволяет заказчику подтвердить получение имущества и предоставить акт получения.
Встречная заявка должна быть предварительно в статусе "доставлено".
Процесс: ORDER (parent) → OFFER (child)
- Parent: денежный паевой взнос (заказчик)
- Child: имущественный паевой взнос (поставщик)
@param coopname Имя кооператива
@param username Имя заказчика
@param request_hash Хэш встречной заявки (offer)
@param document Акт получения имущества
@note Авторизация требуется от аккаунта: @p coopname
**/
[[eosio::action]] void marketplace::recvord(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "delivered"_n, "Только доставленная заявка может быть получена");
eosio::check(change.type == "offer"_n, "Метод orderreceive применим только к заявкам типа offer");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "order"_n, "Родительская заявка должна быть типа order");
auto branch = get_branch_or_fail(coopname, change.braname);
//Проверяем права доступа на КУ (председатель или доверенное лицо)
eosio::check(branch.is_user_authorized(username), "Недостаточно прав доступа для передачи имущества");
//проводим проверку подписи документа
verify_document_or_fail(document);
// Обновляем статус встречной заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &o) {
o.status = "received1"_n;
o.received_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "orderreceive_update");
// Обновляем return сегмент документом получения
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("c2r"), [&](auto &s) {
s.act1 = document;
s.status = "received1"_n;
s.coopactor = username; // Представитель кооператива, который выдал имущество
});
}
@@ -0,0 +1,70 @@
/**
\ingroup public_actions
\brief Подтверждение поставки товара для направления deliver_on_order (supplcnford).
@details Председатель совета подтверждает поставку имущества поставщиком на основе предоставленного документа.
Встречная заявка должна быть предварительно в статусе "поставлено".
Процесс: ORDER (parent) → OFFER (child)
- Parent: денежный паевой взнос (заказчик)
- Child: имущественный паевой взнос (поставщик)
@param coopname Имя кооператива
@param username Имя председателя совета
@param request_hash Хэш встречной заявки (offer)
@param document Документ подтверждения поставки
@note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::supplcnford(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "supplied"_n, "Подтверждение поставки возможно только после поставки");
eosio::check(change.type == "offer"_n, "Метод ordersupplyconfirm применим только к заявкам типа offer");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "order"_n, "Родительская заявка должна быть типа order");
auto branch = get_branch_or_fail(coopname, change.braname);
//Проверяем права доступа на КУ (председатель или доверенное лицо)
eosio::check(branch.is_user_authorized(username), "Недостаточно прав доступа для приёма имущества");
//проводим проверку подписи документа
verify_document_or_fail(document);
std::string memo = "Поставка имущества по программе №" + std::to_string(_marketplace_program_id) + " с ID: " + std::to_string(change.id);
// Начисляем поставщику заблокированный баланс в цифровой кошелек
Wallet::add_blocked_funds(_marketplace, coopname, change.product_contributor, change.base_cost, _marketplace_program, memo);
// Увеличиваем сумму циркуляции в системе на сумму взноса
Fund::add_circulating_funds(_marketplace, coopname, parent_change.total_cost);
// Обновляем статус встречной заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &o) {
o.status = "supplied1"_n;
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "ordersupplyconfirm_update");
// Обновляем contribute сегмент документом подтверждения
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.act2 = document;
s.status = "supplied1"_n;
s.coopactor = username; // Представитель кооператива, который принял имущество
});
}
@@ -0,0 +1,59 @@
/**
\ingroup public_actions
\brief Поставка товара для направления deliver_on_order (supplyord).
@details Данный метод позволяет поставщику зафиксировать поставку имущества.
Встречная заявка должна быть предварительно авторизована советом.
Процесс: ORDER (parent) → OFFER (child)
- Parent: денежный паевой взнос (заказчик)
- Child: имущественный паевой взнос (поставщик)
@param coopname Имя кооператива
@param username Имя поставщика, предоставляющего товар или услугу
@param request_hash Хэш встречной заявки (offer)
@param document Документ, подтверждающий поставку товара или услуги
@note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::supplyord(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "authorized"_n, "Только авторизованная заявка может быть поставлена");
eosio::check(change.type == "offer"_n, "Метод ordersupply применим только к встречным заявкам типа offer");
eosio::check(change.parent_id > 0, "У встречной заявки должна быть родительская заявка");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(parent_change.type == "order"_n, "Родительская заявка должна быть типа order");
eosio::check(change.product_contributor == username, "Недостаточно прав доступа передачи имущества");
//проводим проверку подписи документа
verify_document_or_fail(document);
// Обновляем статус встречной заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Встречная заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &o) {
o.status = "supplied1"_n;
o.supplied_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
});
// Проверяем инварианты дочерней заявки
auto updated_change = requests.find(change.id);
marketplace::check_units_invariant(*updated_change, "ordersupply_update");
// Обновляем contribute сегмент документом поставки
marketplace::update_segment_by_request_and_type(coopname, change.id, marketplace::valid_segment("s2c"), [&](auto &s) {
s.act1 = document;
s.status = "supplied1"_n;
});
}
@@ -1,23 +0,0 @@
[[eosio::action]] void marketplace::delivered(eosio::name coopname, eosio::name username, uint64_t exchange_id) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Заявка не найдена");
eosio::check(change -> parent_id > 0, "Только продукт по встречной заявке может быть доставлен");
eosio::check(change -> status == "supplied2"_n, "Продукт может быть поставлен только по заявке в статусе supplied2");
auto soviet = get_board_by_type_or_fail(coopname, "soviet"_n);
auto chairman = soviet.get_chairman();
eosio::check(username == chairman, "Недостаточно прав доступа для подтверждения доставки");
auto program = get_program_or_fail(coopname, change -> program_id);
exchange.modify(change, coopname, [&](auto &ch) {
ch.status = "delivered"_n;
ch.delivered_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
ch.deadline_for_receipt = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch() + change -> product_lifecycle_secs / 4);
});
}
@@ -1,113 +0,0 @@
[[eosio::action]] void marketplace::dispute(eosio::name coopname, eosio::name username, uint64_t exchange_id, document2 document){
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change -> type == "order"_n, "Спор может быть открыт только по заявке на поставку");
eosio::check(change != exchange.end(), "Заявка не найдена");
eosio::check(change -> username == username, "Только заказчик может открыть спор");
eosio::check(change -> is_warranty_return == false, "Нельзя открыть спор на спор");
auto parent_change = exchange.find(change -> parent_id);
eosio::check(parent_change != exchange.end(), "Родительская заявка не найдена");
eosio::check(change -> status == "recieved2"_n, "Неверный статус для открытия спора");
exchange.modify(change, _marketplace, [&](auto &e){
e.status = "disputed"_n;
e.disputed_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
e.warranty_return_id = change -> id;
});
uint64_t new_id = get_global_id(_marketplace, "exchange"_n);
uint64_t new_parent_id = get_global_id(_marketplace, "exchange"_n);
auto cooperative = get_cooperative_or_fail(coopname);
// Проверяем подпись документа
verify_document_or_fail(document);
//открытая заявка от заказчика на поставку имущества поставщику
exchange.emplace(_marketplace, [&](auto &i) {
i.id = new_parent_id;
i.type = "offer"_n;
i.program_id = change -> program_id;
i.username = username;
i.parent_username = ""_n;
i.parent_id = 0;
i.coopname = coopname;
i.status = "published"_n;
i.remain_units = change -> remain_units;
i.unit_cost = change -> unit_cost;
i.supplier_amount = i.unit_cost * i.remain_units;
i.total_cost = i.supplier_amount;
i.membership_fee = asset(0, cooperative.initial.symbol);
i.cancellation_fee_amount = asset(0, cooperative.initial.symbol);
i.product_lifecycle_secs = change -> product_lifecycle_secs;
i.data = change -> data;
i.meta = change -> meta;
i.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
i.is_warranty_return = true;
i.warranty_return_id = change -> id;
});
action(
permission_level{ _marketplace, "active"_n},
_marketplace,
"newid"_n,
std::make_tuple(new_parent_id, "offer")
).send();
//новая встречная заявка для будущего возврата поставщику
exchange.emplace(_marketplace, [&](auto &i) {
i.id = new_id;
i.parent_id = new_parent_id;
i.type = "order"_n;
i.program_id = parent_change -> program_id;
i.coopname = coopname;
i.username = change -> parent_username;
i.parent_username = change -> username;
i.status = "published"_n;
i.remain_units = change -> remain_units;
i.unit_cost = change -> unit_cost;
i.supplier_amount = change -> supplier_amount;
i.total_cost = change -> supplier_amount;;
i.membership_fee = asset(0, cooperative.initial.symbol); //размер членского взноса должен установить председатель в методе resolve
i.cancellation_fee_amount = asset(0, cooperative.initial.symbol);
i.data = change -> data;
i.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
i.is_warranty_return = true;
i.warranty_return_id = exchange_id;
i.contribute_product_statement = document;
i.money_contributor = parent_change -> username;
i.product_contributor = change -> username;
i.product_lifecycle_secs = 0; //потому что гарантийного срока в споре нет
});
action(
permission_level{ _marketplace, "active"_n},
_marketplace,
"newid"_n,
std::make_tuple(new_id, "order"_n)
).send();
};
@@ -0,0 +1,56 @@
# Система гарантийного возврата (диспут)
## Обзор
Новая система диспута работает через создание дополнительных сегментов в рамках существующей пары заявок, а не через создание новых заявок.
## Типы сегментов диспута
- **wreturn** - сегмент возврата товара от заказчика в кооператив
- **wsupply** - сегмент выдачи товара из кооператива поставщику
## Процесс диспута
### 1. Открытие диспута (`dispute`)
- Заказчик подает претензию с документом
- Создаются два новых сегмента: `wreturn` и `wsupply`
- Статус заявки меняется на `disputed`
### 2. Авторизация диспута (`wauthorize`)
- Совет авторизует принятие товара от заказчика
- Совет авторизует последующую выдачу товара поставщику
- Статус заявки меняется на `wauthorized`
### 3. Возврат товара (`wreturn`)
- Заказчик возвращает товар в кооператив
- Председатель принимает товар и подписывает акт
- Статус заявки меняется на `wreturned`
### 4. Предложение товара поставщику (`woffer`)
- Кооператив предлагает поставщику забрать товар
- Создается акт передачи
- Статус заявки меняется на `woffered`
### 5. Принятие решения поставщиком (`waccept`)
- Поставщик может принять товар (`accept=true`) или отказаться (`accept=false`)
- При принятии: статус становится `wcompleted`
- При отказе: статус становится `wdeclined`
## Статусы заявок диспута
- **disputed** - диспут открыт
- **wauthorized** - диспут авторизован советом
- **wreturned** - товар возвращен в кооператив
- **woffered** - товар предложен поставщику
- **wcompleted** - поставщик принял товар, диспут завершен
- **wdeclined** - поставщик отказался от товара, диспут завершен
## Статусы сегментов
- **created** - сегмент создан
- **statement** - документ/заявление предоставлено
- **authorized** - авторизовано советом
- **returned** - товар возвращен в кооператив
- **offered** - товар предложен поставщику
- **accepted** - поставщик принял товар
- **declined** - поставщик отказался от товара
@@ -0,0 +1,81 @@
/**
\ingroup public_actions
\brief Открытие гарантийного спора по заявке.
@details Заказчик может открыть спор после получения товара, если есть проблемы с качеством или соответствием. Спор создает специальные сегменты для возврата товара и получения компенсации.
@param coopname Имя кооператива
@param username Имя заказчика, открывающего спор
@param request_hash Хэш заявки, по которой открывается спор
@param document Документ с описанием претензии
@note Авторизация требуется от аккаунта: @p coopname
**/
[[eosio::action]] void marketplace::dispute(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document){
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.type == "order"_n, "Спор может быть открыт только по заявке на поставку");
eosio::check(change.username == username, "Только заказчик может открыть спор");
eosio::check(change.is_warranty_return == false, "Нельзя открыть спор на спор");
auto parent_change_opt = Marketplace::get_request_by_hash(coopname, change.parent_hash);
eosio::check(parent_change_opt.has_value(), "Родительская заявка не найдена");
auto parent_change = parent_change_opt.value();
eosio::check(change.status == "received2"_n, "Неверный статус для открытия спора");
// Проверяем подпись документа
verify_document_or_fail(document);
// Обновляем статус заявки на "disputed"
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &e){
e.status = "disputed"_n;
e.disputed_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
e.is_warranty_return = true;
});
// Создаем сегмент для возврата товара от заказчика в кооператив
uint64_t wreturn_segment_id = marketplace::create_segment(coopname, change.id, "wreturn"_n);
// Создаем сегмент для выдачи товара из кооператива поставщику
uint64_t wsupply_segment_id = marketplace::create_segment(coopname, change.id, "wsupply"_n);
// Сохраняем документ с претензией в wreturn сегменте
marketplace::update_segment(coopname, wreturn_segment_id, [&](auto &s) {
s.statement = document;
s.status = "disputed"_n;
});
// Сохраняем документ с претензией в wsupply сегменте
marketplace::update_segment(coopname, wsupply_segment_id, [&](auto &s) {
s.statement = document;
s.status = "disputed"_n;
});
// Используем хэш заявки как идентификатор пакета решений
checksum256 agenda_hash = change.hash;
// Отправляем заявление на гарантийный возврат в совет
Action::send<createagenda_interface>(
_soviet,
"createagenda"_n,
_marketplace,
coopname,
username,
get_valid_soviet_action("mpwreturn"_n),
agenda_hash,
_marketplace,
Marketplace::get_valid_marketplace_action("wauthorize"_n),
"wdecline"_n,
document,
std::string("")
);
}
@@ -0,0 +1,75 @@
/**
\ingroup public_actions
\brief Принятие или отказ поставщика от товара в рамках гарантийного возврата
@details Поставщик может принять товар (accept=true) или отказаться от него (accept=false).
При принятии товар передается поставщику и диспут завершается.
При отказе товар остается у кооператива и диспут завершается.
@param coopname Имя кооператива
@param username Имя поставщика
@param request_hash Хэш встречной заявки с диспутом
@param accept Принимает ли поставщик товар (true/false)
@param document Документ с решением поставщика
@note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::waccept(eosio::name coopname, eosio::name username, checksum256 request_hash, bool accept, document2 document) {
require_auth(coopname);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "woffered"_n, "Товар должен быть предложен поставщику");
eosio::check(change.product_contributor == username, "Только поставщик может принять решение о товаре");
// Проверяем подпись документа
verify_document_or_fail(document);
// ИСПРАВЛЕНИЕ: Возвращаем заблокированные единицы товара в родительскую заявку
requests_index requests(_marketplace, coopname.value);
auto parent_change = requests.find(change.parent_id);
eosio::check(parent_change != requests.end(), "Родительская заявка не найдена");
if (change.blocked_units > 0) {
requests.modify(parent_change, _marketplace, [&](auto &e) {
e.remaining_units += change.blocked_units;
e.blocked_units -= change.blocked_units;
e.base_cost = e.remaining_units * e.unit_cost;
});
}
if (accept) {
// Поставщик принимает товар
marketplace::update_segment_by_request_and_type(coopname, change.id, "wsupply"_n, [&](auto &s) {
s.act2 = document;
s.status = "accepted"_n;
});
// Обновляем статус заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &ch) {
ch.status = "wcompleted"_n;
ch.blocked_units = 0; // Обнуляем заблокированные единицы
});
} else {
// Поставщик отказывается от товара
marketplace::update_segment_by_request_and_type(coopname, change.id, "wsupply"_n, [&](auto &s) {
s.act2 = document;
s.status = "declined"_n;
});
// Обновляем статус заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &ch) {
ch.status = "wdeclined"_n;
ch.blocked_units = 0; // Обнуляем заблокированные единицы
});
}
print("Поставщик ", username, (accept ? " принял" : " отказался от"), " товара. Диспут завершен.");
}
@@ -0,0 +1,46 @@
/**
\ingroup public_actions
\brief Авторизация гарантийного возврата советом
@details Совет авторизует принятие товара от заказчика и его последующую выдачу поставщику
@param coopname Имя кооператива
@param request_hash Хэш встречной заявки с диспутом
@param wreturn_decision_id Идентификатор решения по принятию товара
@param wreturn_authorization Документ авторизации принятия товара
@param wsupply_decision_id Идентификатор решения по выдаче товара поставщику
@param wsupply_authorization Документ авторизации выдачи товара
@note Авторизация требуется от аккаунта: @p _soviet
*/
[[eosio::action]] void marketplace::wauthorize(eosio::name coopname, checksum256 request_hash, uint64_t wreturn_decision_id, document2 wreturn_authorization, uint64_t wsupply_decision_id, document2 wsupply_authorization) {
require_auth(_soviet);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "disputed"_n, "Заявка должна быть в статусе диспута");
// Обновляем wreturn сегмент
marketplace::update_segment_by_request_and_type(coopname, change.id, "wreturn"_n, [&](auto &s) {
s.decision_id = wreturn_decision_id;
s.authorization = wreturn_authorization;
s.status = "authorized"_n;
});
// Обновляем wsupply сегмент
marketplace::update_segment_by_request_and_type(coopname, change.id, "wsupply"_n, [&](auto &s) {
s.decision_id = wsupply_decision_id;
s.authorization = wsupply_authorization;
s.status = "authorized"_n;
});
// Обновляем статус заявки
requests_index requests(_marketplace, coopname.value);
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
requests.modify(change_itr, _soviet, [&](auto &o) {
o.status = "wauthorized"_n;
});
};
@@ -0,0 +1,45 @@
/**
\ingroup public_actions
\brief Предложение товара поставщику в рамках гарантийного возврата
@details Кооператив предлагает поставщику забрать товар, возвращенный заказчиком.
Создается предложение с актом передачи.
@param coopname Имя кооператива
@param username Имя председателя, предлагающего товар
@param request_hash Хэш встречной заявки с диспутом
@param document Акт передачи товара поставщику
@note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::woffer(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document) {
require_auth(coopname);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "wreturned"_n, "Товар может быть предложен только после возврата в кооператив");
auto soviet = get_board_by_type_or_fail(coopname, "soviet"_n);
auto chairman = soviet.get_chairman();
eosio::check(username == chairman, "Недостаточно прав доступа для предложения товара");
// Проверяем подпись документа
verify_document_or_fail(document);
// Сохраняем акт передачи в wsupply сегменте
marketplace::update_segment_by_request_and_type(coopname, change.id, "wsupply"_n, [&](auto &s) {
s.act1 = document;
s.status = "offered"_n;
});
// Обновляем статус заявки
requests_index requests(_marketplace, coopname.value);
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &ch) {
ch.status = "woffered"_n;
});
}
@@ -0,0 +1,45 @@
/**
\ingroup public_actions
\brief Возврат товара от заказчика в кооператив
@details Заказчик возвращает товар в кооператив в рамках гарантийного возврата.
Председатель принимает товар и подписывает акт приёма.
@param coopname Имя кооператива
@param username Имя председателя, принимающего товар
@param request_hash Хэш встречной заявки с диспутом
@param document Акт приёма товара от заказчика
@note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void marketplace::wreturn(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document) {
require_auth(coopname);
requests_index requests(_marketplace, coopname.value);
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
eosio::check(change_opt.has_value(), "Заявка не найдена");
auto change = change_opt.value();
eosio::check(change.status == "wauthorized"_n, "Товар может быть возвращен только по авторизованному диспуту");
auto soviet = get_board_by_type_or_fail(coopname, "soviet"_n);
auto chairman = soviet.get_chairman();
eosio::check(username == chairman, "Недостаточно прав доступа для принятия возврата");
// Проверяем подпись документа
verify_document_or_fail(document);
// Сохраняем акт приёма в wreturn сегменте
marketplace::update_segment_by_request_and_type(coopname, change.id, "wreturn"_n, [&](auto &s) {
s.act1 = document;
s.status = "returned"_n;
});
// Обновляем статус заявки
auto change_itr = requests.find(change.id);
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
requests.modify(change_itr, _marketplace, [&](auto &ch) {
ch.status = "wreturned"_n;
});
}
@@ -1,32 +0,0 @@
/**
\ingroup public_actions
\brief Модерация товара на маркетплейсе.
*
* Данный метод предназначен для модерации товара перед его публикацией на маркетплейсе.
* Метод может быть вызван только администратором маркетплейса.
*
* @param username Имя пользователя-администратора, который вызывает данный метод.
* @param exchange_id Уникальный идентификатор товара, который нужно опубликовать после модерации.
*
* @note Авторизация требуется от аккаунта: @p username
*/
[[eosio::action]] void marketplace::moderate(eosio::name coopname, eosio::name username, uint64_t exchange_id, uint64_t cancellation_fee) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Объявление не найдено");
if (change -> status == "moderation"_n || change -> status == "prohibit"_n) {
eosio::check(cancellation_fee >= 0 && cancellation_fee < 100, "Комиссия отмены должна быть от 0 до 100 процентов");
eosio::asset cancellation_fee_amount = change -> total_cost * cancellation_fee / 100;
exchange.modify(change, username, [&](auto &o){
o.status = "published"_n;
o.cancellation_fee = cancellation_fee;
o.cancellation_fee_amount = cancellation_fee_amount;
});
}
};
@@ -1,16 +0,0 @@
/**
\ingroup public_actions
\brief Создать заявку на имущественный паевой взнос.
*
* Данный метод позволяет пользователю создать заявку на имущественный паевой взнос в системе.
*
* @param params Параметры для создания заявки на имущественный паевой взнос.
*
* @note Авторизация требуется от аккаунта: @p params.username
*/
[[eosio::action]] void marketplace::offer (const exchange_params& params) {
require_auth(params.coopname);
marketplace::create("offer"_n, params);
};
@@ -1,16 +0,0 @@
/**
\ingroup public_actions
\brief Создать заявку на денежный паевой взнос.
*
* Данный метод позволяет пользователю создать заявку на денежный паевой взнос в системе.
*
* @param params Параметры для создания заявки на денежный паевой взнос.
*
* @note Авторизация требуется от аккаунта: @p params.username
*/
[[eosio::action]] void marketplace::order (const exchange_params& params) {
require_auth(params.coopname);
marketplace::create("order"_n, params);
};
@@ -1,28 +0,0 @@
/**
\ingroup public_actions
\brief Отказ в прохождении модерации заявки на товар.
*
* Этот метод предназначен для администраторов маркетплейса, чтобы отказать в публикации товара после его модерации.
* При отказе администратор указывает причину отказа в параметре `meta`.
*
* @param username Имя пользователя-администратора, который вызывает данный метод.
* @param exchange_id Уникальный идентификатор товара, публикацию которого нужно запретить.
* @param meta Строковое описание или причина, по которой товар не прошел модерацию.
*
* @note Авторизация требуется от аккаунта: @p username
*/
[[eosio::action]] void marketplace::prohibit(eosio::name coopname, eosio::name username, uint64_t exchange_id, std::string meta) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Объявление не найдено");
exchange.modify(change, username, [&](auto &o){
o.status = "prohibit"_n;
o.meta = meta;
});
};
@@ -1,31 +0,0 @@
/**
\ingroup public_actions
\brief Опубликовать товар на маркетплейсе.
*
* Этот метод позволяет владельцу товара опубликовать его на маркетплейсе. Для публикации товар должен находиться в статусе "unpublished".
*
* @param username Имя пользователя, являющегося владельцем заявки.
* @param exchange_id Идентификатор заявки, которую следует опубликовать.
*
* @note Авторизация требуется от аккаунта: @p username
*/
[[eosio::action]] void marketplace::publish(eosio::name coopname, eosio::name username, uint64_t exchange_id) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
staff_index staff(_soviet, coopname.value);
auto persona = staff.find(username.value);
eosio::check(change != exchange.end(), "Объявление не найдено");
eosio::check(change->username == username || (persona != staff.end() && persona -> has_right(_marketplace, "unpublish"_n)), "У вас нет права на публикацию данной заявки");
eosio::check(change->status == "unpublished"_n || change->status == "prohibit"_n, "Неверный статус для публикации");
exchange.modify(change, username, [&](auto &o) {
if (change->status == "unpublished"_n)
o.status = "published"_n;
else
o.status = "moderation"_n;
});
}
@@ -1,42 +0,0 @@
/**
\ingroup public_actions
\brief Подпись акта получения имущества пайщиком
**/
[[eosio::action]] void marketplace::recieve(eosio::name coopname, eosio::name username, uint64_t exchange_id, document2 document) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Заявка не найдена");
eosio::check(change -> parent_id > 0, "Только продукт по встречной заявке может быть выдан");
eosio::check(change -> status == "delivered"_n, "Продукт может быть выдан только по заявке в статусе ожидания получения");
eosio::check(change -> deadline_for_receipt.sec_since_epoch() >= eosio::current_time_point().sec_since_epoch(), "Время на выдачу имущества истекло");
auto soviet = get_board_by_type_or_fail(coopname, "soviet"_n);
auto chairman = soviet.get_chairman();
// Проверяем подпись документа
verify_document_or_fail(document);
if (change -> type == "order"_n) { //если указанная заявка - это заказ продукта
//то получение продукта может осуществить только пользователь из username
eosio::check(change -> username == username, "Недостачно прав доступа для получения имущества");
} else { //если указанная заявка - это поставка продукта
//то поставку может осуществить только пользователь username в этой заявке
eosio::check(change -> parent_username == username, "Недостаточно прав доступа для получения имущества");
};
//подписываем акт приёма-передачи кооперативу пайщиком
exchange.modify(change, coopname, [&](auto &ch){
ch.status = "recieved1"_n;
ch.recieved_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
ch.warranty_delay_until = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch() + change -> product_lifecycle_secs / 4);
ch.product_recieve_act = document;
});
}
@@ -1,38 +0,0 @@
[[eosio::action]] void marketplace::recievecnfrm(eosio::name coopname, eosio::name username, uint64_t exchange_id, document2 document) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Заявка не найдена");
eosio::check(change -> parent_id > 0, "Только продукт по встречной заявке может быть поставлен");
eosio::check(change -> status == "recieved1"_n, "Продукт может быть поставлен только по заявке в статусе recieved1");
auto soviet = get_board_by_type_or_fail(coopname, "soviet"_n);
auto chairman = soviet.get_chairman();
eosio::check(username == chairman, "Недостачно прав доступа для подтверждения выдачи имущества");
// Проверяем подпись документа
verify_document_or_fail(document);
exchange.modify(change, _marketplace, [&](auto &ch) {
ch.status = "recieved2"_n;
ch.product_recieve_act_validation = document;
});
action(
permission_level{ _marketplace, "active"_n},
_soviet,
"recieved"_n,
std::make_tuple(coopname, exchange_id)
).send();
//уменьшаем паевой фонд
action(
permission_level{ _marketplace, "active"_n},
_fund,
"subcirculate"_n,
std::make_tuple(coopname, change -> total_cost, false)
).send();
}
@@ -1,24 +0,0 @@
[[eosio::action]] void marketplace::supply(eosio::name coopname, eosio::name username, uint64_t exchange_id, document2 document) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Заявка не найдена");
eosio::check(change -> parent_id > 0, "Только продукт по встречной заявке может быть поставлен");
eosio::check(change -> status == "authorized"_n, "Продукт может быть поставлен только по заявке в статусе authorized");
auto soviet = get_board_by_type_or_fail(coopname, "soviet"_n);
auto chairman = soviet.get_chairman();
eosio::check(username == chairman, "Недостаточно прав доступа для подтверждения поставки");
// Проверяем подпись документа
verify_document_or_fail(document);
exchange.modify(change, _marketplace, [&](auto &ch) {
ch.supplied_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
ch.status = "supplied1"_n;
ch.product_contribution_act_validation = document;
});
}
@@ -1,53 +0,0 @@
/**
\ingroup public_actions
\brief Отказ от предложения.
**/
[[eosio::action]] void marketplace::supplycnfrm(eosio::name coopname, eosio::name username, uint64_t exchange_id, document2 document) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Заявка не найдена");
eosio::check(change -> parent_id > 0, "Только продукт по встречной заявке может быть поставлен");
eosio::check(change -> status == "supplied1"_n, "Продукт может быть поставлен только по заявке в статусе supplied1");
auto soviet = get_board_by_type_or_fail(coopname, "soviet"_n);
auto chairman = soviet.get_chairman();
eosio::check(change -> product_contributor == username, "Недостаточно прав доступа для совершения поставки");
// Проверяем подпись документа
verify_document_or_fail(document);
//подписываем акт приёма-передачи кооперативу пайщиком
exchange.modify(change, coopname, [&](auto &ch) {
ch.status = "supplied2"_n;
ch.product_contribution_act = document;
});
std::string memo = "Приём имущественного паевого взноса по программе №" + std::to_string(change -> program_id) + " с ID: " + std::to_string(change -> id);
action(
permission_level{ _marketplace, "active"_n},
_soviet,
"addbal"_n,
std::make_tuple(coopname, change -> product_contributor, change -> program_id, change -> supplier_amount, memo)
).send();
action(
permission_level{ _marketplace, "active"_n},
_soviet,
"blockbal"_n,
std::make_tuple(coopname, change -> product_contributor, change -> program_id, change -> supplier_amount, memo)
).send();
//увеличиваем паевой фонд
action(
permission_level{ _marketplace, "active"_n},
_fund,
"addcirculate"_n,
std::make_tuple(coopname, change -> total_cost)
).send();
}
@@ -1,26 +0,0 @@
/**
\ingroup public_actions
\brief Снять товар с публикации на маркетплейсе.
*
* Этот метод предназначен для снятия товара с публикации. Только владелец товара может снять его с публикации.
*
* @param username Имя пользователя, являющегося владельцем заявки.
* @param exchange_id Идентификатор заявки, которую следует снять с публикации.
*
* @note Авторизация требуется от аккаунта: @p username
*/
[[eosio::action]] void marketplace::unpublish(eosio::name coopname, eosio::name username, uint64_t exchange_id) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Объявление не найдено");
staff_index staff(_soviet, coopname.value);
auto persona = staff.find(username.value);
eosio::check(change->username == username || (persona != staff.end() && persona -> has_right(_marketplace, "unpublish"_n)), "У вас нет права на снятие данной заявки");
exchange.modify(change, username, [&](auto &o){
o.status = "unpublished"_n;
});
};
@@ -1,37 +0,0 @@
/**
\ingroup public_actions
\brief Отмена заявки и возврат токенов.
@details Позволяет пользователю отменить родительскую или дочернюю заявку, а также обеспечивает возврат токенов владельцу (если применимо). При отмене проверяется наличие заявки и её текущий статус. Если заявка является родительской, вызывается метод `cancel_parent`, иначе — `cancel_child`.
@param username Имя пользователя, инициировавшего отмену.
@param exchange_id Идентификатор заявки для отмены.
@note Авторизация требуется от аккаунта: @p username
*/
[[eosio::action]] void marketplace::update(eosio::name coopname, eosio::name username, uint64_t exchange_id, uint64_t remain_units, eosio::asset unit_cost, std::string data, std::string meta) {
require_auth(coopname);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Заявка на обмен не найдена");
eosio::check(unit_cost.symbol == change -> unit_cost.symbol, "Неверный символ токен");
eosio::check(change -> parent_id == 0, "Встречные заявки можно только отменять");
eosio::check(change -> username == username, "У вас нет права на отмену данной заявки");
eosio::check(remain_units >= 0, "Количество единиц товара должно быть положительным");
eosio::name status = "moderation"_n;
if (change -> data == data && change -> meta == meta)
status = change -> status;
exchange.modify(change, _marketplace, [&](auto &i) {
i.status = status;
i.unit_cost = unit_cost;
i.remain_units = remain_units;
i.data = data;
i.meta = meta;
i.supplier_amount = remain_units * unit_cost;
});
}
@@ -1,24 +1,19 @@
eosio::asset minimum = deposit -> quantity == cooperative.registration ? cooperative.minimum : cooperative.org_minimum.value();
eosio::asset initial = deposit -> quantity == cooperative.registration ? cooperative.initial : cooperative.org_initial.value();
// eosio::asset minimum = deposit -> quantity == cooperative.registration ? cooperative.minimum : cooperative.org_minimum.value();
// eosio::asset initial = deposit -> quantity == cooperative.registration ? cooperative.initial : cooperative.org_initial.value();
action(
permission_level{ _gateway, "active"_n},
_fund,
"subcirculate"_n,
std::make_tuple(coopname, minimum, true)
).send();
// Fund::sub_circulating_funds(_gateway, coopname, minimum, true);
//вычесть вступительную сумму из фонда
action(
permission_level{ _gateway, "active"_n},
_fund,
"subinitial"_n,
std::make_tuple(coopname, initial)
).send();
// //вычесть вступительную сумму из фонда
// action(
// permission_level{ _gateway, "active"_n},
// _fund,
// "subinitial"_n,
// std::make_tuple(coopname, initial)
// ).send();
action (
permission_level{ _gateway, "active"_n},
_soviet,
"cancelreg"_n,
std::make_tuple(coopname, deposit -> username, memo)
).send();
// action (
// permission_level{ _gateway, "active"_n},
// _soviet,
// "cancelreg"_n,
// std::make_tuple(coopname, deposit -> username, memo)
// ).send();
@@ -15,7 +15,7 @@
* - устанавливаем дату вступления
* - фиксируем принятый взнос в реестре взносов
* 4. fund::addcirculate (добавляем минимальный взнос)
* 5. fund::spreadamount? (опционально распределяем вступительный взнос по фондам)
* 5. fund::addinitial (добавляем вступительный взнос в кошелек кооператива)
* )
* )
* )
@@ -78,20 +78,10 @@
});
action(
permission_level{ _registrator, "active"_n},
_fund,
"addcirculate"_n,
std::make_tuple(coopname, minimum)
).send();
Fund::add_circulating_funds(_registrator, coopname, minimum);
if (spread_initial) {
action(
permission_level{ _registrator, "active"_n},
_fund,
"addinitial"_n,
std::make_tuple(coopname, initial)
).send();
Fund::add_initial_funds(_registrator, coopname, initial);
}
eosio::name braname = ""_n;
@@ -35,19 +35,9 @@ void registrator::confirmreg(eosio::name coopname, checksum256 registration_hash
).send();
//добавляем
action(
permission_level{ _registrator, "active"_n},
_fund,
"addcirculate"_n,
std::make_tuple(coopname, candidate -> minimum)
).send();
Fund::add_circulating_funds(_registrator, coopname, candidate -> minimum);
action(
permission_level{ _registrator, "active"_n},
_fund,
"addinitial"_n,
std::make_tuple(coopname, candidate -> initial)
).send();
Fund::add_initial_funds(_registrator, coopname, candidate -> initial);
// Увеличиваем счетчик активных пайщиков
cooperatives2_index cooperatives(_registrator, _registrator.value);
@@ -50,9 +50,6 @@
#include "src/fund/fundwithdraw.cpp"
#include "src/marketplace/change.cpp"
#include "src/marketplace/recieved.cpp"
#include "src/participant/addparticipant.cpp"
#include "src/participant/block.cpp"
#include "src/participant/cancelreg.cpp"
@@ -115,12 +115,6 @@ public:
[[eosio::action]] void cancelvote(eosio::name coopname, eosio::name member, uint64_t decision_id);
//marketplace.cpp
[[eosio::action]] void change(eosio::name coopname, eosio::name parent_username, eosio::name username, uint64_t exchange_id, eosio::name money_contributor, eosio::name product_contributor);
[[eosio::action]] void recieved (eosio::name coopname, uint64_t exchange_id);
static void change_effect(eosio::name executer, eosio::name coopname, uint64_t decision_id, uint64_t batch_id);
//programs.cpp
[[eosio::action]] void createprog(eosio::name coopname, eosio::name username, eosio::name type, std::string title, std::string announce, std::string description, std::string preview, std::string images, eosio::name calculation_type, eosio::asset fixed_membership_contribution, uint64_t membership_percent_fee, bool is_can_coop_spend_share_contributions, std::string meta);
[[eosio::action]] void editprog(eosio::name coopname, eosio::name username, uint64_t program_id, uint64_t draft_id, std::string title, std::string announce, std::string description, std::string preview, std::string images, std::string meta);
@@ -19,9 +19,7 @@ void soviet::exec(eosio::name executer, eosio::name coopname, uint64_t decision_
eosio::check(decision != decisions.end(),"Решение не найдено в оперативной памяти");
eosio::check(decision -> authorized == true, "Только авторизованное решение может быть исполнено");
if (decision -> type == _change_action){//операция взноса-возврата взноса в маркетплейсе
soviet::change_effect(executer, coopname, decision->id, decision->batch_id);
} else if (decision -> type == _withdraw_action){//операция возврата паевого взноса из кошелька (вывод)
if (decision -> type == _withdraw_action){//операция возврата паевого взноса из кошелька (вывод)
soviet::withdraw_effect(executer, coopname, decision->id, decision->batch_id);
} else if (decision -> type == _afund_withdraw_action) {//операция использования фонда накопления
soviet::subaccum_effect(executer, coopname, decision->id, decision->batch_id);
@@ -1,126 +0,0 @@
/**
\ingroup public_actions
\brief Обмен в кооперативе
*
* Этот метод вызывается контрактом marketplace при создании запроса на обмен в кооперативе. При успешном вызове формируется повестка со всеми сопровождающими документами, которая отправляется на голосование членам совета.
*
* @param coopname Имя кооператива
* @param exchange_id Идентификатор обмена
* @param orderer Участник, который вносит денежный паевый взнос
* @param offerer Участник, который вносит имущественный паевый взнос
* @param quantity Сумма взаимного зачёта
*
* @note Авторизация требуется от аккаунта: @p _marketplace
*/
void soviet::change(eosio::name coopname, eosio::name parent_username, eosio::name username, uint64_t exchange_id, eosio::name money_contributor, eosio::name product_contributor) {
require_auth(_marketplace);
decisions_index decisions(_soviet, coopname.value);
auto decision_id_1 = get_id(_soviet, coopname, "decisions"_n);
auto decision_id_2 = get_id(_soviet, coopname, "decisions"_n);
changes_index changes(_soviet, coopname.value);
auto batch_id = get_global_id(_soviet, "change"_n);
requests_index exchange(_marketplace, coopname.value);
auto change = exchange.find(exchange_id);
eosio::check(change != exchange.end(), "Заявка не обнаружена");
changes.emplace(_marketplace, [&](auto &c) {
c.id = batch_id;
c.exchange_id = exchange_id;
c.contribution_product_decision_id = decision_id_1;
c.return_product_decision_id = decision_id_2;
});
// Вызов для первого решения
checksum256 hash = eosio::sha256((char*)&batch_id, sizeof(batch_id));
decisions.emplace(_soviet, [&](auto &d) {
d.id = decision_id_1;
d.type = _change_action;
d.batch_id = batch_id;
d.coopname = coopname;
d.username = product_contributor;
d.statement = change -> contribute_product_statement;
d.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
d.expired_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch() + _decision_expiration);
d.hash = hash;
});
decisions.emplace(_soviet, [&](auto &d){
d.id = decision_id_2;
d.type = _change_action;
d.batch_id = batch_id;
d.coopname = coopname;
d.username = money_contributor;
d.statement = change -> return_product_statement;
d.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
d.expired_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch() + _decision_expiration);
d.hash = hash;
});
Action::send<newsubmitted_interface>(
_soviet,
"newsubmitted"_n,
_soviet,
coopname,
product_contributor,
_product_contribution_action,
hash,
change -> contribute_product_statement
);
// Вызов для второго решения
Action::send<newsubmitted_interface>(
_soviet,
"newsubmitted"_n,
_soviet,
coopname,
money_contributor,
_product_return_action,
hash,
change -> return_product_statement
);
};
void soviet::change_effect(eosio::name executer, eosio::name coopname, uint64_t decision_id, uint64_t batch_id) {
decisions_index decisions(_soviet, coopname.value);
auto decision = decisions.find(decision_id);
eosio::check(decision != decisions.end(), "Решение не найдено");
changes_index changes(_soviet, coopname.value);
auto change = changes.find(batch_id);
eosio::check(change != changes.end(), "Объект обмена не найден");
auto contribution_product_decision = decisions.find(change -> contribution_product_decision_id);
auto return_product_decision = decisions.find(change -> return_product_decision_id);
eosio::check(contribution_product_decision != decisions.end(), "Черновик решения о приёме имущества не найден");
eosio::check(return_product_decision != decisions.end(), "Черновик решения о возврате взноса не найден");
if (contribution_product_decision -> authorized == true && return_product_decision -> authorized == true){
// если принимается второе решение, то эффект должен оповестить контракт кооплейса
action(
permission_level{ _soviet, "active"_n},
_marketplace,
"authorize"_n,
std::make_tuple(coopname, change -> exchange_id, contribution_product_decision -> id, contribution_product_decision -> authorization, return_product_decision -> id, return_product_decision -> authorization)
).send();
decisions.erase(contribution_product_decision);
decisions.erase(return_product_decision);
changes.erase(change);
} else {
print("Ожидаем получение второго решения");
// если принимается первое решение, то эффекта при обмене не должно быть
// просто ждём второе решение
}
};
@@ -1,80 +0,0 @@
// удостоверить акт приёма-передачи от кооператива
// это должно срабатывать на выдаче имущества заказчику для формирования закрывающего списка документов
void soviet::recieved (eosio::name coopname, uint64_t exchange_id) {
require_auth(_marketplace);
requests_index exchange(_marketplace, coopname.value);
auto request = exchange.find(exchange_id);
eosio::check(request != exchange.end(), "Заявка не обнаружена");
eosio::check(request -> parent_id > 0, "Только встречная заявка может быть обработана");
checksum256 hash = eosio::sha256((char*)&request -> id, sizeof(request -> id));
Action::send<newdecision_interface>(
_soviet,
"newdecision"_n,
_soviet,
coopname,
request -> money_contributor,
_product_return_action,
hash,
request -> return_product_authorization
);
Action::send<newresolved_interface>(
_soviet,
"newresolved"_n,
_soviet,
coopname,
request -> money_contributor,
_product_return_action,
hash,
request -> return_product_statement
);
Action::send<newact_interface>(
_soviet,
"newact"_n,
_soviet,
coopname,
request -> money_contributor,
_product_return_action,
hash,
request -> product_recieve_act_validation
);
Action::send<newdecision_interface>(
_soviet,
"newdecision"_n,
_soviet,
coopname,
request -> product_contributor,
_product_contribution_action,
hash,
request -> contribution_product_authorization
);
Action::send<newresolved_interface>(
_soviet,
"newresolved"_n,
_soviet,
coopname,
request -> product_contributor,
_product_contribution_action,
hash,
request -> contribute_product_statement
);
Action::send<newact_interface>(
_soviet,
"newact"_n,
_soviet,
coopname,
request -> product_contributor,
_product_contribution_action,
hash,
request -> product_contribution_act_validation
);
};

Some files were not shown because too many files have changed in this diff Show More