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

Last change on this file since 17932 was 17884, checked in by Henrik Bettermann, 3 months ago

Omit final clearance fields.

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