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

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

Implement PG schoolfee payments.

Show provider_amt in payment ticket pages.

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