feat: add deposit edit and delete for pending deposits
Add PUT /api/v1/dca/deposits/{id} endpoint to update amount, currency,
and notes on pending deposits. Add DELETE endpoint to remove deposits
not yet inserted into the machine. Both endpoints reject confirmed
deposits. Frontend now shows edit/delete buttons only for pending rows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6eb076d5f6
commit
ac85feb959
5 changed files with 121 additions and 3 deletions
24
crud.py
24
crud.py
|
|
@ -8,7 +8,7 @@ from lnbits.helpers import urlsafe_short_hash
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
CreateDcaClientData, DcaClient, UpdateDcaClientData,
|
CreateDcaClientData, DcaClient, UpdateDcaClientData,
|
||||||
CreateDepositData, DcaDeposit, UpdateDepositStatusData,
|
CreateDepositData, DcaDeposit, UpdateDepositData, UpdateDepositStatusData,
|
||||||
CreateDcaPaymentData, DcaPayment,
|
CreateDcaPaymentData, DcaPayment,
|
||||||
ClientBalanceSummary,
|
ClientBalanceSummary,
|
||||||
CreateLamassuConfigData, LamassuConfig, UpdateLamassuConfigData,
|
CreateLamassuConfigData, LamassuConfig, UpdateLamassuConfigData,
|
||||||
|
|
@ -153,6 +153,28 @@ async def update_deposit_status(deposit_id: str, data: UpdateDepositStatusData)
|
||||||
return await get_deposit(deposit_id)
|
return await get_deposit(deposit_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def update_deposit(deposit_id: str, data: UpdateDepositData) -> Optional[DcaDeposit]:
|
||||||
|
update_data = {k: v for k, v in data.dict().items() if v is not None}
|
||||||
|
if not update_data:
|
||||||
|
return await get_deposit(deposit_id)
|
||||||
|
|
||||||
|
set_clause = ", ".join([f"{k} = :{k}" for k in update_data.keys()])
|
||||||
|
update_data["id"] = deposit_id
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
f"UPDATE satoshimachine.dca_deposits SET {set_clause} WHERE id = :id",
|
||||||
|
update_data
|
||||||
|
)
|
||||||
|
return await get_deposit(deposit_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_deposit(deposit_id: str) -> None:
|
||||||
|
await db.execute(
|
||||||
|
"DELETE FROM satoshimachine.dca_deposits WHERE id = :id",
|
||||||
|
{"id": deposit_id}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# DCA Payment CRUD Operations
|
# DCA Payment CRUD Operations
|
||||||
async def create_dca_payment(data: CreateDcaPaymentData) -> DcaPayment:
|
async def create_dca_payment(data: CreateDcaPaymentData) -> DcaPayment:
|
||||||
payment_id = urlsafe_short_hash()
|
payment_id = urlsafe_short_hash()
|
||||||
|
|
|
||||||
12
models.py
12
models.py
|
|
@ -60,6 +60,18 @@ class DcaDeposit(BaseModel):
|
||||||
confirmed_at: Optional[datetime]
|
confirmed_at: Optional[datetime]
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateDepositData(BaseModel):
|
||||||
|
amount: Optional[float] = None
|
||||||
|
currency: Optional[str] = None
|
||||||
|
notes: Optional[str] = None
|
||||||
|
|
||||||
|
@validator('amount')
|
||||||
|
def round_amount_to_cents(cls, v):
|
||||||
|
if v is not None:
|
||||||
|
return round(float(v), 2)
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
class UpdateDepositStatusData(BaseModel):
|
class UpdateDepositStatusData(BaseModel):
|
||||||
status: str
|
status: str
|
||||||
notes: Optional[str] = None
|
notes: Optional[str] = None
|
||||||
|
|
|
||||||
|
|
@ -389,12 +389,12 @@ window.app = Vue.createApp({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.depositFormDialog.data.id) {
|
if (this.depositFormDialog.data.id) {
|
||||||
// Update existing deposit (mainly for notes/status)
|
// Update existing pending deposit
|
||||||
const { data: updatedDeposit } = await LNbits.api.request(
|
const { data: updatedDeposit } = await LNbits.api.request(
|
||||||
'PUT',
|
'PUT',
|
||||||
`/satmachineadmin/api/v1/dca/deposits/${this.depositFormDialog.data.id}`,
|
`/satmachineadmin/api/v1/dca/deposits/${this.depositFormDialog.data.id}`,
|
||||||
null,
|
null,
|
||||||
{ status: this.depositFormDialog.data.status, notes: data.notes }
|
{ amount: data.amount, currency: data.currency, notes: data.notes }
|
||||||
)
|
)
|
||||||
const index = this.deposits.findIndex(d => d.id === updatedDeposit.id)
|
const index = this.deposits.findIndex(d => d.id === updatedDeposit.id)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
|
|
@ -460,6 +460,28 @@ window.app = Vue.createApp({
|
||||||
this.depositFormDialog.show = true
|
this.depositFormDialog.show = true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async deleteDeposit(deposit) {
|
||||||
|
try {
|
||||||
|
await LNbits.utils
|
||||||
|
.confirmDialog('Are you sure you want to delete this pending deposit?')
|
||||||
|
.onOk(async () => {
|
||||||
|
await LNbits.api.request(
|
||||||
|
'DELETE',
|
||||||
|
`/satmachineadmin/api/v1/dca/deposits/${deposit.id}`,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
this.deposits = this.deposits.filter(d => d.id !== deposit.id)
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Deposit deleted successfully',
|
||||||
|
timeout: 5000
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Export Methods
|
// Export Methods
|
||||||
async exportClientsCSV() {
|
async exportClientsCSV() {
|
||||||
await LNbits.utils.exportCSV(this.clientsTable.columns, this.dcaClients)
|
await LNbits.utils.exportCSV(this.clientsTable.columns, this.dcaClients)
|
||||||
|
|
|
||||||
|
|
@ -192,12 +192,21 @@
|
||||||
<q-tooltip>Confirm Deposit</q-tooltip>
|
<q-tooltip>Confirm Deposit</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
v-if="props.row.status === 'pending'"
|
||||||
flat dense size="sm" icon="edit"
|
flat dense size="sm" icon="edit"
|
||||||
color="orange"
|
color="orange"
|
||||||
@click="editDeposit(props.row)"
|
@click="editDeposit(props.row)"
|
||||||
>
|
>
|
||||||
<q-tooltip>Edit Deposit</q-tooltip>
|
<q-tooltip>Edit Deposit</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
v-if="props.row.status === 'pending'"
|
||||||
|
flat dense size="sm" icon="delete"
|
||||||
|
color="red"
|
||||||
|
@click="deleteDeposit(props.row)"
|
||||||
|
>
|
||||||
|
<q-tooltip>Delete Deposit</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
53
views_api.py
53
views_api.py
|
|
@ -19,7 +19,9 @@ from .crud import (
|
||||||
create_deposit,
|
create_deposit,
|
||||||
get_all_deposits,
|
get_all_deposits,
|
||||||
get_deposit,
|
get_deposit,
|
||||||
|
update_deposit,
|
||||||
update_deposit_status,
|
update_deposit_status,
|
||||||
|
delete_deposit,
|
||||||
get_client_balance_summary,
|
get_client_balance_summary,
|
||||||
# Lamassu config CRUD operations
|
# Lamassu config CRUD operations
|
||||||
create_lamassu_config,
|
create_lamassu_config,
|
||||||
|
|
@ -39,6 +41,7 @@ from .models import (
|
||||||
UpdateDcaClientData,
|
UpdateDcaClientData,
|
||||||
CreateDepositData,
|
CreateDepositData,
|
||||||
DcaDeposit,
|
DcaDeposit,
|
||||||
|
UpdateDepositData,
|
||||||
UpdateDepositStatusData,
|
UpdateDepositStatusData,
|
||||||
ClientBalanceSummary,
|
ClientBalanceSummary,
|
||||||
CreateLamassuConfigData,
|
CreateLamassuConfigData,
|
||||||
|
|
@ -161,6 +164,56 @@ async def api_update_deposit_status(
|
||||||
return updated_deposit
|
return updated_deposit
|
||||||
|
|
||||||
|
|
||||||
|
@satmachineadmin_api_router.put("/api/v1/dca/deposits/{deposit_id}")
|
||||||
|
async def api_update_deposit(
|
||||||
|
deposit_id: str,
|
||||||
|
data: UpdateDepositData,
|
||||||
|
user: User = Depends(check_super_user),
|
||||||
|
) -> DcaDeposit:
|
||||||
|
"""Update deposit fields (amount, currency, notes). Only pending deposits can be edited."""
|
||||||
|
deposit = await get_deposit(deposit_id)
|
||||||
|
if not deposit:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND, detail="Deposit not found."
|
||||||
|
)
|
||||||
|
|
||||||
|
if deposit.status != "pending":
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail="Only pending deposits can be edited.",
|
||||||
|
)
|
||||||
|
|
||||||
|
updated_deposit = await update_deposit(deposit_id, data)
|
||||||
|
if not updated_deposit:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
detail="Failed to update deposit.",
|
||||||
|
)
|
||||||
|
return updated_deposit
|
||||||
|
|
||||||
|
|
||||||
|
@satmachineadmin_api_router.delete("/api/v1/dca/deposits/{deposit_id}")
|
||||||
|
async def api_delete_deposit(
|
||||||
|
deposit_id: str,
|
||||||
|
user: User = Depends(check_super_user),
|
||||||
|
):
|
||||||
|
"""Delete a deposit. Only pending deposits (not yet inserted into the machine) can be deleted."""
|
||||||
|
deposit = await get_deposit(deposit_id)
|
||||||
|
if not deposit:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND, detail="Deposit not found."
|
||||||
|
)
|
||||||
|
|
||||||
|
if deposit.status != "pending":
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail="Only pending deposits can be deleted. Confirmed deposits have already been inserted into the machine.",
|
||||||
|
)
|
||||||
|
|
||||||
|
await delete_deposit(deposit_id)
|
||||||
|
return {"message": "Deposit deleted successfully"}
|
||||||
|
|
||||||
|
|
||||||
# Transaction Polling Endpoints
|
# Transaction Polling Endpoints
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue