mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-13 02:46:28 +00:00
Init accounting panel, add import/export
This commit is contained in:
parent
5730ec0383
commit
ca79ea9914
3
.gitignore
vendored
3
.gitignore
vendored
@ -651,4 +651,5 @@ api/lightning/invoices*
|
||||
api/lightning/router*
|
||||
api/lightning/googleapis*
|
||||
frontend/static/admin*
|
||||
frontend/static/rest_framework*
|
||||
frontend/static/rest_framework*
|
||||
frontend/static/import_export*
|
@ -78,6 +78,7 @@ class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
|
||||
"concept",
|
||||
"status",
|
||||
"num_satoshis",
|
||||
"fee",
|
||||
"type",
|
||||
"expires_at",
|
||||
"expiry_height",
|
||||
@ -133,7 +134,6 @@ class CurrencieAdmin(admin.ModelAdmin):
|
||||
readonly_fields = ("currency", "exchange_rate", "timestamp")
|
||||
ordering = ("id", )
|
||||
|
||||
|
||||
@admin.register(MarketTick)
|
||||
class MarketTickAdmin(admin.ModelAdmin):
|
||||
list_display = ("timestamp", "price", "volume", "premium", "currency",
|
||||
|
@ -261,6 +261,7 @@ class LNNode:
|
||||
|
||||
if response.status == 2: # STATUS 'SUCCEEDED'
|
||||
lnpayment.status = LNPayment.Status.SUCCED
|
||||
lnpayment.fee = float(response.fee_msat)/1000
|
||||
lnpayment.save()
|
||||
return True, None
|
||||
|
||||
|
@ -440,6 +440,12 @@ class Logics:
|
||||
"bad_request":
|
||||
"You cannot submit a invoice while bonds are not locked."
|
||||
}
|
||||
if order.status == Order.Status.FAI:
|
||||
if order.payout.status != LNPayment.Status.EXPIRE:
|
||||
return False, {
|
||||
"bad_request":
|
||||
"You cannot submit an invoice only after expiration or 3 failed attempts"
|
||||
}
|
||||
|
||||
num_satoshis = cls.payout_amount(order, user)[1]["invoice_amount"]
|
||||
payout = LNNode.validate_ln_invoice(invoice, num_satoshis)
|
||||
|
@ -109,6 +109,8 @@ class LNPayment(models.Model):
|
||||
MinValueValidator(100),
|
||||
MaxValueValidator(MAX_TRADE * (1 + DEFAULT_BOND_SIZE + FEE)),
|
||||
])
|
||||
# Fee in sats with mSats decimals fee_msat
|
||||
fee = models.DecimalField(max_digits=10, decimal_places=3, default=0, null=False, blank=False)
|
||||
created_at = models.DateTimeField()
|
||||
expires_at = models.DateTimeField()
|
||||
cltv_expiry = models.PositiveSmallIntegerField(null=True,
|
||||
|
@ -123,6 +123,7 @@ def follow_send_payment(lnpayment):
|
||||
if response.status == 2: # Status 2 'SUCCEEDED'
|
||||
print("SUCCEEDED")
|
||||
lnpayment.status = LNPayment.Status.SUCCED
|
||||
lnpayment.fee = float(response.fee_msat)/1000
|
||||
lnpayment.save()
|
||||
order.status = Order.Status.SUC
|
||||
order.expires_at = timezone.now() + timedelta(
|
||||
|
@ -1,41 +1,53 @@
|
||||
from django.contrib import admin
|
||||
from control.models import AccountingDay, AccountingMonth, Dispute
|
||||
from import_export.admin import ImportExportModelAdmin
|
||||
|
||||
# Register your models here.
|
||||
|
||||
@admin.register(AccountingDay)
|
||||
class AccountingDayAdmin(admin.ModelAdmin):
|
||||
class AccountingDayAdmin(ImportExportModelAdmin):
|
||||
|
||||
list_display = (
|
||||
"day",
|
||||
"contracted",
|
||||
"num_contracts",
|
||||
"net_settled",
|
||||
"net_paid",
|
||||
"net_balance",
|
||||
"total_inflow",
|
||||
"total_outflow",
|
||||
"total_routing_fees",
|
||||
"total_cashflow",
|
||||
"pending_rewards",
|
||||
"inflow",
|
||||
"outflow",
|
||||
"routing_fees",
|
||||
"cashflow",
|
||||
"outstanding_earned_rewards",
|
||||
"outstanding_pending_disputes",
|
||||
"lifetime_rewards_claimed",
|
||||
"outstanding_earned_rewards",
|
||||
"pending_disputes",
|
||||
"pending_claimable",
|
||||
"rewards_claimed",
|
||||
)
|
||||
change_links = ["day"]
|
||||
search_fields = ["day"]
|
||||
|
||||
@admin.register(AccountingMonth)
|
||||
class AccountingMonthAdmin(admin.ModelAdmin):
|
||||
class AccountingMonthAdmin(ImportExportModelAdmin):
|
||||
|
||||
list_display = (
|
||||
"month",
|
||||
"contracted",
|
||||
"num_contracts",
|
||||
"net_settled",
|
||||
"net_paid",
|
||||
"net_balance",
|
||||
"total_inflow",
|
||||
"total_outflow",
|
||||
"total_routing_fees",
|
||||
"total_cashflow",
|
||||
"pending_rewards",
|
||||
"inflow",
|
||||
"outflow",
|
||||
"routing_fees",
|
||||
"cashflow",
|
||||
"outstanding_earned_rewards",
|
||||
"outstanding_pending_disputes",
|
||||
"lifetime_rewards_claimed",
|
||||
"outstanding_earned_rewards",
|
||||
"pending_disputes",
|
||||
"pending_claimable",
|
||||
"rewards_claimed",
|
||||
)
|
||||
change_links = ["month"]
|
||||
search_fields = ["month"]
|
@ -7,6 +7,8 @@ class AccountingDay(models.Model):
|
||||
# Every field is denominated in Sats with (3 decimals for millisats)
|
||||
# Total volume contracted
|
||||
contracted = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Number of contracts
|
||||
num_contracts = models.BigIntegerField(default=0, null=False, blank=False)
|
||||
# Net volume of trading invoices settled (excludes disputes)
|
||||
net_settled = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Net volume of trading invoices paid (excludes rewards and disputes)
|
||||
@ -14,19 +16,26 @@ class AccountingDay(models.Model):
|
||||
# Sum of net settled and net paid
|
||||
net_balance = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Total volume of invoices settled
|
||||
total_inflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
inflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Total volume of invoices paid
|
||||
total_outflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
outflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Total cost in routing fees
|
||||
total_routing_fees = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
routing_fees = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Total inflows minus outflows and routing fees
|
||||
total_cashflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Balance on pending rewards (referral rewards and slashed bonds)
|
||||
pending_rewards = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
cashflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Balance on earned rewards (referral rewards, slashed bonds and solved disputes)
|
||||
outstanding_earned_rewards = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Balance on pending disputes (not resolved yet)
|
||||
outstanding_pending_disputes = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Rewards claimed lifetime
|
||||
lifetime_rewards_claimed = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Balance change from last day on earned rewards (referral rewards, slashed bonds and solved disputes)
|
||||
earned_rewards = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Balance change on pending disputes (not resolved yet)
|
||||
pending_disputes = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Balance on pending rewards and resolved disputes
|
||||
pending_claimable = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Rewards claimed on day
|
||||
rewards_claimed = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
|
||||
|
||||
|
||||
class AccountingMonth(models.Model):
|
||||
@ -35,6 +44,8 @@ class AccountingMonth(models.Model):
|
||||
# Every field is denominated in Sats with (3 decimals for millisats)
|
||||
# Total volume contracted
|
||||
contracted = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Number of contracts
|
||||
num_contracts = models.BigIntegerField(default=0, null=False, blank=False)
|
||||
# Net volume of trading invoices settled (excludes disputes)
|
||||
net_settled = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Net volume of trading invoices paid (excludes rewards and disputes)
|
||||
@ -42,19 +53,25 @@ class AccountingMonth(models.Model):
|
||||
# Sum of net settled and net paid
|
||||
net_balance = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Total volume of invoices settled
|
||||
total_inflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
inflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Total volume of invoices paid
|
||||
total_outflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
outflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Total cost in routing fees
|
||||
total_routing_fees = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
routing_fees = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Total inflows minus outflows and routing fees
|
||||
total_cashflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Balance on pending rewards (referral rewards and slashed bonds)
|
||||
pending_rewards = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
cashflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Balance on earned rewards (referral rewards, slashed bonds and solved disputes)
|
||||
outstanding_earned_rewards = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Balance on pending disputes (not resolved yet)
|
||||
outstanding_pending_disputes = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Rewards claimed lifetime
|
||||
lifetime_rewards_claimed = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Balance change from last day on earned rewards (referral rewards, slashed bonds and solved disputes)
|
||||
earned_rewards = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Balance change on pending disputes (not resolved yet)
|
||||
pending_disputes = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Balance on pending rewards and resolved disputes
|
||||
pending_claimable = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
# Rewards claimed on day
|
||||
rewards_claimed = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
|
||||
|
||||
class Dispute(models.Model):
|
||||
pass
|
@ -1,6 +1,95 @@
|
||||
from celery import shared_task
|
||||
from api.models import Order, LNPayment, Profile
|
||||
from api.models import Order, LNPayment, Profile, MarketTick
|
||||
from control.models import AccountingDay, AccountingMonth
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from django.db.models import Sum
|
||||
|
||||
@shared_task(name="do_accounting")
|
||||
def do_accounting():
|
||||
'''
|
||||
Does all accounting from the beginning of time
|
||||
'''
|
||||
|
||||
all_payments = LNPayment.objects.all()
|
||||
all_ticks = MarketTick.objects.all()
|
||||
today = timezone.now().date()
|
||||
|
||||
try:
|
||||
last_accounted_day = AccountingDay.objects.latest('day').day.date()
|
||||
except:
|
||||
last_accounted_day = None
|
||||
|
||||
if last_accounted_day == today:
|
||||
return {'message':'no days to account for'}
|
||||
elif last_accounted_day != None:
|
||||
initial_day = last_accounted_day + timedelta(days=1)
|
||||
elif last_accounted_day == None:
|
||||
initial_day = all_payments.earliest('created_at').created_at.date()
|
||||
|
||||
|
||||
day = initial_day
|
||||
result = {}
|
||||
accounted_yesterday = None
|
||||
while day <= today:
|
||||
day_payments = all_payments.filter(created_at__gte=day,created_at__lte=day+timedelta(days=1))
|
||||
day_ticks = all_ticks.filter(timestamp__gte=day,timestamp__lte=day+timedelta(days=1))
|
||||
|
||||
contracted = day_ticks.aggregate(Sum('volume'))['volume__sum']
|
||||
num_contracts = day_ticks.count()
|
||||
inflow = day_payments.filter(type=LNPayment.Types.HOLD,status=LNPayment.Status.SETLED).aggregate(Sum('num_satoshis'))['num_satoshis__sum']
|
||||
outflow = day_payments.filter(type=LNPayment.Types.NORM,status=LNPayment.Status.SUCCED).aggregate(Sum('num_satoshis'))['num_satoshis__sum']
|
||||
routing_fees = day_payments.filter(type=LNPayment.Types.NORM,status=LNPayment.Status.SUCCED).aggregate(Sum('fee'))['fee__sum']
|
||||
rewards_claimed = day_payments.filter(type=LNPayment.Types.NORM,concept=LNPayment.Concepts.WITHREWA,status=LNPayment.Status.SUCCED).aggregate(Sum('num_satoshis'))['num_satoshis__sum']
|
||||
|
||||
contracted = 0 if contracted == None else contracted
|
||||
inflow = 0 if inflow == None else inflow
|
||||
outflow = 0 if outflow == None else outflow
|
||||
routing_fees = 0 if routing_fees == None else routing_fees
|
||||
rewards_claimed = 0 if rewards_claimed == None else rewards_claimed
|
||||
|
||||
accounted_day = AccountingDay.objects.create(
|
||||
day = day,
|
||||
contracted = contracted,
|
||||
num_contracts = num_contracts,
|
||||
net_settled = 0,
|
||||
net_paid = 0,
|
||||
net_balance = 0,
|
||||
inflow = inflow,
|
||||
outflow = outflow,
|
||||
routing_fees = routing_fees,
|
||||
cashflow = inflow - outflow - routing_fees,
|
||||
rewards_claimed = rewards_claimed,
|
||||
)
|
||||
|
||||
if day == today:
|
||||
pending_disputes = Order.objects.filter(status__in=[Order.Status.DIS,Order.Status.WFR])
|
||||
if len(pending_disputes) > 0:
|
||||
outstanding_pending_disputes = 0
|
||||
for order in pending_disputes:
|
||||
outstanding_pending_disputes += order.payout.num_satoshis
|
||||
|
||||
accounted_day.outstanding_earned_rewards = Profile.objects.all().aggregate(Sum('earned_rewards'))['earned_rewards__sum']
|
||||
accounted_day.outstanding_pending_disputes = outstanding_pending_disputes
|
||||
accounted_day.lifetime_rewards_claimed = Profile.objects.all().aggregate(Sum('claimed_rewards'))['claimed_rewards__sum']
|
||||
if accounted_yesterday != None:
|
||||
accounted_day.earned_rewards = accounted_day.outstanding_earned_rewards - accounted_yesterday.outstanding_earned_rewards
|
||||
accounted_day.pending_disputes = outstanding_pending_disputes - accounted_yesterday.outstanding_earned_rewards
|
||||
|
||||
accounted_day.save()
|
||||
accounted_yesterday = accounted_day
|
||||
result[str(day)]={'contracted':contracted,'inflow':inflow,'outflow':outflow}
|
||||
day = day + timedelta(days=1)
|
||||
|
||||
|
||||
|
||||
return result
|
||||
|
||||
@shared_task(name="account_day")
|
||||
def account_day():
|
||||
'''
|
||||
Does daily accounting since last accounted day.
|
||||
To be run daily.
|
||||
'''
|
||||
|
||||
return
|
@ -350,7 +350,7 @@ export default class MakerPage extends Component {
|
||||
<Grid item xs={12} align="center" spacing={1}>
|
||||
<FormControl align="center">
|
||||
<FormHelperText>
|
||||
<Tooltip enterTouchDelay="0" title={"Set the skin-in-the-game (increase for higher safety assurance)"}>
|
||||
<Tooltip enterTouchDelay="0" placement="top" title={"Set the skin-in-the-game (increase for higher safety assurance)"}>
|
||||
<div align="center" style={{display:'flex',flexWrap:'wrap', transform: 'translate(20%, 0)'}}>
|
||||
Fidelity Bond Size <LockIcon sx={{height:20,width:20}}/>
|
||||
</div>
|
||||
|
File diff suppressed because one or more lines are too long
@ -23,4 +23,5 @@ scipy==1.8.0
|
||||
gunicorn==20.1.0
|
||||
psycopg2==2.9.3
|
||||
SQLAlchemy==1.4.31
|
||||
django-import-export==2.7.1
|
||||
requests[socks]
|
@ -39,6 +39,10 @@ app.conf.beat_schedule = {
|
||||
"task": "give_rewards",
|
||||
"schedule": crontab(hour=0, minute=0),
|
||||
},
|
||||
"account-day": { # Does accounting for the last day
|
||||
"task": "account_day",
|
||||
"schedule": crontab(hour=23, minute=55),
|
||||
},
|
||||
"cache-market-prices": { # Cache market prices every minute
|
||||
"task": "cache_external_market_prices",
|
||||
"schedule": timedelta(seconds=60),
|
||||
|
@ -56,6 +56,7 @@ INSTALLED_APPS = [
|
||||
"channels",
|
||||
"django_celery_beat",
|
||||
"django_celery_results",
|
||||
"import_export",
|
||||
"api",
|
||||
"chat",
|
||||
"control",
|
||||
@ -74,6 +75,7 @@ MIDDLEWARE = [
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "robosats.urls"
|
||||
IMPORT_EXPORT_USE_TRANSACTIONS = True
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
@ -92,7 +94,6 @@ TEMPLATES = [
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = "robosats.wsgi.application"
|
||||
USE_TZ = True
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
||||
|
Loading…
Reference in New Issue
Block a user