Add ability to change case customer from case detail
This commit is contained in:
parent
fb2243f0d4
commit
1f834160ca
@ -161,6 +161,14 @@ def _validate_group_id(group_id: Optional[int], field_name: str = "assigned_grou
|
||||
if not exists:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid {field_name}")
|
||||
|
||||
|
||||
def _validate_customer_id(customer_id: Optional[int], field_name: str = "customer_id") -> None:
|
||||
if customer_id is None:
|
||||
return
|
||||
exists = execute_query("SELECT 1 FROM customers WHERE id = %s", (customer_id,))
|
||||
if not exists:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid {field_name}")
|
||||
|
||||
# ============================================================================
|
||||
# QUICKCREATE AI ANALYSIS
|
||||
# ============================================================================
|
||||
@ -973,6 +981,9 @@ async def update_sag(sag_id: int, updates: dict):
|
||||
if "assigned_group_id" in updates:
|
||||
updates["assigned_group_id"] = _coerce_optional_int(updates.get("assigned_group_id"), "assigned_group_id")
|
||||
_validate_group_id(updates["assigned_group_id"])
|
||||
if "customer_id" in updates:
|
||||
updates["customer_id"] = _coerce_optional_int(updates.get("customer_id"), "customer_id")
|
||||
_validate_customer_id(updates["customer_id"])
|
||||
|
||||
# Build dynamic update query
|
||||
allowed_fields = [
|
||||
@ -980,6 +991,7 @@ async def update_sag(sag_id: int, updates: dict):
|
||||
"beskrivelse",
|
||||
"template_key",
|
||||
"status",
|
||||
"customer_id",
|
||||
"ansvarlig_bruger_id",
|
||||
"assigned_group_id",
|
||||
"priority",
|
||||
|
||||
@ -2376,9 +2376,19 @@
|
||||
<div class="case-meta-cell">
|
||||
<div class="case-meta-label"><i class="bi bi-building"></i>Kunde</div>
|
||||
{% if customer %}
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<a href="/customers/{{ customer.id }}" class="case-meta-value case-meta-link" style="color:{{ tcolor }}; font-size:1.0rem; font-weight:700;">{{ customer.name }}</a>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="showCustomerSearch('replace')" title="Skift primær kunde på sagen">
|
||||
<i class="bi bi-arrow-left-right me-1"></i>Skift kunde
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<span class="case-meta-value text-muted fst-italic">Ingen</span>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="showCustomerSearch('replace')" title="Vælg kunde til sagen">
|
||||
<i class="bi bi-plus-lg me-1"></i>Vælg kunde
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@ -2608,6 +2618,9 @@
|
||||
<div id="sag-titel-view" class="d-flex align-items-center gap-2">
|
||||
<h2 id="sag-titel-text" class="mb-2 fw-bolder" style="color: var(--accent); font-size: 1.8rem; letter-spacing: -0.5px;">{{ case.titel }}</h2>
|
||||
<button class="btn btn-sm btn-link text-muted p-0 mb-1" onclick="startTitelEdit()" title="Rediger overskrift"><i class="bi bi-pencil-square"></i></button>
|
||||
<button class="btn btn-sm btn-outline-primary mb-1" onclick="openAssignmentQuick()" title="Ændr hvem sagen er tildelt til">
|
||||
<i class="bi bi-person-check me-1"></i>Tildel sag
|
||||
</button>
|
||||
</div>
|
||||
<!-- Title edit -->
|
||||
<div id="sag-titel-editor" class="d-none">
|
||||
@ -2991,7 +3004,7 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Søg kunde</h5>
|
||||
<h5 class="modal-title" id="customerSearchModalTitle">Søg kunde</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@ -3325,6 +3338,7 @@
|
||||
let relationSearchTimeout;
|
||||
let wikiSearchTimeout;
|
||||
let selectedRelationCaseId = null;
|
||||
let customerSearchMode = 'link';
|
||||
const caseTypeKey = {{ ((case.template_key or case.type or 'ticket')|lower)|tojson }};
|
||||
|
||||
function escapeCaseTopAlertHtml(value) {
|
||||
@ -3548,7 +3562,12 @@
|
||||
setTimeout(() => document.getElementById('contactSearch').focus(), 300);
|
||||
}
|
||||
|
||||
function showCustomerSearch() {
|
||||
function showCustomerSearch(mode = 'link') {
|
||||
customerSearchMode = mode === 'replace' ? 'replace' : 'link';
|
||||
const title = document.getElementById('customerSearchModalTitle');
|
||||
if (title) {
|
||||
title.textContent = customerSearchMode === 'replace' ? 'Skift kunde på sag' : 'Søg kunde';
|
||||
}
|
||||
customerSearchModal.show();
|
||||
setTimeout(() => document.getElementById('customerSearch').focus(), 300);
|
||||
}
|
||||
@ -4025,7 +4044,7 @@
|
||||
} else {
|
||||
resultsDiv.innerHTML = customers.map(c => `
|
||||
<div class="list-group-item list-group-item-action" style="cursor: pointer;"
|
||||
onclick="addCustomer(${caseId}, ${c.id}, '${c.name.replace(/'/g, "\\'")}')">
|
||||
onclick="selectCustomerFromSearch(${caseId}, ${c.id}, '${c.name.replace(/'/g, "\\'")}')">
|
||||
<strong>${c.name}</strong>
|
||||
<div class="small text-muted">${c.email || ''} ${c.cvr_number ? '(CVR: ' + c.cvr_number + ')' : ''}</div>
|
||||
</div>
|
||||
@ -4038,6 +4057,34 @@
|
||||
});
|
||||
}
|
||||
|
||||
async function selectCustomerFromSearch(caseId, customerId, customerName) {
|
||||
if (customerSearchMode === 'replace') {
|
||||
await replaceCaseCustomer(caseId, customerId, customerName);
|
||||
return;
|
||||
}
|
||||
await addCustomer(caseId, customerId, customerName);
|
||||
}
|
||||
|
||||
async function replaceCaseCustomer(caseId, customerId, customerName) {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/sag/${caseId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({customer_id: customerId})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
customerSearchModal.hide();
|
||||
window.location.reload();
|
||||
} else {
|
||||
const error = await response.json().catch(() => ({}));
|
||||
alert(`Fejl ved skift af kunde: ${error.detail || response.statusText || 'Ukendt fejl'}`);
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Fejl ved skift af kunde: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function addCustomer(caseId, customerId, customerName) {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/sag/${caseId}/customers`, {
|
||||
@ -10010,6 +10057,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
function openAssignmentQuick() {
|
||||
const preferred = document.getElementById('tabsAssignmentUserSelect')
|
||||
|| document.getElementById('assignmentUserSelect');
|
||||
const fallback = document.getElementById('tabsAssignmentGroupSelect')
|
||||
|| document.getElementById('assignmentGroupSelect');
|
||||
const target = preferred || fallback;
|
||||
|
||||
if (!target) {
|
||||
alert('Kunne ikke finde tildelingsfelter på siden.');
|
||||
return;
|
||||
}
|
||||
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
setTimeout(() => target.focus(), 200);
|
||||
|
||||
if (typeof showToast === 'function') {
|
||||
showToast('Vælg ansvarlig bruger/gruppe i feltet, så gemmes det med det samme.', 'info');
|
||||
}
|
||||
}
|
||||
|
||||
async function savePipeline() {
|
||||
const stageValue = document.getElementById('pipelineStageSelect').value;
|
||||
const probabilityValue = document.getElementById('pipelineProbabilityInput').value;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user