chellow 1744710468.0.0__py3-none-any.whl → 1745313690.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/__init__.py CHANGED
@@ -22,6 +22,7 @@ import chellow.e.hh_importer
22
22
  import chellow.e.rcrc
23
23
  import chellow.e.system_price
24
24
  import chellow.e.views
25
+ import chellow.fake_batch_updater
25
26
  import chellow.gas.cv
26
27
  import chellow.gas.views
27
28
  import chellow.national_grid
@@ -62,6 +63,7 @@ def get_importer_modules():
62
63
  chellow.national_grid,
63
64
  chellow.rate_server,
64
65
  chellow.rrun,
66
+ chellow.fake_batch_updater,
65
67
  )
66
68
 
67
69
 
@@ -59,7 +59,7 @@ def _handle_0100(headers, pre_record, record):
59
59
  headers["issue_date"] = issue_date
60
60
  if late_payment is not None:
61
61
  headers["late_payment"] = late_payment
62
- headers["account"] = pre_record[33:41]
62
+ headers["account"] = pre_record[43:52]
63
63
  headers["reference"] = pre_record[41:46]
64
64
  headers["kwh"] = Decimal("0")
65
65
  headers["breakdown"] = defaultdict(int, {"vat": {}})
@@ -4,6 +4,7 @@ from openpyxl import load_workbook
4
4
 
5
5
  from werkzeug.exceptions import BadRequest
6
6
 
7
+ from chellow.e.computer import hh_rate
7
8
  from chellow.models import Session
8
9
  from chellow.utils import ct_datetime, parse_mpan_core, to_utc
9
10
 
@@ -16,12 +17,6 @@ def get_start_date(title_row, row, name):
16
17
  return to_utc(get_ct_date(title_row, row, name))
17
18
 
18
19
 
19
- def get_finish_date(title_row, row, name):
20
- d = get_ct_date(title_row, row, name)
21
-
22
- return to_utc(ct_datetime(d.year, d.month, d.day, 23, 30))
23
-
24
-
25
20
  def get_value(title_row, row, name):
26
21
  idx = None
27
22
  name = name.strip().lower()
@@ -61,9 +56,6 @@ def get_int(title_row, row, name):
61
56
  return int(get_value(title_row, row, name))
62
57
 
63
58
 
64
- METER_RATE = Decimal("60.00")
65
-
66
-
67
59
  class Parser:
68
60
  def __init__(self, f):
69
61
  self.book = load_workbook(f)
@@ -86,6 +78,7 @@ class Parser:
86
78
 
87
79
  def make_raw_bills(self):
88
80
  row_index = None
81
+ caches = {}
89
82
  try:
90
83
  with Session() as sess:
91
84
  bills = []
@@ -101,14 +94,30 @@ class Parser:
101
94
  mpan_core = parse_mpan_core(
102
95
  str(get_int(title_row, row, "mpan ref"))
103
96
  )
104
- start_date = get_start_date(title_row, row, "start")
97
+ start_date_ct = get_ct_date(title_row, row, "start")
98
+ start_date = to_utc(start_date_ct)
105
99
  issue_date = start_date
106
- finish_date = get_finish_date(title_row, row, "end")
100
+ finish_date_ct = get_ct_date(title_row, row, "end")
101
+ finish_date = to_utc(
102
+ ct_datetime(
103
+ finish_date_ct.year,
104
+ finish_date_ct.month,
105
+ finish_date_ct.day,
106
+ 23,
107
+ 30,
108
+ )
109
+ )
107
110
  check = get_str(title_row, row, "check")
108
111
  if check != "Billed":
109
112
  continue
110
-
111
- net = METER_RATE / 12
113
+ rates = hh_rate(sess, caches, 0, start_date)
114
+ meter_rate = rates["annual_rates"]["non_settlement"]["*"]["IP"][
115
+ "*"
116
+ ]["gbp_per_meter"]
117
+ months = (finish_date_ct.year - start_date_ct.year) * 12 + (
118
+ finish_date_ct.month - start_date_ct.month + 1
119
+ )
120
+ net = round(Decimal(float(meter_rate) / 12 * months), 2)
112
121
  vat = round(net * Decimal("0.2"), 2)
113
122
 
114
123
  breakdown = {
@@ -116,7 +125,8 @@ class Parser:
116
125
  "cop": ["5"],
117
126
  "settlement-status": ["non_settlement"],
118
127
  "msn": [msn],
119
- "meter-rate": [METER_RATE],
128
+ "meter-rate": [meter_rate],
129
+ "months": months,
120
130
  "meter-gbp": net,
121
131
  }
122
132
 
@@ -650,15 +650,15 @@ def update_vls(sess, logger, vls, dno_code, fy_start, rs_finish):
650
650
  llfc = sess.execute(q).scalar_one_or_none()
651
651
 
652
652
  if llfc is None:
