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

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

Remove trash.

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