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

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

Customize admission slip.

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