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

Last change on this file since 16384 was 16338, checked in by Henrik Bettermann, 4 years ago

Reanble GNS pack payment requirements.

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