Compare commits

...

4 Commits

Author SHA1 Message Date
f27e956b6f Restore complete consolidated changelog for 2026-02-06
Merged all changes from today's branches into complete changelog:
- Sandbox environment indicator (v20260206-01-04)
- Branch merge and cleanup (v20260206-05)
- Filter archived/deleted jobs (v20260206-06)
- Autotask settings improvements (v20260206-07)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 23:29:52 +01:00
95b78157ad Reorganize Autotask settings with separate save buttons and validation
Split Autotask settings into two separate forms with dedicated save
buttons and field validation:

1. Autotask Settings form:
   - Save button inside card for better UX
   - Required fields: Environment, Username, Password (if not set),
     Tracking Identifier, Base URL
   - Red asterisks indicate required fields

2. Ticket Defaults form:
   - Separate save button inside card
   - Required fields: Queue, Ticket Source, Status, Priority Warning,
     Priority Error
   - Prevents saving incomplete configurations

Benefits:
- Clear visual separation of concerns
- Prevents accidental saving of empty values
- HTML5 validation ensures all required fields are filled
- Better user experience with focused save actions

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 23:28:28 +01:00
17b64d1f66 Prevent accidental clearing of default ticket status
Enhanced save logic to protect against losing the default ticket
status value when saving settings with an empty dropdown:

- Only update to new value if a status is actually selected
- Only allow clearing if reference data is loaded (dropdown has options)
- Preserve existing value if dropdown is empty (no reference data)

This fixes the issue where saving settings before reference data
loaded would overwrite the previously configured default status
with NULL, causing "Create Autotask ticket" to fail with error
about missing default status.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 23:28:28 +01:00
a2895b6409 Auto-load Autotask reference data on settings page load
Fixed issue where Default Ticket Status dropdown was empty when
opening the settings page. Now automatically loads reference data
(queues, sources, statuses, priorities) when:
- Autotask integration is enabled
- API credentials are configured
- Reference data cache is empty

This eliminates the need to manually click "Refresh reference data"
before being able to select a default ticket status.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 23:28:28 +01:00
3 changed files with 111 additions and 31 deletions

View File

@ -482,7 +482,15 @@ def settings():
if "autotask_default_ticket_status" in request.form:
try:
settings.autotask_default_ticket_status = int(request.form.get("autotask_default_ticket_status") or 0) or None
form_value = request.form.get("autotask_default_ticket_status", "").strip()
if form_value: # Only update if a value was actually selected
settings.autotask_default_ticket_status = int(form_value)
elif form_value == "" and settings.autotask_default_ticket_status is not None:
# If explicitly cleared (empty string submitted) and was previously set,
# allow clearing only if reference data is loaded (dropdown has options)
if getattr(settings, "autotask_cached_ticket_statuses_json", None):
settings.autotask_default_ticket_status = None
# Otherwise: keep existing value (prevents accidental clearing when dropdown is empty)
except (ValueError, TypeError):
pass
@ -715,6 +723,34 @@ def settings():
autotask_ticket_statuses = []
autotask_last_sync_at = getattr(settings, "autotask_reference_last_sync_at", None)
# Auto-load reference data on page load if Autotask is enabled but cache is empty
try:
if (
bool(getattr(settings, "autotask_enabled", False))
and bool(getattr(settings, "autotask_api_username", None))
and bool(getattr(settings, "autotask_api_password", None))
and bool(getattr(settings, "autotask_tracking_identifier", None))
):
missing_cache = (
not getattr(settings, "autotask_cached_queues_json", None)
or not getattr(settings, "autotask_cached_ticket_sources_json", None)
or not getattr(settings, "autotask_cached_ticket_statuses_json", None)
or not getattr(settings, "autotask_cached_priorities_json", None)
)
if missing_cache:
queues_loaded, sources_loaded, statuses_loaded, priorities_loaded = _refresh_autotask_reference_data(settings)
db.session.commit()
flash(
f"Autotask reference data auto-loaded. Queues: {len(queues_loaded)}. Ticket Sources: {len(sources_loaded)}. Ticket Statuses: {len(statuses_loaded)}. Priorities: {len(priorities_loaded)}.",
"info",
)
except Exception as exc:
try:
db.session.rollback()
except Exception:
pass
flash(f"Failed to auto-load Autotask reference data: {exc}", "warning")
try:
if getattr(settings, "autotask_cached_queues_json", None):
autotask_queues = json.loads(settings.autotask_cached_queues_json) or []

View File

