- Introduced Technician Dashboard V1 (tech_v1_overview.html) with KPI cards and new cases overview. - Implemented Technician Dashboard V2 (tech_v2_workboard.html) featuring a workboard layout for daily tasks and opportunities. - Developed Technician Dashboard V3 (tech_v3_table_focus.html) with a power table for detailed case management. - Created a dashboard selector page (technician_dashboard_selector.html) for easy navigation between dashboard versions. - Added user dashboard preferences migration (130_user_dashboard_preferences.sql) to store default dashboard paths. - Enhanced sag_sager table with assigned group ID (131_sag_assignment_group.sql) for better case management. - Updated sag_subscriptions table to include cancellation rules and billing dates (132_subscription_cancellation.sql, 134_subscription_billing_dates.sql). - Implemented subscription staging for CRM integration (136_simply_subscription_staging.sql). - Added a script to move time tracking section in detail view (move_time_section.py). - Created a test script for subscription processing (test_subscription_processing.py).
214 lines
8.5 KiB
Python
214 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Move time tracking section from right column to left column with inline quick-add
|
|
"""
|
|
|
|
# Read the file
|
|
with open('app/modules/sag/templates/detail.html', 'r', encoding='utf-8') as f:
|
|
lines = f.readlines()
|
|
|
|
# Find the insertion point in left column (before </div> that closes left column around line 2665)
|
|
# and the section to remove in right column (around lines 2813-2865)
|
|
|
|
# First, find where to insert (before the </div> that closes left column)
|
|
insert_index = None
|
|
for i, line in enumerate(lines):
|
|
if i >= 2660 and i <= 2670:
|
|
if '</div>' in line and 'col-lg-4' in lines[i+1]:
|
|
insert_index = i
|
|
break
|
|
|
|
print(f"Found insert point at line {insert_index + 1}")
|
|
|
|
# Find where to remove (the time card in right column)
|
|
remove_start = None
|
|
remove_end = None
|
|
for i, line in enumerate(lines):
|
|
if i >= 2810 and i <= 2820:
|
|
if 'data-module="time"' in line:
|
|
remove_start = i - 1 # Start from blank line before
|
|
break
|
|
|
|
if remove_start:
|
|
# Find the end of this card
|
|
for i in range(remove_start, min(remove_start + 100, len(lines))):
|
|
if '</div>' in lines[i] and i > remove_start + 50: # Make sure we've gone past the card content
|
|
remove_end = i + 1 # Include the closing div
|
|
break
|
|
|
|
print(f"Found remove section from line {remove_start + 1} to {remove_end + 1}")
|
|
|
|
# Create the new time tracking section with inline quick-add
|
|
new_time_section = '''
|
|
<!-- Tidsregistrering & Fakturering (Now in left column)-->
|
|
<div class="card mb-3">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0 text-primary"><i class="bi bi-clock-history me-2"></i>Tid & Fakturering</h6>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="showAddTimeModal()">
|
|
<i class="bi bi-plus-lg me-1"></i>Åbn Fuld Formular
|
|
</button>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Inline Quick Add -->
|
|
<div class="border rounded p-3 mb-3 bg-light">
|
|
<div class="row g-2 align-items-end">
|
|
<div class="col-md-2">
|
|
<label class="form-label small mb-1">Dato</label>
|
|
<input type="date" class="form-control form-control-sm" id="quickTimeDate" value="">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label small mb-1">Timer</label>
|
|
<div class="input-group input-group-sm">
|
|
<input type="number" class="form-control" id="quickTimeHours" min="0" max="23" placeholder="0" step="1">
|
|
<span class="input-group-text">:</span>
|
|
<input type="number" class="form-control" id="quickTimeMinutes" min="0" max="59" placeholder="00" step="1">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-5">
|
|
<label class="form-label small mb-1">Beskrivelse</label>
|
|
<input type="text" class="form-control form-control-sm" id="quickTimeDesc" placeholder="Hvad blev der arbejdet på?">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<button class="btn btn-sm btn-success w-100" onclick="quickAddTime()">
|
|
<i class="bi bi-plus-circle me-1"></i>Tilføj Tid
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Time Entries Table -->
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0" style="vertical-align: middle;">
|
|
<thead class="bg-light">
|
|
<tr>
|
|
<th class="ps-3">Dato</th>
|
|
<th>Beskrivelse</th>
|
|
<th>Bruger</th>
|
|
<th>Timer</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for entry in time_entries %}
|
|
<tr>
|
|
<td class="ps-3">{{ entry.worked_date }}</td>
|
|
<td>{{ entry.description or '-' }}</td>
|
|
<td>{{ entry.user_name }}</td>
|
|
<td class="fw-bold">{{ entry.original_hours }}</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="4" class="text-center py-3 text-muted">Ingen tid registreret</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Prepaid Cards Info -->
|
|
{% if prepaid_cards %}
|
|
<div class="border-top mt-3 pt-3">
|
|
<div class="fw-semibold text-primary mb-2"><i class="bi bi-credit-card me-1"></i>Klippekort</div>
|
|
<div class="row g-2">
|
|
{% for card in prepaid_cards %}
|
|
<div class="col-md-6">
|
|
<div class="d-flex justify-content-between small">
|
|
<span>#{{ card.card_number or card.id }}</span>
|
|
<span class="badge bg-success">{{ '%.2f' % card.remaining_hours }}t tilbage</span>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif%}
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Set today's date in quick add on page load
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const quickDateInput = document.getElementById('quickTimeDate');
|
|
if (quickDateInput) {
|
|
const today = new Date().toISOString().split('T')[0];
|
|
quickDateInput.value = today;
|
|
}
|
|
});
|
|
|
|
async function quickAddTime() {
|
|
const hours = parseInt(document.getElementById('quickTimeHours').value) || 0;
|
|
const minutes = parseInt(document.getElementById('quickTimeMinutes').value) || 0;
|
|
const desc = document.getElementById('quickTimeDesc').value.trim();
|
|
const date = document.getElementById('quickTimeDate').value;
|
|
|
|
if (hours === 0 && minutes === 0) {
|
|
alert('Angiv timer eller minutter');
|
|
return;
|
|
}
|
|
|
|
if (!desc) {
|
|
alert('Angiv beskrivelse');
|
|
return;
|
|
}
|
|
|
|
const totalHours = hours + (minutes / 60);
|
|
|
|
const data = {
|
|
sag_id: {{ case.id }},
|
|
original_hours: totalHours,
|
|
description: desc,
|
|
worked_date: date,
|
|
work_type: 'support',
|
|
billing_method: 'invoice',
|
|
is_internal: false
|
|
};
|
|
|
|
try {
|
|
const res = await fetch('/api/v1/timetracking/entries/internal', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const err = await res.json();
|
|
throw new Error(err.detail || 'Kunne ikke oprette tidsregistrering');
|
|
}
|
|
|
|
// Reset form
|
|
document.getElementById('quickTimeHours').value = '';
|
|
document.getElementById('quickTimeMinutes').value = '';
|
|
document.getElementById('quickTimeDesc').value = '';
|
|
|
|
// Reload page to show new entry
|
|
window.location.reload();
|
|
} catch (e) {
|
|
alert('Fejl: ' + e.message);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
'''
|
|
|
|
# Build the new file
|
|
new_lines = []
|
|
|
|
# Copy lines up to insert point
|
|
new_lines.extend(lines[:insert_index])
|
|
|
|
# Insert new time section
|
|
new_lines.append(new_time_section)
|
|
|
|
# Copy lines from insert point to remove start
|
|
new_lines.extend(lines[insert_index:remove_start])
|
|
|
|
# Skip the remove section, copy from remove_end onwards
|
|
new_lines.extend(lines[remove_end:])
|
|
|
|
# Write the new file
|
|
with open('app/modules/sag/templates/detail.html', 'w', encoding='utf-8') as f:
|
|
f.writelines(new_lines)
|
|
|
|
print(f"✅ File updated successfully!")
|
|
print(f" - Inserted new time section at line {insert_index + 1}")
|
|
print(f" - Removed old time section (lines {remove_start + 1} to {remove_end + 1})")
|
|
print(f" - New file has {len(new_lines)} lines (was {len(lines)} lines)")
|