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

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

Configure dep_sug payment requirement properly.

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