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

Last change on this file since 12768 was 12762, checked in by Henrik Bettermann, 10 years ago

Add PaymentsContainerPage? to search for and list payments (work in progress, completely untested).

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