Изменены компоненты, связанные с выходом из кооператива. В меню заменены иконки и пути для поддержки и выхода. Обновлены метаданные и названия для кнопок и страниц, чтобы улучшить пользовательский интерфейс. В ExitButton добавлены параметры для настройки иконки и метки. Упрощена структура MembershipExitPage с акцентом на ключевые шаги выхода. Исправлены комментарии и типы для лучшего понимания кода.
confirmexit теперь обходит сет LEDGER2_EXIT_REFUND_WALLETS (w.reg.minshr +
w.wal.share + w.cap.blago): собирает доступные L3-балансы каждого (>0),
консолидирует на главный паевой (w.reg.minshr→o.reg.mvmin, w.cap.blago→
o.cap.wthcap) и ставит полную сумму на возврат единым платежом. Раньше
хардкодил только minshr+share — паевой в Благоросте оставался висеть.
Сет задан один раз в wallets.hpp (источник истины), генерируется gen:from-cpp
в cooptypes (LEDGER2_EXIT_REFUND_WALLETS / EXIT_REFUND_WALLET_NAMES) и обходится
backend-preview (getReturnPreview) — расчёт на фронте всегда совпадает с тем,
что реально вернёт контракт. Подпись «планируемая сумма / итог фиксирует Совет»
убрана из ExitButton и ExitOverlay (сумма теперь авторитетна).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- статус: вышедший пайщик (participant_account стёрт delpartcpnt + user_account
blocked) больше не показывается «Активный пайщик» — добавлен терминал
«Вышел из кооператива» по user_account.status
- дата вступления: фолбэк participant_account.created_at → user_account.registered_at
(у вышедших пайщик-запись стёрта, дата больше не «отсутствует»)
- убрана кнопка удаления пайщика (таблица + карточка + диалог + машинерия):
пайщика из реестра не удаляют
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Культура денег: входящие — подтверждением, исходящие — чеком. Единый механизм
на уровне ядра (gateway), привязка по payment_hash — один на все исходящие
(возврат паевого/withdrawal/registration-refund/аванс расхода), переиспользуется
расширениями. Контроль мягкий: статус не трогаем, но зеркалим proof_count →
реестр рисует «чек приложен / не приложен».
Backend (core gateway):
- таблица payment_files + бакет gateway:files (@UseBucket), PaymentFilesService
(upload/read-url/list/delete + зеркалирование proof_count в платёж)
- enum PaymentFileKind (PAYMENT_PROOF), DTO, резолвер uploadPaymentProof /
paymentProofs / paymentFile; провайдеры в typeorm.module + gateway.module
SDK: selectors/mutations/queries gateway + zeus regen
Desktop:
- features/Payment/AttachPaymentProof — панель чека по payment-hash
- реестр: панель + индикатор «чек приложен» для ЛЮБОГО исходящего (PAID/COMPLETED)
- конвергенция expense: чек ушёл в ядро, AttachExpenseProof оставляет только
закрывающие документы (DIRECT)
Терминология: «чек об оплате» (не «платёжка»/«первичка»).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
После completexit строка registrator::exits стирается (терминал=erase) →
getMembershipExit возвращал null → оверлей пропадал, кабинет разблокировался,
пайщик видел стол и мог подать выход повторно (гарда не было).
- enum MembershipExitStatus += COMPLETED
- getMembershipExit: терминальная фаза по blocked-аккаунту + персистентному
MEMBERSHIP_EXIT-платежу (сумма возврата + статус 'Оплачено')
- createMembershipExit: гард — заблокированный аккаунт не может выйти повторно
(контракт уже блокирует через get_participant_or_fail; отсекаем раньше)
- ExitOverlay: терминальная фаза 'Вы вышли из кооператива' (сумма + 'Оплачено'
+ ожидайте поступления), она же UX-гард — перекрывает кабинет
- SDK Zeus regen под новый enum
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Инцидент 2026-06-16: коммит d02c0ce (18 мая) добавил program_expense_pool и
program_expense_reserved в global_state ПЕРЕД полем config напрямую, без
binary_extension. После деплоя на прод таблица state перестала читаться:
запись сериализована старым layout (config сразу после
program_membership_cumulative_reward_per_share), а новый ABI ждёт два asset
перед config → unpack натыкается на байты config (get table → "Invalid
symbol ...Y@" = double 100.0 = config.expense_pool_percent). Любой action,
читающий global_state, падал.
Фикс: оба поля перенесены в ХВОСТ struct (после config) и обёрнуты в
eosio::binary_extension<asset>. Старая запись прода читается без изменений
(хвостовые extension опциональны → пусто = 0), новая логика программных
расходов сохранена. Поля в таблицу EOSIO дописываются только в конец и только
binary_extension'ом.
utility-функции State:: (topup/reserve/release/consume/spend) переведены на
optional-семантику через ext_or_zero(); инвариант — оба поля материализуются
вместе, чтобы порядок хвостовых extension был консистентным. Прямого доступа к
полям вне State:: нет. Собрано в CDT-докере: capital.wasm слинкован, в ABI
program_expense_* → asset$ после config.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
По решению пользователя заход долей Благороста — только в активные проекты.
В pending больше не заводим: при переходе pending→active придёт новая дельта
capital::projects со status=active, на ней листенер и зарегистрирует.
- Листенер ProgramShareRegistrationOnProjectDeltaListener: гейт pending|active → только active.
- ProgramShareRegistrationService.findActiveProjects (путь крона и wallet-листенера): фильтр pending|active → только active.
- Тест: pending теперь НЕ реагирует; active — позитивный кейс.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Дефолт promote.sh — текущий HEAD чекаута; второй аргумент по-прежнему задаёт явный
ref (ветка/тег/sha). Типичный путь: cut на dev → promote testnet → promote main,
все три от того же HEAD. testnet/main остаются независимыми FF-указателями.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ссылка из письма открывается часто без сессии кабинета (другой браузер/инкогнито/
после logout). Подтверждение работает по токену без входа, но wallet защищён —
гард молча редиректил на login-redirect, и пайщик не понимал, что произошло.
- есть сессия → 'Перейти в кабинет' → wallet (глобальный ExitOverlay сам покажет
'на рассмотрении Совета')
- нет сессии → 'Войти в кабинет' → signin + пояснение, что войти нужно для слежения
за статусом и суммой возврата
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Доли пайщиков Благороста регистрировались в проект только периодическим
scheduler'ом (regshare) и точечным листенером на дельту кошелька. Новый
проект баланс не меняет → wallet-листенер молчит, и наполнение нового
проекта зависело ТОЛЬКО от крона. Если компонент быстро прогнали
pending→…→result до тика крона, пайщики в него не попадали, а откат
result→active контракт не допускает — доли в компоненте терялись
безвозвратно (voskhod, компонент 011bcd92…, 2026-06-16).
- Новый ProgramShareRegistrationOnProjectDeltaListener на
delta::capital::projects: при появлении проекта в статусе pending|active
сразу регистрирует доли всех активных пайщиков (неблокирующе, fire-and-forget,
переиспользуя ядро syncContributor — тот же путь, что и крон).
- ProgramShareRegistrationService.syncProgramSharesForProject — обход пайщиков
по одному проекту; syncContributor переведён с ProjectDomainEntity[] на
projectHashes: string[].
- Крон ОСТАВЛЕН как reconciliation-бэкстоп: он ещё и ДОобновляет уже
зарегистрированные доли при дрейфе баланса (контракт upsert_contributor_segment
это допускает) и подбирает события, потерянные при downtime контроллера.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Паровоз dev→testnet→main заменён независимыми указателями: каждое окружение —
самостоятельный fast-forward-указатель на выбранный релизный тег dev. Прод и тест
катятся разным темпом и на разные версии одновременно, без диверджа и конфликтов
(оба — FF-указатели на линейную историю dev).
- promote.sh <testnet|main> [ref]: FF выбранного тега (по умолчанию — последний на
origin/dev) на ветку окружения; main больше не зависит от testnet; только вперёд.
- release.yaml: workflow_dispatch получил inputs environment+ref — откат на старую
версию / редеплой без бампа / hotfix в один контур (то, что FF не умеет). Сборка
идёт из вычекнутого ref, окружение — из inputs.environment. Авто-триггер по
изменению lerna.json сохранён.
- RELEASE.md: модель и инварианты переписаны под независимые указатели.
Принципы деплоя сохранены: бамп версии один раз на dev, образы по версии,
BUILD_MODE по окружению, webhooks per env, npm publish/доки только на main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- заголовок документа из DocumentHtmlReader (3rem) прижат к h2 по канону,
:deep + !important как в ReadStatement.vue
- 'Сумма к возврату' оформлена soft-панелью (label+hint слева, mono-значение
справа) вместо висящего снизу текста
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Суммы к возврату форматируются канонной formatAsset2Digits ("300.0000 RUB"
→ "300,00 RUB") в диалоге заявления и в оверлее — вместо сырого asset.
- Убрана разбивка "целевой + минимальный" (оба паевые, суммируются): показываем
одну строку "Сумма к возврату".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Было две крутилки рядом («Формирование заявления…» + «Расчёт суммы…»).
Теперь заявление и сумма грузятся параллельно (Promise.allSettled) под одним
центрированным лоадером и показываются вместе. Тот же стиль лоадера — на фазе
проверки реквизитов. Сумма некритична: документ подписывается и без предрасчёта.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Пункт меню «Выход из кооператива» перенесён в самый низ (после «Поддержки»),
иконка group_remove вместо logout — чтобы не сливаться с кнопкой «Выйти»
(выход из кабинета, дверь-logout) внизу панели.
- Гейт реквизитов сделан fail-open: блокируем выход ТОЛЬКО при достоверно
пустом списке методов; null/непонятный ответ не блокирует. + реактивный
фолбэк: если бэкенд отклонил подачу из-за отсутствия реквизитов — диалог
переключается на баннер. Авторитетный источник — бэкенд (createMembershipExit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Счёт регистрационного взноса создаётся со статусом pending сразу при заходе на
шаг оплаты («счёт выставлен», деньги ещё не получены). При возврате на страницу
(перезагрузка/повторный вход) роутинг считал сам факт наличия платежа за «этап
оплаты пройден» и вёл на экран ожидания, где дефолтная ветка показывала «Ваш
платеж принят» — хотя оплаты не было и статус не менялся.
- SignUp: на экран ожидания/отказа ведём только при PAID/COMPLETED либо
терминальном статусе (отказ/отмена/возврат); pending → шаг оплаты (QR + поллинг).
- WaitingRegistration: для pending — отдельная ветка «Ожидаем поступление оплаты»
+ кнопка «Перейти к оплате»; «платёж принят» остаётся только для PAID/COMPLETED.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ExitOverlay: чип статуса исходящего платежа у суммы (ожидает оплаты/оплачивается/
оплачено/ошибка) — берётся из payment_status, меняется по мере обработки кассиром.
- MembershipExitPage переверстана на AuthCard (иконка-предупреждение + заголовок +
ключевые шаги процедуры + запуск подачи) — единый канон с оверлеем.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getMembershipExit подтягивает платёж возврата по hash=exit_hash и отдаёт его
payment_status (PaymentStatus enum, nullable). Фронт показывает реальный статус
платежа кассира (ожидает оплаты → оплачено), а не только статус процесса выхода.
Сумма к возврату — зафиксированная советом on-chain (= сумма платежа), не preview.
+ codegen (controller/sdk zeus, селектор MembershipExit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Контент больше не плавает по пустому экрану: статус выхода — в центрированной
карточке AuthCard (accent-стрип + мягкая тень, канонный hero-контейнер).
Иконка статуса в soft-плитке (primary/pos), заголовок, пояснение, сумма к
возврату на surface-2, действия в футере с разделителем. Всё на токенах --p-*.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ExitOverlay переверстан на канонный EmptyState (иконка-плитка + заголовок +
приглушённый текст + слот действий) вместо россыпи text-h5/64px-иконки.
- Добавлена кнопка «Выйти из личного кабинета» (logout) на оба экрана выхода:
при активном выходе аккаунт заблокирован, и выйти из сессии было нечем.
После logout gate обнуляется, редирект на signin; при повторном входе экран
выхода снова показывается (статус живёт on-chain).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Перед подписанием заявления о выходе проверяем наличие реквизитов пайщика
(getPaymentMethods). Если их нет — вместо формы показываем баннер «установите
реквизиты для получения возврата паевого взноса» + кнопку перехода на страницу
реквизитов (payment-methods). Бэкенд проверяет то же при подаче (страховка).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Закрываем пробел: при одобрении советом выхода (on-chain confirmexit) кассир
не видел платёж возврата — его никто не создавал в реестре gateway.
- Гейт реквизитов: createMembershipExit отклоняет подачу, если у пайщика нет
ни одного платёжного метода («установите реквизиты для возврата паевого»).
- MembershipExitAuthorizationListener (@OnEvent action::registrator::confirmexit):
по exit_hash берёт из таблицы exits username + сумму возврата, заводит
исходящий платёж MEMBERSHIP_EXIT (PENDING — совет одобрил, сразу кассиру) по
реквизитам пайщика (метод по умолчанию). hash платежа = exit_hash, поэтому
подтверждение кассой через default-ветку processOutgoingPayment вызовет
completeOutcome → registrator::completexit (списание + блокировка).
- getExitByHash в account-порт/адаптер: confirmexit отдаёт только coopname+exit_hash.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
После merge dev zeus-клиенты были взяты из dev (без операций выхода).
Интроспекция живого coopback → schema.gql → generate-client вернула обе
группы операций (createMembershipExit/confirm/cancel + expense-шасси).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Регенерация Zeus-клиента контроллера под GraphQL-операции выхода
(CreateMembershipExitInput, MembershipExitStatus, signed-document inputs).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Маршрут membership-exit/confirm авторизуется токеном из ссылки (мутация
confirmMembershipExit публичная), поэтому навигационный гард не должен
редиректить его на login-redirect. requiresAuth:false исключает страницу из
auth-гейта — ссылку из письма можно открыть без активной сессии.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Черновик заявления сохраняется до отправки письма; если провайдер писем
недоступен, приём заявления не падает (try/catch + error-лог), пайщик может
отменить. Ссылка подтверждения пишется в debug-лог для диагностики.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ExitButton: диалог теперь показывает сгенерированное заявление (DocumentHtmlReader)
с предупреждением «читайте внимательно, необратимо» + сумма к возврату, затем
«Подписать и подать» (generateApplication → показ → submitSignedApplication).
- ExitOverlay: новое состояние AWAITING_CONFIRMATION — «пройдите по ссылке из письма»
+ кнопка «Отменить выход»; после перехода/отмены overlay переключается на
ончейн-статус (рассмотрение Совета → одобрено → выплаты).
- useExitGate: isAwaitingConfirmation + cancelExit (Mutations.MembershipExit.CancelMembershipExit).
- model: generateApplication / submitSignedApplication / confirmExit вместо единого processMembershipExit.
- MembershipExitConfirmPage + маршрут membership-exit/confirm (цель ссылки из письма):
onMounted confirmExit(token) → loadExitStatus.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Двойное подтверждение необратимого выхода (закрытия аккаунта): заявление
подписывается и принимается сразу, но в блокчейн уходит только после перехода
по ссылке из письма (по аналогии с verify-email/reset-key токенами).
- notifications: воркфлоу MembershipExitConfirmation (письмо со ссылкой confirmationUrl).
- token: тип CONFIRM_EXIT + generateConfirmExitToken (домен + application).
- entity membership_exit_requests (off-chain черновик подписанного заявления; uniq coopname+username; удаляется при confirm/cancel).
- MembershipExitStatus += AWAITING_CONFIRMATION; MembershipExitResult += status.
- service: createMembershipExit теперь сохраняет черновик + шлёт письмо (НЕ цепь);
confirmMembershipExit(token) проверяет токен и шлёт exitcoop в цепь;
cancelMembershipExit удаляет черновик+токен; getMembershipExit отдаёт off-chain фазу.
- resolver: + confirmMembershipExit (публичная, по токену) + cancelMembershipExit (auth+владелец).
Проверено вживую: мутации в схеме, enum/Result обновлены, таблица создана (synchronize).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Заявление о выходе из кооператива подписывается цифровой подписью (signatures[]),
поэтому из документа убрано поле собственноручной подписи (рукописная картинка):
- cooptypes 200: убраны Action.signature/Model.signature, <img src="{{ signature }}">,
signature из exampleData;
- factory Actions/200: убрана логика поиска/сохранения подписи в mongo;
Templates/200: убрано свойство signature из AJV-схемы;
- controller DTO: убрано поле signature (@IsString) из меты заявления.
Это и вызывало "signature must be a string" — ValidationPipe (422, не логируется)
рубил генерацию, т.к. фронт картинку не передаёт.
Также откат самодуманной шапки "ФОРМА УТВЕРЖДЕНА решением Собрания Совета (Протокол №)":
исходная форма заявления её не содержит. Убраны vars.participant_exit_application
(cooptypes IVars + factory VarsSchema + фикстура теста), проверка протокола в фабрике
и переводы APPROVED/approved_by_council/protocol.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Контроллерный tsc --noEmit (CI) падал: spec тестировал adapter.authExp /
adapter.declineExp, которых нет в ExpensesBlockchainAdapter по дизайну.
authexp/declexp исполняет контракт soviet как callbacks решения совета —
backend-адаптер несёт только 6 прямых actions (createexp/payexp/reportexp/
returnexp/overspendexp/closeexp). Зафиксировано в port, mutations-service и
комментарии адаптера. Прав код — приводим тест к дизайну, удаляя два кейса.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI впервые дошёл до vue-tsc по ветке и вскрыл накопленный долг типов
(локально vue-tsc не гоняем). Чиним точечно, без смены логики:
- uploaded_at — Zeus-скаляр даты типизирован как {}: new Date(String(...))
и date:String(...) в ProgramExpensePage, ExpenseDetailPage и трёх
Payment-panel (Attach/Settlement/Report).
- ExpenseDetailPage: loadPayments options требует sortOrder → 'DESC';
:key платежа допускает null → ключ pay.hash ?? idx.
- PaymentsPage: routeUsername null → undefined под prop :username.
- expenses/model: снят реэкспорт несуществующего IAuthorizeProposalDraft.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
buildZipArchive писал имена в UTF-8, но general-purpose bit flag = 0 → читатели
(включая верификатор на fflate) декодировали кириллицу как latin1 (мохибейк), и
manifest.json (корректный UTF-8) не находил записи → верификатор «в пакете нет
файлов». Ставим bit-11 в local и central заголовках. Архив теперь корректно
открывается любым ZIP-ридером (Explorer/macOS/fflate).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ExpenseDetailPage переведён со сбора платежей по реконструированным хэшам на
один запрос getPayments({ coopname, proposal_hash }) — фильтр из C28-64. Убрана
скопированная серверная формула settlementPaymentHash/sha256Hex из
shared/lib/expenses: фронт больше не дублирует деривацию хэшей расчётных
платёжек. Запрос type-agnostic — ловит все связанные платежи любого типа,
устойчив к добавлению новых видов. Сортировка: выдача/оплата раньше расчётных.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Добавлен фильтр PaymentFiltersInput.proposal_hash — возвращает все платежи,
связанные с расходом (служебной запиской). Связь платёж→расход уже зашита
бэкендом в json-поле blockchain_data.proposal_hash (и у платежа выдачи аванса/
оплаты организации, и у расчётных платёжек возврата/доплаты); фильтр извлекает
его через json-оператор ->> в typeorm-репозитории. Так связанные платежи
достаются одним запросом по расходу, без реконструкции хэшей на фронте.
regen: schema → generate-client → sdk build (zeus-клиент controller+sdk).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
В историю состояний расхода добавлены события платежей: выдача аванса/оплата
организации, возврат недорасхода, доплата перерасхода, отклонения. Берутся из тех
же linkedPayments (C28-61), без отдельного запроса. Тип/иконка/текст по статусу
(исполнен / оплачен кассой / создан / отклонён), сумма и причина отказа — в
описании; сортируются в общую ленту по дате updated_at.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Из детали расхода нельзя было вернуться в реестр расходов. Добавлен canon
back-link под шапкой (как в MeetDetails/DocumentDetails): router.push на
expenses-registry. Виден всегда, даже пока расход грузится.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
В детали расхода (стол совета → реестр расходов → расход) добавлена секция
«Платежи по расходу»: выдача аванса/оплата организации и, если был перерасчёт по
чекам, расчётная платёжка (возврат недорасхода / доплата перерасхода). Реквизиты
«куда уходил платёж», назначение и причина отклонения — внутри PaymentDetails.
Платежи собираются по ДЕТЕРМИНИРОВАННЫМ хэшам, а не по username: у позиции-
организации платёж принадлежит кооперативу, у аванса — пайщику-получателю, и у
каждой позиции свой получатель — единого владельца нет. Хэш выдачи = item_hash,
расчётные = sha256('expense-settlement:coop:item:kind') (новый settlementPaymentHash
в shared/lib/expenses, точная копия серверного generateHashFromString). Точечный
hash-фильтр gateway не требует листать общий реестр и нового бэкенд-поля.
Клик «Открыть в реестре платежей» → реестр кассира, отфильтрованный по владельцу
(:username? в PaymentsPage), с фокусом на платёж (виджет раскрывает его по ?focus=hash).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>