653
- raise BadRequest(
653
+ logger(
654
654
  f"There is no LLFC with the code '{vl_code}' associated with the DNO "
655
- f"{dno.code} from {hh_format(fy_start)} to {hh_format(rs_finish)}."
655
+ f"{dno.dno_code} from {hh_format(fy_start)} to {hh_format(rs_finish)}."
656
656
  )
657
-
658
- vl_voltage_level = VoltageLevel.get_by_code(sess, vl["voltage_level"])
659
- llfc.voltage_level = vl_voltage_level
660
-
661
- llfc.is_substation = vl["is_substation"]
662
- if sess.is_modified(llfc):
663
- logger(f"Updated LLFC {llfc.code} of DNO {dno_code}")
664
- sess.flush()
657
+ else:
658
+ vl_voltage_level = VoltageLevel.get_by_code(sess, vl["voltage_level"])
659
+ llfc.voltage_level = vl_voltage_level
660
+
661
+ llfc.is_substation = vl["is_substation"]
662
+ if sess.is_modified(llfc):
663
+ logger(f"Updated LLFC {llfc.code} of DNO {dno_code}")
664
+ sess.flush()
@@ -0,0 +1,305 @@
1
+ import atexit
2
+ import collections
3
+ import threading
4
+ import traceback
5
+ from datetime import timedelta
6
+
7
+ from sqlalchemy import false, null, select
8
+
9
+ from werkzeug.exceptions import BadRequest
10
+
11
+ from chellow.e.computer import contract_func
12
+ from chellow.gas.engine import g_contract_func
13
+ from chellow.models import (
14
+ Batch,
15
+ BillType,
16
+ Contract,
17
+ GBatch,
18
+ GBill,
19
+ GContract,
20
+ GRateScript,
21
+ GReadType,
22
+ GSupply,
23
+ GUnit,
24
+ RateScript,
25
+ ReadType,
26
+ Session,
27
+ Supply,
28
+ Tpr,
29
+ )
30
+ from chellow.utils import (
31
+ c_months_u,
32
+ ct_datetime_now,
33
+ hh_format,
34
+ keydefaultdict,
35
+ utc_datetime_now,
36
+ )
37
+
38
+
39
+ importer = None
40
+
41
+
42
+ def run_import(sess, log, set_progress):
43
+ log("Starting to update the fake batches")
44
+ caches = {}
45
+
46
+ now_ct = ct_datetime_now()
47
+ (last_month_start, last_month_finish), (
48
+ current_month_start,
49
+ current_month_finish,
50
+ ) = list(c_months_u(finish_year=now_ct.year, finish_month=now_ct.month, months=2))
51
+
52
+ for last_rate_script in sess.scalars(
53
+ select(RateScript)
54
+ .join(Contract, RateScript.contract_id == Contract.id)
55
+ .where(RateScript.finish_date == null())
56
+ ):
57
+ contract = last_rate_script.contract
58
+ fb_func = contract_func(caches, contract, "make_fake_bills")
59
+ if fb_func is None:
60
+ continue
61
+
62
+ fake_batch_name = f"fake_e_batch_{contract.id}"
63
+
64
+ fake_batch = sess.scalar_one_or_none(
65
+ select(Batch).where(
66
+ Batch.contract == contract,
67
+ Batch.reference == fake_batch_name,
68
+ )
69
+ )
70
+
71
+ if fake_batch is not None and fake_batch.start_date < current_month_start:
72
+ fake_batch.delete(sess)
73
+ sess.flush()
74
+ fake_batch = None
75
+
76
+ if fake_batch is None:
77
+ fake_batch = contract.insert_batch(sess, fake_batch_name, "Fake Batch")
78
+ bill_types = keydefaultdict(lambda k: BillType.get_by_code(sess, k))
79
+
80
+ tprs = keydefaultdict(
81
+ lambda k: None if k is None else Tpr.get_by_code(sess, k)
82
+ )
83
+
84
+ read_types = keydefaultdict(lambda k: ReadType.get_by_code(sess, k))
85
+ for raw_bill in fb_func(
86
+ sess,
87
+ log,
88
+ last_month_start,
89
+ last_month_finish,
90
+ current_month_start,
91
+ current_month_finish,
92
+ ):
93
+ mpan_core = raw_bill["mpan_core"]
94
+ supply = Supply.get_by_mpan_core(sess, mpan_core)
95
+ bill = fake_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(
112
+ 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"]],
124
+ )
125
+ for last_rate_script in sess.scalars(
126
+ select(GRateScript)
127
+ .join(GContract, GRateScript.g_contract_id == GContract.id)
128
+ .where(GContract.is_industry == false(), GRateScript.finish_date == null())
129
+ ):
130
+ g_contract = last_rate_script.g_contract
131
+ log(f"Looking at gas contract {g_contract.name}")
132
+ fb_func = g_contract_func(caches, g_contract, "make_fake_bills")
133
+ if fb_func is None:
134
+ log("Doesn't have a make_fake_bills function so skipping")
135
+ continue
136
+
137
+ fake_batch_name = f"fake_g_batch_{g_contract.id}"
138
+
139
+ fake_batch = sess.scalars(
140
+ select(GBatch).where(
141
+ GBatch.g_contract == g_contract,
142
+ GBatch.reference == fake_batch_name,
143
+ )
144
+ ).one_or_none()
145
+
146
+ if fake_batch is not None:
147
+ first_fake_bill = sess.scalars(
148
+ select(GBill)
149
+ .where(GBill.g_batch == fake_batch)
150
+ .order_by(GBill.start_date)
151
+ ).first()
152
+ if (
153
+ first_fake_bill is None
154
+ or first_fake_bill.start_date < current_month_start
155
+ ):
156
+ fake_batch.delete(sess)
157
+ sess.flush()
158
+ fake_batch = None
159
+
160
+ if fake_batch is None:
161
+ fake_batch = g_contract.insert_g_batch(sess, fake_batch_name, "Fake Batch")
162
+ raw_bills = fb_func(
163
+ sess,
164
+ log,
165
+ last_month_start,
166
+ last_month_finish,
167
+ current_month_start,
168
+ current_month_finish,
169
+ )
170
+ if raw_bills is not None and len(raw_bills) > 0:
171
+ for raw_bill in raw_bills:
172
+ bill_type = BillType.get_by_code(sess, raw_bill["bill_type_code"])
173
+ g_supply = GSupply.get_by_mprn(sess, raw_bill["mprn"])
174
+ g_bill = fake_batch.insert_g_bill(
175
+ sess,
176
+ g_supply,
177
+ bill_type,
178
+ raw_bill["reference"],
179
+ raw_bill["account"],
180
+ raw_bill["issue_date"],
181
+ raw_bill["start_date"],
182
+ raw_bill["finish_date"],
183
+ raw_bill["kwh"],
184
+ raw_bill["net_gbp"],
185
+ raw_bill["vat_gbp"],
186
+ raw_bill["gross_gbp"],
187
+ raw_bill["raw_lines"],
188
+ raw_bill["breakdown"],
189
+ )
190
+ sess.flush()
191
+ for raw_read in raw_bill["reads"]:
192
+ prev_type = GReadType.get_by_code(
193
+ sess, raw_read["prev_type_code"]
194
+ )
195
+ pres_type = GReadType.get_by_code(
196
+ sess, raw_read["pres_type_code"]
197
+ )
198
+ g_unit = GUnit.get_by_code(sess, raw_read["unit"])
199
+ g_bill.insert_g_read(
200
+ sess,
201
+ raw_read["msn"],
202
+ g_unit,
203
+ raw_read["correction_factor"],
204
+ raw_read["calorific_value"],
205
+ raw_read["prev_value"],
206
+ raw_read["prev_date"],
207
+ prev_type,
208
+ raw_read["pres_value"],
209
+ raw_read["pres_date"],
210
+ pres_type,
211
+ )
212
+ sess.commit()
213
+
214
+
215
+ LAST_RUN_KEY = "fake_batch_updater_last_run"
216
+
217
+
218
+ class FakeBatchUpdater(threading.Thread):
219
+ def __init__(self):
220
+ super().__init__(name="Fake Batch Updater")
221
+ self.messages = collections.deque(maxlen=500)
222
+ self.progress = ""
223
+ self.stopped = threading.Event()
224
+ self.going = threading.Event()
225
+ self.global_alert = None
226
+
227
+ def stop(self):
228
+ self.stopped.set()
229
+ self.going.set()
230
+ self.join()
231
+
232
+ def go(self):
233
+ self.going.set()
234
+
235
+ def log(self, message):
236
+ self.messages.appendleft(
237
+ f"{ct_datetime_now().strftime('%Y-%m-%d %H:%M:%S')} - {message}"
238
+ )
239
+
240
+ def set_progress(self, progress):
241
+ self.progress = progress
242
+
243
+ def run(self):
244
+ while not self.stopped.is_set():
245
+ with Session() as sess:
246
+ try:
247
+ config = Contract.get_non_core_by_name(sess, "configuration")
248
+ state = config.make_state()
249
+ except BaseException as e:
250
+ msg = f"{e.description} " if isinstance(e, BadRequest) else ""
251
+ self.log(f"{msg}{traceback.format_exc()}")
252
+ self.global_alert = (
253
+ "There's a problem with a <a href='/fake_batch_updater'>"
254
+ "Fake Batch Updater</a>."
255
+ )
256
+ sess.rollback()
257
+
258
+ last_run = state.get(LAST_RUN_KEY)
259
+ if last_run is None or utc_datetime_now() - last_run > timedelta(days=1):
260
+ self.going.set()
261
+
262
+ if self.going.is_set():
263
+ self.global_alert = None
264
+ with Session() as sess:
265
+ try:
266
+ config = Contract.get_non_core_by_name(sess, "configuration")
267
+ state = config.make_state()
268
+ state[LAST_RUN_KEY] = utc_datetime_now()
269
+ config.update_state(state)
270
+ sess.commit()
271
+ run_import(sess, self.log, self.set_progress)
272
+ except BaseException as e:
273
+ msg = f"{e.description} " if isinstance(e, BadRequest) else ""
274
+ self.log(f"{msg}{traceback.format_exc()}")
275
+ self.global_alert = (
276
+ "There's a problem with a "
277
+ "<a href='/fake_batch_updater'>Fake Batch Updater</a>."
278
+ )
279
+ sess.rollback()
280
+ finally:
281
+ self.going.clear()
282
+ self.log("Finished updating fake batches.")
283
+
284
+ else:
285
+ self.log(
286
+ f"The updater was last run at {hh_format(last_run)}. There will "
287
+ f"be another update when a hour has elapsed since the last run."
288
+ )
289
+ self.going.wait(60 * 60)
290
+
291
+
292
+ def get_importer():
293
+ return importer
294
+
295
+
296
+ def startup():
297
+ global importer
298
+ importer = FakeBatchUpdater()
299
+ importer.start()
300
+
301
+
302
+ @atexit.register
303
+ def shutdown():
304
+ if importer is not None:
305
+ importer.stop()
@@ -0,0 +1,158 @@
1
+ import csv
2
+ import threading
3
+ import traceback
4
+
5
+ from flask import g, redirect
6
+
7
+
8
+ from sqlalchemy import null, or_, select
9
+ from sqlalchemy.orm import joinedload
10
+
11
+ from chellow.dloads import open_file
12
+ from chellow.models import Batch, Bill, Contract, Era, RSession, ReportRun, User
13
+ from chellow.utils import (
14
+ c_months_u,
15
+ csv_make_val,
16
+ hh_max,
17
+ hh_min,
18
+ hh_range,
19
+ req_date,
20
+ req_int,
21
+ )
22
+
23
+
24
+ def content(user_id, report_run_id, contract_id, months_length, finish_date):
25
+ f = writer = None
26
+ try:
27
+ with RSession() as sess:
28
+ caches = {}
29
+ contract = Contract.get_by_id(sess, contract_id)
30
+ user = User.get_by_id(sess, user_id)
31
+ f = open_file(
32
+ f"missing_bills_{contract.id}.csv", user, mode="w", newline=""
33
+ )
34
+ writer = csv.writer(f, lineterminator="\n")
35
+ titles = (
36
+ "contract_name",
37
+ "month_start",
38
+ "month_finish",
39
+ "site_code",
40
+ "site_name",
41
+ "imp_mpan_core",
42
+ "exp_mpan_core",
43
+ "account",
44
+ )
45
+ writer.writerow(titles)
46
+
47
+ months = list(
48
+ c_months_u(
49
+ finish_year=finish_date.year,
50
+ finish_month=finish_date.month,
51
+ months=months_length,
52
+ )
53
+ )
54
+
55
+ for month_start, month_finish in months:
56
+ missing_bills = {}
57
+ missing_account = {}
58
+ account_missing_tuple = hh_range(caches, month_start, month_finish)
59
+ for era in sess.scalars(
60
+ select(Era)
61
+ .where(
62
+ Era.start_date <= month_finish,
63
+ or_(Era.finish_date == null(), Era.finish_date >= month_start),
64
+ or_(
65
+ Era.mop_contract == contract,
66
+ Era.dc_contract == contract,
67
+ Era.imp_supplier_contract == contract,
68
+ Era.exp_supplier_contract == contract,
69
+ ),
70
+ )
71
+ .options(joinedload(Era.supply))
72
+ ):
73
+ chunk_start = hh_max(era.start_date, month_start)
74
+ chunk_finish = hh_min(era.finish_date, month_finish)
75
+ missing_set = set(hh_range(caches, chunk_start, chunk_finish))
76
+ if era.mop_contract == contract:
77
+ account = era.mop_account
78
+ elif era.dc_contract == contract:
79
+ account = era.dc_account
80
+ elif era.imp_supplier_contract == contract:
81
+ account = era.imp_supplier_account
82
+ elif era.imp_supplier_contract == contract:
83
+ account = era.exp_supplier_account
84
+
85
+ try:
86
+ account_missing_set = missing_account[account]
87
+ except KeyError:
88
+ account_missing_set = missing_account[account] = set(
89
+ account_missing_tuple
90
+ )
91
+ supply = era.supply
92
+
93
+ for bill in sess.scalars(
94
+ select(Bill)
95
+ .join(Batch)
96
+ .where(
97
+ Batch.contract == contract,
98
+ Bill.supply == supply,
99
+ Bill.start_date <= chunk_finish,
100
+ Bill.finish_date >= chunk_start,
101
+ )
102
+ ):
103
+ found_set = set(
104
+ hh_range(caches, bill.start_date, bill.finish_date)
105
+ )
106
+ missing_set.difference_update(found_set)
107
+ account_missing_set.difference_update(found_set)
108
+
109
+ if len(missing_set) > 0:
110
+ site = era.get_physical_site(sess)
111
+
112
+ values = {
113
+ "contract_id": contract.id,
114
+ "contract_name": contract.name,
115
+ "month_start": month_start,
116
+ "month_finish": month_finish,
117
+ "era_id": era.id,
118
+ "supply_id": supply.id,
119
+ "imp_mpan_core": era.imp_mpan_core,
120
+ "exp_mpan_core": era.exp_mpan_core,
121
+ "site_id": site.id,
122
+ "site_code": site.code,
123
+ "site_name": site.name,
124
+ "account": account,
125
+ "market_role_code": contract.market_role.code,
126
+ }
127
+ missing_bills[era.id] = values
128
+ for era_id, values in missing_bills.items():
129
+ if len(missing_account[values["account"]]) > 0:
130
+ writer.writerow(csv_make_val(values[t]) for t in titles)
131
+ ReportRun.w_insert_row(report_run_id, "", titles, values, {})
132
+
133
+ except BaseException:
134
+ msg = traceback.format_exc()
135
+ print(msg)
136
+ if writer is not None:
137
+ writer.writerow([msg])
138
+ finally:
139
+ ReportRun.w_update(report_run_id, "finished")
140
+ if f is not None:
141
+ f.close()
142
+
143
+
144
+ def do_get(sess):
145
+ contract_id = req_int("contract_id")
146
+ months = req_int("months")
147
+ finish_date = req_date("finish", resolution="month")
148
+ report_run = ReportRun.insert(
149
+ sess,
150
+ "missing_e_bills",
151
+ g.user,
152
+ "missing_e_bills",
153
+ {},
154
+ )
155
+ sess.commit()
156
+ args = g.user.id, report_run.id, contract_id, months, finish_date
157
+ threading.Thread(target=content, args=args).start()
158
+ return redirect(f"/report_runs/{report_run.id}", 303)
@@ -97,7 +97,18 @@
97
97
  <input type="submit" value="Download">
98
98
  </fieldset>
99
99
  </form>
100
- <br>
100
+
101
+ <form action="/reports/missing_bills">
102
+ <fieldset>
103
+ <legend>Download Missing Bills</legend>
104
+ <input type="hidden" name="contract_id" value="{{dc_contract.id}}">
105
+
106
+ <label>Months</label> <input name="months" value="1" maxlength="2" size="2">
107
+ <label>Last Month</label> {{input_date('finish', last_month_finish, 'month')}}
108
+ <input type="submit" value="Download">
109
+ </fieldset>
110
+ </form>
111
+
101
112
  <h3>Script</h3>
102
113
 
103
114
  <pre>{{dc_contract.charge_script}}</pre>
@@ -76,6 +76,17 @@
76
76
  <input type="submit" value="Download"/>
77
77
  </fieldset>
78
78
  </form>
79
+
80
+ <form action="/reports/missing_bills">
81
+ <fieldset>
82
+ <legend>Download Missing Bills</legend>
83
+ <input type="hidden" name="contract_id" value="{{contract.id}}">
84
+
85
+ <label>Months</label> <input name="months" value="1" maxlength="2" size="2">
86
+ <label>Last Month</label> {{input_date('finish', last_month_finish, 'month')}}
87
+ <input type="submit" value="Download">
88
+ </fieldset>
89
+ </form>
79
90
  <br>
80
91
  <h2>Script</h2>
81
92
  <pre>{{contract.charge_script}}</pre>
@@ -97,6 +97,17 @@
97
97
  </fieldset>
98
98
  </form>
99
99
 
100
+ <form action="/reports/missing_bills">
101
+ <fieldset>
102
+ <legend>Download Missing Bills</legend>
103
+ <input type="hidden" name="contract_id" value="{{contract.id}}">
104
+
105
+ <label>Months</label> <input name="months" value="1" maxlength="2" size="2">
106
+ <label>Last Month</label> {{input_date('finish', month_finish, 'month')}}
107
+ <input type="submit" value="Download">
108
+ </fieldset>
109
+ </form>
110
+
100
111
  <h2>Script</h2>
101
112
 
102
113
  <pre>{{contract.charge_script}}</pre>
@@ -0,0 +1,99 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}
4
+ &raquo; Fake Batch Updater
5
+ {% endblock %}
6
+
7
+ {% block nav %}
8
+ Fake Batch Updater
9
+ {% endblock %}
10
+
11
+ {% block content %}
12
+ <p>
13
+ {% if importer %}
14
+ <ul>
15
+ <li><a href="#controls">Controls</a></li>
16
+ <li><a href="#log">Log</a></li>
17
+ <li><a href="#batches">Fake Batches</a></li>
18
+ </ul>
19
+
20
+ <h4 id="controls">Controls</h4>
21
+
22
+ <table>
23
+ <tr>
24
+ <th>Is Going?</th>
25
+ <td>{{importer.going.is_set()}}</td>
26
+ </tr>
27
+ <tr>
28
+ <th>Import Now</th>
29
+ <td>
30
+ <form method="post">
31
+ <fieldset {% if importer.going.is_set() %}disabled{% endif %}>
32
+ <input type="submit" value="Import" name="import">
33
+ </fieldset>
34
+ </form>
35
+ </td>
36
+ </tr>
37
+ {% if importer.going.is_set() %}
38
+ <tr>
39
+ <th>Progress</th>
40
+ <td>{{importer.progress}}</td>
41
+ </tr>
42
+ {% endif %}
43
+ </table>
44
+
45
+
46
+ <h4 id="log">Log</h4>
47
+
48
+ <ul>
49
+ {% for message in importer.messages %}
50
+ <li>{{message}}</li>
51
+ {% endfor %}
52
+ </ul>
53
+
54
+ <h4 id="batches">Fake Batches</h4>
55
+
56
+ <table>
57
+ <caption>Electricity</caption>
58
+ <thead>
59
+ <tr>
60
+ <th>View</th>
61
+ <th>Reference</th>
62
+ <th>Contract Name</th>
63
+ </tr>
64
+ </thead>
65
+ <tbody>
66
+ {% for batch in e_fake_batches %}
67
+ <tr>
68
+ <th><a href="/e/supplier_batches/{{batch.id}}">View</a></th>
69
+ <td>{{ batch.reference }}</td>
70
+ <td>{{ batch.contract.name }}</td>
71
+ </tr>
72
+ {% endfor %}
73
+ </tbody>
74
+ </table>
75
+
76
+ <table>
77
+ <caption>Gas</caption>
78
+ <thead>
79
+ <tr>
80
+ <th>View</th>
81
+ <th>Reference</th>
82
+ <th>Contract Name</th>
83
+ </tr>
84
+ </thead>
85
+ <tbody>
86
+ {% for batch in g_fake_batches %}
87
+ <tr>
88
+ <th><a href="/g/batches/{{batch.id}}">View</a></th>
89
+ <td>{{ batch.reference }}</td>
90
+ <td>{{ batch.g_contract.name }}</td>
91
+ </tr>
92
+ {% endfor %}
93
+ </tbody>
94
+ </table>
95
+
96
+ {% else %}
97
+ <p>Importer not present.</p>
98
+ {% endif %}
99
+ {% endblock %}
@@ -214,6 +214,7 @@
214
214
  <li><a href="/national_grid">Automatic Importer: National Grid</a></li>
