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

161

162

163

164

165

166

167

168

169

#!/usr/bin/python 

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

 

import csv 

import datetime 

import json 

import logging 

 

from django.conf import settings 

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 

NO_CHAOS_NUMBER = None 

 

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() 

verwendungszweck = models.TextField() 

payer = models.CharField(max_length=80) 

payee = models.CharField(max_length=80) 

amount = models.IntegerField() 

balance = models.IntegerField() 

chaos_number = models.PositiveIntegerField(blank=True, null=True) 

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.chaos_number)) 

self.chaos_number = Transaction.NO_CHAOS_NUMBER 

53 ↛ 54line 53 didn't jump to line 54, because the condition on line 53 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, max_chaos_nr=pow(2, 63) - 1): 

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

58 ↛ 59line 58 didn't jump to line 59, because the condition on line 58 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('chaos_number') 

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

raise NegativeCreditError 

for key in shared_keys: 

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

 

try: 

70 ↛ 73line 70 didn't jump to line 73, because the condition on line 70 was never false if int(transaction_reader.chaos_number[0]) <= max_chaos_nr: 

self.chaos_number = int(transaction_reader.chaos_number[0]) 

else: 

self.handle_chaos_nr_error() 

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

75 ↛ 76line 75 didn't jump to line 76, because the exception caught by line 75 didn't happen except ValueError as e: 

logging.warning("Value Error {}".format(e)) 

self.handle_chaos_nr_error() 

78 ↛ 79line 78 didn't jump to line 79, because the exception caught by line 78 didn't happen except TypeError as e: 

logging.warning("Type Error {}".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.chaos_number, e)) 

raise DuplicateBankingWarning 

 

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

self.book() 

 

def __str__(self): 

return str(self.chaos_number) 

 

def book(self): 

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

raise TransactionNotPendingError 

try: 

mem_match = Member.objects.get(pk=self.chaos_number) 

mem_match.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 Member.objects.get(pk=self.chaos_number) 

 

 

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')