chellow 1755614564.0.0__py3-none-any.whl → 1759155233.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of chellow might be problematic. Click here for more details.

Files changed (75) hide show
  1. chellow/e/bill_importer.py +136 -80
  2. chellow/e/bill_parsers/activity_mop_stark_xlsx.py +99 -86
  3. chellow/e/bill_parsers/annual_mop_stark_xlsx.py +78 -61
  4. chellow/e/bill_parsers/csv.py +139 -101
  5. chellow/e/bill_parsers/drax_edi.py +65 -88
  6. chellow/e/bill_parsers/engie_edi.py +187 -255
  7. chellow/e/bill_parsers/engie_xls.py +153 -167
  8. chellow/e/bill_parsers/haven_edi.py +189 -228
  9. chellow/e/bill_parsers/haven_edi_tprs.py +67 -67
  10. chellow/e/bill_parsers/nonsettlement_dc_stark_xlsx.py +75 -66
  11. chellow/e/bill_parsers/settlement_dc_stark_xlsx.py +229 -126
  12. chellow/e/bill_parsers/sse_edi.py +107 -75
  13. chellow/e/bill_parsers/sww_xls.py +78 -91
  14. chellow/e/computer.py +1 -1
  15. chellow/e/views.py +626 -281
  16. chellow/edi_lib.py +4 -27
  17. chellow/models.py +92 -3
  18. chellow/reports/report_111.py +478 -616
  19. chellow/reports/report_247.py +96 -137
  20. chellow/templates/e/dc_batch.html +110 -157
  21. chellow/templates/e/dc_batch_add.html +2 -3
  22. chellow/templates/e/dc_batch_edit.html +42 -46
  23. chellow/templates/e/dc_batch_file.html +2 -3
  24. chellow/templates/e/dc_batch_file_edit.html +28 -40
  25. chellow/templates/e/dc_batch_upload_file.html +68 -0
  26. chellow/templates/e/dc_batches.html +2 -1
  27. chellow/templates/e/dc_batches_edit.html +26 -0
  28. chellow/templates/e/dc_bill.html +27 -5
  29. chellow/templates/e/dc_bill_add.html +4 -4
  30. chellow/templates/e/dc_bill_edit.html +43 -63
  31. chellow/templates/e/dc_bill_import.html +1 -1
  32. chellow/templates/e/dc_bill_import_contract.html +130 -0
  33. chellow/templates/e/dc_contract.html +1 -1
  34. chellow/templates/e/dc_element.html +41 -0
  35. chellow/templates/e/dc_element_add.html +36 -0
  36. chellow/templates/e/dc_element_edit.html +49 -0
  37. chellow/templates/e/dc_rate_script_edit.html +27 -43
  38. chellow/templates/e/mop_batch.html +105 -152
  39. chellow/templates/e/mop_batch_add.html +2 -3
  40. chellow/templates/e/mop_batch_edit.html +43 -51
  41. chellow/templates/e/mop_batch_upload_file.html +71 -5
  42. chellow/templates/e/mop_batches.html +2 -1
  43. chellow/templates/e/mop_batches_edit.html +26 -0
  44. chellow/templates/e/mop_bill.html +31 -8
  45. chellow/templates/e/mop_bill_add.html +7 -27
  46. chellow/templates/e/mop_bill_import.html +1 -1
  47. chellow/templates/e/mop_bill_import_contract.html +130 -0
  48. chellow/templates/e/mop_contract.html +4 -5
  49. chellow/templates/e/mop_element.html +41 -0
  50. chellow/templates/e/mop_element_add.html +36 -0
  51. chellow/templates/e/mop_element_edit.html +49 -0
  52. chellow/templates/e/supplier_batch.html +3 -7
  53. chellow/templates/e/supplier_batch_add.html +2 -2
  54. chellow/templates/e/supplier_batch_edit.html +1 -1
  55. chellow/templates/e/supplier_batch_file.html +3 -5
  56. chellow/templates/e/supplier_batch_file_add.html +18 -11
  57. chellow/templates/e/supplier_batch_upload_file.html +83 -9
  58. chellow/templates/e/supplier_batches.html +4 -4
  59. chellow/templates/e/supplier_batches_edit.html +26 -0
  60. chellow/templates/e/supplier_bill.html +29 -6
  61. chellow/templates/e/supplier_bill_add.html +3 -3
  62. chellow/templates/e/supplier_bill_import.html +1 -1
  63. chellow/templates/e/supplier_bill_import_contract.html +118 -0
  64. chellow/templates/e/supplier_contract.html +1 -1
  65. chellow/templates/e/supplier_element.html +45 -0
  66. chellow/templates/e/supplier_element_add.html +36 -0
  67. chellow/templates/e/supplier_element_edit.html +51 -0
  68. chellow/templates/report_run_bill_check.html +137 -179
  69. chellow/templates/report_run_row_bill_check.html +182 -179
  70. chellow/views.py +55 -65
  71. {chellow-1755614564.0.0.dist-info → chellow-1759155233.0.0.dist-info}/METADATA +2 -2
  72. {chellow-1755614564.0.0.dist-info → chellow-1759155233.0.0.dist-info}/RECORD +73 -60
  73. chellow/e/bill_parsers/drax_element_edi.py +0 -459
  74. chellow/templates/e/supplier_bill_imports.html +0 -421
  75. {chellow-1755614564.0.0.dist-info → chellow-1759155233.0.0.dist-info}/WHEEL +0 -0