215
215
  <li><a href="/e/lcc">Automatic Importer: Low Carbon Contracts</a></li>
216
216
  <li><a href="/e/elexon">Automatic Importer: Elexon</a></li>
217
+ <li><a href="/fake_batch_updater">Automatic Fake Batch Updater</a></li>
217
218
  </ul>
218
219
 
219
220
  <ul>
@@ -0,0 +1,97 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}
4
+ &raquo; Report Runs &raquo; {{run.id}}
5
+ {% endblock %}
6
+
7
+ {% block nav %}
8
+ <a href="/report_runs">Report Runs</a> &raquo; {{run.id}}
9
+ {% endblock %}
10
+
11
+ {% block content %}
12
+ <table class="sticky">
13
+ <caption>Missing Electricity Bills Summary</caption>
14
+ <thead>
15
+ <tr>
16
+ <th>Date Created</th>
17
+ <th>Created By</th>
18
+ <th>State</th>
19
+ <th>Number Of Rows</th>
20
+ <th>Delete</th>
21
+ <th>Download Spreadsheet</th>
22
+ </tr>
23
+ </thead>
24
+ <tbody>
25
+ <tr>
26
+ <td>{{run.date_created|hh_format}}</td>
27
+ <td>{{run.creator}}</td>
28
+ <td>{{run.state}}</td>
29
+ <td>{{rows|length}}</td>
30
+ <td>
31
+ <form hx-delete="/report_runs/{{run.id}}/edit"
32
+ hx-confirm="Are you sure you want to delete this report run?">
33
+ <fieldset style="border: none;">
34
+ <input type="submit" name="delete" value="Delete">
35
+ </fieldset>
36
+ </form>
37
+ </td>
38
+ <td>
39
+ <a href="/report_runs/{{run.id}}/spreadsheet">Download</a>
40
+ </td>
41
+ </tr>
42
+ </tbody>
43
+ </table>
44
+
45
+ <table class="sticky">
46
+ <caption>
47
+ Missing Electricity Bills
48
+ </caption>
49
+ <thead>
50
+ <tr>
51
+ <th>Contract</th>
52
+ <th>Month</th>
53
+ <th>Supply</th>
54
+ <th>Import MPAN</th>
55
+ <th>Export MPAN</th>
56
+ <th>Account</th>
57
+ <th>Site</th>
58
+ </tr>
59
+ </thead>
60
+ <tbody>
61
+ {% for row in rows %}
62
+ {% set values = row.data['values'] %}
63
+ <tr>
64
+ <td>
65
+ {% if values['market_role_code'] == 'M' %}
66
+ <a href="/e/mop_contracts/{{values['contract_id']}}">{{values['contract_name']}}</a>
67
+ {% elif values['market_role_code'] == 'C' %}
68
+ <a href="/e/dc_contracts/{{values['contract_id']}}">{{values['contract_name']}}</a>
69
+ {% elif values['market_role_code'] == 'X' %}
70
+ <a href="/e/supplier_contracts/{{values['contract_id']}}">{{values['contract_name']}}</a>
71
+ {% endif %}
72
+ </td>
73
+ <td>{{values['month_start'][:7]}}</td>
74
+ <td>
75
+ <a href="/e/supplies/{{values['supply_id']}}">View</a>
76
+ </td>
77
+ <td>
78
+ {% if values['imp_mpan_core'] is not none %}
79
+ {{values['imp_mpan_core']}}
80
+ {% endif %}
81
+ </td>
82
+ <td>
83
+ {% if values['exp_mpan_core'] is not none %}
84
+ {{values['exp_mpan_core']}}
85
+ {% endif %}
86
+ </td>
87
+ <td>{{values['account']}}</td>
88
+ <td>
89
+ <a href="/sites/{{values['site_id']}}" title="{{values['site_name']}}">
90
+ {{values['site_code']}}
91
+ </a>
92
+ </td>
93
+ </tr>
94
+ {% endfor %}
95
+ </tbody>
96
+ </table>
97
+ {% endblock %}
chellow/utils.py CHANGED
@@ -67,12 +67,15 @@ def req_zish(name):
67
67
  def req_date(prefix, resolution="minute"):
