feat(v2)(ui): operator-side LP UI matches the new dca_lp authority split
Operator no longer chooses the LP's wallet / DCA mode / autoforward — those belong to the LP, written via satmachineclient. The Add LP / Edit LP dialogs reduce to (machine, user_id, optional username, status). The clients table loses the wallet / mode / autoforward columns and gains an "Onboarded" column showing whether the LP has a `dca_lp` row yet (server-side LEFT JOIN; `DcaClient.lp_onboarded`). Deposit creation gate (the structural enforcement of "must onboard first"): - Picker annotates each LP option with "— pending onboarding" and disables un-onboarded LP rows. - Selecting an un-onboarded LP shows an inline deep-orange banner explaining the LP needs to open satmachineclient once. - The Record button is `:disable`d in that state. The backend refuses with HTTP 422 anyway (see previous commit) — UI is just the first line of feedback. Backend wiring: - `DcaClient` model gains `lp_onboarded: bool = False`, populated at SELECT time via a shared `_CLIENT_SELECT` / `_CLIENT_FROM` fragment that LEFT JOINs `dca_lp` on `user_id`. All four list/ single-row read paths use it: by-id, by-(machine,user), by-machine, by-operator, by-user. No extra round-trip per row. - CSV export drops the removed columns; adds `lp_onboarded`. All 86 unit tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
80b5a6d785
commit
cfad4e341c
4 changed files with 93 additions and 103 deletions
|
|
@ -141,18 +141,21 @@ window.app = Vue.createApp({
|
|||
},
|
||||
|
||||
clientsTable: {
|
||||
// Wallet / mode / autoforward dropped — those are LP-controlled
|
||||
// via satmachineclient, not the operator's concern. `onboarded`
|
||||
// surfaces the dca_lp existence flag (lp_onboarded) so operators
|
||||
// can see at a glance which LPs still need to register before
|
||||
// deposits can be recorded against them.
|
||||
columns: [
|
||||
{name: 'machine', label: 'Machine', field: 'machine_id', align: 'left'},
|
||||
{name: 'username', label: 'LP', field: 'username', align: 'left'},
|
||||
{name: 'wallet_id', label: 'Wallet', field: 'wallet_id', align: 'left'},
|
||||
{name: 'dca_mode', label: 'Mode', field: 'dca_mode', align: 'left'},
|
||||
{name: 'onboarded', label: 'Onboarded', field: 'lp_onboarded', align: 'center'},
|
||||
{
|
||||
name: 'remaining_balance',
|
||||
label: 'Balance',
|
||||
field: 'remaining_balance',
|
||||
align: 'right'
|
||||
},
|
||||
{name: 'autoforward', label: '→', field: 'autoforward_enabled', align: 'center'},
|
||||
{name: 'status', label: 'Status', field: 'status', align: 'left'},
|
||||
{name: 'actions', label: '', field: 'id', align: 'right'}
|
||||
],
|
||||
|
|
@ -257,11 +260,24 @@ window.app = Vue.createApp({
|
|||
}))
|
||||
},
|
||||
depositClientOptions() {
|
||||
// Annotate each LP option with onboarding state so the operator
|
||||
// sees at-pick time which LPs can accept deposits. We don't hide
|
||||
// un-onboarded LPs — the operator might want to know they exist
|
||||
// and chase them — but submission is gated below by
|
||||
// `selectedDepositClient.lp_onboarded`.
|
||||
return this.clients.map(c => ({
|
||||
label: `${c.username || this.shortId(c.user_id)} @ ${this.machineNameById(c.machine_id)}`,
|
||||
value: c.id
|
||||
label:
|
||||
`${c.username || this.shortId(c.user_id)} @ ` +
|
||||
`${this.machineNameById(c.machine_id)}` +
|
||||
(c.lp_onboarded ? '' : ' — pending onboarding'),
|
||||
value: c.id,
|
||||
disable: !c.lp_onboarded
|
||||
}))
|
||||
},
|
||||
selectedDepositClient() {
|
||||
const id = this.depositDialog.data.client_id
|
||||
return id ? this.clients.find(c => c.id === id) : null
|
||||
},
|
||||
worklistBuckets() {
|
||||
return [
|
||||
{
|
||||
|
|
@ -559,9 +575,9 @@ window.app = Vue.createApp({
|
|||
})
|
||||
this._downloadCsv(
|
||||
'clients.csv',
|
||||
['id', 'machine_id', 'machine_name', 'user_id', 'wallet_id',
|
||||
'username', 'dca_mode', 'status', 'autoforward_enabled',
|
||||
'autoforward_ln_address', 'total_deposits', 'total_payments',
|
||||
['id', 'machine_id', 'machine_name', 'user_id',
|
||||
'username', 'lp_onboarded', 'status',
|
||||
'total_deposits', 'total_payments',
|
||||
'remaining_balance', 'balance_currency', 'created_at'],
|
||||
rows
|
||||
)
|
||||
|
|
@ -881,12 +897,7 @@ window.app = Vue.createApp({
|
|||
id: client.id,
|
||||
machine_id: client.machine_id,
|
||||
user_id: client.user_id,
|
||||
wallet_id: client.wallet_id,
|
||||
username: client.username || '',
|
||||
dca_mode: client.dca_mode,
|
||||
fixed_mode_daily_limit: client.fixed_mode_daily_limit,
|
||||
autoforward_enabled: !!client.autoforward_enabled,
|
||||
autoforward_ln_address: client.autoforward_ln_address || '',
|
||||
status: client.status
|
||||
}
|
||||
this.clientDialog.show = true
|
||||
|
|
@ -1277,15 +1288,13 @@ window.app = Vue.createApp({
|
|||
},
|
||||
|
||||
_emptyClientForm() {
|
||||
// Operator-side LP enrolment is just (machine, user, optional
|
||||
// display name). Wallet / mode / autoforward are LP-controlled
|
||||
// via satmachineclient — operator can't pick or change them.
|
||||
return {
|
||||
machine_id: null,
|
||||
user_id: '',
|
||||
wallet_id: '',
|
||||
username: '',
|
||||
dca_mode: 'flow',
|
||||
fixed_mode_daily_limit: null,
|
||||
autoforward_enabled: false,
|
||||
autoforward_ln_address: '',
|
||||
status: 'active'
|
||||
}
|
||||
},
|
||||
|
|
@ -1294,30 +1303,13 @@ window.app = Vue.createApp({
|
|||
return {
|
||||
machine_id: d.machine_id,
|
||||
user_id: (d.user_id || '').trim(),
|
||||
wallet_id: (d.wallet_id || '').trim(),
|
||||
username: (d.username || '').trim() || null,
|
||||
dca_mode: d.dca_mode || 'flow',
|
||||
fixed_mode_daily_limit:
|
||||
d.dca_mode === 'fixed' && d.fixed_mode_daily_limit
|
||||
? Number(d.fixed_mode_daily_limit) : null,
|
||||
autoforward_enabled: !!d.autoforward_enabled,
|
||||
autoforward_ln_address:
|
||||
d.autoforward_enabled && d.autoforward_ln_address
|
||||
? d.autoforward_ln_address.trim() : null
|
||||
username: (d.username || '').trim() || null
|
||||
}
|
||||
},
|
||||
|
||||
_cleanClientUpdate(d) {
|
||||
return {
|
||||
username: (d.username || '').trim() || null,
|
||||
dca_mode: d.dca_mode,
|
||||
fixed_mode_daily_limit:
|
||||
d.dca_mode === 'fixed' && d.fixed_mode_daily_limit
|
||||
? Number(d.fixed_mode_daily_limit) : null,
|
||||
autoforward_enabled: !!d.autoforward_enabled,
|
||||
autoforward_ln_address:
|
||||
d.autoforward_enabled && d.autoforward_ln_address
|
||||
? d.autoforward_ln_address.trim() : null,
|
||||
status: d.status
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue