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

Last change on this file since 15146 was 14215, checked in by Henrik Bettermann, 8 years ago

Contracts must not be added if there are no active products for this category.

  • Property svn:keywords set to Id
File size: 61.3 KB
Line 
1## $Id: browser.py 14215 2016-10-19 09:48:38Z 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 CustomerActivateView(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 CustomerDeactivateView(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            style='success')
743    def finalsubmit(self, **data):
744        msave(self, **data)
745        if self.dataNotComplete():
746            self.flash(self.dataNotComplete(), type="warning")
747            self.redirect(self.url(self.context, 'upload_files'))
748            return
749        IWorkflowInfo(self.context).fireTransition('request')
750        self.flash(_('Registration form has been submitted.'))
751        self.redirect(self.url(self.context))
752        return
753
754    @action(_('Cancel'), validator=NullValidator)
755    def cancel(self, **data):
756        self.redirect(self.url(self.context))
757
758
759class CustomerChangePasswordPage(IkobaEditFormPage):
760    """ View to edit customer passords
761    """
762    grok.context(ICustomer)
763    grok.name('changepassword')
764    grok.require('waeup.handleCustomer')
765    grok.template('changepassword')
766    label = _('Change password')
767    pnav = 4
768
769    @action(_('Save'), style='primary')
770    def save(self, **data):
771        form = self.request.form
772        password = form.get('change_password', None)
773        password_ctl = form.get('change_password_repeat', None)
774        if password:
775            validator = getUtility(IPasswordValidator)
776            errors = validator.validate_password(password, password_ctl)
777            if not errors:
778                IUserAccount(self.context).setPassword(password)
779                # Unset temporary password
780                self.context.temp_password = None
781                self.context.writeLogMessage(self, 'saved: password')
782                self.flash(_('Password changed.'))
783            else:
784                self.flash(' '.join(errors), type="warning")
785        return
786
787    @action(_('Cancel'), validator=NullValidator)
788    def cancel(self, **data):
789        self.redirect(self.url(self.context))
790
791
792class CustomerBasePDFFormPage(IkobaDisplayFormPage):
793    """ Page to display customer base data in pdf files.
794    """
795
796    def __init__(self, context, request, omit_fields=()):
797        self.omit_fields = omit_fields
798        super(CustomerBasePDFFormPage, self).__init__(context, request)
799
800    @property
801    def form_fields(self):
802        form_fields = grok.AutoFields(self.context.form_fields_interface)
803        for field in self.omit_fields:
804            form_fields = form_fields.omit(field)
805        return form_fields
806
807# Pages for customer documents
808
809class DocumentsBreadcrumb(Breadcrumb):
810    """A breadcrumb for the documents container.
811    """
812    grok.context(ICustomerDocumentsContainer)
813    title = _('Documents')
814
815
816class DocumentBreadcrumb(Breadcrumb):
817    """A breadcrumb for the customer container.
818    """
819    grok.context(ICustomerDocument)
820
821    @property
822    def title(self):
823        return "%s" % self.context.document_id[:9]
824
825
826class DocumentsManageFormPage(IkobaEditFormPage):
827    """ Page to manage the customer documents
828
829    This manage form page is for both customers and customers officers.
830    """
831    grok.context(ICustomerDocumentsContainer)
832    grok.name('index')
833    grok.require('waeup.viewCustomer')
834    form_fields = grok.AutoFields(ICustomerDocumentsContainer)
835    grok.template('documentsmanagepage')
836    pnav = 4
837
838    @property
839    def manage_documents_allowed(self):
840        return checkPermission('waeup.editCustomerDocuments', self.context)
841
842    def unremovable(self, document):
843        usertype = getattr(self.request.principal, 'user_type', None)
844        if not usertype:
845            return False
846        if not self.manage_documents_allowed:
847            return True
848        return (self.request.principal.user_type == 'customer' and \
849            document.state in (VERIFIED, REJECTED, EXPIRED))
850
851    @property
852    def label(self):
853        return _('${a}: Documents',
854            mapping = {'a':self.context.__parent__.display_fullname})
855
856    @action(_('Add document'), validator=NullValidator, style='primary')
857    def addDocument(self, **data):
858        self.redirect(self.url(self.context, 'adddoc'))
859        return
860
861    @jsaction(_('Remove selected documents'))
862    def delDocument(self, **data):
863        form = self.request.form
864        if 'val_id' in form:
865            child_id = form['val_id']
866        else:
867            self.flash(_('No document selected.'), type="warning")
868            self.redirect(self.url(self.context))
869            return
870        if not isinstance(child_id, list):
871            child_id = [child_id]
872        deleted = []
873        for id in child_id:
874            # Customers are not allowed to remove used documents
875            document = self.context.get(id, None)
876            if document is not None and not self.unremovable(document):
877                del self.context[id]
878                deleted.append(id)
879        if len(deleted):
880            self.flash(_('Successfully removed: ${a}',
881                mapping = {'a': ', '.join(deleted)}))
882            self.context.writeLogMessage(
883                self,'removed: %s' % ', '.join(deleted))
884        self.redirect(self.url(self.context))
885        return
886
887
888class DocumentAddFormPage(IkobaAddFormPage):
889    """ Page to add a document
890
891    This add form page is for both customers and customers officers.
892    """
893    grok.context(ICustomerDocumentsContainer)
894    grok.name('adddoc')
895    grok.template('documentaddpage')
896    grok.require('waeup.editCustomerDocuments')
897    label = _('Add document')
898    pnav = 4
899
900    form_fields = grok.AutoFields(ICustomerDocument).omit('document_id')
901
902    @property
903    def selectable_doctypes(self):
904        doctypes = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT
905        return sorted(doctypes.items())
906
907    @property
908    def edit_documents_allowed(self):
909        right_customer_state = self.context.customer.state in getUtility(
910            ICustomersUtils).DOCMANAGE_CUSTOMER_STATES
911        if isCustomer(self.request.principal) and not right_customer_state:
912            return False
913        return True
914
915    def update(self):
916        if not self.edit_documents_allowed:
917            emit_lock_message(self)
918            return
919        return super(DocumentAddFormPage, self).update()
920
921    @action(_('Add document'), style='primary')
922    def createDocument(self, **data):
923        form = self.request.form
924        doctype = form.get('doctype', None)
925        # Here we can create various instances of CustomerDocument derived
926        # classes depending on the doctype parameter given in form.
927        document = createObject('waeup.%s' % doctype)
928        self.applyData(document, **data)
929        self.context.addDocument(document)
930        doctype = getUtility(ICustomersUtils).SELECTABLE_DOCTYPES_DICT[doctype]
931        self.flash(_('${a} added.', mapping = {'a': doctype}))
932        self.context.writeLogMessage(
933            self,'added: %s %s' % (doctype, document.document_id))
934        isCustomer = getattr(
935            self.request.principal, 'user_type', None) == 'customer'
936        if isCustomer:
937            self.redirect(self.url(document, 'edit') + '#tab2')
938        else:
939            self.redirect(self.url(document, 'manage') + '#tab2')
940        return
941
942    @action(_('Cancel'), validator=NullValidator)
943    def cancel(self, **data):
944        self.redirect(self.url(self.context))
945
946
947class DocumentDisplayFormPage(IkobaDisplayFormPage):
948    """ Page to view a document
949    """
950    grok.context(ICustomerDocument)
951    grok.name('index')
952    grok.require('waeup.viewCustomer')
953    grok.template('documentpage')
954    pnav = 4
955    label = None  # We render the context title in the documentpage template
956
957    @property
958    def form_fields(self):
959        return grok.AutoFields(self.context.form_fields_interface)
960
961class DocumentManageFormPage(IkobaEditFormPage):
962    """ Page to edit a document
963    """
964    grok.context(ICustomerDocument)
965    grok.name('manage')
966    grok.require('waeup.manageCustomer')
967    grok.template('documenteditpage')
968    pnav = 4
969    deletion_warning = _('Are you sure?')
970
971    @property
972    def form_fields(self):
973        return grok.AutoFields(
974            self.context.form_fields_interface).omit('document_id')
975
976    def update(self):
977        if not self.context.is_editable_by_manager:
978            emit_lock_message(self)
979            return
980        return super(DocumentManageFormPage, self).update()
981
982    @property
983    def label(self):
984        return self.context.title
985
986    @action(_('Save'), style='primary')
987    def save(self, **data):
988        msave(self, **data)
989        return
990
991
992class DocumentEditFormPage(DocumentManageFormPage):
993    """ Page to edit a document
994    """
995    grok.name('edit')
996    grok.require('waeup.handleCustomer')
997
998    def update(self):
999        if not self.context.is_editable_by_customer:
1000            emit_lock_message(self)
1001            return
1002        return super(DocumentEditFormPage, self).update()
1003
1004    @action(_('Save'), style='primary')
1005    def save(self, **data):
1006        msave(self, **data)
1007        return
1008
1009    @action(_('Final Submit'), warning=WARNING_DOC, style='success')
1010    def finalsubmit(self, **data):
1011        msave(self, **data)
1012        IWorkflowInfo(self.context).fireTransition('submit')
1013        self.flash(_('Form has been submitted.'))
1014        self.redirect(self.url(self.context))
1015        return
1016
1017
1018class DocumentTriggerTransitionFormPage(IkobaEditFormPage):
1019    """ View to trigger customer document transitions
1020    """
1021    grok.context(ICustomerDocument)
1022    grok.name('trigtrans')
1023    grok.require('waeup.triggerTransition')
1024    grok.template('trigtrans')
1025    label = _('Trigger document transition')
1026    pnav = 4
1027
1028    def update(self):
1029        return super(IkobaEditFormPage, self).update()
1030
1031    def getTransitions(self):
1032        """Return a list of dicts of allowed transition ids and titles.
1033
1034        Each list entry provides keys ``name`` and ``title`` for
1035        internal name and (human readable) title of a single
1036        transition.
1037        """
1038        wf_info = IWorkflowInfo(self.context)
1039        allowed_transitions = [t for t in wf_info.getManualTransitions()]
1040        return [dict(name='', title=_('No transition'))] +[
1041            dict(name=x, title=y) for x, y in allowed_transitions]
1042
1043    @action(_('Apply'), style='primary')
1044    def apply(self, **data):
1045        form = self.request.form
1046        if 'transition' in form and form['transition']:
1047            transition_id = form['transition']
1048            wf_info = IWorkflowInfo(self.context)
1049            try:
1050                wf_info.fireTransition(transition_id)
1051                self.flash(_("Transition '%s' executed." % transition_id))
1052            except InvalidTransitionError, error:
1053                self.flash(error, type="warning")
1054            self.redirect(self.url(self.context))
1055        return
1056
1057class PDFDocumentsOverviewSlip(UtilityView, grok.View):
1058    """Deliver an overview slip.
1059    """
1060    grok.context(ICustomerDocumentsContainer)
1061    grok.name('documents_overview_slip.pdf')
1062    grok.require('waeup.viewCustomer')
1063    prefix = 'form'
1064
1065    omit_fields = ('suspended', 'sex',
1066                   'suspended_comment',)
1067
1068    form_fields = None
1069
1070    @property
1071    def label(self):
1072        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1073        return translate(_('Documents of'),
1074            'waeup.ikoba', target_language=portal_language) \
1075            + ' %s' % self.context.customer.display_fullname
1076
1077    @property
1078    def tabletitle(self):
1079        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1080        tabletitle = []
1081        tabletitle.append(translate(_('Customer Documents'), 'waeup.ikoba',
1082            target_language=portal_language))
1083        return tabletitle
1084
1085    def render(self):
1086        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1087        #Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
1088        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
1089        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
1090        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
1091        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
1092        tableheader = []
1093        tabledata = []
1094        for i in range(1,3):
1095            tabledata.append(sorted(
1096                [value for value in self.context.values()]))
1097            tableheader.append([
1098                             #(Id, 'document_id', 2),
1099                             (Title, 'title', 6),
1100                             (Type, 'translated_class_name', 6),
1101                             (State, 'translated_state', 2),
1102                             (LT, 'formatted_transition_date', 3),
1103                             ])
1104        customerview = CustomerBasePDFFormPage(self.context.customer,
1105            self.request, self.omit_fields)
1106        customers_utils = getUtility(ICustomersUtils)
1107        return customers_utils.renderPDF(
1108            self, 'overview_slip.pdf',
1109            self.context.customer, customerview,
1110            tableheader=tableheader,
1111            tabledata=tabledata,
1112            omit_fields=self.omit_fields)
1113
1114
1115class PDFDocumentSlip(UtilityView, grok.View):
1116    """Deliver pdf file including metadata.
1117    """
1118    grok.context(ICustomerDocument)
1119    grok.name('document_slip.pdf')
1120    grok.require('waeup.viewCustomer')
1121    prefix = 'form'
1122
1123    omit_fields = ('suspended', 'sex',
1124                   'suspended_comment',)
1125
1126    form_fields =()
1127
1128    @property
1129    def label(self):
1130        return '%s of %s\nTitle: %s' % (
1131            self.context.translated_class_name,
1132            self.context.customer.display_fullname,
1133            self.context.title)
1134
1135    def render(self):
1136        customerview = CustomerBasePDFFormPage(self.context.customer,
1137            self.request, self.omit_fields)
1138        customers_utils = getUtility(ICustomersUtils)
1139        return customers_utils.renderPDF(
1140            self, 'document_slip.pdf',
1141            self.context.customer, customerview,
1142            omit_fields=self.omit_fields)
1143
1144
1145class PDFMergeDocumentSlip(PDFDocumentSlip):
1146    """Deliver pdf file including metadata.
1147    """
1148    grok.context(ICustomerPDFDocument)
1149
1150    def render(self):
1151        customerview = CustomerBasePDFFormPage(self.context.customer,
1152            self.request, self.omit_fields)
1153        customers_utils = getUtility(ICustomersUtils)
1154        if self.context.state == VERIFIED:
1155            watermark_path = os.path.join(
1156                os.path.dirname(__file__), 'static', 'verified.pdf')
1157        else:
1158            watermark_path = os.path.join(
1159                os.path.dirname(__file__), 'static', 'unverified.pdf')
1160        watermark = open(watermark_path, 'rb')
1161        return customers_utils.renderPDF(
1162            self, 'pdfdocument_slip.pdf',
1163            self.context.customer, customerview,
1164            omit_fields=self.omit_fields,
1165            mergefiles=self.context.connected_files,
1166            watermark=watermark)
1167
1168# Pages for customer contracts
1169
1170class ContractsBreadcrumb(Breadcrumb):
1171    """A breadcrumb for the contracts container.
1172    """
1173    grok.context(IContractsContainer)
1174    title = _('Contracts')
1175
1176
1177class ContractBreadcrumb(Breadcrumb):
1178    """A breadcrumb for the customer container.
1179    """
1180    grok.context(IContract)
1181
1182    @property
1183    def title(self):
1184        return "%s" % self.context.contract_id[:9]
1185
1186
1187class ContractsFormPage(IkobaEditFormPage):
1188    """ Page to display, edit or manage customer contracts
1189
1190    This form page is for both customers and officers.
1191    """
1192    grok.context(IContractsContainer)
1193    grok.name('index')
1194    grok.require('waeup.viewCustomer')
1195    form_fields = grok.AutoFields(IContractsContainer)
1196    grok.template('contractsmanagepage')
1197    pnav = 4
1198
1199    @property
1200    def edit_contracts_allowed(self):
1201        right_customer_state = self.context.customer.state in getUtility(
1202            ICustomersUtils).CONMANAGE_CUSTOMER_STATES
1203        if isCustomer(self.request.principal) and not right_customer_state:
1204            return False
1205        return checkPermission('waeup.editContracts', self.context)
1206
1207    def remove_contract_allowed(self, contract):
1208        if isCustomer(self.request.principal):
1209            right_customer_state = self.context.customer.state in getUtility(
1210                ICustomersUtils).CONMANAGE_CUSTOMER_STATES
1211            if not right_customer_state:
1212                return False
1213            if contract.state in (APPROVED, AWAITING, REJECTED, EXPIRED):
1214                return False
1215        return checkPermission('waeup.editContracts', self.context)
1216
1217    @property
1218    def label(self):
1219        return _('${a}: Contracts',
1220            mapping = {'a':self.context.__parent__.display_fullname})
1221
1222    @action(_('Add contract'), validator=NullValidator, style='primary')
1223    def addContract(self, **data):
1224        self.redirect(self.url(self.context, 'addcontract'))
1225        return
1226
1227    @jsaction(_('Remove selected contracts'))
1228    def delContract(self, **data):
1229        form = self.request.form
1230        if 'val_id' in form:
1231            child_id = form['val_id']
1232        else:
1233            self.flash(_('No contract selected.'), type="warning")
1234            self.redirect(self.url(self.context))
1235            return
1236        if not isinstance(child_id, list):
1237            child_id = [child_id]
1238        deleted = []
1239        for id in child_id:
1240            # Customers are not allowed to remove used contracts
1241            contract = self.context.get(id, None)
1242            if contract is not None and self.remove_contract_allowed(contract):
1243                del self.context[id]
1244                deleted.append(id)
1245        if len(deleted):
1246            self.flash(_('Successfully removed: ${a}',
1247                mapping = {'a': ', '.join(deleted)}))
1248            self.context.writeLogMessage(
1249                self,'removed: %s' % ', '.join(deleted))
1250        self.redirect(self.url(self.context))
1251        return
1252
1253
1254class ContractAddFormPage(IkobaAddFormPage):
1255    """ Page to add an contract
1256
1257    This page is for both customers and officers.
1258    """
1259    grok.context(IContractsContainer)
1260    grok.name('addcontract')
1261    grok.template('contractaddpage')
1262    grok.require('waeup.editContracts')
1263    label = _('Add contract')
1264    pnav = 4
1265
1266    @property
1267    def edit_contracts_allowed(self):
1268        right_customer_state = self.context.customer.state in getUtility(
1269            ICustomersUtils).CONMANAGE_CUSTOMER_STATES
1270        if right_customer_state:
1271            return True
1272        return False
1273
1274    def update(self):
1275        if not self.edit_contracts_allowed:
1276            emit_lock_message(self)
1277            return
1278        return super(ContractAddFormPage, self).update()
1279
1280    @property
1281    def selectable_contypes(self):
1282        contypes = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT
1283        return sorted(contypes.items())
1284
1285    @action(_('Add contract'), style='primary')
1286    def createContract(self, **data):
1287        form = self.request.form
1288        contype = form.get('contype', None)
1289        # Here we can create various instances of Contract derived
1290        # classes depending on the contype parameter given in form.
1291        contract = createObject('waeup.%s' % contype)
1292        # Check whether there are active products for this contract.
1293        concat = getattr(contract, 'contract_category', None)
1294        products = grok.getSite()['products'].values()
1295        active_products = [
1296            value for value in products
1297            if value.contract_category == concat and value.active
1298            ]
1299        if not len(active_products):
1300            self.flash(_('No active products in this category.'), type='warning')
1301            self.redirect(self.url(self.context))
1302            return
1303        self.context.addContract(contract)
1304        contype = getUtility(ICustomersUtils).SELECTABLE_CONTYPES_DICT[contype]
1305        self.flash(_('${a} added.', mapping = {'a': contype}))
1306        self.context.writeLogMessage(
1307            self,'added: %s %s' % (contype, contract.contract_id))
1308        self.redirect(self.url(contract, 'selectproduct'))
1309        return
1310
1311    @action(_('Cancel'), validator=NullValidator)
1312    def cancel(self, **data):
1313        self.redirect(self.url(self.context))
1314
1315
1316class ContractSelectProductPage(IkobaAddFormPage):
1317    """ Page to select a contract product
1318
1319    This page is for both customers and officers.
1320    """
1321    grok.context(IContract)
1322    grok.name('selectproduct')
1323    grok.require('waeup.editContracts')
1324    label = _('Select product')
1325    pnav = 4
1326
1327    form_fields = grok.AutoFields(IContractSelectProduct)
1328
1329    def update(self):
1330        if self.context.product_object is not None:
1331            emit_lock_message(self)
1332            return
1333        return super(ContractSelectProductPage, self).update()
1334
1335    @action(_('Save and proceed'), style='primary')
1336    def save(self, **data):
1337        msave(self, **data)
1338        self.context.title = self.context.product_object.contract_autotitle
1339        self.context.tc_dict = self.context.product_object.tc_dict
1340        self.context.valid_from = self.context.product_object.valid_from
1341        self.context.valid_to = self.context.product_object.valid_to
1342        isCustomer = getattr(
1343            self.request.principal, 'user_type', None) == 'customer'
1344        if isCustomer:
1345            self.redirect(self.url(self.context, 'edit'))
1346        else:
1347            self.redirect(self.url(self.context, 'manage'))
1348        return
1349
1350
1351class ContractDisplayFormPage(IkobaDisplayFormPage):
1352    """ Page to view a contract
1353    """
1354    grok.context(IContract)
1355    grok.name('index')
1356    grok.require('waeup.viewCustomer')
1357    grok.template('contractpage')
1358    pnav = 4
1359    label = None  # We render the context title in the contractpage template
1360
1361    @property
1362    def separators(self):
1363        return getUtility(ICustomersUtils).SEPARATORS_DICT
1364
1365    @property
1366    def form_fields(self):
1367        form_fields = grok.AutoFields(self.context.form_fields_interface)
1368        for field in form_fields:
1369            if field.__name__.endswith('_object'):
1370                form_fields[field.__name__].custom_widget = HREFDisplayWidget
1371        return form_fields
1372
1373    @property
1374    def terms_and_conditions(self):
1375        lang = self.request.cookies.get('ikoba.language')
1376        html = self.context.tc_dict.get(lang,'')
1377        if html =='':
1378            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1379            html = self.context.tc_dict.get(portal_language,'')
1380        return html
1381
1382
1383class ContractManageFormPage(IkobaEditFormPage):
1384    """ Page to manage a contract
1385    """
1386    grok.context(IContract)
1387    grok.name('manage')
1388    grok.require('waeup.manageCustomer')
1389    grok.template('contracteditpage')
1390    pnav = 4
1391    deletion_warning = _('Are you sure?')
1392
1393    def update(self):
1394        if not self.context.is_editable:
1395            emit_lock_message(self)
1396            return
1397        return super(ContractManageFormPage, self).update()
1398
1399    @property
1400    def separators(self):
1401        return getUtility(ICustomersUtils).SEPARATORS_DICT
1402
1403    @property
1404    def terms_and_conditions(self):
1405        lang = self.request.cookies.get('ikoba.language')
1406        html = self.context.tc_dict.get(lang,'')
1407        if html =='':
1408            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1409            html = self.context.tc_dict.get(portal_language,'')
1410        return html
1411
1412    @property
1413    def form_fields(self):
1414        return grok.AutoFields(self.context.form_fields_interface).omit(
1415            'contract_id', 'product_object')
1416
1417    @property
1418    def label(self):
1419        return self.context.title
1420
1421    @action(_('Save'), style='primary')
1422    def save(self, **data):
1423        msave(self, **data)
1424        return
1425
1426
1427class ContractOfficialUsePage(IkobaEditFormPage):
1428    """ Page to manage a official use data of contract
1429    """
1430    grok.context(IContract)
1431    grok.name('official_use')
1432    grok.require('waeup.manageCustomer')
1433    grok.template('contracteditpage')
1434    pnav = 4
1435    deletion_warning = _('Are you sure?')
1436    terms_and_conditions = None
1437
1438    @property
1439    def form_fields(self):
1440        return grok.AutoFields(self.context.ou_form_fields_interface)
1441
1442    @property
1443    def label(self):
1444        return self.context.title
1445
1446    @action(_('Save'), style='primary')
1447    def save(self, **data):
1448        msave(self, **data)
1449        return
1450
1451
1452class ContractEditFormPage(ContractManageFormPage):
1453    """ Page to edit a contract by customer only
1454    """
1455    grok.name('edit')
1456    grok.require('waeup.handleCustomer')
1457
1458    def submission_allowed(self, action=None):
1459        if self.context.state == CREATED and not self.context.fee_based:
1460            return True
1461        return False
1462
1463    def payment_expected(self, action=None):
1464        if self.context.state == CREATED and self.context.fee_based:
1465            return True
1466        return False
1467
1468    @property
1469    def form_fields(self):
1470        return grok.AutoFields(self.context.edit_form_fields_interface).omit(
1471            'contract_id', 'product_object')
1472
1473    @action(_('Save'), style='primary')
1474    def save(self, **data):
1475        msave(self, **data)
1476        return
1477
1478    @action(_('Apply now (final submission)'), warning=WARNING_CON,
1479            condition=submission_allowed, style='success')
1480    def submit(self, **data):
1481        if self.terms_and_conditions and not self.request.form.get(
1482            'confirm_tc', False):
1483            self.flash(_('Please read the terms and conditions and '
1484                     'confirm your acceptance of these by ticking '
1485                     'the confirmation box.'), type="danger")
1486            return
1487        msave(self, **data)
1488        IWorkflowInfo(self.context).fireTransition('submit')
1489        self.flash(_('Application form has been submitted.'))
1490        self.redirect(self.url(self.context))
1491        return
1492
1493    @action(_('Proceed to checkout'),
1494            condition=payment_expected, style='primary')
1495    def proceed_checkout(self, **data):
1496        if self.terms_and_conditions and not self.request.form.get(
1497            'confirm_tc', False):
1498            self.flash(_('Please read the terms and conditions and '
1499                     'confirm your acceptance of these by ticking '
1500                     'the confirmation box.'), type="danger")
1501            return
1502        msave(self, **data)
1503        self.redirect(self.url(self.context, 'select_payment_method'))
1504        return
1505
1506
1507class ContractSelectPaymentMethodPage(IkobaEditFormPage):
1508    """ Page to to select payment method
1509    """
1510    grok.context(IContract)
1511    grok.name('select_payment_method')
1512    grok.require('waeup.handleCustomer')
1513    grok.template('selectpaymentmethodpage')
1514    pnav = 4
1515    label = _('Select payment method')
1516
1517    @property
1518    def payment_gateways(self):
1519        """Get an iter over registered and enabled gateway service providers.
1520
1521        We provide tuples ``(value, description)`` for each supported
1522        payment gateway.
1523        """
1524        lister = getUtility(IPaymentGatewayServicesLister)
1525        for name, service in lister().items():
1526            yield {'name': name, 'title': service.title}
1527
1528    def update(self, CANCEL=None, gw=None):
1529        if self.context.state != CREATED or not self.context.fee_based:
1530            emit_lock_message(self)
1531            return
1532        self.gw = gw
1533        super(ContractSelectPaymentMethodPage, self).update()
1534        return
1535
1536    @action(_('Select payment method (final submission)'),
1537            style='success')
1538    def confirm(self, **data):
1539        if self.gw is None:
1540            self.flash(_('Please pick a payment method.'),
1541                       type='warning')
1542        else:
1543            service = queryUtility(IPaymentGatewayService, name=self.gw)
1544            if service is None:
1545                self.flash(_('Invalid payment gateway.'), type='danger')
1546                return
1547            payer = IPayer(self.context)
1548            payable = IPayable(self.context)
1549            payment = service.create_payment(payer, payable)
1550            service.store(payment)
1551            payment, view_name = service.next_step(payment.payment_id)
1552            url = self.url(payment, view_name)
1553            self.redirect(url)
1554            return
1555        return
1556
1557    @action(_('Cancel'))
1558    def cancel(self, **data):
1559        self.redirect(self.url(self.context, 'edit'))
1560        return
1561
1562
1563class ContractTriggerTransitionFormPage(IkobaEditFormPage):
1564    """ View to trigger customer contract transitions
1565    """
1566    grok.context(IContract)
1567    grok.name('trigtrans')
1568    grok.require('waeup.triggerTransition')
1569    grok.template('trigtrans')
1570    label = _('Trigger contract transition')
1571    pnav = 4
1572
1573    def update(self):
1574        return super(IkobaEditFormPage, self).update()
1575
1576    def getTransitions(self):
1577        """Return a list of dicts of allowed transition ids and titles.
1578
1579        Each list entry provides keys ``name`` and ``title`` for
1580        internal name and (human readable) title of a single
1581        transition.
1582        """
1583        wf_info = IWorkflowInfo(self.context)
1584        allowed_transitions = [t for t in wf_info.getManualTransitions()]
1585        # Skip payment transitions if total amount is zero
1586        if not self.context.fee_based:
1587            allowed_transitions = [t for t in allowed_transitions
1588                                   if not t[0] in PAYMENT_TRANSITIONS]
1589        return [dict(name='', title=_('No transition'))] +[
1590            dict(name=x, title=y) for x, y in allowed_transitions]
1591
1592    @action(_('Apply'), style='primary')
1593    def apply(self, **data):
1594        form = self.request.form
1595        if 'transition' in form and form['transition']:
1596            transition_id = form['transition']
1597            wf_info = IWorkflowInfo(self.context)
1598            try:
1599                wf_info.fireTransition(transition_id)
1600                self.flash(_("Transition '%s' executed." % transition_id))
1601            except InvalidTransitionError, error:
1602                self.flash(error, type="warning")
1603            self.redirect(self.url(self.context))
1604        return
1605
1606class PDFContractsOverviewSlip(UtilityView, grok.View):
1607    """Deliver an overview slip.
1608    """
1609    grok.context(IContractsContainer)
1610    grok.name('contracts_overview_slip.pdf')
1611    grok.require('waeup.viewCustomer')
1612    prefix = 'form'
1613
1614    omit_fields = ('suspended', 'sex',
1615                   'suspended_comment',)
1616
1617    form_fields = None
1618
1619    @property
1620    def label(self):
1621        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1622        return translate(_('Contracts of'),
1623            'waeup.ikoba', target_language=portal_language) \
1624            + ' %s' % self.context.customer.display_fullname
1625
1626    @property
1627    def tabletitle(self):
1628        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1629        tabletitle = []
1630        tabletitle.append(translate(_('Customer Contracts'), 'waeup.ikoba',
1631            target_language=portal_language))
1632        return tabletitle
1633
1634    def render(self):
1635        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1636        #Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
1637        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
1638        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
1639        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
1640        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
1641        tableheader = []
1642        tabledata = []
1643        for i in range(1,3):
1644            tabledata.append(sorted(
1645                [value for value in self.context.values()]))
1646            tableheader.append([
1647                             #(Id, 'contract_id', 2),
1648                             (Title, 'title', 6),
1649                             (Type, 'translated_class_name', 6),
1650                             (State, 'translated_state', 2),
1651                             (LT, 'formatted_transition_date', 3),
1652                             ])
1653        customerview = CustomerBasePDFFormPage(self.context.customer,
1654            self.request, self.omit_fields)
1655        customers_utils = getUtility(ICustomersUtils)
1656        return customers_utils.renderPDF(
1657            self, 'overview_slip.pdf',
1658            self.context.customer, customerview,
1659            tableheader=tableheader,
1660            tabledata=tabledata,
1661            omit_fields=self.omit_fields)
1662
1663
1664class PDFContractSlip(UtilityView, grok.View):
1665    """Deliver pdf file including metadata.
1666    """
1667    grok.context(IContract)
1668    grok.name('contract_slip.pdf')
1669    grok.require('waeup.viewCustomer')
1670    prefix = 'form'
1671
1672    omit_fields = ('suspended', 'sex',
1673                   'suspended_comment',)
1674
1675    @property
1676    def form_fields(self):
1677        return grok.AutoFields(self.context.form_fields_interface)
1678
1679    @property
1680    def terms_and_conditions(self):
1681        lang = self.request.cookies.get('ikoba.language')
1682        html = self.context.tc_dict.get(lang,'')
1683        if html =='':
1684            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1685            html = self.context.tc_dict.get(portal_language,'')
1686        return html
1687
1688    @property
1689    def validity_period(self):
1690        if self.context.valid_from or self.context.valid_to:
1691            return "%s - %s" % (format_date(self.context.valid_from, self.request),
1692                                format_date(self.context.valid_to, self.request))
1693        return
1694
1695    @property
1696    def label(self):
1697        return self.context.title
1698
1699    # XXX: not used in waeup.ikoba and thus not tested
1700    def _signatures(self):
1701        return ([_('Customer Signature')],
1702                [_('Company Officer Signature')]
1703                )
1704
1705    def _sigsInFooter(self):
1706        return (_('Date, Customer Signature'),
1707                _('Date, Company Officer Signature'),
1708                )
1709
1710    def render(self):
1711        customerview = CustomerBasePDFFormPage(self.context.customer,
1712            self.request, self.omit_fields)
1713        customers_utils = getUtility(ICustomersUtils)
1714        return customers_utils.renderPDF(
1715            self, 'contract_slip.pdf',
1716            self.context.customer, customerview,
1717            signatures=self._signatures(),
1718            sigs_in_footer=self._sigsInFooter(),
1719            omit_fields=self.omit_fields)
1720
1721
1722class PaymentsPage(IkobaPage):
1723    """ Page to display all payments made by customer.
1724
1725    """
1726    grok.context(ICustomer)
1727    grok.name('payments')
1728    grok.require('waeup.manageCustomer')
1729    grok.template('paymentspage')
1730    pnav = 4
1731
1732    @property
1733    def label(self):
1734        return _('${a}: Payments', mapping={'a':self.context.display_fullname})
1735
1736    @property
1737    def payment_states(self):
1738        return payment_states
1739
1740    @property
1741    def gateway_services(self):
1742        return get_payment_providers()
1743
1744    def payments(self):
1745        return search_payments(
1746            query=self.context.customer_id, searchtype='payer_id')
1747
1748
1749class PDFContractReceiptSlip(UtilityView, grok.View):
1750    """Deliver pdf file including metadata.
1751    """
1752    grok.context(IContract)
1753    grok.name('contract_payment_receipt.pdf')
1754    grok.require('waeup.viewCustomer')
1755    prefix = 'form'
1756
1757    omit_fields = ('suspended', 'sex',
1758                   'suspended_comment',)
1759
1760    @property
1761    def form_fields(self):
1762        return grok.AutoFields(self.context.form_fields_interface)
1763
1764    @property
1765    def label(self):
1766        return 'Payment Receipt: %s' % (
1767            self.context.title)
1768
1769    @property
1770    def payment_tuples(self):
1771        payment_tuples = []
1772        for payment in get_payments_from_payable_id(self.context.contract_id):
1773            form_fields = grok.AutoFields(payment.form_fields_interface).omit(
1774                'payment_items')
1775            form_fields[
1776                'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1777            form_fields[
1778                'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1779            payment_tuples.append((payment, form_fields))
1780        return payment_tuples
1781
1782    def render(self):
1783        customerview = CustomerBasePDFFormPage(self.context.customer,
1784            self.request, self.omit_fields)
1785        customers_utils = getUtility(ICustomersUtils)
1786        return customers_utils.renderPDF(
1787            self, 'contract_payment_receipt.pdf',
1788            self.context.customer, customerview,
1789            omit_fields=self.omit_fields,
1790            show_history=False)
Note: See TracBrowser for help on using the repository browser.