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

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

Add PDFMergeDocumentSlipPage which merges the pdf slip with pdf files connected to the document.

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