@@ -5,6 +5,8 @@ import traceback
5
5
  from io import BytesIO
6
6
  from pkgutil import iter_modules
7
7
 
8
+ from sqlalchemy import select
9
+
8
10
  from werkzeug.exceptions import BadRequest
9
11
 
10
12
  import chellow
@@ -13,12 +15,13 @@ from chellow.models import (
13
15
  Batch,
14
16
  BatchFile,
15
17
  BillType,
18
+ Contract,
16
19
  ReadType,
17
20
  Session,
18
21
  Supply,
19
22
  Tpr,
20
23
  )
21
- from chellow.utils import keydefaultdict, utc_datetime_now
24
+ from chellow.utils import ct_datetime_now, keydefaultdict
22
25
 
23
26
 
24
27
  import_id = 0
@@ -31,24 +34,31 @@ def find_parser_names():
31
34
 
32
35
 
33
36
  class BillImport(threading.Thread):
34
- def __init__(self, batch):
37
+ def __init__(self, batch_contract, now=None):
35
38
  threading.Thread.__init__(self)
36
39
  global import_id
37
40
  self.import_id = import_id
38
41
  import_id += 1
39
42
 
40
- self.batch_id = batch.id
43
+ if isinstance(batch_contract, Batch):
44
+ self.contract_id = None
45
+ self.batch_id = batch_contract.id
46
+ elif isinstance(batch_contract, Contract):
47
+ self.contract_id = batch_contract.id
48
+ self.batch_id = None
49
+ else:
50
+ raise BadRequest("batch_contract must be a Batch or Contract.")
41
51
  self.successful_bills = []
42
52
  self.failed_bills = []
43
53
  self.log = collections.deque()
44
54
  self.bill_num = None
45
55
  self.parser = None
56
+ self.now = now
46
57
 
47
58
  def _log(self, msg):
48
59
  with import_lock:
49
- self.log.appendleft(
50
- f"{utc_datetime_now().strftime('%Y-%m-%d %H:%M:%S')} - {msg}"
51
- )
60
+ ts = ct_datetime_now() if self.now is None else self.now
61
+ self.log.appendleft(f"{ts.strftime('%Y-%m-%d %H:%M:%S')} - {msg}")
52
62
 
53
63
  def status(self):
54
64
  if self.is_alive():
@@ -65,85 +75,117 @@ class BillImport(threading.Thread):
65
75
 
66
76
  def run(self):
67
77
  try:
78
+ batch_ids = []
68
79
  with Session() as sess:
69
- batch = Batch.get_by_id(sess, self.batch_id)
70
-
71
- bill_types = keydefaultdict(lambda k: BillType.get_by_code(sess, k))
80
+ batch_q = select(Batch).order_by(Batch.reference)
81
+ if self.batch_id is not None:
82
+ batch = Batch.get_by_id(sess, self.batch_id)
83
+ batch_q = batch_q.where(Batch.id == self.batch_id)
84
+ elif self.contract_id is not None:
85
+ contract = Contract.get_by_id(sess, self.contract_id)
86
+ batch_q = batch_q.where(Batch.contract == contract)
87
+ for batch in sess.scalars(batch_q):
88
+ batch_ids.append(batch.id)
89
+
90
+ for batch_id in batch_ids:
91
+ with Session() as sess:
92
+ batch = Batch.get_by_id(sess, batch_id)
93
+ self._log(f"Importing bills from batch {batch.reference}.")
94
+ bf_q = (
95
+ select(BatchFile)
96
+ .where(BatchFile.batch == batch)
97
+ .order_by(BatchFile.upload_timestamp)
98
+ )
99
+ bill_types = keydefaultdict(lambda k: BillType.get_by_code(sess, k))
72
100
 
