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

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

HND students pay more.

  • Property svn:keywords set to Id
File size: 15.8 KB
Line 
1## $Id: utils.py 15692 2019-10-17 16:29:14Z 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, combi=[]):
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            # HND students pay more
210            if student.current_mode == 'hnd_ft':
211                amount += 3000
212            # Local ND and HND students are given a rebate which has
213            # to be adjusted if the basic acceptance fee changes.
214            if amount and student.current_mode == 'nd_ft' and local(student):
215                amount -= 10000.0
216            if amount and student.current_mode == 'hnd_ft' and local(student):
217                amount -= 5000.0
218        elif category == 'bed_allocation':
219            p_item = self.getAccommodationDetails(student)['bt']
220            amount = academic_session.booking_fee
221        elif category == 'hostel_maintenance':
222            amount = 0.0
223            bedticket = student['accommodation'].get(
224                str(student.current_session), None)
225            if bedticket is not None and bedticket.bed is not None:
226                p_item = bedticket.bed_coordinates
227                if bedticket.bed.__parent__.maint_fee > 0:
228                    amount = bedticket.bed.__parent__.maint_fee
229                else:
230                    # fallback
231                    amount = academic_session.maint_fee
232            else:
233                return _(u'No bed allocated.'), None
234        elif category == 'dep_sug':
235            amount = 3150.0 # includes GATEWAY_AMT
236            if student.faccode == 'SPAT':
237            #    amount = 1650.0 # includes GATEWAY_AMT
238                amount = 0.0
239            if student.state == RETURNING and not previous_session:
240                p_session, p_level = self.getReturningData(student)
241        else:
242            fee_name = category + '_fee'
243            amount = getattr(academic_session, fee_name, 0.0)
244        if amount in (0.0, None):
245            return _('Amount could not be determined.'), None
246        if self.samePaymentMade(student, category, p_item, p_session):
247            return _('This type of payment has already been made.'), None
248        if self._isPaymentDisabled(p_session, category, student):
249            return _('This category of payments has been disabled.'), None
250        payment = createObject(u'waeup.StudentOnlinePayment')
251        timestamp = ("%d" % int(time()*10000))[1:]
252        payment.p_id = "p%s" % timestamp
253        payment.p_category = category
254        payment.p_item = p_item
255        payment.p_session = p_session
256        payment.p_level = p_level
257        payment.p_current = p_current
258        payment.amount_auth = amount
259        return None, payment
260
261    def warnCreditsOOR(self, studylevel, course=None):
262        """Return message if credits are out of range. In the base
263        package only maximum credits is set.
264        """
265        if course and studylevel.total_credits + course.credits > 80:
266            return _('Maximum credits exceeded.')
267        elif studylevel.total_credits > 80:
268            return _('Maximum credits exceeded.')
269        return
270
271    #: A tuple containing names of file upload viewlets which are not shown
272    #: on the `StudentClearanceManageFormPage`. Nothing is being skipped
273    #: in the base package. This attribute makes only sense, if intermediate
274    #: custom packages are being used, like we do for all Nigerian portals.
275    SKIP_UPLOAD_VIEWLETS = ('acceptanceletterupload',
276                            'higherqualificationresultupload',
277                            'advancedlevelresultupload',
278                            'evidencenameupload',
279                            'refereeletterupload',
280                            'statutorydeclarationupload',
281                            'firstsittingresultupload',
282                            'secondsittingresultupload',
283                            'certificateupload',
284                            'resultstatementupload',
285                            )
286
287    #: A tuple containing the names of registration states in which changing of
288    #: passport pictures is allowed.
289    PORTRAIT_CHANGE_STATES = (ADMITTED, RETURNING,)
290
291    def constructMatricNumber(self, student):
292        #faccode = student.faccode
293        depcode = student.depcode
294        #certcode = student.certcode
295        year = unicode(student.entry_session)[2:]
296        if not student.state in (PAID, ) or not student.is_fresh:
297            return _('Matriculation number cannot be set.'), None
298
299        # ACC/ND/17/00001
300        if student.current_mode == 'nd_ft':
301            next_integer = grok.getSite()['configuration'].next_matric_integer
302            if next_integer == 0:
303                return _('Matriculation number cannot be set.'), None
304            return None, "%s/ND/%s/%05d" % (depcode, year, next_integer)
305
306        # ACC/HND/17/00001
307        if student.current_mode == 'hnd_ft':
308            next_integer = grok.getSite()['configuration'].next_matric_integer_2
309            if next_integer == 0:
310                return _('Matriculation number cannot be set.'), None
311            return None, "%s/HND/%s/%05d" % (depcode, year, next_integer)
312
313        # PT students get the same prefixes but their counter should start
314        # with 10001. This is inconsistent because after 9999 ft students
315        # the ft and pt number ranges will overlap. Issoufou pointed this
316        # out but the director said:
317        # "when the counter gets to 9999 for ft, it can start counting
318        # along where pt is, at that moment."
319
320        # ACC/ND/17/10001
321        if student.current_mode in ('nd_pt, nd_we'):
322            next_integer = grok.getSite()['configuration'].next_matric_integer_3
323            if next_integer == 0:
324                return _('Matriculation number cannot be set.'), None
325            return None, "%s/ND/%s/%05d" % (depcode, year, next_integer)
326        # ACC/HND/17/10001
327        if student.current_mode in ('hnd_pt, hnd_we'):
328            next_integer = grok.getSite()['configuration'].next_matric_integer_4
329            if next_integer == 0:
330                return _('Matriculation number cannot be set.'), None
331            return None, "%s/HND/%s/%05d" % (depcode, year, next_integer)
332
333        return _('Matriculation number cannot be set.'), None
334
335    def increaseMatricInteger(self, student):
336        """Increase counter for matric numbers.
337        """
338        if student.current_mode == 'nd_ft':
339            grok.getSite()['configuration'].next_matric_integer += 1
340            return
341        elif student.current_mode == 'hnd_ft':
342            grok.getSite()['configuration'].next_matric_integer_2 += 1
343            return
344        elif student.current_mode in ('nd_pt, nd_we'):
345            grok.getSite()['configuration'].next_matric_integer_3 += 1
346            return
347        elif student.current_mode in ('hnd_pt, hnd_we'):
348            grok.getSite()['configuration'].next_matric_integer_4 += 1
349            return
350        return
Note: See TracBrowser for help on using the repository browser.