68
68
  year = req_int(f"{prefix}_year")
69
69
  month = req_int(f"{prefix}_month")
70
- day = req_int(f"{prefix}_day")
71
70
 
72
71
  try:
73
- if resolution == "day":
72
+ if resolution == "month":
73
+ d = ct_datetime(year, month)
74
+ elif resolution == "day":
75
+ day = req_int(f"{prefix}_day")
74
76
  d = ct_datetime(year, month, day)
75
77
  elif resolution == "minute":
78
+ day = req_int(f"{prefix}_day")
76
79
  hour = req_int(f"{prefix}_hour")
77
80
  minute = req_int(f"{prefix}_minute")
78
81
  d = ct_datetime(year, month, day, hour, minute)
chellow/views.py CHANGED
@@ -72,6 +72,7 @@ import chellow.national_grid
72
72
  import chellow.rate_server
73
73
  from chellow.edi_lib import SEGMENTS, parse_edi
74
74
  from chellow.models import (
75
+ Batch,
75
76
  BillType,
76
77
  Channel,
77
78
  Comm,
@@ -79,6 +80,7 @@ from chellow.models import (
79
80
  Cop,
80
81
  EnergisationStatus,
81
82
  Era,
83
+ GBatch,
82
84
  GContract,
83
85
  GEra,
84
86
  GExitZone,
@@ -156,6 +158,41 @@ def configuration():
156
158
  return redirect(f"/non_core_contracts/{config.id}")
157
159
 
158
160
 
161
+ @home.route("/fake_batch_updater")
162
+ def fake_batch_updater_get():
163
+ importer = chellow.fake_batch_updater.importer
164
+ config = Contract.get_non_core_by_name(g.sess, "configuration")
165
+ props = config.make_properties()
166
+
167
+ e_fake_batches = g.sess.scalars(
168
+ select(Batch).where(
169
+ Batch.reference.startswith("e_fake_batch_"),
170
+ )
171
+ )
172
+
173
+ g_fake_batches = g.sess.scalars(
174
+ select(GBatch).where(
175
+ GBatch.reference.startswith("g_fake_batch_"),
176
+ )
177
+ )
178
+
179
+ return render_template(
180
+ "fake_batch_updater.html",
181
+ importer=importer,
182
+ config_state=config.make_state(),
183
+ config_properties=props.get("fake_batch_updater", {}),
184
+ e_fake_batches=e_fake_batches,
185
+ g_fake_batches=g_fake_batches,
186
+ )
187
+
188
+
189
+ @home.route("/fake_batch_updater", methods=["POST"])
190
+ def fake_batch_updater_post():
191
+ importer = chellow.fake_batch_updater.importer
192
+ importer.go()
193
+ return redirect("/fake_batch_updater", 303)
194
+
195
+
159
196
  @home.route("/health")
160
197
  def health():
161
198
  return Response("healthy\n", mimetype="text/plain")
@@ -1501,6 +1538,19 @@ def report_run_get(run_id):
1501
1538
  run=run,
1502
1539
  rows=rows,
1503
1540
  )
1541
+ elif run.name == "missing_e_bills":
1542
+ rows = g.sess.scalars(
1543
+ select(ReportRunRow)
1544
+ .filter(ReportRunRow.report_run == run)
1545
+ .order_by(
1546
+ ReportRunRow.data["values"]["month_start"],
1547
+ )
1548
+ ).all()
1549
+ return render_template(
1550
+ "report_run_missing_e_bills.html",
1551
+ run=run,
1552
+ rows=rows,
1553
+ )
1504
1554
 
1505
1555
  else:
1506
1556
  order_by = "row.id"
@@ -1524,12 +1574,12 @@ def report_run_get(run_id):
1524
1574
  )