73
- tprs = keydefaultdict(
74
- lambda k: None if k is None else Tpr.get_by_code(sess, k)
75
- )
101
+ tprs = keydefaultdict(
102
+ lambda k: None if k is None else Tpr.get_by_code(sess, k)
103
+ )
76
104
 
77
- read_types = keydefaultdict(lambda k: ReadType.get_by_code(sess, k))
78
-
79
- for bf in (
80
- sess.query(BatchFile)
81
- .filter(BatchFile.batch == batch)
82
- .order_by(BatchFile.upload_timestamp)
83
- ):
84
- self.parser = _process_batch_file(sess, bf, self._log)
85
- for self.bill_num, raw_bill in enumerate(
86
- self.parser.make_raw_bills()
87
- ):
88
- if "error" in raw_bill:
89
- self.failed_bills.append(raw_bill)
90
- else:
91
- try:
92
- mpan_core = raw_bill["mpan_core"]
93
- supply = Supply.get_by_mpan_core(sess, mpan_core)
94
- with sess.begin_nested():
95
- bill = batch.insert_bill(
96
- sess,
97
- raw_bill["account"],
98
- raw_bill["reference"],
99
- raw_bill["issue_date"],
100
- raw_bill["start_date"],
101
- raw_bill["finish_date"],
102
- raw_bill["kwh"],
103
- raw_bill["net"],
104
- raw_bill["vat"],
105
- raw_bill["gross"],
106
- bill_types[raw_bill["bill_type_code"]],
107
- raw_bill["breakdown"],
108
- supply,
109
- )
110
- for raw_read in raw_bill["reads"]:
111
- bill.insert_read(
105
+ read_types = keydefaultdict(lambda k: ReadType.get_by_code(sess, k))
106
+
107
+ for bf in sess.scalars(bf_q):
108
+ self.parser = _process_batch_file(sess, bf, self._log)
109
+ for self.bill_num, raw_bill in enumerate(
110
+ self.parser.make_raw_bills()
111
+ ):
112
+ batch = bf.batch
113
+ sum_elem = sum(el["net"] for el in raw_bill["elements"])
114
+ raw_bill_net = raw_bill["net"]
115
+ if sum_elem != raw_bill_net:
116
+ raw_bill["error"] = (
117
+ f"The sum of the elements' net {sum_elem} doesn't "
118
+ f"equal the bill net {raw_bill_net}."
119
+ )
120
+ if "error" in raw_bill:
121
+ self.failed_bills.append(raw_bill)
122
+ else:
123
+ try:
124
+ mpan_core = raw_bill["mpan_core"]
125
+ supply = Supply.get_by_mpan_core(sess, mpan_core)
126
+ with sess.begin_nested():
127
+ bill = batch.insert_bill(
112
128
  sess,
113
- tprs[raw_read["tpr_code"]],
114
- raw_read["coefficient"],
115
- raw_read["units"],
116
- raw_read["msn"],
117
- raw_read["mpan"],
118
- raw_read["prev_date"],
119
- raw_read["prev_value"],
120
- read_types[raw_read["prev_type_code"]],
121
- raw_read["pres_date"],
122
- raw_read["pres_value"],
123
- read_types[raw_read["pres_type_code"]],
129
+ raw_bill["account"],
130
+ raw_bill["reference"],
131
+ raw_bill["issue_date"],
132
+ raw_bill["start_date"],
133
+ raw_bill["finish_date"],
134
+ raw_bill["kwh"],
135
+ raw_bill_net,
136
+ raw_bill["vat"],
137
+ raw_bill["gross"],
138
+ bill_types[raw_bill["bill_type_code"]],
139
+ raw_bill["breakdown"],
140
+ supply,
124
141
  )
125
- self.successful_bills.append(raw_bill)
126
- except KeyError as e:
127
- err = raw_bill.get("error", "")
128
- raw_bill["error"] = err + " " + str(e)
129
- self.failed_bills.append(raw_bill)
130
- except BadRequest as e:
131
- raw_bill["error"] = str(e.description)
132
- self.failed_bills.append(raw_bill)
133
-
134
- if len(self.failed_bills) == 0:
135
- sess.commit()
136
- self._log(
137
- "All the bills have been successfully loaded and attached "
138
- "to the batch."
139
- )
140
- else:
141
- sess.rollback()
142
- self._log(
143
- f"The import has finished, but there were "
144
- f"{len(self.failed_bills)} "
145
- f"failures, and so the whole import has been rolled back."
146
- )
142
+ for raw_element in raw_bill["elements"]:
143
+ bill.insert_element(
144
+ sess,
145
+ raw_element["name"],
146
+ raw_element["start_date"],
147
+ raw_element["finish_date"],
148
+ raw_element["net"],
149
+ raw_element["breakdown"],
150
+ )
151
+ for raw_read in raw_bill["reads"]:
152
+ bill.insert_read(
153
+ sess,
154
+ tprs[raw_read["tpr_code"]],
155
+ raw_read["coefficient"],
156
+ raw_read["units"],
157
+ raw_read["msn"],
158
+ raw_read["mpan"],
159
+ raw_read["prev_date"],
160
+ raw_read["prev_value"],
161
+ read_types[raw_read["prev_type_code"]],
162
+ raw_read["pres_date"],
163
+ raw_read["pres_value"],
164
+ read_types[raw_read["pres_type_code"]],
165
+ )
166
+ self.successful_bills.append(raw_bill)
167
+ except KeyError as e:
168
+ err = raw_bill.get("error", "")
169
+ raw_bill["error"] = err + " " + str(e)
170
+ self.failed_bills.append(raw_bill)
171
+ except BadRequest as e:
172
+ raw_bill["error"] = str(e.description)
173
+ self.failed_bills.append(raw_bill)
174
+
175
+ if len(self.failed_bills) == 0:
176
+ sess.commit()
177
+ self._log(
178
+ "All the bills have been successfully loaded and attached "
179
+ "to the batch."
180
+ )
181
+ else:
182
+ sess.rollback()
183
+ self._log(
184
+ f"The import has finished, but there were "
185
+ f"{len(self.failed_bills)} "
186
+ f"failures, and so the whole import has been rolled back."
187
+ )
188
+ break
147
189
 
148
190
  except BadRequest as e:
149
191
  msg = f"Problem: {e.description}"
@@ -193,11 +235,25 @@ def start_bill_import(batch):
193
235
  return bi.import_id
194
236
 
195
237
 
238
+ def start_bill_import_contract(contract):
239
+ with import_lock:
240
+ bi = BillImport(contract)
241
+ imports[bi.import_id] = bi
242
+ bi.start()
243
+
244
+ return bi.import_id
245
+
246
+
196
247
  def get_bill_import_ids(batch):
197
248
  with import_lock:
198
249
  return [k for k, v in imports.items() if v.batch_id == batch.id]
199
250
 
200
251
 
252
+ def get_bill_import_ids_contract(contract):
253
+ with import_lock:
254
+ return [k for k, v in imports.items() if v.contract_id == contract.id]
255
+
256
+
201
257
  def get_bill_import(id):
202
258
  with import_lock:
203
259
  return imports[id]
@@ -5,7 +5,99 @@ from openpyxl import load_workbook
5
5
 
6
6
  from werkzeug.exceptions import BadRequest
7
7
 
8
- from chellow.utils import parse_mpan_core, to_utc
8
+ from chellow.utils import parse_mpan_core, to_ct, to_utc
9
+
10
+
11
+ def get_cell(sheet, col, row):
12
+ try:
13
+ coordinates = f"{col}{row}"
14
+ return sheet[coordinates]
15
+ except IndexError:
16
+ raise BadRequest(f"Can't find the cell {coordinates} on sheet {sheet}.")
17
+
18
+
19
+ def get_int(sheet, col, row):
20
+ return int(get_cell(sheet, col, row).value)
21
+
22
+
23
+ def get_dec(sheet, col, row):
24
+ cell = get_cell(sheet, col, row)
25
+ try:
26
+ return Decimal(str(cell.value))
27
+ except InvalidOperation as e:
28
+ raise BadRequest(f"Problem parsing the number at {cell.coordinate}. {e}")
29
+
30
+
31
+ def get_str(sheet, col, row):
32
+ return get_cell(sheet, col, row).value.strip()
33
+
34
+
35
+ def get_ct_date(sheet, col, row):
36
+ cell = get_cell(sheet, col, row)
37
+ val = cell.value
38
+ if isinstance(val, Datetime):
39
+ dt = val
40
+ elif isinstance(val, str):
41
+ dt = Datetime.strptime(val, "dd/mm/yyyy")
42
+ else:
43
+ raise BadRequest(
44
+ f"The value {val} at {cell.coordinate} is of type {type(val)}, but "
45
+ f"expected a timestamp or string."
46
+ )
47
+ return to_ct(dt)
48
+
49
+
50
+ def _process_row(issue_date, sheet, row):
51
+ mpan_core = parse_mpan_core(str(get_int(sheet, "B", row)))
52
+
53
+ start_date = finish_date = to_utc(get_ct_date(sheet, "F", row))
54
+ activity_name_raw = get_str(sheet, "G", row)
55
+ activity_name = activity_name_raw.lower().replace(" ", "_")
56
+
57
+ net_dec = get_dec(sheet, "I", row)
58
+ net = round(net_dec, 2)
59
+
60
+ vat_dec = get_dec(sheet, "J", row)
61
+ vat = round(vat_dec, 2)
62
+
63
+ gross_dec = get_dec(sheet, "K", row)
64
+ gross = round(gross_dec, 2)
65
+
66
+ breakdown = {
67
+ "raw-lines": [],
68
+ }
69
+
70
+ return {
71
+ "bill_type_code": "N",
72
+ "kwh": Decimal(0),
73
+ "vat": vat,
74
+ "net": net,
75
+ "gross": gross,
76
+ "reads": [],
77
+ "breakdown": breakdown,
78
+ "account": mpan_core,
79
+ "issue_date": issue_date,
80
+ "start_date": start_date,
81
+ "finish_date": finish_date,
82
+ "mpan_core": mpan_core,
83
+ "reference": "_".join(
84
+ (
85
+ start_date.strftime("%Y%m%d"),
86
+ finish_date.strftime("%Y%m%d"),
87
+ issue_date.strftime("%Y%m%d"),
88
+ mpan_core,
89
+ )
90
+ ),
91
+ "elements": [
92
+ {
93
+ "name": "activity",
94
+ "start_date": start_date,
95
+ "finish_date": finish_date,
96
+ "net": net,
97
+ "breakdown": {"name": {activity_name}},
98
+ }
99
+ ],
100
+ }
9
101
 
10
102
 
11
103
  class Parser:
@@ -17,42 +109,6 @@ class Parser:
17
109
  self._line_number = None
18
110
  self._title_line = None
19
111
 
20
- def get_cell(self, col, row):
21
- try:
22
- coordinates = f"{col}{row}"
23
- return self.sheet[coordinates]
24
- except IndexError:
25
- raise BadRequest(
26
- f"Can't find the cell {coordinates} on sheet {self.sheet}."
27
- )
28
-
29
- def get_int(self, col, row):
30
- return int(self.get_cell(col, row).value)
31
-
32
- def get_dec(self, col, row):
33
- cell = self.get_cell(col, row)
34
- try:
35
- return Decimal(str(cell.value))
36
- except InvalidOperation as e:
37
- raise BadRequest(f"Problem parsing the number at {cell.coordinate}. {e}")
38
-
39
- def get_str(self, col, row):
40
- return self.get_cell(col, row).value.strip()
41
-
42
- def get_ct_date(self, col, row):
43
- cell = self.get_cell(col, row)
44
- val = cell.value
45
- if not isinstance(val, Datetime):
46
- raise BadRequest(
47
- f"The value {val} at {cell.coordinate} is of type {type(val)}, but "
48
- f"expected a timestamp."
49
- )
50
- return val
51
-
52
- def get_start_date(self, col, row):
53
- dt = self.get_ct_date(col, row)
54
- return None if dt is None else to_utc(dt)
55
-
56
112
  @property
57
113
  def line_number(self):
58
114
  return None if self._line_number is None else self._line_number + 1
@@ -67,59 +123,16 @@ class Parser:
67
123
  def make_raw_bills(self):
68
124
  try:
69
125
  bills = []
70
- issue_date = self.get_start_date("C", 6)
71
-
126
+ issue_date = to_utc(get_ct_date(self.sheet, "C", 6))
72
127
  for row in range(12, len(self.sheet["A"]) + 1):
73
- val = self.get_cell("B", row).value
128
+ val = get_cell(self.sheet, "B", row).value
74
129
  if val is None or val == "":
75
130
  break
76
-
77
131
  self._set_last_line(row, val)
78
- mpan_core = parse_mpan_core(str(self.get_int("B", row)))
79
-
80
- start_date = finish_date = self.get_start_date("F", row)
81
- activity_name_raw = self.get_str("G", row)
82
- activity_name = activity_name_raw.lower().replace(" ", "_")
83
-
84
- net_dec = self.get_dec("I", row)
85
- net = round(net_dec, 2)
86
-
87
- vat_dec = self.get_dec("J", row)
88
- vat = round(vat_dec, 2)
89
-
90
- gross_dec = self.get_dec("K", row)
91
- gross = round(gross_dec, 2)
92
-
93
- breakdown = {
94
- "raw-lines": [],
95
- "activity-name": [activity_name],
96
- "activity-gbp": net,
97
- }
98
-
99
- bills.append(
100
- {
101
- "bill_type_code": "N",
102
- "kwh": Decimal(0),
103
- "vat": vat,
104
- "net": net,
105
- "gross": gross,
106
- "reads": [],
107
- "breakdown": breakdown,
108
- "account": mpan_core,
109
- "issue_date": issue_date,
110
- "start_date": start_date,
111
- "finish_date": finish_date,
112
- "mpan_core": mpan_core,
113
- "reference": "_".join(
114
- (
115
- start_date.strftime("%Y%m%d"),
116
- finish_date.strftime("%Y%m%d"),
117
- issue_date.strftime("%Y%m%d"),
118
- mpan_core,
119
- )
120
- ),
121
- }
122
- )
132
+
133
+ bill = _process_row(issue_date, self.sheet, row)
134
+ bills.append(bill)
135
+
123
136
  except BadRequest as e:
124
137
  raise BadRequest(f"Row number: {row} {e.description}")
125
138
 
@@ -8,23 +8,22 @@ from openpyxl import load_workbook
8
8
  from werkzeug.exceptions import BadRequest
9
9
 
10
10
 
11
- from chellow.utils import parse_mpan_core, to_utc
11
+ from chellow.utils import parse_mpan_core, to_ct, to_utc
12
12
 
13
13
 
14
14
  def get_ct_date(row, idx):
15
15
  cell = get_cell(row, idx)
16
16
  val = cell.value
17
- if not isinstance(val, Datetime):
18
- raise BadRequest(f"Problem reading {val} as a timestamp at {cell.coordinate}.")
19
- return val
20
-
21
-
22
- def get_start_date(row, idx):
23
- return to_utc(get_ct_date(row, idx))
24
-
25
-
26
- def get_finish_date(row, idx):
27
- return to_utc(get_ct_date(row, idx) + relativedelta(hours=23, minutes=30))
17
+ if isinstance(val, Datetime):
18
+ dt = val
19
+ elif isinstance(val, str):
20
+ dt = Datetime.strptime(val, "dd/mm/yyyy")
21
+ else:
22
+ raise BadRequest(
23
+ f"The value {val} at {cell.coordinate} is of type {type(val)}, but "
24
+ f"expected a timestamp or string."
25
+ )
26
+ return to_ct(dt)
28
27
 
29
28
 
30
29
  def get_cell(row, idx):
@@ -52,6 +51,70 @@ def get_int(row, idx):
52
51
  return int(get_cell(row, idx).value)
53
52
 
54
53
 
54
+ def _process_row(issue_date, row):
55
+
56
+ mpan_core = parse_mpan_core(str(get_int(row, 1)))
57
+ comm = get_str(row, 2)
58
+
59
+ settled_str = get_str(row, 3)
60
+ if settled_str == "Settled":
61
+ settlement_status = "settlement"
62
+ else:
63
+ settlement_status = "non_settlement"
64
+
65
+ start_date_ct = get_ct_date(row, 5)
66
+ finish_date_ct = get_ct_date(row, 6)
67
+ days = (finish_date_ct - start_date_ct).days + 1
68
+
69
+ start_date = to_utc(start_date_ct)
70
+ finish_date = to_utc(finish_date_ct + relativedelta(hours=23, minutes=30))
71
+
72
+ meter_rate = get_dec(row, 7)
73
+ net = round(get_dec(row, 8), 2)
74
+ vat = round(get_dec(row, 9), 2)
75
+ gross = round(get_dec(row, 10), 2)
76
+
77
+ breakdown = {
78
+ "raw-lines": [],
79
+ }
80
+ return {
81
+ "bill_type_code": "N",
82
+ "kwh": Decimal(0),
83
+ "net": net,
84
+ "vat": vat,
85
+ "gross": gross,
86
+ "reads": [],
87
+ "breakdown": breakdown,
88
+ "account": mpan_core,
89
+ "issue_date": issue_date,
90
+ "start_date": start_date,
91
+ "finish_date": finish_date,
92
+ "mpan_core": mpan_core,
93
+ "reference": "_".join(
94
+ (
95
+ start_date.strftime("%Y%m%d"),
96
+ finish_date.strftime("%Y%m%d"),
97
+ issue_date.strftime("%Y%m%d"),
98
+ mpan_core,
99
+ )
100
+ ),
101
+ "elements": [
102
+ {
103
+ "name": "meter",
104
+ "start_date": start_date,
105
+ "finish_date": finish_date,
106
+ "breakdown": {
107
+ "rate": {meter_rate},
108
+ "comm": {comm},
109
+ "settlement-status": {settlement_status},
110
+ "days": days,
111
+ },
112
+ "net": net,
113
+ }
114
+ ],
115
+ }
116
+
117
+
55
118
  class Parser:
56
119
  def __init__(self, f):
57
120
  self.book = load_workbook(f, data_only=True)
@@ -76,61 +139,15 @@ class Parser:
76
139
  try:
77
140
  bills = []
78
141
  row = next(self.sheet.iter_rows(min_row=6, max_row=6, max_col=3))
79
- issue_date = get_start_date(row, 2)
142
+ issue_date = to_utc(get_ct_date(row, 2))
80
143
  for row in self.sheet.iter_rows(min_row=12, max_col=11):
81
144
  val = get_cell(row, 1).value
82
145
  if val is None or val == "":
83
146
  break
84
147
 
85
148
  self._set_last_line(row[0].row, val)
86
- mpan_core = parse_mpan_core(str(get_int(row, 1)))
87
- comms = get_str(row, 2)
88
-
89
- settled_str = get_str(row, 3)
90
- if settled_str == "Settled":
91
- settlement_status = "settlement"
92
- else:
93
- settlement_status = "non_settlement"
94
-
95
- start_date = get_start_date(row, 5)
96
- finish_date = get_finish_date(row, 6)
97
-
98
- meter_rate = get_dec(row, 7)
99
- net = round(get_dec(row, 8), 2)
100
- vat = round(get_dec(row, 9), 2)
101
- gross = round(get_dec(row, 10), 2)
102
-
103
- breakdown = {
104
- "raw-lines": [],
105
- "comms": comms,
106
- "settlement-status": [settlement_status],
107
- "meter-rate": [meter_rate],
108
- "meter-gbp": net,
109
- }
110
- bills.append(
111
- {
112
- "bill_type_code": "N",
113
- "kwh": Decimal(0),
114
- "net": net,
115
- "vat": vat,
116
- "gross": gross,
117
- "reads": [],
118
- "breakdown": breakdown,
119
- "account": mpan_core,
120
- "issue_date": issue_date,
121
- "start_date": start_date,
122
- "finish_date": finish_date,
123
- "mpan_core": mpan_core,
124
- "reference": "_".join(
125
- (
126
- start_date.strftime("%Y%m%d"),
127
- finish_date.strftime("%Y%m%d"),
128
- issue_date.strftime("%Y%m%d"),
129
- mpan_core,
130
- )
131
- ),
132
- }
133
- )
149
+ bill = _process_row(issue_date, row)
150
+ bills.append(bill)
134
151
  except BadRequest as e:
135
152
  raise BadRequest(f"Row number {row[0].row}: {e.description}")
136
153