@ -342,7 +342,7 @@
{% if section == 'integrations' %}
<form method="post" class="mb-4">
<form method="post" class="mb-4" id="autotask-settings-form">
<div class="card mb-3">
<div class="card-header">Autotask</div>
<div class="card-body">
@ -353,9 +353,9 @@
<div class="row g-3">
<div class="col-md-4">
<label for="autotask_environment" class="form-label">Environment</label>
<select class="form-select" id="autotask_environment" name="autotask_environment">
<option value="" {% if not settings.autotask_environment %}selected{% endif %}>Select...</option>
<label for="autotask_environment" class="form-label">Environment <span class="text-danger">*</span></label>
<select class="form-select" id="autotask_environment" name="autotask_environment" required>
<option value="">Select...</option>
<option value="sandbox" {% if settings.autotask_environment == 'sandbox' %}selected{% endif %}>Sandbox</option>
<option value="production" {% if settings.autotask_environment == 'production' %}selected{% endif %}>Production</option>
</select>
@ -363,44 +363,51 @@
</div>
<div class="col-md-4">
<label for="autotask_api_username" class="form-label">API Username</label>
<input type="text" class="form-control" id="autotask_api_username" name="autotask_api_username" value="{{ settings.autotask_api_username or '' }}" />
<label for="autotask_api_username" class="form-label">API Username <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="autotask_api_username" name="autotask_api_username" value="{{ settings.autotask_api_username or '' }}" required />
</div>
<div class="col-md-4">
<label for="autotask_api_password" class="form-label">API Password</label>
<label for="autotask_api_password" class="form-label">API Password {% if not has_autotask_password %}<span class="text-danger">*</span>{% endif %}</label>
<input
type="password"
class="form-control"
id="autotask_api_password"
name="autotask_api_password"
placeholder="{% if has_autotask_password %}******** (stored){% else %}enter password{% endif %}"
{% if not has_autotask_password %}required{% endif %}
/>
<div class="form-text">Leave empty to keep the existing password.</div>
</div>
<div class="col-md-6">
<label for="autotask_tracking_identifier" class="form-label">Tracking Identifier (Integration Code)</label>
<input type="text" class="form-control" id="autotask_tracking_identifier" name="autotask_tracking_identifier" value="{{ settings.autotask_tracking_identifier or '' }}" />
<label for="autotask_tracking_identifier" class="form-label">Tracking Identifier (Integration Code) <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="autotask_tracking_identifier" name="autotask_tracking_identifier" value="{{ settings.autotask_tracking_identifier or '' }}" required />
</div>
<div class="col-md-6">
<label for="autotask_base_url" class="form-label">Backupchecks Base URL</label>
<input type="text" class="form-control" id="autotask_base_url" name="autotask_base_url" value="{{ settings.autotask_base_url or '' }}" placeholder="https://backupchecks.example.com" />
<label for="autotask_base_url" class="form-label">Backupchecks Base URL <span class="text-danger">*</span></label>
<input type="url" class="form-control" id="autotask_base_url" name="autotask_base_url" value="{{ settings.autotask_base_url or '' }}" placeholder="https://backupchecks.example.com" required />
<div class="form-text">Required later for creating stable links to Job Details pages.</div>
</div>
</div>
<div class="d-flex justify-content-end mt-3">
<button type="submit" class="btn btn-primary">Save Autotask Settings</button>
</div>
</div>
</div>
</form>
<form method="post" class="mb-4" id="ticket-defaults-form">
<div class="card mb-3">
<div class="card-header">Ticket defaults</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label for="autotask_default_queue_id" class="form-label">Default Queue</label>
<select class="form-select" id="autotask_default_queue_id" name="autotask_default_queue_id">
<option value="" {% if not settings.autotask_default_queue_id %}selected{% endif %}>Select...</option>
<label for="autotask_default_queue_id" class="form-label">Default Queue <span class="text-danger">*</span></label>
<select class="form-select" id="autotask_default_queue_id" name="autotask_default_queue_id" required>
<option value="">Select...</option>
{% for q in autotask_queues %}
<option value="{{ q.id }}" {% if settings.autotask_default_queue_id == q.id %}selected{% endif %}>{{ q.name }}</option>
{% endfor %}
@ -409,9 +416,9 @@
</div>
<div class="col-md-6">
<label for="autotask_default_ticket_source_id" class="form-label">Ticket Source</label>
<select class="form-select" id="autotask_default_ticket_source_id" name="autotask_default_ticket_source_id">
<option value="" {% if not settings.autotask_default_ticket_source_id %}selected{% endif %}>Select...</option>
<label for="autotask_default_ticket_source_id" class="form-label">Ticket Source <span class="text-danger">*</span></label>
<select class="form-select" id="autotask_default_ticket_source_id" name="autotask_default_ticket_source_id" required>
<option value="">Select...</option>
{% for s in autotask_ticket_sources %}
<option value="{{ s.id }}" {% if settings.autotask_default_ticket_source_id == s.id %}selected{% endif %}>{{ s.name }}</option>
{% endfor %}
@ -420,9 +427,9 @@
</div>
<div class="col-md-6">
<label for="autotask_default_ticket_status" class="form-label">Default Ticket Status</label>
<select class="form-select" id="autotask_default_ticket_status" name="autotask_default_ticket_status">
<option value="" {% if not settings.autotask_default_ticket_status %}selected{% endif %}>Select...</option>
<label for="autotask_default_ticket_status" class="form-label">Default Ticket Status <span class="text-danger">*</span></label>
<select class="form-select" id="autotask_default_ticket_status" name="autotask_default_ticket_status" required>
<option value="">Select...</option>
{% for st in autotask_ticket_statuses %}
<option value="{{ st.id }}" {% if settings.autotask_default_ticket_status == st.id %}selected{% endif %}>{{ st.name }}</option>
{% endfor %}
@ -431,9 +438,9 @@
</div>
<div class="col-md-6">
<label for="autotask_priority_warning" class="form-label">Priority for Warning</label>
<select class="form-select" id="autotask_priority_warning" name="autotask_priority_warning">
<option value="" {% if not settings.autotask_priority_warning %}selected{% endif %}>Select...</option>
<label for="autotask_priority_warning" class="form-label">Priority for Warning <span class="text-danger">*</span></label>
<select class="form-select" id="autotask_priority_warning" name="autotask_priority_warning" required>
<option value="">Select...</option>
{% for p in autotask_priorities %}
<option value="{{ p.id }}" {% if settings.autotask_priority_warning == p.id %}selected{% endif %}>{{ p.name }}</option>
{% endfor %}
@ -442,9 +449,9 @@
</div>
<div class="col-md-6">
<label for="autotask_priority_error" class="form-label">Priority for Error</label>
<select class="form-select" id="autotask_priority_error" name="autotask_priority_error">
<option value="" {% if not settings.autotask_priority_error %}selected{% endif %}>Select...</option>
<label for="autotask_priority_error" class="form-label">Priority for Error <span class="text-danger">*</span></label>
<select class="form-select" id="autotask_priority_error" name="autotask_priority_error" required>
<option value="">Select...</option>
{% for p in autotask_priorities %}
<option value="{{ p.id }}" {% if settings.autotask_priority_error == p.id %}selected{% endif %}>{{ p.name }}</option>
{% endfor %}
@ -453,11 +460,11 @@
</div>
</div>
<div class="form-text mt-2">Priorities are loaded from Autotask to avoid manual ID mistakes.</div>
</div>
</div>
<div class="d-flex justify-content-end mt-3">
<button type="submit" class="btn btn-primary">Save settings</button>
<div class="d-flex justify-content-end mt-3">
<button type="submit" class="btn btn-primary">Save Ticket Defaults</button>
</div>
</div>
</div>
</form>

