Skip to content

Commit

Permalink
Merge pull request #82 from agstrike/update-transaction-model
Browse files Browse the repository at this point in the history
Update transaction model
  • Loading branch information
simhnna authored Jun 21, 2020
2 parents ec21880 + 78a8799 commit 632d185
Show file tree
Hide file tree
Showing 19 changed files with 284 additions and 109 deletions.
4 changes: 4 additions & 0 deletions silverstrike/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ def merge_accounts(self, request, queryset):
return
base = accounts.pop()
for account in accounts:
# update transactions
models.Transaction.objects.filter(src_id=account.id).update(src_id=base.id)
models.Transaction.objects.filter(dst_id=account.id).update(dst_id=base.id)
# update splits
models.Split.objects.filter(account_id=account.id).update(account_id=base.id)
models.Split.objects.filter(opposing_account_id=account.id).update(
Expand Down Expand Up @@ -72,4 +75,5 @@ class SplitInline(admin.TabularInline):
class TransactionAdmin(admin.ModelAdmin):
inlines = [SplitInline]
date_hierarchy = 'date'
list_display = ['title', 'src', 'dst', 'date', 'amount', 'transaction_type']
search_fields = ['title', 'notes', 'splits__title']
62 changes: 33 additions & 29 deletions silverstrike/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,24 @@ def save(self):
class TransactionForm(forms.ModelForm):
class Meta:
model = models.Transaction
fields = ['title', 'source_account', 'destination_account',
fields = ['title', 'src', 'dst',
'amount', 'date', 'value_date', 'category', 'notes']

amount = forms.DecimalField(max_digits=10, decimal_places=2, min_value=0.01)
category = forms.ModelChoiceField(
queryset=models.Category.objects.exclude(active=False).order_by('name'), required=False)
value_date = forms.DateField(required=False)

source_account = forms.ModelChoiceField(queryset=models.Account.objects.filter(
src = forms.ModelChoiceField(queryset=models.Account.objects.filter(
account_type=models.Account.PERSONAL, active=True))
destination_account = forms.ModelChoiceField(queryset=models.Account.objects.filter(
dst = forms.ModelChoiceField(queryset=models.Account.objects.filter(
account_type=models.Account.PERSONAL, active=True))

def save(self, commit=True):
transaction = super().save(commit)
src = self.cleaned_data['source_account']
dst = self.cleaned_data['destination_account']
amount = self.cleaned_data['amount']
src = transaction.src
dst = transaction.dst
amount = transaction.amount
value_date = self.cleaned_data.get('value_date') or transaction.date
models.Split.objects.update_or_create(
transaction=transaction, amount__lt=0,
Expand All @@ -113,8 +113,8 @@ def save(self, commit=True):
class TransferForm(TransactionForm):
def save(self, commit=True):
transaction = super().save(commit)
src = self.cleaned_data['source_account']
dst = self.cleaned_data['destination_account']
src = transaction.src
dst = transaction.dst
amount = self.cleaned_data['amount']
models.Split.objects.update_or_create(
transaction=transaction, amount__lt=0,
Expand All @@ -133,37 +133,35 @@ def save(self, commit=True):
def clean(self):
super().clean()
self.instance.transaction_type = models.Transaction.TRANSFER
if self.cleaned_data['source_account'] == self.cleaned_data['destination_account']:
if self.cleaned_data['src'] == self.cleaned_data['dst']:
error = 'source and destination account have to be different'
self.add_error('destination_account', error)
self.add_error('source_account', error)
self.add_error('dst', error)
self.add_error('src', error)


class WithdrawForm(TransactionForm):
destination_account = forms.CharField(max_length=64, label=_('Debitor'),
widget=forms.TextInput(attrs={'autocomplete': 'off'}))
dst = forms.CharField(max_length=64, label=_('Debitor'),
widget=forms.TextInput(attrs={'autocomplete': 'off'}))

def save(self, commit=True):
def clean_dst(self):
account, _ = models.Account.objects.get_or_create(
name=self.cleaned_data['destination_account'],
name=self.cleaned_data['dst'],
account_type=models.Account.FOREIGN)
self.cleaned_data['destination_account'] = account
return super().save(commit)
return account

def clean(self):
super().clean()
self.instance.transaction_type = models.Transaction.WITHDRAW


class DepositForm(TransactionForm):
source_account = forms.CharField(max_length=64, label=_('Creditor'),
widget=forms.TextInput(attrs={'autocomplete': 'off'}))
src = forms.CharField(max_length=64, label=_('Creditor'),
widget=forms.TextInput(attrs={'autocomplete': 'off'}))

def save(self, commit=True):
account, _ = models.Account.objects.get_or_create(name=self.cleaned_data['source_account'],
def clean_src(self):
account, _ = models.Account.objects.get_or_create(name=self.cleaned_data['src'],
account_type=models.Account.FOREIGN)
self.cleaned_data['source_account'] = account
return super().save(commit)
return account

def clean(self):
super().clean()
Expand Down Expand Up @@ -219,15 +217,21 @@ def __init__(self, *args, **kwargs):
def save(self, commit=True):
transaction = super().save(False)
transaction.transaction_type = models.Transaction.SYSTEM
transaction.save()
src = models.Account.objects.get(account_type=models.Account.SYSTEM).pk
dst = models.Account.objects.get(pk=self.account)
transaction.src = models.Account.objects.get(account_type=models.Account.SYSTEM)
transaction.dst = models.Account.objects.get(pk=self.account)

balance = self.cleaned_data['balance']
amount = balance - dst.balance
amount = balance - transaction.dst.balance
transaction.amount = abs(amount)

transaction.save()

models.Split.objects.create(transaction=transaction, amount=-amount,
account_id=src, opposing_account=dst, title=transaction.title)
account=transaction.src, opposing_account=transaction.dst,
title=transaction.title)
models.Split.objects.create(transaction=transaction, amount=amount,
account=dst, opposing_account_id=src, title=transaction.title)
account=transaction.dst, opposing_account=transaction.src,
title=transaction.title)
return transaction

def clean(self):
Expand Down
5 changes: 4 additions & 1 deletion silverstrike/importers/firefly.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ def import_firefly(csv_path):

transaction = models.Transaction.objects.create(
title=line[title], date=line[date],
transaction_type=t_type)
transaction_type=t_type,
src_id=line[source],
dst_id=line[destination],
amount=line[amount])
models.Split.objects.bulk_create(
[models.Split(
account_id=line[source], title=line[title],
Expand Down
1 change: 1 addition & 0 deletions silverstrike/management/commands/createtestdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ def _create_monthly(self, year, month):
if transaction_date > date.today():
continue
self.transactions.append(Transaction(
src=src, dst=dst,
title=title, date=transaction_date, id=self.counter,
transaction_type=type, recurrence=recurrence))
self.splits.append(Split(
Expand Down
44 changes: 44 additions & 0 deletions silverstrike/migrations/0008_auto_20190105_1147.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 2.1.2 on 2019-01-05 11:47

from django.db import migrations, models
import django.db.models.deletion


def updateTransactions(apps, schema_editor):
Transaction = apps.get_model('silverstrike', 'Transaction')
db_alias = schema_editor.connection.alias
for t in Transaction.objects.using(db_alias).all():
for s in t.splits.filter(amount__gt=0):
t.amount += s.amount
t.src = s.opposing_account
t.dst = s.account
t.save()

class Migration(migrations.Migration):

dependencies = [
('silverstrike', '0007_auto_20181230_2157'),
]

operations = [
migrations.AddField(
model_name='transaction',
name='amount',
field=models.DecimalField(decimal_places=2, default=0, max_digits=10),
),
migrations.AddField(
model_name='transaction',
name='dst',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='credits', to='silverstrike.Account'),
preserve_default=False,
),
migrations.AddField(
model_name='transaction',
name='src',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='debits', to='silverstrike.Account'),
preserve_default=False,
),
migrations.RunPython(
updateTransactions
)
]
42 changes: 42 additions & 0 deletions silverstrike/migrations/0009_auto_20190204_1925.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Generated by Django 2.1.5 on 2019-02-04 19:25

from django.db import migrations, models


def set_ibans(apps, schema_editor):
Account = apps.get_model('silverstrike', 'Account')
db_alias = schema_editor.connection.alias
for a in Account.objects.using(db_alias).exclude(iban=''):
a.import_ibans = '["{}"]'.format(a.iban)
a.save()


class Migration(migrations.Migration):

dependencies = [
('silverstrike', '0008_auto_20190105_1147'),
]

operations = [
migrations.AddField(
model_name='account',
name='import_ibans',
field=models.TextField(default='[]'),
),
migrations.AddField(
model_name='account',
name='import_names',
field=models.TextField(default='[]'),
),
migrations.AlterField(
model_name='recurringtransaction',
name='transaction_type',
field=models.IntegerField(choices=[(1, 'Deposit'), (2, 'Withdrawal'), (3, 'Transfer')]),
),
migrations.AlterField(
model_name='transaction',
name='transaction_type',
field=models.IntegerField(choices=[(1, 'Deposit'), (2, 'Withdrawal'), (3, 'Transfer'), (4, 'Reconcile')]),
),
migrations.RunPython(set_ibans)
]
24 changes: 13 additions & 11 deletions silverstrike/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class Account(models.Model):
last_modified = models.DateTimeField(auto_now=True)
show_on_dashboard = models.BooleanField(default=False)
iban = models.CharField(max_length=34, blank=True, null=True)
import_ibans = models.TextField(default='[]')
import_names = models.TextField(default='[]')

objects = AccountQuerySet.as_manager()

Expand Down Expand Up @@ -102,7 +104,10 @@ def get_data_points(self, dstart=date.today() - timedelta(days=365),
def set_initial_balance(self, amount):
system = Account.objects.get(account_type=Account.SYSTEM)
transaction = Transaction.objects.create(title=_('Initial Balance'),
transaction_type=Transaction.SYSTEM)
transaction_type=Transaction.SYSTEM,
src=system,
dst=self,
amount=amount)
Split.objects.create(transaction=transaction, amount=-amount,
account=system, opposing_account=self)
Split.objects.create(transaction=transaction, amount=amount,
Expand All @@ -113,6 +118,9 @@ class TransactionQuerySet(models.QuerySet):
def last_10(self):
return self.order_by('-date')[:10]

def date_range(self, dstart, dend):
return self.filter(date__gte=dstart, date__lte=dend)


class Transaction(models.Model):
DEPOSIT = 1
Expand All @@ -133,6 +141,9 @@ class Meta:
date = models.DateField(default=date.today)
notes = models.TextField(blank=True, null=True)
transaction_type = models.IntegerField(choices=TRANSACTION_TYPES)
src = models.ForeignKey(Account, models.CASCADE, 'debits')
dst = models.ForeignKey(Account, models.CASCADE, 'credits')
amount = models.DecimalField(default=0, max_digits=10, decimal_places=2)
last_modified = models.DateTimeField(auto_now=True)
recurrence = models.ForeignKey('RecurringTransaction', models.SET_NULL,
related_name='recurrences', blank=True, null=True)
Expand All @@ -150,14 +161,6 @@ def get_transaction_type_str(self):
if i == self.transaction_type:
return name

@property
def amount(self):
if self.transaction_type == Transaction.TRANSFER:
return abs(
self.splits.transfers_once().aggregate(models.Sum('amount'))['amount__sum'] or 0)
else:
return self.splits.personal().aggregate(models.Sum('amount'))['amount__sum'] or 0

@property
def is_split(self):
return len(self.splits.all()) > 2
Expand Down Expand Up @@ -196,8 +199,7 @@ def category(self, category):
return self.filter(category=category)

def transfers_once(self):
return self.personal().exclude(opposing_account__account_type=Account.PERSONAL,
amount__gte=0)
return self.exclude(opposing_account__account_type=Account.PERSONAL, amount__gte=0)

def exclude_transfers(self):
return self.exclude(account__account_type=Account.PERSONAL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ <h3 class="box-title">{% trans 'Import statements' %}</h3>
<th>{% trans 'Notes' %}</th>
<th>{% trans 'Recurrence' %}</th>
<th>{% trans 'Amount' %}</th>
<th>{% trans 'Ignore' %}</th>
</tr>
{% for datum in data %}
<tr>
<td><input type="date" value="{{ datum.transaction_date|date:'Y-m-d' }}" name="date-{{forloop.counter0}}"></td>
<td>{{ datum.book_date|date:"SHORT_DATE_FORMAT" }}</td>
<td><input class="account-input" type="text" name="account-{{forloop.counter0}}" autocomplete="off"></td>
<td><input class="account-input" type="text" name="account-{{forloop.counter0}}" autocomplete="off"
{% if datum.suggested_account %} value="{{ datum.suggested_account }}"
{% endif %}
></td>
<td>{{ datum.account }}</td>
<td><input type="text" name="title-{{forloop.counter0}}"></td>
<td>{{ datum.notes }}</td>
Expand All @@ -48,6 +52,7 @@ <h3 class="box-title">{% trans 'Import statements' %}</h3>
</select>
</td>
<td>{{ datum.amount }}</td>
<td><input type="checkbox" name="ignore-{{forloop.counter0}}" {% if datum.ignore %}checked="true"{% endif %}"></td>
</tr>
{% endfor %}
</table>
Expand Down
24 changes: 12 additions & 12 deletions silverstrike/templates/silverstrike/transaction_edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,20 @@ <h3 class="box-title">{% trans 'Transaction Details' %}</h3>
{% endfor %}
</div>
</div>
<div class="form-group{% if form.source_account.errors %} has-error{% endif %}">
<label class="col-sm-2 control-label" for="{{ form.source_account.id_for_label }}">{{ form.source_account.label }}</label>
<div class="form-group{% if form.src.errors %} has-error{% endif %}">
<label class="col-sm-2 control-label" for="{{ form.src.id_for_label }}">{{ form.src.label }}</label>
<div class="col-sm-8">
{{ form.source_account|add_class:"form-control" }}
{% for error in form.source_account.errors %}
{{ form.src|add_class:"form-control" }}
{% for error in form.src.errors %}
<span class="help-block">{{ error }}</span>
{% endfor %}
</div>
</div>
<div class="form-group{% if form.destination_account.errors %} has-error{% endif %}">
<label class="col-sm-2 control-label" for="{{ form.destination_account.id_for_label }}">{{ form.destination_account.label }}</label>
<div class="form-group{% if form.dst.errors %} has-error{% endif %}">
<label class="col-sm-2 control-label" for="{{ form.dst.id_for_label }}">{{ form.dst.label }}</label>
<div class="col-sm-8">
{{ form.destination_account|add_class:"form-control" }}
{% for error in form.destination_account.errors %}
{{ form.dst|add_class:"form-control" }}
{% for error in form.dst.errors %}
<span class="help-block">{{ error }}</span>
{% endfor %}
</div>
Expand Down Expand Up @@ -127,14 +127,14 @@ <h3 class="box-title">{% trans 'Transaction Details' %}</h3>
orientation: 'bottom'

});
if ($('input#id_source_account').length > 0) {
if ($('input#id_src').length > 0) {
$.getJSON('{% url 'api_accounts' 'FOREIGN' %}').done(function (data) {
$('#id_source_account').typeahead({source: data});
$('#id_src').typeahead({source: data});
});
}
if ($('input#id_destination_account').length > 0) {
if ($('input#id_dst').length > 0) {
$.getJSON('{% url 'api_accounts' 'FOREIGN' %}').done(function (data) {
$('#id_destination_account').typeahead({source: data});
$('#id_dst').typeahead({source: data});
});
}
$('.date-input').attr('autocomplete', 'off');
Expand Down
3 changes: 2 additions & 1 deletion silverstrike/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@


def create_transaction(title, src, dst, amount, type, date=date.today(), category=None):
t = Transaction.objects.create(title=title, date=date, transaction_type=type)
t = Transaction.objects.create(title=title, date=date, transaction_type=type,
src=src, dst=dst, amount=amount)
Split.objects.bulk_create([
Split(title=title, account=src, opposing_account=dst,
amount=-amount, transaction=t, date=date, category=category),
Expand Down
Loading

0 comments on commit 632d185

Please sign in to comment.