Compare commits

..

No commits in common. "f27e956b6fce699ecfda7f403ae5e031dfc16ece" and "a555dc9c6131784c98bcc7f7bd8c5e1a2e6c6372" have entirely different histories.

3 changed files with 31 additions and 111 deletions

View File

@ -482,15 +482,7 @@ def settings():
if "autotask_default_ticket_status" in request.form: if "autotask_default_ticket_status" in request.form:
try: try:
form_value = request.form.get("autotask_default_ticket_status", "").strip() settings.autotask_default_ticket_status = int(request.form.get("autotask_default_ticket_status") or 0) or None
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): except (ValueError, TypeError):
pass pass
@ -723,34 +715,6 @@ def settings():
autotask_ticket_statuses = [] autotask_ticket_statuses = []
autotask_last_sync_at = getattr(settings, "autotask_reference_last_sync_at", None) 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: try:
if getattr(settings, "autotask_cached_queues_json", None): if getattr(settings, "autotask_cached_queues_json", None):
autotask_queues = json.loads(settings.autotask_cached_queues_json) or [] autotask_queues = json.loads(settings.autotask_cached_queues_json) or []

View File

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

View File

@ -27,11 +27,6 @@ This file documents all changes made to this project via Claude Code.
- CSS include added to `<head>` section - CSS include added to `<head>` section
- Banner placed directly after `<body>` tag, before navbar - Banner placed directly after `<body>` tag, before navbar
- The banner displays "SANDBOX" in uppercase with letter-spacing for clear visibility across all pages - 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 ### Changed
- Renamed "Refresh" button to "Search" in Link existing Autotask ticket modal for better clarity (the button performs a search operation) - Renamed "Refresh" button to "Search" in Link existing Autotask ticket modal for better clarity (the button performs a search operation)
@ -41,38 +36,6 @@ 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 - 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 - Opens in new tab with proper URL format including workspace and ticket ID parameters
- Styled as small outline button to maintain compact layout - 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] ## [2026-02-05]