1525
1575
 
1526
1576
 
1527
- @home.route("/report_runs/<int:run_id>", methods=["POST"])
1528
- def report_run_post(run_id):
1577
+ @home.route("/report_runs/<int:run_id>", methods=["DELETE"])
1578
+ def report_run_delete(run_id):
1529
1579
  run = g.sess.query(ReportRun).filter(ReportRun.id == run_id).one()
1530
1580
  run.delete(g.sess)
1531
1581
  g.sess.commit()
1532
- return redirect("/report_runs", 303)
1582
+ return hx_redirect("/report_runs", 303)
1533
1583
 
1534
1584
 
1535
1585
  @home.route("/report_runs/<int:run_id>/spreadsheet")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chellow
3
- Version: 1744710468.0.0
3
+ Version: 1745313690.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)
@@ -1,9 +1,10 @@
1
- chellow/__init__.py,sha256=S1IjQChkEMA_MfzE5F1pPP-54bz_CRH4KE9X6SgX8bI,9722
1
+ chellow/__init__.py,sha256=yBjAoOWh4NlA847_j4hiI7O6YyiEysf2X1y873JS-aA,9792
2
2
  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=dixp-O0MF2_mlwrnKx3D9DH09Qu05BjTo0rZfigTjR4,5534
6
6
  chellow/edi_lib.py,sha256=alu20x9ZX06iPfnNI9dEJzuP6RIf4We3Y_M_bl7RrcY,51789
