From c8c0620cebac436054515614ec5471ac1d633926 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 12 Feb 2026 15:53:09 +0000 Subject: [PATCH] debugging expense report --- core/__pycache__/urls.cpython-311.pyc | Bin 14457 -> 14859 bytes core/__pycache__/views.cpython-311.pyc | Bin 128968 -> 130024 bytes core/urls.py | 3 + core/views.py | 25 ++++ hr/__pycache__/forms.cpython-311.pyc | Bin 2125 -> 3190 bytes hr/__pycache__/urls.cpython-311.pyc | Bin 3309 -> 3411 bytes hr/__pycache__/views.cpython-311.pyc | Bin 12775 -> 14913 bytes hr/forms.py | 17 ++- hr/templates/hr/attendance_form.html | 139 +++++++++++++++++----- hr/templates/hr/attendance_list.html | 157 ++++++++++++++++++++----- hr/urls.py | 1 + hr/views.py | 41 ++++++- 12 files changed, 320 insertions(+), 63 deletions(-) diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index accc30c5a26cc2c40ca86919b7f58254a59e7b6c..b31ede33ae068b00825de81cc1b92b2f4c2b680a 100644 GIT binary patch delta 2938 zcmaKu`%hcf9l$aE(p-noG-)BVZxWk4F}5);1A~z82;e8L-~dj+5I>3^*ai$VDd4Q# zv`$l%pfhd!u&SOkwU*W&sE{V4{jgNpPn#y4E1h4qNz?v<>h6bb>l#fvK3^_n>bi@3 zbw1~O&i8O0_j~V`xgX_U{uC!6~E*^ymob=LiDS971kFAFU}3Cf}V)-FE)zHLX5;WQKV=>8*X*UB9xP%`fH?+03_gt~>60?3TM9{J!>A zHJ#UT>#LEa#Wg;5I+y*n@n1O(!6ni8$<^=abWaYduD9q;t#E8Z#J-X&k%96wEXQOAF4?~-7{WM{ncY~^1;dUE$Y<1=4ziGouBCj+NiRc=vV zs;Dv?wPh%{6mT(csi)%PFAbM;aq>dkP1bU`%W8f}^f^y-)(fXz73J4NxmJ|x%4N$1 zQGOlcnw|xJJXbPOXj7n#L7Qrf6Kb{aXn(U>Fxq7ja+vzeqL&4G$J!hpo>A5dNoc~8qGZFZllE_RQriY4W-8vx)tbV(5*V+L}|3# z6jHO0sNvGyIy1M?ZZ8F&0zL*lwM7W6q|j-)(k9C7NAk5|jNRxwT&Cbxz|X+1y5eNY zR-V(kxc|gv#_+d}dNh`Xn0=TAA{3$uL>WZYUJ>#(`L)e>>W(PiC4aUVr6HYwVe-Nz zOCtzwVz$c#4}w>B6x3ddu_iB_;H!{SAju%9`cUy}%FR+Ov-;>{j>3upD-2fD04k%J za-&qm8gA2}X$mt6%rKZy$50v96ie|syK<|J4$e{tDG*{1QYX;4uOpu|8>MNa8S)3a zEX^W>h-#NdA0WJ;#j=)M%r;29^Ay4ggc*d@IaKB~B~b ztV5P!2ywE-;Z1}k@}a{dB@mM2Gly(QA*6NWS&PZGjFiy^xm>JbmQH$mg2JQ%lME)+ zEGl`i^6Lm2B+w*Fn+R`_T$5aQgz#94Au#HP0@Nzq}H9w9v@Pg`We+X!2gWTDk$ z-$vTeV&LQTUr$#7mAiW4Z!sE%^q3O#<;aE+B)7hNt|bqWmxNp8f)By3Kgtjn_tNY# zU(hKEK?Q;gf@%PjNlmFQy4cnB!(Iv=1w0Hq>OC~3G($|$zR!(*1^OBEt3fpGYlhhR z&cn=k3IPQI3q(}=7+6Qz&~))`XgxH4y+oU6Y-t7$$QlQz=fuCZjmi%BUAt^3AoKzGyxnBz2eF+4 zK+d^Lqk~9pINCFD8oKD66N$-#idR#_+l1%P6T|sX@smd`SqdPGlAm!HLzp7Jamj`t z!hJEcVH#-$%E!_+ixkpE5!3FacTZ&M2dFF%Ux!IrM0iM+IYbbmwk!zd*X zlI1>y6hc}XL!|ol!&?;G3b+}#)nzm?njwH+ui7b$DKN%hOkG1IPabs2(mKKh+2OE>utk2}X)0_Z>}ZihW^wLkDGVzx z%wSk8pwU-#w1M9=`td_+Kr{Zw$HpKkZcP!{o+n(=-Bm4lu+d8bUGh1ffG5@Eztvs- GmG>VFZJZGR delta 2452 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 IL!{^b0d#=+I{*Lx delta 5310 zcmb7Idw3Mp72n_N&L-I1|df_~cg!+mTgY0(E}lPU0= zd_X~I^V5gtg4UsR6i#U0YaNV^=6S6L6nxa2-xjantTySGPm6mjN=b&+*71AIcf8hG zW14AlS+%qeDil|gmUCrTg3BzPrMP>73y=$w)^xJJt&WO5oqDf|D5sjJZbdbV7JVlt&P4>6vqm}Ve!v9f`Y8{t%4KVuj9CKB&D2_5^Whz3^JozcwdspJsNcw+y^O-BHW}BVJt?YKSR&{Xr zA7E2plXe9SYBWin*Y8)b2gE9|;C5RqvG%ZXC3ibfYiiBaJ1xk88c&kf^fW6n6ud1b zM`Ej*OsqHc^O0DnsOd!S*DLLKLctkXk&J#Z)t;btphAz z%5zTiww068_rAl*oHS(Erjx?h?@&09Msds_gJTpmm!g1j>HgTe%sclA(*1xP75mZl|!%%m?LwtF%Y-OvTQij zc_g_*zbhM?&2UIZE*9Ar6RVoy3(SP(IoL;bmO+(&eHHO~b1ocaRgip@{!Tu&13}q1 z0!4j-#0uGKgTBS?Kv2}xFBEsneIt;iE+@gS_0LA245*hSqtIyO_mu;gLJXp~za#icRvL;;j#?2@0(DvBpniX$f-FXNhjpmz7|V)p|ekJ@V{L7@qzMrXS=7@oAz$ zu|cnIiTkdl{-7sV=dTaU5^Lr7QaG%?WW9%EWhpX>f5r4StHr#B30xWCeGY_gJ5kMs z!xd!tG3GrYUoJ&T=CxXiU$aOuBr>dHSkLgN{Hhd*nOs`2f#ER*uB+I{U{v%Orot7S zXjF7NGdkImkybNPo0#`F!xId@VR({ZGs7uX*}_y;8+MnWzx{b)bZ{G17;VsLi`JX* zlyq0%^sRmpUXEK*vmkHr62F^%b9rNmaDPbFnB)ltJ=L{7Z+&pAb3xr=Z@tIoEle+~ znC=$e>RbT_(ABw3GcY)g%?l&)t{xsFuG&o8{`%D!*nO>sf0~QIO-2umB|AwY6lL^~ z;^OWx!@@C&c6JhOBQYCo*R=8)RSL_QA-Jz0wMKXr1VfepMgHAQ9&}xk9sjw@_uTsb zbUF9Gclq}ensD%LI>~8V-|s)N$mn-Z)&8%37uffZiF7NqdgZkzE=NCl8y>LWCAr9r zB=u#|sMPOwqXOzHL|5s@s`$L|%c1izhJF^zM~3&XWYR0z&lZ+;GkUX!w4LbyL=d^ z9-$@c^)?@hK&%0Nq|r|y{iq?5Y|*Rz$hO%%M2iXRe6?KH02e-&dmB(W+Bn1BXW=mh zV>&ZtujBmheMWzH)gwl`nCEBel$_m&CXCkKYeWN-^Hy203_r7m4asB6@UZfQRR))% z06&-8mScGL(R*?^rjqZR704Pqn#~!_K2Lr^vDHh3@YYv1xC0Ho`ToV?W0s$h4J$Cr zZdf*=J0^G1;%Em6T~Xn2woZ1ezLQL?_!bJf4=1k*ccDJRYgHtVNEksV4;^*b}U4L`+0ir`m}O zS*wM&rYTbGhT!&PgmS>(t3Jps7I#hMn zB4s_X9CGY>0_1; z?Lc39&n@kgJ9Z$^${s{tnM+3HFJ3@QTq>=4n>&y|gy?6Mz9=N>uf2d+B`cMc2Zn`@-)@{#dbwCRJ8b!DWq9x)2tDmw5s^&HqIqOvz;rREvN27t~G}=MV?%?4@1o$%%c?phq4xf;!P&mY-VLR;P=w5Nt~&lGHhkw_b2f@15e>%4+9T{;uVJ179SX%12/', views.customer_payment_receipt, name='customer_payment_receipt'), path('invoices/sale-receipt//', views.sale_receipt, name='sale_receipt'), + path('invoices/download-pdf//', views.download_invoice_pdf, name='download_invoice_pdf'), path("invoices/edit//", views.edit_invoice, name="edit_invoice"), # Quotations path('quotations/', views.quotations, name='quotations'), path('quotations/create/', views.quotation_create, name='quotation_create'), path('quotations//', views.quotation_detail, name='quotation_detail'), + path('quotations/download-pdf//', views.download_quotation_pdf, name='download_quotation_pdf'), path('quotations/convert//', views.convert_quotation_to_invoice, name='convert_quotation_to_invoice'), path('quotations/delete//', views.delete_quotation, name='delete_quotation'), path('api/create-quotation/', views.create_quotation_api, name='create_quotation_api'), @@ -153,6 +155,7 @@ urlpatterns = [ path('purchases/lpo/', views.lpo_list, name='lpo_list'), path('purchases/lpo/create/', views.lpo_create, name='lpo_create'), path('purchases/lpo//', views.lpo_detail, name='lpo_detail'), + path('purchases/lpo/download-pdf//', views.download_lpo_pdf, name='download_lpo_pdf'), path('purchases/lpo/convert//', views.convert_lpo_to_purchase, name='convert_lpo_to_purchase'), path('purchases/lpo/delete//', views.lpo_delete, name='lpo_delete'), path('api/create-lpo/', views.create_lpo_api, name='create_lpo_api'), diff --git a/core/views.py b/core/views.py index 4bfb7ba..d261d7c 100644 --- a/core/views.py +++ b/core/views.py @@ -1034,8 +1034,33 @@ def expense_report(request): if not (request.user.is_staff or request.user.has_perm('core.view_reports')): messages.error(request, _("You do not have permission to view reports.")) return redirect('index') + start_date = request.GET.get('start_date') end_date = request.GET.get('end_date') + category_id = request.GET.get('category') + + expenses = Expense.objects.all().order_by('-date') + + if start_date: + expenses = expenses.filter(date__gte=start_date) + if end_date: + expenses = expenses.filter(date__lte=end_date) + if category_id: + expenses = expenses.filter(category_id=category_id) + + total_amount = expenses.aggregate(Sum('amount'))['amount__sum'] or 0 + categories = ExpenseCategory.objects.all().order_by('name_en') + + context = { + 'expenses': expenses, + 'total_amount': total_amount, + 'start_date': start_date, + 'end_date': end_date, + 'categories': categories, + 'category_id': category_id, + 'settings': SystemSetting.objects.first(), + } + return render(request, 'core/expense_report.html', context) @login_required def export_expenses_excel(request): diff --git a/hr/__pycache__/forms.cpython-311.pyc b/hr/__pycache__/forms.cpython-311.pyc index d17db607d950b2c08261467c71a4708dcaa54b63..61b6823b03f297dae6657d0bdab383a5532cd352 100644 GIT binary patch delta 1318 zcmaJ>O-vI}5Pom}+HIj?3T6dV5{y_3RTPXdMB@RBibX?JQWH(n_rPk~#kY$_Jd^_m z4o0aZ(F13c$U);pPu`3dk|p8L?7?{DRw6Nx#Kf7cpcHj>_RG9^Gt+tRo9$xVHzV|- zwl+XO-hX(Mp3n;+BcFrSGr|a?8Iq+5N(qygoRP9}Le44)g%XLJB}|zlOy%^pirFZ0 zG9j%n1DWIHUL;VIiySkmV6l4vaUE6vUGvcM2m5$1hMG=Vv; z9El$3j9|c|@>#``Yi~!?IT||#rsxKU%`4&nF4_OfMN9jOl&@7 zrFh-fse8c~S9sFf7TP=p?}LU@;NFzrFYBvB3*Xz7G+j`Nol-cAxt=hUY0Nh(dHy}R z%KZOWg^NA3M%6bey-geoqPc5xB_`>{R({N}P$*Epq?c3yToozlJ-`=6_`H80OP-RGS{ z7aXI1$>?7;`b+h#)4ekTuVbJ3oc2M~`uI{yeSEn-UNYJsWyiaabLpxRy@qNGEg3`0 z#!&I0w&$s~E8oyMeP!kbB%g6^+;!RoYJ;`ZU@bRT@HVR_Qwpe1s~YlZU=^Kf+ji7I<>#+t cSJA)rd$TKuvD*P|{pKnOHevAgUGL~W0gdN6D*ylh delta 502 zcmZutJxBvF6i)7Pcm1)&%?cf=+kyR~x)nvRbr8FByG4qL(vtH^(o)(qU64flMVWU=iR$Z9pARJ|xvq$T5QzZDQ#YcX3BkO@eu+hikX)tf7~ zIZtg;DwveE{+$GKN>i09Vq=6 thKfqu=4@RQlp6j^?)lHrzWf3+HPW8?O+5`zJXW=?hP)a4i4; diff --git a/hr/__pycache__/urls.cpython-311.pyc b/hr/__pycache__/urls.cpython-311.pyc index b501e4d261532ef1b98b1aa66d29e642e36e8a41..d9be205a03dd5106dfe437bdd04b2bd56e41f640 100644 GIT binary patch delta 231 zcmaDWd09$*IWI340}w3u+M5~9&cN^(#DM_`DC2X~Ms;uI&C{5B7$+`Lovh4iI&lK4 zrcNqzmLyDRitZZrWy}l=tAQ8-(nX@AQuKluH1#(pu}))D7Dy~9NzF@1%u7zyFRsi> z*1shHqT&;Ca^h1`%QBNwizk0)`^G4}`5ikSBWsZe(3s7CICvSk%z-RMATEBrS(xh( yqvQ=1js}+toYFHwugDr-u=2UU;(L+B_X>;e=BwPCjQqw@{7enpAXsDy)CB-d?sVXi8nN!zOZF_ATsy8e;wocs7R zbjvQjKc9Qfx#ymH&$%DwZ^4_Y{au?amxFTSU$2Gsxi8qA{KprzDeXh0eWg4nagr(G z9CG!!7;TP}4VCwm4{hn&!rHl!ilNHBN=92ERee>UZ;gmU?mqX0KUVpw1@0gx<(=cC zeAy)P@8~)1DpalX)i7xRNDDzK(00DWRRpbKXz8+aYw1&b1z$&_fZOP+fc5l8fFAlA zzQWhQELA~Y5iMo)ZDdk6NUPB@Z4pXbHPG4$t!$rsv|Xs+o9GF^W*P@

F_gr#FNO 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 Y1$S1ZR{BZH-3o`3Z&>6u0*l&z0B7bu%K!iX delta 3046 zcma)8TWk|Y6y32C$Bq*d5<7OBIB{?iypZ>!fiysP1ZbmF2`w}=sFv|AiD7MrS%)em}6`+Oq+a=Hd)|9lZUjz4U?~|O=>wVgxaN6h8>dq zWkGC%Urj$360N8|KCUFEo|I&Tc0kxx;R;|kAH*Yd zW3wAh+nV%9-9T1VK)h%O#KNzG=saVlb?C%1BVj7ChKhRm{r+h^oqDLS$Hp+|OcBkI z0bP#)${1*c2a+_dS4TUr7dM+`S0|!TSyjUmRN)iRPUN~6{I~30?CgaHc1cgRcRty@ zmJP^kgt^i*3>L2l^kI`%x*4sUDW{9aI`}$lMHk~#JFycXh{kpV?z|K3Ix3*UG1#$W zoxCi=G7n&Y0mwS)^~?uYn8|g95Ds+WOT|Ow<^on`82H6ToFk5PemXcqV zRd&zHL$7kB>_(-YOs`oE#AHQM7X+gHI4|yi+`JwMEkH4VFwx^1TN7&l!#Qrcy(o7s6fhtQ_iis zUlOgxdRO3x#mlYP$0MV$L?k7L_fHwuK8*edyVzgF8bEZ{*rx}k2lR=2H|wI?P|&R8_7tRH$=%ad5IW{r-?(L>=_LbK^saw4T!B>6}zDr@D09z$0$avRqRBFqH!33YppV^dtkJxtJiRr zO3-DoxJEsXpbJ6*GB#(WXS)B6GdMkX$Gd7a&#gcDBu7}872x;1l1~pXcR1=B-oE5? zxfa-Ph^KI31QlG>&+AJ$#S1xCz0FuiDLOy9kTkj=Okp8VSL4!Srdi%8$kw#8!*H+W zt7QrDt-pmF=Yoi}&Lwu!LQ1)cUHX8xw zZj7Ww6TOD5PY`%gHtIqfE7ff3R3aK?H&jG3kIE^Y7sYAyoZd%^&z(mvqy1!FdWALr z{V?$J4+E`45617bu#y~A>2<*@2%p2JO+WG#5<6kI^90!fKXrCNSyx%w$%1Gl(!oe# zG})|ia9YhV&qb4o6piiI^Sgvh(L6JeiYY2zH~l+_f8y{@2YjDX{wn70 z(IIT|w+P -

-

{{ title }}

-
+
+
+
+ -
-
-
- {% csrf_token %} - {% for field in form %} -
- - {{ field }} - {% if field.errors %} -
- {{ field.errors }} -
- {% endif %} +
+
+ + {% csrf_token %} + +
+ +
+ + {{ form.employee }} + {% if form.employee.errors %} +
{{ form.employee.errors }}
+ {% endif %} +
{% trans "Select the employee for this attendance record." %}
+
+ + +
+ + {{ form.date }} + {% if form.date.errors %} +
{{ form.date.errors }}
+ {% endif %} +
+ + +
+ + {{ form.device }} + {% if form.device.errors %} +
{{ form.device.errors }}
+ {% endif %} +
{% trans "Optional: Source biometric device." %}
+
+ + +
+ + {{ form.check_in }} + {% if form.check_in.errors %} +
{{ form.check_in.errors }}
+ {% endif %} +
+ +
+ + {{ form.check_out }} + {% if form.check_out.errors %} +
{{ form.check_out.errors }}
+ {% endif %} +
+ + +
+ + {{ form.notes }} + {% if form.notes.errors %} +
{{ form.notes.errors }}
+ {% endif %} +
+
+ +
+ + + {% trans "Cancel" %} + +
+
- {% endfor %} - - {% trans "Cancel" %} - +
+ + -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/hr/templates/hr/attendance_list.html b/hr/templates/hr/attendance_list.html index afbc3c7..6dc342a 100644 --- a/hr/templates/hr/attendance_list.html +++ b/hr/templates/hr/attendance_list.html @@ -2,43 +2,114 @@ {% load i18n %} {% block content %} -
+
-

{% trans "Attendance Records" %}

- - {% trans "Add Attendance" %} - +
+

{% trans "Attendance Records" %}

+

{% trans "Track and manage employee daily check-ins." %}

+
+
-
-
+ +
+
+
+
+
+
+
+ {% trans "Today's Present" %}
+
{{ today_count|default:"0" }}
+
+
+ +
+
+
+
+
+
+ +
+
- - +
+ - + - + + {% for att in attendances %} - - - - - + + + + + {% empty %} - + {% endfor %} @@ -46,19 +117,43 @@ {% if is_paginated %} - +
+ +
{% endif %} -{% endblock %} \ No newline at end of file + + +{% endblock %} diff --git a/hr/urls.py b/hr/urls.py index b46d620..8d6f539 100644 --- a/hr/urls.py +++ b/hr/urls.py @@ -17,6 +17,7 @@ urlpatterns = [ path('attendance/', views.AttendanceListView.as_view(), name='attendance_list'), path('attendance/add/', views.AttendanceCreateView.as_view(), name='attendance_add'), path('attendance//edit/', views.AttendanceUpdateView.as_view(), name='attendance_edit'), + path('attendance/sync/', views.sync_all_devices, name='sync_all_devices'), path('leave/', views.LeaveRequestListView.as_view(), name='leave_list'), path('leave/add/', views.LeaveRequestCreateView.as_view(), name='leave_add'), diff --git a/hr/views.py b/hr/views.py index e219216..9406089 100644 --- a/hr/views.py +++ b/hr/views.py @@ -3,7 +3,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from .models import Employee, Department, Attendance, LeaveRequest, JobPosition, BiometricDevice -from .forms import EmployeeForm +from .forms import EmployeeForm, AttendanceForm from django.db.models import Count from django.shortcuts import get_object_or_404, redirect from django.contrib import messages @@ -85,9 +85,15 @@ class AttendanceListView(LoginRequiredMixin, ListView): context_object_name = 'attendances' paginate_by = 50 + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['today_count'] = Attendance.objects.filter(date=timezone.now().date()).count() + return context + class AttendanceCreateView(LoginRequiredMixin, CreateView): + model = Attendance - fields = ['employee', 'date', 'check_in', 'check_out', 'device', 'notes'] + form_class = AttendanceForm template_name = 'hr/attendance_form.html' success_url = reverse_lazy('hr:attendance_list') @@ -98,7 +104,7 @@ class AttendanceCreateView(LoginRequiredMixin, CreateView): class AttendanceUpdateView(LoginRequiredMixin, UpdateView): model = Attendance - fields = ['employee', 'date', 'check_in', 'check_out', 'device', 'notes'] + form_class = AttendanceForm template_name = 'hr/attendance_form.html' success_url = reverse_lazy('hr:attendance_list') @@ -108,6 +114,7 @@ class AttendanceUpdateView(LoginRequiredMixin, UpdateView): return context class LeaveRequestListView(LoginRequiredMixin, ListView): + model = LeaveRequest template_name = 'hr/leave_list.html' context_object_name = 'leaves' @@ -186,4 +193,30 @@ def sync_device_logs(request, pk): else: messages.success(request, _("Sync Successful! Fetched %(total)s records, %(new)s new.") % {'total': result['total'], 'new': result['new']}) - return redirect('hr:device_list') \ No newline at end of file + return redirect('hr:device_list') + +def sync_all_devices(request): + devices = BiometricDevice.objects.filter(status='active') + if not devices.exists(): + messages.warning(request, _("No active biometric devices found to sync.")) + return redirect('hr:attendance_list') + + total_new = 0 + total_fetched = 0 + errors = [] + + for device in devices: + result = device.sync_data() + if result.get('error'): + errors.append(f"{device.name}: {result['error']}") + else: + total_new += result['new'] + total_fetched += result['total'] + + if errors: + messages.warning(request, _("Sync partially completed. Errors: %(errors)s") % {'errors': ", ".join(errors)}) + + if total_fetched > 0 or not errors: + messages.success(request, _("Sync completed! Total records: %(total)s, New: %(new)s") % {'total': total_fetched, 'new': total_new}) + + return redirect('hr:attendance_list') \ No newline at end of file
{% trans "Date" %}{% trans "Date" %} {% trans "Employee" %} {% trans "Check In" %} {% trans "Check Out" %}{% trans "Actions" %}{% trans "Device" %}{% trans "Actions" %}
{{ att.date }}{{ att.employee }}{{ att.check_in|default:"--" }}{{ att.check_out|default:"--" }} - - + + {{ att.date|date:"d M Y" }} + +
+
+ +
+ {{ att.employee }} +
+
+ {% if att.check_in %} + + {{ att.check_in|time:"H:i" }} + + {% else %} + -- + {% endif %} + + {% if att.check_out %} + + {{ att.check_out|time:"H:i" }} + + {% else %} + -- + {% endif %} + + + {% if att.device %} + {{ att.device.name }} + {% else %} + {% trans "Manual" %} + {% endif %} + + + +
{% trans "No attendance records found." %} +
+ +

{% trans "No attendance records found." %}

+ + {% trans "Add First Record" %} + +
+