- Timestamp:
- 4 Jun 2016, 04:08:45 (8 years ago)
- Location:
- main/waeup.kofa/trunk
- Files:
-
- 1 added
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.kofa/trunk/CHANGES.txt
r13773 r13872 4 4 1.4.2.dev0 (unreleased) 5 5 ======================= 6 7 * Add `ApplicantOnlinePaymentProcessor`. 6 8 7 9 * Improve `AddStudyLevelFormPage`. -
main/waeup.kofa/trunk/src/waeup/kofa/applicants/batching.py
r12872 r13872 22 22 from zope.schema import getFields 23 23 from zope.interface import Interface 24 from zope.component import queryUtility 24 from zope.component import queryUtility, getUtility 25 25 from hurry.workflow.interfaces import IWorkflowState 26 26 from zope.catalog.interfaces import ICatalog 27 27 from waeup.kofa.interfaces import ( 28 28 IBatchProcessor, IObjectConverter, FatalCSVError, IGNORE_MARKER, 29 IObjectHistory, IUserAccount )29 IObjectHistory, IUserAccount, DuplicationError) 30 30 from waeup.kofa.interfaces import MessageFactory as _ 31 from waeup.kofa.payments.interfaces import IPayer 31 32 from waeup.kofa.utils.batching import BatchProcessor 32 33 from waeup.kofa.applicants.interfaces import ( 33 IApplicantsContainer, IApplicant, IApplicantUpdateByRegNo) 34 IApplicantsContainer, IApplicant, IApplicantUpdateByRegNo, 35 IApplicantOnlinePayment) 34 36 from waeup.kofa.applicants.workflow import IMPORTABLE_STATES, CREATED 35 37 … … 335 337 return 'Applicant is blocked.' 336 338 return None 339 340 class ApplicantOnlinePaymentProcessor(BatchProcessor): 341 """The Applicant Online Payment Processor imports applicant payment tickets. 342 The tickets are located in the applicant container. 343 The only additional locator is `p_id`, the object id. 344 345 The `checkConversion` method checks the format of the payment identifier. 346 In create mode it does also ensures that same p_id does not exist 347 elsewhere. It must be portal-wide unique. 348 349 When adding a payment ticket, the `addEntry` method checks if a 350 payment has already been made. If so, a `DuplicationError` is raised. 351 """ 352 grok.implements(IBatchProcessor) 353 grok.provides(IBatchProcessor) 354 grok.context(Interface) 355 util_name = 'applicantpaymentprocessor' 356 grok.name(util_name) 357 358 name = _('ApplicantOnlinePayment Processor') 359 iface = IApplicantOnlinePayment 360 factory_name = 'waeup.ApplicantOnlinePayment' 361 362 location_fields = ['applicant_id', 'p_id'] 363 364 @property 365 def available_fields(self): 366 af = sorted(list(set( 367 self.location_fields + getFields(self.iface).keys())) + ['p_id',]) 368 af.remove('display_item') 369 return af 370 371 def parentsExist(self, row, site): 372 return self.getParent(row, site) is not None 373 374 def getParent(self, row, site): 375 applicant_id = row['applicant_id'] 376 cat = queryUtility(ICatalog, name='applicants_catalog') 377 results = list( 378 cat.searchResults(applicant_id=(applicant_id, applicant_id))) 379 if results: 380 return results[0] 381 return None 382 383 def getEntry(self, row, site): 384 applicant = self.getParent(row, site) 385 if applicant is None: 386 return None 387 p_id = row.get('p_id', None) 388 if p_id in (None, IGNORE_MARKER): 389 return None 390 # We can use the hash symbol at the end of p_id in import files 391 # to avoid annoying automatic number transformation 392 # by Excel or Calc 393 p_id = p_id.strip('#') 394 entry = applicant.get(p_id) 395 return entry 396 397 def entryExists(self, row, site): 398 return self.getEntry(row, site) is not None 399 400 def updateEntry(self, obj, row, site, filename): 401 """Update obj to the values given in row. 402 """ 403 items_changed = super(ApplicantOnlinePaymentProcessor, self).updateEntry( 404 obj, row, site, filename) 405 applicant = self.getParent(row, site) 406 applicant.__parent__.__parent__.logger.info( 407 '%s - %s - %s - updated: %s' 408 % (self.name, filename, applicant.applicant_id, items_changed)) 409 return 410 411 def samePaymentMade(self, applicant, category): 412 for key in applicant.keys(): 413 ticket = applicant[key] 414 if ticket.p_state == 'paid' and\ 415 ticket.p_category == category: 416 return True 417 return False 418 419 def addEntry(self, obj, row, site): 420 applicant = self.getParent(row, site) 421 p_id = row['p_id'].strip('#') 422 if self.samePaymentMade(applicant, obj.p_category): 423 applicant.__parent__.__parent__.logger.info( 424 '%s - %s - previous update cancelled' 425 % (self.name, applicant.applicant_id)) 426 raise DuplicationError('Payment has already been made.') 427 applicant[p_id] = obj 428 return 429 430 def delEntry(self, row, site): 431 payment = self.getEntry(row, site) 432 applicant = self.getParent(row, site) 433 if payment is not None: 434 applicant.__parent__.__parent__.logger.info('%s - Payment ticket removed: %s' 435 % (applicant.applicant_id, payment.p_id)) 436 del applicant[payment.p_id] 437 return 438 439 def checkConversion(self, row, mode='ignore'): 440 """Validates all values in row. 441 """ 442 errs, inv_errs, conv_dict = super( 443 ApplicantOnlinePaymentProcessor, self).checkConversion(row, mode=mode) 444 # We have to check p_id. 445 p_id = row.get('p_id', None) 446 if mode == 'create' and p_id in (None, IGNORE_MARKER): 447 timestamp = ("%d" % int(time()*10000))[1:] 448 p_id = "p%s" % timestamp 449 conv_dict['p_id'] = p_id 450 return errs, inv_errs, conv_dict 451 elif p_id in (None, IGNORE_MARKER): 452 errs.append(('p_id','missing')) 453 return errs, inv_errs, conv_dict 454 else: 455 p_id = p_id.strip('#') 456 if not len(p_id) == 14: 457 errs.append(('p_id','invalid length')) 458 return errs, inv_errs, conv_dict 459 if mode == 'create': 460 cat = getUtility(ICatalog, name='payments_catalog') 461 results = list(cat.searchResults(p_id=(p_id, p_id))) 462 if len(results) > 0: 463 sids = [IPayer(payment).id for payment in results] 464 sids_string = '' 465 for id in sids: 466 sids_string += '%s ' % id 467 errs.append(('p_id','p_id exists in %s' % sids_string)) 468 return errs, inv_errs, conv_dict 469 return errs, inv_errs, conv_dict -
main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_batching.py
r13073 r13872 33 33 from waeup.kofa.app import University 34 34 from waeup.kofa.applicants.batching import ( 35 ApplicantsContainerProcessor, ApplicantProcessor) 35 ApplicantsContainerProcessor, ApplicantProcessor, 36 ApplicantOnlinePaymentProcessor) 36 37 from waeup.kofa.applicants.container import ApplicantsContainer 37 38 from waeup.kofa.applicants.applicant import Applicant … … 39 40 from waeup.kofa.university.department import Department 40 41 from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase 41 from waeup.kofa.interfaces import IBatchProcessor, IUserAccount 42 from waeup.kofa.interfaces import ( 43 IBatchProcessor, IUserAccount, DuplicationError) 42 44 from waeup.kofa.applicants.workflow import CREATED 43 45 … … 75 77 76 78 APPLICANT_HEADER_FIELDS_UPDATE2 = APPLICANT_SAMPLE_DATA_UPDATE2.split( 79 '\n')[0].split(',') 80 81 PAYMENT_SAMPLE_DATA = open( 82 os.path.join(os.path.dirname(__file__), 'sample_payment_data.csv'), 83 'rb').read() 84 85 PAYMENT_HEADER_FIELDS = PAYMENT_SAMPLE_DATA.split( 77 86 '\n')[0].split(',') 78 87 … … 225 234 self.application_number] 226 235 self.workdir = tempfile.mkdtemp() 236 237 self.logfile = os.path.join( 238 self.app['datacenter'].storage, 'logs', 'applicants.log') 227 239 return 228 240 … … 252 264 open(self.csv_file_update, 'wb').write(APPLICANT_SAMPLE_DATA_UPDATE) 253 265 open(self.csv_file_update2, 'wb').write(APPLICANT_SAMPLE_DATA_UPDATE2) 254 255 self.logfile = os.path.join(256 self.app['datacenter'].storage, 'logs', 'applicants.log')257 266 258 267 def test_interface(self): … … 415 424 logcontent) 416 425 shutil.rmtree(os.path.dirname(fin_file)) 426 427 class PaymentProcessorTest(ApplicantImportExportSetup): 428 429 def setUp(self): 430 super(PaymentProcessorTest, self).setUp() 431 432 applicant = Applicant() 433 applicant.firstname = u'Anna2' 434 applicant.lastname = u'Tester' 435 applicant.applicant_id = u'dp2011_1234' 436 self.app['applicants']['dp2011'].addApplicant(applicant) 437 payment = createObject(u'waeup.ApplicantOnlinePayment') 438 payment.p_id = 'p120' 439 payment.p_session = 2012 440 payment.p_category = 'application' 441 payment.p_state = 'paid' 442 applicant['p120'] = payment 443 self.applicant2 = applicant 444 self.processor = ApplicantOnlinePaymentProcessor() 445 self.csv_file = os.path.join( 446 self.workdir, 'sample_payment_data.csv') 447 open(self.csv_file, 'wb').write(PAYMENT_SAMPLE_DATA) 448 449 def test_interface(self): 450 # Make sure we fulfill the interface contracts. 451 assert verifyObject(IBatchProcessor, self.processor) is True 452 assert verifyClass( 453 IBatchProcessor, ApplicantOnlinePaymentProcessor) is True 454 455 def test_getEntry(self): 456 assert self.processor.getEntry( 457 dict(applicant_id='ID_NONE', p_id='nonsense'), self.app) is None 458 assert self.processor.getEntry( 459 dict(applicant_id=self.applicant2.applicant_id, p_id='p120'), 460 self.app) is self.applicant2['p120'] 461 assert self.processor.getEntry( 462 dict(applicant_id=self.applicant2.applicant_id, p_id='p120#'), 463 self.app) is self.applicant2['p120'] 464 465 def test_delEntry(self): 466 assert self.processor.getEntry( 467 dict(applicant_id=self.applicant2.applicant_id, p_id='p120'), 468 self.app) is self.applicant2['p120'] 469 self.assertEqual(len(self.applicant2.keys()),1) 470 self.processor.delEntry( 471 dict(applicant_id=self.applicant2.applicant_id, p_id='p120'), 472 self.app) 473 assert self.processor.getEntry( 474 dict(applicant_id=self.applicant2.applicant_id, p_id='p120'), 475 self.app) is None 476 self.assertEqual(len(self.applicant.keys()),0) 477 478 def test_addEntry(self): 479 self.assertEqual(len(self.applicant2.keys()),1) 480 self.processor.delEntry( 481 dict(applicant_id=self.applicant2.applicant_id, p_id='p120'), 482 self.app) 483 payment1 = createObject(u'waeup.ApplicantOnlinePayment') 484 payment1.p_category = 'application' 485 payment1.p_id = 'p234' 486 self.processor.addEntry( 487 payment1, dict(applicant_id=self.applicant2.applicant_id, p_id='p234'), 488 self.app) 489 self.assertEqual(len(self.applicant2.keys()),1) 490 self.assertEqual(self.applicant2['p234'].p_id, 'p234') 491 # Same payment must not exist. 492 payment1.p_state = 'paid' 493 payment2 = createObject(u'waeup.ApplicantOnlinePayment') 494 payment2.p_id = 'p456' 495 payment2.p_category = 'application' 496 self.assertRaises( 497 DuplicationError, self.processor.addEntry, payment2, 498 dict(applicant_id=self.applicant2.applicant_id, p_id='p456'), self.app) 499 500 def test_import(self): 501 num, num_warns, fin_file, fail_file = self.processor.doImport( 502 self.csv_file, PAYMENT_HEADER_FIELDS,'create') 503 self.assertEqual(num_warns,1) 504 fail_file = open(fail_file).read() 505 self.assertTrue('Payment has already been made' in fail_file) 506 self.processor.delEntry( 507 dict(applicant_id=self.applicant2.applicant_id, p_id='p120'), 508 self.app) 509 num, num_warns, fin_file, fail_file = self.processor.doImport( 510 self.csv_file, PAYMENT_HEADER_FIELDS,'create') 511 self.assertEqual(num_warns,0) 512 payment = self.processor.getEntry(dict(applicant_id='dp2011_1234', 513 p_id='p1266236341955'), self.app) 514 self.assertEqual(payment.p_id, 'p1266236341955') 515 cdate = payment.creation_date.strftime("%Y-%m-%d %H:%M:%S") 516 # Ooooh, still the old problem, see 517 # http://mail.dzug.org/mailman/archives/zope/2006-August/001153.html. 518 # WAT is interpreted as GMT-1 and not GMT+1 519 self.assertEqual(cdate, '2010-11-25 21:16:33') 520 self.assertEqual(str(payment.creation_date.tzinfo),'UTC') 521 shutil.rmtree(os.path.dirname(fin_file)) 522 logcontent = open(self.logfile).read() 523 # Logging message from updateEntry 524 self.assertTrue( 525 'INFO - system - ApplicantOnlinePayment Processor - dp2011_1234 - ' 526 'previous update cancelled' 527 in logcontent) 528 self.assertTrue( 529 'INFO - system - ApplicantOnlinePayment Processor - ' 530 'sample_payment_data - dp2011_1234 - updated: p_id=p1266236341955, ' 531 'creation_date=2010-11-25 21:16:33.757000+00:00, ' 532 'r_amount_approved=19500.1, p_category=application, ' 533 'amount_auth=19500.1, p_session=2015, p_state=paid' 534 in logcontent) -
main/waeup.kofa/trunk/src/waeup/kofa/doctests/batchprocessing_browser.txt
r13431 r13872 98 98 ['AccessCodeBatch Processor', 99 99 'AccessCode Processor', 100 'ApplicantOnlinePayment Processor', 100 101 'Applicant Processor', 101 102 'ApplicantsContainer Processor', -
main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py
r13871 r13872 827 827 # Requirement added on 19/02/2015: same payment must not exist. 828 828 if self.samePaymentMade(student, obj.p_category, obj.p_session): 829 student.__parent__.logger.info( 830 '%s - %s - previous update cancelled' 831 % (self.name, student.student_id)) 829 832 raise DuplicationError('Same payment has already been made.') 830 833 if len(p_id.split('-')) != 3 and not p_id.startswith('p'): -
main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_batching.py
r13103 r13872 1060 1060 DuplicationError, self.processor.addEntry, payment3, 1061 1061 dict(student_id=self.student.student_id, p_id='p456'), self.app) 1062 logcontent = open(self.logfile).read() 1063 self.assertTrue( 1064 'INFO - system - StudentOnlinePayment Processor - K1000000 - ' 1065 'previous update cancelled' 1066 in logcontent) 1062 1067 1063 1068 def test_checkConversion(self):
Note: See TracChangeset for help on using the changeset viewer.