Coverage for jbank/aeb43.py: 0%
81 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-27 13:36 +0700
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-27 13:36 +0700
1from typing import Tuple, List, Optional, Dict, Any, Union
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
8AEB43_STATEMENT_SUFFIXES = ["TXT", "AEB43"]
10DEBIT_REC_TYPE = "1" # 1=debit, 2=credit
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 ("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]
27ACCOUNT_HEADER_DATES = ["initial_date", "final_date"]
28ACCOUNT_HEADER_DECIMALS = [("initial_balance", "initial_balance_debit_or_credit_code")]
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 ("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]
45TRANSACTION_DATES = ["transaction_date", "value_date"]
46TRANSACTION_DECIMALS = [("amount", "debit_or_credit_code")]
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]
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", "X(3)", "P"),
59 ("amount", "X(14)", "P"),
60 ("free", "X(59)", "P"),
61]
63AMOUNT_EQUIVALENCE_DECIMALS = [("amount", "data_code")]
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_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]
80ACCOUNT_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]
86END_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]
94def 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
105def 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
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]
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)
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