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

1from typing import Tuple, List, Optional, Dict, Any 

2from django.core.exceptions import ValidationError 

3from django.utils.translation import gettext as _ 

4from pytz import timezone 

5import os 

6from jbank.parsers import parse_filename_suffix, parse_records, convert_date_fields, convert_decimal_fields 

7 

8AEB43_STATEMENT_SUFFIXES = ["TXT", "AEB43"] 

9 

10DEBIT_REC_TYPE = "1" # 1=debit, 2=credit 

11 

12ACCOUNT_HEADER_RECORD: List[Tuple[str, str, str]] = [ 

13 ("registration_code", "9(2)", "P"), # 11 

14 ("entity_key", "X(4)", "P"), 

15 ("office_key", "X(4)", "P"), 

16 ("account_number", "X(10)", "P"), 

17 ("initial_date", "9(6)", "P"), 

18 ("final_date", "9(6)", "P"), 

19 ("debt_or_credit_code", "9(1)", "P"), 

20 ("initial_balance_amount", "X(14)", "P"), 

21 ("currency_key", "X(3)", "P"), 

22 ("information_mode", "X(1)", "P"), 

23 ("name", "X(26)", "P"), 

24 ("free", "X(3)", "P"), 

25] 

26 

27ACCOUNT_HEADER_DATES = ["final_date", "initial_date"] 

28ACCOUNT_HEADER_DECIMALS = [("initial_balance_amount", "debt_or_credit_code")] 

29 

30TRANSACTION_RECORD: List[Tuple[str, str, str]] = [ 

31 ("registration_code", "9(2)", "P"), # 22 

32 ("free", "X(4)", "P"), 

33 ("origin_office_code", "X(4)", "P"), 

34 ("date_of_transaction", "X(6)", "P"), 

35 ("value_date", "X(6)", "P"), 

36 ("common_concept", "X(2)", "P"), 

37 ("own_concept", "X(3)", "P"), 

38 ("debt_or_credit_code", "X(1)", "P"), # 1=debit, 2=credit 

39 ("amount", "X(14)", "P"), # cents, left-padded with zeros 

40 ("document_number", "X(10)", "P"), 

41 ("reference_1", "X(12)", "P"), 

42 ("reference_2", "X(16)", "P"), 

43] 

44 

45TRANSACTION_DATES = ["date_of_transaction", "value_date"] 

46TRANSACTION_DECIMALS = [("amount", "debt_or_credit_code")] 

47 

48CONCEPT_RECORD: List[Tuple[str, str, str]] = [ 

49 ("registration_code", "9(2)", "P"), # 23 

50 ("data_code", "X(2)", "P"), 

51 ("concept", "X(38)", "P"), 

52 ("concept", "X(38)", "P"), 

53] 

54 

55AMOUNT_EQUIVALENCE_RECORD: List[Tuple[str, str, str]] = [ 

56 ("registration_code", "9(2)", "P"), # 24 

57 ("data_code", "X(2)", "P"), 

58 ("currency_key_origin_of_the_movement", "X(3)", "P"), 

59 ("amount", "X(14)", "P"), 

60 ("free", "X(59)", "P"), 

61] 

62 

63AMOUNT_EQUIVALENCE_DECIMALS = [("amount", "debt_or_credit_code")] 

64 

65ACCOUNT_SUMMARY_RECORD: List[Tuple[str, str, str]] = [ 

66 ("registration_code", "9(2)", "P"), # 33 

67 ("entity_key", "X(4)", "P"), 

68 ("office_key", "X(4)", "P"), 

69 ("account_number", "X(10)", "P"), 

70 ("no_of_notes_must", "X(5)", "P"), 

71 ("total_amounts_debit", "X(14)", "P"), 

72 ("no_of_notes_to_have", "X(5)", "P"), 

73 ("total_amounts_credit", "X(14)", "P"), 

74 ("ending_balance_code", "X(1)", "P"), 

75 ("final_balance", "X(14)", "P"), 

76 ("currency_code", "X(3)", "P"), 

77 ("free", "X(4)", "P"), 

78] 

79 

80ACCOUNT_SUMMARY_DECIMALS = ["final_balance", "total_amounts_debit", "total_amounts_credit"] 

81 

