2017-10-13 19:25:31 +02:00
|
|
|
import json
|
2017-10-14 18:07:35 +02:00
|
|
|
from datetime import datetime, time, timedelta
|
2017-10-13 19:25:31 +02:00
|
|
|
from decimal import Decimal
|
|
|
|
|
2017-10-14 11:53:45 +02:00
|
|
|
import pytz
|
2018-01-11 19:24:13 +01:00
|
|
|
from byro_shackspace.models import ShackProfile
|
2017-10-13 19:25:31 +02:00
|
|
|
from django.core.management.base import BaseCommand
|
|
|
|
from django.db import transaction
|
|
|
|
from django.utils.dateparse import parse_date
|
|
|
|
|
2017-10-14 18:07:35 +02:00
|
|
|
from byro.bookkeeping.models import (
|
|
|
|
Account, AccountCategory, RealTransaction,
|
|
|
|
TransactionChannel, VirtualTransaction,
|
|
|
|
)
|
2017-10-13 19:25:31 +02:00
|
|
|
from byro.members.models import Member, Membership
|
|
|
|
|
2017-10-14 11:53:45 +02:00
|
|
|
TIMEZONE = pytz.timezone('Europe/Berlin')
|
|
|
|
|
|
|
|
|
|
|
|
def localize(date):
|
|
|
|
if date:
|
|
|
|
return TIMEZONE.localize(datetime.combine(date, time.min))
|
|
|
|
|
2017-10-13 19:25:31 +02:00
|
|
|
|
|
|
|
def _import_sepa(member_data, member):
|
|
|
|
sepa_keys = [
|
|
|
|
'iban', 'mandate_reason', 'zip_code', 'country',
|
|
|
|
'city', 'bic', 'address', 'fullname', 'issue_date',
|
|
|
|
'institute', 'mandate_reference',
|
|
|
|
]
|
|
|
|
for key in sepa_keys:
|
|
|
|
setattr(member.profile_sepa, key, member_data.get(f'sepa__{key}'))
|
|
|
|
member.profile_sepa.save()
|
|
|
|
|
|
|
|
|
2017-10-13 20:27:56 +02:00
|
|
|
def _get_main_accounts():
|
|
|
|
fee_account, _ = Account.objects.get_or_create(
|
|
|
|
account_category=AccountCategory.MEMBER_FEES,
|
|
|
|
)
|
|
|
|
donation_account, _ = Account.objects.get_or_create(
|
|
|
|
account_category=AccountCategory.MEMBER_DONATION,
|
|
|
|
)
|
|
|
|
liability_account, _ = Account.objects.get_or_create(
|
|
|
|
account_category=AccountCategory.LIABILITY,
|
|
|
|
)
|
|
|
|
|
|
|
|
return (
|
|
|
|
fee_account,
|
|
|
|
donation_account,
|
|
|
|
liability_account,
|
|
|
|
)
|
2017-10-13 19:25:31 +02:00
|
|
|
|
2017-10-13 20:27:56 +02:00
|
|
|
|
|
|
|
def _import_real_transactions(real_transactions):
|
2017-10-13 19:25:31 +02:00
|
|
|
transactions = []
|
2017-10-13 20:27:56 +02:00
|
|
|
|
2017-10-13 19:25:31 +02:00
|
|
|
for real_transaction in real_transactions:
|
|
|
|
transactions.append(RealTransaction(
|
|
|
|
channel=TransactionChannel.BANK,
|
2017-10-14 11:53:45 +02:00
|
|
|
value_datetime=localize(parse_date(real_transaction['booking_date'] or real_transaction['due_date'])),
|
2017-10-13 19:25:31 +02:00
|
|
|
amount=real_transaction['amount'],
|
|
|
|
purpose=real_transaction['reference'],
|
|
|
|
originator=real_transaction.get('transaction_owner') or 'imported',
|
|
|
|
# TODO: reverses?
|
|
|
|
importer='shackbureau',
|
|
|
|
))
|
|
|
|
|
2017-10-13 20:27:56 +02:00
|
|
|
ids = [rt.pk for rt in RealTransaction.objects.bulk_create(transactions)]
|
|
|
|
return RealTransaction.objects.filter(pk__in=ids)
|
2017-10-13 19:25:31 +02:00
|
|
|
|
|
|
|
|
2017-10-13 20:27:56 +02:00
|
|
|
def _import_fee_claims(member, virtual_transactions):
|
|
|
|
fee_account, donation_account, liability_account = _get_main_accounts()
|
|
|
|
|
|
|
|
claims = [v for v in virtual_transactions if v['booking_type'] == 'fee_claim']
|
2017-10-13 19:25:31 +02:00
|
|
|
|
|
|
|
transactions = []
|
2017-10-13 20:27:56 +02:00
|
|
|
|
2017-10-13 19:25:31 +02:00
|
|
|
for claim in claims:
|
|
|
|
transactions.append(VirtualTransaction(
|
|
|
|
source_account=fee_account,
|
|
|
|
destination_account=liability_account,
|
|
|
|
member=member,
|
|
|
|
amount=abs(Decimal(claim['amount'])),
|
2017-10-14 11:53:45 +02:00
|
|
|
value_datetime=localize(parse_date(claim['due_date'])),
|
2017-10-13 19:25:31 +02:00
|
|
|
))
|
2017-10-13 20:27:56 +02:00
|
|
|
|
2017-10-13 19:25:31 +02:00
|
|
|
VirtualTransaction.objects.bulk_create(transactions)
|
|
|
|
|
2017-10-13 20:27:56 +02:00
|
|
|
|
|
|
|
def _import_inflows(member, virtual_transactions, real_transactions):
|
|
|
|
fee_account, donation_account, liability_account = _get_main_accounts()
|
|
|
|
|
|
|
|
inflows = [v for v in virtual_transactions if v['booking_type'] == 'deposit']
|
|
|
|
|
2017-10-13 19:25:31 +02:00
|
|
|
for inflow in inflows:
|
|
|
|
account = fee_account if inflow['transaction_type'] == 'membership fee' else donation_account
|
2017-10-14 11:53:45 +02:00
|
|
|
possible_real_transaction = real_transactions.filter(
|
|
|
|
virtual_transactions__isnull=True,
|
|
|
|
amount=abs(Decimal(inflow['amount'])),
|
|
|
|
value_datetime=localize(parse_date(inflow['due_date'])),
|
|
|
|
purpose=inflow['payment_reference'],
|
|
|
|
)
|
2017-10-14 18:07:13 +02:00
|
|
|
|
|
|
|
if possible_real_transaction.count() == 1:
|
2017-10-14 11:53:45 +02:00
|
|
|
real_transaction = possible_real_transaction.first()
|
2017-10-13 19:25:31 +02:00
|
|
|
|
2017-10-14 18:07:13 +02:00
|
|
|
VirtualTransaction.objects.create(
|
|
|
|
destination_account=account,
|
|
|
|
source_account=liability_account,
|
|
|
|
member=member,
|
|
|
|
amount=abs(Decimal(inflow['amount'])),
|
|
|
|
value_datetime=localize(parse_date(inflow['due_date'])),
|
|
|
|
real_transaction=real_transaction,
|
|
|
|
)
|
|
|
|
elif possible_real_transaction.count() == 0:
|
|
|
|
print(f'Found no transaction matching our query: {inflow}')
|
|
|
|
elif possible_real_transaction.count() > 1:
|
|
|
|
print(f'Found more than one transactions matching our query: {possible_real_transaction.values_list("pk", flat=True)}')
|
2017-10-13 20:27:56 +02:00
|
|
|
|
|
|
|
|
|
|
|
def _import_transactions(member_data, member):
|
|
|
|
real_transactions = member_data.get('bank_transactions')
|
|
|
|
virtual_transactions = member_data.get('account_transactions')
|
|
|
|
|
|
|
|
real_transactions = _import_real_transactions(real_transactions)
|
|
|
|
|
|
|
|
_import_fee_claims(member, virtual_transactions)
|
|
|
|
_import_inflows(member, virtual_transactions, real_transactions)
|
|
|
|
|
2017-10-13 19:25:31 +02:00
|
|
|
|
|
|
|
def import_member(member_data):
|
2018-01-13 12:55:33 +01:00
|
|
|
member, _ = Member.objects.update_or_create(
|
2017-10-13 19:25:31 +02:00
|
|
|
number=member_data['number'],
|
2018-01-13 12:55:33 +01:00
|
|
|
defaults={
|
|
|
|
'name': member_data['name'],
|
|
|
|
'address': member_data['address'],
|
|
|
|
'email': member_data['email'],
|
|
|
|
})
|
|
|
|
profile, _ = ShackProfile.objects.update_or_create(
|
2017-12-06 20:38:41 +01:00
|
|
|
member=member,
|
2018-01-13 12:55:33 +01:00
|
|
|
defaults={
|
|
|
|
'has_loeffelhardt_account': member_data.get('has_loeffelhardt_account', False),
|
|
|
|
'has_matomat_key': member_data.get('has_matomat_key', False),
|
|
|
|
'has_metro_card': member_data.get('has_metro_card', False),
|
2018-01-13 14:31:59 +01:00
|
|
|
'has_safe_key': member_data.get('has_safe_key', False),
|
2018-01-13 12:55:33 +01:00
|
|
|
'has_selgros_card': member_data.get('has_selgros_card', False),
|
|
|
|
'has_shack_iron_key': member_data.get('has_shack_iron_key', False),
|
|
|
|
'has_snackomat_key': member_data.get('has_snackomat_key', False),
|
|
|
|
'is_keyholder': member_data.get('is_keyholder', False),
|
|
|
|
'signed_DSV': member_data.get('signed_DSV', False),
|
|
|
|
'ssh_public_key': member_data.get('ssh_public_key', False),
|
|
|
|
})
|
2017-10-13 19:25:31 +02:00
|
|
|
memberships = member_data.get('memberships')
|
|
|
|
last = None
|
|
|
|
for membership in sorted(memberships, key=lambda m: m['membership_start']):
|
2018-01-13 13:12:54 +01:00
|
|
|
obj, _ = Membership.objects.update_or_create(
|
2017-10-13 19:25:31 +02:00
|
|
|
member=member,
|
2018-01-13 13:22:31 +01:00
|
|
|
start=parse_date(membership['membership_start']),
|
2018-01-13 12:55:33 +01:00
|
|
|
defaults={
|
|
|
|
'amount': Decimal(membership['membership_fee_monthly'])*membership['membership_fee_interval'],
|
|
|
|
'interval': membership['membership_fee_interval'],
|
2018-01-13 13:22:31 +01:00
|
|
|
})
|
2017-10-13 19:25:31 +02:00
|
|
|
if last:
|
|
|
|
last.end = obj.start - timedelta(days=1)
|
|
|
|
last.save(update_fields=['end'])
|
|
|
|
last = obj
|
|
|
|
|
2017-10-14 11:57:03 +02:00
|
|
|
if member_data['leave_date']:
|
2017-10-14 12:20:57 +02:00
|
|
|
last.end = parse_date(member_data['leave_date'])
|
2017-10-14 12:21:25 +02:00
|
|
|
last.save(update_fields=['end'])
|
2017-10-14 11:57:03 +02:00
|
|
|
|
2017-10-13 19:25:31 +02:00
|
|
|
if member_data['payment_type'].lower() == 'sepa':
|
|
|
|
_import_sepa(member_data, member)
|
|
|
|
|
|
|
|
for key in ['birth_date', 'nick', 'phone_number']:
|
|
|
|
value = member_data.get(f'profile__{key}')
|
|
|
|
if value:
|
|
|
|
setattr(member.profile_profile, key, value)
|
|
|
|
member.profile_profile.save()
|
2018-01-13 12:55:43 +01:00
|
|
|
# _import_transactions(member_data, member)
|
2017-10-14 18:07:27 +02:00
|
|
|
|
2017-10-13 19:25:31 +02:00
|
|
|
|
2017-10-13 20:28:36 +02:00
|
|
|
def import_members(data):
|
|
|
|
for member in data:
|
|
|
|
import_member(member)
|
|
|
|
|
|
|
|
|
2017-10-13 19:25:31 +02:00
|
|
|
class Command(BaseCommand):
|
2017-10-13 20:29:46 +02:00
|
|
|
help = 'Imports a shackbureau json export'
|
2017-10-13 19:25:31 +02:00
|
|
|
|
|
|
|
def add_arguments(self, parser):
|
|
|
|
parser.add_argument('path', type=str)
|
|
|
|
|
|
|
|
@transaction.atomic
|
|
|
|
def handle(self, *args, **options):
|
|
|
|
path = options.get('path')
|
|
|
|
with open(path) as export:
|
|
|
|
data = json.load(export)
|
|
|
|
|
2017-10-14 11:57:20 +02:00
|
|
|
import_members(data['members'])
|
2018-01-13 13:44:57 +01:00
|
|
|
# _import_real_transactions(data['unresolved_bank_transactions'])
|