chore: pre-realignment cleanup
- department_record_complaint: broaden permission to include any involved department (mirrors the OR-filter used to surface complaints in the list) - send_to_modal: simplify — sends directly to champion+manager, drop the contact-person picker (loadDepartmentContacts is now a no-op stub) - department_inquiry_detail: confirm dialogs on Resolve/Close; fold No-Response into the contact_status dropdown - department_detail: JS string-escaping fixes
This commit is contained in:
parent
41bb9b5dd7
commit
8783d738f9
@ -1 +1 @@
|
||||
hh-dev:hh-dev-build-1784025225|1784026493
|
||||
hh-dev:hh-dev-build-1784453317|1784453370
|
||||
|
||||
@ -3017,13 +3017,32 @@ def department_record_complaint(request, pk):
|
||||
|
||||
department = get_object_or_404(Department, pk=pk if False else complaint.department_id)
|
||||
user = request.user
|
||||
|
||||
# A department member may view a complaint when their department is the
|
||||
# complaint's primary department (and it has been dispatched) OR when their
|
||||
# department is one of the routed involved departments (and that routing
|
||||
# was sent). This mirrors the OR-filter used to surface complaints on the
|
||||
# department detail page, so the modal authorizes exactly the records the
|
||||
# user can already see in the list.
|
||||
allowed_dept_ids = set()
|
||||
if complaint.department_id and complaint.sent_to_department:
|
||||
allowed_dept_ids.add(complaint.department_id)
|
||||
allowed_dept_ids.update(
|
||||
complaint.involved_departments.filter(sent=True).values_list("department_id", flat=True)
|
||||
)
|
||||
|
||||
dept_member_ok = user.department_id in allowed_dept_ids
|
||||
director_ok = user.is_director() and user.get_directed_departments().filter(
|
||||
id__in=allowed_dept_ids
|
||||
).exists()
|
||||
|
||||
if not (
|
||||
user.is_px_admin()
|
||||
or (user.is_hospital_admin() and user.hospital == complaint.hospital)
|
||||
or (user.is_champion() and user.department_id == complaint.department_id)
|
||||
or (user.is_department_manager() and user.department_id == complaint.department_id)
|
||||
or (user.is_basic_staff() and user.department_id == complaint.department_id)
|
||||
or (user.is_director() and user.get_directed_departments().filter(id=complaint.department_id).exists())
|
||||
or (user.is_champion() and dept_member_ok)
|
||||
or (user.is_department_manager() and dept_member_ok)
|
||||
or (user.is_basic_staff() and dept_member_ok)
|
||||
or director_ok
|
||||
):
|
||||
return JsonResponse({"error": "Access denied"}, status=403)
|
||||
|
||||
|
||||
@ -22,45 +22,18 @@ Required context variables:
|
||||
<i data-lucide="x" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-sm text-slate mt-2">{% trans "Select a person or department to send this item to for response." %}</p>
|
||||
<p class="text-sm text-slate mt-2">{% trans "Select a department to send this item to for response." %}</p>
|
||||
</div>
|
||||
|
||||
<form id="sendToForm" onsubmit="handleSendToSubmit(event)">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" id="sendItemId" name="item_id" value="">
|
||||
<input type="hidden" id="sendItemType" name="item_type" value="">
|
||||
<input type="hidden" name="recipient_type" value="department">
|
||||
|
||||
<div class="p-6">
|
||||
<!-- Recipient Type Tabs -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-semibold text-navy mb-2">{% trans "Send To" %}</label>
|
||||
<div class="flex gap-2">
|
||||
<label class="flex-1 flex items-center gap-2 px-4 py-3 border-2 rounded-xl cursor-pointer transition-all border-navy bg-navy/5 text-navy font-semibold text-sm" id="recipientLabelPerson" onclick="switchRecipientType('person')">
|
||||
<input type="radio" name="recipient_type" value="person" checked class="accent-navy" onchange="switchRecipientType('person')">
|
||||
<i data-lucide="user" class="w-4 h-4"></i>
|
||||
{% trans "Person" %}
|
||||
</label>
|
||||
<label class="flex-1 flex items-center gap-2 px-4 py-3 border-2 rounded-xl cursor-pointer transition-all border-slate-200 text-slate-500 text-sm" id="recipientLabelDepartment" onclick="switchRecipientType('department')">
|
||||
<input type="radio" name="recipient_type" value="department" class="accent-navy" onchange="switchRecipientType('department')">
|
||||
<i data-lucide="building-2" class="w-4 h-4"></i>
|
||||
{% trans "Department" %}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Person Selection -->
|
||||
<div id="personSection" class="mb-4">
|
||||
<label class="block text-sm font-semibold text-navy mb-2">{% trans "Select Person" %} <span class="text-red-500">*</span></label>
|
||||
<select name="person_id" id="personSelect" class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:outline-none focus:border-navy focus:ring-2 focus:ring-navy/20 text-sm">
|
||||
<option value="">{% trans "Select Person" %}</option>
|
||||
{% for user in users %}
|
||||
<option value="{{ user.id }}">{{ user.get_full_name|default:user.email }} {% if user.department %}({{ user.department.get_localized_name }}){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Department Selection -->
|
||||
<div id="departmentSection" class="mb-4 hidden">
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-semibold text-navy mb-2">{% trans "Select Department" %} <span class="text-red-500">*</span></label>
|
||||
<select name="department_id" id="departmentSelect" class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:outline-none focus:border-navy focus:ring-2 focus:ring-navy/20 text-sm">
|
||||
<option value="">{% trans "Select Department" %}</option>
|
||||
@ -131,13 +104,10 @@ function showSendModal(itemId, itemType, preselectDeptId) {
|
||||
document.getElementById('sendSuccess').classList.add('hidden');
|
||||
|
||||
if (preselectDeptId) {
|
||||
switchRecipientType('department');
|
||||
var deptSelect = document.getElementById('departmentSelect');
|
||||
if (deptSelect) {
|
||||
deptSelect.value = preselectDeptId;
|
||||
}
|
||||
} else {
|
||||
switchRecipientType('person');
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,33 +115,6 @@ function closeSendModal() {
|
||||
document.getElementById('sendToModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function switchRecipientType(type) {
|
||||
const personSection = document.getElementById('personSection');
|
||||
const departmentSection = document.getElementById('departmentSection');
|
||||
const personLabel = document.getElementById('recipientLabelPerson');
|
||||
const deptLabel = document.getElementById('recipientLabelDepartment');
|
||||
const personRadio = document.querySelector('input[name="recipient_type"][value="person"]');
|
||||
const deptRadio = document.querySelector('input[name="recipient_type"][value="department"]');
|
||||
|
||||
if (type === 'person') {
|
||||
if (personRadio) personRadio.checked = true;
|
||||
personSection.classList.remove('hidden');
|
||||
departmentSection.classList.add('hidden');
|
||||
personLabel.classList.add('border-navy', 'bg-navy/5', 'text-navy', 'font-semibold');
|
||||
personLabel.classList.remove('border-slate-200', 'text-slate-500');
|
||||
deptLabel.classList.add('border-slate-200', 'text-slate-500');
|
||||
deptLabel.classList.remove('border-navy', 'bg-navy/5', 'text-navy', 'font-semibold');
|
||||
} else {
|
||||
if (deptRadio) deptRadio.checked = true;
|
||||
personSection.classList.add('hidden');
|
||||
departmentSection.classList.remove('hidden');
|
||||
deptLabel.classList.add('border-navy', 'bg-navy/5', 'text-navy', 'font-semibold');
|
||||
deptLabel.classList.remove('border-slate-200', 'text-slate-500');
|
||||
personLabel.classList.add('border-slate-200', 'text-slate-500');
|
||||
personLabel.classList.remove('border-navy', 'bg-navy/5', 'text-navy', 'font-semibold');
|
||||
}
|
||||
}
|
||||
|
||||
function loadDepartmentContacts(deptId) {
|
||||
// Deprecated: contact-person picker removed. Sends to champion + manager automatically.
|
||||
}
|
||||
@ -185,13 +128,7 @@ function handleSendToSubmit(event) {
|
||||
const successDiv = document.getElementById('sendSuccess');
|
||||
const submitBtn = document.getElementById('sendSubmitBtn');
|
||||
|
||||
const recipientType = formData.get('recipient_type');
|
||||
if (recipientType === 'person' && !formData.get('person_id')) {
|
||||
errorDiv.textContent = '{% trans "Please select a person." %}';
|
||||
errorDiv.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
if (recipientType === 'department' && !formData.get('department_id')) {
|
||||
if (!formData.get('department_id')) {
|
||||
errorDiv.textContent = '{% trans "Please select a department." %}';
|
||||
errorDiv.classList.remove('hidden');
|
||||
return;
|
||||
|
||||
@ -1680,10 +1680,10 @@ function renderRecordModal(type, d) {
|
||||
|
||||
if (type === 'inquiry' && d.response) {
|
||||
html += '<div class="bg-blue-50 rounded-xl p-4">';
|
||||
html += '<h3 class="text-sm font-bold text-blue-700 flex items-center gap-2 mb-2"><i data-lucide="message-square" class="w-4 h-4"></i> {% trans "Response to Patient" | escapejs %}';
|
||||
html += '<h3 class="text-sm font-bold text-blue-700 flex items-center gap-2 mb-2"><i data-lucide="message-square" class="w-4 h-4"></i> {% filter escapejs %}{% trans "Response to Patient" %}{% endfilter %}';
|
||||
if (d.responded_at) html += '<span class="text-[10px] text-blue-600 font-normal ml-2">' + escHtml(d.responded_at) + '</span>';
|
||||
html += '</h3>';
|
||||
if (d.responded_by) html += '<p class="text-[10px] text-slate-500 mb-2">{% trans "By" | escapejs %} ' + escHtml(d.responded_by) + '</p>';
|
||||
if (d.responded_by) html += '<p class="text-[10px] text-slate-500 mb-2">{% filter escapejs %}{% trans "By" %}{% endfilter %} ' + escHtml(d.responded_by) + '</p>';
|
||||
html += '<p class="text-sm text-slate-700 whitespace-pre-wrap">' + escHtml(d.response) + '</p>';
|
||||
html += '</div>';
|
||||
}
|
||||
@ -1691,7 +1691,7 @@ function renderRecordModal(type, d) {
|
||||
if (type === 'inquiry' && d.resolved_at) {
|
||||
html += '<div class="bg-green-50 rounded-xl p-3 flex items-center gap-2">';
|
||||
html += '<i data-lucide="check-circle-2" class="w-4 h-4 text-green-600"></i>';
|
||||
html += '<span class="text-xs text-green-700 font-semibold">{% trans "Resolved" | escapejs %} ' + escHtml(d.resolved_at);
|
||||
html += '<span class="text-xs text-green-700 font-semibold">{% filter escapejs %}{% trans "Resolved" %}{% endfilter %} ' + escHtml(d.resolved_at);
|
||||
if (d.resolved_by) html += ' · ' + escHtml(d.resolved_by);
|
||||
html += '</span>';
|
||||
html += '</div>';
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
|
||||
{% if inquiry.status == 'in_progress' %}
|
||||
<!-- Mark Resolved (requires a patient contact outcome first) -->
|
||||
<form method="post" action="{% url 'inquiries:inquiry_change_status' inquiry.pk %}">
|
||||
<form method="post" action="{% url 'inquiries:inquiry_change_status' inquiry.pk %}" onsubmit="return confirm('{% filter escapejs %}{% trans "Mark this inquiry as resolved?" %}{% endfilter %}')">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="status" value="resolved">
|
||||
<input type="hidden" name="next" value="{% url 'organizations:department_inquiry_detail' department.pk inquiry.pk %}">
|
||||
@ -76,7 +76,7 @@
|
||||
</form>
|
||||
|
||||
<!-- Mark Closed -->
|
||||
<form method="post" action="{% url 'inquiries:inquiry_change_status' inquiry.pk %}">
|
||||
<form method="post" action="{% url 'inquiries:inquiry_change_status' inquiry.pk %}" onsubmit="return confirm('{% filter escapejs %}{% trans "Close this inquiry? No further actions will be available." %}{% endfilter %}')">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="status" value="closed">
|
||||
<input type="hidden" name="next" value="{% url 'organizations:department_inquiry_detail' department.pk inquiry.pk %}">
|
||||
@ -85,19 +85,9 @@
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Mark Contacted - No Response (terminal: couldn't reach the patient) -->
|
||||
<form method="post" action="{% url 'inquiries:inquiry_change_status' inquiry.pk %}" onsubmit="return confirm('{% trans "Mark this inquiry as Contacted - No Response? The patient's contact status will be updated automatically." %}')">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="status" value="contact_no_response">
|
||||
<input type="hidden" name="next" value="{% url 'organizations:department_inquiry_detail' department.pk inquiry.pk %}">
|
||||
<button type="submit" class="px-3 py-2 bg-amber-100 text-amber-700 border border-amber-200 rounded-lg text-sm font-semibold hover:bg-amber-200 transition flex items-center gap-1">
|
||||
<i data-lucide="phone-off" class="w-4 h-4"></i> {% trans "No Response" %}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Forward to another department (only available while in progress) -->
|
||||
<button type="button"
|
||||
onclick="showSendModal('{{ inquiry.pk }}', 'inquiry'); switchRecipientType('department');"
|
||||
onclick="showSendModal('{{ inquiry.pk }}', 'inquiry');"
|
||||
class="px-3 py-2 bg-amber-50 text-amber-700 border border-amber-200 rounded-lg text-sm font-semibold hover:bg-amber-100 transition flex items-center gap-1">
|
||||
<i data-lucide="share-2" class="w-4 h-4"></i> {% trans "Forward" %}
|
||||
</button>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user