307 lines
11 KiB
Markdown
307 lines
11 KiB
Markdown
# PDF Implementation Guide
|
|
|
|
This guide explains how to implement PDF generation (view, download, email) for all clinical documents using the reusable `BasePDFGenerator` service.
|
|
|
|
## Overview
|
|
|
|
We've created a centralized PDF service (`core/pdf_service.py`) that provides:
|
|
- ✅ Tenant branding (logo + name)
|
|
- ✅ Arabic font support with proper text rendering
|
|
- ✅ Bilingual labels (English/Arabic)
|
|
- ✅ Consistent professional styling
|
|
- ✅ Email functionality with PDF attachment
|
|
- ✅ View inline or download options
|
|
|
|
## Implementation Steps for Each Module
|
|
|
|
### Step 1: Create PDF Generator Class
|
|
|
|
In each module's `views.py`, create a PDF generator class that extends `BasePDFGenerator`:
|
|
|
|
```python
|
|
from core.pdf_service import BasePDFGenerator
|
|
|
|
class MedicalConsultationPDFGenerator(BasePDFGenerator):
|
|
"""PDF generator for Medical Consultation (MD-F-1)."""
|
|
|
|
def get_document_title(self):
|
|
"""Return document title in English and Arabic."""
|
|
consultation = self.document
|
|
return (
|
|
f"Medical Consultation - {consultation.patient.mrn}",
|
|
"استشارة طبية"
|
|
)
|
|
|
|
def get_pdf_filename(self):
|
|
"""Return PDF filename."""
|
|
consultation = self.document
|
|
return f"medical_consultation_{consultation.patient.mrn}_{consultation.consultation_date}.pdf"
|
|
|
|
def get_document_sections(self):
|
|
"""Return document sections to render."""
|
|
consultation = self.document
|
|
patient = consultation.patient
|
|
|
|
sections = []
|
|
|
|
# Patient Information Section
|
|
sections.append({
|
|
'heading_en': 'Patient Information',
|
|
'heading_ar': 'معلومات المريض',
|
|
'type': 'table',
|
|
'content': [
|
|
('Name', 'الاسم', f"{patient.first_name_en} {patient.last_name_en}",
|
|
f"{patient.first_name_ar} {patient.last_name_ar}" if patient.first_name_ar else ""),
|
|
('MRN', 'رقم السجل الطبي', patient.mrn, ""),
|
|
('Date of Birth', 'تاريخ الميلاد', patient.date_of_birth.strftime('%Y-%m-%d'), ""),
|
|
('Gender', 'الجنس', patient.get_sex_display(), ""),
|
|
]
|
|
})
|
|
|
|
# Consultation Details Section
|
|
sections.append({
|
|
'heading_en': 'Consultation Details',
|
|
'heading_ar': 'تفاصيل الاستشارة',
|
|
'type': 'table',
|
|
'content': [
|
|
('Date', 'التاريخ', consultation.consultation_date.strftime('%Y-%m-%d'), ""),
|
|
('Provider', 'مقدم الخدمة', consultation.provider.get_full_name(), ""),
|
|
('Chief Complaint', 'الشكوى الرئيسية', consultation.chief_complaint or 'N/A', ""),
|
|
]
|
|
})
|
|
|
|
# Add more sections as needed...
|
|
|
|
return sections
|
|
```
|
|
|
|
### Step 2: Create PDF View
|
|
|
|
```python
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
from django.shortcuts import get_object_or_404
|
|
from django.views import View
|
|
from core.mixins import TenantFilterMixin
|
|
|
|
class MedicalConsultationPDFView(LoginRequiredMixin, TenantFilterMixin, View):
|
|
"""Generate PDF for medical consultation."""
|
|
|
|
def get(self, request, pk):
|
|
"""Generate and return PDF."""
|
|
# Get consultation
|
|
consultation = get_object_or_404(
|
|
MedicalConsultation.objects.select_related(
|
|
'patient', 'provider', 'tenant'
|
|
),
|
|
pk=pk,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
# Create PDF generator
|
|
pdf_generator = MedicalConsultationPDFGenerator(consultation, request)
|
|
|
|
# Get view mode from query parameter
|
|
view_mode = request.GET.get('view', 'download')
|
|
|
|
# Generate and return PDF
|
|
return pdf_generator.generate_pdf(view_mode=view_mode)
|
|
```
|
|
|
|
### Step 3: Create Email PDF View
|
|
|
|
```python
|
|
from django.contrib import messages
|
|
from django.shortcuts import redirect
|
|
from django.utils.translation import gettext as _
|
|
|
|
class MedicalConsultationEmailPDFView(LoginRequiredMixin, TenantFilterMixin, View):
|
|
"""Email medical consultation PDF to patient."""
|
|
|
|
def post(self, request, pk):
|
|
"""Send PDF via email."""
|
|
# Get consultation
|
|
consultation = get_object_or_404(
|
|
MedicalConsultation.objects.select_related(
|
|
'patient', 'provider', 'tenant'
|
|
),
|
|
pk=pk,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
# Get form data
|
|
email_address = request.POST.get('email_address', '').strip()
|
|
custom_message = request.POST.get('email_message', '').strip()
|
|
|
|
if not email_address:
|
|
messages.error(request, _('Email address is required.'))
|
|
return redirect('medical:consultation_detail', pk=pk)
|
|
|
|
# Create PDF generator
|
|
pdf_generator = MedicalConsultationPDFGenerator(consultation, request)
|
|
|
|
# Prepare email content
|
|
subject = f"Medical Consultation - {consultation.patient.mrn}"
|
|
body = f"""
|
|
Dear {consultation.patient.first_name_en} {consultation.patient.last_name_en},
|
|
|
|
Please find attached your medical consultation details.
|
|
|
|
Consultation Date: {consultation.consultation_date.strftime('%Y-%m-%d')}
|
|
Provider: {consultation.provider.get_full_name()}
|
|
|
|
Best regards,
|
|
{consultation.tenant.name}
|
|
"""
|
|
|
|
# Send email
|
|
success, message = pdf_generator.send_email(
|
|
email_address=email_address,
|
|
subject=subject,
|
|
body=body,
|
|
custom_message=custom_message
|
|
)
|
|
|
|
if success:
|
|
messages.success(request, _('PDF sent to %(email)s successfully!') % {'email': email_address})
|
|
else:
|
|
messages.error(request, _('Failed to send email: %(error)s') % {'error': message})
|
|
|
|
return redirect('medical:consultation_detail', pk=pk)
|
|
```
|
|
|
|
### Step 4: Add URL Routes
|
|
|
|
In the module's `urls.py`:
|
|
|
|
```python
|
|
urlpatterns = [
|
|
# ... existing routes ...
|
|
path('<uuid:pk>/pdf/', views.MedicalConsultationPDFView.as_view(), name='consultation_pdf'),
|
|
path('<uuid:pk>/email-pdf/', views.MedicalConsultationEmailPDFView.as_view(), name='consultation_email_pdf'),
|
|
]
|
|
```
|
|
|
|
### Step 5: Update Detail Template
|
|
|
|
Add PDF options dropdown to the detail template:
|
|
|
|
```html
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fas fa-file-pdf me-1"></i>{% trans "PDF Options" %}
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li>
|
|
<a class="dropdown-item" href="{% url 'medical:consultation_pdf' consultation.pk %}?view=inline" target="_blank">
|
|
<i class="fas fa-eye me-2"></i>{% trans "View PDF" %}
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="{% url 'medical:consultation_pdf' consultation.pk %}" target="_blank">
|
|
<i class="fas fa-download me-2"></i>{% trans "Download PDF" %}
|
|
</a>
|
|
</li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li>
|
|
<a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#emailPdfModal">
|
|
<i class="fas fa-envelope me-2"></i>{% trans "Email PDF to Patient" %}
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Email PDF Modal -->
|
|
<div class="modal fade" id="emailPdfModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<form method="post" action="{% url 'medical:consultation_email_pdf' consultation.pk %}">
|
|
{% csrf_token %}
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="fas fa-envelope me-2"></i>{% trans "Email PDF to Patient" %}
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="email_address" class="form-label">{% trans "Email Address" %}</label>
|
|
<input type="email" name="email_address" id="email_address" class="form-control"
|
|
value="{{ consultation.patient.email }}" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="email_message" class="form-label">{% trans "Additional Message (Optional)" %}</label>
|
|
<textarea name="email_message" id="email_message" class="form-control" rows="3"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-paper-plane me-1"></i>{% trans "Send Email" %}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
## Modules to Implement
|
|
|
|
### Priority 1 (Week 1)
|
|
1. ✅ **Appointments** - Already implemented
|
|
2. ✅ **Finance/Invoices** - Already implemented
|
|
3. ⏳ **Medical Consultation** (MD-F-1)
|
|
4. ⏳ **Medical Follow-up** (MD-F-2)
|
|
5. ⏳ **Nursing Encounter**
|
|
|
|
### Priority 2 (Week 2)
|
|
6. ⏳ **OT Consultation** (OT-F-1)
|
|
7. ⏳ **OT Session** (OT-F-3)
|
|
8. ⏳ **SLP Consultation** (SLP-F-1)
|
|
|
|
### Priority 3 (Week 3)
|
|
9. ⏳ **SLP Assessment** (SLP-F-2)
|
|
10. ⏳ **SLP Intervention** (SLP-F-3)
|
|
11. ⏳ **ABA Consult** (ABA-F-1)
|
|
|
|
### Priority 4 (Week 4)
|
|
12. ⏳ **Consent Forms**
|
|
13. ⏳ **Patient Summary**
|
|
|
|
## Tips for Implementation
|
|
|
|
1. **Keep sections modular**: Each clinical form has different sections, so structure them clearly in `get_document_sections()`
|
|
|
|
2. **Handle optional fields**: Use `or 'N/A'` for fields that might be empty
|
|
|
|
3. **Format dates consistently**: Use `strftime('%Y-%m-%d')` for dates
|
|
|
|
4. **Test Arabic text**: Make sure Arabic names and content display correctly
|
|
|
|
5. **Check permissions**: Ensure only authorized users can generate/email PDFs
|
|
|
|
6. **Validate email addresses**: Always validate before sending emails
|
|
|
|
7. **Handle errors gracefully**: Wrap email sending in try-except blocks
|
|
|
|
## Testing Checklist
|
|
|
|
For each implementation, test:
|
|
- [ ] PDF generates without errors
|
|
- [ ] Tenant logo displays correctly
|
|
- [ ] Arabic text renders properly
|
|
- [ ] All sections display complete data
|
|
- [ ] View inline opens in browser
|
|
- [ ] Download saves file correctly
|
|
- [ ] Email sends successfully
|
|
- [ ] Email contains correct PDF attachment
|
|
- [ ] Custom message appears in email
|
|
- [ ] Permissions are enforced
|
|
|
|
## Next Steps
|
|
|
|
1. Start with Medical Consultation (most used)
|
|
2. Test thoroughly
|
|
3. Use as template for other modules
|
|
4. Iterate and improve based on feedback
|