source: main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/browser.py @ 12707

Last change on this file since 12707 was 12704, checked in by uli, 10 years ago

At least step 1.

  • Property svn:keywords set to Id
File size: 58.7 KB
Line 
1## $Id: browser.py 12704 2015-03-09 06:34:58Z uli $
2##
3## Copyright (C) 2014 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##
18"""UI components for customers and related components.
19"""
20import grok
21import os
22from urllib import urlencode
23from zope.event import notify
24from zope.i18n import translate
25from zope.catalog.interfaces import ICatalog
26from zope.component import queryUtility, getUtility, createObject
27from zope.security import checkPermission
28from hurry.workflow.interfaces import (
29    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
30from waeup.ikoba.interfaces import MessageFactory as _
31from waeup.ikoba.interfaces import (
32    IContactForm, IObjectHistory, IIkobaObject, IIkobaUtils, IExtFileStore,
33    IPasswordValidator, IUserAccount,
34    STARTED, VERIFIED, REJECTED, EXPIRED, CREATED, REQUESTED,
35    APPROVED, PROVISIONALLY)
36from waeup.ikoba.browser.layout import (
37    IkobaPage, IkobaEditFormPage, IkobaAddFormPage, IkobaDisplayFormPage,
38    NullValidator, jsaction, action, UtilityView)
39from waeup.ikoba.browser.pages import ContactAdminForm
40from waeup.ikoba.browser.breadcrumbs import Breadcrumb
41from waeup.ikoba.browser.interfaces import ICaptchaManager
42from waeup.ikoba.mandates.mandate import PasswordMandate
43from waeup.ikoba.payments.payment import format_payment_item_values
44from waeup.ikoba.payments.interfaces import (
45    IPaymentGatewayServicesLister, IPaymentGatewayService, IPayer
46    )
47from waeup.ikoba.widgets.hrefwidget import HREFDisplayWidget
48from waeup.ikoba.utils.helpers import (
49    get_current_principal, format_date)
50from waeup.ikoba.customers.interfaces import (
51    ICustomer, ICustomersContainer, ICustomerRequestPW, ICustomersUtils,
52    ICustomerDocument, ICustomerDocumentsContainer, ICustomerCreate,
53    ICustomerPDFDocument, IContractsContainer, IContract,
54    IContractSelectProduct,
55    )
56from waeup.ikoba.customers.catalog import search
57from waeup.ikoba.customers.workflow import PAYMENT_TRANSITIONS
58from waeup.ikoba.customers.contracts import payment_items_from_contract
59
60
61grok.context(IIkobaObject)
62
63WARNING_CUST = _('You can not edit some data after final submission.'
64            ' You really want to submit?')
65
66WARNING_DOC = _('You can not edit your document after final submission.'
67            ' You really want to submit?')
68
69WARNING_CON = _('You can not edit your contract after final submission.'
70            ' You really want to submit?')
71
72
73# Save function used for save methods in pages
74def msave(view, **data):
75    changed_fields = view.applyData(view.context, **data)
76    # Turn list of lists into single list
77    if changed_fields:
78        changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
79    if 'product_object' in changed_fields and data['product_object'] is not None:
80        view.context.last_product_id = data['product_object'].product_id
81        notify(grok.ObjectModifiedEvent(view.context))
82    fields_string = ' + '.join(changed_fields)
83    view.flash(_('Form has been saved.'))
84    if fields_string:
85        view.context.writeLogMessage(
86            view, '%s - saved: %s' % (view.context.__name__, fields_string))
87    return
88
89
90def emit_lock_message(view):
91    """Flash a lock message.
92    """
93    view.flash(_('The requested form is locked (read-only).'), type="warning")
94    view.redirect(view.url(view.context))
95    return
96
97def isCustomer(principal):
98    return getattr(principal, 'user_type', None) == 'customer'
99
100
101class CustomersBreadcrumb(Breadcrumb):
102    """A breadcrumb for the customers container.
103    """
104    grok.context(ICustomersContainer)
105    title = _('Customers')
106
107    @property
108    def target(self):
109        user = get_current_principal()
110        if getattr(user, 'user_type', None) == 'customer':
111            return None
112        return self.viewname
113
114
115class CustomerBreadcrumb(Breadcrumb):
116    """A breadcrumb for the customer container.
117    """
118    grok.context(ICustomer)
119
120    def title(self):
121        return self.context.display_fullname
122
123
124class CustomersContainerPage(IkobaPage):
125    """The standard view for customer containers.
126    """
127    grok.context(ICustomersContainer)
128    grok.name('index')
129    grok.require('waeup.viewCustomersContainer')
130    grok.template('containerpage')
131    label = _('Find customers')
132    search_button = _('Find customer(s)')
133    pnav = 4
134
135    def update(self, *args, **kw):
136        form = self.request.form
137        self.hitlist = []
138        if form.get('searchtype', None) == 'suspended':
139            self.searchtype = form['searchtype']
140            self.searchterm = None
141        elif 'searchterm' in form and form['searchterm']:
142            self.searchterm = form['searchterm']
143            self.searchtype = form['searchtype']
144        elif 'old_searchterm' in form:
145            self.searchterm = form['old_searchterm']
146            self.searchtype = form['old_searchtype']
147        else:
148            if 'search' in form:
149                self.flash(_('Empty search string'), type="warning")
150            return
151        if self.searchtype == 'current_session':
152            try:
153                self.searchterm = int(self.searchterm)
154            except ValueError:
155                self.flash(_('Only year dates allowed (e.g. 2011).'),
156                           type="danger")
157                return
158        self.hitlist = search(query=self.searchterm,
159            searchtype=self.searchtype, view=self)
160        if not self.hitlist:
161            self.flash(_('No customer found.'), type="warning")
162        return
163
164
165class CustomersContainerManagePage(IkobaPage):
166    """The manage page for customer containers.
167    """
168    grok.context(ICustomersContainer)
169    grok.name('manage')
170    grok.require('waeup.manageCustomer')
171    grok.template('containermanagepage')
172    pnav = 4
173    label = _('Manage customer section')
174    search_button = _('Find customer(s)')
175    remove_button = _('Remove selected')
176
177    def update(self, *args, **kw):
178        form = self.request.form
179        self.hitlist = []
180        if form.get('searchtype', None) == 'suspended':
181            self.searchtype = form['searchtype']
182            self.searchterm = None
183        elif 'searchterm' in form and form['searchterm']:
184            self.searchterm = form['searchterm']
185            self.searchtype = form['searchtype']
186        elif 'old_searchterm' in form:
187            self.searchterm = form['old_searchterm']
188            self.searchtype = form['old_searchtype']
189        else:
190            if 'search' in form:
191                self.flash(_('Empty search string'), type="warning")
192            return
193        if self.searchtype == 'current_session':
194            try:
195                self.searchterm = int(self.searchterm)
196            except ValueError:
197                self.flash(_('Only year dates allowed (e.g. 2011).'),
198                           type="danger")
199                return
200        if not 'entries' in form:
201            self.hitlist = search(query=self.searchterm,
202                searchtype=self.searchtype, view=self)
203            if not self.hitlist:
204                self.flash(_('No customer found.'), type="warning")
205            if 'remove' in form:
206                self.flash(_('No item selected.'), type="warning")
207            return
208        entries = form['entries']
209        if isinstance(entries, basestring):
210            entries = [entries]
211        deleted = []
212        for entry in entries:
213            if 'remove' in form:
214                del self.context[entry]
215                deleted.append(entry)
216        self.hitlist = search(query=self.searchterm,
217            searchtype=self.searchtype, view=self)
218        if len(deleted):
219            self.flash(_('Successfully removed: ${a}',
220                mapping={'a': ','.join(deleted)}))
221        return
222
223
224class CustomerAddFormPage(IkobaAddFormPage):
225    """Add-form to add a customer.
226    """
227    grok.context(ICustomersContainer)
228    grok.require('waeup.manageCustomer')
229    grok.name('addcustomer')
230    form_fields = grok.AutoFields(ICustomer).select(
231        'firstname', 'middlename', 'lastname', 'reg_number')
232    label = _('Add customer')
233    pnav = 4
234
235    @action(_('Create customer record'), style='primary')
236    def addCustomer(self, **data):
237        customer = createObject(u'waeup.Customer')
238        self.applyData(customer, **data)
239        self.context.addCustomer(customer)
240        self.flash(_('Customer created.'))
241        self.redirect(self.url(self.context[customer.customer_id], 'index'))
242        return
243
244
245class LoginAsCustomerStep1(IkobaEditFormPage):
246    """ View to temporarily set a customer password.
247    """
248    grok.context(ICustomer)
249    grok.name('loginasstep1')
250    grok.require('waeup.loginAsCustomer')
251    grok.template('loginasstep1')
252    pnav = 4
253
254    def label(self):
255        return _(u'Set temporary password for ${a}',
256            mapping={'a': self.context.display_fullname})
257
258    @action('Set password now', style='primary')
259    def setPassword(self, *args, **data):
260        ikoba_utils = getUtility(IIkobaUtils)
261        password = ikoba_utils.genPassword()
262        self.context.setTempPassword(self.request.principal.id, password)
263        self.context.writeLogMessage(
264            self, 'temp_password generated: %s' % password)
265        args = {'password': password}
266        self.redirect(self.url(self.context) +
267            '/loginasstep2?%s' % urlencode(args))
268        return
269
270
271class LoginAsCustomerStep2(IkobaPage):
272    """ View to temporarily login as customer with a temporary password.
273    """
274    grok.context(ICustomer)
275    grok.name('loginasstep2')
276    grok.require('waeup.Public')
277    grok.template('loginasstep2')
278    login_button = _('Login now')
279    pnav = 4
280
281    def label(self):
282        return _(u'Login as ${a}',
283            mapping={'a': self.context.customer_id})
284
285    def update(self, SUBMIT=None, password=None):
286        self.password = password
287        if SUBMIT is not None:
288            self.flash(_('You successfully logged in as customer.'))
289            self.redirect(self.url(self.context))
290        return
291
292
293class CustomerBaseDisplayFormPage(IkobaDisplayFormPage):
294    """ Page to display customer base data
295    """
296    grok.context(ICustomer)
297    grok.name('index')
298    grok.require('waeup.viewCustomer')
299    grok.template('basepage')
300    pnav = 4
301
302    @property
303    def form_fields(self):
304        return grok.AutoFields(
305            self.context.form_fields_interface).omit(
306            'password', 'suspended', 'suspended_comment')
307
308    @property
309    def label(self):
310        if self.context.suspended:
311            return _('${a}: Base Data (account deactivated)',
312                mapping={'a': self.context.display_fullname})
313        return  _('${a}: Base Data',
314            mapping={'a': self.context.display_fullname})
315
316    @property
317    def hasPassword(self):
318        if self.context.password:
319            return _('set')
320        return _('unset')
321
322    @property
323    def is_requestable(self):
324        if self.context.state in (REQUESTED, PROVISIONALLY, APPROVED):
325            return False
326        return True
327
328    def update(self):
329        # Fire transition if customer logs in for the first time
330        usertype = getattr(self.request.principal, 'user_type', None)
331        if usertype == 'customer' and \
332            IWorkflowState(self.context).getState() == CREATED:
333            IWorkflowInfo(self.context).fireTransition('start')
334        return
335
336
337class ContactCustomerForm(ContactAdminForm):
338    grok.context(ICustomer)
339    grok.name('contactcustomer')
340    grok.require('waeup.viewCustomer')
341    pnav = 4
342    form_fields = grok.AutoFields(IContactForm).select('subject', 'body')
343
344
345    def update(self, subject=u'', body=u''):
346        super(ContactCustomerForm, self).update()
347        self.form_fields.get('subject').field.default = subject
348        self.form_fields.get('body').field.default = body
349        return
350
351    def label(self):
352        return _(u'Send message to ${a}',
353            mapping={'a': self.context.display_fullname})
354
355    @action('Send message now', style='primary')
356    def send(self, *args, **data):
357        try:
358            email = self.request.principal.email
359        except AttributeError:
360            email = self.config.email_admin
361        usertype = getattr(self.request.principal,
362                           'user_type', 'system').title()
363        ikoba_utils = getUtility(IIkobaUtils)
364        success = ikoba_utils.sendContactForm(
365                self.request.principal.title, email,
366                self.context.display_fullname, self.context.email,
367                self.request.principal.id,usertype,
368                self.config.name,
369                data['body'], data['subject'])
370        if success:
371            self.flash(_('Your message has been sent.'))
372        else:
373            self.flash(_('An smtp server error occurred.'), type="danger")
374        return
375
376
377class CustomerBaseManageFormPage(IkobaEditFormPage):
378    """ View to manage customer base data
379    """
380    grok.context(ICustomer)
381    grok.name('manage_base')
382    grok.require('waeup.manageCustomer')
383    grok.template('basemanagepage')
384    label = _('Manage base data')
385    pnav = 4
386    deletion_warning = _('Are you sure?')
387
388    @property
389    def form_fields(self):
390        return grok.AutoFields(
391            self.context.form_fields_interface).omit(
392            'customer_id', 'suspended')
393
394    def update(self):
395        super(CustomerBaseManageFormPage, self).update()
396        self.wf_info = IWorkflowInfo(self.context)
397        return
398
399    @action(_('Save'), style='primary')
400    def save(self, **data):
401        form = self.request.form
402        password = form.get('password', None)
403        password_ctl = form.get('control_password', None)
404        if password:
405            validator = getUtility(IPasswordValidator)
406            errors = validator.validate_password(password, password_ctl)
407            if errors:
408                self.flash(' '.join(errors), type="danger")
409                return
410        changed_fields = self.applyData(self.context, **data)
411        # Turn list of lists into single list
412        if changed_fields:
413            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
414        else:
415            changed_fields = []
416        if password:
417            # Now we know that the form has no errors and can set password
418            IUserAccount(self.context).setPassword(password)
419            changed_fields.append('password')
420        fields_string = ' + '.join(changed_fields)
421        self.flash(_('Form has been saved.'))
422        if fields_string:
423            self.context.writeLogMessage(self, 'saved: % s' % fields_string)
424        return
425
426
427class CustomerTriggerTransitionFormPage(IkobaEditFormPage):
428    """ View to trigger customer workflow transitions
429    """
430    grok.context(ICustomer)
431    grok.name('trigtrans')
432    grok.require('waeup.triggerTransition')
433    grok.template('trigtrans')
434    label = _('Trigger registration transition')
435    pnav = 4
436
437    def getTransitions(self):
438        """Return a list of dicts of allowed transition ids and titles.
439
440        Each list entry provides keys ``name`` and ``title`` for
441        internal name and (human readable) title of a single
442        transition.
443        """
444        wf_info = IWorkflowInfo(self.context)
445        allowed_transitions = [t for t in wf_info.getManualTransitions()]
446        return [dict(name='', title=_('No transition'))] +[
447            dict(name=x, title=y) for x, y in allowed_transitions]
448
449    @action(_('Apply'), style='primary')
450    def apply(self, **data):
451        form = self.request.form
452        if 'transition' in form and form['transition']:
453            transition_id = form['transition']
454            wf_info = IWorkflowInfo(self.context)
455            wf_info.fireTransition(transition_id)
456            self.flash(_("Transition '%s' executed." % transition_id))
457            self.redirect(self.url(self.context))
458        return
459
460
461class CustomerActivatePage(UtilityView, grok.View):
462    """ Activate customer account
463    """
464    grok.context(ICustomer)
465    grok.name('activate')
466    grok.require('waeup.manageCustomer')
467
468    def update(self):
469        self.context.suspended = False
470        self.context.writeLogMessage(self, 'account activated')
471        history = IObjectHistory(self.context)
472        history.addMessage('Customer account activated')
473        self.flash(_('Customer account has been activated.'))
474        self.redirect(self.url(self.context))
475        return
476
477    def render(self):
478        return
479
480
481class CustomerDeactivatePage(UtilityView, grok.View):
482    """ Deactivate customer account
483    """
484    grok.context(ICustomer)
485    grok.name('deactivate')
486    grok.require('waeup.manageCustomer')
487
488    def update(self):
489        self.context.suspended = True
490        self.context.writeLogMessage(self, 'account deactivated')
491        history = IObjectHistory(self.context)
492        history.addMessage('Customer account deactivated')
493        self.flash(_('Customer account has been deactivated.'))
494        self.redirect(self.url(self.context))
495        return
496
497    def render(self):
498        return
499
500
501class CustomerHistoryPage(IkobaPage):
502    """ Page to display customer history
503    """
504    grok.context(ICustomer)
505    grok.name('history')
506    grok.require('waeup.viewCustomer')
507    grok.template('customerhistory')
508    pnav = 4
509
510    @property
511    def label(self):
512        return _('${a}: History', mapping={'a':self.context.display_fullname})
513
514
515class CustomerRequestPasswordPage(IkobaAddFormPage):
516    """Captcha'd password request page for customers.
517    """
518    grok.name('requestpw')
519    grok.require('waeup.Anonymous')
520    grok.template('requestpw')
521    form_fields = grok.AutoFields(ICustomerRequestPW)
522    label = _('Request password for first-time login')
523
524    def update(self):
525        # Handle captcha
526        self.captcha = getUtility(ICaptchaManager).getCaptcha()
527        self.captcha_result = self.captcha.verify(self.request)
528        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
529        return
530
531    def _redirect(self, email, password, customer_id):
532        # Forward only email address to landing page in base package.
533        self.redirect(self.url(self.context, 'requestpw_complete',
534            data=dict(email=email)))
535        return
536
537    def _pw_used(self):
538        # XXX: False if password has not been used. We need an extra
539        #      attribute which remembers if customer logged in.
540        return True
541
542    @action(_('Send login credentials to email address'), style='primary')
543    def get_credentials(self, **data):
544        if not self.captcha_result.is_valid:
545            # Captcha will display error messages automatically.
546            # No need to flash something.
547            return
548        number = data.get('number','')
549        firstname = data.get('firstname','')
550        cat = getUtility(ICatalog, name='customers_catalog')
551        results = list(
552            cat.searchResults(reg_number=(number, number)))
553        if results:
554            customer = results[0]
555            if getattr(customer,'firstname',None) is None:
556                self.flash(_('An error occurred.'), type="danger")
557                return
558            elif customer.firstname.lower() != firstname.lower():
559                # Don't tell the truth here. Anonymous must not
560                # know that a record was found and only the firstname
561                # verification failed.
562                self.flash(_('No customer found.'), type="warning")
563                return
564            elif customer.password is not None and self._pw_used:
565                self.flash(_('Your password has already been set and used. '
566                             'Please proceed to the login page.'),
567                           type="warning")
568                return
569            # Store email address but nothing else.
570            customer.email = data['email']
571            notify(grok.ObjectModifiedEvent(customer))
572        else:
573            # No record found, this is the truth.
574            self.flash(_('No customer found.'), type="warning")
575            return
576
577        ikoba_utils = getUtility(IIkobaUtils)
578        password = ikoba_utils.genPassword()
579        mandate = PasswordMandate()
580        mandate.params['password'] = password
581        mandate.params['user'] = customer
582        site = grok.getSite()
583        site['mandates'].addMandate(mandate)
584        # Send email with credentials
585        args = {'mandate_id':mandate.mandate_id}
586        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
587        url_info = u'Confirmation link: %s' % mandate_url
588        msg = _('You have successfully requested a password for the')
589        if ikoba_utils.sendCredentials(IUserAccount(customer),
590            password, url_info, msg):
591            email_sent = customer.email
592        else:
593            email_sent = None
594        self._redirect(email=email_sent, password=password,
595            customer_id=customer.customer_id)
596        ob_class = self.__implemented__.__name__.replace('waeup.ikoba.','')
597        self.context.logger.info(
598            '%s - %s (%s) - %s' % (ob_class, number, customer.customer_id, email_sent))
599        return
600
601
602class CustomerCreateAccountPage(IkobaAddFormPage):
603    """Captcha'd account creation page for customers.
604    """
605    grok.name('createaccount')
606    grok.require('waeup.Anonymous')
607    grok.template('createaccount')
608    form_fields = grok.AutoFields(ICustomerCreate)
609    label = _('Create customer account')
610
611    def update(self):
612        # Handle captcha
613        self.captcha = getUtility(ICaptchaManager).getCaptcha()
614        self.captcha_result = self.captcha.verify(self.request)
615        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
616        return
617
618    def _redirect(self, email, password, customer_id):
619        # Forward only email address to landing page in base package.
620        self.redirect(self.url(self.context, 'requestpw_complete',
621            data=dict(email=email)))
622        return
623
624    @action(_('Send login credentials to email address'), style='primary')
625    def create_account(self, **data):
626        if not self.captcha_result.is_valid:
627            # Captcha will display error messages automatically.
628            # No need to flash something.
629            return
630        customer = createObject(u'waeup.Customer')
631        customer.firstname = data.get('firstname','')
632        customer.middlename = data.get('middlename','')
633        customer.lastname = data.get('lastname','')
634        customer.email = data.get('email','')
635        self.context['customers'].addCustomer(customer)
636        ikoba_utils = getUtility(IIkobaUtils)
637        password = ikoba_utils.genPassword()
638        mandate = PasswordMandate()
639        mandate.params['password'] = password
640        mandate.params['user'] = customer
641        site = grok.getSite()
642        site['mandates'].addMandate(mandate)
643        # Send email with credentials
644        args = {'mandate_id':mandate.mandate_id}
645        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
646        url_info = u'Confirmation link: %s' % mandate_url
647        msg = _('You have successfully created a customer account for the')
648        if ikoba_utils.sendCredentials(IUserAccount(customer),
649            password, url_info, msg):
650            email_sent = customer.email
651        else:
652            email_sent = None
653        self._redirect(email=email_sent, password=password,
654            customer_id=customer.customer_id)
655        ob_class = self.__implemented__.__name__.replace('waeup.ikoba.','')
656        self.context.logger.info(
657            '%s - %s - %s' % (ob_class, customer.customer_id, email_sent))
658        return
659
660
661class CustomerRequestPasswordEmailSent(IkobaPage):
662    """Landing page after successful password request.
663
664    """
665    grok.name('requestpw_complete')
666    grok.require('waeup.Public')
667    grok.template('requestpwmailsent')
668    label = _('Your request was successful.')
669
670    def update(self, email=None, customer_id=None, password=None):
671        self.email = email
672        self.password = password
673        self.customer_id = customer_id
674        return
675
676# Pages for customers
677
678class CustomerFilesUploadPage(IkobaPage):
679    """ View to upload files by customer
680
681    We use this page only to upload a passport picture (portrait).
682    """
683    grok.context(ICustomer)
684    grok.name('upload_files')
685    grok.require('waeup.handleCustomer')
686    grok.template('filesuploadpage')
687    label = _('Upload files')
688    pnav = 4
689    deletion_warning = _('Are you sure?')
690
691    def update(self, CANCEL=None):
692        CUSTMANAGE_STATES = getUtility(
693            ICustomersUtils).CUSTMANAGE_CUSTOMER_STATES
694        if self.context.state not in CUSTMANAGE_STATES:
695            emit_lock_message(self)
696            return
697        if CANCEL is not None:
698            self.redirect(self.url(self.context))
699            return
700        super(CustomerFilesUploadPage, self).update()
701        return
702
703
704class CustomerBaseEditFormPage(IkobaEditFormPage):
705    """ View to edit customer base data
706    """
707    grok.context(ICustomer)
708    grok.name('edit_base')
709    grok.require('waeup.handleCustomer')
710    pnav = 4
711
712    def is_requestable(self, action=None):
713        if self.context.state == STARTED:
714            return True
715        return False
716
717    @property
718    def label(self):
719        if self.is_requestable():
720            return _('Edit base data and request registration')
721        return _('Edit base data')
722
723    @property
724    def form_fields(self):
725        if not self.is_requestable():
726            return grok.AutoFields(
727                self.context.form_fields_interface).select('email', 'phone')
728        return grok.AutoFields(self.context.form_fields_interface).omit(
729            'suspended', 'suspended_comment', 'reg_number', 'customer_id')
730
731    @action(_('Save'), style='primary')
732    def save(self, **data):
733        msave(self, **data)
734        return
735
736    def dataNotComplete(self):
737        store = getUtility(IExtFileStore)
738        error = ''
739        if not store.getFileByContext(self.context, attr=u'passport.jpg'):
740            error += _('Passport picture is missing.')
741        if error:
742            return error
743        return
744
745    @action(_('Save and request registration now'),
746            warning=WARNING_CUST, condition=is_requestable)
747    def finalsubmit(self, **data):
748        msave(self, **data)
749        if self.dataNotComplete():
750            self.flash(self.dataNotComplete(), type="warning")
751            self.redirect(self.url(self.context, 'upload_files'))
752            return
753        IWorkflowInfo(self.context).fireTransition('request')
754        self.flash(_('Registration form has been submitted.'))
755        self.redirect(self.url(self.context))
756        return
757
758    @action(_('Cancel'), validator=NullValidator)
759    def cancel(self, **data):
760        self.redirect(self.url(self.context))
761
762
763class CustomerChangePasswordPage(IkobaEditFormPage):
764    """ View to edit customer passords
765    """
766    grok.context(ICustomer)
767    grok.name('changepassword')
768    grok.require('waeup.handleCustomer')
769    grok.template('changepassword')
770    label = _('Change password')
771    pnav = 4
772
773    @action(_('Save'), style='primary')
774    def save(self, **data):
775        form = self.request.form
776        password = form.get('change_password', None)
777        password_ctl = form.get('change_password_repeat', None)
778        if password:
779            validator = getUtility(IPasswordValidator)
780            errors = validator.validate_password(password, password_ctl)
781            if not errors:
782                IUserAccount(self.context).setPassword(password)
783                self.context.writeLogMessage(self, 'saved: password')
784                self.flash(_('Password changed.'))
785            else:
786                self.flash(' '.join(errors), type="warning")
787        return
788
789    @action(_('Cancel'), validator=NullValidator)
790    def cancel(self, **data):
791        self.redirect(self.url(self.context))
792
793
794class CustomerBasePDFFormPage(IkobaDisplayFormPage):
795    """ Page to display customer base data in pdf files.
796    """
797
798    def __init__(self, context, request, omit_fields=()):
799        self.omit_fields = omit_fields
800        super(CustomerBasePDFFormPage, self).__init__(context, request)
801
802    @property
803    def form_fields(self):
804        form_fields = grok.AutoFields(self.context.form_fields_interface)
805        for field in self.omit_fields:
806            form_fields = form_fields.omit(field)
807        return form_fields
808
809# Pages for customer documents
810
811class DocumentsBreadcrumb(Breadcrumb):
812    """A breadcrumb for the documents container.
813    """
814    grok.context(ICustomerDocumentsContainer)
815    title = _('Documents')
816
817
818class DocumentBreadcrumb(Breadcrumb):
819    """A breadcrumb for the customer container.
820    """
821    grok.context(ICustomerDocument)
822
823    @property
824    def title(self):
825        return "%s..." % self.context.document_id[:9]
826
827
828class DocumentsManageFormPage(IkobaEditFormPage):
829    """ Page to manage the customer documents
830
831    This manage form page is for both customers and customers officers.
832    """
833    grok.context(ICustomerDocumentsContainer)
834    grok.name('index')
835    grok.require('waeup.viewCustomer')
836    form_fields = grok.AutoFields(ICustomerDocumentsContainer)
837    grok.template('documentsmanagepage')
838    pnav = 4
839
840    @property
841    def manage_documents_allowed(self):
842        return checkPermission('waeup.editCustomerDocuments', self.context)
843
844    def unremovable(self, document):
845        usertype = getattr(self.request.principal, 'user_type', None)
846        if not usertype:
847            return False
848        if not self.manage_documents_allowed:
849            return True
850        return (self.request.principal.user_type == 'customer' and \
851            document.state in (VERIFIED, REJECTED, EXPIRED))
852
853    @property
854    def label(self):
855        return _('${a}: Documents',
856            mapping = {'a':self.context.__parent__.display_fullname})
857
858    @action(_('Add document'), validator=NullValidator, style='primary')
859    def addDocument(self, **data):
860        self.redirect(self.url(self.context, 'adddoc'))
861        return
862
863    @jsaction(_('Remove selected documents'))
864    def delDocument(self, **data):
865        form = self.request.form
866        if 'val_id' in form:
867            child_id = form['val_id']
868        else:
869            self.flash(_('No document selected.'), type="warning")
870            self.redirect(self.url(self.context))
871            return
872        if not isinstance(child_id, list):
873            child_id = [child_id]
874        deleted = []
875        for id in child_id:
876            # Customers are not allowed to remove used documents
877            document = self.context.get(id, None)
878            if document is not None and not self.unremovable(document):
879                del self.context[id]
880                deleted.append(id)
881        if len(deleted):
882            self.flash(_('Successfully removed: ${a}',
883                mapping = {'a': ', '.join(deleted)}))
884            self.context.writeLogMessage(
885                self,'removed: %s' % ', '.join(deleted))
886        self.redirect(self.url(self.context))
887        return
888
889
890class DocumentAddFormPage(IkobaAddFormPage):
891    """ Page to add a document
892
893    This add form page is for both customers and customers officers.
894    """
895    grok.context(ICustomerDocumentsContainer)
896    grok.name('adddoc')
897    grok.template('documentaddpage')
898    grok.require('waeup.editCustomerDocuments')
899    label = _('Add document')
900    pnav = 4
901
902    form_fields = grok.AutoFields(ICustomerDocument).omit('document_id')
903
904    @property
905    def selectable_doctypes(self):
906        doctypes = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT
907        return sorted(doctypes.items())
908
909    @property
910    def edit_documents_allowed(self):
911        right_customer_state = self.context.customer.state in getUtility(
912            ICustomersUtils).DOCMANAGE_CUSTOMER_STATES
913        if isCustomer(self.request.principal) and not right_customer_state:
914            return False
915        return True
916
917    def update(self):
918        if not self.edit_documents_allowed:
919            emit_lock_message(self)
920            return
921        return super(DocumentAddFormPage, self).update()
922
923    @action(_('Add document'), style='primary')
924    def createDocument(self, **data):
925        form = self.request.form
926        doctype = form.get('doctype', None)
927        # Here we can create various instances of CustomerDocument derived
928        # classes depending on the doctype parameter given in form.
929        document = createObject('waeup.%s' % doctype)
930        self.applyData(document, **data)
931        self.context.addDocument(document)
932        doctype = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT[doctype]
933        self.flash(_('${a} added.', mapping = {'a': doctype}))
934        self.context.writeLogMessage(
935            self,'added: %s %s' % (doctype, document.document_id))
936        isCustomer = getattr(
937            self.request.principal, 'user_type', None) == 'customer'
938        if isCustomer:
939            self.redirect(self.url(document, 'edit') + '#tab2')
940        else:
941            self.redirect(self.url(document, 'manage') + '#tab2')
942        return
943
944    @action(_('Cancel'), validator=NullValidator)
945    def cancel(self, **data):
946        self.redirect(self.url(self.context))
947
948
949class DocumentDisplayFormPage(IkobaDisplayFormPage):
950    """ Page to view a document
951    """
952    grok.context(ICustomerDocument)
953    grok.name('index')
954    grok.require('waeup.viewCustomer')
955    grok.template('documentpage')
956    pnav = 4
957    label = None  # We render the context title in the documentpage template
958
959    @property
960    def form_fields(self):
961        return grok.AutoFields(self.context.form_fields_interface)
962
963class DocumentManageFormPage(IkobaEditFormPage):
964    """ Page to edit a document
965    """
966    grok.context(ICustomerDocument)
967    grok.name('manage')
968    grok.require('waeup.manageCustomer')
969    grok.template('documenteditpage')
970    pnav = 4
971    deletion_warning = _('Are you sure?')
972
973    @property
974    def form_fields(self):
975        return grok.AutoFields(
976            self.context.form_fields_interface).omit('document_id')
977
978    def update(self):
979        if not self.context.is_editable_by_manager:
980            emit_lock_message(self)
981            return
982        return super(DocumentManageFormPage, self).update()
983
984    @property
985    def label(self):
986        return self.context.title
987
988    @action(_('Save'), style='primary')
989    def save(self, **data):
990        msave(self, **data)
991        return
992
993
994class DocumentEditFormPage(DocumentManageFormPage):
995    """ Page to edit a document
996    """
997    grok.name('edit')
998    grok.require('waeup.handleCustomer')
999
1000    def update(self):
1001        if not self.context.is_editable_by_customer:
1002            emit_lock_message(self)
1003            return
1004        return super(DocumentEditFormPage, self).update()
1005
1006    @action(_('Save'), style='primary')
1007    def save(self, **data):
1008        msave(self, **data)
1009        return
1010
1011    @action(_('Final Submit'), warning=WARNING_DOC)
1012    def finalsubmit(self, **data):
1013        msave(self, **data)
1014        IWorkflowInfo(self.context).fireTransition('submit')
1015        self.flash(_('Form has been submitted.'))
1016        self.redirect(self.url(self.context))
1017        return
1018
1019
1020class DocumentTriggerTransitionFormPage(IkobaEditFormPage):
1021    """ View to trigger customer document transitions
1022    """
1023    grok.context(ICustomerDocument)
1024    grok.name('trigtrans')
1025    grok.require('waeup.triggerTransition')
1026    grok.template('trigtrans')
1027    label = _('Trigger document transition')
1028    pnav = 4
1029
1030    def update(self):
1031        return super(IkobaEditFormPage, self).update()
1032
1033    def getTransitions(self):
1034        """Return a list of dicts of allowed transition ids and titles.
1035
1036        Each list entry provides keys ``name`` and ``title`` for
1037        internal name and (human readable) title of a single
1038        transition.
1039        """
1040        wf_info = IWorkflowInfo(self.context)
1041        allowed_transitions = [t for t in wf_info.getManualTransitions()]
1042        return [dict(name='', title=_('No transition'))] +[
1043            dict(name=x, title=y) for x, y in allowed_transitions]
1044
1045    @action(_('Apply'), style='primary')
1046    def apply(self, **data):
1047        form = self.request.form
1048        if 'transition' in form and form['transition']:
1049            transition_id = form['transition']
1050            wf_info = IWorkflowInfo(self.context)
1051            try:
1052                wf_info.fireTransition(transition_id)
1053                self.flash(_("Transition '%s' executed." % transition_id))
1054            except InvalidTransitionError, error:
1055                self.flash(error, type="warning")
1056            self.redirect(self.url(self.context))
1057        return
1058
1059class PDFDocumentsOverviewPage(UtilityView, grok.View):
1060    """Deliver an overview slip.
1061    """
1062    grok.context(ICustomerDocumentsContainer)
1063    grok.name('documents_overview_slip.pdf')
1064    grok.require('waeup.viewCustomer')
1065    prefix = 'form'
1066
1067    omit_fields = ('suspended', 'sex',
1068                   'suspended_comment',)
1069
1070    form_fields = None
1071
1072    @property
1073    def label(self):
1074        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1075        return translate(_('Documents of'),
1076            'waeup.ikoba', target_language=portal_language) \
1077            + ' %s' % self.context.customer.display_fullname
1078
1079    @property
1080    def tabletitle(self):
1081        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1082        tabletitle = []
1083        tabletitle.append(translate(_('Customer Documents'), 'waeup.ikoba',
1084            target_language=portal_language))
1085        return tabletitle
1086
1087    def render(self):
1088        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1089        Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
1090        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
1091        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
1092        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
1093        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
1094        tableheader = []
1095        tabledata = []
1096        for i in range(1,3):
1097            tabledata.append(sorted(
1098                [value for value in self.context.values()]))
1099            tableheader.append([(Id, 'document_id', 2),
1100                             (Title, 'title', 6),
1101                             (Type, 'translated_class_name', 6),
1102                             (State, 'translated_state', 2),
1103                             (LT, 'formatted_transition_date', 3),
1104                             ])
1105        customerview = CustomerBasePDFFormPage(self.context.customer,
1106            self.request, self.omit_fields)
1107        customers_utils = getUtility(ICustomersUtils)
1108        return customers_utils.renderPDF(
1109            self, 'overview_slip.pdf',
1110            self.context.customer, customerview,
1111            tableheader=tableheader,
1112            tabledata=tabledata,
1113            omit_fields=self.omit_fields)
1114
1115
1116class PDFDocumentSlipPage(UtilityView, grok.View):
1117    """Deliver pdf file including metadata.
1118    """
1119    grok.context(ICustomerDocument)
1120    grok.name('document_slip.pdf')
1121    grok.require('waeup.viewCustomer')
1122    prefix = 'form'
1123
1124    omit_fields = ('suspended', 'sex',
1125                   'suspended_comment',)
1126
1127    form_fields =()
1128
1129    @property
1130    def label(self):
1131        return '%s of %s\nTitle: %s' % (
1132            self.context.translated_class_name,
1133            self.context.customer.display_fullname,
1134            self.context.title)
1135
1136    def render(self):
1137        customerview = CustomerBasePDFFormPage(self.context.customer,
1138            self.request, self.omit_fields)
1139        customers_utils = getUtility(ICustomersUtils)
1140        return customers_utils.renderPDF(
1141            self, 'document_slip.pdf',
1142            self.context.customer, customerview,
1143            omit_fields=self.omit_fields)
1144
1145
1146class PDFMergeDocumentSlipPage(PDFDocumentSlipPage):
1147    """Deliver pdf file including metadata.
1148    """
1149    grok.context(ICustomerPDFDocument)
1150
1151    def render(self):
1152        customerview = CustomerBasePDFFormPage(self.context.customer,
1153            self.request, self.omit_fields)
1154        customers_utils = getUtility(ICustomersUtils)
1155        if self.context.state == VERIFIED:
1156            watermark_path = os.path.join(
1157                os.path.dirname(__file__), 'static', 'verified.pdf')
1158        else:
1159            watermark_path = os.path.join(
1160                os.path.dirname(__file__), 'static', 'unverified.pdf')
1161        watermark = open(watermark_path, 'rb')
1162        return customers_utils.renderPDF(
1163            self, 'pdfdocument_slip.pdf',
1164            self.context.customer, customerview,
1165            omit_fields=self.omit_fields,
1166            mergefiles=self.context.connected_files,
1167            watermark=watermark)
1168
1169# Pages for customer contracts
1170
1171class ContractsBreadcrumb(Breadcrumb):
1172    """A breadcrumb for the contracts container.
1173    """
1174    grok.context(IContractsContainer)
1175    title = _('Contracts')
1176
1177
1178class ContractBreadcrumb(Breadcrumb):
1179    """A breadcrumb for the customer container.
1180    """
1181    grok.context(IContract)
1182
1183    @property
1184    def title(self):
1185        return "%s..." % self.context.contract_id[:9]
1186
1187
1188class ContractsFormPage(IkobaEditFormPage):
1189    """ Page to display, edit or manage customer contracts
1190
1191    This form page is for both customers and officers.
1192    """
1193    grok.context(IContractsContainer)
1194    grok.name('index')
1195    grok.require('waeup.viewCustomer')
1196    form_fields = grok.AutoFields(IContractsContainer)
1197    grok.template('contractsmanagepage')
1198    pnav = 4
1199
1200    @property
1201    def edit_contracts_allowed(self):
1202        right_customer_state = self.context.customer.state in getUtility(
1203            ICustomersUtils).CONMANAGE_CUSTOMER_STATES
1204        if isCustomer(self.request.principal) and not right_customer_state:
1205            return False
1206        return checkPermission('waeup.editContracts', self.context)
1207
1208    def remove_contract_allowed(self, contract):
1209        if isCustomer(self.request.principal):
1210            right_customer_state = self.context.customer.state in getUtility(
1211                ICustomersUtils).CONMANAGE_CUSTOMER_STATES
1212            if not right_customer_state:
1213                return False
1214            if contract.state in (APPROVED, REJECTED, EXPIRED):
1215                return False
1216        return checkPermission('waeup.editContracts', self.context)
1217
1218    @property
1219    def label(self):
1220        return _('${a}: Contracts',
1221            mapping = {'a':self.context.__parent__.display_fullname})
1222
1223    @action(_('Add contract'), validator=NullValidator, style='primary')
1224    def addContract(self, **data):
1225        self.redirect(self.url(self.context, 'addcontract'))
1226        return
1227
1228    @jsaction(_('Remove selected contracts'))
1229    def delContract(self, **data):
1230        form = self.request.form
1231        if 'val_id' in form:
1232            child_id = form['val_id']
1233        else:
1234            self.flash(_('No contract selected.'), type="warning")
1235            self.redirect(self.url(self.context))
1236            return
1237        if not isinstance(child_id, list):
1238            child_id = [child_id]
1239        deleted = []
1240        for id in child_id:
1241            # Customers are not allowed to remove used contracts
1242            contract = self.context.get(id, None)
1243            if contract is not None and self.remove_contract_allowed(contract):
1244                del self.context[id]
1245                deleted.append(id)
1246        if len(deleted):
1247            self.flash(_('Successfully removed: ${a}',
1248                mapping = {'a': ', '.join(deleted)}))
1249            self.context.writeLogMessage(
1250                self,'removed: %s' % ', '.join(deleted))
1251        self.redirect(self.url(self.context))
1252        return
1253
1254
1255class ContractAddFormPage(IkobaAddFormPage):
1256    """ Page to add an contract
1257
1258    This page is for both customers and officers.
1259    """
1260    grok.context(IContractsContainer)
1261    grok.name('addcontract')
1262    grok.template('contractaddpage')
1263    grok.require('waeup.editContracts')
1264    label = _('Add contract')
1265    pnav = 4
1266
1267    @property
1268    def edit_contracts_allowed(self):
1269        right_customer_state = self.context.customer.state in getUtility(
1270            ICustomersUtils).CONMANAGE_CUSTOMER_STATES
1271        if right_customer_state:
1272            return True
1273        return False
1274
1275    def update(self):
1276        if not self.edit_contracts_allowed:
1277            emit_lock_message(self)
1278            return
1279        return super(ContractAddFormPage, self).update()
1280
1281    @property
1282    def selectable_contypes(self):
1283        contypes = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT
1284        return sorted(contypes.items())
1285
1286    @action(_('Add contract'), style='primary')
1287    def createContract(self, **data):
1288        form = self.request.form
1289        contype = form.get('contype', None)
1290        # Here we can create various instances of Contract derived
1291        # classes depending on the contype parameter given in form.
1292        contract = createObject('waeup.%s' % contype)
1293        self.context.addContract(contract)
1294        contype = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT[contype]
1295        self.flash(_('${a} added.', mapping = {'a': contype}))
1296        self.context.writeLogMessage(
1297            self,'added: %s %s' % (contype, contract.contract_id))
1298        self.redirect(self.url(contract, 'selectproduct'))
1299        return
1300
1301    @action(_('Cancel'), validator=NullValidator)
1302    def cancel(self, **data):
1303        self.redirect(self.url(self.context))
1304
1305
1306class ContractSelectProductPage(IkobaAddFormPage):
1307    """ Page to select a contract product
1308
1309    This page is for both customers and officers.
1310    """
1311    grok.context(IContract)
1312    grok.name('selectproduct')
1313    grok.require('waeup.editContracts')
1314    label = _('Select product')
1315    pnav = 4
1316
1317    form_fields = grok.AutoFields(IContractSelectProduct)
1318
1319    def update(self):
1320        if self.context.product_object is not None:
1321            emit_lock_message(self)
1322            return
1323        return super(ContractSelectProductPage, self).update()
1324
1325    @action(_('Save and proceed'), style='primary')
1326    def save(self, **data):
1327        msave(self, **data)
1328        self.context.title = self.context.product_object.contract_autotitle
1329        self.context.tc_dict = self.context.product_object.tc_dict
1330        self.context.valid_from = self.context.product_object.valid_from
1331        self.context.valid_to = self.context.product_object.valid_to
1332        isCustomer = getattr(
1333            self.request.principal, 'user_type', None) == 'customer'
1334        if isCustomer:
1335            self.redirect(self.url(self.context, 'edit'))
1336        else:
1337            self.redirect(self.url(self.context, 'manage'))
1338        return
1339
1340
1341class ContractDisplayFormPage(IkobaDisplayFormPage):
1342    """ Page to view a contract
1343    """
1344    grok.context(IContract)
1345    grok.name('index')
1346    grok.require('waeup.viewCustomer')
1347    grok.template('contractpage')
1348    pnav = 4
1349    label = None  # We render the context title in the contractpage template
1350
1351    @property
1352    def form_fields(self):
1353        form_fields = grok.AutoFields(self.context.form_fields_interface)
1354        for field in form_fields:
1355            if field.__name__.endswith('_object'):
1356                form_fields[field.__name__].custom_widget = HREFDisplayWidget
1357        return form_fields
1358
1359    @property
1360    def terms_and_conditions(self):
1361        lang = self.request.cookies.get('ikoba.language')
1362        html = self.context.tc_dict.get(lang,'')
1363        if html =='':
1364            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1365            html = self.context.tc_dict.get(portal_language,'')
1366        return html
1367
1368
1369class ContractManageFormPage(IkobaEditFormPage):
1370    """ Page to manage a contract
1371    """
1372    grok.context(IContract)
1373    grok.name('manage')
1374    grok.require('waeup.manageCustomer')
1375    grok.template('contracteditpage')
1376    pnav = 4
1377    deletion_warning = _('Are you sure?')
1378
1379    def update(self):
1380        if not self.context.is_editable:
1381            emit_lock_message(self)
1382            return
1383        return super(ContractManageFormPage, self).update()
1384
1385    @property
1386    def terms_and_conditions(self):
1387        lang = self.request.cookies.get('ikoba.language')
1388        html = self.context.tc_dict.get(lang,'')
1389        if html =='':
1390            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1391            html = self.context.tc_dict.get(portal_language,'')
1392        return html
1393
1394    @property
1395    def form_fields(self):
1396        return grok.AutoFields(self.context.form_fields_interface).omit(
1397            'contract_id', 'product_object')
1398
1399    @property
1400    def label(self):
1401        return self.context.title
1402
1403    @action(_('Save'), style='primary')
1404    def save(self, **data):
1405        msave(self, **data)
1406        return
1407
1408
1409class ContractOfficialUsePage(IkobaEditFormPage):
1410    """ Page to manage a official use data of contract
1411    """
1412    grok.context(IContract)
1413    grok.name('official_use')
1414    grok.require('waeup.manageCustomer')
1415    grok.template('contracteditpage')
1416    pnav = 4
1417    deletion_warning = _('Are you sure?')
1418    terms_and_conditions = None
1419
1420    @property
1421    def form_fields(self):
1422        return grok.AutoFields(self.context.ou_form_fields_interface)
1423
1424    @property
1425    def label(self):
1426        return self.context.title
1427
1428    @action(_('Save'), style='primary')
1429    def save(self, **data):
1430        msave(self, **data)
1431        return
1432
1433
1434class ContractEditFormPage(ContractManageFormPage):
1435    """ Page to edit a contract by customer only
1436    """
1437    grok.name('edit')
1438    grok.require('waeup.handleCustomer')
1439
1440    def submission_allowed(self, action=None):
1441        if self.context.state == CREATED and not self.context.fee_based:
1442            return True
1443        return False
1444
1445    def payment_expected(self, action=None):
1446        if self.context.state == CREATED and self.context.fee_based:
1447            return True
1448        return False
1449
1450    @property
1451    def form_fields(self):
1452        return grok.AutoFields(self.context.edit_form_fields_interface).omit(
1453            'contract_id', 'product_object')
1454
1455    @action(_('Save'), style='primary')
1456    def save(self, **data):
1457        msave(self, **data)
1458        return
1459
1460    @action(_('Apply now (final submission)'), warning=WARNING_CON,
1461            condition=submission_allowed, style='primary')
1462    def submit(self, **data):
1463        if self.terms_and_conditions and not self.request.form.get(
1464            'confirm_tc', False):
1465            self.flash(_('Please read the terms and conditions and '
1466                     'confirm your acceptance of these by ticking '
1467                     'the confirmation box.'), type="danger")
1468            return
1469        msave(self, **data)
1470        IWorkflowInfo(self.context).fireTransition('submit')
1471        self.flash(_('Application form has been submitted.'))
1472        self.redirect(self.url(self.context))
1473        return
1474
1475    @action(_('Proceed to checkout'),
1476            condition=payment_expected, style='primary')
1477    def proceed_checkout(self, **data):
1478        if self.terms_and_conditions and not self.request.form.get(
1479            'confirm_tc', False):
1480            self.flash(_('Please read the terms and conditions and '
1481                     'confirm your acceptance of these by ticking '
1482                     'the confirmation box.'), type="danger")
1483            return
1484        msave(self, **data)
1485        self.redirect(self.url(self.context, 'select_payment_method'))
1486        return
1487
1488
1489class SelectPaymentMethodPage(IkobaEditFormPage):
1490    """ Page to to select payment method
1491    """
1492    grok.context(IContract)
1493    grok.name('select_payment_method')
1494    grok.require('waeup.handleCustomer')
1495    grok.template('selectpaymentmethodpage')
1496    pnav = 4
1497    label = _('Select payment method')
1498
1499    @property
1500    def payment_gateways(self):
1501        """Get an iter over registered and enabled gateway service providers.
1502
1503        We provide tuples ``(value, description)`` for each supported
1504        payment gateway.
1505        """
1506        lister = getUtility(IPaymentGatewayServicesLister)
1507        for name, service in lister().items():
1508            yield {'name': name, 'title': service.title}
1509
1510    def update(self, CANCEL=None, gw=None):
1511        if self.context.state != CREATED or not self.context.fee_based:
1512            emit_lock_message(self)
1513            return
1514        self.gw = gw
1515        super(SelectPaymentMethodPage, self).update()
1516        return
1517
1518    @action(_('Select payment method and proceed to payment gateway'
1519              ' (final submission)'),
1520            style='primary', warning=WARNING_CON,)
1521    def confirm(self, **data):
1522        if self.gw is None:
1523            self.flash(_('Please pick a payment method.'),
1524                       type='warning')
1525        else:
1526
1527            service = queryUtility(IPaymentGatewayService, name=self.gw)
1528            if service is None:
1529                self.flash(_('Invalid payment gateway.'), type='danger')
1530                return
1531            payer = IPayer(self.context)
1532            payment_items = payment_items_from_contract(self.context)
1533            payment = service.create_payment(payer, payment_items)
1534            payment, view_name = service.next_step(payment.payment_id)
1535            url = self.url(payment, view_name)
1536            self.flash(_('Payment has been initiated.'))
1537            IWorkflowInfo(self.context).fireTransition('await')
1538            self.redirect(url)
1539            return
1540
1541        return
1542
1543class ContractTriggerTransitionFormPage(IkobaEditFormPage):
1544    """ View to trigger customer contract transitions
1545    """
1546    grok.context(IContract)
1547    grok.name('trigtrans')
1548    grok.require('waeup.triggerTransition')
1549    grok.template('trigtrans')
1550    label = _('Trigger contract transition')
1551    pnav = 4
1552
1553    def update(self):
1554        return super(IkobaEditFormPage, self).update()
1555
1556    def getTransitions(self):
1557        """Return a list of dicts of allowed transition ids and titles.
1558
1559        Each list entry provides keys ``name`` and ``title`` for
1560        internal name and (human readable) title of a single
1561        transition.
1562        """
1563        wf_info = IWorkflowInfo(self.context)
1564        allowed_transitions = [t for t in wf_info.getManualTransitions()]
1565        # Skip payment transitions if total amount is zero
1566        if not self.context.fee_based:
1567            allowed_transitions = [t for t in allowed_transitions
1568                                   if not t[0] in PAYMENT_TRANSITIONS]
1569        return [dict(name='', title=_('No transition'))] +[
1570            dict(name=x, title=y) for x, y in allowed_transitions]
1571
1572    @action(_('Apply'), style='primary')
1573    def apply(self, **data):
1574        form = self.request.form
1575        if 'transition' in form and form['transition']:
1576            transition_id = form['transition']
1577            wf_info = IWorkflowInfo(self.context)
1578            try:
1579                wf_info.fireTransition(transition_id)
1580                self.flash(_("Transition '%s' executed." % transition_id))
1581            except InvalidTransitionError, error:
1582                self.flash(error, type="warning")
1583            self.redirect(self.url(self.context))
1584        return
1585
1586class PDFContractsOverviewPage(UtilityView, grok.View):
1587    """Deliver an overview slip.
1588    """
1589    grok.context(IContractsContainer)
1590    grok.name('contracts_overview_slip.pdf')
1591    grok.require('waeup.viewCustomer')
1592    prefix = 'form'
1593
1594    omit_fields = ('suspended', 'sex',
1595                   'suspended_comment',)
1596
1597    form_fields = None
1598
1599    @property
1600    def label(self):
1601        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1602        return translate(_('Contracts of'),
1603            'waeup.ikoba', target_language=portal_language) \
1604            + ' %s' % self.context.customer.display_fullname
1605
1606    @property
1607    def tabletitle(self):
1608        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1609        tabletitle = []
1610        tabletitle.append(translate(_('Customer Contracts'), 'waeup.ikoba',
1611            target_language=portal_language))
1612        return tabletitle
1613
1614    def render(self):
1615        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1616        #Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
1617        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
1618        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
1619        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
1620        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
1621        tableheader = []
1622        tabledata = []
1623        for i in range(1,3):
1624            tabledata.append(sorted(
1625                [value for value in self.context.values()]))
1626            tableheader.append([
1627                             #(Id, 'contract_id', 2),
1628                             (Title, 'title', 6),
1629                             (Type, 'translated_class_name', 6),
1630                             (State, 'translated_state', 2),
1631                             (LT, 'formatted_transition_date', 3),
1632                             ])
1633        customerview = CustomerBasePDFFormPage(self.context.customer,
1634            self.request, self.omit_fields)
1635        customers_utils = getUtility(ICustomersUtils)
1636        return customers_utils.renderPDF(
1637            self, 'overview_slip.pdf',
1638            self.context.customer, customerview,
1639            tableheader=tableheader,
1640            tabledata=tabledata,
1641            omit_fields=self.omit_fields)
1642
1643
1644class PDFContractSlipPage(UtilityView, grok.View):
1645    """Deliver pdf file including metadata.
1646    """
1647    grok.context(IContract)
1648    grok.name('contract_slip.pdf')
1649    grok.require('waeup.viewCustomer')
1650    prefix = 'form'
1651
1652    omit_fields = ('suspended', 'sex',
1653                   'suspended_comment',)
1654
1655    @property
1656    def form_fields(self):
1657        return grok.AutoFields(self.context.form_fields_interface)
1658
1659    @property
1660    def terms_and_conditions(self):
1661        lang = self.request.cookies.get('ikoba.language')
1662        html = self.context.tc_dict.get(lang,'')
1663        if html =='':
1664            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1665            html = self.context.tc_dict.get(portal_language,'')
1666        return html
1667
1668    @property
1669    def validity_period(self):
1670        if self.context.valid_from or self.context.valid_to:
1671            return "%s - %s" % (format_date(self.context.valid_from, self.request),
1672                                format_date(self.context.valid_to, self.request))
1673        return
1674
1675    @property
1676    def label(self):
1677        return self.context.title
1678
1679    # XXX: not used in waeup.ikoba and thus not tested
1680    def _signatures(self):
1681        return ([_('Customer Signature')],
1682                [_('Company Officer Signature')]
1683                )
1684
1685    def _sigsInFooter(self):
1686        return (_('Date, Customer Signature'),
1687                _('Date, Company Officer Signature'),
1688                )
1689
1690    def render(self):
1691        customerview = CustomerBasePDFFormPage(self.context.customer,
1692            self.request, self.omit_fields)
1693        customers_utils = getUtility(ICustomersUtils)
1694        return customers_utils.renderPDF(
1695            self, 'contract_slip.pdf',
1696            self.context.customer, customerview,
1697            signatures=self._signatures(),
1698            sigs_in_footer=self._sigsInFooter(),
1699            omit_fields=self.omit_fields)
Note: See TracBrowser for help on using the repository browser.