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

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

Use correct interfaces.

  • Property svn:keywords set to Id
File size: 54.6 KB
Line 
1## $Id: browser.py 12535 2015-02-01 07:27:38Z henrik $
2##
3## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""UI components for customers and related components.
19"""
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.tc_dict = self.context.product_object.tc_dict
1340        isCustomer = getattr(
1341            self.request.principal, 'user_type', None) == 'customer'
1342        if isCustomer:
1343            self.redirect(self.url(self.context, 'edit'))
1344        else:
1345            self.redirect(self.url(self.context, 'manage'))
1346        return
1347
1348
1349class ContractDisplayFormPage(IkobaDisplayFormPage):
1350    """ Page to view a contract
1351    """
1352    grok.context(IContract)
1353    grok.name('index')
1354    grok.require('waeup.viewCustomer')
1355    grok.template('contractpage')
1356    pnav = 4
1357    label = None  # We render the context title in the contractpage template
1358
1359    @property
1360    def form_fields(self):
1361        form_fields = grok.AutoFields(self.context.form_fields_interface)
1362        for field in form_fields:
1363            if field.__name__.endswith('_object'):
1364                form_fields[field.__name__].custom_widget = HREFDisplayWidget
1365        return form_fields
1366
1367    @property
1368    def terms_and_conditions(self):
1369        lang = self.request.cookies.get('ikoba.language')
1370        html = self.context.tc_dict.get(lang,'')
1371        if html =='':
1372            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1373            html = self.context.tc_dict.get(portal_language,'')
1374        return html
1375
1376
1377class ContractManageFormPage(IkobaEditFormPage):
1378    """ Page to manage a contract
1379    """
1380    grok.context(IContract)
1381    grok.name('manage')
1382    grok.require('waeup.manageCustomer')
1383    grok.template('contracteditpage')
1384    pnav = 4
1385    deletion_warning = _('Are you sure?')
1386
1387    def update(self):
1388        if not self.context.is_editable:
1389            emit_lock_message(self)
1390            return
1391        return super(ContractManageFormPage, self).update()
1392
1393    @property
1394    def form_fields(self):
1395        return grok.AutoFields(self.context.form_fields_interface).omit(
1396            'contract_id', 'product_object')
1397
1398    @property
1399    def label(self):
1400        return self.context.title
1401
1402    @action(_('Save'), style='primary')
1403    def save(self, **data):
1404        msave(self, **data)
1405        return
1406
1407
1408class ContractOfficialUsePage(IkobaEditFormPage):
1409    """ Page to manage a official use data of contract
1410    """
1411    grok.context(IContract)
1412    grok.name('official_use')
1413    grok.require('waeup.manageCustomer')
1414    grok.template('contracteditpage')
1415    pnav = 4
1416    deletion_warning = _('Are you sure?')
1417
1418    @property
1419    def form_fields(self):
1420        return grok.AutoFields(self.context.ou_form_fields_interface)
1421
1422    @property
1423    def label(self):
1424        return self.context.title
1425
1426    @action(_('Save'), style='primary')
1427    def save(self, **data):
1428        msave(self, **data)
1429        return
1430
1431
1432class ContractEditFormPage(ContractManageFormPage):
1433    """ Page to edit a contract by customer only
1434    """
1435    grok.name('edit')
1436    grok.require('waeup.handleCustomer')
1437
1438    @property
1439    def form_fields(self):
1440        return grok.AutoFields(self.context.edit_form_fields_interface).omit(
1441            'contract_id', 'product_object')
1442
1443    @action(_('Save'), style='primary')
1444    def save(self, **data):
1445        msave(self, **data)
1446        return
1447
1448    @action(_('Apply now (final submit)'), warning=WARNING_CON)
1449    def finalsubmit(self, **data):
1450        msave(self, **data)
1451        IWorkflowInfo(self.context).fireTransition('submit')
1452        self.flash(_('Form has been submitted.'))
1453        self.redirect(self.url(self.context))
1454        return
1455
1456
1457class ContractTriggerTransitionFormPage(IkobaEditFormPage):
1458    """ View to trigger customer contract transitions
1459    """
1460    grok.context(IContract)
1461    grok.name('trigtrans')
1462    grok.require('waeup.triggerTransition')
1463    grok.template('trigtrans')
1464    label = _('Trigger contract transition')
1465    pnav = 4
1466
1467    def update(self):
1468        return super(IkobaEditFormPage, self).update()
1469
1470    def getTransitions(self):
1471        """Return a list of dicts of allowed transition ids and titles.
1472
1473        Each list entry provides keys ``name`` and ``title`` for
1474        internal name and (human readable) title of a single
1475        transition.
1476        """
1477        wf_info = IWorkflowInfo(self.context)
1478        allowed_transitions = [t for t in wf_info.getManualTransitions()]
1479        return [dict(name='', title=_('No transition'))] +[
1480            dict(name=x, title=y) for x, y in allowed_transitions]
1481
1482    @action(_('Apply'), style='primary')
1483    def apply(self, **data):
1484        form = self.request.form
1485        if 'transition' in form and form['transition']:
1486            transition_id = form['transition']
1487            wf_info = IWorkflowInfo(self.context)
1488            try:
1489                wf_info.fireTransition(transition_id)
1490                self.flash(_("Transition '%s' executed." % transition_id))
1491            except InvalidTransitionError, error:
1492                self.flash(error, type="warning")
1493            self.redirect(self.url(self.context))
1494        return
1495
1496class PDFContractsOverviewPage(UtilityView, grok.View):
1497    """Deliver an overview slip.
1498    """
1499    grok.context(IContractsContainer)
1500    grok.name('contracts_overview_slip.pdf')
1501    grok.require('waeup.viewCustomer')
1502    prefix = 'form'
1503
1504    omit_fields = ('suspended', 'sex',
1505                   'suspended_comment',)
1506
1507    form_fields = None
1508
1509    @property
1510    def label(self):
1511        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1512        return translate(_('Contracts of'),
1513            'waeup.ikoba', target_language=portal_language) \
1514            + ' %s' % self.context.customer.display_fullname
1515
1516    @property
1517    def tabletitle(self):
1518        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1519        tabletitle = []
1520        tabletitle.append(translate(_('Customer Contracts'), 'waeup.ikoba',
1521            target_language=portal_language))
1522        return tabletitle
1523
1524    def render(self):
1525        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1526        #Id = translate(_('Id'), 'waeup.ikoba', target_language=portal_language)
1527        Title = translate(_('Title'), 'waeup.ikoba', target_language=portal_language)
1528        Type = translate(_('Type'), 'waeup.ikoba', target_language=portal_language)
1529        State = translate(_('State'), 'waeup.ikoba', target_language=portal_language)
1530        LT = translate(_('Last Transition'), 'waeup.ikoba', target_language=portal_language)
1531        tableheader = []
1532        tabledata = []
1533        contenttitle = []
1534        for i in range(1,3):
1535            tabledata.append(sorted(
1536                [value for value in self.context.values()]))
1537            tableheader.append([
1538                             #(Id, 'contract_id', 2),
1539                             (Title, 'title', 6),
1540                             (Type, 'translated_class_name', 6),
1541                             (State, 'translated_state', 2),
1542                             (LT, 'formatted_transition_date', 3),
1543                             ])
1544        customerview = CustomerBasePDFFormPage(self.context.customer,
1545            self.request, self.omit_fields)
1546        customers_utils = getUtility(ICustomersUtils)
1547        return customers_utils.renderPDF(
1548            self, 'overview_slip.pdf',
1549            self.context.customer, customerview,
1550            tableheader=tableheader,
1551            tabledata=tabledata,
1552            omit_fields=self.omit_fields)
1553
1554
1555class PDFContractSlipPage(UtilityView, grok.View):
1556    """Deliver pdf file including metadata.
1557    """
1558    grok.context(IContract)
1559    grok.name('contract_slip.pdf')
1560    grok.require('waeup.viewCustomer')
1561    prefix = 'form'
1562
1563    omit_fields = ('suspended', 'sex',
1564                   'suspended_comment',)
1565
1566    @property
1567    def form_fields(self):
1568        return grok.AutoFields(self.context.form_fields_interface)
1569
1570    @property
1571    def terms_and_conditions(self):
1572        lang = self.request.cookies.get('ikoba.language')
1573        html = self.context.tc_dict.get(lang,'')
1574        if html =='':
1575            portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1576            html = self.context.tc_dict.get(portal_language,'')
1577        return html
1578
1579    @property
1580    def label(self):
1581        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1582        return self.context.title
1583
1584    def render(self):
1585        portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE
1586        customerview = CustomerBasePDFFormPage(self.context.customer,
1587            self.request, self.omit_fields)
1588        customers_utils = getUtility(ICustomersUtils)
1589        return customers_utils.renderPDF(
1590            self, 'contract_slip.pdf',
1591            self.context.customer, customerview,
1592            omit_fields=self.omit_fields)
Note: See TracBrowser for help on using the repository browser.