[mp2-11][@ant] feat(standards): двусторонний L3 + игнор Дт=Кт same-account — расширяем схему Ledger2Operation массивом l3[] для случаев перевода между двумя USER_SHARED одного пайщика (классика — w.wal.member ↔ w.mkt.member); FocusBar рендерит каждое движение отдельной строкой; supply ftarg/fefund переведены на массив l3 (-X на источнике, +X на получателе) + L1 проводки Дт 86 / Кт 86 убраны (нетто-нулевое изменение по 86 — смарт-контракт игнорирует, аналитика «по программе» фиксируется на L2/L3)
This commit is contained in:
@@ -36,7 +36,8 @@
|
||||
# Дт 80 / Кт 86) — conditional, только если на
|
||||
# w.wal.member.available заказчика не хватает
|
||||
# • o.mkt.ftarg — целевое назначение членского взноса в программу
|
||||
# (TRANSFER, w.wal.member → w.mkt.member) — conditional,
|
||||
# (TRANSFER, w.wal.member → w.mkt.member, без проводки —
|
||||
# Дт=Кт по 86 смарт-контракт игнорирует) — conditional,
|
||||
# только если на w.mkt.member.available не хватает
|
||||
# • o.mkt.fblock — блокировка средств под Order на программном кошельке
|
||||
# (BLOCK)
|
||||
@@ -44,7 +45,7 @@
|
||||
# (UNBLOCK; средства остаются на w.mkt.member.available
|
||||
# и могут быть потрачены на следующий заказ)
|
||||
# • o.mkt.fefund — вывод из программы в общий членский кошелёк пайщика
|
||||
# (TRANSFER, w.mkt.member → w.wal.member) —
|
||||
# (TRANSFER, w.mkt.member → w.wal.member, без проводки) —
|
||||
# ОТДЕЛЬНОЕ явное действие пайщика, не часть авто-flow
|
||||
# отмены/возврата
|
||||
# • o.mkt.purch — приёмка имущества кооперативом по АПП приёмки
|
||||
@@ -765,18 +766,25 @@ operations:
|
||||
- ledger_code: o.mkt.ftarg
|
||||
human_name: Целевое назначение членского взноса в программу Marketplace
|
||||
wallet_op: TRANSFER
|
||||
# L1 — внутри 86 счёта между аналитиками (TBD-Standardization)
|
||||
debit: 86 # Целевое финансирование (программа Marketplace)
|
||||
credit: 86 # Целевое финансирование (общий членский пайщика)
|
||||
# L2 — кошельки кооператива (агрегаты, по аналитике программ)
|
||||
# L1 — нет проводки. По плану счетов оба кошелька висят на 86, и Дт 86 /
|
||||
# Кт 86 даёт нетто-нулевое изменение по счёту — смарт-контракт такие
|
||||
# «фиктивные» проводки игнорирует. Аналитика «по программе» фиксируется
|
||||
# на L2 (через имена агрегатных кошельков) — этого достаточно.
|
||||
debit: null
|
||||
credit: null
|
||||
# L2 — реальный перевод между двумя именованными агрегатами кооператива
|
||||
wallet_from: w.wal.member # Общий членский — пайщик расходует
|
||||
wallet_to: w.mkt.member # ЦПП «Стол Заказов» — целевое назначение в программу
|
||||
# L3 — у того же пайщика available перетекает с w.wal.member на w.mkt.member;
|
||||
# рендерим сторону получателя; на источнике подразумевается -X
|
||||
user_wallet: w.mkt.member
|
||||
user_ref: order.customer
|
||||
available_delta: +order.total_cost
|
||||
blocked_delta: null
|
||||
# L3 — две стороны движения у одного и того же пайщика
|
||||
l3:
|
||||
- user_wallet: w.wal.member
|
||||
user_ref: order.customer
|
||||
available_delta: -order.total_cost
|
||||
blocked_delta: null
|
||||
- user_wallet: w.mkt.member
|
||||
user_ref: order.customer
|
||||
available_delta: +order.total_cost
|
||||
blocked_delta: null
|
||||
amount_ref: order.total_cost
|
||||
triggered_by: marketplace::createorder
|
||||
description: >
|
||||
@@ -784,12 +792,9 @@ operations:
|
||||
w.mkt.member.available заказчика не хватает суммы заказа). Перенос
|
||||
членского взноса пайщика из универсального кошелька (w.wal.member)
|
||||
в программу Marketplace (w.mkt.member). Сумма берётся ровно как
|
||||
недостача. По бухгалтерии — внутрисчётное движение по 86 (членский
|
||||
фонд кооператива), меняется аналитика «программа: marketplace»; в
|
||||
ledger2 это отражено как L2-перевод между двумя именованными агрегатами.
|
||||
(TBD-Standardization: проводка Дт 86 / Кт 86 фиксируется как baseline;
|
||||
возможна альтернатива через null/null если бухгалтерия посчитает
|
||||
перенос аналитики не требующим явной двойной записи.)
|
||||
недостача. По бухгалтерии — без проводки (Дт=Кт по 86 нетто-нулевое
|
||||
изменение, смарт-контракт такие фиктивные проводки игнорирует);
|
||||
аналитика «по программе» фиксируется на L2/L3.
|
||||
|
||||
- ledger_code: o.mkt.fblock
|
||||
human_name: Блокировка членского взноса под заказ
|
||||
@@ -844,17 +849,22 @@ operations:
|
||||
- ledger_code: o.mkt.fefund
|
||||
human_name: Вывод членского взноса в общий членский кошелёк
|
||||
wallet_op: TRANSFER
|
||||
# L1 — внутри 86 счёта между аналитиками (TBD-Standardization)
|
||||
debit: 86 # Целевое финансирование (общий членский пайщика)
|
||||
credit: 86 # Целевое финансирование (программа Marketplace)
|
||||
# L2 — обратный перенос аналитики
|
||||
# L1 — нет проводки (Дт=Кт по 86 нетто-нулевое; смарт-контракт игнорирует).
|
||||
debit: null
|
||||
credit: null
|
||||
# L2 — реальный перевод между двумя именованными агрегатами кооператива
|
||||
wallet_from: w.mkt.member # ЦПП «Стол Заказов» — программный членский
|
||||
wallet_to: w.wal.member # Универсальный членский — общий членский пайщика
|
||||
# L3 — у того же пайщика available перетекает с w.mkt.member на w.wal.member
|
||||
user_wallet: w.wal.member
|
||||
user_ref: order.customer
|
||||
available_delta: +order.total_cost
|
||||
blocked_delta: null
|
||||
# L3 — две стороны движения у одного и того же пайщика
|
||||
l3:
|
||||
- user_wallet: w.mkt.member
|
||||
user_ref: order.customer
|
||||
available_delta: -order.total_cost
|
||||
blocked_delta: null
|
||||
- user_wallet: w.wal.member
|
||||
user_ref: order.customer
|
||||
available_delta: +order.total_cost
|
||||
blocked_delta: null
|
||||
amount_ref: order.total_cost
|
||||
triggered_by: TBD
|
||||
description: >
|
||||
@@ -864,8 +874,7 @@ operations:
|
||||
Благорост или обратно на цифровой паевой через отдельную операцию
|
||||
контракта wallet). НЕ часть авто-flow отмены/возврата — на cancel/
|
||||
expire/decline средства остаются на w.mkt.member.available, и
|
||||
инициатива вывода — у пайщика. Внутрисчётное движение по 86
|
||||
(TBD-Standardization).
|
||||
инициатива вывода — у пайщика.
|
||||
|
||||
- ledger_code: o.mkt.purch
|
||||
human_name: Приёмка имущества кооперативом
|
||||
|
||||
@@ -234,13 +234,33 @@ function formatDelta(expr: string | null | undefined): string {
|
||||
|
||||
// Показываем L3-карточку только когда операция реально двигает кошелёк
|
||||
// пайщика. Достаточно одного из полей user_wallet / available_delta /
|
||||
// blocked_delta — остальные инферим как «не указано».
|
||||
// blocked_delta (flat) или непустой массив l3 — остальные инферим как
|
||||
// «не указано».
|
||||
function hasL3(op: Ledger2Operation): boolean {
|
||||
if (op.l3 && op.l3.length > 0) return true;
|
||||
return Boolean(
|
||||
op.user_wallet || op.user_ref || op.available_delta || op.blocked_delta,
|
||||
);
|
||||
}
|
||||
|
||||
// Возвращаем все L3-строки операции в едином формате — массив movements.
|
||||
// Если op.l3 задан — используем его; иначе строим единственный movement
|
||||
// из flat-полей (legacy-совместимость с одиночным L3).
|
||||
function getL3Movements(op: Ledger2Operation) {
|
||||
if (op.l3 && op.l3.length > 0) return op.l3;
|
||||
if (
|
||||
op.user_wallet || op.user_ref || op.available_delta || op.blocked_delta
|
||||
) {
|
||||
return [{
|
||||
user_wallet: op.user_wallet ?? null,
|
||||
user_ref: op.user_ref,
|
||||
available_delta: op.available_delta,
|
||||
blocked_delta: op.blocked_delta,
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -397,21 +417,27 @@ function hasL3(op: Ledger2Operation): boolean {
|
||||
>
|
||||
<div class="focus-bar__op-sub-label">Кошелёк пайщика</div>
|
||||
<div class="focus-bar__op-sub-body focus-bar__op-sub-body--stack">
|
||||
<div class="focus-bar__l3-row">
|
||||
<span class="tooltip" :data-tip="walletTitle(op.user_wallet, op.wallet_op)">
|
||||
<code>{{ walletDisplayId(op.user_wallet) }}</code>
|
||||
</span>
|
||||
<span v-if="op.user_ref" class="focus-bar__l3-user">
|
||||
· {{ op.user_ref }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="op.available_delta" class="focus-bar__l3-delta">
|
||||
<span class="focus-bar__l3-delta-label">Доступно</span>
|
||||
<code>{{ formatDelta(op.available_delta) }}</code>
|
||||
</div>
|
||||
<div v-if="op.blocked_delta" class="focus-bar__l3-delta">
|
||||
<span class="focus-bar__l3-delta-label">Заблокировано</span>
|
||||
<code>{{ formatDelta(op.blocked_delta) }}</code>
|
||||
<div
|
||||
v-for="(mov, mi) in getL3Movements(op)"
|
||||
:key="mi"
|
||||
class="focus-bar__l3-movement"
|
||||
>
|
||||
<div class="focus-bar__l3-row">
|
||||
<span class="tooltip" :data-tip="walletTitle(mov.user_wallet, op.wallet_op)">
|
||||
<code>{{ walletDisplayId(mov.user_wallet) }}</code>
|
||||
</span>
|
||||
<span v-if="mov.user_ref" class="focus-bar__l3-user">
|
||||
· {{ mov.user_ref }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="mov.available_delta" class="focus-bar__l3-delta">
|
||||
<span class="focus-bar__l3-delta-label">Доступно</span>
|
||||
<code>{{ formatDelta(mov.available_delta) }}</code>
|
||||
</div>
|
||||
<div v-if="mov.blocked_delta" class="focus-bar__l3-delta">
|
||||
<span class="focus-bar__l3-delta-label">Заблокировано</span>
|
||||
<code>{{ formatDelta(mov.blocked_delta) }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -493,21 +519,27 @@ function hasL3(op: Ledger2Operation): boolean {
|
||||
>
|
||||
<div class="focus-bar__op-sub-label">Кошелёк пайщика</div>
|
||||
<div class="focus-bar__op-sub-body focus-bar__op-sub-body--stack">
|
||||
<div class="focus-bar__l3-row">
|
||||
<span class="tooltip" :data-tip="walletTitle(focusedOperation.user_wallet, focusedOperation.wallet_op)">
|
||||
<code>{{ walletDisplayId(focusedOperation.user_wallet) }}</code>
|
||||
</span>
|
||||
<span v-if="focusedOperation.user_ref" class="focus-bar__l3-user">
|
||||
· {{ focusedOperation.user_ref }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="focusedOperation.available_delta" class="focus-bar__l3-delta">
|
||||
<span class="focus-bar__l3-delta-label">Доступно</span>
|
||||
<code>{{ formatDelta(focusedOperation.available_delta) }}</code>
|
||||
</div>
|
||||
<div v-if="focusedOperation.blocked_delta" class="focus-bar__l3-delta">
|
||||
<span class="focus-bar__l3-delta-label">Заблокировано</span>
|
||||
<code>{{ formatDelta(focusedOperation.blocked_delta) }}</code>
|
||||
<div
|
||||
v-for="(mov, mi) in getL3Movements(focusedOperation)"
|
||||
:key="mi"
|
||||
class="focus-bar__l3-movement"
|
||||
>
|
||||
<div class="focus-bar__l3-row">
|
||||
<span class="tooltip" :data-tip="walletTitle(mov.user_wallet, focusedOperation.wallet_op)">
|
||||
<code>{{ walletDisplayId(mov.user_wallet) }}</code>
|
||||
</span>
|
||||
<span v-if="mov.user_ref" class="focus-bar__l3-user">
|
||||
· {{ mov.user_ref }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="mov.available_delta" class="focus-bar__l3-delta">
|
||||
<span class="focus-bar__l3-delta-label">Доступно</span>
|
||||
<code>{{ formatDelta(mov.available_delta) }}</code>
|
||||
</div>
|
||||
<div v-if="mov.blocked_delta" class="focus-bar__l3-delta">
|
||||
<span class="focus-bar__l3-delta-label">Заблокировано</span>
|
||||
<code>{{ formatDelta(mov.blocked_delta) }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -741,11 +773,21 @@ function hasL3(op: Ledger2Operation): boolean {
|
||||
background: var(--bg); border-color: var(--accent-border); color: var(--accent);
|
||||
}
|
||||
|
||||
/* L3-карточка: горизонтальная (wallet · user · Доступно · Заблокировано). */
|
||||
/* L3-карточка: каждое движение — отдельная строка (wallet · user · Доступно ·
|
||||
Заблокировано). Несколько movement'ов рендерятся вертикально друг под
|
||||
другом — для TRANSFER между двумя USER_SHARED одного пайщика и подобных. */
|
||||
.focus-bar__op-sub-body--stack {
|
||||
display: flex; flex-direction: column; align-items: stretch;
|
||||
gap: 6px;
|
||||
}
|
||||
.focus-bar__l3-movement {
|
||||
display: flex; flex-direction: row; align-items: baseline;
|
||||
gap: 8px 18px; flex-wrap: wrap;
|
||||
}
|
||||
.focus-bar__l3-movement + .focus-bar__l3-movement {
|
||||
border-top: 1px dashed var(--border-subtle, rgba(0,0,0,0.08));
|
||||
padding-top: 6px;
|
||||
}
|
||||
.focus-bar__l3-row {
|
||||
display: inline-flex; align-items: baseline; gap: 4px; flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@@ -169,10 +169,33 @@ export interface Ledger2Operation {
|
||||
// затрагивает per-user split available/blocked. Поля независимы от L2 —
|
||||
// user_wallet может совпадать по имени с одним из L2-кошельков (для
|
||||
// USER_SHARED-кошельков типа w.mkt.member) или быть отдельным.
|
||||
//
|
||||
// Для одиночного движения — flat-поля ниже. Для TRANSFER между двумя
|
||||
// USER_SHARED одного пайщика (списание -X с одного, зачисление +X на
|
||||
// другой) — массив `l3` ниже с двумя элементами; flat-поля при этом
|
||||
// не используются (или дублируют первый элемент массива для legacy-
|
||||
// совместимости).
|
||||
user_wallet?: WalletId | null;
|
||||
user_ref?: string; // ссылка на пайщика — order.customer, debt.holder
|
||||
available_delta?: string | null; // выражение по amount_ref: -order.total_cost
|
||||
blocked_delta?: string | null; // +order.total_cost
|
||||
|
||||
// L3 multi-entry — для случаев, когда одна операция затрагивает
|
||||
// несколько per-user строк (например, TRANSFER между двумя
|
||||
// USER_SHARED-кошельками одного пайщика, или multi-user effects).
|
||||
// Если задан — рендерим каждый элемент отдельной L3-строкой.
|
||||
l3?: L3Movement[];
|
||||
}
|
||||
|
||||
// Один per-user split, одна сторона движения. Массив таких объектов —
|
||||
// в `Ledger2Operation.l3` для случаев, когда операция касается нескольких
|
||||
// USER_SHARED-кошельков (TRANSFER между ними у одного и того же пайщика,
|
||||
// либо multi-user effects).
|
||||
export interface L3Movement {
|
||||
user_wallet: WalletId | null;
|
||||
user_ref?: string;
|
||||
available_delta?: string | null;
|
||||
blocked_delta?: string | null;
|
||||
}
|
||||
|
||||
// §7 Связи
|
||||
|
||||
Reference in New Issue
Block a user