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

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

The term 'application' should really not be used in Python-based portal software.

Replace 'application' by 'contract': batch 1

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