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

Last change on this file since 15640 was 15372, checked in by Henrik Bettermann, 5 years ago

First-year probating students are treated like fresh students.

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