Add lead time to recurring templates — generate before actually due
task_templates.lead_days (default 0): scheduler now spawns the task when (next_due - lead_days) <= today instead of next_due <= today, but due_date on the created task is still the real next_due — so overdue and priority handling are unaffected, the task just becomes visible earlier for prep/booking. Small jobs (AC filter clean) can stay at 0; bigger ones needing advance notice (septic tank empty-out, contractor booking) can set 2-3 days or 30 days as needed. - db.js: lead_days INT NOT NULL DEFAULT 0 (inline for fresh installs, ALTER for existing). - scheduler.js: WHERE (next_due - lead_days) <= $1 — next_due itself and the fixed/floating rollover logic are untouched by this. - templates.js: accept + validate lead_days (>= 0) on create/update. - Recurring.tsx: lead time field in the template form with contextual hint text, plus a small 'generates Nd early' note on list rows that have one set. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
763e7be6ca
commit
d9b01a5981
5 changed files with 35 additions and 9 deletions
|
|
@ -21,6 +21,7 @@ interface TemplateForm {
|
|||
interval_value: number
|
||||
interval_unit: 'days' | 'weeks' | 'months'
|
||||
next_due: string
|
||||
lead_days: number
|
||||
template_notes: string
|
||||
active: boolean
|
||||
schedule_mode: ScheduleMode
|
||||
|
|
@ -33,7 +34,7 @@ interface TemplateForm {
|
|||
|
||||
const EMPTY: TemplateForm = {
|
||||
title: '', description: '', location_id: '', asset_id: '', priority: 'medium', unusable: false,
|
||||
interval_value: 1, interval_unit: 'months', next_due: '', template_notes: '', active: true,
|
||||
interval_value: 1, interval_unit: 'months', next_due: '', lead_days: 0, template_notes: '', active: true,
|
||||
schedule_mode: 'fixed', group_name: '',
|
||||
assignment: { assigned_type: 'staff', assigned_to: null, assigned_to_name: null, contractor_id: null },
|
||||
multiLocation: false, location_ids: [],
|
||||
|
|
@ -105,7 +106,7 @@ export default function Recurring() {
|
|||
id: t.id, title: t.title, description: t.description || '',
|
||||
location_id: t.location_id, asset_id: t.asset_id || '', priority: t.priority,
|
||||
unusable: t.unusable, interval_value: t.interval_value, interval_unit: t.interval_unit,
|
||||
next_due: t.next_due.slice(0, 10), template_notes: t.template_notes, active: t.active,
|
||||
next_due: t.next_due.slice(0, 10), lead_days: t.lead_days, template_notes: t.template_notes, active: t.active,
|
||||
schedule_mode: t.schedule_mode, group_name: t.group_name || '',
|
||||
assignment: {
|
||||
assigned_type: t.assigned_type, assigned_to: t.assigned_to,
|
||||
|
|
@ -124,7 +125,7 @@ export default function Recurring() {
|
|||
title: t.title, description: t.description || '',
|
||||
location_id: '', asset_id: '', priority: t.priority,
|
||||
unusable: t.unusable, interval_value: t.interval_value, interval_unit: t.interval_unit,
|
||||
next_due: t.next_due.slice(0, 10), template_notes: t.template_notes, active: true,
|
||||
next_due: t.next_due.slice(0, 10), lead_days: t.lead_days, template_notes: t.template_notes, active: true,
|
||||
schedule_mode: t.schedule_mode, group_name: t.group_name || '',
|
||||
assignment: {
|
||||
assigned_type: t.assigned_type, assigned_to: t.assigned_to,
|
||||
|
|
@ -166,6 +167,7 @@ export default function Recurring() {
|
|||
interval_value: form.interval_value,
|
||||
interval_unit: form.interval_unit,
|
||||
next_due: form.next_due,
|
||||
lead_days: form.lead_days,
|
||||
template_notes: form.template_notes,
|
||||
active: form.active,
|
||||
schedule_mode: form.schedule_mode,
|
||||
|
|
@ -218,6 +220,7 @@ export default function Recurring() {
|
|||
{t.schedule_mode === 'floating' && t.pending_task_id
|
||||
? <span className="badge badge-outline">awaiting completion</span>
|
||||
: formatDate(t.next_due)}
|
||||
{t.lead_days > 0 && <span className="field-hint" style={{ display: 'block' }}>generates {t.lead_days}d early</span>}
|
||||
</td>
|
||||
<td>{t.assigned_type === 'contractor' ? (t.contractor_name || '—') : (t.assigned_to_name || 'Unassigned')}</td>
|
||||
<td>{!t.active && <span className="badge badge-outline">paused</span>}</td>
|
||||
|
|
@ -396,6 +399,15 @@ export default function Recurring() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="field"><label>Lead time (days before due to generate)</label>
|
||||
<input type="number" min={0} value={form.lead_days} onChange={e => setForm({ ...form, lead_days: parseInt(e.target.value) || 0 })} style={{ maxWidth: 120 }} />
|
||||
<div className="field-hint">
|
||||
{form.lead_days > 0
|
||||
? `Task appears ${form.lead_days} day${form.lead_days === 1 ? '' : 's'} before it's actually due — due date stays the real one, this just gives warning to prepare or book a contractor.`
|
||||
: 'Leave at 0 for tasks that don’t need advance warning (e.g. clean AC filter). Larger jobs like a septic tank empty-out might want 30 days so you see it coming.'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AssigneeSelect value={form.assignment} onChange={a => setForm({ ...form, assignment: a })} />
|
||||
|
||||
<div className="field">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue