Completes Phase 2: Adds reconciliation features
Implements balance assertions, reconciliation API endpoints, a reconciliation UI dashboard, and automated daily balance checks. This provides comprehensive reconciliation tools to ensure accounting accuracy and catch discrepancies early. Updates roadmap to mark Phase 2 as complete.
This commit is contained in:
parent
c0277dfc98
commit
6d84479f7d
7 changed files with 963 additions and 6 deletions
161
views_api.py
161
views_api.py
|
|
@ -1,3 +1,4 @@
|
|||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from http import HTTPStatus
|
||||
|
||||
|
|
@ -1228,3 +1229,163 @@ async def api_delete_balance_assertion(
|
|||
await delete_balance_assertion(assertion_id)
|
||||
|
||||
return {"success": True, "message": "Balance assertion deleted"}
|
||||
|
||||
|
||||
# ===== RECONCILIATION ENDPOINTS =====
|
||||
|
||||
|
||||
@castle_api_router.get("/api/v1/reconciliation/summary")
|
||||
async def api_get_reconciliation_summary(
|
||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> dict:
|
||||
"""Get reconciliation summary (admin only)"""
|
||||
from lnbits.settings import settings as lnbits_settings
|
||||
|
||||
if wallet.wallet.user != lnbits_settings.super_user:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN,
|
||||
detail="Only super user can access reconciliation",
|
||||
)
|
||||
|
||||
# Get all assertions
|
||||
all_assertions = await get_balance_assertions(limit=1000)
|
||||
|
||||
# Count by status
|
||||
passed = len([a for a in all_assertions if a.status == AssertionStatus.PASSED])
|
||||
failed = len([a for a in all_assertions if a.status == AssertionStatus.FAILED])
|
||||
pending = len([a for a in all_assertions if a.status == AssertionStatus.PENDING])
|
||||
|
||||
# Get all journal entries
|
||||
all_entries = await get_all_journal_entries(limit=1000)
|
||||
|
||||
# Count entries by flag
|
||||
cleared = len([e for e in all_entries if e.flag == JournalEntryFlag.CLEARED])
|
||||
pending_entries = len([e for e in all_entries if e.flag == JournalEntryFlag.PENDING])
|
||||
flagged = len([e for e in all_entries if e.flag == JournalEntryFlag.FLAGGED])
|
||||
voided = len([e for e in all_entries if e.flag == JournalEntryFlag.VOID])
|
||||
|
||||
# Get all accounts
|
||||
accounts = await get_all_accounts()
|
||||
|
||||
return {
|
||||
"assertions": {
|
||||
"total": len(all_assertions),
|
||||
"passed": passed,
|
||||
"failed": failed,
|
||||
"pending": pending,
|
||||
},
|
||||
"entries": {
|
||||
"total": len(all_entries),
|
||||
"cleared": cleared,
|
||||
"pending": pending_entries,
|
||||
"flagged": flagged,
|
||||
"voided": voided,
|
||||
},
|
||||
"accounts": {
|
||||
"total": len(accounts),
|
||||
},
|
||||
"last_checked": datetime.now().isoformat(),
|
||||
}
|
||||
|
||||
|
||||
@castle_api_router.post("/api/v1/reconciliation/check-all")
|
||||
async def api_check_all_assertions(
|
||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> dict:
|
||||
"""Re-check all balance assertions (admin only)"""
|
||||
from lnbits.settings import settings as lnbits_settings
|
||||
|
||||
if wallet.wallet.user != lnbits_settings.super_user:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN,
|
||||
detail="Only super user can run reconciliation checks",
|
||||
)
|
||||
|
||||
# Get all assertions
|
||||
all_assertions = await get_balance_assertions(limit=1000)
|
||||
|
||||
results = {
|
||||
"total": len(all_assertions),
|
||||
"checked": 0,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"errors": 0,
|
||||
}
|
||||
|
||||
for assertion in all_assertions:
|
||||
try:
|
||||
checked = await check_balance_assertion(assertion.id)
|
||||
results["checked"] += 1
|
||||
if checked.status == AssertionStatus.PASSED:
|
||||
results["passed"] += 1
|
||||
elif checked.status == AssertionStatus.FAILED:
|
||||
results["failed"] += 1
|
||||
except Exception as e:
|
||||
results["errors"] += 1
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@castle_api_router.get("/api/v1/reconciliation/discrepancies")
|
||||
async def api_get_discrepancies(
|
||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> dict:
|
||||
"""Get all discrepancies (failed assertions, flagged entries) (admin only)"""
|
||||
from lnbits.settings import settings as lnbits_settings
|
||||
|
||||
if wallet.wallet.user != lnbits_settings.super_user:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN,
|
||||
detail="Only super user can view discrepancies",
|
||||
)
|
||||
|
||||
# Get failed assertions
|
||||
failed_assertions = await get_balance_assertions(
|
||||
status=AssertionStatus.FAILED,
|
||||
limit=1000,
|
||||
)
|
||||
|
||||
# Get flagged entries
|
||||
all_entries = await get_all_journal_entries(limit=1000)
|
||||
flagged_entries = [e for e in all_entries if e.flag == JournalEntryFlag.FLAGGED]
|
||||
pending_entries = [e for e in all_entries if e.flag == JournalEntryFlag.PENDING]
|
||||
|
||||
return {
|
||||
"failed_assertions": failed_assertions,
|
||||
"flagged_entries": flagged_entries,
|
||||
"pending_entries": pending_entries,
|
||||
"total_discrepancies": len(failed_assertions) + len(flagged_entries),
|
||||
}
|
||||
|
||||
|
||||
# ===== AUTOMATED TASKS ENDPOINTS =====
|
||||
|
||||
|
||||
@castle_api_router.post("/api/v1/tasks/daily-reconciliation")
|
||||
async def api_run_daily_reconciliation(
|
||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> dict:
|
||||
"""
|
||||
Manually trigger the daily reconciliation check (admin only).
|
||||
This endpoint can also be called via cron job.
|
||||
|
||||
Returns a summary of the reconciliation check results.
|
||||
"""
|
||||
from lnbits.settings import settings as lnbits_settings
|
||||
|
||||
if wallet.wallet.user != lnbits_settings.super_user:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN,
|
||||
detail="Only super user can run daily reconciliation",
|
||||
)
|
||||
|
||||
from .tasks import check_all_balance_assertions
|
||||
|
||||
try:
|
||||
results = await check_all_balance_assertions()
|
||||
return results
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error running daily reconciliation: {str(e)}",
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue