Coverage for jbank/aeb43.py: 0%

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

2 from django.core.exceptions import ValidationError

3 from django.utils.translation import gettext as _

4 from pytz import timezone

5 import os

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

7

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

9

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

11

12 ACCOUNT_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 ("initial_balance_debit_or_credit_code", "9(1)", "P"), # 1=debit, 2=credit

20 ("initial_balance", "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

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

28 ACCOUNT_HEADER_DECIMALS = [("initial_balance", "initial_balance_debit_or_credit_code")]

29

30 TRANSACTION_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 ("transaction_date", "X(6)", "P"),

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

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

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

38 ("debit_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

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

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

47

48 CONCEPT_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

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

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

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

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

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

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

61]

62

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

64

65 ACCOUNT_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_amount_debits", "X(14)", "P"),

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

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

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

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

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

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

78]

79

80 ACCOUNT_SUMMARY_DECIMALS: List[Union[Tuple[str, str], str]] = [

81 ("final_balance", "final_balance_debit_or_credit_code"),

82 "total_amount_credits",

83 "total_amount_debits",

84]

85

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

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

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

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

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

91]

92

93

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

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

96 raise ValidationError(

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

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

99 )

100 )

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

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

103

104

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

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

107 nlines = len(lines)

108 line_number = 0

109 tz = timezone("Europe/Madrid")

110 batches: List[dict] = []

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

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

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

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

115 rec_count = 0

116

117 while line_number < nlines:

118 line_number += 1

119 line = lines[line_number - 1]

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

121 line_number += 1

122 continue

123 record_type = line[:2]

124

125 if record_type == "11":

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

127 convert_date_fields(header, ACCOUNT_HEADER_DATES, tz)

128 convert_decimal_fields(header, ACCOUNT_HEADER_DECIMALS, DEBIT_REC_TYPE)

129 rec_count += 1

130 elif record_type == "33":

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

132 convert_decimal_fields(summary, ACCOUNT_SUMMARY_DECIMALS, DEBIT_REC_TYPE)

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

134 records = []

135 header = summary = None

136 rec_count += 1

137 elif record_type == "22":

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

139 convert_date_fields(tx_rec, TRANSACTION_DATES, tz)

140 convert_decimal_fields(tx_rec, TRANSACTION_DECIMALS, DEBIT_REC_TYPE)

141 records.append(tx_rec)

142 rec_count += 1

143 elif record_type == "23":

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

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

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

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

148 rec_count += 1

149 elif record_type == "24":

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

151 convert_decimal_fields(sub_rec, AMOUNT_EQUIVALENCE_DECIMALS, DEBIT_REC_TYPE)

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

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

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

155 rec_count += 1

156 elif record_type == "88":

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

158

159 if eof is None:

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

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

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

163 return batches