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:
ismail 2026-07-20 21:14:00 +03:00
parent 41bb9b5dd7
commit 8783d738f9
5 changed files with 35 additions and 89 deletions

View File

@ -1 +1 @@
hh-dev:hh-dev-build-1784025225|1784026493
hh-dev:hh-dev-build-1784453317|1784453370

View File

@ -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)

View File

@ -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;

View File

@ -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 += ' &middot; ' + escHtml(d.resolved_by);
html += '</span>';
html += '</div>';

View File

@ -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>