7
+ chellow/fake_batch_updater.py,sha256=UdI1ygrrU5a9UZVxc9j9Lq-tIejHFGalG_4g-rPsriI,10596
7
8
  chellow/general_import.py,sha256=y8X-FzQJzVrfvVMyErNHns2MGI511KwDC19AjIX3nTk,65325
8
9
  chellow/models.py,sha256=XD5wl3Pa8vZFGA0aB1Pu-xJs3iBoBoeX44E8Myho_68,244648
9
10
  chellow/national_grid.py,sha256=czwIZqzJndSGhEMQ5YzI6hRBhvjkM6VRVYXybf4_KXg,4377
@@ -11,8 +12,8 @@ chellow/proxy.py,sha256=cVXIktPlX3tQ1BYcwxq0nJXKE6r3DtFTtfFHPq55HaM,1351
11
12
  chellow/rate_server.py,sha256=fg-Pf_9Hk3bXmC9riPQNGQxBvLvBa_WtNYdwDCjnCSg,5678
12
13
  chellow/rrun.py,sha256=1Kt2q_K9UoDG_nsZz-Q6XJiMNKroWqlqFdxn2M6Q8CA,2088
13
14
  chellow/testing.py,sha256=Dj2c1NX8lVlygueOrh2eyYawLW6qKEHxNhXVVUaNRO0,3637
14
- chellow/utils.py,sha256=Ej7dsbQ6Ee8X2aZ7B2Vs-hUFCsMABioAdOV1DJjwY-0,19293
15
- chellow/views.py,sha256=2WCU-_8lDuSDROvNlCGsAQVLSMh5qrD2wmeSr2G-umA,83872
15
+ chellow/utils.py,sha256=i3GQK9MIcweosZk2gi-nX_IFq2DxURAJDyNoLBg6YwM,19421
16
+ chellow/views.py,sha256=GVmmChaMaFmZmcsgvR9Z5xfSh4Y1mPEBDR90kiZH5Xk,85292
16
17
  chellow/e/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
18
  chellow/e/aahedc.py,sha256=d2usudp7KYWpU6Pk3fal5EQ47EbvkvKeaFGylnb3NWw,606
18
19
  chellow/e/bill_importer.py,sha256=7UcnqNlKbJc2GhW9gy8sDp9GuqambJVpZLvbafOZztA,7411
@@ -21,7 +22,7 @@ chellow/e/bsuos.py,sha256=hdP9vnOJSuZl46OAkJeUg1XJYvYIBj4J6Sqce1Hy9Vs,15542
21
22
  chellow/e/ccl.py,sha256=30dh_SvlgzsTQPPAJNZWILaMvbeDsv9-P-S1JxS5_SQ,3184
22
23
  chellow/e/cfd.py,sha256=CWLdYeNjgqT6Ro8YRf4vhwXIAJ2aV4Wi6HLNClVSeaQ,14260
23
24
  chellow/e/computer.py,sha256=aGti5aXRTdC85M-WRiAoZkC_76sqfAiP-79ggWZF09Q,68020
24
- chellow/e/dno_rate_parser.py,sha256=A5TP6KjyfT5lVWh7dX4SiXRi6wnf2lGv-H_T4Sod8CI,21731
25
+ chellow/e/dno_rate_parser.py,sha256=NOVfS9HRDsc0rO282hU-IdrcuvMlC7VE8RySqd_5eT0,21762
25
26
  chellow/e/duos.py,sha256=RHrn93I1ASO2uYkuF18qlhG4p-jpuJhd_g3o69wtP4U,31004
26
27
  chellow/e/elexon.py,sha256=ALhXS9Es7PV0z9ukPbIramn3cf3iLyFi-PMWPSm5iOs,5487
