source: main/kofacustom.iuokada/trunk/src/kofacustom/iuokada/students/browser.py @ 17548

Last change on this file since 17548 was 17546, checked in by Henrik Bettermann, 13 months ago

Fix school fees summary.

  • Property svn:keywords set to Id
File size: 21.3 KB
Line 
1## $Id: browser.py 17546 2023-08-17 07:43:50Z henrik $
2##
3## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18import grok
19import os
20from zope.i18n import translate
21from zope.schema.interfaces import ConstraintNotSatisfied
22from zope.component import getUtility
23from zope.security import checkPermission
24from zope.formlib.textwidgets import BytesDisplayWidget
25from hurry.workflow.interfaces import IWorkflowInfo
26from waeup.kofa.interfaces import (
27    REQUESTED, ADMITTED, CLEARANCE, REQUESTED, CLEARED,
28    IExtFileStore, IKofaUtils, academic_sessions_vocab)
29from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
30from waeup.kofa.browser.layout import (
31    action, jsaction, UtilityView, KofaEditFormPage)
32from waeup.kofa.students.browser import (
33    StudyLevelEditFormPage, StudyLevelDisplayFormPage,
34    StudentBasePDFFormPage, ExportPDFCourseRegistrationSlip,
35    CourseTicketDisplayFormPage, StudentTriggerTransitionFormPage,
36    StartClearancePage, BalancePaymentAddFormPage,
37    ExportPDFAdmissionSlip, ExportPDFPersonalDataSlip,
38    PaymentsManageFormPage,
39    msave, emit_lock_message)
40from waeup.kofa.students.interfaces import (
41    IStudentsUtils, ICourseTicket, IStudent)
42from waeup.kofa.students.vocabularies import StudyLevelSource
43from waeup.kofa.students.workflow import FORBIDDEN_POSTGRAD_TRANS
44from kofacustom.nigeria.students.browser import (
45    NigeriaOnlinePaymentDisplayFormPage,
46    NigeriaStudentBaseDisplayFormPage,
47    NigeriaStudentBaseManageFormPage,
48    NigeriaStudentClearanceEditFormPage,
49    NigeriaOnlinePaymentAddFormPage,
50    NigeriaExportPDFPaymentSlip,
51    NigeriaExportPDFClearanceSlip,
52    NigeriaExportPDFCourseRegistrationSlip,
53    NigeriaStudentBaseEditFormPage,
54    NigeriaBedTicketAddPage,
55    NigeriaAccommodationManageFormPage,
56    NigeriaAccommodationDisplayFormPage,
57    NigeriaStudentPersonalManageFormPage,
58    NigeriaStudentPersonalEditFormPage,
59    NigeriaStudentPersonalDisplayFormPage
60    )
61from kofacustom.iuokada.students.interfaces import (
62    ICustomStudentOnlinePayment, ICustomStudentStudyCourse,
63    ICustomStudentStudyLevel, ICustomStudentBase, ICustomStudent,
64    ICustomStudentPersonal, ICustomStudentPersonalEdit)
65from kofacustom.iuokada.interfaces import MessageFactory as _
66
67class CustomStudentBaseDisplayFormPage(NigeriaStudentBaseDisplayFormPage):
68    """ Page to display student base data
69    """
70    form_fields = grok.AutoFields(ICustomStudentBase).omit(
71        'password', 'suspended', 'suspended_comment', 'flash_notice')
72
73class CustomStudentBaseManageFormPage(NigeriaStudentBaseManageFormPage):
74    """ View to manage student base data
75    """
76    form_fields = grok.AutoFields(ICustomStudentBase).omit(
77        'student_id', 'adm_code', 'suspended',
78        'financially_cleared_by', 'financial_clearance_date')
79
80class StudentBaseEditFormPage(NigeriaStudentBaseEditFormPage):
81    """ View to edit student base data
82    """
83    @property
84    def form_fields(self):
85        form_fields = grok.AutoFields(ICustomStudentBase).select(
86            'email', 'email2', 'parents_email', 'phone',)
87        if not self.context.state in (ADMITTED, CLEARANCE):
88            form_fields['parents_email'].for_display = True
89        return form_fields
90
91class CustomExportPDFCourseRegistrationSlip(
92    NigeriaExportPDFCourseRegistrationSlip):
93    """Deliver a PDF slip of the context.
94    """
95
96    def _signatures(self):
97        return (
98                ['Student Signature'],
99                ['HoD / Course Adviser Signature'],
100                ['College Officer Signature'],
101                ['Dean Signature']
102                )
103
104    #def _sigsInFooter(self):
105    #    return (_('Student'),
106    #            _('HoD / Course Adviser'),
107    #            _('College Officer'),
108    #            _('Dean'),
109    #            )
110    #    return ()
111
112class CustomStudentPersonalDisplayFormPage(NigeriaStudentPersonalDisplayFormPage):
113    """ Page to display student personal data
114    """
115    form_fields = grok.AutoFields(ICustomStudentPersonal)
116    form_fields['perm_address'].custom_widget = BytesDisplayWidget
117    form_fields['postal_address'].custom_widget = BytesDisplayWidget
118    form_fields['hostel_address'].custom_widget = BytesDisplayWidget
119    form_fields['father_address'].custom_widget = BytesDisplayWidget
120    form_fields['mother_address'].custom_widget = BytesDisplayWidget
121    form_fields['guardian_address'].custom_widget = BytesDisplayWidget
122    form_fields[
123        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
124
125
126class CustomStudentPersonalEditFormPage(NigeriaStudentPersonalEditFormPage):
127    """ Page to edit personal data
128    """
129    form_fields = grok.AutoFields(ICustomStudentPersonalEdit).omit(
130        'personal_updated')
131
132    def update(self):
133        #if not self.context.is_fresh:
134        #    self.flash('Not allowed.', type="danger")
135        #    self.redirect(self.url(self.context))
136        #    return
137        if not self.context.minimumStudentPayments():
138            self.flash('Please make 40% of your tution fee payments first.',
139                       type="warning")
140            self.redirect(self.url(self.context, 'view_personal'))
141            return
142        super(CustomStudentPersonalEditFormPage, self).update()
143        return
144
145
146class CustomStudentPersonalManageFormPage(NigeriaStudentPersonalManageFormPage):
147    """ Page to manage personal data
148    """
149    form_fields = grok.AutoFields(ICustomStudentPersonal)
150    form_fields['personal_updated'].for_display = True
151    form_fields[
152        'personal_updated'].custom_widget = FriendlyDatetimeDisplayWidget('le')
153
154class CustomExportPDFPersonalDataSlip(ExportPDFPersonalDataSlip):
155    """Deliver a PDF base and personal data slip.
156    """
157    grok.name('course_registration_clearance.pdf')
158    omit_fields = (
159        'phone', 'email',
160        'suspended',
161        'adm_code', 'suspended_comment',
162        'current_level',
163        'flash_notice', 'entry_session',
164        'parents_email')
165
166    form_fields = grok.AutoFields(ICustomStudentPersonal)
167
168    def _signatures(self):
169        return ([('I certify that the above named student has satisfied the financial requirements for registration.', 'Name and Signature of Bursary Staff', '<br><br>')],
170                [('I certify that the credentials of the student have been screened by me and the student is hereby cleared.', 'Name and Signature of Registry Staff', '<br><br>')],
171                [('I certify that the above named student has registered with the Library.', 'Name and Signature of Library Staff', '<br><br>')],
172                [('I certify that the above named student has been registered with the college. ', 'Name and Signature of College Officer', '<br><br>')],
173                [('I certify that the above named student has completed his/her ICT registration. ', 'Name and Signature of ICT Staff', '<br><br>')],
174                [('Eligibility/Congratulation Station', 'Name and Signature of Registrar', '')],
175                )
176
177    @property
178    def tabletitle(self):
179        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
180        tabletitle = []
181        session = self.context.student.current_session
182        tabletitle.append('_PB_Successful %s/%s Session Payments' %(session, session+1))
183        return tabletitle
184
185    def render(self):
186        if not self.context.minimumStudentPayments():
187            self.redirect(self.url(self.context))
188            return
189        studentview = StudentBasePDFFormPage(self.context.student,
190            self.request, self.omit_fields)
191        students_utils = getUtility(IStudentsUtils)
192
193        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
194        P_ID = translate(_('Payment Id'), 'waeup.kofa', target_language=portal_language)
195        #CD = translate(_('Creation Date'), 'waeup.kofa', target_language=portal_language)
196        PD = translate(_('Payment Date'), 'waeup.kofa', target_language=portal_language)
197        CAT = translate(_('Payment Category'), 'waeup.kofa', target_language=portal_language)
198        ITEM = translate(_('Payment Item'), 'waeup.kofa', target_language=portal_language)
199        AMT = translate(_('Amount (Naira)'), 'waeup.kofa', target_language=portal_language)
200        SSS = translate(_('Payment Session'), 'waeup.kofa', target_language=portal_language)
201        tabledata = []
202        tableheader = []
203        tabledata.append(sorted(
204            [value for value in self.context['payments'].values()
205             if value.p_state in ('paid', 'waived', 'scholarship')
206             and value.p_session >= value.student.current_session],
207             key=lambda value: value.p_session))
208        tableheader.append([(P_ID,'p_id', 4.2),
209                         #(CD,'creation_date', 3),
210                         (PD,'formatted_p_date', 3),
211                         (CAT,'category', 3),
212                         (ITEM, 'p_item', 3),
213                         (AMT, 'amount_auth', 2),
214                         (SSS, 'p_session', 2),
215                         ])
216
217        #watermark_path = os.path.join(
218        #    os.path.dirname(__file__), 'static', 'watermark.pdf')
219        #watermark = open(watermark_path, 'rb')
220        #file_path = os.path.join(
221        #    os.path.dirname(__file__), 'static', 'biodataPage2.pdf')
222        #file = open(file_path, 'rb')
223        #mergefiles = [file,]
224
225        return students_utils.renderPDF(
226            self, 'course_registration_clearance.pdf',
227            self.context.student, studentview,
228            omit_fields=self.omit_fields,
229            signatures=self._signatures(),
230
231            tableheader=tableheader,
232            tabledata=tabledata,
233
234            pagebreak=True,
235        #    mergefiles=mergefiles,
236        #    watermark=watermark
237            )
238
239class CustomAccommodationDisplayFormPage(NigeriaAccommodationDisplayFormPage):
240    """ Page to view bed tickets.
241    """
242    with_hostel_selection = True
243
244class CustomAccommodationManageFormPage(NigeriaAccommodationManageFormPage):
245    """ Page to manage bed tickets.
246    This manage form page is for both students and students officers.
247    """
248    with_hostel_selection = True
249
250class CustomBedTicketAddPage(NigeriaBedTicketAddPage):
251    """ Page to add a bed ticket
252    """
253    with_ac = False
254    with_bedselection = True
255
256class CustomPaymentsManageFormPage(PaymentsManageFormPage):
257    """ Page to manage the student payments. This manage form page is for
258    both students and students officers. IUOkada does not allow students
259    to remove any payment ticket.
260    """
261    grok.template('paymentsmanagepage')
262
263    def _schoolfee_payments_made(self):
264        cs = self.context.student.current_session
265        SF_PAYMENTS = ('schoolfee', 'schoolfee40', 'secondinstal', 'clearance')
266        sf_paid = dict()
267        try:
268            certificate = self.context.student['studycourse'].certificate
269        except (AttributeError, TypeError):
270            return sf_paid, 0
271        session = self.context.student.entry_session
272        # Initiliaze sf_paid dict with items
273        # session: [school fee in session,, amount paid in session, amount due]
274        sf_paid[session] = [getattr(certificate, 'school_fee_1', 0.0), 0.0, 0.0]
275        while session < cs + 1:
276            session += 1
277            sf_paid[session]= [getattr(certificate, 'school_fee_2', 0.0), 0.0, 0.0]
278        sessions = sf_paid.keys()
279        brought_fwd = 0.0
280        # Collect all payments made or brought forward
281        for ticket in self.context.values():
282            amt = ticket.net_amt
283            if not amt:
284                amt = ticket.amount_auth
285            if ticket.p_category in SF_PAYMENTS and \
286                ticket.p_state == 'paid' and \
287                ticket.p_session in sessions:
288                sf_paid[ticket.p_session][1] += amt
289            if ticket.p_state != 'paid' and\
290               ticket.p_category == 'brought_fwd':
291                  brought_fwd += ticket.amount_auth
292        # Calculate due, first entry session
293        entry_session = sessions[0]
294        #   amount due in session =  brought forward from previous sessions + school fee in session - amount paid in session
295        sf_paid[entry_session][2] = brought_fwd + sf_paid[entry_session][0] - sf_paid[entry_session][1]
296        for session in sessions[1:]:
297            #   amount due in session =  brought forward from previous session + school fee in session - amount paid in session
298            sf_paid[session][2] = sf_paid[session-1][2] + sf_paid[session][0] - sf_paid[session][1]
299        return sorted(sf_paid.items(), key=lambda value: value[0]), brought_fwd
300
301    def update(self):
302        super(CustomPaymentsManageFormPage, self).update()
303        self.sfp_made = self._schoolfee_payments_made()
304        return
305
306    @property
307    def manage_payments_allowed(self):
308        return checkPermission('waeup.manageStudent', self.context)
309
310class StudentGetMatricNumberPage(UtilityView, grok.View):
311    """ Construct and set the matriculation number.
312    """
313    grok.context(IStudent)
314    grok.name('get_matric_number')
315    grok.require('waeup.manageStudent')
316
317    def update(self):
318        students_utils = getUtility(IStudentsUtils)
319        msg, mnumber = students_utils.setMatricNumber(self.context)
320        if msg:
321            self.flash(msg, type="danger")
322        else:
323            self.flash(_('Matriculation number %s assigned.' % mnumber))
324            self.context.writeLogMessage(self, '%s assigned' % mnumber)
325        self.redirect(self.url(self.context))
326        return
327
328    def render(self):
329        return
330
331class SwitchLibraryAccessView(UtilityView, grok.View):
332    """ Switch the library attribute
333    """
334    grok.context(ICustomStudent)
335    grok.name('switch_library_access')
336    grok.require('waeup.switchLibraryAccess')
337
338    def update(self):
339        if self.context.library:
340            self.context.library = False
341            self.context.writeLogMessage(self, 'library access disabled')
342            self.flash(_('Library access disabled'))
343        else:
344            self.context.library = True
345            self.context.writeLogMessage(self, 'library access enabled')
346            self.flash(_('Library access enabled'))
347        self.redirect(self.url(self.context))
348        return
349
350    def render(self):
351        return
352
353class ExportLibIdCard(UtilityView, grok.View):
354    """Deliver an id card for the library.
355    """
356    grok.context(ICustomStudent)
357    grok.name('lib_idcard.pdf')
358    grok.require('waeup.viewStudent')
359    prefix = 'form'
360
361    label = u"Library Clearance"
362
363    omit_fields = (
364        'suspended', 'email', 'phone',
365        'adm_code', 'suspended_comment',
366        'date_of_birth',
367        'current_mode', 'certificate',
368        'entry_session',
369        'flash_notice')
370
371    form_fields = []
372
373    def _sigsInFooter(self):
374        isStudent = getattr(
375            self.request.principal, 'user_type', None) == 'student'
376        if isStudent:
377            return ''
378        return (_("Date, Reader's Signature"),
379                _("Date, Circulation Librarian's Signature"),
380                )
381
382    def update(self):
383        if not self.context.library:
384            self.flash(_('Forbidden!'), type="danger")
385            self.redirect(self.url(self.context))
386        return
387
388    @property
389    def note(self):
390        return """
391<br /><br /><br /><br /><font size='12'>
392This is to certify that the bearer whose photograph and other details appear
393 overleaf is a registered user of the <b>University Library</b>.
394 The card is not transferable. A replacement fee is charged for a loss,
395 mutilation or otherwise. If found, please, return to the University Library,
396 Igbinedion University, Okada.
397</font>
398
399"""
400        return
401
402    def render(self):
403        studentview = StudentBasePDFFormPage(self.context.student,
404            self.request, self.omit_fields)
405        students_utils = getUtility(IStudentsUtils)
406        return students_utils.renderPDF(
407            self, 'lib_idcard',
408            self.context.student, studentview,
409            omit_fields=self.omit_fields,
410            sigs_in_footer=self._sigsInFooter(),
411            note=self.note)
412
413class CustomStartClearancePage(StartClearancePage):
414    with_ac = False
415
416class CustomBalancePaymentAddFormPage(BalancePaymentAddFormPage):
417    grok.require('waeup.payStudent')
418
419from kofacustom.iuokada.students.admission_letter_ug import (
420    ADML_UG_1, ADML_UG_2, ADML_UG_3, ADML_UG_2_MEDICAL, ADML_UG_2_PHARMACY,
421    BASIC_MEDICAL_ONLY, BASIC_PHARMACY_ONLY)
422from kofacustom.iuokada.students.admission_letter_pg import ADML_PG
423from kofacustom.iuokada.students.admission_letter_pt import ADML_PT
424from kofacustom.iuokada.students.admission_letter_jupeb import ADML_JUPEB
425
426class CustomExportPDFAdmissionSlip(ExportPDFAdmissionSlip):
427    """Deliver a PDF Admission slip.
428    """
429
430    omit_fields = ('date_of_birth',
431                   #'current_level',
432                   #'current_mode',
433                   #'entry_session'
434                   )
435
436    @property
437    def session(self):
438        return academic_sessions_vocab.getTerm(
439            self.context.entry_session).title
440
441    @property
442    def level(self):
443        studylevelsource = StudyLevelSource()
444        return studylevelsource.factory.getTitle(
445            self.context['studycourse'].certificate, self.context.current_level)
446
447    @property
448    def label(self):
449        return 'OFFER OF PROVISIONAL ADMISSION \nFOR %s SESSION' % self.session
450
451    @property
452    def pre_text_ug(self):
453        return (
454            'Following your performance in the screening exercise '
455            'for the %s academic session, I am pleased to inform '
456            'you that you have been offered provisional admission into the '
457            'Igbinedion University, Okada as follows:' % self.session)
458
459    @property
460    def pre_text_pg(self):
461        return (
462            'I am pleased to inform you that your application for admission'
463            ' into the Igbinedion University, Okada was successful. You have'
464            ' been admitted as follows:')
465
466    def render(self):
467        students_utils = getUtility(IStudentsUtils)
468        watermark_path = os.path.join(
469            os.path.dirname(__file__), 'static', 'watermark.pdf')
470        watermark = open(watermark_path, 'rb')
471        if self.context.is_jupeb:
472            return students_utils.renderPDFAdmissionLetter(self,
473                self.context.student, omit_fields=self.omit_fields,
474                pre_text=self.pre_text_ug, post_text=ADML_JUPEB,
475                watermark=watermark)
476        if self.context.current_mode.endswith('_pt'):
477            return students_utils.renderPDFAdmissionLetter(self,
478                self.context.student, omit_fields=self.omit_fields,
479                pre_text=self.pre_text_ug, post_text=ADML_PT,
480                watermark=watermark)
481        if self.context.is_postgrad:
482            file_path = os.path.join(
483                os.path.dirname(__file__), 'static', 'admission_letter_pg.pdf')
484            file = open(file_path, 'rb')
485            mergefiles = [file,]
486            return students_utils.renderPDFAdmissionLetter(self,
487                self.context.student, omit_fields=self.omit_fields,
488                pre_text=self.pre_text_pg, post_text=ADML_PG,
489                mergefiles=mergefiles,
490                watermark=watermark)
491        file_path = os.path.join(
492            os.path.dirname(__file__), 'static', 'admission_letter_ug.pdf')
493        file = open(file_path, 'rb')
494        mergefiles = [file,]
495        if self.context.certcode in ('BBMS', 'MBBS') and self.context.current_level == 100:
496            post_text = BASIC_MEDICAL_ONLY + ADML_UG_1 + ADML_UG_2_MEDICAL + ADML_UG_3
497        elif self.context.certcode in ('BPHARM',) and self.context.current_level == 100:
498            post_text = BASIC_PHARMACY_ONLY + ADML_UG_1 + ADML_UG_2_MEDICAL + ADML_UG_3
499        else:
500            post_text = ADML_UG_1 + ADML_UG_2 + ADML_UG_3
501        return students_utils.renderPDFAdmissionLetter(self,
502            self.context.student, omit_fields=self.omit_fields,
503            pre_text=self.pre_text_ug, post_text=post_text,
504            mergefiles=mergefiles,
505            watermark=watermark)
506
507class CustomOnlinePaymentDisplayFormPage(NigeriaOnlinePaymentDisplayFormPage):
508    """ Page to view an online payment ticket. We do not omit provider_amt.
509    """
510    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
511        'gateway_amt', 'thirdparty_amt', 'p_item','p_combi', 'provider_amt')
512    form_fields[
513        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
514    form_fields[
515        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
516
517class CustomExportPDFPaymentSlip(NigeriaExportPDFPaymentSlip):
518    """Deliver a PDF slip of the context. We do not omit provider_amt.
519    """
520    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).omit(
521        'gateway_amt', 'thirdparty_amt', 'p_item',
522        'p_split_data','p_combi', 'provider_amt')
523    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
524    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
Note: See TracBrowser for help on using the repository browser.