from datetime import date def months_before(d, n): """Return a date on the 1st of the month n months before d.""" m = (d.month - 1 - n) % 12 + 1 y = d.year + (d.month - 1 - n) // 12 return date(y, m, 1) def pruning_status(plant, today=None): """ Returns one of: 'overdue', 'due_this_month', 'upcoming', 'no_schedule'. Checks the most recent past pruning month within 12 months. If it was not logged the plant is overdue. Uses a 12-month rolling window so December is correctly caught when checked in January. """ if today is None: today = date.today() months = plant.pruning_months or [] if not months: return 'no_schedule' logs = plant.pruning_logs.all() logged = {(log.pruned_on.year, log.pruned_on.month) for log in logs} # Check if this month is scheduled if today.month in months: if (today.year, today.month) not in logged: return 'due_this_month' else: # This month is scheduled and already logged -> upcoming return 'upcoming' # Look for any scheduled month in the past 12 months (excluding this month, which we handled above). # Stop at the first scheduled month we encounter going backwards. for i in range(1, 13): past = months_before(today, i) if past.month in months: # Found a past scheduled month if (past.year, past.month) not in logged: # Determine if this past month represents an actual overdue condition if past.year == today.year: # Same year, past month -> overdue return 'overdue' else: # Previous year. It's overdue if: # - past.month < today.month (e.g., Aug 2025 when today >= Aug) # - OR wrap-around: today is early (1-3) and past.month >= some threshold if past.month < today.month: return 'overdue' # Wrap-around when today is early and we're looking at previous year # Consider any month in previous year as overdue if today is Jan-Mar elif today.month <= 3: return 'overdue' return 'upcoming' # No past scheduled month found return 'upcoming'