27
28
  chellow/e/energy_management.py,sha256=aXC2qlGt3FAODlNl_frWzVYAQrJLP8FFOiNX3m-QE_Y,12388
@@ -58,8 +59,8 @@ chellow/e/bill_parsers/gdf_csv.py,sha256=ZfK3Oc6oP28p_P9DIevLNB_zW2WLcEJ3Lvb1gL3
58
59
  chellow/e/bill_parsers/haven_csv.py,sha256=0uENq8IgVNqdxfBQMBxLTSZWCOuDHXZC0xzk52SbfyE,13652
59
60
  chellow/e/bill_parsers/haven_edi.py,sha256=YGPHRxPOhje9s32jqPHHELni2tooOYj3cMC_qaZVPq4,16107
60
61
  chellow/e/bill_parsers/haven_edi_tprs.py,sha256=ZVX9CCqUybsot_Z0BEOJPvl9x5kSr7fEWyuJXvZDcz4,11841
61
- chellow/e/bill_parsers/mm.py,sha256=MmGPNWYPY9UURvKwAHYpWQF2C8YuF8D1IVH9xptemDs,11227
62
- chellow/e/bill_parsers/nonsettlement_dc_stark_xlsx.py,sha256=yogXTuQHGRL7IiqvRWr2C9V24ez1j9Yx0128UygPE_k,4723
62
+ chellow/e/bill_parsers/mm.py,sha256=tv7SAIbwFJYCiHz1ZwTV9QJeGkSo3-KuOp--DUimzLI,11227
63
+ chellow/e/bill_parsers/nonsettlement_dc_stark_xlsx.py,sha256=YvQ0Q6HlZ4XSk6Phx1UWaTSj8FREVjwQe8nrpiLVzbU,5458
63
64
  chellow/e/bill_parsers/settlement_dc_stark_xlsx.py,sha256=osCpUYUdLcPtlo7ngXWGw0ImnxssLa1fOSejMwe51-k,6381
64
65
  chellow/e/bill_parsers/sse_edi.py,sha256=L85DOfNkqexeEIEr8pCBn_2sHJI-zEaw6cogpE3YyYM,15204
65
66
  chellow/e/bill_parsers/sww_xls.py,sha256=QEjiuvwvr5FuWCfqqVw8LaA_vZyAKsvRAS5fw3xtFhM,7533
@@ -106,6 +107,7 @@ chellow/reports/report_g_supplies_snapshot.py,sha256=9xB6RDrnbgxuomMcP1b1yEP4kOn
106
107
  chellow/reports/report_g_supply_virtual_bill.py,sha256=EaYrB8PHJIXrUuhiZ7dwUlbNBkuyJebQHrdc308_z1o,3653
107
108
  chellow/reports/report_g_virtual_bills.py,sha256=20vHa5LGQwOAlJlaGJaGszZrrbT0PMOZJf6hSxU2hIQ,4528
108
109
  chellow/reports/report_g_virtual_bills_hh.py,sha256=gaiLEmKTpq6JsfZ1p0SdCDuPvzvigXp6z88gHRCA63w,3416
110
+ chellow/reports/report_missing_bills.py,sha256=3uWA6Wyskn30tUDOV_W6u_009flfKzua7vDh_jsBDaM,5956
109
111
  chellow/reports/report_sscs.py,sha256=fQWyVG-gdg37DyNHgpNARpSxIwTl7mCn20fDLwx9oHg,3214
110
112
  chellow/reports/report_supply_contacts.py,sha256=pvwlInaPYV_pa9MMK6vh854plHFwv3m5zo5xulR1g5I,3599
111
113
  chellow/static/css/chellow.css,sha256=dnkuj9Z1BCOV_L2Y26lDd2QlTmFFhATa1YvwPVch1Oc,5375
@@ -121,9 +123,10 @@ chellow/templates/chain.html,sha256=iOnSoDQOO_-1tfcxUHQKDAUnV4QbCYu5PPNmS4RkOg4,
121
123
  chellow/templates/csv_sites_monthly_duration.html,sha256=59gErG6KwA57z-qh2EJsVGvTUazqm85CSXefSfqPWcQ,486
122
124
  chellow/templates/downloads.html,sha256=R9QPcFz-PLJOX7rDlmquIk-Hp9Iq-thzWCTfOexSpXg,937
123
125
  chellow/templates/edi_viewer.html,sha256=szUthgHOdph6Of-7f_1LeC_zYlNJaMeS5ctn6xTAeiM,1437
126
+ chellow/templates/fake_batch_updater.html,sha256=aRQbxtNUlIzxwgSUy2pr-Km5NbhZkse4WSBtlqFIJMg,1885
124
127
  chellow/templates/general_import.html,sha256=9ezzieDjaPBZ0nUJkMkzoDxWVzYtr4D-Dr2UCA5xV8U,1370
125
128
  chellow/templates/general_imports.html,sha256=9sYN7FdzfxFypAUbUJ4VbhU3WhJrjArtqneohgX1hGE,13171
126
- chellow/templates/home.html,sha256=illtSabepcScYwmKss0o2gwvjXGBtr6LkfwmeL6ezSA,5655
129
+ chellow/templates/home.html,sha256=EZbvIvNw0RiuIlaUTOojYSZMTd3VMbTkulABgTB_lAc,5732
127
130
  chellow/templates/input_date.html,sha256=rpgB5n0LfN8Y5djN_ZiuSxqdskxzCoKrEqI7hyJkVQo,1248
128
131
  chellow/templates/local_report.html,sha256=pV7_0QwyQ-D3OS9LXrly5pq3qprZnwTCoq6vCnMTkS4,1332
129
132
  chellow/templates/local_reports.html,sha256=4wbfVkY4wUfSSfWjxqIsvCpIsa9k7H_dGAjznrG5jNM,701
@@ -143,6 +146,7 @@ chellow/templates/report_run_asset_comparison.html,sha256=VYCCUmIC7Mfe7uuaAHb6ih
143
146
  chellow/templates/report_run_bill_check.html,sha256=H2ayoBL7EgKMq2Nwq5VjE_TNvcIKcqeCm0alQWLIw78,5084
144
147
  chellow/templates/report_run_ecoes_comparison.html,sha256=VmkT5ypWLP8qZS6NbDTC4yDaG7mnUlxZz7EV8xkHGZw,4086
145
148
  chellow/templates/report_run_g_bill_check.html,sha256=tOXl_mjR__foYKiOYflJbK-459actAtjzv8rfuL3TwM,4851
149
+ chellow/templates/report_run_missing_e_bills.html,sha256=l5idQhfaNhMvvzIRv-iqCpeDnYl_wgs6-mZMBOmuyR8,2447
146
150
  chellow/templates/report_run_monthly_duration_org.html,sha256=gGNGJ4Q50q4BtIMi98rhO-7NqRHcsFUmbj2qzeOLejw,1713
