source: main/waeup.ikoba/trunk/src/waeup/ikoba/customers/browser.py @ 12741

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

Merge changes from uli-payments back into trunk.

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