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

Last change on this file since 13606 was 13066, checked in by Henrik Bettermann, 9 years ago

Rename customer views which are not layout-aware and thus not pages.

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