147
151
  chellow/templates/report_run_row.html,sha256=bmtcdqJaS1CXpL0i8PuqvmeF98jKNYX5-mnQu-OuDKQ,3791
148
152
  chellow/templates/report_run_row_bill_check.html,sha256=aC2LMu_6NvmTN3ZdxHJPPPczyxPN6hg0F-PPcqIWUws,4683
@@ -198,7 +202,7 @@ chellow/templates/e/dc_bill_add.html,sha256=73Sn_MKBsUuYYnDfUMCdX1Dul6GimMC9YXk6
198
202
  chellow/templates/e/dc_bill_edit.html,sha256=0GsN-ZIc4q-z_xs8igC2ZS6t4soo2SvB3qRA6iC-AuM,2707
199
203
  chellow/templates/e/dc_bill_import.html,sha256=NHjMSoFizvFQIaPWuVE3nTCtMDTzJB0XmH8jXfV1tiA,2188
200
204
  chellow/templates/e/dc_bill_imports.html,sha256=lCaUR47r9KPr0VrQhEvVEaKexM5R_nmkxtzgpWZ0e9s,2135
201
- chellow/templates/e/dc_contract.html,sha256=b6DLDrGdzN9Rri56pFvfpE7QZKYXGHib2veYAztuHlg,2352
205
+ chellow/templates/e/dc_contract.html,sha256=MqkRck0qCgxXrHSbYTfyqjf_bAbIfMy93b9kdqH0pCQ,2746
202
206
  chellow/templates/e/dc_contract_edit.html,sha256=IObmbHQmeZ_LSpnYgabmhoSNUR3aPmm-Jk5nJLM_u74,1706
203
207
  chellow/templates/e/dc_contract_hh_import.html,sha256=JncR3L6cOK4jghsGyr-itEqlIemXBXt3kL09wpqnQSE,856
204
208
  chellow/templates/e/dc_contract_hh_imports.html,sha256=eXFDGyzSgag4JRism81_p5yTzQOjCIXaVkQ8tl3dDcM,8172
@@ -255,7 +259,7 @@ chellow/templates/e/mop_bill_add.html,sha256=fjoJj6QvcCk28rPD0Z6tIXrFYZ7ygN6lIXu
255
259
  chellow/templates/e/mop_bill_edit.html,sha256=Hld9b-ckLCd9N7hleugg0BuwsZrmkvDeiJ5yLW8z-Js,2748
256
260
  chellow/templates/e/mop_bill_import.html,sha256=sSVCZnarru0GKYFW1uuP0f1Ix9iNFDtEypQ93hqeUsk,3201
257
261
  chellow/templates/e/mop_bill_imports.html,sha256=jIhWaOuk4hYj9jhXBUxQ7C8v9HJljWfOGGeu1rZa1PI,1610
258
- chellow/templates/e/mop_contract.html,sha256=CyuBhj6da1-TiaomXSDD8JD449Ksu8QvIZODHmsE804,1748
262
+ chellow/templates/e/mop_contract.html,sha256=M6_pZuNpjdh9EaSGA5UsqSFIAvB0ABOehJ7s5oQ3pvk,2144
259
263
  chellow/templates/e/mop_contract_add.html,sha256=qyjDNz_pOVgfrsKaju0KwxBKJnWF9RxUa-8gUtaOc60,738
260
264
  chellow/templates/e/mop_contract_edit.html,sha256=4DbyiKbWRdliQGPh0wlh4dEuhD-R4-3XRzXoNMLfFD4,1719
261
265
  chellow/templates/e/mop_contracts.html,sha256=2TPVkeUefsYOVZIiiLMPJgwcrxhvjk8Io4UKvgdQ2AY,768
@@ -315,7 +319,7 @@ chellow/templates/e/supplier_bill_add.html,sha256=BsD-Zh7d9auiqJ61VPHiQrP8u8rTcw
315
319
  chellow/templates/e/supplier_bill_edit.html,sha256=oxZrMcMwrvluJSPxD4yfM9mWNeugoguAwT_ai9Ynl88,2732
316
320
  chellow/templates/e/supplier_bill_import.html,sha256=2_VvBoNE838UwuN0AiMwIrzqxQmWBo5DGY0lY833Bpk,5265
317
321
  chellow/templates/e/supplier_bill_imports.html,sha256=9iTNGWKn9XjQTBP1Sepbo0QVNlgKh7EfizXapam7I6s,9356
318
- chellow/templates/e/supplier_contract.html,sha256=oVGrYGIbCb-7MmpFQaXeTNRKBKy8GoGCWICWgo5E4xI,2863
322
+ chellow/templates/e/supplier_contract.html,sha256=TOm5ey5b1tm0A-8g2EmZhY7WddbGDih1B4ieJknA8Ns,3254
319
323
  chellow/templates/e/supplier_contract_add.html,sha256=gsozEtF24lzYi_Bb4LTenvh62tCt7dQ4CwaIz7rFck4,899
320
324
  chellow/templates/e/supplier_contract_edit.html,sha256=Afwtn0l8XRbt86bMOru2AjjvctoUkieD052aY0B2mDc,1463
321
325
  chellow/templates/e/supplier_contracts.html,sha256=VwWD4q88Fynz7vioFSAsyH6RR_1SyQQl6bQwzL-W1m0,1508
@@ -378,6 +382,6 @@ chellow/templates/g/supply_note_edit.html,sha256=b8mB6_ucBwoljp03iy6AgVaZUhGw3-1
378
382
  chellow/templates/g/supply_notes.html,sha256=6epNmZ3NKdXZz27fvmRUGeffg_oc1kmwuBeyRzQe3Rg,854
379
383
  chellow/templates/g/unit.html,sha256=KouNVU0-i84afANkLQ_heJ0uDfJ9H5A05PuLqb8iCN8,438
380
384
  chellow/templates/g/units.html,sha256=p5Nd-lAIboKPEOO6N451hx1bcKxMg4BDODnZ-43MmJc,441
381
- chellow-1744710468.0.0.dist-info/METADATA,sha256=iMSbXSKlBSPZZa8DhxsOskSD_yN4Q40MZJBuA_CXHf4,12238
382
- chellow-1744710468.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
383
- chellow-1744710468.0.0.dist-info/RECORD,,
385
+ chellow-1745313690.0.0.dist-info/METADATA,sha256=6L9prKczSdFxE1p_b8WJ_f16sePhQBrdB5ZLbBmbnTk,12238
386
+ chellow-1745313690.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
387
+ chellow-1745313690.0.0.dist-info/RECORD,,