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

Last change on this file since 16954 was 16948, checked in by Henrik Bettermann, 3 years ago

Customize _isPaymentDisabled.

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