source: main/kofacustom.iuokada/trunk/src/kofacustom/iuokada/students/utils.py @ 16142

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

Uups.

  • Property svn:keywords set to Id
File size: 10.8 KB
Line 
1## $Id: utils.py 16140 2020-06-30 12:37:29Z 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 copy import deepcopy
21from zope.component import createObject, getUtility
22from waeup.kofa.interfaces import (IKofaUtils,
23    ADMITTED, CLEARANCE, REQUESTED, CLEARED, RETURNING, PAID,
24    REGISTERED, VALIDATED)
25from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
26from kofacustom.iuokada.interfaces import MessageFactory as _
27
28class CustomStudentsUtils(NigeriaStudentsUtils):
29    """A collection of customized methods.
30
31    """
32
33    # prefix
34    STUDENT_ID_PREFIX = u'I'
35
36    SKIP_UPLOAD_VIEWLETS = (
37        'acceptanceletterupload', 'certificateupload'
38        )
39    # Maximum size of upload files in kB
40    MAX_KB = 500
41
42    #: A tuple containing the names of registration states in which changing of
43    #: passport pictures is allowed.
44
45    PORTRAIT_CHANGE_STATES = (ADMITTED, CLEARANCE,)
46
47    REQUIRED_PAYMENTS = {
48        'registration': 'Registration Fee',
49        'book': 'Book Deposit',
50        'develop': 'Development Fee',
51        'parentsconsult': 'Parents Consultative Forum (PCF) Fee',
52        'municipal': 'Municipal Fee',
53        }
54
55    def warnCreditsOOR(self, studylevel, course=None):
56        """Return message if credits are out of range. In the base
57        package only maximum credits is set.
58        """
59        if course and studylevel.total_credits + course.credits > 60:
60            return _('Maximum credits exceeded.')
61        elif studylevel.total_credits > 60:
62            return _('Maximum credits exceeded.')
63        return
64
65    def _requiredPaymentsMissing(self, student, session):
66        cats_missing = deepcopy(self.REQUIRED_PAYMENTS)
67        if len(student['payments']):
68            for category in self.REQUIRED_PAYMENTS.keys():
69                for ticket in student['payments'].values():
70                    if ticket.p_state == 'paid' and \
71                        ticket.p_category.startswith(category) and \
72                        ticket.p_session == session:
73                        del cats_missing[category]
74        if cats_missing:
75            return "%s must be paid before Tution Fee." % ', '.join(
76                cats_missing.values())
77        return
78
79    def setPaymentDetails(self, category, student,
80            previous_session=None, previous_level=None, combi=[]):
81        """Create a payment ticket and set the payment data of a
82        student for the payment category specified.
83        """
84        p_item = u''
85        amount = 0.0
86        if previous_session:
87            if previous_session < student['studycourse'].entry_session:
88                return _('The previous session must not fall below '
89                         'your entry session.'), None
90            if category == 'schoolfee':
91                # School fee is always paid for the following session
92                if previous_session > student['studycourse'].current_session:
93                    return _('This is not a previous session.'), None
94            else:
95                if previous_session > student['studycourse'].current_session - 1:
96                    return _('This is not a previous session.'), None
97            p_session = previous_session
98            p_level = previous_level
99            p_current = False
100        else:
101            p_session = student['studycourse'].current_session
102            p_level = student['studycourse'].current_level
103            p_current = True
104            if category in self.REQUIRED_PAYMENTS.keys() + [
105                'schoolfee','schoolfee40','secondinstal'] \
106                and student.state == RETURNING:
107                # In case of school fee or required sundry fee payments the
108                # payment session is always next session if students are in
109                # state returning.
110                p_session, p_level = self.getReturningData(student)
111        academic_session = self._getSessionConfiguration(p_session)
112        if academic_session == None:
113            return _(u'Session configuration object is not available.'), None
114        # Determine fee.
115        if category in ('schoolfee', 'schoolfee40', 'secondinstal'):
116            rpm = self._requiredPaymentsMissing(student, p_session)
117            if rpm:
118                return rpm, None
119            try:
120                certificate = student['studycourse'].certificate
121                p_item = certificate.code
122            except (AttributeError, TypeError):
123                return _('Study course data are incomplete.'), None
124            if previous_session:
125                # Students can pay for previous sessions in all
126                # workflow states.  Fresh students are excluded by the
127                # update method of the PreviousPaymentAddFormPage.
128                if previous_level == 100:
129                    amount = getattr(certificate, 'school_fee_1', 0.0)
130                else:
131                    amount = getattr(certificate, 'school_fee_2', 0.0)
132                if category == 'schoolfee40':
133                    amount = 0.4*amount
134                elif category == 'secondinstal':
135                    amount = 0.6*amount
136            else:
137                if category == 'secondinstal':
138                    if student.is_fresh:
139                        amount = 0.6 * getattr(certificate, 'school_fee_1', 0.0)
140                    else:
141                        amount = 0.6 * getattr(certificate, 'school_fee_2', 0.0)
142                else:
143                    if student.state in (CLEARANCE, REQUESTED, CLEARED):
144                        amount = getattr(certificate, 'school_fee_1', 0.0)
145                    elif student.state == RETURNING:
146                        amount = getattr(certificate, 'school_fee_2', 0.0)
147                    elif student.is_postgrad and student.state == PAID:
148                        # Returning postgraduate students also pay for the
149                        # next session but their level always remains the
150                        # same.
151                        p_session += 1
152                        academic_session = self._getSessionConfiguration(p_session)
153                        if academic_session == None:
154                            return _(
155                                u'Session configuration object is not available.'
156                                ), None
157                        amount = getattr(certificate, 'school_fee_2', 0.0)
158                    if amount and category == 'schoolfee40':
159                        amount = 0.4*amount
160        elif category == 'clearance':
161            try:
162                p_item = student['studycourse'].certificate.code
163            except (AttributeError, TypeError):
164                return _('Study course data are incomplete.'), None
165            amount = academic_session.clearance_fee
166        elif category.startswith('resit'):
167            amount = academic_session.resit_fee
168            number = int(category.strip('resit'))
169            amount *= number
170        #elif category == 'bed_allocation':
171        #    p_item = self.getAccommodationDetails(student)['bt']
172        #    amount = academic_session.booking_fee
173        #elif category == 'hostel_maintenance':
174        #    amount = 0.0
175        #    bedticket = student['accommodation'].get(
176        #        str(student.current_session), None)
177        #    if bedticket is not None and bedticket.bed is not None:
178        #        p_item = bedticket.bed_coordinates
179        #        if bedticket.bed.__parent__.maint_fee > 0:
180        #            amount = bedticket.bed.__parent__.maint_fee
181        #        else:
182        #            # fallback
183        #            amount = academic_session.maint_fee
184        #    else:
185        #        return _(u'No bed allocated.'), None
186        elif category == 'combi' and combi:
187            categories = getUtility(IKofaUtils).COMBI_PAYMENT_CATEGORIES
188            for cat in combi:
189                fee_name = cat + '_fee'
190                cat_amount = getattr(academic_session, fee_name, 0.0)
191                if not cat_amount:
192                    return _('%s undefined.' % categories[cat]), None
193                amount += cat_amount
194                p_item += u'%s + ' % categories[cat]
195            p_item = p_item.strip(' + ')
196        else:
197            fee_name = category + '_fee'
198            amount = getattr(academic_session, fee_name, 0.0)
199        if amount in (0.0, None):
200            return _('Amount could not be determined.'), None
201        if self.samePaymentMade(student, category, p_item, p_session):
202            return _('This type of payment has already been made.'), None
203        if self._isPaymentDisabled(p_session, category, student):
204            return _('This category of payments has been disabled.'), None
205        payment = createObject(u'waeup.StudentOnlinePayment')
206        timestamp = ("%d" % int(time()*10000))[1:]
207        payment.p_id = "p%s" % timestamp
208        payment.p_category = category
209        payment.p_item = p_item
210        payment.p_session = p_session
211        payment.p_level = p_level
212        payment.p_current = p_current
213        payment.amount_auth = amount
214        payment.p_combi = combi
215        return None, payment
216
217    def setBalanceDetails(self, category, student,
218            balance_session, balance_level, balance_amount):
219        """Create a balance payment ticket and set the payment data
220        as selected by the student.
221        """
222        if category in ('schoolfee', 'schoolfee40', 'secondinstal') \
223            and balance_session > 2019:
224            rpm = self._requiredPaymentsMissing(student, balance_session)
225            if rpm:
226                return rpm, None
227        return super(
228            CustomStudentsUtils, self).setBalanceDetails(category, student,
229            balance_session, balance_level, balance_amount)
230
231    def constructMatricNumber(self, student):
232        """Fetch the matric number counter which fits the student and
233        construct the new matric number of the student.
234        """
235        next_integer = grok.getSite()['configuration'].next_matric_integer
236        if next_integer == 0:
237            return _('Matriculation number cannot be set.'), None
238        if not student.state in (
239            RETURNING, CLEARED, PAID, REGISTERED, VALIDATED):
240            return _('Matriculation number cannot be set.'), None
241        year = unicode(student.entry_session)[2:]
242        return None, "%s/%06d" % (year, next_integer)
Note: See TracBrowser for help on using the repository browser.