Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

#!/usr/bin/python 

# -*- coding: utf-8 -*- 

 

import csv 

import datetime 

import json 

import logging 

 

from django.db import IntegrityError, models 

from import_app.csv_parser import TransactionReader 

from members.models import BalanceTransactionLog, Member 

 

logging.getLogger(__name__) 

logging.getLogger().setLevel(logging.INFO) 

 

 

class Transaction(models.Model): 

RATING_BAD = 64 

 

STATUS_MATCHED_CHAOS_NR = 'M' 

STATUS_UNKNOWN_CHAOS_NR = 'U' 

STATUS_COMPLETED = 'C' 

STATUS_IGNOREFOREVER = 'I' 

STATUS_FAILED = 'F' 

# code blaming: symbols on classes _pending [fixed] 

STATUS_OPTIONS = ((STATUS_MATCHED_CHAOS_NR, 'matched chaos nr'), 

(STATUS_UNKNOWN_CHAOS_NR, 'unknown chaos nr'), 

(STATUS_COMPLETED, 'completed'), 

(STATUS_IGNOREFOREVER, 'ignore forever'), 

(STATUS_FAILED, 'failed at booking')) 

string_in = models.CharField(max_length=1000, unique=True) 

booking_day = models.DateField(null=False, default=datetime.date.today) 

available_on = models.DateField(null=False, default=datetime.date.today) 

payment_type = models.CharField(max_length=80) 

information = models.TextField() 

referenz = models.TextField(blank=True) 

verwendungszweck = models.TextField() 

payer = models.CharField(max_length=80) 

payee = models.CharField(max_length=80) 

amount = models.IntegerField() 

balance = models.IntegerField() 

member = models.ForeignKey(Member, blank=True, null=True, on_delete=models.CASCADE) 

chaosnr_options = models.CharField(max_length=200, blank=True, null=True) 

rating = models.PositiveSmallIntegerField() 

status = models.CharField(max_length=1, choices=STATUS_OPTIONS) 

 

def handle_chaos_nr_error(self): 

self.rating = Transaction.RATING_BAD 

logging.warning("pre wipe chaos number {}".format(self.member)) 

self.member = None 

51 ↛ 52line 51 didn't jump to line 52, because the condition on line 51 was never true if self.status == self.STATUS_MATCHED_CHAOS_NR: 

self.status = self.STATUS_UNKNOWN_CHAOS_NR 

 

def add_transaction(self, transaction_reader, threshold=15): 

# TODO: check for addition of identical transactions (transaction ID? whole entry?) 

56 ↛ 57line 56 didn't jump to line 57, because the condition on line 56 was never true if not isinstance(transaction_reader, TransactionReader): 

raise ValueError() 

transaction_fields = self._meta.get_fields() 

transaction_fields = [x.name for x in transaction_fields] 

shared_keys = set(transaction_fields) & set(transaction_reader.__dict__.keys()) 

shared_keys.remove('member') 

62 ↛ 63line 62 didn't jump to line 63, because the condition on line 62 was never true if transaction_reader.amount <= 0: 

raise NegativeCreditError 

for key in shared_keys: 

setattr(self, key, getattr(transaction_reader, key)) 

 

try: 

self.member = Member.objects.get(chaos_number=transaction_reader.member[0]) 

self.chaosnr_options = json.dumps(transaction_reader.member[1:]) 

70 ↛ 71line 70 didn't jump to line 71, because the exception caught by line 70 didn't happen except Member.DoesNotExist as e: 

logging.warning("Chaos number does not exist: {}".format(e)) 

self.handle_chaos_nr_error() 

except IndexError as e: 

logging.warning("Index Error: {}".format(e)) 

self.handle_chaos_nr_error() 

 

if transaction_reader.donation: 

self.status = self.STATUS_IGNOREFOREVER 

elif self.rating < threshold: 

self.status = self.STATUS_MATCHED_CHAOS_NR 

else: 

self.status = self.STATUS_UNKNOWN_CHAOS_NR 

try: 

self.save() 

except IntegrityError as e: 

logging.warning('db integrity constraint failed, unique transaction for: {}\n{}?'.format(self.member, e)) 

raise DuplicateBankingWarning 

 

89 ↛ 90line 89 didn't jump to line 90, because the condition on line 89 was never true if self.status == self.STATUS_MATCHED_CHAOS_NR and str(self.member.chaos_number) == self.verwendungszweck: 

self.book() 

 

def __str__(self): 

return str(self.member) 

 

def book(self): 

96 ↛ 97line 96 didn't jump to line 97, because the condition on line 96 was never true if not self.status == Transaction.STATUS_MATCHED_CHAOS_NR: 

raise TransactionNotPendingError 

try: 

self.member.increase_balance_by(increase_by=self.amount, 

reason=BalanceTransactionLog.IMPORT_BANKING, 

comment=self.information, 

booking_day=self.booking_day, 

bank_transaction=self) 

self.status = Transaction.STATUS_COMPLETED 

except Exception as e: 

self.status = Transaction.STATUS_FAILED 

logging.warning(e) 

self.save() 

 

def get_expected_member(self): 

return self.member 

 

 

class TransactionNotPendingError(Exception): 

pass 

 

 

class DuplicateBankingWarning(Warning): 

pass 

 

 

class NegativeCreditError(Exception): 

pass 

 

# TODO: discard 'Spende' & fee transactions that cannot be resolved 

 

# TODO: Exception on Doppelmitgliedschaft and individual payment 

# TODO: dont accept payment when membership end date set 

 

 

def pseudotest(): 

csv_in = open("../testdata/PB_Umsatzauskunft_KtoNr0599090201_01-08-2015_2007.csv", encoding="iso-8859-1").readlines() 

csv_in = csv.reader(csv_in, delimiter=';') 

for item in csv_in: 

print("---") 

if len(item) == 8: 

tr = TransactionReader(item) 

if tr.income: 

try: 

new_tr = Transaction() 

new_tr.add_transaction(tr) 

except Exception as e: 

print(e) 

 

 

# TODO: import app: (multiline) 

# name anzeigen 

# lower confidence at least for present year 

# pagination!! 

# pending for every number after a key 

# lower confidence for back2back numbers e.g. chaos nr. 123 4 

# vergleich mit datenbank before manual checking 

# comment field zu 1024 

# spende raus/third color? 

# will be paid until 

# status ausblenden (failed/completed) 

# umnennen erroneous -> unknown, pending -> match 

# clean up debug output 

# linking in with navigation 

# failed transactions wieder anzeigen (by 'OK')