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

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

Add signatures to contract slip.

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