[UPDT] HORILLA_API: Payslip download api view
This commit is contained in:
@@ -13,7 +13,7 @@ urlpatterns = [
|
||||
),
|
||||
path("payslip/", PayslipView.as_view(), name=""),
|
||||
path("payslip/<int:id>", PayslipView.as_view(), name=""),
|
||||
path("payslip-download/<int:id>", PayslipDownloadView.as_view(), name=""),
|
||||
path("payslip-download/<int:id>", PayslipPDFAPIView.as_view(), name=""),
|
||||
path("payslip-send-mail/", PayslipSendMailView.as_view(), name=""),
|
||||
path("loan-account/", LoanAccountView.as_view(), name=""),
|
||||
path("loan-account/<int:pk>", LoanAccountView.as_view(), name=""),
|
||||
|
||||
@@ -89,6 +89,7 @@ class ClockInAPIView(APIView):
|
||||
if request.__dict__.get("date"):
|
||||
date_today = request.date
|
||||
attendance_date = date_today
|
||||
|
||||
day = date_today.strftime("%A").lower()
|
||||
day = EmployeeShiftDay.objects.get(day=day)
|
||||
now = datetime.now().strftime("%H:%M")
|
||||
|
||||
@@ -377,3 +377,173 @@ class TaxBracketView(APIView):
|
||||
tax_bracket = TaxBracket.objects.get(id=pk)
|
||||
tax_bracket.delete()
|
||||
return Response(status=200)
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template.loader import render_to_string
|
||||
from rest_framework import status
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
|
||||
# DRF / Simple JWT imports
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
|
||||
from horilla.horilla_settings import HORILLA_DATE_FORMATS
|
||||
|
||||
# Your models / helpers
|
||||
from payroll.models.models import Company, EmployeeWorkInformation, Payslip
|
||||
from payroll.models.tax_models import PayrollSettings
|
||||
from payroll.views.component_views import filter_payslip
|
||||
from payroll.views.views import equalize_lists_length
|
||||
|
||||
try:
|
||||
import pdfkit
|
||||
|
||||
HAVE_PDFKIT = True
|
||||
except Exception:
|
||||
HAVE_PDFKIT = False
|
||||
|
||||
|
||||
class PayslipPDFAPIView(APIView):
|
||||
"""
|
||||
GET /api/payslip/<payslip_id>/?format=pdf
|
||||
Auth:
|
||||
- Accepts SimpleJWT Bearer token (Authorization: Bearer <token>)
|
||||
- Also accepts session auth (browser) when available
|
||||
"""
|
||||
|
||||
authentication_classes = (JWTAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def get(self, request, id, format=None):
|
||||
# get payslip or 404
|
||||
payslip = get_object_or_404(Payslip, id=id)
|
||||
|
||||
# authorization: same logic as your view
|
||||
user = request.user
|
||||
if not (
|
||||
user.has_perm("payroll.view_payslip")
|
||||
or payslip.employee_id.employee_user_id == user
|
||||
):
|
||||
return Response(
|
||||
{"detail": "You do not have permission to view this payslip."},
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
|
||||
# employee & company date format resolution
|
||||
employee = user.employee_get # keep same accessor you used
|
||||
info = EmployeeWorkInformation.objects.filter(employee_id=employee)
|
||||
if info.exists():
|
||||
# take the last one (mirrors your loop behavior)
|
||||
employee_company = info.last().company_id
|
||||
emp_company = Company.objects.filter(company=employee_company).first()
|
||||
date_format = (
|
||||
emp_company.date_format
|
||||
if emp_company and emp_company.date_format
|
||||
else "MMM. D, YYYY"
|
||||
)
|
||||
else:
|
||||
date_format = "MMM. D, YYYY"
|
||||
|
||||
# compose data from payslip (same as original)
|
||||
data = (
|
||||
payslip.pay_head_data.copy()
|
||||
if isinstance(payslip.pay_head_data, dict)
|
||||
else {}
|
||||
)
|
||||
start_date_str = data.get("start_date")
|
||||
end_date_str = data.get("end_date")
|
||||
|
||||
if not start_date_str or not end_date_str:
|
||||
return Response(
|
||||
{"detail": "Payslip missing start_date or end_date"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# parse dates
|
||||
start_date = datetime.strptime(start_date_str, "%Y-%m-%d").date()
|
||||
end_date = datetime.strptime(end_date_str, "%Y-%m-%d").date()
|
||||
|
||||
month_start_name = start_date.strftime("%B %d, %Y")
|
||||
month_end_name = end_date.strftime("%B %d, %Y")
|
||||
|
||||
# formatted date for chosen company format (safe default if not found)
|
||||
formatted_start_date = start_date.strftime(
|
||||
HORILLA_DATE_FORMATS.get(date_format, "%b. %d, %Y")
|
||||
)
|
||||
formatted_end_date = end_date.strftime(
|
||||
HORILLA_DATE_FORMATS.get(date_format, "%b. %d, %Y")
|
||||
)
|
||||
|
||||
# fill template context like original view
|
||||
data["month_start_name"] = month_start_name
|
||||
data["month_end_name"] = month_end_name
|
||||
data["formatted_start_date"] = formatted_start_date
|
||||
data["formatted_end_date"] = formatted_end_date
|
||||
data["employee"] = payslip.employee_id
|
||||
data["payslip"] = payslip
|
||||
data["json_data"] = data.copy()
|
||||
data["json_data"]["employee"] = payslip.employee_id.id
|
||||
data["json_data"]["payslip"] = payslip.id
|
||||
data["instance"] = payslip
|
||||
data["currency"] = (
|
||||
PayrollSettings.objects.first().currency_symbol
|
||||
if PayrollSettings.objects.exists()
|
||||
else "₹"
|
||||
)
|
||||
data["all_deductions"] = []
|
||||
for deduction_list in [
|
||||
data.get("basic_pay_deductions", []),
|
||||
data.get("gross_pay_deductions", []),
|
||||
data.get("pretax_deductions", []),
|
||||
data.get("post_tax_deductions", []),
|
||||
data.get("tax_deductions", []),
|
||||
data.get("net_deductions", []),
|
||||
]:
|
||||
data["all_deductions"].extend(deduction_list)
|
||||
|
||||
data["all_allowances"] = data.get("allowances", []).copy()
|
||||
# equalize lengths (your helper)
|
||||
equalize_lists_length(data.setdefault("allowances", []), data["all_deductions"])
|
||||
data["zipped_data"] = zip(data["allowances"], data["all_deductions"])
|
||||
data["host"] = request.get_host()
|
||||
data["protocol"] = "https" if request.is_secure() else "http"
|
||||
data["company"] = Company.objects.filter(hq=True).first()
|
||||
|
||||
# render HTML string using template
|
||||
html = render_to_string(
|
||||
"payroll/payslip/payslip_pdf.html", context=data, request=request
|
||||
)
|
||||
|
||||
# If client asked for PDF and pdfkit is available -> return PDF
|
||||
requested_format = request.GET.get("format", "").lower()
|
||||
if requested_format == "pdf":
|
||||
if not HAVE_PDFKIT:
|
||||
return Response(
|
||||
{
|
||||
"detail": "PDF generation not available on server. Install pdfkit/wkhtmltopdf."
|
||||
},
|
||||
status=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
)
|
||||
try:
|
||||
# optional: configure pdfkit with path if needed
|
||||
pdf_options = {
|
||||
"enable-local-file-access": None, # if your template references local CSS
|
||||
}
|
||||
pdf_bytes = pdfkit.from_string(html, False, options=pdf_options)
|
||||
response = HttpResponse(pdf_bytes, content_type="application/pdf")
|
||||
response["Content-Disposition"] = f'inline; filename="payslip-{id}.pdf"'
|
||||
return response
|
||||
except Exception as e:
|
||||
return Response(
|
||||
{"detail": f"PDF generation failed: {str(e)}"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
# default: return rendered HTML (use HttpResponse so browser sees it nicely)
|
||||
return HttpResponse(html)
|
||||
|
||||
Reference in New Issue
Block a user