chellow 1715858374.0.0__py3-none-any.whl → 1715955542.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/edi_lib.py +27 -20
- chellow/gas/bill_parser_edi.py +403 -0
- chellow/gas/bill_parser_engie_edi.py +4 -2
- chellow/gas/views.py +10 -0
- chellow/reports/report_233.py +13 -9
- chellow/templates/e/channel_snags.html +2 -1
- chellow/templates/g/bill_import.html +22 -16
- chellow/views.py +13 -15
- {chellow-1715858374.0.0.dist-info → chellow-1715955542.0.0.dist-info}/METADATA +1 -1
- {chellow-1715858374.0.0.dist-info → chellow-1715955542.0.0.dist-info}/RECORD +11 -10
- {chellow-1715858374.0.0.dist-info → chellow-1715955542.0.0.dist-info}/WHEEL +0 -0
chellow/edi_lib.py
CHANGED
|
@@ -6,54 +6,56 @@ from werkzeug.exceptions import BadRequest
|
|
|
6
6
|
from chellow.utils import ct_datetime, to_ct, to_utc
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
def line_iter(f):
|
|
10
|
+
line = []
|
|
11
|
+
while c := f.read(1) != "":
|
|
12
|
+
if c == "'":
|
|
13
|
+
yield "".join(line)
|
|
14
|
+
line.clear()
|
|
15
|
+
else:
|
|
16
|
+
line.append(c)
|
|
17
|
+
|
|
18
|
+
|
|
9
19
|
class EdiParser:
|
|
10
20
|
def __init__(self, f):
|
|
11
|
-
|
|
21
|
+
|
|
22
|
+
self.line_iterator = line_iter(f)
|
|
12
23
|
self.line_number = 0
|
|
13
24
|
|
|
14
25
|
def __iter__(self):
|
|
15
26
|
return self
|
|
16
27
|
|
|
17
28
|
def __next__(self):
|
|
18
|
-
self.line = next(self.
|
|
29
|
+
self.line = next(self.line_iterator).strip()
|
|
19
30
|
self.line_number += 1
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
raise BadRequest(
|
|
23
|
-
f"The parser expects each line to end with a ', but line number "
|
|
24
|
-
f"{self.line_number} doesn't: {self.line}."
|
|
25
|
-
)
|
|
26
|
-
self.elements = [element.split(":") for element in self.line[4:-1].split("+")]
|
|
32
|
+
self.elements = [element.split(":") for element in self.line[4].split("+")]
|
|
27
33
|
return self.line[:3]
|
|
28
34
|
|
|
29
35
|
|
|
30
36
|
def parse_edi(edi_str):
|
|
31
|
-
for line_number, raw_line in enumerate(edi_str.
|
|
32
|
-
if raw_line[-1] != "'":
|
|
33
|
-
raise BadRequest(
|
|
34
|
-
f"The parser expects each line to end with a ', but line "
|
|
35
|
-
f"number {line_number} doesn't: {raw_line}."
|
|
36
|
-
)
|
|
37
|
-
|
|
37
|
+
for line_number, raw_line in enumerate(edi_str.split("'"), start=1):
|
|
38
38
|
line = raw_line.strip()
|
|
39
|
+
if len(line) == 0:
|
|
40
|
+
continue
|
|
39
41
|
code = line[:3]
|
|
40
42
|
|
|
41
|
-
els = [el.split(":") for el in line[4
|
|
43
|
+
els = [el.split(":") for el in line[4:].split("+")]
|
|
42
44
|
|
|
43
|
-
segment_name = code + els[1][0] if code == "CCD" else code
|
|
45
|
+
segment_name = code + els[1][0].strip() if code == "CCD" else code
|
|
44
46
|
|
|
45
47
|
try:
|
|
46
48
|
elem_data = SEGMENTS[segment_name]
|
|
47
49
|
except KeyError:
|
|
48
50
|
raise BadRequest(
|
|
49
51
|
f"At line number {line_number} the segment name {segment_name} isn't "
|
|
50
|
-
f"recognized."
|
|
52
|
+
f"recognized. {raw_line}"
|
|
51
53
|
)
|
|
52
54
|
elem_codes = [m["code"] for m in elem_data["elements"]]
|
|
53
55
|
|
|
54
56
|
elements = dict(zip(elem_codes, els))
|
|
55
57
|
|
|
56
|
-
yield line_number, line, segment_name, elements
|
|
58
|
+
yield line_number, f"{line}'", segment_name, elements
|
|
57
59
|
|
|
58
60
|
|
|
59
61
|
def to_decimal(components):
|
|
@@ -67,11 +69,16 @@ def to_decimal(components):
|
|
|
67
69
|
|
|
68
70
|
|
|
69
71
|
def to_ct_date(component):
|
|
72
|
+
if len(component) == 0:
|
|
73
|
+
return None
|
|
70
74
|
return to_ct(Datetime.strptime(component, "%y%m%d"))
|
|
71
75
|
|
|
72
76
|
|
|
73
77
|
def to_date(component):
|
|
74
|
-
|
|
78
|
+
dt = to_ct_date(component)
|
|
79
|
+
if dt is None:
|
|
80
|
+
return None
|
|
81
|
+
return to_utc(dt)
|
|
75
82
|
|
|
76
83
|
|
|
77
84
|
def to_finish_date(component):
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from datetime import datetime as Datetime
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from dateutil.relativedelta import relativedelta
|
|
6
|
+
|
|
7
|
+
from werkzeug.exceptions import BadRequest
|
|
8
|
+
|
|
9
|
+
from chellow.edi_lib import parse_edi, to_date, to_decimal
|
|
10
|
+
from chellow.utils import HH, to_ct, to_utc
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
READ_TYPE_MAP = {"00": "A", "01": "E", "02": "E"}
|
|
14
|
+
|
|
15
|
+
TCOD_MAP = {
|
|
16
|
+
"Energy Bill Discount Scheme": {"PPK": "ebrs"},
|
|
17
|
+
"Energy Bill Relief Scheme": {"PPK": "ebrs"},
|
|
18
|
+
"Energy Bill Relief Scheme Discount": {"PPK": "ebrs"},
|
|
19
|
+
"Unidentified Gas": {"PPK": "ug"},
|
|
20
|
+
"Commodity": {"PPK": "commodity"},
|
|
21
|
+
"Transportation": {"PPD": "transportation_fixed", "PPK": "transportation_variable"},
|
|
22
|
+
"Gas Flexi": {"PPK": "commodity"},
|
|
23
|
+
"Flex - Gas Flexi (New)": {"PPK": "commodity"},
|
|
24
|
+
"Meter Reading": {"PPD": "meter_read"},
|
|
25
|
+
"Meter Reading Credit Oct 19": {"FIX": "meter_read"},
|
|
26
|
+
"Meter Rental": {"PPD": "metering"},
|
|
27
|
+
"CCL": {"PPK": "ccl"},
|
|
28
|
+
"Consumption Based Administration": {"PPK": "admin_variable"},
|
|
29
|
+
"Swing": {"PPK": "swing"},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
SUPPLIER_CODE_MAP = {
|
|
33
|
+
"STD": "standing",
|
|
34
|
+
"MET": "commodity",
|
|
35
|
+
"CCL": "ccl",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
UNIT_MAP = {"M3": "M3", "HH": "HCUF", "HCUF": "HCUF"}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _to_finish_date(date_str):
|
|
42
|
+
if len(date_str) == 0:
|
|
43
|
+
return None
|
|
44
|
+
return to_utc(
|
|
45
|
+
to_ct(Datetime.strptime(date_str, "%y%m%d") + relativedelta(days=1) - HH)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _process_ADJ(elements, headers):
|
|
50
|
+
adjf = elements["ADJF"]
|
|
51
|
+
if adjf[0] == "CV":
|
|
52
|
+
headers["cv"] = Decimal(adjf[1]) / Decimal(100000)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _process_BCD(elements, headers):
|
|
56
|
+
ivdt = elements["IVDT"]
|
|
57
|
+
headers["issue_date"] = to_date(ivdt[0])
|
|
58
|
+
|
|
59
|
+
invn = elements["INVN"]
|
|
60
|
+
headers["reference"] = invn[0]
|
|
61
|
+
|
|
62
|
+
btcd = elements["BTCD"]
|
|
63
|
+
headers["bill_type_code"] = btcd[0]
|
|
64
|
+
|
|
65
|
+
sumo = elements["SUMO"]
|
|
66
|
+
start_date = to_date(sumo[0])
|
|
67
|
+
if start_date is not None:
|
|
68
|
+
headers["start_date"] = start_date
|
|
69
|
+
|
|
70
|
+
if len(sumo) > 1:
|
|
71
|
+
finish_date = _to_finish_date(sumo[1])
|
|
72
|
+
if finish_date is not None:
|
|
73
|
+
headers["finish_date"] = finish_date
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _process_MHD(elements, headers):
|
|
77
|
+
headers.clear()
|
|
78
|
+
|
|
79
|
+
typ = elements["TYPE"]
|
|
80
|
+
message_type = headers["message_type"] = typ[0]
|
|
81
|
+
if message_type == "UTLBIL":
|
|
82
|
+
headers["reads"] = []
|
|
83
|
+
headers["raw_lines"] = []
|
|
84
|
+
headers["breakdown"] = defaultdict(int, {"units_consumed": Decimal(0)})
|
|
85
|
+
headers["kwh"] = Decimal("0.00")
|
|
86
|
+
headers["net"] = Decimal("0.00")
|
|
87
|
+
headers["vat"] = Decimal("0.00")
|
|
88
|
+
headers["gross"] = Decimal("0.00")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _process_CCD1(elements, headers):
|
|
92
|
+
mtnr = elements["MTNR"]
|
|
93
|
+
msn = mtnr[0]
|
|
94
|
+
|
|
95
|
+
mloc = elements["MLOC"]
|
|
96
|
+
|
|
97
|
+
# Bug in EDI where MPRN missing in second CCD 1
|
|
98
|
+
if "mprn" not in headers:
|
|
99
|
+
headers["mprn"] = mloc[0]
|
|
100
|
+
|
|
101
|
+
prdt = elements["PRDT"]
|
|
102
|
+
pvdt = elements["PVDT"]
|
|
103
|
+
|
|
104
|
+
pres_read_date = to_date(prdt[0])
|
|
105
|
+
prev_read_date = to_date(pvdt[0])
|
|
106
|
+
|
|
107
|
+
prrd = elements["PRRD"]
|
|
108
|
+
pres_read_value = Decimal(prrd[0])
|
|
109
|
+
pres_read_type = READ_TYPE_MAP[prrd[1]]
|
|
110
|
+
prev_read_value = Decimal(prrd[2])
|
|
111
|
+
prev_read_type = READ_TYPE_MAP[prrd[3]]
|
|
112
|
+
|
|
113
|
+
conb = elements["CONB"]
|
|
114
|
+
unit = UNIT_MAP[conb[1]]
|
|
115
|
+
headers["breakdown"]["units_consumed"] += to_decimal(conb) / Decimal("1000")
|
|
116
|
+
|
|
117
|
+
adjf = elements["ADJF"]
|
|
118
|
+
correction_factor = Decimal(adjf[1]) / Decimal(100000)
|
|
119
|
+
|
|
120
|
+
nuct = elements["NUCT"]
|
|
121
|
+
|
|
122
|
+
headers["kwh"] += to_decimal(nuct) / Decimal("1000")
|
|
123
|
+
|
|
124
|
+
headers["reads"].append(
|
|
125
|
+
{
|
|
126
|
+
"msn": msn,
|
|
127
|
+
"unit": unit,
|
|
128
|
+
"correction_factor": correction_factor,
|
|
129
|
+
"prev_date": prev_read_date,
|
|
130
|
+
"prev_value": prev_read_value,
|
|
131
|
+
"prev_type_code": prev_read_type,
|
|
132
|
+
"pres_date": pres_read_date,
|
|
133
|
+
"pres_value": pres_read_value,
|
|
134
|
+
"pres_type_code": pres_read_type,
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
NUCT_LOOKUP = {"DAY": "days", "KWH": "kwh"}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _process_CCD2(elements, headers):
|
|
143
|
+
breakdown = headers["breakdown"]
|
|
144
|
+
ccde = elements["CCDE"]
|
|
145
|
+
ccde_supplier_code = ccde[2]
|
|
146
|
+
tcod = elements["TCOD"]
|
|
147
|
+
nuct = elements["NUCT"]
|
|
148
|
+
mtnr = elements["MTNR"]
|
|
149
|
+
conb = elements["CONB"]
|
|
150
|
+
adjf = elements["ADJF"]
|
|
151
|
+
prdt = elements["PRDT"]
|
|
152
|
+
pvdt = elements["PVDT"]
|
|
153
|
+
prrd = elements["PRRD"]
|
|
154
|
+
|
|
155
|
+
if len(tcod) > 1:
|
|
156
|
+
tpref_lookup = TCOD_MAP[tcod[1]]
|
|
157
|
+
else:
|
|
158
|
+
tpref_lookup = SUPPLIER_CODE_MAP
|
|
159
|
+
|
|
160
|
+
tpref = tpref_lookup[ccde_supplier_code]
|
|
161
|
+
|
|
162
|
+
bpri = elements["BPRI"]
|
|
163
|
+
if len(bpri[0]) > 0:
|
|
164
|
+
rate_key = f"{tpref}_rate"
|
|
165
|
+
if rate_key not in breakdown:
|
|
166
|
+
breakdown[rate_key] = set()
|
|
167
|
+
rate = Decimal(bpri[0]) / Decimal("10000000")
|
|
168
|
+
breakdown[rate_key].add(rate)
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
ctot = elements["CTOT"]
|
|
172
|
+
breakdown[f"{tpref}_gbp"] += to_decimal(ctot) / Decimal("100")
|
|
173
|
+
|
|
174
|
+
if len(nuct) > 1:
|
|
175
|
+
key = NUCT_LOOKUP[nuct[1]]
|
|
176
|
+
else:
|
|
177
|
+
if ccde_supplier_code == "PPK":
|
|
178
|
+
key = f"{tpref}_kwh"
|
|
179
|
+
elif ccde_supplier_code == "PPD":
|
|
180
|
+
key = f"{tpref}_days"
|
|
181
|
+
|
|
182
|
+
breakdown[key] += to_decimal(nuct) / Decimal("1000")
|
|
183
|
+
except KeyError:
|
|
184
|
+
pass
|
|
185
|
+
|
|
186
|
+
if "start_date" not in headers:
|
|
187
|
+
csdt = elements["CSDT"]
|
|
188
|
+
start_date = to_date(csdt[0])
|
|
189
|
+
if start_date is not None:
|
|
190
|
+
headers["start_date"] = start_date
|
|
191
|
+
|
|
192
|
+
if "finish_date" not in headers:
|
|
193
|
+
cedt = elements["CSDT"]
|
|
194
|
+
finish_date = _to_finish_date(cedt[0])
|
|
195
|
+
if finish_date is not None:
|
|
196
|
+
headers["finish_date"] = finish_date
|
|
197
|
+
|
|
198
|
+
if "mprn" not in headers:
|
|
199
|
+
mloc = elements["MLOC"]
|
|
200
|
+
headers["mprn"] = mloc[0]
|
|
201
|
+
|
|
202
|
+
if len(conb) > 0 and len(conb[0]) > 0:
|
|
203
|
+
headers["breakdown"]["units_consumed"] += to_decimal(conb) / Decimal("1000")
|
|
204
|
+
|
|
205
|
+
if len(prrd) > 0 and len(prrd[0]) > 0:
|
|
206
|
+
pres_read_date = to_date(prdt[0])
|
|
207
|
+
prev_read_date = to_date(pvdt[0])
|
|
208
|
+
|
|
209
|
+
pres_read_value = Decimal(prrd[0])
|
|
210
|
+
pres_read_type = READ_TYPE_MAP[prrd[1]]
|
|
211
|
+
prev_read_value = Decimal(prrd[2])
|
|
212
|
+
prev_read_type = READ_TYPE_MAP[prrd[3]]
|
|
213
|
+
msn = mtnr[0]
|
|
214
|
+
unit = UNIT_MAP[conb[1]]
|
|
215
|
+
correction_factor = Decimal(adjf[1]) / Decimal(100000)
|
|
216
|
+
|
|
217
|
+
headers["reads"].append(
|
|
218
|
+
{
|
|
219
|
+
"msn": msn,
|
|
220
|
+
"unit": unit,
|
|
221
|
+
"correction_factor": correction_factor,
|
|
222
|
+
"prev_date": prev_read_date,
|
|
223
|
+
"prev_value": prev_read_value,
|
|
224
|
+
"prev_type_code": prev_read_type,
|
|
225
|
+
"pres_date": pres_read_date,
|
|
226
|
+
"pres_value": pres_read_value,
|
|
227
|
+
"pres_type_code": pres_read_type,
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _process_CCD3(elements, headers):
|
|
233
|
+
breakdown = headers["breakdown"]
|
|
234
|
+
ccde = elements["CCDE"]
|
|
235
|
+
ccde_supplier_code = ccde[2]
|
|
236
|
+
tcod = elements["TCOD"]
|
|
237
|
+
|
|
238
|
+
tpref = TCOD_MAP[tcod[1]][ccde_supplier_code]
|
|
239
|
+
|
|
240
|
+
bpri = elements["BPRI"]
|
|
241
|
+
bpri_str = bpri[0]
|
|
242
|
+
if len(bpri_str) > 0:
|
|
243
|
+
rate_key = f"{tpref}_rate"
|
|
244
|
+
if rate_key not in breakdown:
|
|
245
|
+
breakdown[rate_key] = set()
|
|
246
|
+
rate = Decimal(bpri_str) / Decimal("10000000")
|
|
247
|
+
breakdown[rate_key].add(rate)
|
|
248
|
+
|
|
249
|
+
nuct = elements["NUCT"]
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
ctot = elements["CTOT"]
|
|
253
|
+
breakdown[f"{tpref}_gbp"] += to_decimal(ctot) / Decimal("100")
|
|
254
|
+
|
|
255
|
+
if ccde_supplier_code == "PPK":
|
|
256
|
+
key = f"{tpref}_kwh"
|
|
257
|
+
elif ccde_supplier_code == "PPD":
|
|
258
|
+
key = f"{tpref}_days"
|
|
259
|
+
|
|
260
|
+
breakdown[key] += to_decimal(nuct) / Decimal("1000")
|
|
261
|
+
except KeyError:
|
|
262
|
+
pass
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _process_CCD4(elements, headers):
|
|
266
|
+
breakdown = headers["breakdown"]
|
|
267
|
+
ccde = elements["ccde"]
|
|
268
|
+
ccde_supplier_code = ccde[2]
|
|
269
|
+
tcod = elements["TCOD"]
|
|
270
|
+
|
|
271
|
+
tpref = TCOD_MAP[tcod[1]][ccde_supplier_code]
|
|
272
|
+
|
|
273
|
+
bpri = elements["BPRI"]
|
|
274
|
+
rate_key = f"{tpref}_rate"
|
|
275
|
+
if rate_key not in breakdown:
|
|
276
|
+
breakdown[rate_key] = set()
|
|
277
|
+
rate = Decimal(bpri[0]) / Decimal("10000000")
|
|
278
|
+
breakdown[rate_key].add(rate)
|
|
279
|
+
|
|
280
|
+
nuct = elements["NUCT"]
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
ctot = elements["CTOT"]
|
|
284
|
+
breakdown[tpref + "_gbp"] += to_decimal(ctot) / Decimal("100")
|
|
285
|
+
|
|
286
|
+
if ccde_supplier_code == "PPK":
|
|
287
|
+
key = f"{tpref}_kwh"
|
|
288
|
+
elif ccde_supplier_code == "PPD":
|
|
289
|
+
key = f"{tpref}_days"
|
|
290
|
+
|
|
291
|
+
breakdown[key] += to_decimal(nuct) / Decimal("1000")
|
|
292
|
+
except KeyError:
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _process_MTR(elements, headers):
|
|
297
|
+
if headers["message_type"] == "UTLBIL":
|
|
298
|
+
breakdown = headers["breakdown"]
|
|
299
|
+
for k, v in tuple(breakdown.items()):
|
|
300
|
+
if isinstance(v, set):
|
|
301
|
+
breakdown[k] = sorted(v)
|
|
302
|
+
|
|
303
|
+
for read in headers["reads"]:
|
|
304
|
+
read["calorific_value"] = headers["cv"]
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
"raw_lines": "\n".join(headers["raw_lines"]),
|
|
308
|
+
"mprn": headers["mprn"],
|
|
309
|
+
"reference": headers["reference"],
|
|
310
|
+
"account": headers["mprn"],
|
|
311
|
+
"reads": headers["reads"],
|
|
312
|
+
"kwh": headers["kwh"],
|
|
313
|
+
"breakdown": headers["breakdown"],
|
|
314
|
+
"net_gbp": headers["net"],
|
|
315
|
+
"vat_gbp": headers["vat"],
|
|
316
|
+
"gross_gbp": headers["gross"],
|
|
317
|
+
"bill_type_code": headers["bill_type_code"],
|
|
318
|
+
"start_date": headers["start_date"],
|
|
319
|
+
"finish_date": headers["finish_date"],
|
|
320
|
+
"issue_date": headers["issue_date"],
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _process_VAT(elements, headers):
|
|
325
|
+
breakdown = headers["breakdown"]
|
|
326
|
+
vatp = elements["VATP"]
|
|
327
|
+
if "vat" in breakdown:
|
|
328
|
+
vat = breakdown["vat"]
|
|
329
|
+
else:
|
|
330
|
+
vat = breakdown["vat"] = {}
|
|
331
|
+
|
|
332
|
+
vat_perc = to_decimal(vatp) / Decimal(1000)
|
|
333
|
+
try:
|
|
334
|
+
vat_bd = vat[vat_perc]
|
|
335
|
+
except KeyError:
|
|
336
|
+
vat_bd = vat[vat_perc] = defaultdict(int)
|
|
337
|
+
|
|
338
|
+
uvtt = elements["UVTT"]
|
|
339
|
+
vat_gbp = to_decimal(uvtt) / Decimal("100")
|
|
340
|
+
|
|
341
|
+
vat_bd["vat"] += vat_gbp
|
|
342
|
+
|
|
343
|
+
uvla = elements["UVLA"]
|
|
344
|
+
net_gbp = to_decimal(uvla) / Decimal("100")
|
|
345
|
+
vat_bd["net"] += net_gbp
|
|
346
|
+
headers["net"] += net_gbp
|
|
347
|
+
headers["vat"] += vat_gbp
|
|
348
|
+
ucsi = elements["UCSI"]
|
|
349
|
+
headers["gross"] += to_decimal(ucsi) / Decimal("100")
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _process_NOOP(elements, headers):
|
|
353
|
+
pass
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
CODE_FUNCS = {
|
|
357
|
+
"ADJ": _process_ADJ,
|
|
358
|
+
"BCD": _process_BCD,
|
|
359
|
+
"BTL": _process_NOOP,
|
|
360
|
+
"CCD1": _process_CCD1,
|
|
361
|
+
"CCD2": _process_CCD2,
|
|
362
|
+
"CCD3": _process_CCD3,
|
|
363
|
+
"CCD4": _process_CCD4,
|
|
364
|
+
"CDT": _process_NOOP,
|
|
365
|
+
"CLO": _process_NOOP,
|
|
366
|
+
"END": _process_NOOP,
|
|
367
|
+
"FIL": _process_NOOP,
|
|
368
|
+
"MHD": _process_MHD,
|
|
369
|
+
"MTR": _process_MTR,
|
|
370
|
+
"SDT": _process_NOOP,
|
|
371
|
+
"STX": _process_NOOP,
|
|
372
|
+
"TYP": _process_NOOP,
|
|
373
|
+
"TTL": _process_NOOP,
|
|
374
|
+
"VAT": _process_VAT,
|
|
375
|
+
"VTS": _process_NOOP,
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class Parser:
|
|
380
|
+
def __init__(self, file_bytes):
|
|
381
|
+
self.edi_str = str(file_bytes, "utf-8", errors="ignore")
|
|
382
|
+
self.line_number = None
|
|
383
|
+
|
|
384
|
+
def make_raw_bills(self):
|
|
385
|
+
bills = []
|
|
386
|
+
headers = {}
|
|
387
|
+
bill = None
|
|
388
|
+
for self.line_number, line, seg_name, elements in parse_edi(self.edi_str):
|
|
389
|
+
try:
|
|
390
|
+
func = CODE_FUNCS[seg_name]
|
|
391
|
+
except KeyError:
|
|
392
|
+
raise BadRequest(f"Code {seg_name} not recognized.")
|
|
393
|
+
try:
|
|
394
|
+
bill = func(elements, headers)
|
|
395
|
+
except BaseException as e:
|
|
396
|
+
raise Exception(f"Propblem with segment {line}: {e}") from e
|
|
397
|
+
|
|
398
|
+
if "raw_lines" in headers:
|
|
399
|
+
headers["raw_lines"].append(line)
|
|
400
|
+
if bill is not None:
|
|
401
|
+
bills.append(bill)
|
|
402
|
+
|
|
403
|
+
return bills
|
|
@@ -301,8 +301,10 @@ class Parser:
|
|
|
301
301
|
func = CODE_FUNCS[seg_name]
|
|
302
302
|
except KeyError:
|
|
303
303
|
raise BadRequest(f"Code {seg_name} not recognized.")
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
try:
|
|
305
|
+
bill = func(elements, headers)
|
|
306
|
+
except BaseException as e:
|
|
307
|
+
raise BadRequest(f"Propblem with segment {line}: {e}") from e
|
|
306
308
|
|
|
307
309
|
if "raw_lines" in headers:
|
|
308
310
|
headers["raw_lines"].append(line)
|
chellow/gas/views.py
CHANGED
|
@@ -418,6 +418,16 @@ def batch_get(g_batch_id):
|
|
|
418
418
|
vbd["vat"] += g_bill.vat
|
|
419
419
|
vbd["net"] += g_bill.net
|
|
420
420
|
|
|
421
|
+
if "vat" in bd:
|
|
422
|
+
for vat_percentage, vat_bd in bd["vat"].items():
|
|
423
|
+
try:
|
|
424
|
+
vbd = vat_breakdown[vat_percentage]
|
|
425
|
+
except KeyError:
|
|
426
|
+
vbd = vat_breakdown[vat_percentage] = defaultdict(int)
|
|
427
|
+
|
|
428
|
+
vbd["vat"] += vat_bd["vat"]
|
|
429
|
+
vbd["net"] += vat_bd["net"]
|
|
430
|
+
|
|
421
431
|
config_contract = Contract.get_non_core_by_name(g.sess, "configuration")
|
|
422
432
|
properties = config_contract.make_properties()
|
|
423
433
|
|
chellow/reports/report_233.py
CHANGED
|
@@ -7,7 +7,7 @@ from dateutil.relativedelta import relativedelta
|
|
|
7
7
|
|
|
8
8
|
from flask import g
|
|
9
9
|
|
|
10
|
-
from sqlalchemy.sql.expression import true
|
|
10
|
+
from sqlalchemy.sql.expression import false, select, true
|
|
11
11
|
|
|
12
12
|
from chellow.dloads import open_file
|
|
13
13
|
from chellow.models import (
|
|
@@ -21,11 +21,11 @@ from chellow.models import (
|
|
|
21
21
|
Supply,
|
|
22
22
|
User,
|
|
23
23
|
)
|
|
24
|
-
from chellow.utils import csv_make_val, hh_before, req_int, utc_datetime_now
|
|
24
|
+
from chellow.utils import csv_make_val, hh_before, req_bool, req_int, utc_datetime_now
|
|
25
25
|
from chellow.views import chellow_redirect
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def content(contract_id, days_hidden, user_id):
|
|
28
|
+
def content(contract_id, days_hidden, is_ignored, user_id):
|
|
29
29
|
f = writer = None
|
|
30
30
|
try:
|
|
31
31
|
with Session() as sess:
|
|
@@ -54,15 +54,14 @@ def content(contract_id, days_hidden, user_id):
|
|
|
54
54
|
|
|
55
55
|
now = utc_datetime_now()
|
|
56
56
|
cutoff_date = now - relativedelta(days=days_hidden)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
sess.query(Snag, Channel, Era, Supply, SiteEra, Site)
|
|
57
|
+
q = (
|
|
58
|
+
select(Snag, Channel, Era, Supply, SiteEra, Site)
|
|
60
59
|
.join(Channel, Snag.channel_id == Channel.id)
|
|
61
60
|
.join(Era, Channel.era_id == Era.id)
|
|
62
61
|
.join(Supply, Era.supply_id == Supply.id)
|
|
63
62
|
.join(SiteEra, Era.site_eras)
|
|
64
63
|
.join(Site, SiteEra.site_id == Site.id)
|
|
65
|
-
.
|
|
64
|
+
.where(
|
|
66
65
|
SiteEra.is_physical == true(),
|
|
67
66
|
Era.dc_contract == contract,
|
|
68
67
|
Snag.start_date < cutoff_date,
|
|
@@ -76,7 +75,11 @@ def content(contract_id, days_hidden, user_id):
|
|
|
76
75
|
Snag.start_date,
|
|
77
76
|
Snag.id,
|
|
78
77
|
)
|
|
79
|
-
)
|
|
78
|
+
)
|
|
79
|
+
if not is_ignored:
|
|
80
|
+
q = q.where(Snag.is_ignored == false())
|
|
81
|
+
|
|
82
|
+
for snag, channel, era, supply, site_era, site in sess.execute(q):
|
|
80
83
|
snag_start = snag.start_date
|
|
81
84
|
snag_finish = snag.finish_date
|
|
82
85
|
imp_mc = "" if era.imp_mpan_core is None else era.imp_mpan_core
|
|
@@ -123,7 +126,8 @@ def content(contract_id, days_hidden, user_id):
|
|
|
123
126
|
def do_get(sess):
|
|
124
127
|
contract_id = req_int("dc_contract_id")
|
|
125
128
|
days_hidden = req_int("days_hidden")
|
|
129
|
+
is_ignored = req_bool("is_ignored")
|
|
126
130
|
|
|
127
|
-
args = contract_id, days_hidden, g.user.id
|
|
131
|
+
args = contract_id, days_hidden, is_ignored, g.user.id
|
|
128
132
|
threading.Thread(target=content, args=args).start()
|
|
129
133
|
return chellow_redirect("/downloads", 303)
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
<legend>Download CSV</legend>
|
|
18
18
|
<input type="hidden" name="dc_contract_id" value="{{contract.id}}">
|
|
19
19
|
<label>Hide snags < days old</label>
|
|
20
|
-
|
|
20
|
+
{{input_text('days_hidden', '0', 3, 3)}}
|
|
21
|
+
<label>Include ignored snags</label> {{input_checkbox('is_ignored', False)}}
|
|
21
22
|
<input type="submit" value="Download">
|
|
22
23
|
</fieldset>
|
|
23
24
|
</form>
|
|
@@ -46,17 +46,19 @@
|
|
|
46
46
|
<th>Start Date</th>
|
|
47
47
|
<th>Finish Date</th>
|
|
48
48
|
<th>Net GBP</th>
|
|
49
|
-
<th>VAT 5%</th>
|
|
50
|
-
<th>VAT 15%</th>
|
|
51
|
-
<th>VAT 17.5%</th>
|
|
52
|
-
<th>VAT 20%</th>
|
|
53
49
|
<th>VAT</th>
|
|
54
50
|
<th>Gross GBP</th>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
<th>Breakdown</th>
|
|
52
|
+
<th>R1 calorific_value</th>
|
|
53
|
+
<th>R1 correction_factor</th>
|
|
54
|
+
<th>R1 msn</th>
|
|
55
|
+
<th>R1 unit</th>
|
|
56
|
+
<th>R1 prev_date</th>
|
|
57
|
+
<th>R1 prev_value</th>
|
|
58
|
+
<th>R1 prev_type_code</th>
|
|
59
|
+
<th>R1 pres_date</th>
|
|
60
|
+
<th>R1 pres_value</th>
|
|
61
|
+
<th>R1 pres_type_code</th>
|
|
60
62
|
</tr>
|
|
61
63
|
</thead>
|
|
62
64
|
<tbody>
|
|
@@ -71,16 +73,20 @@
|
|
|
71
73
|
<td>{{bill.start_date|hh_format}}</td>
|
|
72
74
|
<td>{{bill.finish_date|hh_format}}</td>
|
|
73
75
|
<td>{{bill.net_gbp}}</td>
|
|
74
|
-
<td>{{bill.vat_5pc}}</td>
|
|
75
|
-
<td>{{bill.vat_15pc}}</td>
|
|
76
|
-
<td>{{bill.vat_17_5pc}}</td>
|
|
77
|
-
<td>{{bill.vat_20pc}}</td>
|
|
78
76
|
<td>{{bill.vat_gbp}}</td>
|
|
79
77
|
<td>{{bill.gross_gbp}}</td>
|
|
78
|
+
<td>{{bill.breakdown|dumps}}</td>
|
|
80
79
|
{% for read in bill.reads %}
|
|
81
|
-
{
|
|
82
|
-
|
|
83
|
-
{
|
|
80
|
+
<td>{{read.calorific_value}}</td>
|
|
81
|
+
<td>{{read.correction_factor}}</td>
|
|
82
|
+
<td>{{read.msn}}</td>
|
|
83
|
+
<td>{{read. unit}}</td>
|
|
84
|
+
<td>{{read.prev_date|hh_format}}</td>
|
|
85
|
+
<td>{{read.prev_value}}</td>
|
|
86
|
+
<td>{{read.prev_type_code}}</td>
|
|
87
|
+
<td>{{read.pres_date|hh_format}}</td>
|
|
88
|
+
<td>{{read.pres_value}}</td>
|
|
89
|
+
<td>{{read.pres_type_code}}</td>
|
|
84
90
|
{% endfor %}
|
|
85
91
|
</tr>
|
|
86
92
|
{% endfor %}
|
chellow/views.py
CHANGED
|
@@ -67,10 +67,10 @@ import chellow.e.mdd_importer
|
|
|
67
67
|
import chellow.e.rcrc
|
|
68
68
|
import chellow.e.system_price
|
|
69
69
|
import chellow.e.tlms
|
|
70
|
-
import chellow.edi_lib
|
|
71
70
|
import chellow.general_import
|
|
72
71
|
import chellow.national_grid
|
|
73
72
|
import chellow.rate_server
|
|
73
|
+
from chellow.edi_lib import SEGMENTS, parse_edi
|
|
74
74
|
from chellow.models import (
|
|
75
75
|
BillType,
|
|
76
76
|
Channel,
|
|
@@ -644,23 +644,19 @@ def edi_viewer_post():
|
|
|
644
644
|
else:
|
|
645
645
|
edi_str = req_str("edi_fragment")
|
|
646
646
|
|
|
647
|
-
|
|
648
|
-
f.seek(0)
|
|
649
|
-
parser = chellow.edi_lib.EdiParser(f)
|
|
650
|
-
for segment_name in parser:
|
|
651
|
-
elements = parser.elements
|
|
647
|
+
for line_number, line, segment_name, elements in parse_edi(edi_str):
|
|
652
648
|
|
|
653
649
|
if segment_name == "CCD":
|
|
654
|
-
segment_name = segment_name + elements[
|
|
650
|
+
segment_name = segment_name + elements["CCDE"][0]
|
|
655
651
|
try:
|
|
656
|
-
seg =
|
|
652
|
+
seg = SEGMENTS[segment_name]
|
|
657
653
|
except KeyError:
|
|
658
654
|
raise BadRequest(
|
|
659
655
|
f"The segment name {segment_name} is not recognized."
|
|
660
656
|
)
|
|
661
657
|
else:
|
|
662
658
|
try:
|
|
663
|
-
seg =
|
|
659
|
+
seg = SEGMENTS[segment_name]
|
|
664
660
|
except KeyError:
|
|
665
661
|
raise BadRequest(
|
|
666
662
|
f"The segment name {segment_name} is not recognized."
|
|
@@ -669,13 +665,14 @@ def edi_viewer_post():
|
|
|
669
665
|
titles_element = []
|
|
670
666
|
titles_component = []
|
|
671
667
|
values = []
|
|
672
|
-
elems = seg["elements"]
|
|
668
|
+
elems = {el["code"]: el for el in seg["elements"]}
|
|
673
669
|
if len(elements) > len(elems):
|
|
674
670
|
raise BadRequest(
|
|
675
671
|
f"There are more elements than recognized for the segment "
|
|
676
|
-
f"{segment_name}."
|
|
672
|
+
f"{segment_name}. {line}"
|
|
677
673
|
)
|
|
678
|
-
for
|
|
674
|
+
for element_code, element in elements.items():
|
|
675
|
+
elem = elems[element_code]
|
|
679
676
|
comps = elem["components"]
|
|
680
677
|
colspan = len(comps)
|
|
681
678
|
titles_element.append(
|
|
@@ -688,8 +685,9 @@ def edi_viewer_post():
|
|
|
688
685
|
)
|
|
689
686
|
if len(element) > len(comps):
|
|
690
687
|
raise BadRequest(
|
|
691
|
-
f"
|
|
692
|
-
f"{
|
|
688
|
+
f"For the segment {segment_name} the number of components "
|
|
689
|
+
f"{element} is greater than the expected components {comps}. "
|
|
690
|
+
f"{line}"
|
|
693
691
|
)
|
|
694
692
|
|
|
695
693
|
for i, (title, typ) in enumerate(comps):
|
|
@@ -726,7 +724,7 @@ def edi_viewer_post():
|
|
|
726
724
|
"titles_element": titles_element,
|
|
727
725
|
"titles_component": titles_component,
|
|
728
726
|
"vals": values,
|
|
729
|
-
"raw_line":
|
|
727
|
+
"raw_line": line,
|
|
730
728
|
}
|
|
731
729
|
)
|
|
732
730
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: chellow
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1715955542.0.0
|
|
4
4
|
Summary: Web Application for checking UK energy bills.
|
|
5
5
|
Project-URL: Homepage, https://github.com/WessexWater/chellow
|
|
6
6
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
@@ -3,7 +3,7 @@ chellow/api.py,sha256=mk17TfweR76DPFC8lX2SArTjai6y6YshASxqO1w-_-s,11036
|
|
|
3
3
|
chellow/bank_holidays.py,sha256=T_utYMwe_g1dz5X-aOTdIPryg49SvB7QsWM1yphlqG8,4423
|
|
4
4
|
chellow/commands.py,sha256=ESBe9ZWj1c3vdZgqMZ9gFvYAB3hRag2R1PzOwuw9yFo,1302
|
|
5
5
|
chellow/dloads.py,sha256=5SmP-0QPK6xCkd_wjIWh_8FAzN5OxNHCzyEQ1Xb-Y-M,5256
|
|
6
|
-
chellow/edi_lib.py,sha256=
|
|
6
|
+
chellow/edi_lib.py,sha256=alu20x9ZX06iPfnNI9dEJzuP6RIf4We3Y_M_bl7RrcY,51789
|
|
7
7
|
chellow/general_import.py,sha256=l3EHq9zG9Vfl5Ee6XTVrC1nusXo4YGGB4VBmZ_JaJR8,65798
|
|
8
8
|
chellow/models.py,sha256=2YSRKOt89mgawuz1CV02FsjLi5ue9Np38PGOIS-yoc4,242202
|
|
9
9
|
chellow/national_grid.py,sha256=uxcv0qisHPyzw9AVIYPzsRqwt2XPAcZL-SBR12qcrS0,4364
|
|
@@ -11,7 +11,7 @@ chellow/proxy.py,sha256=cVXIktPlX3tQ1BYcwxq0nJXKE6r3DtFTtfFHPq55HaM,1351
|
|
|
11
11
|
chellow/rate_server.py,sha256=fg-Pf_9Hk3bXmC9riPQNGQxBvLvBa_WtNYdwDCjnCSg,5678
|
|
12
12
|
chellow/testing.py,sha256=Od4HHH6pZrhJ_De118_F55RJEKmAvhUH2S24QE9qFQk,3635
|
|
13
13
|
chellow/utils.py,sha256=32qPWEGzCPKPhSM7ztpzcR6ZG74sVZanScDIdK50Rq4,19308
|
|
14
|
-
chellow/views.py,sha256=
|
|
14
|
+
chellow/views.py,sha256=eDvTQM_PUqRvrCQvBdlF5q7MEs7w2yJmRjcC8WDbHtE,79584
|
|
15
15
|
chellow/e/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
chellow/e/aahedc.py,sha256=d2usudp7KYWpU6Pk3fal5EQ47EbvkvKeaFGylnb3NWw,606
|
|
17
17
|
chellow/e/bill_importer.py,sha256=y1bpn49xDwltj0PRFowWsjAVamcD8eyxUbCTdGxEZiE,7389
|
|
@@ -58,13 +58,14 @@ chellow/e/bill_parsers/sse_edi.py,sha256=L85DOfNkqexeEIEr8pCBn_2sHJI-zEaw6cogpE3
|
|
|
58
58
|
chellow/e/bill_parsers/sww_xls.py,sha256=QEjiuvwvr5FuWCfqqVw8LaA_vZyAKsvRAS5fw3xtFhM,7533
|
|
59
59
|
chellow/gas/bill_import.py,sha256=w0lPgK_Drzh8rtnEBQe3qFuxrgzZ6qQSgpaGrrGznMU,6549
|
|
60
60
|
chellow/gas/bill_parser_csv.py,sha256=Ecdy-apFT-mWAxddAsM4k1s-9-FpIaOfjP0oFc0rdQg,5557
|
|
61
|
-
chellow/gas/
|
|
61
|
+
chellow/gas/bill_parser_edi.py,sha256=3PGofkIxfxWG1x5WM7pYFi0tfTmvOuusRxEc-76Zvi0,11484
|
|
62
|
+
chellow/gas/bill_parser_engie_edi.py,sha256=Ko0vZP-QdVQ1uuhS_5cdrii60_cM4b_LFJMoY0pZqnk,8950
|
|
62
63
|
chellow/gas/ccl.py,sha256=DMlcPUELZi00CaDekVJINYk3GgH5apFrImVdmkbyba0,2913
|
|
63
64
|
chellow/gas/cv.py,sha256=4cdYYQ8Qak6NeYdBCB4YaQ0jX8-UkaydIIdibCQuXxM,7344
|
|
64
65
|
chellow/gas/dn_rate_parser.py,sha256=Mq8rAcUEUxIQOks59bsCKl8GrefvoHbrTCHqon9N0z0,11340
|
|
65
66
|
chellow/gas/engine.py,sha256=PhaCWDslhEzDmuCu5PMt3IpFANm27OO1bupq3yQCmc0,25518
|
|
66
67
|
chellow/gas/transportation.py,sha256=Bkg8TWOs-v0ES-4qqwbleiOhqbE_t2KauUx9JYMZELM,5300
|
|
67
|
-
chellow/gas/views.py,sha256=
|
|
68
|
+
chellow/gas/views.py,sha256=0oiQBmhI6egENXdi0CcgjM80BaF0nxptPJvRKJdce_M,56781
|
|
68
69
|
chellow/reports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
70
|
chellow/reports/report_109.py,sha256=S8fsOwh-jVWGL0RsgyYLdqn00BAvlkBbi4VfTSGI-Mc,11181
|
|
70
71
|
chellow/reports/report_111.py,sha256=qU5MYPQzZ_cEvuERE8bxq93D-dN1yd4oML6FIqd0Jdk,28278
|
|
@@ -74,7 +75,7 @@ chellow/reports/report_183.py,sha256=DZX-hHJPl_Tbgkt31C_cuLZg_5L2b6iCPQ5foOZizR0
|
|
|
74
75
|
chellow/reports/report_187.py,sha256=UvpaYHjyJFNV5puYq8_KxfzoBtVrwFgIGUOmC5oGA9A,9956
|
|
75
76
|
chellow/reports/report_219.py,sha256=o2eEg3mX9_raP2b4gjc2Gu4vqnMqcvvJBYQ1oQjxvpE,6637
|
|
76
77
|
chellow/reports/report_231.py,sha256=gOb1AkXZQvwVpRg5cIenO7iR7Se1_zsWnJp9l2BlgpA,5008
|
|
77
|
-
chellow/reports/report_233.py,sha256=
|
|
78
|
+
chellow/reports/report_233.py,sha256=cIatj-HHYW_GNIRsji-DlsmYjt8rUdm_5xujPLOYL8U,4537
|
|
78
79
|
chellow/reports/report_241.py,sha256=AlFmSHnfG2HWv_ICmWX7fNpPwLHjq7mo1QtOTjSKO3k,5384
|
|
79
80
|
chellow/reports/report_247.py,sha256=ozgCcee8XeqYbOpZCyU2STJKaz6h2x7TYQogagTaYLw,46626
|
|
80
81
|
chellow/reports/report_29.py,sha256=KDFjgrLBv4WbG9efCdu_geMR7gT_QV9N97Wfdt7aDc4,2736
|
|
@@ -158,7 +159,7 @@ chellow/templates/e/channel_add.html,sha256=X1hi0b-ZjchlQ2m7uZYCyqkqU-Qd1s3_5u4z
|
|
|
158
159
|
chellow/templates/e/channel_edit.html,sha256=OUkdiS2NBQ_w4gmxRxXAcRToM5BT8DWWJrtQMFs-GUU,2198
|
|
159
160
|
chellow/templates/e/channel_snag.html,sha256=wBJ5KBfeJdAeRmaB0qu0AD9Z4nM5fn6tJ_8bNqTWtoo,1477
|
|
160
161
|
chellow/templates/e/channel_snag_edit.html,sha256=sUFI4Ml3F1B35x8_t_Pz3hWuQN9Xtxr3kt4u8hdxNwY,1911
|
|
161
|
-
chellow/templates/e/channel_snags.html,sha256=
|
|
162
|
+
chellow/templates/e/channel_snags.html,sha256=tDZWp8s0gt5AtqArpclSl6hOafQCetz7Sp4MfZJ7dWQ,2964
|
|
162
163
|
chellow/templates/e/comm.html,sha256=DSlAaDg1r4KYq9VUgDtt2lgW6apZjZvwhMujIJINmps,383
|
|
163
164
|
chellow/templates/e/comms.html,sha256=bUXZePnMfpKk1E71qLGOSkx8r3GxdPPD_-MosIXXq4s,434
|
|
164
165
|
chellow/templates/e/cop.html,sha256=ULv7ALFJHMUgPX96hQNm2irc3edtKYHS6fYAxvmzj8M,376
|
|
@@ -326,7 +327,7 @@ chellow/templates/g/batches.html,sha256=mnzLroYfhwvL5gFK1PNtI-vS7GcDtcggNd0E15Sh
|
|
|
326
327
|
chellow/templates/g/bill.html,sha256=S8moZ06CDl4_nQQgqyy4mdkyhfvgoQJGZS8ppsluT_E,3455
|
|
327
328
|
chellow/templates/g/bill_add.html,sha256=sDSpUgEbdalDsea1Ma5lgVRgtbFf0bZ042jUdOFeDDk,1674
|
|
328
329
|
chellow/templates/g/bill_edit.html,sha256=ynfUR_lZXLgTK3T0x9GjzAHahuR823ykMpjCWrY8ot8,2754
|
|
329
|
-
chellow/templates/g/bill_import.html,sha256=
|
|
330
|
+
chellow/templates/g/bill_import.html,sha256=iK8Bn_s_RmWr1noOdZ7f3L-VztyOl23YAnFXLK16x_o,4584
|
|
330
331
|
chellow/templates/g/bill_imports.html,sha256=AHC0l0Wkr1RZ9fdGWTqihOEcn8lTZ63Uh9BHqPxfRCU,3157
|
|
331
332
|
chellow/templates/g/dn.html,sha256=ttEdvFANFUCBV8e9tVrZy35-tzsC9dU-biZhAPxE2Bw,481
|
|
332
333
|
chellow/templates/g/dns.html,sha256=RuxXvQ9eHs6B7nVGHtTbW8pdmSAaMbQw2f_BwiLZptM,403
|
|
@@ -363,6 +364,6 @@ chellow/templates/g/supply_note_edit.html,sha256=6UQf_qbhFDys3cVsTp-c7ABWZpggW9R
|
|
|
363
364
|
chellow/templates/g/supply_notes.html,sha256=WR3YwGh_qqTklSJ7JqWX6BKBc9rk_jMff4RiWZiF2CM,936
|
|
364
365
|
chellow/templates/g/unit.html,sha256=KouNVU0-i84afANkLQ_heJ0uDfJ9H5A05PuLqb8iCN8,438
|
|
365
366
|
chellow/templates/g/units.html,sha256=p5Nd-lAIboKPEOO6N451hx1bcKxMg4BDODnZ-43MmJc,441
|
|
366
|
-
chellow-
|
|
367
|
-
chellow-
|
|
368
|
-
chellow-
|
|
367
|
+
chellow-1715955542.0.0.dist-info/METADATA,sha256=9p1jWHXcSPCm5spNAJsGfUTL9DSmz_kVMN9Bd36K8Hg,12205
|
|
368
|
+
chellow-1715955542.0.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
|
369
|
+
chellow-1715955542.0.0.dist-info/RECORD,,
|
|
File without changes
|