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

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

Puuh, there are different municipal fees for fresh and returning students.

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