agdar/PDF_IMPLEMENTATION_GUIDE.md
Marwan Alwali 25c9701c34 update
2025-11-06 18:18:43 +03:00

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