source: main/kofacustom.dspg/trunk/src/kofacustom/dspg/students/utils.py @ 15344

Last change on this file since 15344 was 15288, checked in by Henrik Bettermann, 6 years ago

Add matric number construction tools.

  • Property svn:keywords set to Id
File size: 14.4 KB
Line 
1## $Id: utils.py 15288 2019-01-10 10:12:19Z henrik $
2##
3## Copyright (C) 2011 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
19from time import time
20from zope.component import createObject, getUtility
21from waeup.kofa.interfaces import (IKofaUtils,
22    ADMITTED, CLEARED, RETURNING, PAID, REGISTERED, VALIDATED)
23from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
24from kofacustom.dspg.interfaces import MessageFactory as _
25
26
27def local(student):
28    lga = getattr(student, 'lga')
29    if lga and lga.startswith('delta'):
30        return True
31    return False
32
33class CustomStudentsUtils(NigeriaStudentsUtils):
34    """A collection of customized methods.
35    """
36
37    # prefix
38    STUDENT_ID_PREFIX = u'P'
39
40    def _dep_sug_paymentMade(self, student, session):
41        if len(student['payments']):
42            for ticket in student['payments'].values():
43                if ticket.p_state == 'paid' and \
44                    ticket.p_category == 'dep_sug' and \
45                    ticket.p_session == session:
46                    return True
47        return False
48
49    def _lsfp_penalty_paymentMade(self, student, session):
50        if student.current_mode not in ('hnd_ft', 'nd_ft'):
51            return True
52        if len(student['payments']):
53            for ticket in student['payments'].values():
54                if ticket.p_state == 'paid' and \
55                    ticket.p_category == 'lsfp_penalty' and \
56                    ticket.p_session == session:
57                    return True
58        return False
59
60    def getAccommodationDetails(self, student):
61        """Determine the accommodation data of a student.
62        """
63        d = {}
64        d['error'] = u''
65        hostels = grok.getSite()['hostels']
66        d['booking_session'] = hostels.accommodation_session
67        d['allowed_states'] = hostels.accommodation_states
68        d['startdate'] = hostels.startdate
69        d['enddate'] = hostels.enddate
70        d['expired'] = hostels.expired
71        # Determine bed type
72        bt = 'all'
73        if student.sex == 'f':
74            sex = 'female'
75        else:
76            sex = 'male'
77        special_handling = 'regular'
78        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
79        return d
80
81    def getReturningData(self, student):
82        """ This method defines what happens after school fee payment
83        of returning students depending on the student's senate verdict.
84        """
85        prev_level = student['studycourse'].current_level
86        cur_verdict = student['studycourse'].current_verdict
87        if cur_verdict in ('A','B','L','M','N','Z',):
88            # Successful student
89            new_level = divmod(int(prev_level),100)[0]*100 + 100
90        elif cur_verdict == 'C':
91            # Student on probation
92            new_level = int(prev_level) + 10
93        else:
94            # Student is somehow in an undefined state.
95            # Level has to be set manually.
96            new_level = prev_level
97        new_session = student['studycourse'].current_session + 1
98        return new_session, new_level
99
100    def setPaymentDetails(self, category, student,
101            previous_session, previous_level):
102        """Create a payment ticket and set the payment data of a
103        student for the payment category specified.
104        """
105        p_item = u''
106        amount = 0.0
107        if previous_session:
108            if previous_session < student['studycourse'].entry_session:
109                return _('The previous session must not fall below '
110                         'your entry session.'), None
111            if category == 'schoolfee':
112                # School fee is always paid for the following session
113                if previous_session > student['studycourse'].current_session:
114                    return _('This is not a previous session.'), None
115            else:
116                if previous_session > student['studycourse'].current_session - 1:
117                    return _('This is not a previous session.'), None
118            p_session = previous_session
119            p_level = previous_level
120            p_current = False
121        else:
122            p_session = student['studycourse'].current_session
123            p_level = student['studycourse'].current_level
124            p_current = True
125        academic_session = self._getSessionConfiguration(p_session)
126        if academic_session == None:
127            return _(u'Session configuration object is not available.'), None
128        # Determine fee.
129        if category == 'schoolfee':
130            try:
131                certificate = student['studycourse'].certificate
132                p_item = certificate.code
133            except (AttributeError, TypeError):
134                return _('Study course data are incomplete.'), None
135            if previous_session:
136                # Students can pay for previous sessions in all
137                # workflow states.  Fresh students are excluded by the
138                # update method of the PreviousPaymentAddFormPage.
139                if previous_level == 100:
140                    if local(student):
141                        amount = getattr(certificate, 'school_fee_1', 0.0)
142                    else:
143                        amount = getattr(certificate, 'school_fee_3', 0.0)
144                else:
145                    if local(student):
146                        amount = getattr(certificate, 'school_fee_2', 0.0)
147                    else:
148                        amount = getattr(certificate, 'school_fee_4', 0.0)
149            else:
150                # Students are only allowed to pay school fee
151                # if current session dep_sug payment has been made.
152                if student.faccode != 'SPAT' and not self._dep_sug_paymentMade(
153                    student, student.current_session):
154                    return _('You have to pay NADESU/SA/SUG Dues first.'), None
155                if student.state == CLEARED:
156                    penalty = getattr(academic_session, 'lsfp_penalty_fee')
157                    if  penalty and not self._lsfp_penalty_paymentMade(
158                        student, student.current_session):
159                        return _('You have to pay late school fee payment penalty first.'), None
160                    if local(student):
161                        amount = getattr(certificate, 'school_fee_1', 0.0)
162                    else:
163                        amount = getattr(certificate, 'school_fee_3', 0.0)
164                elif student.state == RETURNING:
165                    # In case of returning school fee payment the
166                    # payment session and level contain the values of
167                    # the session the student has paid for. Payment
168                    # session is always next session.
169                    p_session, p_level = self.getReturningData(student)
170                    academic_session = self._getSessionConfiguration(p_session)
171                    if academic_session == None:
172                        return _(
173                            u'Session configuration object is not available.'
174                            ), None
175                    if local(student):
176                        amount = getattr(certificate, 'school_fee_2', 0.0)
177                    else:
178                        amount = getattr(certificate, 'school_fee_4', 0.0)
179                elif student.is_postgrad and student.state == PAID:
180                    # Returning postgraduate students also pay for the
181                    # next session but their level always remains the
182                    # same.
183                    p_session += 1
184                    academic_session = self._getSessionConfiguration(p_session)
185                    if academic_session == None:
186                        return _(
187                            u'Session configuration object is not available.'
188                            ), None
189                    if local(student):
190                        amount = getattr(certificate, 'school_fee_2', 0.0)
191                    else:
192                        amount = getattr(certificate, 'school_fee_4', 0.0)
193        elif category == 'clearance':
194            try:
195                p_item = student['studycourse'].certificate.code
196            except (AttributeError, TypeError):
197                return _('Study course data are incomplete.'), None
198            amount = academic_session.clearance_fee
199            # Local ND and HND students are given a rebate which has
200            # to be adjusted if the basic acceptance fee changes.
201            if amount and student.current_mode == 'nd_ft' and local(student):
202                amount -= 10000.0
203            if amount and student.current_mode == 'hnd_ft' and local(student):
204                amount -= 5000.0
205        elif category == 'bed_allocation':
206            p_item = self.getAccommodationDetails(student)['bt']
207            amount = academic_session.booking_fee
208        elif category == 'hostel_maintenance':
209            amount = 0.0
210            bedticket = student['accommodation'].get(
211                str(student.current_session), None)
212            if bedticket is not None and bedticket.bed is not None:
213                p_item = bedticket.bed_coordinates
214                if bedticket.bed.__parent__.maint_fee > 0:
215                    amount = bedticket.bed.__parent__.maint_fee
216                else:
217                    # fallback
218                    amount = academic_session.maint_fee
219            else:
220                return _(u'No bed allocated.'), None
221        elif category == 'dep_sug':
222            amount = 3150.0 # includes GATEWAY_AMT
223            if student.faccode == 'SPAT':
224            #    amount = 1650.0 # includes GATEWAY_AMT
225                amount = 0.0
226        else:
227            fee_name = category + '_fee'
228            amount = getattr(academic_session, fee_name, 0.0)
229        if amount in (0.0, None):
230            return _('Amount could not be determined.'), None
231        if self.samePaymentMade(student, category, p_item, p_session):
232            return _('This type of payment has already been made.'), None
233        if self._isPaymentDisabled(p_session, category, student):
234            return _('This category of payments has been disabled.'), None
235        payment = createObject(u'waeup.StudentOnlinePayment')
236        timestamp = ("%d" % int(time()*10000))[1:]
237        payment.p_id = "p%s" % timestamp
238        payment.p_category = category
239        payment.p_item = p_item
240        payment.p_session = p_session
241        payment.p_level = p_level
242        payment.p_current = p_current
243        payment.amount_auth = amount
244        return None, payment
245
246    def warnCreditsOOR(self, studylevel, course=None):
247        """Return message if credits are out of range. In the base
248        package only maximum credits is set.
249        """
250        if course and studylevel.total_credits + course.credits > 80:
251            return _('Maximum credits exceeded.')
252        elif studylevel.total_credits > 80:
253            return _('Maximum credits exceeded.')
254        return
255
256    #: A tuple containing names of file upload viewlets which are not shown
257    #: on the `StudentClearanceManageFormPage`. Nothing is being skipped
258    #: in the base package. This attribute makes only sense, if intermediate
259    #: custom packages are being used, like we do for all Nigerian portals.
260    SKIP_UPLOAD_VIEWLETS = ('acceptanceletterupload',
261                            'higherqualificationresultupload',
262                            'advancedlevelresultupload',
263                            'evidencenameupload',
264                            'refereeletterupload',
265                            'statutorydeclarationupload',
266                            'firstsittingresultupload',
267                            'secondsittingresultupload',
268                            'certificateupload',
269                            'resultstatementupload',
270                            )
271
272    #: A tuple containing the names of registration states in which changing of
273    #: passport pictures is allowed.
274    PORTRAIT_CHANGE_STATES = (ADMITTED, RETURNING,)
275
276    def constructMatricNumber(self, student):
277        #faccode = student.faccode
278        depcode = student.depcode
279        #certcode = student.certcode
280        year = unicode(student.entry_session)[2:]
281        if not student.state in (PAID, ) or not student.is_fresh:
282            return _('Matriculation number cannot be set.'), None
283
284        # ACC/ND/17/00001
285        if student.current_mode == 'nd_ft':
286            next_integer = grok.getSite()['configuration'].next_matric_integer
287            if next_integer == 0:
288                return _('Matriculation number cannot be set.'), None
289            return None, "%s/ND/%s/%05d" % (depcode, year, next_integer)
290
291        # ACC/HND/17/00001
292        if student.current_mode == 'hnd_ft':
293            next_integer = grok.getSite()['configuration'].next_matric_integer_2
294            if next_integer == 0:
295                return _('Matriculation number cannot be set.'), None
296            return None, "%s/HND/%s/%05d" % (depcode, year, next_integer)
297
298        # PT students get the same prefixes but their counter should start
299        # with 10001. This is inconsistent because after 9999 ft students
300        # the ft and pt number ranges will overlap. Issoufou pointed this
301        # out but the director said:
302        # "when the counter gets to 9999 for ft, it can start counting
303        # along where pt is, at that moment."
304
305        # ACC/ND/17/10001
306        if student.current_mode in ('nd_pt, nd_we'):
307            next_integer = grok.getSite()['configuration'].next_matric_integer_3
308            if next_integer == 0:
309                return _('Matriculation number cannot be set.'), None
310            return None, "%s/ND/%s/%05d" % (depcode, year, next_integer)
311        # ACC/HND/17/10001
312        if student.current_mode in ('hnd_pt, hnd_we'):
313            next_integer = grok.getSite()['configuration'].next_matric_integer_4
314            if next_integer == 0:
315                return _('Matriculation number cannot be set.'), None
316            return None, "%s/HND/%s/%05d" % (depcode, year, next_integer)
317
318        return _('Matriculation number cannot be set.'), None
Note: See TracBrowser for help on using the repository browser.