chellow 1757320031.0.0__py3-none-any.whl → 1759411815.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.
- chellow/e/bill_importer.py +136 -80
- chellow/e/bill_parsers/activity_mop_stark_xlsx.py +99 -86
- chellow/e/bill_parsers/annual_mop_stark_xlsx.py +78 -61
- chellow/e/bill_parsers/csv.py +139 -101
- chellow/e/bill_parsers/drax_edi.py +65 -89
- chellow/e/bill_parsers/engie_edi.py +187 -255
- chellow/e/bill_parsers/engie_xls.py +153 -167
- chellow/e/bill_parsers/haven_edi.py +189 -228
- chellow/e/bill_parsers/haven_edi_tprs.py +67 -67
- chellow/e/bill_parsers/nonsettlement_dc_stark_xlsx.py +75 -66
- chellow/e/bill_parsers/settlement_dc_stark_xlsx.py +229 -126
- chellow/e/bill_parsers/sse_edi.py +107 -75
- chellow/e/bill_parsers/sww_xls.py +78 -91
- chellow/e/computer.py +1 -1
- chellow/e/views.py +626 -281
- chellow/edi_lib.py +4 -27
- chellow/models.py +92 -3
- chellow/reports/report_111.py +514 -616
- chellow/reports/report_247.py +96 -137
- chellow/templates/e/dc_batch.html +110 -157
- chellow/templates/e/dc_batch_add.html +2 -3
- chellow/templates/e/dc_batch_edit.html +42 -46
- chellow/templates/e/dc_batch_file.html +2 -3
- chellow/templates/e/dc_batch_file_edit.html +28 -40
- chellow/templates/e/dc_batch_upload_file.html +68 -0
- chellow/templates/e/dc_batches.html +2 -1
- chellow/templates/e/dc_batches_edit.html +26 -0
- chellow/templates/e/dc_bill.html +27 -5
- chellow/templates/e/dc_bill_add.html +4 -4
- chellow/templates/e/dc_bill_edit.html +43 -63
- chellow/templates/e/dc_bill_import.html +1 -1
- chellow/templates/e/dc_bill_import_contract.html +130 -0
- chellow/templates/e/dc_contract.html +1 -1
- chellow/templates/e/dc_element.html +41 -0
- chellow/templates/e/dc_element_add.html +36 -0
- chellow/templates/e/dc_element_edit.html +49 -0
- chellow/templates/e/dc_rate_script_edit.html +27 -43
- chellow/templates/e/mop_batch.html +105 -152
- chellow/templates/e/mop_batch_add.html +2 -3
- chellow/templates/e/mop_batch_edit.html +43 -51
- chellow/templates/e/mop_batch_upload_file.html +71 -5
- chellow/templates/e/mop_batches.html +2 -1
- chellow/templates/e/mop_batches_edit.html +26 -0
- chellow/templates/e/mop_bill.html +31 -8
- chellow/templates/e/mop_bill_add.html +7 -27
- chellow/templates/e/mop_bill_import.html +1 -1
- chellow/templates/e/mop_bill_import_contract.html +130 -0
- chellow/templates/e/mop_contract.html +4 -5
- chellow/templates/e/mop_element.html +41 -0
- chellow/templates/e/mop_element_add.html +36 -0
- chellow/templates/e/mop_element_edit.html +49 -0
- chellow/templates/e/supplier_batch.html +3 -7
- chellow/templates/e/supplier_batch_add.html +2 -2
- chellow/templates/e/supplier_batch_edit.html +1 -1
- chellow/templates/e/supplier_batch_file.html +3 -5
- chellow/templates/e/supplier_batch_file_add.html +18 -11
- chellow/templates/e/supplier_batch_upload_file.html +83 -9
- chellow/templates/e/supplier_batches.html +4 -4
- chellow/templates/e/supplier_batches_edit.html +26 -0
- chellow/templates/e/supplier_bill.html +29 -6
- chellow/templates/e/supplier_bill_add.html +3 -3
- chellow/templates/e/supplier_bill_import.html +1 -1
- chellow/templates/e/supplier_bill_import_contract.html +118 -0
- chellow/templates/e/supplier_contract.html +1 -1
- chellow/templates/e/supplier_element.html +45 -0
- chellow/templates/e/supplier_element_add.html +36 -0
- chellow/templates/e/supplier_element_edit.html +51 -0
- chellow/templates/report_run_bill_check.html +137 -179
- chellow/templates/report_run_row_bill_check.html +187 -179
- chellow/views.py +57 -64
- {chellow-1757320031.0.0.dist-info → chellow-1759411815.0.0.dist-info}/METADATA +2 -2
- {chellow-1757320031.0.0.dist-info → chellow-1759411815.0.0.dist-info}/RECORD +73 -60
- chellow/e/bill_parsers/drax_element_edi.py +0 -459
- chellow/templates/e/supplier_bill_imports.html +0 -421
- {chellow-1757320031.0.0.dist-info → chellow-1759411815.0.0.dist-info}/WHEEL +0 -0
chellow/e/bill_importer.py
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
-
|
|
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.
|
|
50
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
101
|
+
tprs = keydefaultdict(
|
|
102
|
+
lambda k: None if k is None else Tpr.get_by_code(sess, k)
|
|
103
|
+
)
|
|
76
104
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 =
|
|
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
|
-
|
|
87
|
-
|
|
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
|
|