Jul 27 - Update code for scheduled tasks 2
This commit is contained in:
@@ -106,10 +106,32 @@ def _apply_recurrence(sched, form):
|
||||
sched.month_mode = sched.day_of_month = None
|
||||
sched.nth_week = sched.nth_weekday = None
|
||||
|
||||
# End date (phase44) — a boundary, not a cadence setting. A one-time
|
||||
# schedule has none: it ends by deactivating when it is completed.
|
||||
sched.end_date = form.end_date.data if sched.frequency != 'once' else None
|
||||
|
||||
# Snap the picked date forward onto the first matching occurrence.
|
||||
sched.next_due_date = sched.align_due_date(form.next_due_date.data)
|
||||
|
||||
|
||||
def _reject_if_past_end_date(sched, form):
|
||||
"""True (and a form error set) if the aligned first occurrence falls past
|
||||
the end date.
|
||||
|
||||
The form already rejects an end date earlier than the *picked* due date, but
|
||||
align_due_date() can push that date forward onto the recurrence rule — pick
|
||||
a Tuesday for a Mon/Wed/Fri schedule and the first occurrence is Wednesday.
|
||||
Without this check that combination would save as active with no occurrence
|
||||
it is ever allowed to run.
|
||||
"""
|
||||
if sched.is_within_end_date(sched.next_due_date):
|
||||
return False
|
||||
form.end_date.errors.append(
|
||||
f'With this recurrence the first occurrence falls on '
|
||||
f'{sched.next_due_date:%b %d, %Y}, after the end date.')
|
||||
return True
|
||||
|
||||
|
||||
def _open_inspection_ids(schedules):
|
||||
"""{schedule_id: inspection_id} for schedules with an inspection already
|
||||
in progress, so the UI offers Continue instead of a duplicate Start."""
|
||||
@@ -167,6 +189,10 @@ def index():
|
||||
def create():
|
||||
form = ScheduledInspectionForm()
|
||||
_populate_choices(form)
|
||||
# On a new schedule this date IS the start; on edit it is whatever the next
|
||||
# occurrence happens to be. One field, two meanings — so the label follows
|
||||
# the context instead of saying both at once.
|
||||
form.next_due_date.label.text = 'Start Date'
|
||||
if not form.next_due_date.data:
|
||||
form.next_due_date.data = now_eastern().date()
|
||||
|
||||
@@ -181,11 +207,18 @@ def create():
|
||||
created_by = current_user.id,
|
||||
)
|
||||
_apply_recurrence(sched, form)
|
||||
if _reject_if_past_end_date(sched, form):
|
||||
# sched was never added to the session — nothing to roll back.
|
||||
return render_template('scheduled_inspections/form.html',
|
||||
form=form, title='New Scheduled Inspection',
|
||||
projects=_active_contracts(),
|
||||
selected_project_id=_selected_project_id(form))
|
||||
db.session.add(sched)
|
||||
db.session.commit()
|
||||
log_action(ACTION_CREATE, 'ScheduledInspection', sched.id,
|
||||
f'{sched.template.name} @ {sched.facility.name}',
|
||||
f'freq={sched.recurrence_label}; due={sched.next_due_date}; '
|
||||
f'end={sched.end_date or "—"}; '
|
||||
f'inspector={sched.inspector_id}')
|
||||
logger.info('SCHED INSP | create | by=%s | id=%s', current_user.username, sched.id)
|
||||
|
||||
@@ -213,6 +246,7 @@ def edit(schedule_id):
|
||||
abort(404)
|
||||
form = ScheduledInspectionForm(obj=sched)
|
||||
_populate_choices(form)
|
||||
form.next_due_date.label.text = 'Next Due Date'
|
||||
if request.method == 'GET':
|
||||
# obj= copies the raw CSV column into a multi-select field; hand it the
|
||||
# parsed int list instead so the checkboxes pre-tick correctly.
|
||||
@@ -227,10 +261,20 @@ def edit(schedule_id):
|
||||
sched.notes = (form.notes.data or '').strip() or None
|
||||
sched.active = form.active.data
|
||||
_apply_recurrence(sched, form)
|
||||
if _reject_if_past_end_date(sched, form):
|
||||
# sched is a persistent object and has already been mutated — discard
|
||||
# those pending changes before re-rendering so nothing leaks out on
|
||||
# the next flush.
|
||||
db.session.rollback()
|
||||
return render_template('scheduled_inspections/form.html',
|
||||
form=form, title='Edit Scheduled Inspection',
|
||||
schedule=sched, projects=_active_contracts(),
|
||||
selected_project_id=_selected_project_id(form))
|
||||
db.session.commit()
|
||||
log_action(ACTION_UPDATE, 'ScheduledInspection', sched.id,
|
||||
f'{sched.template.name} @ {sched.facility.name}',
|
||||
f'freq={sched.recurrence_label}; due={sched.next_due_date}; '
|
||||
f'end={sched.end_date or "—"}; '
|
||||
f'active={sched.active}')
|
||||
|
||||
# Notify the inspector if the assignment changed to them.
|
||||
@@ -333,10 +377,23 @@ def run_reminders():
|
||||
abort(403)
|
||||
|
||||
today = now_eastern().date()
|
||||
sent = {'advance': 0, 'due': 0, 'overdue': 0}
|
||||
sent = {'advance': 0, 'due': 0, 'overdue': 0, 'expired': 0}
|
||||
|
||||
schedules = ScheduledInspection.query.filter_by(active=True).all()
|
||||
|
||||
# Expire schedules past their end date BEFORE any reminder work (phase44).
|
||||
# fulfill() closes out a schedule that reaches its boundary by being
|
||||
# completed; this covers the one that reaches it without ever being done —
|
||||
# otherwise it stays active and re-alerts as overdue indefinitely.
|
||||
live = []
|
||||
for s in schedules:
|
||||
if s.expire_if_past_end_date(today):
|
||||
sent['expired'] += 1
|
||||
logger.info('SCHED INSP | expired | id=%s | end=%s', s.id, s.end_date)
|
||||
else:
|
||||
live.append(s)
|
||||
schedules = live
|
||||
|
||||
# Cache admin/director recipients for overdue alerts
|
||||
managers = User.query.filter(
|
||||
User.role.in_(['admin', 'director']), User.active == True # noqa: E712
|
||||
@@ -395,6 +452,6 @@ def run_reminders():
|
||||
sent['overdue'] += 1
|
||||
|
||||
db.session.commit()
|
||||
logger.info('SCHED INSP | reminders | advance=%s due=%s overdue=%s',
|
||||
sent['advance'], sent['due'], sent['overdue'])
|
||||
logger.info('SCHED INSP | reminders | advance=%s due=%s overdue=%s expired=%s',
|
||||
sent['advance'], sent['due'], sent['overdue'], sent['expired'])
|
||||
return {'ok': True, 'sent': sent}, 200
|
||||
|
||||
Reference in New Issue
Block a user