82END_OF_FILE_RECORD: List[Tuple[str, str, str]] = [ 

83 ("registration_code", "9(2)", "P"), # 88 

84 ("nine", "X(18)", "P"), 

85 ("no_of_records", "X(6)", "P"), 

86 ("free", "X(54)", "P"), 

87] 

88 

89 

90def parse_aeb43_statements_from_file(filename: str) -> list: 

91 if parse_filename_suffix(filename).upper() not in AEB43_STATEMENT_SUFFIXES: 

92 raise ValidationError( 

93 _('File {filename} has unrecognized ({suffixes}) suffix for file type "{file_type}"').format( 

94 filename=filename, suffixes=", ".join(AEB43_STATEMENT_SUFFIXES), file_type="AEB43" 

95 ) 

96 ) 

97 with open(filename, "rt", encoding="UTF-8") as fp: 

98 return parse_aeb43_statements(fp.read(), filename=os.path.basename(filename)) # type: ignore 

99 

100 

101def parse_aeb43_statements(content: str, filename: str) -> list: # pylint: disable=too-many-locals,unused-argument 

102 lines = content.split("\n") 

103 nlines = len(lines) 

104 line_number = 0 

105 tz = timezone("Europe/Madrid") 

106 batches: List[dict] = [] 

107 header: Optional[Dict[str, Any]] = None 

108 records: List[Dict[str, Any]] = [] 

109 summary: Optional[Dict[str, Any]] = None 

110 eof: Optional[Dict[str, Any]] = None 

111 rec_count = 0 

112 

113 while line_number < nlines: 

114 line_number += 1 

115 line = lines[line_number - 1] 

116 if line.strip() == "": 

117 line_number += 1 

118 continue 

119 record_type = line[:2] 

120 

121 if record_type == "11": 

122 header = parse_records(lines[line_number - 1], ACCOUNT_HEADER_RECORD, line_number=line_number) 

123 convert_date_fields(header, ACCOUNT_HEADER_DATES, tz) 

124 convert_decimal_fields(header, ACCOUNT_HEADER_DECIMALS, DEBIT_REC_TYPE) 

125 rec_count += 1 

126 elif record_type == "33": 

127 summary = parse_records(lines[line_number - 1], ACCOUNT_SUMMARY_RECORD, line_number=line_number) 

128 convert_decimal_fields(summary, ACCOUNT_SUMMARY_DECIMALS, DEBIT_REC_TYPE) 

129 batches.append({"header": header, "records": records, "summary": summary}) 

130 records = [] 

131 header = summary = None 

132 rec_count += 1 

133 elif record_type == "22": 

134 tx_rec = parse_records(lines[line_number - 1], TRANSACTION_RECORD, line_number=line_number) 

135 convert_date_fields(tx_rec, TRANSACTION_DATES, tz) 

136 convert_decimal_fields(tx_rec, TRANSACTION_DECIMALS, DEBIT_REC_TYPE) 

137 records.append(tx_rec) 

138 rec_count += 1 

139 elif record_type == "23": 

140 sub_rec = parse_records(lines[line_number - 1], CONCEPT_RECORD, line_number=line_number) 

141 prev = records[len(records) - 1] 

142 prev.setdefault("concept_records", []) 

143 prev["concept_records"].append(sub_rec) # type: ignore 

144 rec_count += 1 

145 elif record_type == "24": 

146 sub_rec = parse_records(lines[line_number - 1], AMOUNT_EQUIVALENCE_RECORD, line_number=line_number) 

147 convert_decimal_fields(sub_rec, AMOUNT_EQUIVALENCE_DECIMALS, DEBIT_REC_TYPE) 

148 prev = records[len(records) - 1] 

149 prev.setdefault("amount_equivalence_records", []) 

150 prev["amount_equivalence_records"].append(sub_rec) # type: ignore 

151 rec_count += 1 

152 elif record_type == "88": 

153 eof = parse_records(lines[line_number - 1], END_OF_FILE_RECORD, line_number=line_number) 

154 

155 if eof is None: 

156 raise ValidationError(_("EOF record missing")) 

157 if int(eof["no_of_records"]) != rec_count: 

158 raise ValidationError(_("Number of records does not match EOF record")) 

159 return batches