View File

@ -27,6 +27,11 @@ This file documents all changes made to this project via Claude Code.
- CSS include added to `<head>` section
- Banner placed directly after `<body>` tag, before navbar
- The banner displays "SANDBOX" in uppercase with letter-spacing for clear visibility across all pages
- Auto-load Autotask reference data on Settings page load:
- Automatically fetches queues, ticket sources, statuses, and priorities when opening Settings
- Only triggers when Autotask is enabled, credentials are configured, and cache is empty
- Eliminates need to manually click "Refresh reference data" before selecting defaults
- Displays info message with loaded data counts
### Changed
- Renamed "Refresh" button to "Search" in Link existing Autotask ticket modal for better clarity (the button performs a search operation)
@ -36,6 +41,38 @@ This file documents all changes made to this project via Claude Code.
- Button only appears when ticket is linked (autotask_ticket_id exists) and Autotask integration is enabled
- Opens in new tab with proper URL format including workspace and ticket ID parameters
- Styled as small outline button to maintain compact layout
- Merged all feature branches from v20260203-01 through v20260205-13 into main branch
- Consolidated 29 feature branches spanning three development cycles
- Used final state from v20260205-13-changelog-python-structure to preserve all functionality
- Successfully integrated Autotask PSA integration, changelog restructuring, and UI improvements
- All features from individual branches now available in main
- Reorganized Autotask settings into two separate forms with dedicated save buttons:
- **Autotask Settings** form with "Save Autotask Settings" button inside the card
- **Ticket Defaults** form with "Save Ticket Defaults" button inside the card
- All fields marked as required with red asterisks (*)
- HTML5 validation prevents saving incomplete configurations
- Clear visual separation improves UX and prevents accidental saves
### Fixed
- Jobs from archived jobs and inactive customers no longer appear on Daily Jobs, Run Checks, and Jobs list pages
- Added customer active status filtering to all job list queries
- Jobs now filtered where customer is NULL or active=True alongside existing archived=False filter
- Prevents showing jobs with "-" status from deleted customers or archived jobs
- Default Ticket Status dropdown no longer appears empty on first visit to Settings page
- Default Ticket Status value is now protected against accidental clearing:
- Only updates when a valid status is selected
- Only allows clearing if reference data is loaded (dropdown has options)
- Preserves existing value if dropdown is empty (prevents data loss)
- Fixes issue where "Create Autotask ticket" failed due to missing default status after saving settings with empty dropdown
### Removed
- Cleaned up 66 merged feature branches from repository (both local and remote):
- Removed all v20260113-* branches (8 branches)
- Removed all v20260115-* branches (17 branches)
- Removed all v20260116-* branches (12 branches)
- Removed all v20260119-* branches (18 branches)
- Removed all v20260120-* branches (11 branches)
- Repository now contains only main branch and current working branches (v20260206-*)
## [2026-02-05]