source: main/waeup.aaue/trunk/src/waeup/aaue/students/tests/test_browser.py @ 8755

Last change on this file since 8755 was 8753, checked in by Henrik Bettermann, 13 years ago

Implement school fee payment by two instalments. Instalment 1 replaces the original school fee payment, instalment 2 is an additional payment which can only be made if instalment 1 has been paid. Furthermore, instalment 1 can only be made if instalment 2 of the previous session has been paid (returning students only).

  • Property svn:keywords set to Id
File size: 19.6 KB
Line 
1## $Id: test_browser.py 8753 2012-06-18 17:00:00Z 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 os
19import shutil
20import tempfile
21from StringIO import StringIO
22from hurry.workflow.interfaces import IWorkflowState
23from zope.component.hooks import setSite, clearSite
24from zope.component import getUtility, createObject
25from zope.interface import verify
26from waeup.kofa.app import University
27from waeup.kofa.students.tests.test_browser import StudentsFullSetup
28from waeup.kofa.testing import FunctionalTestCase
29from waeup.kofa.interfaces import (
30    IExtFileStore, IFileStoreNameChooser)
31from waeup.kofa.students.batching import StudentProcessor
32from waeup.kofa.students.interfaces import IStudentsUtils
33from waeup.aaue.students.batching import CustomStudentProcessor
34from waeup.aaue.testing import FunctionalLayer
35from waeup.aaue.students.interfaces import (
36    ICustomStudentStudyCourse, ICustomStudent,
37    ICustomStudentStudyLevel, ICustomCourseTicket)
38
39
40STUDENT_SAMPLE_DATA = open(
41    os.path.join(os.path.dirname(__file__), 'sample_student_data.csv'),
42    'rb').read()
43
44STUDENT_HEADER_FIELDS = STUDENT_SAMPLE_DATA.split(
45    '\n')[0].split(',')
46
47class StudentProcessorTest(FunctionalTestCase):
48    """Perform some batching tests.
49    """
50
51    layer = FunctionalLayer
52
53    def setUp(self):
54        super(StudentProcessorTest, self).setUp()
55        # Setup a sample site for each test
56        app = University()
57        self.dc_root = tempfile.mkdtemp()
58        app['datacenter'].setStoragePath(self.dc_root)
59
60        # Prepopulate the ZODB...
61        self.getRootFolder()['app'] = app
62        # we add the site immediately after creation to the
63        # ZODB. Catalogs and other local utilities are not setup
64        # before that step.
65        self.app = self.getRootFolder()['app']
66        # Set site here. Some of the following setup code might need
67        # to access grok.getSite() and should get our new app then
68        setSite(app)
69
70        self.processor_base = StudentProcessor()
71        self.processor = CustomStudentProcessor()
72        self.workdir = tempfile.mkdtemp()
73        self.csv_file = os.path.join(self.workdir, 'sample_student_data.csv')
74        open(self.csv_file, 'wb').write(STUDENT_SAMPLE_DATA)
75
76    def tearDown(self):
77        super(StudentProcessorTest, self).tearDown()
78        shutil.rmtree(self.workdir)
79        shutil.rmtree(self.dc_root)
80        clearSite()
81        return
82
83    def test_import(self):
84        # We have an empty column 'date_of_birth' in  the import file.
85        # The original processor will fail because 'date_of_birth' is required
86        # in the base package.
87        num, num_warns, fin_file, fail_file = self.processor_base.doImport(
88            self.csv_file, STUDENT_HEADER_FIELDS)
89        self.assertEqual(num_warns,3)
90        assert len(self.app['students'].keys()) == 0
91        # The customized processor does not complain since 'date_of_birth' is
92        # not required in the custom package.
93        num, num_warns, fin_file, fail_file = self.processor.doImport(
94            self.csv_file, STUDENT_HEADER_FIELDS)
95        #print open(fail_file).read()
96        self.assertEqual(num_warns,0)
97        assert len(self.app['students'].keys()) == 3
98        shutil.rmtree(os.path.dirname(fin_file))
99
100
101class StudentUITests(StudentsFullSetup):
102    """Tests for customized student class views and pages
103    """
104
105    layer = FunctionalLayer
106
107    def test_classes(self):
108        # Let's see if objects created in the customized
109        # portal really implement the customized interfaces
110        verify.verifyObject(ICustomStudent, self.student)
111        verify.verifyObject(
112            ICustomStudentStudyCourse, self.student['studycourse'])
113        studylevel = createObject(u'waeup.StudentStudyLevel')
114        verify.verifyObject(ICustomStudentStudyLevel, studylevel)
115        ticket = createObject(u'waeup.CourseTicket')
116        verify.verifyObject(ICustomCourseTicket, ticket)
117        IWorkflowState(self.student).setState('returning')
118        # Let's see if next_session_allowed works as expected
119        # A, ug_ft, 100
120        self.assertTrue(self.student['studycourse'].next_session_allowed)
121        # O, ug_ft, 100
122        self.student['studycourse'].current_verdict = 'O'
123        self.assertTrue(self.student['studycourse'].next_session_allowed)
124        # O, ug_ft, 200
125        self.student['studycourse'].current_level = 200
126        self.assertFalse(self.student['studycourse'].next_session_allowed)
127        # O, de_ft, 200
128        self.student['studycourse'].certificate.study_mode = 'de_ft'
129        self.assertTrue(self.student['studycourse'].next_session_allowed)
130        # O, ph_ft, 300
131        self.student['studycourse'].certificate.study_mode = 'ph_ft'
132        self.student['studycourse'].current_level = 300
133        self.assertTrue(self.student['studycourse'].next_session_allowed)
134        # O, ph_ft, 400
135        self.student['studycourse'].current_level = 400
136        self.assertFalse(self.student['studycourse'].next_session_allowed)
137
138        # Now we convert the certificate into a postgraduate certificate
139        IWorkflowState(self.student).setState('school fee paid')
140        self.certificate.study_mode = 'pg_ft'
141        # ... and voila next session registration is allowed
142        self.assertTrue(self.student['studycourse'].next_session_allowed)
143
144    def test_manage_access(self):
145        # Managers can access the pages of students
146        # and can perform actions
147        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
148        # The student created in the base package is an ug student
149        self.browser.open(self.student_path)
150        self.browser.getLink("Clearance Data").click()
151        self.assertEqual(self.browser.headers['Status'], '200 Ok')
152        self.assertEqual(self.browser.url, self.clearance_path)
153        self.browser.getLink("Manage").click()
154        self.assertEqual(self.browser.headers['Status'], '200 Ok')
155        self.assertEqual(self.browser.url, self.manage_clearance_path)
156        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
157        self.browser.getControl("Save").click()
158        self.assertMatches('...Form has been saved...',
159                           self.browser.contents)
160        self.assertMatches('...First Sitting Record...',
161                           self.browser.contents)
162        # Managers can open clearance slip of ug students
163        self.browser.open(self.student_path + '/clearance.pdf')
164        self.assertEqual(self.browser.headers['Status'], '200 Ok')
165        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
166        # There is no pg field in the clearance form
167        self.assertFalse('Second Higher Education Record'
168            in self.browser.contents)
169        # Now we change the study mode ...
170        self.certificate.study_mode = 'pg_ft'
171        self.browser.open(self.clearance_path)
172        # ... and additional pg clearance fields appear
173        self.assertMatches('...Second Higher Education Record...',
174                           self.browser.contents)
175        # But also fields from the ug form are displayed
176        self.assertMatches('...First Sitting Record...',
177                           self.browser.contents)
178        # Managers can open clearance slip of pg students
179        self.browser.open(self.student_path + '/clearance.pdf')
180        self.assertEqual(self.browser.headers['Status'], '200 Ok')
181        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
182
183    def test_manage_payments(self):
184        # Add missing configuration data
185        self.app['configuration']['2004'].gown_fee = 150.0
186        self.app['configuration']['2004'].transfer_fee = 90.0
187        #self.app['configuration']['2004'].clearance_fee = 120.0
188        self.app['configuration']['2004'].booking_fee = 150.0
189        self.app['configuration']['2004'].maint_fee = 180.0
190
191        # Managers can add online payment tickets
192        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
193        self.browser.open(self.payments_path)
194        self.browser.getControl("Add online payment ticket").click()
195        self.browser.getControl("Create ticket").click()
196        self.assertMatches('...Wrong state...',
197                           self.browser.contents)
198        IWorkflowState(self.student).setState('cleared')
199        self.browser.open(self.payments_path + '/addop')
200        self.browser.getControl("Create ticket").click()
201        self.assertMatches('...Amount could not be determined...',
202                           self.browser.contents)
203
204        self.app['configuration']['2004'].school_fee_base = 6666.0
205
206        self.browser.getControl("Add online payment ticket").click()
207        self.browser.getControl("Create ticket").click()
208        self.assertMatches('...ticket created...',
209                           self.browser.contents)
210        ctrl = self.browser.getControl(name='val_id')
211        value = ctrl.options[0]
212        self.browser.getLink(value).click()
213        self.assertMatches('...Amount Authorized...',
214                           self.browser.contents)
215        # Managers can open payment slip
216        self.browser.getLink("Download payment slip").click()
217        self.assertEqual(self.browser.headers['Status'], '200 Ok')
218        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
219        # Set ticket paid
220        ticket = self.student['payments'].items()[0][1]
221        ticket.p_state = 'paid'
222        self.browser.open(self.payments_path + '/addop')
223        self.browser.getControl("Create ticket").click()
224        self.assertMatches('...This type of payment has already been made...',
225                           self.browser.contents)
226        self.browser.open(self.payments_path + '/addop')
227        # Also the other payments can be made
228        self.browser.getControl(name="form.p_category").value = ['gown']
229        self.browser.getControl("Create ticket").click()
230        self.assertMatches('...ticket created...',
231                           self.browser.contents)
232        self.browser.open(self.payments_path + '/addop')
233        self.browser.getControl(name="form.p_category").value = ['transfer']
234        self.browser.getControl("Create ticket").click()
235        self.assertMatches('...ticket created...',
236                           self.browser.contents)
237        self.browser.open(self.payments_path + '/addop')
238        self.browser.getControl(
239            name="form.p_category").value = ['bed_allocation']
240        self.browser.getControl("Create ticket").click()
241        self.assertMatches('...ticket created...',
242                           self.browser.contents)
243        self.browser.open(self.payments_path + '/addop')
244        self.browser.getControl(
245            name="form.p_category").value = ['hostel_maintenance']
246        self.browser.getControl("Create ticket").click()
247        self.assertMatches('...ticket created...',
248                           self.browser.contents)
249        self.browser.open(self.payments_path + '/addop')
250        self.browser.getControl(name="form.p_category").value = ['clearance']
251        self.browser.getControl("Create ticket").click()
252        self.assertMatches('...ticket created...',
253                           self.browser.contents)
254        # Remove all payments so that we can add a school fee payment again
255        keys = [i for i in self.student['payments'].keys()]
256        for payment in keys:
257            del self.student['payments'][payment]
258        self.browser.open(self.payments_path + '/addop')
259        self.browser.getControl(name="form.p_category").value = ['schoolfee_1']
260        self.browser.getControl("Create ticket").click()
261        self.assertMatches('...ticket created...',
262                           self.browser.contents)
263        # In state returning we can't add a new school fee ticket
264        # for the next session because the second instalment is missing ...
265        IWorkflowState(self.student).setState('returning')
266        self.browser.open(self.payments_path + '/addop')
267        self.browser.getControl(name="form.p_category").value = ['schoolfee_1']
268        self.browser.getControl("Create ticket").click()
269        self.assertMatches('...2nd school fee instalment has not yet been paid...',
270                           self.browser.contents)
271        self.browser.open(self.payments_path + '/addop')
272        self.browser.getControl(name="form.p_category").value = ['schoolfee_2']
273        # ... and the first one has not yet been approved.
274        self.browser.getControl("Create ticket").click()
275        self.assertMatches('...1st school fee instalment has not yet been paid...',
276                           self.browser.contents)
277        # Ok, then we approve the first instalment ...
278        ctrl = self.browser.getControl(name='val_id')
279        p_id = ctrl.options[0]
280        self.browser.open(self.payments_path + '/' + p_id + '/approve')
281        # ... add the second instalment ...
282        self.browser.open(self.payments_path + '/addop')
283        self.browser.getControl(name="form.p_category").value = ['schoolfee_2']
284        self.browser.getControl("Create ticket").click()
285        self.assertMatches('...ticket created...',
286                           self.browser.contents)
287        # ... approve the second instalment ...
288        ctrl = self.browser.getControl(name='val_id')
289        p_id = ctrl.options[1]
290        self.browser.open(self.payments_path + '/' + p_id + '/approve')
291        # ... and finally add the 1st instalment for the next session.
292        self.browser.open(self.payments_path + '/addop')
293        self.browser.getControl(name="form.p_category").value = ['schoolfee_1']
294        self.browser.getControl("Create ticket").click()
295        self.assertMatches('...ticket created...',
296                           self.browser.contents)
297
298        # If the session configuration doesn't exist an error message will
299        # be shown. No other requirement is being checked.
300        del self.app['configuration']['2004']
301        self.browser.open(self.payments_path)
302        self.browser.getControl("Add online payment ticket").click()
303        self.browser.getControl("Create ticket").click()
304        self.assertMatches('...Session configuration object is not...',
305                           self.browser.contents)
306
307    def test_student_access(self):
308        # Students can edit clearance data
309        IWorkflowState(self.student).setState('cleared')
310        self.student.clearance_locked = False
311        self.browser.open(self.login_path)
312        self.browser.getControl(name="form.login").value = self.student_id
313        self.browser.getControl(name="form.password").value = 'spwd'
314        self.browser.getControl("Login").click()
315        # Even in state admitted students can't change the portait if
316        # application slip exists.
317        IWorkflowState(self.student).setState('admitted')
318        self.browser.open(self.student_path)
319        self.assertTrue('Change portrait' in self.browser.contents)
320        file_store = getUtility(IExtFileStore)
321        applicant_slip = 'My application slip'
322        file_id = IFileStoreNameChooser(self.student).chooseName(
323            attr="application_slip.pdf")
324        file_store.createFile(file_id, StringIO(applicant_slip))
325        self.browser.open(self.student_path)
326        self.assertFalse('Change portrait' in self.browser.contents)
327        self.browser.open(self.student_path + '/change_portrait')
328        self.assertTrue('The requested form is locked' in self.browser.contents)
329        # Student can view and edit clearance data
330        self.browser.getLink("Clearance Data").click()
331        self.browser.getLink("Edit").click()
332        self.assertTrue('Save' in self.browser.contents)
333
334    def test_get_returning_data(self):
335        # Student is in level 100, session 2004 with verdict A
336        utils = getUtility(IStudentsUtils)
337        self.assertEqual(utils.getReturningData(self.student),(2005, 200))
338        self.student['studycourse'].current_verdict = 'C'
339        self.assertEqual(utils.getReturningData(self.student),(2005, 110))
340        self.student['studycourse'].current_verdict = 'D'
341        self.assertEqual(utils.getReturningData(self.student),(2005, 100))
342        return
343
344    def test_set_payment_details(self):
345        self.app['configuration']['2004'].gown_fee = 150.0
346        self.app['configuration']['2004'].transfer_fee = 90.0
347        self.app['configuration']['2004'].booking_fee = 150.0
348        self.app['configuration']['2004'].maint_fee = 180.0
349        self.app['configuration']['2004'].clearance_fee = 1234.0
350        self.app['configuration']['2004'].school_fee_base = 6666.0
351        utils = getUtility(IStudentsUtils)
352
353        error, payment = utils.setPaymentDetails('schoolfee_1',self.student)
354        self.assertEqual(payment, None)
355        self.assertEqual(error, u'Wrong state.')
356
357        IWorkflowState(self.student).setState('cleared')
358        error, payment = utils.setPaymentDetails('schoolfee_1',self.student)
359        self.assertEqual(payment.p_level, 100)
360        self.assertEqual(payment.p_session, 2004)
361        self.assertEqual(payment.amount_auth, 6666.0)
362        self.assertEqual(payment.p_item, u'CERT1')
363        self.assertEqual(error, None)
364
365        # Add penalty fee.
366        self.app['configuration']['2004'].penalty_ug = 99.0
367        error, payment = utils.setPaymentDetails('schoolfee_1',self.student)
368        self.assertEqual(payment.amount_auth, 6765.0)
369
370        IWorkflowState(self.student).setState('returning')
371        error, payment = utils.setPaymentDetails('schoolfee_1',self.student)
372        self.assertEqual(payment.p_level, 200)
373        self.assertEqual(payment.p_session, 2005)
374        self.assertEqual(payment.amount_auth, 6765.0)
375        self.assertEqual(payment.p_item, u'CERT1')
376        self.assertEqual(error, None)
377
378        error, payment = utils.setPaymentDetails('clearance',self.student)
379        self.assertEqual(payment.p_level, 100)
380        self.assertEqual(payment.p_session, 2004)
381        self.assertEqual(payment.amount_auth, 1234.0)
382        self.assertEqual(payment.p_item, u'CERT1')
383        self.assertEqual(error, None)
384
385        error, payment = utils.setPaymentDetails('gown',self.student)
386        self.assertEqual(payment.p_level, 100)
387        self.assertEqual(payment.p_session, 2004)
388        self.assertEqual(payment.amount_auth, 150.0)
389        self.assertEqual(payment.p_item, u'')
390        self.assertEqual(error, None)
391
392        error, payment = utils.setPaymentDetails('hostel_maintenance',self.student)
393        self.assertEqual(payment.p_level, 100)
394        self.assertEqual(payment.p_session, 2004)
395        self.assertEqual(payment.amount_auth, 180.0)
396        self.assertEqual(payment.p_item, u'')
397        self.assertEqual(error, None)
398
399        error, payment = utils.setPaymentDetails('bed_allocation',self.student)
400        self.assertEqual(payment.p_level, 100)
401        self.assertEqual(payment.p_session, 2004)
402        self.assertEqual(payment.amount_auth, 150.0)
403        self.assertEqual(payment.p_item, u'')
404        self.assertEqual(error, None)
405
406        error, payment = utils.setPaymentDetails('transfer',self.student)
407        self.assertEqual(payment.p_level, 100)
408        self.assertEqual(payment.p_session, 2004)
409        self.assertEqual(payment.amount_auth, 90.0)
410        self.assertEqual(payment.p_item, u'')
411        self.assertEqual(error, None)
412        return
Note: See TracBrowser for help on using the repository browser.