feat(marketplace): Story 11.1 — 18 canonical actions через Ledger2::apply на 13 операциях (#375)
Publish Docs / build-and-publish-docs (push) Failing after 15m19s
Publish Docs / build-and-publish-docs (push) Failing after 15m19s
* fix(ledger2): handle WalletOp::REVOKE in walletop dispatcher (Story 11.4) REVOKE (op_code=6) was added to enum and OPERATION_REGISTRY for o.mkt.consum (2026-05-11), but walletop.cpp rejected op_code > 4. Added explicit case: списывает amount с wallet_from.blocked в пустоту, без зачисления; L3-зеркало для USER_SHARED. NONE добавлен как unreachable case для исчерпывающего switch. Прерывает блокер для Story 11.1 (canonical marketplace actions). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(marketplace): canonical scaffolding — entity tables + 18 action skeletons (Story 11.1) Удалены donor-actions старой клиринговой модели (AR30): - 50 .cpp файлов в components/contracts/cpp/marketplace/src/{deliver_on_offer, deliver_on_order,shipment,dispute_on_offer,корень}; - legacy marketplace.{cpp,hpp} с ~30 actions (orderoffer/accept/supply/dispute/ shipment/etc) на legacy Wallet:: API мимо ledger2; - три donor entity-таблицы: lib/domain/table_marketplace_{requests,segments, shipments}.hpp; - lib/core/marketplace/marketplace.hpp donor-helpers (get_request_by_hash, get_shipment_by_hash, namespace DocumentNames клирингового состава). Создана canonical инфраструктура трёх процессов p.mkt.{supply,return,wroff}: - lib/domain/table_marketplace_orders.hpp — Order entity с полным жизненным циклом p.mkt.supply (active → cancelled / accepted → ship_ready → supply_prepared → accepted_to_coop → ready_to_receive → received), 8 secondary индексов, поля для трассировки signsupp/signchair/ signiss1/signiss2 АПП-документов и warranty_until для submretrn гарда. - lib/domain/table_marketplace_return_requests.hpp — ReturnRequest entity для p.mkt.return (pending_review → approved_for_visit → return_accepted / rejected_at_ku / rejected_remote), original_consume_op_id для compensating-forward трассировки (d6 A4), photos vector<checksum256>. - lib/domain/table_marketplace_writeoff_proposals.hpp — WriteoffProposal entity для p.mkt.wroff с vector<wroff_item> per-КУ позиций. - lib/core/marketplace/marketplace.hpp — canonical helpers get_*_by_hash[_or_fail] / update_* + cross-contract get_user_wallet_balance для guard'а Locked Decision L6 в createorder. Контракт marketplace.{cpp,hpp} переписан целиком под 18 canonical actions (p.mkt.supply 10 + p.mkt.return 5 + p.mkt.wroff 3) согласно YAML-стандартам рядом с .hpp. Каждый action — отдельный .cpp файл в marketplace/src/ по образцу Capital'а; на этом коммите все 18 — skeleton с eosio::check(false, "TODO Story 11.1 Шаг N: <action> ещё не реализован") и подробным @brief docstring'ом со ссылкой на Story / guards / ledger2- операции из YAML. Реальная реализация actions — последующими коммитами (Шаги 4-6 спецификации Story 11.1). Batch (consolidated request) намеренно НЕ выведен в on-chain entity — Locked Decision L10: backend-only сущность, on-chain действия per-Order; batch_hash на Order'е — opaque ссылка для трассировки. Foundation Story 11.4 на месте (предыдущий коммит): WalletOp::REVOKE обработан walletop dispatcher'ом; OPERATION_REGISTRY содержит все 13 marketplace-операций; счета 10/91 в LEDGER2_ACCOUNT_MAP. Spec артефакт: blago/production/1-prilozhenie-stol-zakazov/components/ 3-minimalnyy-produkt/_bmad-output/implementation-artifacts/ spec-11-1-ledger2-marketplace-ops.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(marketplace): implement createorder action (Story 4.1, p.mkt.supply) Первая реализация canonical action — proof-of-pattern для остальных 17. Серия трёх ledger2-операций (атомарно в одной транзакции Antelope): - o.wal.conv (conditional, недостача из w.wal.share); - o.mkt.assign (conditional, недостача из w.wal.member); - o.mkt.block (всегда, total_cost на w.mkt.member). L6 guard через cross-contract read трёх кошельков (Marketplace:: get_user_wallet_balance) — фейлится с понятной ошибкой если суммарных средств не хватает. process_hash для всех трёх ops = order_hash. Создан Order entity со status=ACTIVE; cycle_type валидируется по enum (timebased/volumebased/opensubscr/individual). actual_quantity и fact_cost ставятся равными quantity/total_cost — обновятся в signiss2 при выдаче. Plus: rename WroffStatus::DRAFT → PROPOSED (DRAFT конфликтует с macro из lib/consts.hpp; on-chain строка осталась "draft"_n как в YAML). Build: pnpm build:contract:test marketplace — green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(marketplace): implement 17 canonical actions (Story 11.1, p.mkt.{supply,return,wroff}) Реализованы все оставшиеся 17 actions поверх createorder (proof-of-pattern): p.mkt.supply (9 actions, дополнительно к уже реализованному createorder): - cancelorder — отмена до акцепта; o.mkt.unblk; ACTIVE → CANCELLED. - expirecycle — закрытие цикла; threshold_reached==false → o.mkt.unblk per order + ACTIVE → CANCELLED; threshold_reached==true — no-op (backend формирует batch). - acceptbatch — поставщик акцепт; ACTIVE → ACCEPTED, batch_hash зафиксирован; без ledger2 (L10). - declinebatch — отказ до акцепта; o.mkt.unblk per order + ACTIVE → CANCELLED. - prepship — сборка партии; ACCEPTED → SHIP_READY; shipping_method валидируется (varianta | variantb). - signsupp — первая подпись АПП приёмки поставщиком; verify_document_or_fail ({offerer}); SHIP_READY → SUPPLY_PREPARED + acceptance_act_signsupp. - signchair — закрывающая подпись АПП приёмки председателем; per-Order Ledger2::apply(o.mkt.purch) + Ledger2::apply(o.mkt.payout) атомарно; SUPPLY_PREPARED → ACCEPTED_TO_COOP + acceptance_act_signchair. - signiss1 — открытие выдачи председателем; verify_document_or_fail({chairman}); ACCEPTED_TO_COOP → READY_TO_RECEIVE + issue_act_signiss1. - signiss2 — финальная подпись заказчика с поддержкой actual_quantity ≠ ordered: при actual<ordered — o.mkt.unblk на разницу; при actual>ordered — L6 guard на доступность diff в трёх кошельках, conditional o.wal.conv + o.mkt.assign + o.mkt.block на разницу; всегда — o.mkt.consum + o.mkt.consum2 на fact_cost; READY_TO_RECEIVE → RECEIVED + warranty_until. p.mkt.return (5 actions): - submretrn — заявление на возврат; гард warranty_until > now; создание return_request в PENDING_REVIEW; order.return_request_id для двусторонней связи. photos обязательны. - aprretrem — удалённое одобрение визита; PENDING_REVIEW → APPROVED_FOR_VISIT. - rejretrem — удалённый отказ; PENDING_REVIEW → REJECTED_REMOTE + reason_remote. - accretrn — приём возврата на очном осмотре; Ledger2::apply(o.mkt.return) + Ledger2::apply(o.mkt.return2) (compensating forward, не revert); APPROVED_FOR_VISIT → RETURN_ACCEPTED. - rejretrn — отказ на очном осмотре; APPROVED_FOR_VISIT → REJECTED_AT_KU + reason_visit. p.mkt.wroff (3 actions): - propwroff — формирование проекта списания; total_amount = Σ items.amount; PROPOSED. - execwroff — исполнение per-item: Ledger2::apply(o.mkt.wroff) + Ledger2::apply(o.mkt.wroff2) каждой позиции; PROPOSED → EXECUTED; process_hash единый = proposal.hash, item.ku_chairman как username для per-КУ аналитики. - declwroff — отклонение советом; PROPOSED → REJECTED + reject_reason. Все compose'ятся через единый Ledger2::apply orchestrator, не нарушая foundation Story 11.4. Composite-операции (consum+consum2, return+return2, wroff+wroff2) в одной транзакции Antelope обеспечивают атомарность L1 проводок через транзит счёта 91. Cross-contract balance read через Marketplace::get_user_wallet_balance(_ledger2 scope) — для L6-гарда createorder и signiss2 (correction>). Build: pnpm build:contract:test marketplace — green. Foundation: walletop REVOKE + 13 marketplace operations + WalletOp NONE/REVOKE + счета 10/91 + кошельки w.mkt.{member,payout} — на месте (предыдущие коммиты). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(marketplace): структура по процессам + чистка избыточных дат on-chain Структура: - src/p.mkt.supply/ — 10 actions процесса прямой поставки-приобретения (createorder, cancelorder, expirecycle, acceptbatch, declinebatch, prepship, signsupp, signchair, signiss1, signiss2); - src/p.mkt.return/ — 5 actions процесса гарантийного возврата (submretrn, aprretrem, rejretrem, accretrn, rejretrn); - src/p.mkt.wroff/ — 3 actions процесса утилизации скоропорта (списания) (propwroff, execwroff, declwroff). Имена подпапок 1:1 совпадают с process_type из YAML-стандартов рядом — прозрачная навигация от .cpp к стандарту. Удалены избыточные date-поля из 3 entity-таблиц (orders / return_requests / writeoff_proposals): created_at, accepted_at, shipped_at, received_to_coop_at, ready_at, received_at, cancelled_at, reviewed_at, resolved_at, proposed_at, decided_at. Контракт ничего не проверяет по этим датам — все timestamp'ы переходов состояний восстанавливаются на бэкенде из blockchain_actions[at] по соответствующим actions через ParserClient. Сокращает RAM-стоимость state на каждый Order / ReturnRequest / WriteoffProposal. Сохранено on-chain: - order.warranty_until — нужно для guard'а submretrn (`now() < warranty_until`) без cross-action lookup; вычисляется в signiss2 как signiss2.timestamp + warranty_period_secs. - batch_hash, status, документы (acceptance_act_signsupp/signchair, issue_act_signiss1/signiss2, statement, decision_remote/visit, protocol, photos), reason fields, fact_cost / actual_quantity, items для wroff_proposal, links (return_request_id, original_order_id, original_consume_op_id) — это смысловая on-chain информация, не выводимая из истории. Удалены secondary-индексы bycreated и byproposed (бэкенд индексирует по сохранённым в Postgres timestamp'ам блоков, on-chain pagination по чрон. порядку = по primary key id). Build: pnpm build:contract:test marketplace — green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(marketplace): ревью PR #375 — braname/branch, per-Order, memos, без prepship - ku_chairman заменён на braname (приёмка/выдача) в Order; в return_request привязка к КУ удалена — каждое решение принимает braname параметром, авторизация через Branch::is_user_authorized (председатель/trustee/доверенные) - Все per-batch actions теперь per-Order (acceptorder/declineorder/expireorder, signsupp/signchair); векторов order_hashes нет — backend проходит циклом - prepship удалён как избыточный шаг (после acceptorder сразу signsupp); OrderStatus::SHIP_READY убран - execwroff per-item: один item за вызов с item_index; финализация PROPOSED → EXECUTED автоматически после исполнения последней позиции; declwroff фейлится если хотя бы одна позиция уже исполнена - propwroff валидирует существование braname для каждого item через get_branch_or_fail - Memos вынесены в lib/core/marketplace/memo.hpp по аналогии с Capital::Memo; все Ledger2::apply используют человекочитаемые memo с подстановкой id - Все eosio::check сообщения переписаны на пользовательский русский текст без префиксов action/Locked Decision (UI показывает их напрямую) - walletop REVOKE дополнен подробным docstring: разница с BURN (blocked vs available, обязательная пара Dr/Cr, отсутствие альтернативы через UNBLOCK+BURN) Build: marketplace.wasm + ledger2.wasm — green под coopos 5.2. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(marketplace): order.current_warehouse_braname (текущая точка хранения) Поле введено сейчас, чтобы избежать binary_extension при включении полноценного процесса перемещения имущества между КУ после релиза. Заполнение: - signchair (приёмка кооперативом): current_warehouse_braname = accept_braname - signiss1 (готово к выдаче): current_warehouse_braname = delivery_braname Промежуточные перемещения между приёмным и выдающим КУ контрактом не подписываются (бездокументарно) — точка хранения переходит «скачком» в момент готовности к выдаче. Подписание ТТН по внутренним передачам — отдельная история после релиза, текущее поле даёт ей точку расширения без миграции. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: coopops <coopos@coopenomics.world> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -56,7 +56,9 @@ void ledger2::walletop(eosio::name coopname,
|
||||
// op_code = 5 (NONE) намеренно не допускается: NONE-операции — это только
|
||||
// бухпроводка без кошелькового движения, apply.cpp не диспатчит для них walletop.
|
||||
// Прямой вызов с op_code=5 был бы no-op и сбил бы инвариант parity.
|
||||
eosio::check(op_code <= 4, "walletop: неизвестный op_code");
|
||||
// op_code = 6 (REVOKE) — целевое расходование blocked в пустоту; обязательная пара Dr/Cr;
|
||||
// допускается, обрабатывается отдельным case ниже (введён 2026-05-11 под o.mkt.consum).
|
||||
eosio::check(op_code <= 6 && op_code != 5, "walletop: неизвестный op_code");
|
||||
|
||||
wallets2_index wallets(get_self(), coopname.value);
|
||||
userwallets_index user_wallets(get_self(), coopname.value);
|
||||
@@ -298,6 +300,65 @@ void ledger2::walletop(eosio::name coopname,
|
||||
user_wallets.modify(uw_pri, payer, [&](auto& r) { r.available -= amount; });
|
||||
}
|
||||
|
||||
cleanup_l2_if_empty(wallet_from);
|
||||
cleanup_l3_if_empty(wallet_from);
|
||||
break;
|
||||
}
|
||||
case WalletOp::NONE: {
|
||||
// unreachable: apply.cpp не диспатчит walletop для NONE-операций
|
||||
// (бухпроводка без кошелькового движения). Pre-check на op_code != 5 выше
|
||||
// отсекает прямой вызов с этим кодом. case оставлен для исчерпывающего
|
||||
// switch — иначе компилятор предупредит про unhandled enum value.
|
||||
eosio::check(false, "walletop NONE: must be unreachable");
|
||||
break;
|
||||
}
|
||||
case WalletOp::REVOKE: {
|
||||
// Целевое расходование заблокированной суммы: amount списывается с
|
||||
// wallet_from.blocked в пустоту, без зачисления на wallet_to.
|
||||
//
|
||||
// Отличие от BURN:
|
||||
// - BURN списывает available (свободный остаток). Применяется к штатному
|
||||
// «сжиганию» свободных средств (например, DROP_PREIMP при переходе на
|
||||
// электронный учёт РИД-взноса). Dr/Cr пара опциональна.
|
||||
// - REVOKE списывает blocked (зарезервированный остаток). Применяется на
|
||||
// финализации заранее зарезервированной операции, когда резерв
|
||||
// «уходит в потребление», а не возвращается. Обязательная пара Dr/Cr
|
||||
// на уровне OPERATION_REGISTRY (compile-time validator
|
||||
// revoke_pattern_correct в operations.hpp).
|
||||
//
|
||||
// Применение в членской модели Стола заказов: o.mkt.consum (выдача
|
||||
// имущества пайщику по АПП выдачи). Пайщик заблокировал сумму под Order
|
||||
// на createorder (BLOCK на w.mkt.member). При фактической выдаче
|
||||
// имущества этот blocked-резерв должен «сгореть» как целевое
|
||||
// потребление с одновременной фиксацией Дт 91 / Кт 10 (выбытие
|
||||
// имущества со склада на счёт «прочие»). Альтернатива через
|
||||
// UNBLOCK + BURN — это две операции с искажённой семантикой:
|
||||
// «снял резерв» (которого по факту не снимал — потребил) +
|
||||
// «сжёг свободные» (которые в моменте не были свободными). REVOKE
|
||||
// одной операцией точно описывает целевое расходование blocked.
|
||||
//
|
||||
// Не путать с rollback: ledger2 не использует ledger2::revert в
|
||||
// membership-модели marketplace; компенсирующие проводки делаются
|
||||
// forward через обратную операцию (см. RETURN_BY_MEMBER + RETURN_TRANSIT_CLOSE).
|
||||
eosio::check(wallet_from.value != 0, "walletop REVOKE: требуется wallet_from");
|
||||
eosio::check(wallet_to.value == 0, "walletop REVOKE: wallet_to должен быть пустым");
|
||||
|
||||
auto it = wallets.find(wallet_from.value);
|
||||
eosio::check(it != wallets.end() && it->blocked >= amount,
|
||||
std::string{"walletop REVOKE: недостаточно blocked на кошельке "} +
|
||||
wallet_from.to_string());
|
||||
wallets.modify(it, payer, [&](auto& w) { w.blocked -= amount; });
|
||||
|
||||
if (is_user_shared_l3(wallet_from)) {
|
||||
auto uw = find_l3(wallet_from);
|
||||
eosio::check(uw != user_wallets.get_index<"byuserwallet"_n>().end() &&
|
||||
uw->blocked >= amount,
|
||||
std::string{"walletop REVOKE: недостаточно L3-blocked у пайщика "} +
|
||||
username.to_string() + " на " + wallet_from.to_string());
|
||||
auto uw_pri = user_wallets.find(uw->id);
|
||||
user_wallets.modify(uw_pri, payer, [&](auto& r) { r.blocked -= amount; });
|
||||
}
|
||||
|
||||
cleanup_l2_if_empty(wallet_from);
|
||||
cleanup_l3_if_empty(wallet_from);
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <eosio/crypto.hpp>
|
||||
@@ -9,93 +9,132 @@
|
||||
|
||||
#include "../../consts.hpp"
|
||||
#include "../../domain/document_core.hpp"
|
||||
#include "../../domain/table_ledger2_userwallets.hpp"
|
||||
#include "../../domain/table_marketplace_orders.hpp"
|
||||
#include "../../domain/table_marketplace_return_requests.hpp"
|
||||
#include "../../domain/table_marketplace_writeoff_proposals.hpp"
|
||||
|
||||
#define AUTH_SIGNATURE eosio::name coopname, checksum256 request_hash, document2 authorization
|
||||
|
||||
using auth_interface = void(AUTH_SIGNATURE);
|
||||
|
||||
/**
|
||||
* @brief Canonical helpers контракта marketplace (Story 11.1).
|
||||
*
|
||||
* Donor-helpers (`get_request_by_hash`, `get_shipment_by_hash`, namespace
|
||||
* `DocumentNames`, `marketplace_callback_actions`) удалены вместе с
|
||||
* соответствующими actions и таблицами (AR30).
|
||||
*
|
||||
* Этот файл содержит только утилиты доступа к canonical-сущностям трёх
|
||||
* процессов p.mkt.supply / p.mkt.return / p.mkt.wroff и helper'ы для
|
||||
* проверки доступного баланса (для createorder guard'а Locked Decision L6).
|
||||
*/
|
||||
namespace Marketplace {
|
||||
|
||||
using namespace eosio;
|
||||
|
||||
static const std::set<eosio::name> marketplace_callback_actions = {
|
||||
"authoffs2c"_n,
|
||||
"authoffc2r"_n,
|
||||
"authordcont"_n,
|
||||
"authordret"_n,
|
||||
"declineacc"_n,
|
||||
// ── Orders ──────────────────────────────────────────────────────────────
|
||||
|
||||
inline std::optional<order> get_order_by_hash(eosio::name coopname, const checksum256& order_hash) {
|
||||
orders_index orders(_marketplace, coopname.value);
|
||||
auto idx = orders.get_index<"byhash"_n>();
|
||||
auto it = idx.find(order_hash);
|
||||
if (it == idx.end()) return std::nullopt;
|
||||
return *it;
|
||||
}
|
||||
|
||||
inline order get_order_by_hash_or_fail(eosio::name coopname, const checksum256& order_hash,
|
||||
const std::string& msg = "Заказ не найден по хэшу") {
|
||||
auto o = get_order_by_hash(coopname, order_hash);
|
||||
eosio::check(o.has_value(), msg);
|
||||
return *o;
|
||||
}
|
||||
|
||||
inline void update_order(eosio::name coopname, uint64_t order_id, const std::function<void(order&)>& fn) {
|
||||
orders_index orders(_marketplace, coopname.value);
|
||||
auto it = orders.find(order_id);
|
||||
eosio::check(it != orders.end(), "Заказ не найден по id");
|
||||
orders.modify(it, _marketplace, [&](auto& o) { fn(o); });
|
||||
}
|
||||
|
||||
// ── Return requests ─────────────────────────────────────────────────────
|
||||
|
||||
inline std::optional<return_request> get_return_request_by_hash(eosio::name coopname,
|
||||
const checksum256& request_hash) {
|
||||
return_requests_index requests(_marketplace, coopname.value);
|
||||
auto idx = requests.get_index<"byhash"_n>();
|
||||
auto it = idx.find(request_hash);
|
||||
if (it == idx.end()) return std::nullopt;
|
||||
return *it;
|
||||
}
|
||||
|
||||
inline return_request get_return_request_by_hash_or_fail(eosio::name coopname,
|
||||
const checksum256& request_hash,
|
||||
const std::string& msg = "Заявление на возврат не найдено по хэшу") {
|
||||
auto r = get_return_request_by_hash(coopname, request_hash);
|
||||
eosio::check(r.has_value(), msg);
|
||||
return *r;
|
||||
}
|
||||
|
||||
inline void update_return_request(eosio::name coopname, uint64_t request_id,
|
||||
const std::function<void(return_request&)>& fn) {
|
||||
return_requests_index requests(_marketplace, coopname.value);
|
||||
auto it = requests.find(request_id);
|
||||
eosio::check(it != requests.end(), "Заявление на возврат не найдено по id");
|
||||
requests.modify(it, _marketplace, [&](auto& r) { fn(r); });
|
||||
}
|
||||
|
||||
// ── Writeoff proposals ──────────────────────────────────────────────────
|
||||
|
||||
inline std::optional<writeoff_proposal> get_writeoff_proposal_by_hash(eosio::name coopname,
|
||||
const checksum256& proposal_hash) {
|
||||
writeoff_proposals_index proposals(_marketplace, coopname.value);
|
||||
auto idx = proposals.get_index<"byhash"_n>();
|
||||
auto it = idx.find(proposal_hash);
|
||||
if (it == idx.end()) return std::nullopt;
|
||||
return *it;
|
||||
}
|
||||
|
||||
inline writeoff_proposal get_writeoff_proposal_by_hash_or_fail(eosio::name coopname,
|
||||
const checksum256& proposal_hash,
|
||||
const std::string& msg = "Проект списания не найден по хэшу") {
|
||||
auto p = get_writeoff_proposal_by_hash(coopname, proposal_hash);
|
||||
eosio::check(p.has_value(), msg);
|
||||
return *p;
|
||||
}
|
||||
|
||||
inline void update_writeoff_proposal(eosio::name coopname, uint64_t proposal_id,
|
||||
const std::function<void(writeoff_proposal&)>& fn) {
|
||||
writeoff_proposals_index proposals(_marketplace, coopname.value);
|
||||
auto it = proposals.find(proposal_id);
|
||||
eosio::check(it != proposals.end(), "Проект списания не найден по id");
|
||||
proposals.modify(it, _marketplace, [&](auto& p) { fn(p); });
|
||||
}
|
||||
|
||||
// ── Cross-contract read: ledger2 wallet/userwallet balances ─────────────
|
||||
//
|
||||
// Используется в createorder для guard'а Locked Decision L6 (без отрицательного
|
||||
// баланса) и для решения о пропуске conditional операций o.wal.conv / o.mkt.assign
|
||||
// (если на целевом кошельке уже хватает available — соответствующий шаг скипается).
|
||||
//
|
||||
// ВАЖНО: контракт marketplace не вызывает ledger2::walletop напрямую, а только
|
||||
// читает state (RAM-таблицы wallets2 / userwallets через cross-contract scope).
|
||||
// Все мутации идут через `Ledger2::apply` (см. lib/core/ledger2/ledger2.hpp).
|
||||
|
||||
struct UserWalletAvailable {
|
||||
eosio::asset available = eosio::asset(0, _root_govern_symbol);
|
||||
eosio::asset blocked = eosio::asset(0, _root_govern_symbol);
|
||||
bool exists = false;
|
||||
};
|
||||
|
||||
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;
|
||||
inline UserWalletAvailable get_user_wallet_balance(eosio::name coopname,
|
||||
eosio::name wallet_id,
|
||||
eosio::name username) {
|
||||
// userwallets_index — глобальный typedef в lib/domain/table_ledger2_userwallets.hpp.
|
||||
// Для cross-contract read берём scope = coopname.value, code = _ledger2.
|
||||
userwallets_index user_wallets(_ledger2, coopname.value);
|
||||
auto idx = user_wallets.get_index<"byuserwallet"_n>();
|
||||
auto it = idx.find(combine_ids(wallet_id.value, username.value));
|
||||
if (it == idx.end()) {
|
||||
return UserWalletAvailable{};
|
||||
}
|
||||
return std::nullopt;
|
||||
return UserWalletAvailable{ it->available, it->blocked, true };
|
||||
}
|
||||
|
||||
static std::optional<shipment> get_shipment_by_hash(eosio::name coopname, checksum256 shipment_hash) {
|
||||
shipments_index shipments(_marketplace, coopname.value);
|
||||
auto idx = shipments.get_index<"byhash"_n>();
|
||||
auto ship = idx.find(shipment_hash);
|
||||
|
||||
if (ship != idx.end()) {
|
||||
return *ship;
|
||||
}
|
||||
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 shipment get_shipment_by_hash_or_fail(eosio::name coopname, checksum256 shipment_hash,
|
||||
const std::string &error_msg = "Перевозка не найдена по хэшу") {
|
||||
auto shipment_opt = get_shipment_by_hash(coopname, shipment_hash);
|
||||
eosio::check(shipment_opt.has_value(), error_msg);
|
||||
return *shipment_opt;
|
||||
}
|
||||
|
||||
namespace DocumentNames {
|
||||
static constexpr const name RETURN_STMT = "returnstmt"_n;
|
||||
static constexpr const name CONVERT_FROM = "convertfrom"_n;
|
||||
static constexpr const name CONVERT_TO = "convertto"_n;
|
||||
static constexpr const name CONTRIB_STMT = "contribstmt"_n;
|
||||
static constexpr const name CONTRIB_AUTH = "contribauth"_n;
|
||||
static constexpr const name RETURN_AUTH = "returnauth"_n;
|
||||
static constexpr const name RECEIVE_ACT = "receiveact"_n;
|
||||
static constexpr const name RECEIVE_ACT_CONF = "receiveconf"_n;
|
||||
static constexpr const name TRANSPORT1 = "transport1"_n;
|
||||
static constexpr const name TRANSPORT2 = "transport2"_n;
|
||||
static constexpr const name TRANSPORT3 = "transport3"_n;
|
||||
static constexpr const name TRANSPORT4 = "transport4"_n;
|
||||
static constexpr const name SUPPLY_ACT = "supplyact"_n;
|
||||
static constexpr const name SUPPLY_ACT_CONF = "supplyconf"_n;
|
||||
static constexpr const name SHIPMENT_ACT = "shipmentact"_n;
|
||||
static constexpr const name DELIVERY_ACT = "deliveryact"_n;
|
||||
|
||||
static constexpr const name SHIPMENT_SEND_ACT = "shsendact"_n;
|
||||
static constexpr const name SHIPMENT_LOADING_ACT = "shloadact"_n;
|
||||
static constexpr const name SHIPMENT_ARRIVE_ACT = "sharriveact"_n;
|
||||
static constexpr const name SHIPMENT_RECV_ACT = "shrecvact"_n;
|
||||
|
||||
static constexpr const name WDISPUTE = "wdispute"_n;
|
||||
static constexpr const name WRETURN_AUTH = "wreturnauth"_n;
|
||||
static constexpr const name WSUPPLY_AUTH = "wsupplyauth"_n;
|
||||
static constexpr const name WRETURN_ACT = "wreturnact"_n;
|
||||
static constexpr const name WOFFER_ACT = "wofferact"_n;
|
||||
static constexpr const name WACCEPT_ACT = "wacceptact"_n;
|
||||
} // namespace DocumentNames
|
||||
|
||||
} // namespace Marketplace
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include <eosio/asset.hpp>
|
||||
#include <eosio/crypto.hpp>
|
||||
#include <eosio/eosio.hpp>
|
||||
#include <string>
|
||||
|
||||
#include "../../core/utils.hpp"
|
||||
|
||||
/**
|
||||
* @file memo.hpp
|
||||
* @brief Человекочитаемые memo для marketplace ledger2-операций.
|
||||
*
|
||||
* Аналог `Capital::Memo` (см. `cpp/capital/domain/entities/memo.hpp`).
|
||||
* Текст memo попадает в журнал ledger2 (поле `journal.memo`) и в выписки
|
||||
* пайщика / отчёт бухгалтеру — это пользовательский слой, поэтому никаких
|
||||
* технических токенов ("p.mkt.supply", "L6", op_code) здесь быть не должно.
|
||||
*
|
||||
* Параметризуется значимыми идентификаторами (id Order'а) — для трассировки
|
||||
* в выписке без необходимости резолвить hash через backend.
|
||||
*/
|
||||
namespace Marketplace::Memo {
|
||||
|
||||
// ---------------------------------------------------------------- p.mkt.supply
|
||||
|
||||
inline std::string get_create_order_block_memo(uint64_t order_id) {
|
||||
return "Резерв средств под заказ имущества № " + std::to_string(order_id) + " в Столе заказов";
|
||||
}
|
||||
|
||||
inline std::string get_create_order_assign_memo(uint64_t order_id) {
|
||||
return "Целевое назначение взноса под заказ имущества № " + std::to_string(order_id) + " в Столе заказов";
|
||||
}
|
||||
|
||||
inline std::string get_create_order_convert_memo(uint64_t order_id) {
|
||||
return "Конвертация паевого взноса в членский для заказа имущества № " + std::to_string(order_id) + " в Столе заказов";
|
||||
}
|
||||
|
||||
inline std::string get_cancel_order_memo(uint64_t order_id) {
|
||||
return "Возврат резерва по отменённому заказу имущества № " + std::to_string(order_id) + " в Столе заказов";
|
||||
}
|
||||
|
||||
inline std::string get_decline_order_memo(uint64_t order_id) {
|
||||
return "Возврат резерва по отклонённому поставщиком заказу имущества № " + std::to_string(order_id) + " в Столе заказов";
|
||||
}
|
||||
|
||||
inline std::string get_expire_order_memo(uint64_t order_id) {
|
||||
return "Возврат резерва по неисполненному в срок заказу имущества № " + std::to_string(order_id) + " в Столе заказов";
|
||||
}
|
||||
|
||||
inline std::string get_purchase_from_supplier_memo(uint64_t order_id) {
|
||||
return "Приёмка имущества от поставщика по заказу № " + std::to_string(order_id) + " на склад кооператива";
|
||||
}
|
||||
|
||||
inline std::string get_pay_supplier_memo(uint64_t order_id) {
|
||||
return "Оплата поставщику имущества по заказу № " + std::to_string(order_id);
|
||||
}
|
||||
|
||||
inline std::string get_signiss2_correction_less_memo(uint64_t order_id) {
|
||||
return "Возврат разницы пайщику: фактически выдано меньше заказанного по заказу имущества № " + std::to_string(order_id);
|
||||
}
|
||||
|
||||
inline std::string get_signiss2_correction_more_convert_memo(uint64_t order_id) {
|
||||
return "Конвертация паевого взноса в членский на доплату по заказу имущества № " + std::to_string(order_id) + " (фактически выдано больше заказанного)";
|
||||
}
|
||||
|
||||
inline std::string get_signiss2_correction_more_assign_memo(uint64_t order_id) {
|
||||
return "Целевое назначение взноса на доплату по заказу имущества № " + std::to_string(order_id) + " (фактически выдано больше заказанного)";
|
||||
}
|
||||
|
||||
inline std::string get_signiss2_correction_more_block_memo(uint64_t order_id) {
|
||||
return "Резерв на доплату по заказу имущества № " + std::to_string(order_id) + " (фактически выдано больше заказанного)";
|
||||
}
|
||||
|
||||
inline std::string get_consume_by_member_memo(uint64_t order_id) {
|
||||
return "Выдача имущества пайщику по заказу № " + std::to_string(order_id) + ": выбытие со склада";
|
||||
}
|
||||
|
||||
inline std::string get_consume_transit_close_memo(uint64_t order_id) {
|
||||
return "Выдача имущества пайщику по заказу № " + std::to_string(order_id) + ": списание целевого назначения членского взноса";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- p.mkt.return
|
||||
|
||||
inline std::string get_return_by_member_memo(uint64_t return_request_id, uint64_t order_id) {
|
||||
return "Гарантийный возврат имущества пайщиком по заявлению № " + std::to_string(return_request_id) + " (исходный заказ № " + std::to_string(order_id) + "): восстановление членского взноса";
|
||||
}
|
||||
|
||||
inline std::string get_return_transit_close_memo(uint64_t return_request_id, uint64_t order_id) {
|
||||
return "Гарантийный возврат имущества пайщиком по заявлению № " + std::to_string(return_request_id) + " (исходный заказ № " + std::to_string(order_id) + "): возврат имущества на склад";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- p.mkt.wroff
|
||||
|
||||
inline std::string get_writeoff_memo(uint64_t proposal_id, uint64_t item_index) {
|
||||
return "Утилизация скоропорта по решению совета № " + std::to_string(proposal_id) + ", позиция " + std::to_string(item_index + 1) + ": выбытие со склада";
|
||||
}
|
||||
|
||||
inline std::string get_writeoff_transit_close_memo(uint64_t proposal_id, uint64_t item_index) {
|
||||
return "Утилизация скоропорта по решению совета № " + std::to_string(proposal_id) + ", позиция " + std::to_string(item_index + 1) + ": списание целевого назначения";
|
||||
}
|
||||
|
||||
} // namespace Marketplace::Memo
|
||||
@@ -65,9 +65,14 @@
|
||||
#include "table_ledger2_meta.hpp"
|
||||
#include "table_loan_debts.hpp"
|
||||
#include "table_loan_summaries.hpp"
|
||||
#include "table_marketplace_requests.hpp"
|
||||
#include "table_marketplace_segments.hpp"
|
||||
#include "table_marketplace_shipments.hpp"
|
||||
|
||||
// marketplace (Story 11.1, canonical) — anchor-таблицы трёх процессов
|
||||
// p.mkt.supply / p.mkt.return / p.mkt.wroff. Donor-таблицы (requests/segments/
|
||||
// shipments) удалены вместе с donor-actions (AR30). Batch (consolidated request)
|
||||
// — backend-only, on-chain не хранится (Locked Decision L10).
|
||||
#include "table_marketplace_orders.hpp"
|
||||
#include "table_marketplace_return_requests.hpp"
|
||||
#include "table_marketplace_writeoff_proposals.hpp"
|
||||
|
||||
// apps (каталог приложений)
|
||||
#include "table_apps_packages.hpp"
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
#pragma once
|
||||
|
||||
#include <eosio/asset.hpp>
|
||||
#include <eosio/crypto.hpp>
|
||||
#include <eosio/eosio.hpp>
|
||||
#include <string>
|
||||
|
||||
#include "../consts.hpp"
|
||||
#include "../core/document.hpp"
|
||||
#include "../core/utils.hpp"
|
||||
|
||||
namespace Marketplace {
|
||||
|
||||
using namespace eosio;
|
||||
|
||||
/**
|
||||
* @brief Статусы Order'а в процессе p.mkt.supply.
|
||||
*
|
||||
* Граф: ∅ → active → cancelled (canceled by orderer | expireorder | declineorder)
|
||||
* → accepted → supply_prepared → accepted_to_coop
|
||||
* → ready_to_receive → received
|
||||
*
|
||||
* Источник правды — `p.mkt.supply.standard.yaml` секция `states:`.
|
||||
*
|
||||
* Промежуточный статус `ship_ready` (после prepship поставщика) удалён:
|
||||
* после `acceptorder` поставщик автоматически считается обязанным доставить
|
||||
* партию — двойного подтверждения «принял заявку» + «готов отгружать» не
|
||||
* требуется (отдельная подпись «готов отгрузить» лишь добавляет шум в UX).
|
||||
* Переход accepted → supply_prepared идёт сразу через signsupp.
|
||||
*/
|
||||
namespace OrderStatus {
|
||||
inline constexpr eosio::name ACTIVE = "active"_n;
|
||||
inline constexpr eosio::name CANCELLED = "cancelled"_n;
|
||||
inline constexpr eosio::name ACCEPTED = "accepted"_n;
|
||||
inline constexpr eosio::name SUPPLY_PREPARED = "supplyprep"_n;
|
||||
inline constexpr eosio::name ACCEPTED_TO_COOP = "acceptcoop"_n;
|
||||
inline constexpr eosio::name READY_TO_RECEIVE = "readyrecv"_n;
|
||||
inline constexpr eosio::name RECEIVED = "received"_n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Тип цикла отсечки заявок поставщика (атрибут Offer'а — Locked Decision L11).
|
||||
*
|
||||
* Сохраняется на Order'е, потому что фактический cycle_type фиксируется в момент
|
||||
* createorder и не меняется при последующем редактировании Offer'а поставщиком.
|
||||
*/
|
||||
namespace CycleType {
|
||||
inline constexpr eosio::name TIME_BASED = "timebased"_n;
|
||||
inline constexpr eosio::name VOLUME_BASED = "volumebased"_n;
|
||||
inline constexpr eosio::name OPEN_SUBSCRIPT = "opensubscr"_n;
|
||||
inline constexpr eosio::name INDIVIDUAL = "individual"_n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief On-chain Order — анкер процесса p.mkt.supply.
|
||||
*
|
||||
* scope = coopname; primary_key = id; уникальность через `byhash` индекс на
|
||||
* `order.hash` — этот hash используется как `process_hash` во всех ledger2-
|
||||
* операциях процесса (BLOCK/UNBLOCK/PURCH/PAYOUT/CONSUM/CONSUM2/RETURN).
|
||||
*
|
||||
* Привязка к кооперативным участкам (КУ) идёт через `braname` (см. контракт
|
||||
* `branch`, таблица `branches`). Председатель / trustee / доверенные лица
|
||||
* каждого КУ известны контракту `branch` — авторизация подписей актов
|
||||
* выполняется через `Branch::is_user_authorized(coopname, braname, signer)`,
|
||||
* а не по сохранённому имени председателя (председатель может делегировать
|
||||
* подпись доверенному лицу из `coobranch.trusted[]`, состав которого может
|
||||
* меняться независимо от Order'а).
|
||||
*
|
||||
* Точки контракта:
|
||||
* - `delivery_braname` — КУ выдачи имущества пайщику; задаётся пайщиком на
|
||||
* createorder и неизменна. Источник проверки signiss1/signiss2/p.mkt.return.
|
||||
* - `accept_braname` — КУ приёмки от поставщика; заполняется на signsupp
|
||||
* как параметр action'а (поставщик указывает, в какой КУ сдаёт партию).
|
||||
* Источник проверки signchair.
|
||||
* - `current_warehouse_braname` — текущая точка хранения имущества по этому
|
||||
* Order'у. Заполняется на signchair (= `accept_braname`, имущество на
|
||||
* приёмном складе) и обновляется на signiss1 (= `delivery_braname`, готово
|
||||
* к выдаче — фиксирует факт логистической передачи). Бездокументарно —
|
||||
* промежуточные перемещения по заготовочным КУ контрактом не подписываются;
|
||||
* точка хранения переходит «скачком» в момент готовности к выдаче.
|
||||
*
|
||||
* История внутренних передач между КУ (заготовочный → точка выдачи и т.п.)
|
||||
* с подписью ТТН — отложена. Backend может реконструировать движение из
|
||||
* blockchain_actions если потребуется. Поле введено заранее, чтобы не
|
||||
* добавлять его потом через binary_extension.
|
||||
*
|
||||
* `acceptance_act` (АПП приёмки) и `issue_act` (АПП выдачи) хранятся
|
||||
* полным document2 — это дублирование в случае batch-поставки (один
|
||||
* физический акт → копия в каждом order'е batch'а), но это допустимо для
|
||||
* on-chain (минимум данных + hash) и упрощает аудит.
|
||||
*
|
||||
* `batch_hash` — opaque ссылка на off-chain consolidated request (Locked
|
||||
* Decision L10: batch — backend-only сущность). Контракт не валидирует
|
||||
* существование batch'а, только хранит ссылку для трассировки и группировки
|
||||
* Order'ов в UI. Все per-batch операции на on-chain делаются per-Order
|
||||
* (backend проходит циклом по orders батча) — векторов order'ов в action'ах нет.
|
||||
*
|
||||
* `actual_quantity` / `fact_cost` заполняются на signiss2 (Story 6.2/6.3).
|
||||
* До signiss2 равны соответственно `quantity` / `total_cost`.
|
||||
*
|
||||
* `warranty_until` — рассчитывается в signiss2 как `now() + warranty_period_secs`
|
||||
* (period приходит с Offer'а через backend; в `submretrn` валидируется только это поле).
|
||||
*/
|
||||
struct [[eosio::table, eosio::contract(MARKETPLACE)]] order {
|
||||
uint64_t id; ///< внутренний ID
|
||||
checksum256 hash; ///< process_hash для p.mkt.supply
|
||||
eosio::name coopname; ///< scope-валидация
|
||||
eosio::name orderer; ///< пайщик-заказчик
|
||||
eosio::name offerer; ///< пайщик-поставщик из Offer'а (для acceptorder/declineorder/signsupp guard'а)
|
||||
checksum256 offer_hash; ///< ссылка на Offer (off-chain в backend)
|
||||
|
||||
eosio::name delivery_braname; ///< КУ выдачи (выбран пайщиком на createorder); проверка signiss1/signiss2/p.mkt.return через Branch::is_user_authorized
|
||||
eosio::name accept_braname; ///< КУ приёмки от поставщика (заполняется на signsupp); проверка signchair через Branch::is_user_authorized
|
||||
eosio::name current_warehouse_braname; ///< текущая точка хранения; signchair: = accept_braname; signiss1: = delivery_braname (фиксация готовности к выдаче)
|
||||
|
||||
uint64_t quantity = 0; ///< заказанное количество
|
||||
uint64_t actual_quantity = 0; ///< фактически выданное (signiss2); до signiss2 == quantity
|
||||
eosio::asset unit_price = asset(0, _root_govern_symbol); ///< цена за единицу
|
||||
eosio::asset total_cost = asset(0, _root_govern_symbol); ///< quantity * unit_price (заблокированная сумма)
|
||||
eosio::asset fact_cost = asset(0, _root_govern_symbol); ///< actual_quantity * unit_price (после signiss2)
|
||||
|
||||
eosio::name cycle_type = CycleType::TIME_BASED; ///< снимок cycle_type Offer'а на момент createorder
|
||||
uint32_t warranty_period_secs = 0; ///< из Offer'а — для submretrn гард'а
|
||||
time_point_sec warranty_until = time_point_sec(0); ///< now() + warranty_period_secs (заполняется в signiss2)
|
||||
|
||||
eosio::name status = OrderStatus::ACTIVE; ///< canonical статус
|
||||
checksum256 batch_hash; ///< opaque ссылка на consolidated request (off-chain)
|
||||
|
||||
document2 acceptance_act_signsupp; ///< АПП приёмки — первая подпись поставщика (signsupp)
|
||||
document2 acceptance_act_signchair; ///< АПП приёмки — финальная подпись председателя приёмного КУ (signchair)
|
||||
document2 issue_act_signiss1; ///< АПП выдачи — первая подпись председателя КУ выдачи (signiss1)
|
||||
document2 issue_act_signiss2; ///< АПП выдачи — финальная подпись заказчика (signiss2)
|
||||
|
||||
uint64_t return_request_id = 0; ///< 0 если активного гарантийного возврата нет
|
||||
|
||||
// Все timestamp'ы переходов состояний (createorder/accepted/received_to_coop/
|
||||
// ready/received/cancelled) восстанавливаются на бэкенде из blockchain_actions[at]
|
||||
// по соответствующим action'ам — нет смысла держать их в RAM-таблице.
|
||||
// Единственное исключение — warranty_until (выше): нужен on-chain для
|
||||
// submretrn guard `now() < warranty_until` без cross-action lookup.
|
||||
|
||||
uint64_t primary_key() const { return id; }
|
||||
checksum256 by_hash() const { return hash; }
|
||||
uint64_t by_orderer() const { return orderer.value; }
|
||||
uint64_t by_offerer() const { return offerer.value; }
|
||||
uint64_t by_status() const { return status.value; }
|
||||
checksum256 by_batch() const { return batch_hash; }
|
||||
checksum256 by_offer() const { return offer_hash; }
|
||||
uint64_t by_delivery_bra() const { return delivery_braname.value; }
|
||||
uint64_t by_accept_bra() const { return accept_braname.value; }
|
||||
};
|
||||
|
||||
typedef eosio::multi_index<
|
||||
"orders"_n, order,
|
||||
eosio::indexed_by<"byhash"_n, eosio::const_mem_fun<order, checksum256, &order::by_hash>>,
|
||||
eosio::indexed_by<"byorderer"_n, eosio::const_mem_fun<order, uint64_t, &order::by_orderer>>,
|
||||
eosio::indexed_by<"byofferer"_n, eosio::const_mem_fun<order, uint64_t, &order::by_offerer>>,
|
||||
eosio::indexed_by<"bystatus"_n, eosio::const_mem_fun<order, uint64_t, &order::by_status>>,
|
||||
eosio::indexed_by<"bybatch"_n, eosio::const_mem_fun<order, checksum256, &order::by_batch>>,
|
||||
eosio::indexed_by<"byoffer"_n, eosio::const_mem_fun<order, checksum256, &order::by_offer>>,
|
||||
eosio::indexed_by<"bydelivbra"_n, eosio::const_mem_fun<order, uint64_t, &order::by_delivery_bra>>,
|
||||
eosio::indexed_by<"byacceptbra"_n, eosio::const_mem_fun<order, uint64_t, &order::by_accept_bra>>>
|
||||
orders_index;
|
||||
|
||||
} // namespace Marketplace
|
||||
@@ -1,112 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <eosio/asset.hpp>
|
||||
#include <eosio/crypto.hpp>
|
||||
#include <eosio/eosio.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../consts.hpp"
|
||||
#include "../core/document.hpp"
|
||||
#include "../core/utils.hpp"
|
||||
#include "document_core.hpp"
|
||||
|
||||
namespace Marketplace {
|
||||
|
||||
using namespace eosio;
|
||||
|
||||
struct [[eosio::table, eosio::contract(MARKETPLACE)]] request {
|
||||
uint64_t id;
|
||||
checksum256 hash;
|
||||
name coopname;
|
||||
name type;
|
||||
name status;
|
||||
name username;
|
||||
name braname;
|
||||
name warehouse;
|
||||
name token_contract;
|
||||
|
||||
name receiver_braname;
|
||||
name supplier_braname;
|
||||
|
||||
asset unit_cost;
|
||||
asset base_cost;
|
||||
asset membership_fee_amount;
|
||||
asset total_cost;
|
||||
|
||||
uint64_t units;
|
||||
std::string meta;
|
||||
|
||||
name money_contributor;
|
||||
name product_contributor;
|
||||
|
||||
std::vector<Document::named_document> documents;
|
||||
|
||||
uint64_t product_lifecycle_secs;
|
||||
uint64_t warranty_period_secs;
|
||||
asset cancellation_fee_amount;
|
||||
|
||||
time_point_sec warranty_delay_until;
|
||||
time_point_sec deadline_for_receipt;
|
||||
|
||||
name delivery_type; /*!< тип доставки: "internal" (между КУ) или "external" (внешний сервис) */
|
||||
name contribution_type; /*!< тип взноса: "share" (паевой) или "member" (членский — кооператив покупает) */
|
||||
|
||||
bool is_warranty_return = false;
|
||||
uint64_t warranty_return_id;
|
||||
|
||||
time_point_sec created_at;
|
||||
time_point_sec accepted_at;
|
||||
time_point_sec supplied_at;
|
||||
time_point_sec delivered_at;
|
||||
time_point_sec received_at;
|
||||
time_point_sec completed_at;
|
||||
time_point_sec declined_at;
|
||||
time_point_sec disputed_at;
|
||||
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; }
|
||||
checksum256 by_hash() const { return hash; }
|
||||
uint64_t by_username() const { return 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; }
|
||||
|
||||
name get_money_contributor() const {
|
||||
return is_warranty_return ? product_contributor : money_contributor;
|
||||
}
|
||||
|
||||
name get_product_contributor() const {
|
||||
return is_warranty_return ? money_contributor : product_contributor;
|
||||
}
|
||||
|
||||
name get_payer() const { return get_money_contributor(); }
|
||||
|
||||
name get_supplier() const { return get_product_contributor(); }
|
||||
|
||||
name get_product_backer() const { return money_contributor; }
|
||||
|
||||
name get_defective_supplier() const { return product_contributor; }
|
||||
};
|
||||
|
||||
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<"byhash"_n, eosio::const_mem_fun<request, checksum256, &request::by_hash>>,
|
||||
eosio::indexed_by<"byusername"_n, eosio::const_mem_fun<request, uint64_t, &request::by_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;
|
||||
|
||||
} // namespace Marketplace
|
||||
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <eosio/asset.hpp>
|
||||
#include <eosio/crypto.hpp>
|
||||
#include <eosio/eosio.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../consts.hpp"
|
||||
#include "../core/document.hpp"
|
||||
#include "../core/utils.hpp"
|
||||
|
||||
namespace Marketplace {
|
||||
|
||||
using namespace eosio;
|
||||
|
||||
/**
|
||||
* @brief Статусы заявления на гарантийный возврат (процесс p.mkt.return).
|
||||
*
|
||||
* Граф: ∅ → pending_review → approved_for_visit → return_accepted (final)
|
||||
* → rejected_at_ku (final)
|
||||
* → rejected_remote (final)
|
||||
*
|
||||
* Источник правды — `p.mkt.return.standard.yaml` секция `states:`.
|
||||
*/
|
||||
namespace ReturnStatus {
|
||||
inline constexpr eosio::name PENDING_REVIEW = "pendrev"_n;
|
||||
inline constexpr eosio::name APPROVED_FOR_VISIT = "approvvisit"_n;
|
||||
inline constexpr eosio::name RETURN_ACCEPTED = "accepted"_n;
|
||||
inline constexpr eosio::name REJECTED_REMOTE = "rejremote"_n;
|
||||
inline constexpr eosio::name REJECTED_AT_KU = "rejatku"_n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief On-chain Заявление на гарантийный возврат — анкер процесса p.mkt.return.
|
||||
*
|
||||
* scope = coopname; primary_key = id; уникальность через `byhash` индекс на
|
||||
* `return_request.hash` — этот hash используется как `process_hash` во всех
|
||||
* ledger2-операциях процесса (RETURN + RETURN2).
|
||||
*
|
||||
* Привязка к КУ не сохраняется — она может измениться от шага к шагу
|
||||
* (председатель delivery-КУ может рассмотреть удалённо, а очный осмотр сделать
|
||||
* на любом другом КУ; состав доверенных лиц коробки `branches` тоже может
|
||||
* меняться). На каждом действии (aprretrem/rejretrem/accretrn/rejretrn) braname
|
||||
* приходит параметром action'а и валидируется через
|
||||
* `Branch::is_user_authorized(coopname, braname, signer)`. Контракт хранит
|
||||
* только неизменные участники процесса: orderer / offerer (через original Order)
|
||||
* и coopname.
|
||||
*
|
||||
* Связь с исходным Order'ом — `original_order_id` + `original_order_hash`;
|
||||
* Order.return_request_id ставится в submretrn для двусторонней связи.
|
||||
*
|
||||
* `photos` — vector<checksum256> хешей файлов в bucket'е stol-zakazov:images
|
||||
* (Story 7.1, AR32). Реальные изображения off-chain в file-storage (PR #359);
|
||||
* on-chain — только ссылки (hash для дедупликации + URL восстанавливает backend).
|
||||
*
|
||||
* `decision_remote` / `decision_visit` — декларативные документы решений
|
||||
* председателя (rejretrem / accretrn / rejretrn). reason_remote / reason_visit
|
||||
* — текстовые причины отказа для пользовательского UI (заполняются в
|
||||
* rejretrem / rejretrn соответственно).
|
||||
*/
|
||||
struct [[eosio::table, eosio::contract(MARKETPLACE)]] return_request {
|
||||
uint64_t id;
|
||||
checksum256 hash; ///< process_hash для p.mkt.return
|
||||
eosio::name coopname;
|
||||
eosio::name orderer; ///< пайщик-заказчик (заявитель)
|
||||
|
||||
uint64_t original_order_id; ///< внутренний id Order'а
|
||||
checksum256 original_order_hash; ///< process_hash оригинального p.mkt.supply
|
||||
checksum256 original_consume_op_id; ///< ссылка на оригинальный o.mkt.consum (для journal трассировки compensating forward; см. d6 A4)
|
||||
|
||||
uint64_t actual_quantity = 0; ///< возвращаемое количество (по умолчанию = order.actual_quantity, может быть меньше)
|
||||
eosio::asset fact_cost = asset(0, _root_govern_symbol); ///< возвращаемая сумма (actual_quantity * unit_price)
|
||||
|
||||
std::string reason_text; ///< причина обращения (≤ 500 символов)
|
||||
std::vector<checksum256> photos; ///< хеши файлов в bucket'е stol-zakazov:images
|
||||
|
||||
eosio::name status = ReturnStatus::PENDING_REVIEW;
|
||||
document2 statement; ///< заявление пайщика (опционально подписанное)
|
||||
document2 decision_remote; ///< решение председателя удалённо (aprretrem | rejretrem)
|
||||
document2 decision_visit; ///< решение председателя по итогам очного осмотра (accretrn | rejretrn)
|
||||
std::string reason_remote; ///< причина отказа удалённо (для rejretrem)
|
||||
std::string reason_visit; ///< причина отказа на очном осмотре (для rejretrn)
|
||||
|
||||
// Timestamp'ы submretrn/aprretrem/rejretrem/accretrn/rejretrn — на бэкенде
|
||||
// из blockchain_actions[at]. В контракте никаких guard'ов по датам нет.
|
||||
|
||||
uint64_t primary_key() const { return id; }
|
||||
checksum256 by_hash() const { return hash; }
|
||||
uint64_t by_orderer() const { return orderer.value; }
|
||||
uint64_t by_status() const { return status.value; }
|
||||
uint64_t by_original_order() const { return original_order_id; }
|
||||
};
|
||||
|
||||
typedef eosio::multi_index<
|
||||
"retrequests"_n, return_request,
|
||||
eosio::indexed_by<"byhash"_n, eosio::const_mem_fun<return_request, checksum256, &return_request::by_hash>>,
|
||||
eosio::indexed_by<"byorderer"_n, eosio::const_mem_fun<return_request, uint64_t, &return_request::by_orderer>>,
|
||||
eosio::indexed_by<"bystatus"_n, eosio::const_mem_fun<return_request, uint64_t, &return_request::by_status>>,
|
||||
eosio::indexed_by<"byorigorder"_n, eosio::const_mem_fun<return_request, uint64_t, &return_request::by_original_order>>>
|
||||
return_requests_index;
|
||||
|
||||
} // namespace Marketplace
|
||||
@@ -1,52 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <eosio/eosio.hpp>
|
||||
|
||||
#include "../consts.hpp"
|
||||
#include "document_core.hpp"
|
||||
|
||||
namespace Marketplace {
|
||||
|
||||
using namespace eosio;
|
||||
|
||||
struct [[eosio::table, eosio::contract(MARKETPLACE)]] segment {
|
||||
uint64_t id;
|
||||
uint64_t request_id;
|
||||
name type;
|
||||
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;
|
||||
|
||||
name coopactor;
|
||||
name username;
|
||||
name driver_username;
|
||||
name receive_from_driver_coopactor;
|
||||
|
||||
time_point_sec created_at;
|
||||
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;
|
||||
|
||||
} // namespace Marketplace
|
||||
@@ -1,55 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <eosio/crypto.hpp>
|
||||
#include <eosio/eosio.hpp>
|
||||
#include <vector>
|
||||
|
||||
#include "../consts.hpp"
|
||||
#include "../core/document.hpp"
|
||||
#include "../core/utils.hpp"
|
||||
#include "document_core.hpp"
|
||||
|
||||
namespace Marketplace {
|
||||
|
||||
using namespace eosio;
|
||||
|
||||
struct [[eosio::table, eosio::contract(MARKETPLACE)]] shipment {
|
||||
uint64_t id;
|
||||
checksum256 hash;
|
||||
name coopname;
|
||||
name driver_username;
|
||||
name source_braname;
|
||||
name destination_braname;
|
||||
name status;
|
||||
|
||||
std::vector<checksum256> request_hashes;
|
||||
|
||||
std::vector<Document::named_document> documents;
|
||||
|
||||
time_point_sec created_at;
|
||||
time_point_sec loaded_at;
|
||||
time_point_sec delivered_at;
|
||||
time_point_sec completed_at;
|
||||
|
||||
uint64_t primary_key() const { return id; }
|
||||
checksum256 by_hash() const { return hash; }
|
||||
uint64_t by_coop() const { return coopname.value; }
|
||||
uint64_t by_driver() const { return driver_username.value; }
|
||||
uint64_t by_source() const { return source_braname.value; }
|
||||
uint64_t by_destination() const { return destination_braname.value; }
|
||||
uint64_t by_status() const { return status.value; }
|
||||
uint64_t by_created() const { return created_at.sec_since_epoch(); }
|
||||
};
|
||||
|
||||
typedef eosio::multi_index<
|
||||
"shipments"_n, shipment,
|
||||
eosio::indexed_by<"byhash"_n, eosio::const_mem_fun<shipment, checksum256, &shipment::by_hash>>,
|
||||
eosio::indexed_by<"bycoop"_n, eosio::const_mem_fun<shipment, uint64_t, &shipment::by_coop>>,
|
||||
eosio::indexed_by<"bydriver"_n, eosio::const_mem_fun<shipment, uint64_t, &shipment::by_driver>>,
|
||||
eosio::indexed_by<"bysource"_n, eosio::const_mem_fun<shipment, uint64_t, &shipment::by_source>>,
|
||||
eosio::indexed_by<"bydest"_n, eosio::const_mem_fun<shipment, uint64_t, &shipment::by_destination>>,
|
||||
eosio::indexed_by<"bystatus"_n, eosio::const_mem_fun<shipment, uint64_t, &shipment::by_status>>,
|
||||
eosio::indexed_by<"bycreated"_n, eosio::const_mem_fun<shipment, uint64_t, &shipment::by_created>>>
|
||||
shipments_index;
|
||||
|
||||
} // namespace Marketplace
|
||||
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include <eosio/asset.hpp>
|
||||
#include <eosio/crypto.hpp>
|
||||
#include <eosio/eosio.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../consts.hpp"
|
||||
#include "../core/document.hpp"
|
||||
#include "../core/utils.hpp"
|
||||
|
||||
namespace Marketplace {
|
||||
|
||||
using namespace eosio;
|
||||
|
||||
/**
|
||||
* @brief Статусы проекта решения совета о списании скоропорта (процесс p.mkt.wroff).
|
||||
*
|
||||
* Граф: ∅ → draft → executed (final, ledger2-операции применены)
|
||||
* → rejected (final, без ledger2-операций)
|
||||
*
|
||||
* Источник правды — `p.mkt.wroff.standard.yaml` секция `states:`.
|
||||
*/
|
||||
namespace WroffStatus {
|
||||
// Имя константы PROPOSED (а не DRAFT) — конфликт с макросом
|
||||
// DRAFT="draft" из lib/consts.hpp; on-chain строка осталась "draft"_n,
|
||||
// как в p.mkt.wroff.standard.yaml.
|
||||
inline constexpr eosio::name PROPOSED = "draft"_n;
|
||||
inline constexpr eosio::name EXECUTED = "executed"_n;
|
||||
inline constexpr eosio::name REJECTED = "rejected"_n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Позиция к списанию в составе writeoff_proposal.
|
||||
*
|
||||
* Может ссылаться:
|
||||
* - на конкретный Order (если позиция «не выдана первичному заказчику») —
|
||||
* `source_order_id` != 0;
|
||||
* - на излишек в результате signiss2 fact > ordered + остаток на складе —
|
||||
* `source_order_id` == 0, аналитика только через `braname` + `meta`;
|
||||
* - на возвращённое имущество из p.mkt.return — `source_order_id` ссылка
|
||||
* через original Order, через который имущество физически вернулось.
|
||||
*
|
||||
* `braname` — кооперативный участок (склад) источник списания. Пер-КУ
|
||||
* аналитика счёта 10. Подпись протокола проверяется через
|
||||
* `Branch::is_user_authorized(coopname, braname, signer)` — председатель
|
||||
* соответствующего КУ может делегировать подпись доверенному лицу.
|
||||
*
|
||||
* `amount` — сумма к списанию по этой позиции (Дт 91 / Кт 10 в части 1
|
||||
* и Дт 86 / Кт 91 в части 2 — обе в одной транзакции execwroff).
|
||||
*
|
||||
* `meta` — произвольная строка для UI / отчёта (название позиции, причина);
|
||||
* не валидируется контрактом.
|
||||
*
|
||||
* `executed` — true после успешного списания этой позиции (см. execwroff
|
||||
* per-item action). Когда все items в proposal.items имеют executed=true,
|
||||
* статус proposal автоматически переходит в EXECUTED.
|
||||
*/
|
||||
struct wroff_item {
|
||||
uint64_t source_order_id = 0; ///< 0 если списание из складского остатка без привязки к order'у
|
||||
eosio::name braname; ///< КУ-склад источник списания
|
||||
eosio::asset amount = asset(0, _root_govern_symbol);
|
||||
std::string meta;
|
||||
bool executed = false; ///< true после execwroff(proposal_hash, item_index)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief On-chain Проект решения совета о списании скоропорта — анкер процесса p.mkt.wroff.
|
||||
*
|
||||
* scope = coopname; primary_key = id; уникальность через `byhash` индекс на
|
||||
* `proposal.hash` — этот hash используется как `process_hash` во всех
|
||||
* ledger2-операциях процесса (WROFF + WROFF2 — по паре per-item).
|
||||
*
|
||||
* `items` — vector<wroff_item> позиций к списанию; на execwroff контракт
|
||||
* последовательно вызывает `Ledger2::apply(o.mkt.wroff, item.amount, …)` +
|
||||
* `Ledger2::apply(o.mkt.wroff2, item.amount, …)` для каждой позиции в
|
||||
* одной транзакции Antelope.
|
||||
*
|
||||
* `protocol` — document2 решения совета (signed_by: council_members).
|
||||
* Подпись — через стандартный sov.decision-протокол (см. p.mkt.wroff.standard.yaml
|
||||
* секция documents).
|
||||
*/
|
||||
struct [[eosio::table, eosio::contract(MARKETPLACE)]] writeoff_proposal {
|
||||
uint64_t id;
|
||||
checksum256 hash; ///< process_hash для p.mkt.wroff
|
||||
eosio::name coopname;
|
||||
eosio::name proposed_by; ///< backend / админ — инициатор propwroff
|
||||
eosio::name decided_by; ///< actor execwroff/declwroff (председатель / совет)
|
||||
|
||||
std::vector<wroff_item> items;
|
||||
eosio::asset total_amount = asset(0, _root_govern_symbol); ///< Σ items.amount (для UI / отчёта)
|
||||
|
||||
eosio::name status = WroffStatus::PROPOSED;
|
||||
document2 protocol; ///< протокол решения совета (для execwroff)
|
||||
std::string reject_reason; ///< причина отклонения (для declwroff)
|
||||
|
||||
// Timestamp'ы propwroff/execwroff/declwroff — на бэкенде из blockchain_actions[at].
|
||||
|
||||
uint64_t primary_key() const { return id; }
|
||||
checksum256 by_hash() const { return hash; }
|
||||
uint64_t by_status() const { return status.value; }
|
||||
};
|
||||
|
||||
typedef eosio::multi_index<
|
||||
"wroffprops"_n, writeoff_proposal,
|
||||
eosio::indexed_by<"byhash"_n, eosio::const_mem_fun<writeoff_proposal, checksum256, &writeoff_proposal::by_hash>>,
|
||||
eosio::indexed_by<"bystatus"_n, eosio::const_mem_fun<writeoff_proposal, uint64_t, &writeoff_proposal::by_status>>>
|
||||
writeoff_proposals_index;
|
||||
|
||||
} // namespace Marketplace
|
||||
Executable → Regular
+29
-41
@@ -1,48 +1,36 @@
|
||||
#include "marketplace.hpp"
|
||||
#include <eosio/transaction.hpp>
|
||||
|
||||
// Процесс поставки по заявкам orderoffer
|
||||
#include "src/deliver_on_offer/orderoffer.cpp"
|
||||
#include "src/deliver_on_offer/accept.cpp"
|
||||
#include "src/deliver_on_offer/authcontrib.cpp"
|
||||
#include "src/deliver_on_offer/authreturn.cpp"
|
||||
#include "src/deliver_on_offer/declineacc.cpp"
|
||||
#include "src/deliver_on_offer/supply.cpp"
|
||||
#include "src/deliver_on_offer/supplcnf.cpp"
|
||||
// Раскладка по процессам соответствует YAML-стандартам рядом с этим файлом
|
||||
// (p.mkt.supply.standard.yaml / p.mkt.return.standard.yaml /
|
||||
// p.mkt.wroff.standard.yaml). Имена подпапок 1:1 совпадают с process_type
|
||||
// — связь от файла → к стандарту прозрачная.
|
||||
|
||||
#include "src/deliver_on_offer/coopstock.cpp"
|
||||
#include "src/deliver_on_offer/acceptstock.cpp"
|
||||
#include "src/deliver_on_offer/reqreturn.cpp"
|
||||
#include "src/deliver_on_offer/delivered.cpp"
|
||||
#include "src/deliver_on_offer/receive.cpp"
|
||||
#include "src/deliver_on_offer/receivecnf.cpp"
|
||||
#include "src/deliver_on_offer/complete.cpp"
|
||||
#include "src/deliver_on_offer/decline.cpp"
|
||||
#include "src/deliver_on_offer/cancel.cpp"
|
||||
// ── p.mkt.supply (9 actions) ─── Stories Эпиков 4-5-6 ──────────────────
|
||||
#include "src/p.mkt.supply/createorder.cpp"
|
||||
#include "src/p.mkt.supply/cancelorder.cpp"
|
||||
#include "src/p.mkt.supply/expireorder.cpp"
|
||||
#include "src/p.mkt.supply/acceptorder.cpp"
|
||||
#include "src/p.mkt.supply/declineorder.cpp"
|
||||
#include "src/p.mkt.supply/signsupp.cpp"
|
||||
#include "src/p.mkt.supply/signchair.cpp"
|
||||
#include "src/p.mkt.supply/signiss1.cpp"
|
||||
#include "src/p.mkt.supply/signiss2.cpp"
|
||||
|
||||
// Новая система перевозок
|
||||
#include "src/shipment/createship.cpp"
|
||||
#include "src/shipment/signbydriver.cpp"
|
||||
#include "src/shipment/arrived.cpp"
|
||||
#include "src/shipment/receiveshipm.cpp"
|
||||
#include "src/shipment/retransport.cpp"
|
||||
// ── p.mkt.return (5 actions) ──── Stories Эпика 7 ──────────────────────
|
||||
#include "src/p.mkt.return/submretrn.cpp"
|
||||
#include "src/p.mkt.return/aprretrem.cpp"
|
||||
#include "src/p.mkt.return/rejretrem.cpp"
|
||||
#include "src/p.mkt.return/accretrn.cpp"
|
||||
#include "src/p.mkt.return/rejretrn.cpp"
|
||||
|
||||
// ORDER→OFFER direction
|
||||
#include "src/deliver_on_order/createorder.cpp"
|
||||
#include "src/deliver_on_order/respondoffer.cpp"
|
||||
// ── p.mkt.wroff (3 actions) ───── Stories Эпика 8 ──────────────────────
|
||||
#include "src/p.mkt.wroff/propwroff.cpp"
|
||||
#include "src/p.mkt.wroff/execwroff.cpp"
|
||||
#include "src/p.mkt.wroff/declwroff.cpp"
|
||||
|
||||
// Уничтожение и перепредложение
|
||||
#include "src/deliver_on_offer/destroy.cpp"
|
||||
#include "src/deliver_on_offer/reoffer.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(){
|
||||
// require_auth(_marketplace);
|
||||
[[eosio::action]] void marketplace::migrate() {
|
||||
// Donor-таблиц нет (AR30 — donor-actions удалены вместе с requests/segments/
|
||||
// shipments). Заглушка остаётся для совместимости с прежним ABI; вызывать
|
||||
// не имеет эффекта.
|
||||
require_auth(_marketplace);
|
||||
}
|
||||
|
||||
Executable → Regular
+266
-86
@@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <eosio/asset.hpp>
|
||||
#include <eosio/contract.hpp>
|
||||
#include <eosio/crypto.hpp>
|
||||
@@ -6,40 +8,59 @@
|
||||
#include <eosio/system.hpp>
|
||||
#include <eosio/time.hpp>
|
||||
|
||||
#include "../lib/index.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../lib/index.hpp"
|
||||
#include "../lib/core/marketplace/marketplace.hpp"
|
||||
#include "../lib/core/marketplace/memo.hpp"
|
||||
#include "../lib/core/branch/branch.hpp"
|
||||
#include "../lib/core/ledger2/ledger2.hpp"
|
||||
|
||||
using namespace eosio;
|
||||
using namespace Marketplace;
|
||||
|
||||
/**
|
||||
* \ingroup public_contracts
|
||||
* @brief Класс `marketplace` предоставляет функционал кооперативного маркетплейса, позволяя пользователям
|
||||
* создавать, обновлять, принимать и отменять заявки на обмен товаров и услуг. Этот контракт служит
|
||||
* центральной точкой для всех операций обмена в рамках кооперативной экосистемы.
|
||||
* \ingroup public_contracts
|
||||
*
|
||||
* Основные функции класса:
|
||||
* - Создание и управление заявками типа orderoffer (заказчик → поставщик).
|
||||
* - Операции обновления, принятия, отказа и завершения обменных операций.
|
||||
* - Модерация и управление публикацией заявок на обмен.
|
||||
* - Административные функции, такие как создание идентификаторов и авторизация операций.
|
||||
*
|
||||
* ## Процесс поставки orderoffer:
|
||||
*
|
||||
* 1. **orderoffer** - заказчик создает заявку с документами на возврат и конвертацию, средства блокируются
|
||||
* 2. **accept** - поставщик принимает заявку и предоставляет документы на взнос и конвертацию
|
||||
* 3. **authcontrib/authreturn** - совет авторизует оба заявления раздельно
|
||||
* 4. **supply** → **supplcnf** - поставка товара и подтверждение председателем КУ
|
||||
* 5. **deliver1** → **deliver2** → **deliver3** → **deliver4** - этапы транспортировки
|
||||
* 6. **receive** → **receivecnf** - получение товара заказчиком
|
||||
* 7. **complete** - завершение поставки после гарантийного периода
|
||||
*
|
||||
* ## Документооборот:
|
||||
*
|
||||
* Все документы сохраняются в векторе `std::vector<document2> documents` в заявке.
|
||||
* Каждый этап процесса добавляет необходимые документы в этот вектор.
|
||||
*
|
||||
* \note Контракт маркетплейса является центральной точкой экономической активности на платформе.
|
||||
* \note Система упрощена для работы с одной заявкой вместо двух встречных.
|
||||
*/
|
||||
* @brief Контракт `marketplace` — кооперативный «Стол заказов» в режиме
|
||||
* членских взносов.
|
||||
*
|
||||
* Реализует canonical actions трёх процессов из YAML-стандартов:
|
||||
* - **p.mkt.supply** (9 actions): createorder, cancelorder, expireorder,
|
||||
* acceptorder, declineorder, signsupp, signchair, signiss1, signiss2.
|
||||
* - **p.mkt.return** (5 actions): submretrn, aprretrem, rejretrem, accretrn,
|
||||
* rejretrn.
|
||||
* - **p.mkt.wroff** (3 actions): propwroff, execwroff, declwroff.
|
||||
*
|
||||
* Все per-batch операции на on-chain выполняются per-Order (бэкенд
|
||||
* проходит циклом по Order'ам соответствующего batch'а, объединяя их по
|
||||
* `batch_hash`). Векторов order_hashes в actions нет — это ограничение
|
||||
* на размер транзакции в Antelope (тысячи orders в одной транзакции
|
||||
* не пройдут).
|
||||
*
|
||||
* Все ledger2-движения средств — через `Ledger2::apply(_marketplace, …)`,
|
||||
* никаких прямых wallet/account-операций. 13 marketplace-операций
|
||||
* зарегистрированы в `lib/core/ledger2/operations.hpp` (`OPERATION_REGISTRY`).
|
||||
*
|
||||
* Composite-операции (consum+consum2, return+return2, wroff+wroff2) —
|
||||
* последовательные `Ledger2::apply` в одной транзакции Antelope (атомарность
|
||||
* через single-action wrapper).
|
||||
*
|
||||
* Авторизация подписей под актами / решениями привязана к кооперативному
|
||||
* участку (КУ) через контракт `branch` и helper
|
||||
* `Branch::is_user_authorized(coopname, braname, signer)` — председатель
|
||||
* КУ может делегировать подпись доверенному лицу из `coobranch.trusted[]`.
|
||||
*
|
||||
* Источник правды по логике actions, гардам, state-переходам и
|
||||
* операциям — три YAML-файла рядом с этим .hpp:
|
||||
* - `p.mkt.supply.standard.yaml`
|
||||
* - `p.mkt.return.standard.yaml`
|
||||
* - `p.mkt.wroff.standard.yaml`
|
||||
*
|
||||
* Donor-actions старой клиринговой модели (FR19a, AR30) удалены вместе с
|
||||
* соответствующими таблицами `Marketplace::request/segment/shipment`.
|
||||
*/
|
||||
class [[eosio::contract(MARKETPLACE)]] marketplace : public eosio::contract {
|
||||
|
||||
public:
|
||||
@@ -47,62 +68,221 @@ public:
|
||||
eosio::datastream<const char *> ds)
|
||||
: eosio::contract(receiver, code, ds) {}
|
||||
|
||||
void apply(uint64_t receiver, uint64_t code, uint64_t action);
|
||||
// ── p.mkt.supply ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief Заказчик размещает заказ на товар из каталога (Story 4.1).
|
||||
* Серия: o.wal.conv (conditional) → o.mkt.assign (conditional) → o.mkt.block.
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void createorder(eosio::name coopname,
|
||||
eosio::name orderer,
|
||||
checksum256 order_hash,
|
||||
checksum256 offer_hash,
|
||||
eosio::name offerer,
|
||||
eosio::name delivery_braname,
|
||||
uint64_t quantity,
|
||||
eosio::asset unit_price,
|
||||
eosio::name cycle_type,
|
||||
uint32_t warranty_period_secs,
|
||||
checksum256 batch_hash);
|
||||
|
||||
/**
|
||||
* @brief Заказчик отменяет заказ до акцепта (Story 4.4). Триггерит o.mkt.unblk.
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void cancelorder(eosio::name coopname,
|
||||
eosio::name orderer,
|
||||
checksum256 order_hash);
|
||||
|
||||
/**
|
||||
* @brief Backend закрывает Order по таймауту цикла отсечки (Story 4.3).
|
||||
* Per-Order: o.mkt.unblk + статус active → cancelled. Backend вычисляет
|
||||
* threshold по batch'у вне контракта; для каждого истёкшего Order'а
|
||||
* вызывается отдельный `expireorder`.
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void expireorder(eosio::name coopname,
|
||||
checksum256 order_hash);
|
||||
|
||||
/**
|
||||
* @brief Поставщик акцептует один Order (Story 4.5).
|
||||
* Без ledger2-операций — статус active → accepted. Backend проходит циклом
|
||||
* по orders соответствующего batch'а, вызывая `acceptorder` per Order.
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void acceptorder(eosio::name coopname,
|
||||
eosio::name offerer,
|
||||
checksum256 order_hash);
|
||||
|
||||
/**
|
||||
* @brief Поставщик отказывается от одного Order'а до акцепта (Story 4.5).
|
||||
* Per-Order: o.mkt.unblk на total_cost + статус active → cancelled.
|
||||
* Backend проходит циклом по orders батча, вызывая `declineorder` per Order.
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void declineorder(eosio::name coopname,
|
||||
eosio::name offerer,
|
||||
checksum256 order_hash);
|
||||
|
||||
/**
|
||||
* @brief Поставщик первой подписью на АПП приёмки фиксирует партию по одному
|
||||
* Order'у (Story 5.3/5.4). Без ledger2-операций — статус accepted →
|
||||
* supply_prepared. Параметр `accept_braname` указывает приёмный КУ; запись
|
||||
* в Order. Подпись валидируется как `verify_document_or_fail(act, {offerer})`.
|
||||
* Backend проходит циклом по orders батча с одинаковым `act`.
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void signsupp(eosio::name coopname,
|
||||
eosio::name offerer,
|
||||
checksum256 order_hash,
|
||||
eosio::name accept_braname,
|
||||
document2 act);
|
||||
|
||||
/**
|
||||
* @brief Председатель приёмного КУ ставит закрывающую подпись на АПП
|
||||
* приёмки одного Order'а (Story 5.3/5.4). Per-Order: o.mkt.purch +
|
||||
* o.mkt.payout (атомарно). Авторизация подписи: председатель / trustee /
|
||||
* trusted ∈ branches[o.accept_braname]. Backend проходит циклом по orders
|
||||
* батча с одинаковым `act`.
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void signchair(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
checksum256 order_hash,
|
||||
document2 act);
|
||||
|
||||
/**
|
||||
* @brief Председатель КУ выдачи открывает выдачу первой подписью АПП-выдачи
|
||||
* (Story 6.1). Без ledger2-операций — статус ready_to_receive. Авторизация:
|
||||
* подписант ∈ branches[o.delivery_braname].
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void signiss1(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
checksum256 order_hash,
|
||||
document2 act);
|
||||
|
||||
/**
|
||||
* @brief Заказчик ставит финальную подпись АПП-выдачи (Story 6.3).
|
||||
* Per-Order с поддержкой actual_quantity ≠ ordered (Story 6.2).
|
||||
* Atomic: [o.mkt.unblk на разницу если actual<ordered |
|
||||
* o.wal.conv+o.mkt.assign+o.mkt.block на разницу если actual>ordered]
|
||||
* + o.mkt.consum + o.mkt.consum2.
|
||||
* Подпись акта: orderer + любой авторизованный из branches[o.delivery_braname].
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void signiss2(eosio::name coopname,
|
||||
eosio::name orderer,
|
||||
checksum256 order_hash,
|
||||
uint64_t actual_quantity,
|
||||
eosio::name delivery_signer,
|
||||
document2 act);
|
||||
|
||||
// ── p.mkt.return ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief Пайщик подаёт заявление на гарантийный возврат (Story 7.1).
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void submretrn(eosio::name coopname,
|
||||
eosio::name orderer,
|
||||
checksum256 request_hash,
|
||||
checksum256 original_order_hash,
|
||||
uint64_t actual_quantity,
|
||||
std::string reason_text,
|
||||
std::vector<checksum256> photos,
|
||||
document2 statement);
|
||||
|
||||
/**
|
||||
* @brief Председатель удалённо одобряет очный визит (Story 7.2). Авторизация:
|
||||
* подписант ∈ branches[braname]; параметр `braname` фиксирует КУ, в котором
|
||||
* рассматривается заявление.
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void aprretrem(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
eosio::name braname,
|
||||
checksum256 request_hash,
|
||||
document2 decision);
|
||||
|
||||
/**
|
||||
* @brief Председатель удалённо отказывает (Story 7.2). Авторизация:
|
||||
* подписант ∈ branches[braname].
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void rejretrem(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
eosio::name braname,
|
||||
checksum256 request_hash,
|
||||
std::string reason,
|
||||
document2 decision);
|
||||
|
||||
/**
|
||||
* @brief Председатель принимает возврат на очном осмотре (Story 7.4).
|
||||
* Atomic: o.mkt.return + o.mkt.return2 (compensating forward).
|
||||
* Авторизация: подписант ∈ branches[braname].
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void accretrn(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
eosio::name braname,
|
||||
checksum256 request_hash,
|
||||
document2 decision);
|
||||
|
||||
/**
|
||||
* @brief Председатель отказывает на очном осмотре (Story 7.3).
|
||||
* Авторизация: подписант ∈ branches[braname].
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void rejretrn(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
eosio::name braname,
|
||||
checksum256 request_hash,
|
||||
std::string reason,
|
||||
document2 decision);
|
||||
|
||||
// ── p.mkt.wroff ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief Backend / админ выносит проект списания на повестку совета (Story 8.1).
|
||||
* Без ledger2-операций — только создание proposal с N позициями.
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void propwroff(eosio::name coopname,
|
||||
eosio::name proposed_by,
|
||||
checksum256 proposal_hash,
|
||||
std::vector<wroff_item> items);
|
||||
|
||||
/**
|
||||
* @brief Совет исполняет одну позицию проекта списания (Story 8.3).
|
||||
* Per-item: o.mkt.wroff + o.mkt.wroff2 (атомарно в той же транзакции),
|
||||
* `items[item_index].executed = true`. Когда все items.executed → proposal
|
||||
* status переходит в EXECUTED. Авторизация: подписант ∈ branches[items[item_index].braname].
|
||||
* Backend проходит циклом по неисполненным items, вызывая `execwroff` per item.
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void execwroff(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
checksum256 proposal_hash,
|
||||
uint64_t item_index,
|
||||
document2 protocol);
|
||||
|
||||
/**
|
||||
* @brief Совет отклоняет проект списания целиком (Story 8.3).
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void declwroff(eosio::name coopname,
|
||||
eosio::name decided_by,
|
||||
checksum256 proposal_hash,
|
||||
std::string reason);
|
||||
|
||||
// ── service ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief Заглушка миграции — donor-таблиц нет, мигрировать нечего.
|
||||
* Оставлена для совместимости с CMake-build и прежним ABI.
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
[[eosio::action]] void migrate();
|
||||
|
||||
// Действия для создания заявок
|
||||
[[eosio::action]] void orderoffer(eosio::name coopname, eosio::name receiver_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, document2 convert_in, eosio::name delivery_type, eosio::name contribution_type, std::string meta);
|
||||
|
||||
static void cancel_request(eosio::name coopname, eosio::name username, checksum256 request_hash);
|
||||
|
||||
// Статические методы для отклонения заявок
|
||||
static void decline_request(eosio::name coopname, const request& change);
|
||||
|
||||
// Методы для направления заявок
|
||||
[[eosio::action]] void accept(eosio::name coopname, eosio::name supplier_braname, eosio::name username, checksum256 request_hash, document2 convert_out, document2 return_document);
|
||||
[[eosio::action]] void authcontrib(eosio::name coopname, checksum256 request_hash, document2 authorization);
|
||||
[[eosio::action]] void authreturn(eosio::name coopname, checksum256 request_hash, document2 authorization);
|
||||
[[eosio::action]] void declineacc(eosio::name coopname, checksum256 hash, std::string reason);
|
||||
[[eosio::action]] void supply(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 act);
|
||||
[[eosio::action]] void supplcnf(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 act);
|
||||
|
||||
// Новая система перевозок
|
||||
[[eosio::action]] void createship(eosio::name coopname, checksum256 hash, eosio::name driver_username, eosio::name source_braname, eosio::name destination_braname, std::vector<checksum256> request_hashes, document2 transport_act_sender);
|
||||
[[eosio::action]] void signbydriver(eosio::name coopname, checksum256 hash, document2 transport_act_driver);
|
||||
[[eosio::action]] void arrived(eosio::name coopname, checksum256 hash, document2 transport_act_delivery);
|
||||
[[eosio::action]] void receiveshipm(eosio::name coopname, checksum256 hash, document2 warehouse_receipt_act);
|
||||
[[eosio::action]] void retransport(eosio::name coopname, checksum256 completed_hash, eosio::name new_driver_username, eosio::name source_braname, eosio::name new_destination_braname, std::vector<checksum256> request_hashes, document2 transport_act_sender);
|
||||
|
||||
// ORDER→OFFER direction (заказчик публикует → поставщики откликаются)
|
||||
[[eosio::action]] void createorder(eosio::name coopname, eosio::name receiver_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, document2 convert_in, eosio::name delivery_type, std::string meta);
|
||||
[[eosio::action]] void respondoffer(eosio::name coopname, eosio::name supplier_braname, eosio::name username, checksum256 order_hash, checksum256 offer_hash, uint64_t units, uint32_t product_lifecycle_secs, document2 contribution_statement, document2 convert_out);
|
||||
|
||||
// Имущество из запасов кооператива
|
||||
[[eosio::action]] void coopstock(eosio::name coopname, eosio::name braname, checksum256 hash, uint64_t units, eosio::asset unit_cost, uint32_t product_lifecycle_secs, uint32_t warranty_period_secs, eosio::asset membership_fee_amount, std::string meta);
|
||||
[[eosio::action]] void acceptstock(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 convert_in, document2 return_statement);
|
||||
|
||||
// Запрос возврата перед получением
|
||||
[[eosio::action]] void reqreturn(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 return_statement);
|
||||
|
||||
// Доставка заказчику
|
||||
[[eosio::action]] void delivered(eosio::name coopname, eosio::name username, checksum256 request_hash);
|
||||
[[eosio::action]] void receive(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document);
|
||||
[[eosio::action]] void receivecnf(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document);
|
||||
[[eosio::action]] void complete(eosio::name coopname, eosio::name username, checksum256 request_hash);
|
||||
[[eosio::action]] void decline(eosio::name coopname, eosio::name username, checksum256 request_hash, std::string meta);
|
||||
[[eosio::action]] void cancel(eosio::name coopname, eosio::name username, checksum256 request_hash);
|
||||
|
||||
// Уничтожение и перепредложение
|
||||
[[eosio::action]] void destroy(eosio::name coopname, checksum256 request_hash, document2 destruction_act);
|
||||
[[eosio::action]] void reoffer(eosio::name coopname, checksum256 request_hash, checksum256 new_hash, eosio::asset new_unit_cost, std::string new_meta);
|
||||
|
||||
// Методы для работы с диспутом (гарантийный возврат)
|
||||
[[eosio::action]] void dispute(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 document);
|
||||
[[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();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Принятие заявки поставщиком.
|
||||
|
||||
@details Поставщик принимает заявку orderoffer на поставку имущества.
|
||||
При принятии создаётся ОДНО заявление в совет — на имущественный паевой взнос.
|
||||
Заявление на возврат будет создано позже, перед получением имущества заказчиком.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param supplier_braname Имя кооперативного участка поставщика
|
||||
@param username Имя поставщика
|
||||
@param request_hash Хэш заявки
|
||||
@param convert_out Заявление на конвертацию в кошелек (сохраняется для будущего использования)
|
||||
@param product_contribution_statement Заявление на имущественный паевой взнос
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::accept(eosio::name coopname, eosio::name supplier_braname, 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 == "orderoffer"_n, "Метод accept применим только к заявкам типа orderoffer");
|
||||
|
||||
get_branch_or_fail(coopname, supplier_braname);
|
||||
|
||||
verify_document_or_fail(convert_out);
|
||||
verify_document_or_fail(product_contribution_statement);
|
||||
|
||||
Document::validate_registry_id(convert_out, 0);
|
||||
Document::validate_registry_id(product_contribution_statement, 0);
|
||||
|
||||
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.accepted_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
o.product_contributor = username;
|
||||
o.supplier_braname = supplier_braname;
|
||||
Document::add_document(o.documents, DocumentNames::CONVERT_TO, convert_out);
|
||||
Document::add_document(o.documents, DocumentNames::CONTRIB_STMT, product_contribution_statement);
|
||||
});
|
||||
|
||||
checksum256 agenda_hash = change.hash;
|
||||
|
||||
// Одно заявление в совет — на имущественный паевой взнос
|
||||
::Soviet::create_agenda(
|
||||
_marketplace,
|
||||
coopname,
|
||||
username,
|
||||
get_valid_soviet_action("authcontrib"_n),
|
||||
agenda_hash,
|
||||
_marketplace,
|
||||
Marketplace::get_valid_marketplace_action("authcontrib"_n),
|
||||
"declineacc"_n,
|
||||
product_contribution_statement,
|
||||
std::string("")
|
||||
);
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Заказчик принимает предложение из запасов кооператива.
|
||||
|
||||
@details Заказчик блокирует средства и запрашивает возврат.
|
||||
Поскольку имущество уже на балансе — авторизация взноса не нужна,
|
||||
сразу создаётся заявление на возврат в совет.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param username Имя заказчика
|
||||
@param request_hash Хэш заявки
|
||||
@param convert_in Заявление на конвертацию из кошелька
|
||||
@param return_statement Заявление на возврат паевого взноса имуществом
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::acceptstock(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 convert_in, document2 return_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.type == "coopstock"_n, "Метод acceptstock применим только к заявкам типа coopstock");
|
||||
eosio::check(change.status == "delivered"_n, "Только доставленная заявка может быть принята");
|
||||
|
||||
get_participant_or_fail(coopname, username);
|
||||
|
||||
verify_document_or_fail(convert_in);
|
||||
verify_document_or_fail(return_statement);
|
||||
|
||||
auto change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
|
||||
|
||||
requests.modify(change_itr, _marketplace, [&](auto &o) {
|
||||
o.money_contributor = username;
|
||||
o.status = "reqreturn"_n;
|
||||
Document::add_document(o.documents, DocumentNames::CONVERT_FROM, convert_in);
|
||||
Document::add_document(o.documents, DocumentNames::RETURN_STMT, return_statement);
|
||||
});
|
||||
|
||||
// Блокируем средства заказчика
|
||||
std::string memo = "Блокировка средств для заказа из запасов кооператива №" + std::to_string(change.id);
|
||||
Wallet::sub_available_funds(_marketplace, coopname, username, change.total_cost, _wallet_program, memo);
|
||||
Wallet::add_available_funds(_marketplace, coopname, username, change.total_cost, _marketplace_program, memo);
|
||||
Wallet::block_funds(_marketplace, coopname, username, change.total_cost, _marketplace_program, memo);
|
||||
|
||||
// Сразу отправляем заявление на возврат в совет
|
||||
::Soviet::create_agenda(
|
||||
_marketplace,
|
||||
coopname,
|
||||
username,
|
||||
get_valid_soviet_action("authreturn"_n),
|
||||
change.hash,
|
||||
_marketplace,
|
||||
Marketplace::get_valid_marketplace_action("authreturn"_n),
|
||||
"declineacc"_n,
|
||||
return_statement,
|
||||
std::string("")
|
||||
);
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Авторизация заявления на имущественный паевой взнос советом кооператива.
|
||||
|
||||
@details Совет авторизует заявление поставщика на взнос имуществом.
|
||||
После авторизации заявка переходит в статус authorized — поставщик может начинать поставку.
|
||||
Заявление на возврат будет подано и авторизовано отдельно, перед выдачей заказчику.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param request_hash Хэш заявки
|
||||
@param authorization Документ авторизации от совета
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p _soviet
|
||||
**/
|
||||
[[eosio::action]] void marketplace::authcontrib(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.status == "accepted"_n, "Только принятая заявка может быть авторизована");
|
||||
|
||||
verify_document_or_fail(authorization);
|
||||
Document::validate_registry_id(authorization, 0);
|
||||
|
||||
auto change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
|
||||
requests.modify(change_itr, _marketplace, [&](auto &o){
|
||||
Document::add_document(o.documents, DocumentNames::CONTRIB_AUTH, authorization);
|
||||
o.status = "authorized"_n;
|
||||
});
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Авторизация заявления на возврат паевого взноса имуществом.
|
||||
|
||||
@details Совет авторизует заявление заказчика на возврат паевого взноса.
|
||||
Вызывается ПОСЛЕ подачи заявления заказчиком (requestreturn),
|
||||
когда имущество уже доставлено на КУ и готово к выдаче.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param request_hash Хэш заявки
|
||||
@param authorization Документ авторизации от совета
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p _soviet
|
||||
**/
|
||||
[[eosio::action]] void marketplace::authreturn(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();
|
||||
|
||||
// Заявка должна быть в статусе delivered (имущество доставлено и ждёт выдачи)
|
||||
eosio::check(change.status == "reqreturn"_n, "Возврат можно авторизовать только для заявки с запрошенным возвратом");
|
||||
|
||||
verify_document_or_fail(authorization);
|
||||
Document::validate_registry_id(authorization, 0);
|
||||
|
||||
auto change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
|
||||
requests.modify(change_itr, _marketplace, [&](auto &o){
|
||||
Document::add_document(o.documents, DocumentNames::RETURN_AUTH, authorization);
|
||||
o.status = "retauthorized"_n;
|
||||
});
|
||||
};
|
||||
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Отмена заявки пользователем.
|
||||
|
||||
@details Пользователь может отменить свою заявку с учетом комиссии за отмену.
|
||||
|
||||
@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 || change.product_contributor == username, "Недостаточно прав доступа");
|
||||
|
||||
// Ограничения отмены
|
||||
eosio::check(change.status != "completed"_n, "Нельзя отменить завершенную заявку");
|
||||
eosio::check(change.status != "canceled"_n, "Заявка уже отменена");
|
||||
eosio::check(change.status != "declined"_n, "Нельзя отменить отклоненную заявку");
|
||||
|
||||
// Поставщик не может отменить заявку после поставки
|
||||
if ((change.money_contributor != username && change.product_contributor != username) ||
|
||||
(change.product_contributor == username && (change.status == "supplied1"_n || change.status == "supplied2"_n ||
|
||||
change.status == "delivered"_n || change.status == "received1"_n || change.status == "received2"_n))) {
|
||||
eosio::check(false, "Поставщик не может отменить заявку после поставки");
|
||||
}
|
||||
|
||||
std::string memo = "Отмена заявки №" + std::to_string(change.id);
|
||||
|
||||
// Возвращаем средства заказчику с учетом комиссии за отмену
|
||||
if (username == change.money_contributor) {
|
||||
eosio::asset return_amount = change.total_cost - change.cancellation_fee_amount;
|
||||
|
||||
// Списываем заблокированные средства
|
||||
Wallet::sub_blocked_funds(_marketplace, coopname, change.money_contributor, change.total_cost, _marketplace_program, memo);
|
||||
|
||||
// Возвращаем средства в кошелёк
|
||||
Wallet::add_available_funds(_marketplace, coopname, change.money_contributor, return_amount, _wallet_program, memo);
|
||||
|
||||
// Направляем комиссию в фонд членских взносов
|
||||
if (change.cancellation_fee_amount.amount > 0) {
|
||||
// Wallet::add_member_fee(_marketplace, coopname, change.username, _marketplace_program_id, change.cancellation_fee_amount, memo);
|
||||
}
|
||||
}
|
||||
|
||||
// Удаляем заявку из системы
|
||||
auto change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена для удаления");
|
||||
requests.erase(change_itr);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Завершение поставки.
|
||||
|
||||
@details После истечения гарантийной задержки происходит завершение поставки.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param username Имя пользователя
|
||||
@param request_hash Хэш заявки
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::complete(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 == "received"_n, "Завершение возможно только после статуса received");
|
||||
eosio::check(eosio::time_point_sec(eosio::current_time_point()) >= change.warranty_delay_until, "Гарантийная задержка ещё не истекла");
|
||||
|
||||
// Проводки по кошельку
|
||||
std::string memo = "Завершение поставки для заказа №" + std::to_string(change.id);
|
||||
|
||||
// Поставщик - списываем заблокированный баланс и начисляем в кошелёк
|
||||
Wallet::sub_blocked_funds(_marketplace, coopname, change.product_contributor, change.base_cost, _marketplace_program, memo);
|
||||
Wallet::add_available_funds(_marketplace, coopname, change.product_contributor, change.base_cost, _wallet_program, memo);
|
||||
|
||||
// Членские взносы (если больше нуля)
|
||||
if (change.membership_fee_amount.amount > 0) {
|
||||
// Wallet::add_member_fee(_marketplace, coopname, change.username, _marketplace_program_id, change.membership_fee_amount, memo);
|
||||
}
|
||||
|
||||
// Удаляем заявку из системы
|
||||
auto change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена для удаления");
|
||||
requests.erase(change_itr);
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Создать заявку на имущество из запасов кооператива.
|
||||
|
||||
@details Кооператив (представитель КУ) создаёт предложение на имущество,
|
||||
которое УЖЕ находится на балансе кооператива. В этом случае:
|
||||
- Не требуется внешний поставщик
|
||||
- Не требуется взнос имуществом (имущество уже на балансе)
|
||||
- Не требуется авторизация взноса советом
|
||||
- Требуется только авторизация возврата при получении заказчиком
|
||||
|
||||
Заявка создаётся со статусом supplied2 (имущество уже на складе КУ).
|
||||
Это упрощённый путь для реализации уценённого или перепредложенного имущества.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param braname КУ, где хранится имущество
|
||||
@param units Количество единиц
|
||||
@param unit_cost Стоимость за единицу (может быть уценённой)
|
||||
@param hash Хэш заявки
|
||||
@param product_lifecycle_secs Срок годности
|
||||
@param warranty_period_secs Гарантийный срок
|
||||
@param membership_fee_amount Членский взнос
|
||||
@param meta Метаданные (описание, фото)
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::coopstock(eosio::name coopname, eosio::name braname, checksum256 hash, uint64_t units, eosio::asset unit_cost, uint32_t product_lifecycle_secs, uint32_t warranty_period_secs, eosio::asset membership_fee_amount, std::string meta) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto existing = get_request_by_hash(coopname, hash);
|
||||
eosio::check(!existing.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(units > 0, "Количество единиц должно быть больше нуля");
|
||||
|
||||
get_branch_or_fail(coopname, braname);
|
||||
|
||||
eosio::asset base_cost = unit_cost * units;
|
||||
eosio::asset total_cost = base_cost + membership_fee_amount;
|
||||
eosio::asset zero_fee = eosio::asset(0, coop.initial.symbol);
|
||||
|
||||
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 = "coopstock"_n;
|
||||
i.username = coopname;
|
||||
i.coopname = coopname;
|
||||
i.status = "delivered"_n;
|
||||
i.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.money_contributor = ""_n;
|
||||
i.product_contributor = coopname;
|
||||
i.meta = meta;
|
||||
i.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
i.supplied_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
i.delivered_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
i.cancellation_fee_amount = zero_fee;
|
||||
i.receiver_braname = braname;
|
||||
i.supplier_braname = braname;
|
||||
i.warehouse = braname;
|
||||
});
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Отклонение заявки.
|
||||
|
||||
@details Отклонение заявки с указанием причины.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param username Имя пользователя
|
||||
@param request_hash Хэш заявки
|
||||
@param meta Причина отклонения
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::decline(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.status == "active"_n || change.status == "accepted"_n, "Можно отклонить только активную или принятую заявку");
|
||||
|
||||
std::string memo = "Отклонение заявки №" + std::to_string(change.id) + ": " + meta;
|
||||
|
||||
// Возвращаем средства заказчику если они были заблокированы
|
||||
if (change.status == "active"_n && change.money_contributor != ""_n) {
|
||||
// Списываем заблокированные средства
|
||||
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);
|
||||
}
|
||||
|
||||
// Удаляем заявку из системы
|
||||
auto change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена для удаления");
|
||||
requests.erase(change_itr);
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Отклонение принятия заявки советом (declineacc).
|
||||
|
||||
@details Данный метод вызывается советом когда заявление на конвертацию, возврат или взнос отклоняется.
|
||||
После рефакторинга работаем только с одной заявкой orderoffer.
|
||||
|
||||
@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);
|
||||
|
||||
// Отклоняем заявку
|
||||
decline_request(coopname, change);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Статический метод для отклонения заявки (используется советом)
|
||||
*/
|
||||
void marketplace::decline_request(eosio::name coopname, const request& change) {
|
||||
requests_index requests(_marketplace, coopname.value);
|
||||
|
||||
// Проверяем, что заявка все еще существует
|
||||
auto change_itr = requests.find(change.id);
|
||||
if (change_itr == requests.end()) {
|
||||
return; // Заявка уже удалена
|
||||
}
|
||||
|
||||
std::string memo = "Отклонение заявления советом по программе №" + std::to_string(_marketplace_program_id) + " для заявки ID: " + std::to_string(change.id);
|
||||
|
||||
// Возвращаем средства заказчику если они были заблокированы
|
||||
if (change.type == "orderoffer"_n && change.total_cost.amount > 0 && change.money_contributor != ""_n) {
|
||||
// Списываем заблокированные средства с маркетплейса
|
||||
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);
|
||||
}
|
||||
|
||||
// Удаляем заявку из системы
|
||||
requests.erase(change_itr);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Перевод заявки в статус готово к выдаче.
|
||||
|
||||
@details Председатель КУ переводит заявку в статус delivered независимо от транспортировки.
|
||||
Используется когда транспортировка между КУ не нужна или имущество уже находится на складе.
|
||||
Просто перевод статуса без дополнительных документов.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param username Имя пользователя (может быть поставщиком или заказчиком)
|
||||
@param request_hash Хэш заявки
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::delivered(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 == "supplied2"_n || change.status == "shiprecvd"_n, "Перевод в статус delivered возможен только после поставки (supplied2) или после получения на склад (shiprecvd)");
|
||||
|
||||
// Обновляем заявку - просто перевод статуса
|
||||
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());
|
||||
// Устанавливаем крайний срок получения (3 дня)
|
||||
o.deadline_for_receipt = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch() + 3 * 24 * 60 * 60);
|
||||
});
|
||||
};
|
||||
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Уничтожение просроченного имущества.
|
||||
|
||||
@details Если имущество не получено заказчиком в срок (deadline_for_receipt)
|
||||
и его срок годности истёк, кооператив может уничтожить его.
|
||||
Требуется комиссионный акт и видео/фото подтверждение.
|
||||
|
||||
При уничтожении:
|
||||
- Заблокированные средства заказчика разблокируются за вычетом cancellation_fee
|
||||
- cancellation_fee зачисляется в фонд членских взносов
|
||||
- Из фонда членских взносов поставщику выплачивается стоимость
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param request_hash Хэш заявки
|
||||
@param destruction_act Акт уничтожения имущества (с комиссией)
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::destroy(eosio::name coopname, checksum256 request_hash, document2 destruction_act) {
|
||||
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 || change.status == "supplied2"_n,
|
||||
"Уничтожение возможно только для доставленного/поставленного имущества"
|
||||
);
|
||||
|
||||
// Проверяем что срок получения истёк (если был установлен)
|
||||
if (change.deadline_for_receipt.sec_since_epoch() > 0) {
|
||||
eosio::check(
|
||||
eosio::current_time_point().sec_since_epoch() > change.deadline_for_receipt.sec_since_epoch(),
|
||||
"Срок получения ещё не истёк"
|
||||
);
|
||||
}
|
||||
|
||||
verify_document_or_fail(destruction_act);
|
||||
|
||||
auto change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена");
|
||||
|
||||
// Возврат средств заказчику за вычетом штрафа
|
||||
if (change.money_contributor.value != 0) {
|
||||
eosio::asset refund = change.total_cost - change.cancellation_fee_amount;
|
||||
|
||||
if (refund.amount > 0) {
|
||||
std::string memo = "Возврат за уничтоженное имущество по заявке №" + std::to_string(change.id);
|
||||
Wallet::unblock_funds(_marketplace, coopname, change.money_contributor, change.total_cost, _marketplace_program, memo);
|
||||
Wallet::sub_available_funds(_marketplace, coopname, change.money_contributor, change.total_cost, _marketplace_program, memo);
|
||||
Wallet::add_available_funds(_marketplace, coopname, change.money_contributor, refund, _wallet_program, memo);
|
||||
}
|
||||
|
||||
// Штраф — в фонд членских взносов
|
||||
if (change.cancellation_fee_amount.amount > 0) {
|
||||
eosio::action(
|
||||
eosio::permission_level{ _marketplace, "active"_n },
|
||||
_fund,
|
||||
"spreadamount"_n,
|
||||
std::make_tuple(coopname, change.cancellation_fee_amount)
|
||||
).send();
|
||||
}
|
||||
}
|
||||
|
||||
// Выплата поставщику (если был)
|
||||
if (change.product_contributor.value != 0 && change.product_contributor != coopname) {
|
||||
std::string memo = "Выплата поставщику за уничтоженное имущество по заявке №" + std::to_string(change.id);
|
||||
Wallet::unblock_funds(_marketplace, coopname, change.product_contributor, change.base_cost, _marketplace_program, memo);
|
||||
Wallet::sub_available_funds(_marketplace, coopname, change.product_contributor, change.base_cost, _marketplace_program, memo);
|
||||
Wallet::add_available_funds(_marketplace, coopname, change.product_contributor, change.base_cost, _wallet_program, memo);
|
||||
}
|
||||
|
||||
// Удаляем заявку
|
||||
requests.erase(change_itr);
|
||||
};
|
||||
@@ -1,92 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Создать заявку orderoffer — заказчик создаёт заявку на поставку товара.
|
||||
|
||||
@details Заказчик создаёт заявку на поставку. Средства блокируются сразу.
|
||||
Заявление на возврат НЕ подаётся на этом этапе — оно будет подано перед получением
|
||||
(requestreturn), когда известен точный вес/состав имущества.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param receiver_braname КУ заказчика для получения товара
|
||||
@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 convert_in Заявление на конвертацию из кошелька в маркетплейс
|
||||
@param meta Метаданные
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
*/
|
||||
[[eosio::action]] void marketplace::orderoffer(eosio::name coopname, eosio::name receiver_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, document2 convert_in, eosio::name delivery_type, eosio::name contribution_type, 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, receiver_braname);
|
||||
auto program = get_program_or_fail(coopname, _marketplace_program_id);
|
||||
|
||||
eosio::check(product_lifecycle_secs > 0, "Срок годности должен быть установлен");
|
||||
eosio::check(warranty_period_secs > 0, "Гарантийный срок должен быть больше нуля");
|
||||
|
||||
verify_document_or_fail(convert_in);
|
||||
Document::validate_registry_id(convert_in, 0);
|
||||
|
||||
eosio::asset base_cost = unit_cost * units;
|
||||
eosio::asset total_cost = base_cost + membership_fee_amount;
|
||||
|
||||
eosio::check(cancellation_fee_amount <= total_cost, "Комиссия за отмену не может превышать общую стоимость");
|
||||
eosio::check(delivery_type == "internal"_n || delivery_type == "external"_n, "Тип доставки: internal или external");
|
||||
eosio::check(contribution_type == "share"_n || contribution_type == "member"_n, "Тип взноса: share или member");
|
||||
|
||||
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);
|
||||
|
||||
std::vector<Document::named_document> documents;
|
||||
Document::add_document(documents, DocumentNames::CONVERT_FROM, convert_in);
|
||||
|
||||
requests.emplace(_marketplace, [&](auto &i) {
|
||||
i.id = request_id;
|
||||
i.hash = hash;
|
||||
i.type = "orderoffer"_n;
|
||||
i.username = username;
|
||||
i.coopname = coopname;
|
||||
i.status = "active"_n;
|
||||
i.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.money_contributor = username;
|
||||
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.receiver_braname = receiver_braname;
|
||||
i.delivery_type = delivery_type;
|
||||
i.contribution_type = contribution_type;
|
||||
i.documents = documents;
|
||||
});
|
||||
|
||||
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);
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Получение товара заказчиком.
|
||||
|
||||
@details Заказчик приходит на КУ для получения имущества.
|
||||
Председатель КУ подписывает акт и передаёт имущество заказчику.
|
||||
Получение возможно ТОЛЬКО после авторизации возврата советом (статус retauthorized).
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param username Имя заказчика (председатель КУ подписывает от лица кооператива)
|
||||
@param request_hash Хэш заявки
|
||||
@param document Акт получения имущества (подпись председателя КУ)
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::receive(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 == "retauthorized"_n, "Получение возможно только после авторизации возврата советом");
|
||||
eosio::check(change.money_contributor == username, "Недостаточно прав доступа");
|
||||
|
||||
verify_document_or_fail(document);
|
||||
Document::validate_registry_id(document, 0);
|
||||
|
||||
auto change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
|
||||
requests.modify(change_itr, _marketplace, [&](auto &o){
|
||||
o.status = "received1"_n;
|
||||
Document::add_document(o.documents, DocumentNames::RECEIVE_ACT, document);
|
||||
});
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Подтверждение получения заказчиком.
|
||||
|
||||
@details Заказчик подтверждает факт получения имущества второй подписью акта приёма-передачи.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param username Имя заказчика
|
||||
@param request_hash Хэш заявки
|
||||
@param document Акт подтверждения получения
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::receivecnf(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, "Подтверждение возможно только после статуса received1");
|
||||
eosio::check(change.money_contributor == username, "Недостаточно прав доступа");
|
||||
|
||||
// Проверяем подпись документа
|
||||
verify_document_or_fail(document);
|
||||
|
||||
// Валидируем документ по registry_id (пока что ноль)
|
||||
Document::validate_registry_id(document, 0);
|
||||
|
||||
// Обновляем заявку
|
||||
auto change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
|
||||
requests.modify(change_itr, _marketplace, [&](auto &o){
|
||||
o.status = "received2"_n;
|
||||
o.received_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
// Устанавливаем время окончания гарантийной задержки
|
||||
o.warranty_delay_until = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch() + change.warranty_period_secs);
|
||||
// Добавляем акт подтверждения с именем
|
||||
Document::add_document(o.documents, DocumentNames::RECEIVE_ACT_CONF, document);
|
||||
});
|
||||
|
||||
// Проводки по кошельку
|
||||
std::string memo = "Подтверждение получения для заказа №" + std::to_string(change.id);
|
||||
|
||||
// Паевой фонд - уменьшаем циркуляцию
|
||||
Ledger::sub(_marketplace, coopname, Ledger::accounts::SHARE_FUND, change.base_cost, memo, request_hash, username);
|
||||
|
||||
// Заказчик - списываем заблокированный баланс
|
||||
Wallet::sub_blocked_funds(_marketplace, coopname, change.money_contributor, change.total_cost, _marketplace_program, memo);
|
||||
};
|
||||
@@ -1,95 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Перепредложение непринятого имущества по пути coopstock.
|
||||
|
||||
@details Если заказчик не получил имущество в срок, но срок годности ещё не истёк,
|
||||
председатель КУ может перепредложить его другим пайщикам. Это создаёт новую заявку
|
||||
типа coopstock на основе текущей, с возможностью изменения цены.
|
||||
|
||||
Текущая заявка закрывается (средства возвращаются заказчику за вычетом штрафа),
|
||||
создаётся новая заявка coopstock.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param request_hash Хэш текущей заявки
|
||||
@param new_hash Хэш новой заявки
|
||||
@param new_unit_cost Новая цена за единицу (может быть уценена)
|
||||
@param new_meta Новые метаданные
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::reoffer(eosio::name coopname, checksum256 request_hash, checksum256 new_hash, eosio::asset new_unit_cost, std::string new_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.status == "delivered"_n || change.status == "supplied2"_n,
|
||||
"Перепредложение возможно только для доставленного имущества"
|
||||
);
|
||||
|
||||
auto coop = get_cooperative_or_fail(coopname);
|
||||
eosio::check(new_unit_cost.symbol == coop.initial.symbol, "Неверный символ токена");
|
||||
|
||||
// Возврат средств заказчику за вычетом штрафа (если заказчик был)
|
||||
if (change.money_contributor.value != 0) {
|
||||
eosio::asset refund = change.total_cost - change.cancellation_fee_amount;
|
||||
|
||||
if (refund.amount > 0) {
|
||||
std::string memo = "Возврат при перепредложении имущества по заявке №" + std::to_string(change.id);
|
||||
Wallet::unblock_funds(_marketplace, coopname, change.money_contributor, change.total_cost, _marketplace_program, memo);
|
||||
Wallet::sub_available_funds(_marketplace, coopname, change.money_contributor, change.total_cost, _marketplace_program, memo);
|
||||
Wallet::add_available_funds(_marketplace, coopname, change.money_contributor, refund, _wallet_program, memo);
|
||||
}
|
||||
|
||||
if (change.cancellation_fee_amount.amount > 0) {
|
||||
eosio::action(
|
||||
eosio::permission_level{ _marketplace, "active"_n },
|
||||
_fund,
|
||||
"spreadamount"_n,
|
||||
std::make_tuple(coopname, change.cancellation_fee_amount)
|
||||
).send();
|
||||
}
|
||||
}
|
||||
|
||||
// Удаляем старую заявку
|
||||
auto change_itr = requests.find(change.id);
|
||||
requests.erase(change_itr);
|
||||
|
||||
// Создаём новую заявку типа coopstock
|
||||
eosio::asset zero_fee = eosio::asset(0, coop.initial.symbol);
|
||||
eosio::asset new_base_cost = new_unit_cost * change.units;
|
||||
eosio::asset new_total_cost = new_base_cost + change.membership_fee_amount;
|
||||
|
||||
auto new_existing = get_request_by_hash(coopname, new_hash);
|
||||
eosio::check(!new_existing.has_value(), "Заявка с новым хэшем уже существует");
|
||||
|
||||
uint64_t new_id = get_global_id(_marketplace, "requests"_n);
|
||||
|
||||
requests.emplace(_marketplace, [&](auto &i) {
|
||||
i.id = new_id;
|
||||
i.hash = new_hash;
|
||||
i.type = "coopstock"_n;
|
||||
i.username = coopname;
|
||||
i.coopname = coopname;
|
||||
i.status = "delivered"_n;
|
||||
i.units = change.units;
|
||||
i.unit_cost = new_unit_cost;
|
||||
i.base_cost = new_base_cost;
|
||||
i.membership_fee_amount = change.membership_fee_amount;
|
||||
i.total_cost = new_total_cost;
|
||||
i.product_lifecycle_secs = change.product_lifecycle_secs;
|
||||
i.warranty_period_secs = change.warranty_period_secs;
|
||||
i.money_contributor = ""_n;
|
||||
i.product_contributor = coopname;
|
||||
i.meta = new_meta;
|
||||
i.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
i.delivered_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
i.cancellation_fee_amount = zero_fee;
|
||||
i.receiver_braname = change.warehouse;
|
||||
i.supplier_braname = change.warehouse;
|
||||
i.warehouse = change.warehouse;
|
||||
});
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Запрос на возврат паевого взноса имуществом — перед получением.
|
||||
|
||||
@details Заказчик подаёт заявление на возврат паевого взноса имуществом.
|
||||
Вызывается когда имущество доставлено на КУ получателя (статус delivered).
|
||||
Председатель КУ фиксирует факт выдачи и точные параметры имущества (вес, количество).
|
||||
|
||||
Создаёт пункт повестки для совета на авторизацию возврата.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param username Имя заказчика
|
||||
@param request_hash Хэш заявки
|
||||
@param return_statement Заявление на возврат паевого взноса (обновлённое, с точным весом)
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::reqreturn(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 return_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 == "delivered"_n, "Запрос возврата возможен только для доставленного имущества");
|
||||
eosio::check(change.money_contributor == username, "Только заказчик может запросить возврат");
|
||||
|
||||
verify_document_or_fail(return_statement);
|
||||
Document::validate_registry_id(return_statement, 0);
|
||||
|
||||
auto change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
|
||||
|
||||
// Обновляем/добавляем заявление на возврат с актуальными данными
|
||||
requests.modify(change_itr, _marketplace, [&](auto &o){
|
||||
// Удаляем старое заявление на возврат если было
|
||||
Document::remove_document(o.documents, DocumentNames::RETURN_STMT);
|
||||
Document::add_document(o.documents, DocumentNames::RETURN_STMT, return_statement);
|
||||
o.status = "reqreturn"_n;
|
||||
});
|
||||
|
||||
// Отправляем заявление на возврат в совет
|
||||
::Soviet::create_agenda(
|
||||
_marketplace,
|
||||
coopname,
|
||||
username,
|
||||
get_valid_soviet_action("authreturn"_n),
|
||||
change.hash,
|
||||
_marketplace,
|
||||
Marketplace::get_valid_marketplace_action("authreturn"_n),
|
||||
"declineacc"_n,
|
||||
return_statement,
|
||||
std::string("")
|
||||
);
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Подтверждение поставки председателем КУ.
|
||||
|
||||
@details Председатель кооперативного участка или доверенное им лицо подтверждает факт поставки имущества.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param username Имя представителя кооператива
|
||||
@param request_hash Хэш заявки
|
||||
@param act Акт подтверждения поставки
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::supplcnf(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 act) {
|
||||
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 == "supplied1"_n, "Поставка должна быть подтверждена после статуса supplied1");
|
||||
|
||||
// Проверяем подпись документа
|
||||
verify_document_or_fail(act);
|
||||
|
||||
// Валидируем документ по registry_id (пока что ноль)
|
||||
Document::validate_registry_id(act, 0);
|
||||
|
||||
// Обновляем заявку
|
||||
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.warehouse = change.supplier_braname; // Ставим имущество на склад КУ приёма
|
||||
o.supplied_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
// Добавляем акт подтверждения с именем
|
||||
Document::add_document(o.documents, DocumentNames::SUPPLY_ACT_CONF, act);
|
||||
});
|
||||
|
||||
// Проводки по кошельку
|
||||
std::string memo = "Подтверждение поставки для заказа №" + std::to_string(change.id);
|
||||
|
||||
// Паевой фонд - увеличиваем циркуляцию
|
||||
Ledger::add(_marketplace, coopname, Ledger::accounts::SHARE_FUND, change.total_cost, memo, request_hash, username);
|
||||
|
||||
// Поставщик - начисляем и блокируем средства
|
||||
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);
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Поставка имущества в кооператив.
|
||||
|
||||
@details Поставщик поставляет имущество на указанный кооперативный участок и предоставляет подписанный акт приёма-передачи.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param username Имя поставщика
|
||||
@param request_hash Хэш заявки
|
||||
@param act Акт поставки имущества
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::supply(eosio::name coopname, eosio::name username, checksum256 request_hash, document2 act) {
|
||||
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.product_contributor == username, "Недостаточно прав доступа");
|
||||
|
||||
// Проверяем подпись документа
|
||||
verify_document_or_fail(act);
|
||||
|
||||
// Валидируем документ по registry_id (пока что ноль)
|
||||
Document::validate_registry_id(act, 0);
|
||||
|
||||
// Обновляем заявку
|
||||
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());
|
||||
// Добавляем акт поставки с именем
|
||||
Document::add_document(o.documents, DocumentNames::SUPPLY_ACT, act);
|
||||
});
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Создать заказ — заказчик публикует запрос «поставьте мне».
|
||||
|
||||
@details Заказчик создаёт заказ типа order с указанием желаемого количества,
|
||||
стоимости, характеристик. Средства блокируются сразу.
|
||||
Поставщики могут откликнуться встречными предложениями (offer).
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param receiver_braname КУ заказчика
|
||||
@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 convert_in Заявление на конвертацию
|
||||
@param delivery_type Тип доставки: "internal" или "external"
|
||||
@param meta Метаданные
|
||||
|
||||
@note Авторизация: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::createorder(eosio::name coopname, eosio::name receiver_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, document2 convert_in, eosio::name delivery_type, std::string meta) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto existing = get_request_by_hash(coopname, hash);
|
||||
eosio::check(!existing.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(delivery_type == "internal"_n || delivery_type == "external"_n, "Тип доставки: internal или external");
|
||||
|
||||
get_participant_or_fail(coopname, username);
|
||||
get_branch_or_fail(coopname, receiver_braname);
|
||||
auto program = get_program_or_fail(coopname, _marketplace_program_id);
|
||||
|
||||
verify_document_or_fail(convert_in);
|
||||
|
||||
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(request_id);
|
||||
|
||||
std::vector<Document::named_document> documents;
|
||||
Document::add_document(documents, DocumentNames::CONVERT_FROM, 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.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.money_contributor = username;
|
||||
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.receiver_braname = receiver_braname;
|
||||
i.documents = documents;
|
||||
});
|
||||
|
||||
// Блокировка средств заказчика
|
||||
Wallet::sub_available_funds(_marketplace, coopname, username, total_cost, _wallet_program, memo);
|
||||
Wallet::add_available_funds(_marketplace, coopname, username, total_cost, _marketplace_program, memo);
|
||||
Wallet::block_funds(_marketplace, coopname, username, total_cost, _marketplace_program, memo);
|
||||
};
|
||||
@@ -1,88 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Поставщик откликается на заказ встречным предложением.
|
||||
|
||||
@details Поставщик видит заказ и предлагает поставку.
|
||||
Создаёт встречную заявку типа offer, привязанную к order.
|
||||
Заявление на взнос имуществом отправляется в совет.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param supplier_braname КУ поставщика
|
||||
@param username Имя поставщика
|
||||
@param order_hash Хэш заказа
|
||||
@param offer_hash Хэш предложения
|
||||
@param units Количество предлагаемых единиц
|
||||
@param product_lifecycle_secs Срок годности от поставщика
|
||||
@param contribution_statement Заявление на взнос имуществом
|
||||
@param convert_out Заявление на конвертацию
|
||||
|
||||
@note Авторизация: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::respondoffer(eosio::name coopname, eosio::name supplier_braname, eosio::name username, checksum256 order_hash, checksum256 offer_hash, uint64_t units, uint32_t product_lifecycle_secs, document2 contribution_statement, document2 convert_out) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto order_opt = Marketplace::get_request_by_hash(coopname, order_hash);
|
||||
eosio::check(order_opt.has_value(), "Заказ не найден");
|
||||
auto order = order_opt.value();
|
||||
|
||||
eosio::check(order.type == "order"_n, "respondoffer применим только к заказам типа order");
|
||||
eosio::check(order.status == "active"_n, "Заказ должен быть активным");
|
||||
eosio::check(units > 0 && units <= order.units, "Недопустимое количество единиц");
|
||||
|
||||
auto existing_offer = get_request_by_hash(coopname, offer_hash);
|
||||
eosio::check(!existing_offer.has_value(), "Предложение с таким хэшем уже существует");
|
||||
|
||||
get_participant_or_fail(coopname, username);
|
||||
get_branch_or_fail(coopname, supplier_braname);
|
||||
|
||||
verify_document_or_fail(contribution_statement);
|
||||
verify_document_or_fail(convert_out);
|
||||
|
||||
eosio::asset supplier_amount = order.unit_cost * units;
|
||||
|
||||
requests_index requests(_marketplace, coopname.value);
|
||||
uint64_t offer_id = get_global_id(_marketplace, "requests"_n);
|
||||
|
||||
std::vector<Document::named_document> documents;
|
||||
Document::add_document(documents, DocumentNames::CONTRIB_STMT, contribution_statement);
|
||||
Document::add_document(documents, DocumentNames::CONVERT_TO, convert_out);
|
||||
|
||||
requests.emplace(_marketplace, [&](auto &i) {
|
||||
i.id = offer_id;
|
||||
i.hash = offer_hash;
|
||||
i.type = "offer"_n;
|
||||
i.username = username;
|
||||
i.coopname = coopname;
|
||||
i.status = "accepted"_n;
|
||||
i.units = units;
|
||||
i.unit_cost = order.unit_cost;
|
||||
i.base_cost = supplier_amount;
|
||||
i.membership_fee_amount = eosio::asset(0, order.unit_cost.symbol);
|
||||
i.total_cost = supplier_amount;
|
||||
i.product_lifecycle_secs = product_lifecycle_secs;
|
||||
i.warranty_period_secs = order.warranty_period_secs;
|
||||
i.money_contributor = order.money_contributor;
|
||||
i.product_contributor = username;
|
||||
i.meta = "";
|
||||
i.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
i.accepted_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
i.cancellation_fee_amount = eosio::asset(0, order.unit_cost.symbol);
|
||||
i.receiver_braname = order.receiver_braname;
|
||||
i.supplier_braname = supplier_braname;
|
||||
i.documents = documents;
|
||||
});
|
||||
|
||||
// Отправляем заявление на взнос в совет
|
||||
::Soviet::create_agenda(
|
||||
_marketplace,
|
||||
coopname,
|
||||
username,
|
||||
get_valid_soviet_action("authcontrib"_n),
|
||||
offer_hash,
|
||||
_marketplace,
|
||||
Marketplace::get_valid_marketplace_action("authcontrib"_n),
|
||||
"declineacc"_n,
|
||||
contribution_statement,
|
||||
std::string("")
|
||||
);
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
\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 == "orderoffer"_n, "Спор может быть открыт только по заявке orderoffer");
|
||||
eosio::check(change.username == username, "Только заказчик может открыть спор");
|
||||
eosio::check(change.is_warranty_return == false, "Нельзя открыть спор на спор");
|
||||
|
||||
eosio::check(change.status == "received"_n || change.status == "completed"_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;
|
||||
Document::add_document(e.documents, Marketplace::DocumentNames::WDISPUTE, document);
|
||||
});
|
||||
|
||||
// Используем хэш заявки как идентификатор пакета решений
|
||||
checksum256 agenda_hash = change.hash;
|
||||
|
||||
// Отправляем заявление на гарантийный возврат в совет
|
||||
::Soviet::create_agenda(
|
||||
_marketplace,
|
||||
coopname,
|
||||
username,
|
||||
get_valid_soviet_action("mpwreturn"_n),
|
||||
agenda_hash,
|
||||
_marketplace,
|
||||
Marketplace::get_valid_marketplace_action("wauthorize"_n),
|
||||
"wdecline"_n,
|
||||
document,
|
||||
std::string("")
|
||||
);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
\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 change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
|
||||
|
||||
if (accept) {
|
||||
// Поставщик принимает товар
|
||||
requests.modify(change_itr, _marketplace, [&](auto &ch) {
|
||||
ch.status = "wcompleted"_n;
|
||||
Document::add_document(ch.documents, Marketplace::DocumentNames::WACCEPT_ACT, document);
|
||||
});
|
||||
} else {
|
||||
// Поставщик отказывается от товара
|
||||
requests.modify(change_itr, _marketplace, [&](auto &ch) {
|
||||
ch.status = "wdeclined"_n;
|
||||
Document::add_document(ch.documents, Marketplace::DocumentNames::WACCEPT_ACT, document);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
\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, "Заявка должна быть в статусе диспута");
|
||||
|
||||
// Обновляем статус заявки и добавляем документы авторизации
|
||||
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;
|
||||
Document::add_document(o.documents, Marketplace::DocumentNames::WRETURN_AUTH, wreturn_authorization);
|
||||
Document::add_document(o.documents, Marketplace::DocumentNames::WSUPPLY_AUTH, wsupply_authorization);
|
||||
});
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
\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);
|
||||
|
||||
// Обновляем статус заявки и добавляем акт предложения товара
|
||||
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;
|
||||
Document::add_document(ch.documents, Marketplace::DocumentNames::WOFFER_ACT, document);
|
||||
});
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
\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);
|
||||
|
||||
// Обновляем статус заявки и добавляем акт приёма возврата
|
||||
auto change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
|
||||
requests.modify(change_itr, _marketplace, [&](auto &ch) {
|
||||
ch.status = "wreturned"_n;
|
||||
Document::add_document(ch.documents, Marketplace::DocumentNames::WRETURN_ACT, document);
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @brief Председатель принимает гарантийный возврат на очном осмотре (Story 7.4, p.mkt.return).
|
||||
*
|
||||
* Композитная транзакция (атомарно в одной Antelope tx):
|
||||
* - Ledger2::apply(o.mkt.return, fact_cost, orderer, hash=request.hash) — Дт 91 / Кт 86, ISSUE w.mkt.member.
|
||||
* - Ledger2::apply(o.mkt.return2, fact_cost, orderer, hash=request.hash) — Дт 10 / Кт 91, NONE.
|
||||
*
|
||||
* Compensating forward, не revert (Locked Decision L3 — AR14): новое событие в
|
||||
* journal с прикладным полем `original_consume_op_id` (заполняется backend'ом
|
||||
* в submretrn) для трассировки. Исходные o.mkt.consum / o.mkt.consum2 в
|
||||
* журнале НЕ модифицируются.
|
||||
*
|
||||
* Status: approved_for_visit → return_accepted (final). Имущество возвращается
|
||||
* на склад КУ; средства восстанавливаются на w.mkt.member.available заказчика.
|
||||
*
|
||||
* Guards:
|
||||
* - Подписант (`signer`) авторизован для указанного КУ (`braname`).
|
||||
* - return_request.status == approved_for_visit.
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::accretrn(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
eosio::name braname,
|
||||
checksum256 request_hash,
|
||||
document2 decision) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto branch = get_branch_or_fail(coopname, braname);
|
||||
eosio::check(branch.is_user_authorized(signer),
|
||||
"Подписант не уполномочен принимать возвраты данного кооперативного участка");
|
||||
|
||||
auto r = Marketplace::get_return_request_by_hash_or_fail(coopname, request_hash);
|
||||
eosio::check(r.status == ReturnStatus::APPROVED_FOR_VISIT,
|
||||
"Заявление не одобрено для очного осмотра");
|
||||
|
||||
if (!is_empty_document(decision)) {
|
||||
verify_document_or_fail(decision, { signer });
|
||||
}
|
||||
|
||||
// Композитная пара return + return2
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::RETURN_BY_MEMBER,
|
||||
r.fact_cost, r.orderer, r.hash,
|
||||
Marketplace::Memo::get_return_by_member_memo(r.id, r.original_order_id));
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::RETURN_TRANSIT_CLOSE,
|
||||
r.fact_cost, r.orderer, r.hash,
|
||||
Marketplace::Memo::get_return_transit_close_memo(r.id, r.original_order_id));
|
||||
|
||||
Marketplace::update_return_request(coopname, r.id, [&](auto& upd) {
|
||||
upd.status = ReturnStatus::RETURN_ACCEPTED;
|
||||
upd.decision_visit = decision;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @brief Председатель удалённо одобряет очный визит (Story 7.2, p.mkt.return).
|
||||
*
|
||||
* Без ledger2-операций. Статус return_request: pending_review → approved_for_visit.
|
||||
* decision document сохраняется (опциональный — может быть пустой, тогда решение
|
||||
* фиксируется только статусом + actor + blockchain_actions[at]).
|
||||
*
|
||||
* Guards:
|
||||
* - Подписант (`signer`) авторизован для указанного КУ (`braname`):
|
||||
* председатель / trustee / trusted в `branches[braname]`.
|
||||
* - return_request.status == pending_review.
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::aprretrem(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
eosio::name braname,
|
||||
checksum256 request_hash,
|
||||
document2 decision) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto branch = get_branch_or_fail(coopname, braname);
|
||||
eosio::check(branch.is_user_authorized(signer),
|
||||
"Подписант не уполномочен принимать решения по заявлениям данного кооперативного участка");
|
||||
|
||||
auto r = Marketplace::get_return_request_by_hash_or_fail(coopname, request_hash);
|
||||
eosio::check(r.status == ReturnStatus::PENDING_REVIEW,
|
||||
"Заявление не находится на рассмотрении");
|
||||
|
||||
if (!is_empty_document(decision)) {
|
||||
verify_document_or_fail(decision, { signer });
|
||||
}
|
||||
|
||||
Marketplace::update_return_request(coopname, r.id, [&](auto& upd) {
|
||||
upd.status = ReturnStatus::APPROVED_FOR_VISIT;
|
||||
upd.decision_remote = decision;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @brief Председатель удалённо отказывает в гарантийном возврате (Story 7.2, p.mkt.return).
|
||||
*
|
||||
* Финальное решение, без ledger2-операций. Статус: pending_review → rejected_remote.
|
||||
* reason сохраняется в return_request.reason_remote для UI заказчика.
|
||||
*
|
||||
* Guards:
|
||||
* - Подписант (`signer`) авторизован для указанного КУ (`braname`).
|
||||
* - return_request.status == pending_review.
|
||||
* - reason.size() > 0.
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::rejretrem(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
eosio::name braname,
|
||||
checksum256 request_hash,
|
||||
std::string reason,
|
||||
document2 decision) {
|
||||
require_auth(coopname);
|
||||
eosio::check(reason.size() > 0 && reason.size() <= 500,
|
||||
"Укажите причину отказа (от 1 до 500 символов)");
|
||||
|
||||
auto branch = get_branch_or_fail(coopname, braname);
|
||||
eosio::check(branch.is_user_authorized(signer),
|
||||
"Подписант не уполномочен принимать решения по заявлениям данного кооперативного участка");
|
||||
|
||||
auto r = Marketplace::get_return_request_by_hash_or_fail(coopname, request_hash);
|
||||
eosio::check(r.status == ReturnStatus::PENDING_REVIEW,
|
||||
"Заявление не находится на рассмотрении");
|
||||
|
||||
if (!is_empty_document(decision)) {
|
||||
verify_document_or_fail(decision, { signer });
|
||||
}
|
||||
|
||||
Marketplace::update_return_request(coopname, r.id, [&](auto& upd) {
|
||||
upd.status = ReturnStatus::REJECTED_REMOTE;
|
||||
upd.decision_remote = decision;
|
||||
upd.reason_remote = reason;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @brief Председатель отказывает в гарантийном возврате на очном осмотре (Story 7.3, p.mkt.return).
|
||||
*
|
||||
* Финальное решение, без ledger2-операций. Статус: approved_for_visit → rejected_at_ku.
|
||||
* reason сохраняется в return_request.reason_visit для UI заказчика.
|
||||
*
|
||||
* Guards:
|
||||
* - Подписант (`signer`) авторизован для указанного КУ (`braname`).
|
||||
* - return_request.status == approved_for_visit.
|
||||
* - reason.size() > 0.
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::rejretrn(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
eosio::name braname,
|
||||
checksum256 request_hash,
|
||||
std::string reason,
|
||||
document2 decision) {
|
||||
require_auth(coopname);
|
||||
eosio::check(reason.size() > 0 && reason.size() <= 500,
|
||||
"Укажите причину отказа (от 1 до 500 символов)");
|
||||
|
||||
auto branch = get_branch_or_fail(coopname, braname);
|
||||
eosio::check(branch.is_user_authorized(signer),
|
||||
"Подписант не уполномочен принимать решения по возвратам данного кооперативного участка");
|
||||
|
||||
auto r = Marketplace::get_return_request_by_hash_or_fail(coopname, request_hash);
|
||||
eosio::check(r.status == ReturnStatus::APPROVED_FOR_VISIT,
|
||||
"Заявление не одобрено для очного осмотра");
|
||||
|
||||
if (!is_empty_document(decision)) {
|
||||
verify_document_or_fail(decision, { signer });
|
||||
}
|
||||
|
||||
Marketplace::update_return_request(coopname, r.id, [&](auto& upd) {
|
||||
upd.status = ReturnStatus::REJECTED_AT_KU;
|
||||
upd.decision_visit = decision;
|
||||
upd.reason_visit = reason;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* @brief Пайщик подаёт заявление на гарантийный возврат (Story 7.1, p.mkt.return).
|
||||
*
|
||||
* Без ledger2-операций. Создаётся return_request в pending_review;
|
||||
* order.return_request_id ставится для двусторонней связи. Привязка к
|
||||
* конкретному КУ не сохраняется — каждое последующее действие
|
||||
* (aprretrem/rejretrem/accretrn/rejretrn) принимает `braname` параметром
|
||||
* и валидирует его через `Branch::is_user_authorized`.
|
||||
*
|
||||
* Guards (из p.mkt.return.standard.yaml):
|
||||
* - actor == original_order.orderer.
|
||||
* - original_order.status == received.
|
||||
* - original_order.warranty_until > now() (гарантийный срок не истёк, если
|
||||
* warranty_period_secs > 0; иначе возврат запрещён).
|
||||
* - photos.size() > 0 (фото приложены).
|
||||
* - actual_quantity > 0 && <= original_order.actual_quantity.
|
||||
* - request_hash уникален.
|
||||
* - Active возврат на этот order ещё не открыт (idempotency через
|
||||
* order.return_request_id == 0).
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::submretrn(eosio::name coopname,
|
||||
eosio::name orderer,
|
||||
checksum256 request_hash,
|
||||
checksum256 original_order_hash,
|
||||
uint64_t actual_quantity,
|
||||
std::string reason_text,
|
||||
std::vector<checksum256> photos,
|
||||
document2 statement) {
|
||||
require_auth(coopname);
|
||||
|
||||
eosio::check(actual_quantity > 0, "Возвращаемое количество должно быть больше нуля");
|
||||
eosio::check(!photos.empty(), "Приложите хотя бы одну фотографию товара");
|
||||
eosio::check(reason_text.size() > 0 && reason_text.size() <= 500,
|
||||
"Опишите причину возврата (от 1 до 500 символов)");
|
||||
|
||||
eosio::check(!Marketplace::get_return_request_by_hash(coopname, request_hash).has_value(),
|
||||
"Заявление с таким идентификатором уже подано");
|
||||
|
||||
auto o = Marketplace::get_order_by_hash_or_fail(coopname, original_order_hash);
|
||||
eosio::check(o.orderer == orderer,
|
||||
"Вы не заказчик исходного заказа");
|
||||
eosio::check(o.status == OrderStatus::RECEIVED,
|
||||
"Возврат возможен только по выданному заказу");
|
||||
eosio::check(o.return_request_id == 0,
|
||||
"По этому заказу уже открыто заявление на возврат");
|
||||
eosio::check(actual_quantity <= o.actual_quantity,
|
||||
"Нельзя вернуть больше единиц, чем было выдано");
|
||||
eosio::check(o.warranty_period_secs > 0,
|
||||
"По этому заказу гарантия не предусмотрена");
|
||||
|
||||
const auto now = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
eosio::check(now < o.warranty_until,
|
||||
"Гарантийный срок по заказу истёк");
|
||||
|
||||
const eosio::asset fact_cost = eosio::asset(
|
||||
static_cast<int64_t>(actual_quantity) * o.unit_price.amount,
|
||||
_root_govern_symbol);
|
||||
|
||||
// Создание return_request entity
|
||||
return_requests_index requests(_marketplace, coopname.value);
|
||||
uint64_t request_id = requests.available_primary_key();
|
||||
requests.emplace(_marketplace, [&](auto& r) {
|
||||
r.id = request_id;
|
||||
r.hash = request_hash;
|
||||
r.coopname = coopname;
|
||||
r.orderer = orderer;
|
||||
r.original_order_id = o.id;
|
||||
r.original_order_hash = o.hash;
|
||||
// r.original_consume_op_id заполнит backend post-effect через ParserClient
|
||||
// (подбор по journal с process_hash=order.hash + operation_code=o.mkt.consum).
|
||||
r.actual_quantity = actual_quantity;
|
||||
r.fact_cost = fact_cost;
|
||||
r.reason_text = reason_text;
|
||||
r.photos = photos;
|
||||
r.status = ReturnStatus::PENDING_REVIEW;
|
||||
r.statement = statement;
|
||||
});
|
||||
|
||||
// Двусторонняя связь — order.return_request_id
|
||||
Marketplace::update_order(coopname, o.id, [&](auto& upd) {
|
||||
upd.return_request_id = request_id;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @brief Поставщик акцептует один Order (Story 4.5, p.mkt.supply).
|
||||
*
|
||||
* Без ledger2-операций — статус active → accepted. Backend проходит циклом
|
||||
* по orders соответствующего batch'а, вызывая `acceptorder` per Order
|
||||
* (контракт не принимает векторов order_hash — единичные транзакции
|
||||
* масштабируются на любой размер batch'а).
|
||||
*
|
||||
* После акцепта поставщик считается обязанным доставить партию: отдельная
|
||||
* подпись «готов отгрузить» (бывший prepship) удалена из процесса —
|
||||
* следующий шаг сразу signsupp с актом приёмки.
|
||||
*
|
||||
* Guards:
|
||||
* - Order существует и в статусе active.
|
||||
* - actor == order.offerer.
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::acceptorder(eosio::name coopname,
|
||||
eosio::name offerer,
|
||||
checksum256 order_hash) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto o = Marketplace::get_order_by_hash_or_fail(coopname, order_hash);
|
||||
eosio::check(o.offerer == offerer, "Вы не поставщик этого заказа");
|
||||
eosio::check(o.status == OrderStatus::ACTIVE,
|
||||
"Заказ уже не в активном статусе — акцептовать нельзя");
|
||||
|
||||
Marketplace::update_order(coopname, o.id, [&](auto& upd) {
|
||||
upd.status = OrderStatus::ACCEPTED;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @brief Заказчик отменяет заказ до акцепта поставщиком (Story 4.4, p.mkt.supply).
|
||||
*
|
||||
* Триггерит `o.mkt.unblk` на full `order.total_cost`. Сумма остаётся на
|
||||
* `w.mkt.member.available` заказчика — может быть потрачена на следующий
|
||||
* заказ программы либо выведена явным `o.mkt.recall` (отдельное действие).
|
||||
*
|
||||
* Guards:
|
||||
* - Order существует.
|
||||
* - actor == order.orderer.
|
||||
* - Order в статусе active (до acceptorder). После acceptorder отмена
|
||||
* запрещена — поставщик уже взял обязательство.
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::cancelorder(eosio::name coopname,
|
||||
eosio::name orderer,
|
||||
checksum256 order_hash) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto o = Marketplace::get_order_by_hash_or_fail(coopname, order_hash);
|
||||
eosio::check(o.orderer == orderer, "Вы не заказчик этого заказа");
|
||||
eosio::check(o.status == OrderStatus::ACTIVE,
|
||||
"Нельзя отменить заказ: он уже акцептован поставщиком");
|
||||
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::UNBLOCK_ON_CANCEL,
|
||||
o.total_cost, orderer, o.hash,
|
||||
Marketplace::Memo::get_cancel_order_memo(o.id));
|
||||
|
||||
Marketplace::update_order(coopname, o.id, [&](auto& upd) {
|
||||
upd.status = OrderStatus::CANCELLED;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* @brief Заказчик размещает заказ на товар (Story 4.1, p.mkt.supply шаг 1).
|
||||
*
|
||||
* Серия операций (атомарно в одной транзакции Antelope):
|
||||
* 1. `o.wal.conv` (conditional) — TRANSFER w.wal.share → w.wal.member,
|
||||
* Дт 80 / Кт 86. Только если на `w.wal.member.available` заказчика и
|
||||
* на `w.mkt.member.available` суммарно не хватает суммы заказа.
|
||||
* 2. `o.mkt.assign` (conditional) — TRANSFER w.wal.member → w.mkt.member,
|
||||
* без проводки. Только если на `w.mkt.member.available` не хватает.
|
||||
* 3. `o.mkt.block` (всегда) — BLOCK на `w.mkt.member` пайщика на total_cost.
|
||||
*
|
||||
* Guards (из p.mkt.supply.standard.yaml + Locked Decision L6):
|
||||
* - quantity > 0; unit_price > 0 в _root_govern_symbol; cycle_type валидный.
|
||||
* - Order с таким hash ещё не создан (idempotency).
|
||||
* - Заказчик — активный пайщик кооператива (`get_participant_or_fail`).
|
||||
* - `delivery_braname` существует в `branches` (КУ выдачи задаётся пайщиком
|
||||
* из доступных и неизменен после создания Order'а).
|
||||
* - Σ available трёх кошельков (w.wal.share + w.wal.member + w.mkt.member)
|
||||
* >= total_cost; иначе createorder фейлится без создания Order'а.
|
||||
* - Подписка пайщика на оферту ЦПП «Стол заказов» (L2/L3 онбординг) —
|
||||
* автоматически проверяется в `ledger2::walletop` через
|
||||
* `assert_program_signed` (cross-contract в `wallet::users.programs[]`)
|
||||
* при первом ASSIGN/BLOCK на USER_SHARED-кошельке программы.
|
||||
*
|
||||
* Сообщения проверок — для прямого показа пользователю (UI ловит check'ом).
|
||||
*
|
||||
* @note process_hash для всех трёх ledger2-операций — `order_hash`. Это
|
||||
* даёт backend'у одну точку группировки операций процесса
|
||||
* p.mkt.supply через `getProcess(process_hash)` (Story 9.3).
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::createorder(eosio::name coopname,
|
||||
eosio::name orderer,
|
||||
checksum256 order_hash,
|
||||
checksum256 offer_hash,
|
||||
eosio::name offerer,
|
||||
eosio::name delivery_braname,
|
||||
uint64_t quantity,
|
||||
eosio::asset unit_price,
|
||||
eosio::name cycle_type,
|
||||
uint32_t warranty_period_secs,
|
||||
checksum256 batch_hash) {
|
||||
require_auth(coopname);
|
||||
|
||||
// ── Базовая валидация параметров ────────────────────────────────────
|
||||
eosio::check(quantity > 0, "Количество должно быть больше нуля");
|
||||
eosio::check(unit_price.is_valid() && unit_price.amount > 0,
|
||||
"Некорректная цена за единицу");
|
||||
eosio::check(unit_price.symbol == _root_govern_symbol,
|
||||
"Некорректный символ валюты в цене");
|
||||
eosio::check(cycle_type == CycleType::TIME_BASED ||
|
||||
cycle_type == CycleType::VOLUME_BASED ||
|
||||
cycle_type == CycleType::OPEN_SUBSCRIPT ||
|
||||
cycle_type == CycleType::INDIVIDUAL,
|
||||
"Неизвестный тип цикла отсечки заявок");
|
||||
|
||||
// Idempotency: Order с таким hash не должен существовать
|
||||
eosio::check(!Marketplace::get_order_by_hash(coopname, order_hash).has_value(),
|
||||
"Заказ с таким идентификатором уже создан");
|
||||
|
||||
// Заказчик — активный пайщик кооператива (бросает если не найден / blocked)
|
||||
get_participant_or_fail(coopname, orderer);
|
||||
|
||||
// КУ выдачи существует
|
||||
get_branch_or_fail(coopname, delivery_braname);
|
||||
|
||||
// ── Расчёт total_cost ────────────────────────────────────────────────
|
||||
eosio::asset total_cost = eosio::asset(
|
||||
static_cast<int64_t>(quantity) * unit_price.amount,
|
||||
_root_govern_symbol);
|
||||
eosio::check(total_cost.amount > 0,
|
||||
"Итоговая сумма заказа должна быть больше нуля");
|
||||
|
||||
// ── Достаточность средств: Σ available трёх кошельков >= total_cost ─
|
||||
auto bal_share = Marketplace::get_user_wallet_balance(
|
||||
coopname, ledger2_wallets::SHARE_FUND_PAY, orderer);
|
||||
auto bal_member = Marketplace::get_user_wallet_balance(
|
||||
coopname, ledger2_wallets::CK_MEMBER, orderer);
|
||||
auto bal_mkt = Marketplace::get_user_wallet_balance(
|
||||
coopname, ledger2_wallets::MARKETPLACE_MEMBER, orderer);
|
||||
|
||||
eosio::asset total_available =
|
||||
bal_share.available + bal_member.available + bal_mkt.available;
|
||||
eosio::check(total_available >= total_cost,
|
||||
std::string{"Недостаточно средств для заказа: требуется "} +
|
||||
total_cost.to_string() + ", доступно " + total_available.to_string());
|
||||
|
||||
// ── Расчёт conditional-долей серии ──────────────────────────────────
|
||||
// Сначала тратим w.mkt.member.available; недостающее берём из w.wal.member
|
||||
// через assign; недостающее в w.wal.member берём из w.wal.share через conv.
|
||||
const eosio::asset zero = eosio::asset(0, _root_govern_symbol);
|
||||
|
||||
eosio::asset need_to_assign = (bal_mkt.available >= total_cost)
|
||||
? zero
|
||||
: (total_cost - bal_mkt.available);
|
||||
eosio::asset need_to_conv = (bal_member.available >= need_to_assign)
|
||||
? zero
|
||||
: (need_to_assign - bal_member.available);
|
||||
|
||||
// ── Создание Order entity (id потребуется для memo) ─────────────────
|
||||
orders_index orders(_marketplace, coopname.value);
|
||||
uint64_t new_id = orders.available_primary_key();
|
||||
|
||||
orders.emplace(_marketplace, [&](auto& o) {
|
||||
o.id = new_id;
|
||||
o.hash = order_hash;
|
||||
o.coopname = coopname;
|
||||
o.orderer = orderer;
|
||||
o.offerer = offerer;
|
||||
o.offer_hash = offer_hash;
|
||||
|
||||
o.delivery_braname = delivery_braname;
|
||||
o.accept_braname = eosio::name{}; // заполняется на signsupp
|
||||
|
||||
o.quantity = quantity;
|
||||
o.actual_quantity = quantity; // до signiss2 == quantity (Story 6.2/6.3)
|
||||
o.unit_price = unit_price;
|
||||
o.total_cost = total_cost;
|
||||
o.fact_cost = total_cost; // до signiss2 == total_cost
|
||||
|
||||
o.cycle_type = cycle_type;
|
||||
o.warranty_period_secs = warranty_period_secs;
|
||||
|
||||
o.status = OrderStatus::ACTIVE;
|
||||
o.batch_hash = batch_hash;
|
||||
});
|
||||
|
||||
// ── Шаг 1: o.wal.conv (conditional) ──────────────────────────────────
|
||||
if (need_to_conv.amount > 0) {
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::wallet::CONVERT_TO_MEMBER,
|
||||
need_to_conv, orderer, order_hash,
|
||||
Marketplace::Memo::get_create_order_convert_memo(new_id));
|
||||
}
|
||||
|
||||
// ── Шаг 2: o.mkt.assign (conditional) ────────────────────────────────
|
||||
if (need_to_assign.amount > 0) {
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::ASSIGN_TO_PROGRAM,
|
||||
need_to_assign, orderer, order_hash,
|
||||
Marketplace::Memo::get_create_order_assign_memo(new_id));
|
||||
}
|
||||
|
||||
// ── Шаг 3: o.mkt.block (всегда) ──────────────────────────────────────
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::BLOCK_FOR_ORDER,
|
||||
total_cost, orderer, order_hash,
|
||||
Marketplace::Memo::get_create_order_block_memo(new_id));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @brief Поставщик отказывается от одного Order'а до акцепта (Story 4.5, p.mkt.supply).
|
||||
*
|
||||
* Per-Order: o.mkt.unblk на total_cost (резерв возвращается заказчику) +
|
||||
* статус active → cancelled. Backend проходит циклом по orders соответствующего
|
||||
* batch'а, вызывая `declineorder` per Order — векторов order_hash нет.
|
||||
*
|
||||
* Guards:
|
||||
* - Order существует и в статусе active.
|
||||
* - actor == order.offerer.
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::declineorder(eosio::name coopname,
|
||||
eosio::name offerer,
|
||||
checksum256 order_hash) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto o = Marketplace::get_order_by_hash_or_fail(coopname, order_hash);
|
||||
eosio::check(o.offerer == offerer, "Вы не поставщик этого заказа");
|
||||
eosio::check(o.status == OrderStatus::ACTIVE,
|
||||
"Заказ уже не в активном статусе — отклонить нельзя");
|
||||
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::UNBLOCK_ON_CANCEL,
|
||||
o.total_cost, o.orderer, o.hash,
|
||||
Marketplace::Memo::get_decline_order_memo(o.id));
|
||||
|
||||
Marketplace::update_order(coopname, o.id, [&](auto& upd) {
|
||||
upd.status = OrderStatus::CANCELLED;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @brief Backend закрывает один Order по таймауту цикла отсечки заявок
|
||||
* (Story 4.3, p.mkt.supply).
|
||||
*
|
||||
* Вызывается бэкендом после расчёта по batch'у: если за время цикла Offer'а
|
||||
* threshold не достигнут, бэкенд проходит циклом по всем active Order'ам
|
||||
* этого batch'а и для каждого вызывает `expireorder`. Контракт не знает про
|
||||
* threshold — это вычисление backend'а; on-chain — только закрытие конкретного
|
||||
* Order'а с возвратом резерва.
|
||||
*
|
||||
* Per-Order: o.mkt.unblk на total_cost + статус active → cancelled.
|
||||
*
|
||||
* Guards:
|
||||
* - Order существует и в статусе active (после акцепта поставщика
|
||||
* expireorder не применим — поставщик уже взял обязательство; такие
|
||||
* Order'ы должны идти через `signiss2` обычным порядком либо через
|
||||
* отдельный механизм просрочки доставки).
|
||||
* - require_auth(coopname) — backend от имени кооператива.
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::expireorder(eosio::name coopname,
|
||||
checksum256 order_hash) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto o = Marketplace::get_order_by_hash_or_fail(coopname, order_hash);
|
||||
eosio::check(o.status == OrderStatus::ACTIVE,
|
||||
"Закрыть по таймауту можно только активный заказ");
|
||||
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::UNBLOCK_ON_CANCEL,
|
||||
o.total_cost, o.orderer, o.hash,
|
||||
Marketplace::Memo::get_expire_order_memo(o.id));
|
||||
|
||||
Marketplace::update_order(coopname, o.id, [&](auto& upd) {
|
||||
upd.status = OrderStatus::CANCELLED;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @brief Председатель приёмного КУ ставит закрывающую подпись на АПП приёмки
|
||||
* по одному Order'у (Story 5.3/5.4, p.mkt.supply).
|
||||
*
|
||||
* Per-Order композитная транзакция (атомарно в одной Antelope tx):
|
||||
* - Ledger2::apply(o.mkt.purch, total_cost, …, hash=order.hash) — Дт 10 / Кт 86.
|
||||
* - Ledger2::apply(o.mkt.payout, total_cost, …, hash=order.hash) — Дт 86 / Кт 51.
|
||||
*
|
||||
* Имущество приходуется на склад приёмного КУ (`accept_braname`); поставщику
|
||||
* уходит оплата на w.mkt.payout. Каждый Order — отдельная пара (purch +
|
||||
* payout) с собственным process_hash — векторов order_hash нет, backend
|
||||
* проходит циклом по orders соответствующего batch'а с одним и тем же актом.
|
||||
*
|
||||
* Status: supply_prepared → accepted_to_coop. acceptance_act_signchair
|
||||
* сохраняется.
|
||||
*
|
||||
* Guards:
|
||||
* - Order существует и в статусе supply_prepared.
|
||||
* - Подписант (`signer`) авторизован для приёмного КУ — председатель,
|
||||
* trustee либо доверенное лицо в `branches[accept_braname].trusted[]`.
|
||||
* - На акте есть подписи поставщика и подписанта приёмки.
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::signchair(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
checksum256 order_hash,
|
||||
document2 act) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto o = Marketplace::get_order_by_hash_or_fail(coopname, order_hash);
|
||||
eosio::check(o.status == OrderStatus::SUPPLY_PREPARED,
|
||||
"Заказ не готов к приёмке кооперативом");
|
||||
|
||||
// Авторизация подписи: signer должен быть в trusted списке приёмного КУ.
|
||||
auto branch = get_branch_or_fail(coopname, o.accept_braname);
|
||||
eosio::check(branch.is_user_authorized(signer),
|
||||
"Подписант не уполномочен подписывать акты приёмки данного кооперативного участка");
|
||||
|
||||
verify_document_or_fail(act, { o.offerer, signer });
|
||||
|
||||
// Композитная пара purch + payout (атомарно)
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::PURCHASE_FROM_SUPPLIER,
|
||||
o.total_cost, o.offerer, o.hash,
|
||||
Marketplace::Memo::get_purchase_from_supplier_memo(o.id));
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::PAY_SUPPLIER,
|
||||
o.total_cost, o.offerer, o.hash,
|
||||
Marketplace::Memo::get_pay_supplier_memo(o.id));
|
||||
|
||||
Marketplace::update_order(coopname, o.id, [&](auto& upd) {
|
||||
upd.status = OrderStatus::ACCEPTED_TO_COOP;
|
||||
upd.acceptance_act_signchair = act;
|
||||
upd.current_warehouse_braname = o.accept_braname; // имущество на приёмном складе
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @brief Председатель КУ выдачи открывает выдачу первой подписью АПП-выдачи
|
||||
* (Story 6.1, signiss1).
|
||||
*
|
||||
* Без ledger2-операций. Per-Order: статус accepted_to_coop → ready_to_receive;
|
||||
* issue_act_signiss1 сохраняется; current_warehouse_braname обновляется на
|
||||
* delivery_braname (фиксация факта логистической передачи имущества на склад
|
||||
* выдачи — промежуточные перемещения по заготовочным КУ контрактом не
|
||||
* подписываются, точка хранения переходит «скачком» в этот момент).
|
||||
* Нотификация заказчику — post-effect в backend через ParserClient.
|
||||
*
|
||||
* Guards:
|
||||
* - Order существует и в статусе accepted_to_coop.
|
||||
* - Подписант (`signer`) авторизован для КУ выдачи (`o.delivery_braname`):
|
||||
* председатель / trustee / trusted в `branches[delivery_braname]`.
|
||||
* - verify_document_or_fail(act, {signer}).
|
||||
* - Idempotency: is_empty_document(issue_act_signiss1).
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::signiss1(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
checksum256 order_hash,
|
||||
document2 act) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto o = Marketplace::get_order_by_hash_or_fail(coopname, order_hash);
|
||||
eosio::check(o.status == OrderStatus::ACCEPTED_TO_COOP,
|
||||
"Заказ не готов к открытию выдачи");
|
||||
eosio::check(is_empty_document(o.issue_act_signiss1),
|
||||
"Первая подпись акта выдачи уже зафиксирована");
|
||||
|
||||
auto branch = get_branch_or_fail(coopname, o.delivery_braname);
|
||||
eosio::check(branch.is_user_authorized(signer),
|
||||
"Подписант не уполномочен подписывать акты выдачи данного кооперативного участка");
|
||||
|
||||
verify_document_or_fail(act, { signer });
|
||||
|
||||
Marketplace::update_order(coopname, o.id, [&](auto& upd) {
|
||||
upd.status = OrderStatus::READY_TO_RECEIVE;
|
||||
upd.issue_act_signiss1 = act;
|
||||
upd.current_warehouse_braname = o.delivery_braname; // имущество готово к выдаче на КУ выдачи
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* @brief Заказчик закрывающей подписью АПП-выдачи получает имущество (Story 6.3, signiss2).
|
||||
*
|
||||
* Per-Order атомарная транзакция с поддержкой actual_quantity ≠ ordered (Story 6.2):
|
||||
*
|
||||
* 1) actual == ordered:
|
||||
* Ledger2::apply(o.mkt.consum, fact_cost) — Дт 91 / Кт 10, REVOKE на blocked.
|
||||
* Ledger2::apply(o.mkt.consum2, fact_cost) — Дт 86 / Кт 91, NONE.
|
||||
*
|
||||
* 2) actual < ordered (выдано меньше — остаток на blocked возвращается):
|
||||
* Ledger2::apply(o.mkt.unblk, ordered_cost - fact_cost) — снятие резерва на разницу.
|
||||
* затем — те же consum + consum2 на fact_cost.
|
||||
*
|
||||
* 3) actual > ordered (доплата с паевого):
|
||||
* diff = fact_cost - ordered_cost
|
||||
* проверка достаточности средств для diff (через w.wal.share + w.wal.member).
|
||||
* Conditional o.wal.conv (если на w.wal.member недостача).
|
||||
* Conditional o.mkt.assign (если на w.mkt.member недостача).
|
||||
* o.mkt.block(diff) — резервируем доплату на этот же Order.
|
||||
* затем — consum + consum2 на fact_cost.
|
||||
*
|
||||
* Status: ready_to_receive → received. actual_quantity, fact_cost,
|
||||
* issue_act_signiss2, warranty_until заполняются.
|
||||
*
|
||||
* Guards:
|
||||
* - actor == order.orderer; Order в ready_to_receive.
|
||||
* - Подписант со стороны кооператива (`delivery_signer`) авторизован для
|
||||
* КУ выдачи (`o.delivery_braname`).
|
||||
* - verify_document_or_fail(act, {delivery_signer, orderer}).
|
||||
* - actual_quantity > 0.
|
||||
* - При actual > ordered и нехватке средств — транзакция фейлится с
|
||||
* человеческим сообщением, которое UI показывает напрямую.
|
||||
* - Idempotency: is_empty_document(issue_act_signiss2).
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::signiss2(eosio::name coopname,
|
||||
eosio::name orderer,
|
||||
checksum256 order_hash,
|
||||
uint64_t actual_quantity,
|
||||
eosio::name delivery_signer,
|
||||
document2 act) {
|
||||
require_auth(coopname);
|
||||
eosio::check(actual_quantity > 0, "Фактическое количество должно быть больше нуля");
|
||||
|
||||
auto o = Marketplace::get_order_by_hash_or_fail(coopname, order_hash);
|
||||
eosio::check(o.orderer == orderer, "Вы не заказчик этого заказа");
|
||||
eosio::check(o.status == OrderStatus::READY_TO_RECEIVE,
|
||||
"Заказ не готов к выдаче");
|
||||
eosio::check(is_empty_document(o.issue_act_signiss2),
|
||||
"Финальная подпись акта выдачи уже зафиксирована");
|
||||
|
||||
auto branch = get_branch_or_fail(coopname, o.delivery_braname);
|
||||
eosio::check(branch.is_user_authorized(delivery_signer),
|
||||
"Подписант со стороны кооператива не уполномочен подписывать акты выдачи данного кооперативного участка");
|
||||
|
||||
verify_document_or_fail(act, { delivery_signer, orderer });
|
||||
|
||||
const eosio::asset fact_cost = eosio::asset(
|
||||
static_cast<int64_t>(actual_quantity) * o.unit_price.amount,
|
||||
_root_govern_symbol);
|
||||
eosio::check(fact_cost.amount > 0,
|
||||
"Итоговая фактическая сумма заказа должна быть больше нуля");
|
||||
|
||||
// ── Корректирующие операции (если факт ≠ заказ) ─────────────────────
|
||||
if (fact_cost < o.total_cost) {
|
||||
// actual < ordered: возвращаем разницу с blocked → available
|
||||
const eosio::asset diff = o.total_cost - fact_cost;
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::UNBLOCK_ON_CANCEL,
|
||||
diff, orderer, o.hash,
|
||||
Marketplace::Memo::get_signiss2_correction_less_memo(o.id));
|
||||
|
||||
} else if (fact_cost > o.total_cost) {
|
||||
// actual > ordered: добираем разницу с паевого + assign + block
|
||||
const eosio::asset diff = fact_cost - o.total_cost;
|
||||
|
||||
// Проверка доступности diff в трёх кошельках
|
||||
auto bal_share = Marketplace::get_user_wallet_balance(
|
||||
coopname, ledger2_wallets::SHARE_FUND_PAY, orderer);
|
||||
auto bal_member = Marketplace::get_user_wallet_balance(
|
||||
coopname, ledger2_wallets::CK_MEMBER, orderer);
|
||||
auto bal_mkt = Marketplace::get_user_wallet_balance(
|
||||
coopname, ledger2_wallets::MARKETPLACE_MEMBER, orderer);
|
||||
|
||||
eosio::asset total_avail = bal_share.available + bal_member.available + bal_mkt.available;
|
||||
eosio::check(total_avail >= diff,
|
||||
std::string{"Недостаточно средств для дооплаты по факту: требуется "} +
|
||||
diff.to_string() + ", доступно " + total_avail.to_string());
|
||||
|
||||
const eosio::asset zero = eosio::asset(0, _root_govern_symbol);
|
||||
eosio::asset need_to_assign = (bal_mkt.available >= diff)
|
||||
? zero
|
||||
: (diff - bal_mkt.available);
|
||||
eosio::asset need_to_conv = (bal_member.available >= need_to_assign)
|
||||
? zero
|
||||
: (need_to_assign - bal_member.available);
|
||||
|
||||
if (need_to_conv.amount > 0) {
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::wallet::CONVERT_TO_MEMBER,
|
||||
need_to_conv, orderer, o.hash,
|
||||
Marketplace::Memo::get_signiss2_correction_more_convert_memo(o.id));
|
||||
}
|
||||
if (need_to_assign.amount > 0) {
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::ASSIGN_TO_PROGRAM,
|
||||
need_to_assign, orderer, o.hash,
|
||||
Marketplace::Memo::get_signiss2_correction_more_assign_memo(o.id));
|
||||
}
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::BLOCK_FOR_ORDER,
|
||||
diff, orderer, o.hash,
|
||||
Marketplace::Memo::get_signiss2_correction_more_block_memo(o.id));
|
||||
}
|
||||
// fact == ordered — без корректировок
|
||||
|
||||
// ── Композитная пара consum + consum2 на fact_cost ──────────────────
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::CONSUME_BY_MEMBER,
|
||||
fact_cost, orderer, o.hash,
|
||||
Marketplace::Memo::get_consume_by_member_memo(o.id));
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::CONSUME_TRANSIT_CLOSE,
|
||||
fact_cost, orderer, o.hash,
|
||||
Marketplace::Memo::get_consume_transit_close_memo(o.id));
|
||||
|
||||
// ── Закрытие Order'а ────────────────────────────────────────────────
|
||||
const auto now = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
const auto warranty_until = (o.warranty_period_secs > 0)
|
||||
? eosio::time_point_sec(now.sec_since_epoch() + o.warranty_period_secs)
|
||||
: eosio::time_point_sec(0);
|
||||
|
||||
Marketplace::update_order(coopname, o.id, [&](auto& upd) {
|
||||
upd.status = OrderStatus::RECEIVED;
|
||||
upd.actual_quantity = actual_quantity;
|
||||
upd.fact_cost = fact_cost;
|
||||
upd.issue_act_signiss2 = act;
|
||||
upd.warranty_until = warranty_until;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @brief Поставщик первой подписью на АПП приёмки фиксирует партию по одному
|
||||
* Order'у (Story 5.3/5.4, p.mkt.supply).
|
||||
*
|
||||
* Без ledger2-операций (имущество физически на складе, но юридически не
|
||||
* оприходовано до signchair). Per-Order: статус accepted → supply_prepared,
|
||||
* параметр `accept_braname` указывает приёмный КУ — куда поставщик сдаёт
|
||||
* партию. Документ acceptance_act_signsupp сохраняется (копия per Order
|
||||
* — допустимо для on-chain).
|
||||
*
|
||||
* Backend проходит циклом по orders соответствующего batch'а с одним и тем
|
||||
* же актом, вызывая `signsupp` per Order — это позволяет масштабировать
|
||||
* batch на любой размер без риска превысить лимит транзакции Antelope.
|
||||
*
|
||||
* Guards:
|
||||
* - Order существует и в статусе accepted.
|
||||
* - actor == order.offerer.
|
||||
* - `accept_braname` существует в `branches[coopname]`.
|
||||
* - verify_document_or_fail(act, {offerer}) — поставщик подписал.
|
||||
* - Idempotency: повторный вызов запрещён (`is_empty_document(acceptance_act_signsupp)`).
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::signsupp(eosio::name coopname,
|
||||
eosio::name offerer,
|
||||
checksum256 order_hash,
|
||||
eosio::name accept_braname,
|
||||
document2 act) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto o = Marketplace::get_order_by_hash_or_fail(coopname, order_hash);
|
||||
eosio::check(o.offerer == offerer, "Вы не поставщик этого заказа");
|
||||
eosio::check(o.status == OrderStatus::ACCEPTED,
|
||||
"Заказ не в статусе акцепта — подпись поставщика на акт приёмки невозможна");
|
||||
eosio::check(is_empty_document(o.acceptance_act_signsupp),
|
||||
"Подпись поставщика на акт приёмки уже зафиксирована");
|
||||
|
||||
// КУ приёмки существует
|
||||
get_branch_or_fail(coopname, accept_braname);
|
||||
|
||||
verify_document_or_fail(act, { offerer });
|
||||
|
||||
Marketplace::update_order(coopname, o.id, [&](auto& upd) {
|
||||
upd.status = OrderStatus::SUPPLY_PREPARED;
|
||||
upd.acceptance_act_signsupp = act;
|
||||
upd.accept_braname = accept_braname;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @brief Совет отклоняет проект списания скоропорта целиком (Story 8.3, p.mkt.wroff).
|
||||
*
|
||||
* Без ledger2-операций. Status: PROPOSED → REJECTED (final).
|
||||
* reason сохраняется в proposal.reject_reason. Позиции остаются на складах
|
||||
* участков и попадут в следующий цикл списания. Отклонить можно только
|
||||
* непочатый proposal — если хотя бы одна позиция уже исполнена через
|
||||
* execwroff, отклонить целиком запрещено.
|
||||
*
|
||||
* Guards:
|
||||
* - proposal.status == PROPOSED.
|
||||
* - Ни одна из items не была исполнена (executed == false для всех).
|
||||
* - reason.size() > 0.
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::declwroff(eosio::name coopname,
|
||||
eosio::name decided_by,
|
||||
checksum256 proposal_hash,
|
||||
std::string reason) {
|
||||
require_auth(coopname);
|
||||
eosio::check(reason.size() > 0 && reason.size() <= 500,
|
||||
"Укажите причину отклонения проекта списания (от 1 до 500 символов)");
|
||||
|
||||
auto p = Marketplace::get_writeoff_proposal_by_hash_or_fail(coopname, proposal_hash);
|
||||
eosio::check(p.status == WroffStatus::PROPOSED,
|
||||
"Проект списания не находится на рассмотрении");
|
||||
|
||||
for (const auto& it : p.items) {
|
||||
eosio::check(!it.executed,
|
||||
"Нельзя отклонить проект — часть позиций уже исполнена");
|
||||
}
|
||||
|
||||
Marketplace::update_writeoff_proposal(coopname, p.id, [&](auto& upd) {
|
||||
upd.status = WroffStatus::REJECTED;
|
||||
upd.decided_by = decided_by;
|
||||
upd.reject_reason = reason;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @brief Совет исполняет одну позицию проекта списания скоропорта (Story 8.3,
|
||||
* p.mkt.wroff).
|
||||
*
|
||||
* Per-item композитная транзакция (атомарно):
|
||||
* - Ledger2::apply(o.mkt.wroff, item.amount, …, hash=proposal.hash) — Дт 91 / Кт 10.
|
||||
* - Ledger2::apply(o.mkt.wroff2, item.amount, …, hash=proposal.hash) — Дт 86 / Кт 91.
|
||||
*
|
||||
* Backend проходит циклом по неисполненным позициям protocols proposal'а,
|
||||
* вызывая `execwroff(proposal_hash, item_index, protocol)` per item — это
|
||||
* снимает ограничение на максимальный размер протокола (тысячи позиций
|
||||
* не помещаются в одну транзакцию Antelope).
|
||||
*
|
||||
* Все операции с одним process_hash = proposal.hash — backend через
|
||||
* `getProcess(proposal.hash)` соберёт полную трассировку процесса.
|
||||
*
|
||||
* Status: PROPOSED → EXECUTED наступает автоматически после исполнения
|
||||
* последней позиции (когда все items[i].executed становятся true).
|
||||
*
|
||||
* Guards:
|
||||
* - proposal.status == PROPOSED.
|
||||
* - item_index в пределах proposal.items.
|
||||
* - items[item_index].executed == false (idempotency).
|
||||
* - Подписант (`signer`) авторизован для КУ-источника списания
|
||||
* (`items[item_index].braname`) — председатель / trustee / trusted.
|
||||
* - verify_document_or_fail(protocol, {signer}) — sanity check на подпись;
|
||||
* реальная сверка с протоколом совета — backend pre-validation через
|
||||
* soviet::decisions.
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::execwroff(eosio::name coopname,
|
||||
eosio::name signer,
|
||||
checksum256 proposal_hash,
|
||||
uint64_t item_index,
|
||||
document2 protocol) {
|
||||
require_auth(coopname);
|
||||
|
||||
auto p = Marketplace::get_writeoff_proposal_by_hash_or_fail(coopname, proposal_hash);
|
||||
eosio::check(p.status == WroffStatus::PROPOSED,
|
||||
"Проект списания не находится на исполнении");
|
||||
eosio::check(item_index < p.items.size(),
|
||||
"Указана несуществующая позиция в проекте списания");
|
||||
eosio::check(!p.items[item_index].executed,
|
||||
"Эта позиция проекта списания уже исполнена");
|
||||
|
||||
const auto& item = p.items[item_index];
|
||||
|
||||
auto branch = get_branch_or_fail(coopname, item.braname);
|
||||
eosio::check(branch.is_user_authorized(signer),
|
||||
"Подписант не уполномочен исполнять списание данного кооперативного участка");
|
||||
|
||||
verify_document_or_fail(protocol, { signer });
|
||||
|
||||
// Композитная пара wroff + wroff2 для одной позиции
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::WRITE_OFF_PERISHABLE,
|
||||
item.amount, item.braname, p.hash,
|
||||
Marketplace::Memo::get_writeoff_memo(p.id, item_index));
|
||||
Ledger2::apply(_marketplace, coopname,
|
||||
operations::marketplace::WRITE_OFF_TRANSIT_CLOSE,
|
||||
item.amount, item.braname, p.hash,
|
||||
Marketplace::Memo::get_writeoff_transit_close_memo(p.id, item_index));
|
||||
|
||||
// Помечаем позицию исполненной + финализируем proposal если все позиции готовы
|
||||
Marketplace::update_writeoff_proposal(coopname, p.id, [&](auto& upd) {
|
||||
upd.items[item_index].executed = true;
|
||||
upd.protocol = protocol;
|
||||
upd.decided_by = signer;
|
||||
|
||||
bool all_done = true;
|
||||
for (const auto& it : upd.items) {
|
||||
if (!it.executed) { all_done = false; break; }
|
||||
}
|
||||
if (all_done) {
|
||||
upd.status = WroffStatus::EXECUTED;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @brief Backend / админ выносит проект списания скоропорта на повестку
|
||||
* совета (Story 8.1, p.mkt.wroff).
|
||||
*
|
||||
* Без ledger2-операций. Создаётся writeoff_proposal в статусе proposed
|
||||
* (= "draft" в YAML); total_amount = Σ items.amount; все items создаются
|
||||
* с executed=false. Списание выполняется отдельно — `execwroff(proposal_hash,
|
||||
* item_index, protocol)` исполняет одну позицию за вызов; backend проходит
|
||||
* циклом по items, что снимает ограничение на размер протокола.
|
||||
*
|
||||
* Guards:
|
||||
* - actor backend (auth coopname).
|
||||
* - items.size() > 0.
|
||||
* - Все items.amount > 0 в _root_govern_symbol.
|
||||
* - Все items.braname существуют в `branches[coopname]`.
|
||||
* - proposal_hash уникален.
|
||||
*
|
||||
* @ingroup public_marketplace_actions
|
||||
*/
|
||||
void marketplace::propwroff(eosio::name coopname,
|
||||
eosio::name proposed_by,
|
||||
checksum256 proposal_hash,
|
||||
std::vector<wroff_item> items) {
|
||||
require_auth(coopname);
|
||||
|
||||
eosio::check(!items.empty(), "Список позиций к списанию пуст");
|
||||
eosio::check(!Marketplace::get_writeoff_proposal_by_hash(coopname, proposal_hash).has_value(),
|
||||
"Проект списания с таким идентификатором уже создан");
|
||||
|
||||
eosio::asset total = eosio::asset(0, _root_govern_symbol);
|
||||
for (auto& item : items) {
|
||||
eosio::check(item.amount.is_valid() && item.amount.amount > 0,
|
||||
"Каждая позиция должна иметь положительную сумму");
|
||||
eosio::check(item.amount.symbol == _root_govern_symbol,
|
||||
"Некорректный символ валюты в позиции списания");
|
||||
// КУ-источник существует
|
||||
get_branch_or_fail(coopname, item.braname);
|
||||
item.executed = false; // защита от случайно проставленного флага
|
||||
total += item.amount;
|
||||
}
|
||||
|
||||
writeoff_proposals_index proposals(_marketplace, coopname.value);
|
||||
proposals.emplace(_marketplace, [&](auto& p) {
|
||||
p.id = proposals.available_primary_key();
|
||||
p.hash = proposal_hash;
|
||||
p.coopname = coopname;
|
||||
p.proposed_by = proposed_by;
|
||||
p.items = items;
|
||||
p.total_amount = total;
|
||||
p.status = WroffStatus::PROPOSED;
|
||||
});
|
||||
}
|
||||
@@ -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,35 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Перевозка прибыла в место назначения.
|
||||
|
||||
@details Перевозка переходит в статус arrived и ожидает подписи получателя.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param hash Идентификатор перевозки
|
||||
@param transport_act_delivery Акт доставки подписанный водителем
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::arrived(eosio::name coopname, checksum256 hash, document2 transport_act_delivery) {
|
||||
require_auth(coopname);
|
||||
|
||||
// Проверяем что перевозка существует
|
||||
auto shipment = Marketplace::get_shipment_by_hash_or_fail(coopname, hash, "Перевозка не найдена");
|
||||
eosio::check(shipment.status == "transit"_n, "Подписывать акт доставки можно только для перевозки в статусе transit");
|
||||
|
||||
// Проверяем подпись документа
|
||||
verify_document_or_fail(transport_act_delivery, { shipment.driver_username });
|
||||
|
||||
// Валидируем документ по registry_id (пока что ноль)
|
||||
Document::validate_registry_id(transport_act_delivery, 0);
|
||||
|
||||
// Обновляем перевозку - добавляем третий акт
|
||||
shipments_index shipments(_marketplace, coopname.value);
|
||||
auto hash_index = shipments.get_index<"byhash"_n>();
|
||||
auto shipment_itr = hash_index.find(hash);
|
||||
hash_index.modify(shipment_itr, _marketplace, [&](auto &s) {
|
||||
s.status = "arrived"_n; // Новый статус - прибыл, ожидает приёма получателем
|
||||
Document::add_document(s.documents, DocumentNames::SHIPMENT_ARRIVE_ACT, transport_act_delivery);
|
||||
s.delivered_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
});
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Создание новой перевозки с массивом заявок.
|
||||
|
||||
@details Создает новую перевозку от одного КУ к другому с указанием водителя и массива заявок.
|
||||
Представитель КУ отправления подписывает первый акт приёма-передачи.
|
||||
Заявки остаются на складе до получения подписи от водителя.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param hash Внешний идентификатор перевозки
|
||||
@param driver_username Имя водителя-пайщика
|
||||
@param source_braname КУ отправителя
|
||||
@param destination_braname КУ назначения
|
||||
@param request_hashes Массив хэшей заявок для перевозки
|
||||
@param transport_act_sender Акт приёма-передачи от представителя КУ отправления
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::createship(eosio::name coopname, checksum256 hash, eosio::name driver_username, eosio::name source_braname, eosio::name destination_braname, std::vector<checksum256> request_hashes, document2 transport_act_sender) {
|
||||
require_auth(coopname);
|
||||
|
||||
// Проверяем что КУ отправителя и назначения разные
|
||||
eosio::check(source_braname != destination_braname, "КУ отправителя и назначения не могут совпадать");
|
||||
|
||||
// Проверяем что есть хотя бы одна заявка
|
||||
eosio::check(!request_hashes.empty(), "Должна быть указана хотя бы одна заявка");
|
||||
|
||||
// Проверяем что перевозка с таким hash не существует
|
||||
auto existing_shipment = Marketplace::get_shipment_by_hash(coopname, hash);
|
||||
eosio::check(!existing_shipment.has_value(), "Перевозка с таким идентификатором уже существует");
|
||||
|
||||
// Проверяем подпись документа
|
||||
verify_document_or_fail(transport_act_sender);
|
||||
|
||||
// Валидируем документ по registry_id (пока что ноль)
|
||||
Document::validate_registry_id(transport_act_sender, 0);
|
||||
|
||||
requests_index requests(_marketplace, coopname.value);
|
||||
|
||||
// Проверяем все заявки но НЕ снимаем их со склада (это будет при signbydriver)
|
||||
for (auto& request_hash : request_hashes) {
|
||||
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 || change.status == "shiprecvd"_n || change.status == "delivered"_n, "Перевозить можно только заявки в статусе supplied2, shiprecvd или delivered");
|
||||
eosio::check(change.type == "orderoffer"_n, "Перевозить можно только заявки типа orderoffer");
|
||||
eosio::check(change.warehouse == source_braname, "Заявка должна находиться на складе КУ отправителя");
|
||||
}
|
||||
|
||||
// Получаем новый ID для перевозки
|
||||
uint64_t next_shipment_id = get_global_id_in_scope(_marketplace, coopname, "shipments"_n);
|
||||
|
||||
// Создаем новую перевозку
|
||||
shipments_index shipments(_marketplace, coopname.value);
|
||||
shipments.emplace(_marketplace, [&](auto &s) {
|
||||
s.id = next_shipment_id;
|
||||
s.hash = hash;
|
||||
s.coopname = coopname;
|
||||
s.driver_username = driver_username;
|
||||
s.source_braname = source_braname;
|
||||
s.destination_braname = destination_braname;
|
||||
s.status = "loading"_n;
|
||||
s.request_hashes = request_hashes;
|
||||
|
||||
// Добавляем первый документ
|
||||
Document::add_document(s.documents, DocumentNames::SHIPMENT_SEND_ACT, transport_act_sender);
|
||||
|
||||
s.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
});
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Приём имущества на склад по накладной.
|
||||
|
||||
@details Представитель КУ получения принимает имущество на склад по накладной.
|
||||
Все заявки из перевозки переходят в статус delivered и ставятся на склад КУ получения.
|
||||
Объект перевозки удаляется.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param hash Идентификатор перевозки
|
||||
@param warehouse_receipt_act Акт приёма на склад подписанный получателем
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::receiveshipm(eosio::name coopname, checksum256 hash, document2 warehouse_receipt_act) {
|
||||
require_auth(coopname);
|
||||
|
||||
// Проверяем что перевозка существует
|
||||
auto shipment = Marketplace::get_shipment_by_hash_or_fail(coopname, hash, "Перевозка не найдена");
|
||||
eosio::check(shipment.status == "arrived"_n, "Принимать имущество на склад можно только для перевозки в статусе arrived");
|
||||
|
||||
// Проверяем подпись документа
|
||||
verify_document_or_fail(warehouse_receipt_act);
|
||||
|
||||
// Валидируем документ по registry_id (пока что ноль)
|
||||
Document::validate_registry_id(warehouse_receipt_act, 0);
|
||||
|
||||
requests_index requests(_marketplace, coopname.value);
|
||||
|
||||
// Обновляем все заявки из перевозки - ставим их на склад получения
|
||||
for (auto& request_hash : shipment.request_hashes) {
|
||||
auto change_opt = Marketplace::get_request_by_hash(coopname, request_hash);
|
||||
eosio::check(change_opt.has_value(), "Заявка не найдена");
|
||||
auto change = change_opt.value();
|
||||
|
||||
// Обновляем заявку
|
||||
auto change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
|
||||
requests.modify(change_itr, _marketplace, [&](auto &o){
|
||||
o.status = "shiprecvd"_n; // Доставка получена
|
||||
o.warehouse = shipment.destination_braname; // Ставим на склад КУ получения
|
||||
o.delivered_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
// Устанавливаем крайний срок получения (3 дня)
|
||||
o.deadline_for_receipt = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch() + 3 * 24 * 60 * 60);
|
||||
// Добавляем акт приёма на складе получателем
|
||||
Document::add_document(o.documents, DocumentNames::SHIPMENT_RECV_ACT, warehouse_receipt_act);
|
||||
});
|
||||
}
|
||||
|
||||
// Удаляем объект перевозки после завершения
|
||||
shipments_index shipments(_marketplace, coopname.value);
|
||||
auto hash_index = shipments.get_index<"byhash"_n>();
|
||||
auto shipment_itr = hash_index.find(hash);
|
||||
hash_index.erase(shipment_itr);
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Промежуточная передача товаров между складами.
|
||||
|
||||
@details Создает новую перевозку для доставленных заявок, позволяя передать все товары
|
||||
из текущего склада в другое место назначения с новым водителем.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param completed_hash Внешний идентификатор новой перевозки
|
||||
@param new_driver_username Имя нового водителя-пайщика
|
||||
@param source_braname КУ с которого забираем товары
|
||||
@param new_destination_braname Новый КУ назначения
|
||||
@param request_hashes Массив хэшей заявок для переотправки
|
||||
@param transport_act_sender Акт передачи от текущего склада
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::retransport(eosio::name coopname, checksum256 completed_hash, eosio::name new_driver_username, eosio::name source_braname, eosio::name new_destination_braname, std::vector<checksum256> request_hashes, document2 transport_act_sender) {
|
||||
require_auth(coopname);
|
||||
|
||||
// Проверяем что есть хотя бы одна заявка
|
||||
eosio::check(!request_hashes.empty(), "Должна быть указана хотя бы одна заявка");
|
||||
|
||||
// Проверяем что перевозка с таким hash не существует
|
||||
auto existing_shipment = Marketplace::get_shipment_by_hash(coopname, completed_hash);
|
||||
eosio::check(!existing_shipment.has_value(), "Перевозка с таким идентификатором уже существует");
|
||||
|
||||
// Проверяем подпись документа
|
||||
verify_document_or_fail(transport_act_sender);
|
||||
|
||||
// Валидируем документ по registry_id (пока что ноль)
|
||||
Document::validate_registry_id(transport_act_sender, 0);
|
||||
|
||||
requests_index requests(_marketplace, coopname.value);
|
||||
|
||||
// Проверяем все заявки но НЕ снимаем их со склада (это будет при signbydriver)
|
||||
for (auto& request_hash : request_hashes) {
|
||||
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.warehouse == source_braname, "Заявка должна находиться на указанном складе");
|
||||
}
|
||||
|
||||
// Получаем новый ID для перевозки
|
||||
uint64_t next_shipment_id = get_global_id_in_scope(_marketplace, coopname, "shipments"_n);
|
||||
|
||||
// Создаем новую перевозку
|
||||
shipments_index shipments(_marketplace, coopname.value);
|
||||
shipments.emplace(_marketplace, [&](auto &s) {
|
||||
s.id = next_shipment_id;
|
||||
s.hash = completed_hash;
|
||||
s.coopname = coopname;
|
||||
s.driver_username = new_driver_username;
|
||||
s.source_braname = source_braname;
|
||||
s.destination_braname = new_destination_braname;
|
||||
s.status = "loading"_n;
|
||||
s.request_hashes = request_hashes;
|
||||
|
||||
// Добавляем первый документ
|
||||
Document::add_document(s.documents, DocumentNames::SHIPMENT_SEND_ACT, transport_act_sender);
|
||||
|
||||
s.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
});
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
\ingroup public_actions
|
||||
\brief Подпись акта приёма-передачи водителем.
|
||||
|
||||
@details Водитель подписывает акт приёма имущества на транспортировку.
|
||||
Все заявки снимаются со склада отправления.
|
||||
Перевозка переходит в статус transit.
|
||||
|
||||
@param coopname Имя кооператива
|
||||
@param hash Идентификатор перевозки
|
||||
@param transport_act_driver Акт приёма-передачи подписанный водителем
|
||||
|
||||
@note Авторизация требуется от аккаунта: @p coopname
|
||||
**/
|
||||
[[eosio::action]] void marketplace::signbydriver(eosio::name coopname, checksum256 hash, document2 transport_act_driver) {
|
||||
require_auth(coopname);
|
||||
|
||||
// Проверяем что перевозка существует
|
||||
auto shipment = Marketplace::get_shipment_by_hash_or_fail(coopname, hash, "Перевозка не найдена");
|
||||
eosio::check(shipment.status == "loading"_n, "Подписывать акт водитель может только для перевозки в статусе loading");
|
||||
|
||||
// Проверяем подпись документа
|
||||
verify_document_or_fail(transport_act_driver);
|
||||
|
||||
// Валидируем документ по registry_id (пока что ноль)
|
||||
Document::validate_registry_id(transport_act_driver, 0);
|
||||
|
||||
requests_index requests(_marketplace, coopname.value);
|
||||
|
||||
// Снимаем все заявки со склада отправления
|
||||
for (auto& request_hash : shipment.request_hashes) {
|
||||
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.warehouse == shipment.source_braname, "Заявка должна находиться на складе КУ отправителя");
|
||||
|
||||
// Снимаем заявку со склада
|
||||
auto change_itr = requests.find(change.id);
|
||||
eosio::check(change_itr != requests.end(), "Заявка не найдена для обновления");
|
||||
requests.modify(change_itr, _marketplace, [&](auto &o){
|
||||
o.warehouse = ""_n; // Снимаем со склада
|
||||
});
|
||||
}
|
||||
|
||||
// Обновляем перевозку
|
||||
shipments_index shipments(_marketplace, coopname.value);
|
||||
auto hash_index = shipments.get_index<"byhash"_n>();
|
||||
auto shipment_itr = hash_index.find(hash);
|
||||
hash_index.modify(shipment_itr, _marketplace, [&](auto &s) {
|
||||
s.status = "transit"_n;
|
||||
Document::add_document(s.documents, DocumentNames::SHIPMENT_LOADING_ACT, transport_act_driver);
|
||||
s.loaded_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
});
|
||||
};
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user