Project

General

Profile

Download (20.4 KB) Statistics
| Branch: | Tag: | Revision:
import time
import pprint
import locale
import decimal
import datetime

from quixote import get_request, get_response, get_session, redirect
from quixote.directory import Directory, AccessControlled

import wcs
import wcs.admin.root
from wcs.backoffice.menu import *
from wcs.formdef import FormDef

from qommon import errors, misc, template, get_logger
from qommon.form import *
from qommon.strftime import strftime
from qommon.admin.emails import EmailsDirectory

from payments import (eopayment, Regie, is_payment_supported, Invoice,
Transaction, notify_paid_invoice)

from qommon.admin.texts import TextsDirectory

if not set:
from sets import Set as set

def invoice_as_html [html] (invoice):
'<div id="invoice">'
'<h2>%s</h2>' % _('Invoice: %s') % invoice.subject
'<h3>%s' % _('Amount: %s') % invoice.amount
' &euro;</h3>'
'<!-- DEBUG \n'
'Invoice:\n'
pprint.pformat(invoice.__dict__)
for transaction in Transaction.get_with_indexed_value('invoice_ids', invoice.id):
'\nTransaction:\n'
pprint.pformat(transaction.__dict__)
'\n-->'
if invoice.formdef_id and invoice.formdata_id and \
get_session().user == invoice.user_id:
formdef = FormDef.get(invoice.formdef_id)
if formdef:
formdata = formdef.data_class().get(invoice.formdata_id, ignore_errors=True)
if formdata:
name = _('%(form_name)s #%(formdata_id)s') % {
'form_name': formdata.formdef.name,
'formdata_id': formdata.id }
'<p class="from">%s <a href="%s">%s</a></p>' % (_('From:'), formdata.get_url(), name)
'<p class="regie">%s</p>' % _('Regie: %s') % Regie.get(invoice.regie_id).label
'<p class="date">%s</p>' % _('Created on: %s') % misc.localstrftime(invoice.date)
if invoice.details:
'<p class="details">%s</p>' % _('Details:')
'<div class="details">'
htmltext(invoice.details)
'</div>'
if invoice.canceled:
'<p class="canceled">'
'%s' % _('canceled on %s') % misc.localstrftime(invoice.canceled_date)
if invoice.canceled_reason:
' (%s)' % invoice.canceled_reason
'</p>'
if invoice.paid:
'<p class="paid">%s</p>' % _('paid on %s') % misc.localstrftime(invoice.paid_date)
'</div>'

class InvoicesDirectory(Directory):
_q_exports = ['', 'multiple']

def _q_traverse(self, path):
if not is_payment_supported():
raise errors.TraversalError()
get_response().filter['bigdiv'] = 'profile'
if get_session().user:
# fake breadcrumb
get_response().breadcrumb.append(('myspace/', _('My Space')))
get_response().breadcrumb.append(('invoices/', _('Invoices')))
return Directory._q_traverse(self, path)

def multiple [html] (self):
invoice_ids = get_request().form.get('invoice')
if type(invoice_ids) is not list:
return redirect('%s' % invoice_ids)
return redirect('+'.join(invoice_ids))

def _q_lookup [html] (self, component):
if str('+') in component:
invoice_ids = component.split(str('+'))
else:
invoice_ids = [component]
for invoice_id in invoice_ids:
if not Invoice.check_crc(invoice_id):
raise errors.TraversalError()

template.html_top(_('Invoices'))
TextsDirectory.get_html_text('aq-invoice')

regies_id = set()
for invoice_id in invoice_ids:
try:
invoice = Invoice.get(invoice_id)
except KeyError:
raise errors.TraversalError()
invoice_as_html(invoice)
if not (invoice.paid or invoice.canceled):
regies_id.add(invoice.regie_id)

if len(regies_id) == 1:
'<p class="command">'
'<a href="%s/payment/init?invoice_ids=%s">' % (get_publisher().get_frontoffice_url(), component)
if len(invoice_ids) > 1:
_('Pay Selected Invoices')
else:
_('Pay')
'</a></p>'
if len(regies_id) > 1:
_('You can not pay to different regies.')

def _q_index(self):
return redirect('..')


class RegieDirectory(Directory):
_q_exports = ['', 'edit', 'delete', 'options']

def __init__(self, regie):
self.regie = regie

def _q_index [html] (self):
html_top('payments', title = _('Regie: %s') % self.regie.label)
get_response().filter['sidebar'] = self.get_sidebar()
'<h2>%s</h2>' % _('Regie: %s') % self.regie.label

get_session().display_message()

if self.regie.description:
'<div class="bo-block">'
'<p>'
self.regie.description
'</p>'
'</div>'

if self.regie.service:
'<div class="bo-block">'
url = get_publisher().get_frontoffice_url() + '/payment/back_asynchronous/'
url += str(self.regie.id)
'<p>'
'%s %s' % (_('Banking Service:'), self.regie.service)
' (<a href="options">%s</a>)' % _('options')
'</p>'
'<p>'
'%s %s' % (_('Payment notification URL:'), url)
'</div>'

self.invoice_listing()

def get_sidebar [html] (self):
'<ul>'
'<li><a href="edit">%s</a></li>' % _('Edit')
'<li><a href="delete">%s</a></li>' % _('Delete')
'</ul>'

def edit [html] (self):
form = self.form()
if form.get_submit() == 'cancel':
return redirect('.')

if form.is_submitted() and not form.has_errors():
self.submit(form)
return redirect('..')

html_top('payments', title = _('Edit Regie: %s') % self.regie.label)
'<h2>%s</h2>' % _('Edit Regie: %s') % self.regie.label
form.render()


def form(self):
form = Form(enctype='multipart/form-data')
form.add(StringWidget, 'label', title=_('Label'), required=True,
value=self.regie.label)
form.add(TextWidget, 'description', title=_('Description'),
value=self.regie.description, rows=5, cols=60)
form.add(SingleSelectWidget, 'service', title=_('Banking Service'),
value=self.regie.service, required=True,
options = [
('dummy', _('Dummy (for tests)')),
('sips', 'SIPS'),
('systempayv2', 'systempay (Banque Populaire)'),
('spplus', _('SP+ (Caisse d\'epargne)'))])
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form

def submit(self, form):
for k in ('label', 'description', 'service'):
widget = form.get_widget(k)
if widget:
setattr(self.regie, k, widget.parse())
self.regie.store()

def delete [html] (self):
form = Form(enctype='multipart/form-data')
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
'You are about to irrevocably delete this regie.')))
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_submit() == 'cancel':
return redirect('..')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append(('delete', _('Delete')))
html_top('payments', title = _('Delete Regie'))
'<h2>%s</h2>' % _('Deleting Regie: %s') % self.regie.label
form.render()
else:
self.regie.remove_self()
return redirect('..')

def option_form(self):
form = Form(enctype='multipart/form-data')
module = eopayment.get_backend(self.regie.service)
service_options = {}
for infos in module.description['parameters']:
if 'default' in infos:
service_options[infos['name']] = infos['default']
service_options.update(self.regie.service_options or {})

banking_titles = {
('dummy', 'direct_notification_url'): N_('Direct Notification URL'),
('dummy', 'siret'): N_('Dummy SIRET'),
}

for infos in module.description['parameters']:
name = infos['name']
caption = infos.get('caption', name).encode(get_publisher().site_charset)
title = banking_titles.get((self.regie.service, name), caption)
kwargs = {}
widget = StringWidget
if infos.get('help_text') is not None:
kwargs['hint'] = _(infos['help_text'].encode(get_publisher().site_charset))
if infos.get('required', False):
kwargs['required'] = True
if infos.get('max_length') is not None:
kwargs['size'] = infos['max_length']
elif infos.get('length') is not None:
kwargs['size'] = infos['length']
else:
kwargs['size'] = 80
if kwargs['size'] > 100:
widget = TextWidget
kwargs['cols'] = 80
kwargs['rows'] = 5
if 'type' not in infos or infos['type'] is str:
form.add(widget, name, title=_(title),
value=service_options.get(name), **kwargs)
elif infos['type'] is bool:
form.add(CheckboxWidget, name, title=title,
value=service_options.get(name), **kwargs)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form

def options [html] (self):
form = self.option_form()

module = eopayment.get_backend(self.regie.service)
try:
'<!-- Payment backend description: \n'
pprint.pformat(module.description)
'-->'
except:
return template.error_page(_('Payment backend do not list its options'))
raise errors.TraversalError()
'<!-- \n'
'Service options\n'
pprint.pformat(self.regie.service_options)
'-->'

if form.get_submit() == 'cancel':
return redirect('.')

if form.is_submitted() and not form.has_errors():
if self.submit_options(form, module):
return redirect('..')

html_top('payments', title=_('Edit Service Options'))
'<h2>%s</h2>' % _('Edit Service Options')
form.render()

def submit_options(self, form, module):
# extra validation
error = False
for infos in module.description['parameters']:
widget = form.get_widget(infos['name'])
value = widget.parse()
if value and 'validation' in infos:
try:
if not infos['validation'](value):
widget.set_error(_('Valeur invalide'))
error = True
except ValueError, e:
widget.set_error(_(e.message))
error = True
if error:
return False
if not self.regie.service_options:
self.regie.service_options = {}
for infos in module.description['parameters']:
name = infos['name']
value = form.get_widget(name).parse()
if value is None:
value = ''
if hasattr(value, 'strip'):
value = value.strip()
if infos.get('default') is not None:
if value == infos['default']:
self.regie.service_options.pop(name, None)
else:
self.regie.service_options[name] = form.get_widget(name).parse()
elif not value:
self.regie.service_options.pop(name, None)
else:
self.regie.service_options[name] = form.get_widget(name).parse()
self.regie.store()
return True

PAGINATION = 50

def monetary_amount(self, val):
if isinstance(val, basestring):
val = val.replace(',', '.')
return '%.2f' % decimal.Decimal(val)

def get_sort_by(self):
request = get_request()
sort_by = request.form.get('sort_by')
if sort_by not in ('date', 'paid_date', 'username'):
sort_by = 'date'
return sort_by

def get_invoices(self):
sort_by = self.get_sort_by()
invoices = Invoice.get_with_indexed_value('regie_id', self.regie.id,
ignore_errors=True)
if 'date' in sort_by:
reverse = True
key = lambda i: getattr(i, sort_by) or datetime.datetime.now()
else:
reverse = False
key = lambda i: getattr(i, sort_by) or ''
invoices.sort(reverse=reverse, key=key)
return invoices

def unpay(self, request, invoice):
get_logger().info(_('manually set unpaid invoice %(invoice_id)s in regie %(regie)s')
% dict(invoice_id=invoice.id, regie=self.regie.id))
transaction = Transaction()
transaction.invoice_ids = [ invoice.id ]
transaction.order_id = 'Manual action'
transaction.start = datetime.datetime.now()
transaction.end = transaction.start
transaction.bank_data = {
'action': 'Set unpaid',
'by': request.user.get_display_name() + ' (%s)' % request.user.id
}
transaction.store()
invoice.unpay()

def pay(self, request, invoice):
get_logger().info(_('manually set paid invoice %(invoice_id)s in regie %(regie)s')
% dict(invoice_id=invoice.id, regie=self.regie.id))
transaction = Transaction()
transaction.invoice_ids = [ invoice.id ]
transaction.order_id = 'Manual action'
transaction.start = datetime.datetime.now()
transaction.end = transaction.start
transaction.bank_data = {
'action': 'Set paid',
'by': request.user.get_display_name() + ' (%s)' % request.user.id
}
transaction.store()
invoice.pay()

def invoice_listing [html] (self):
request = get_request()
get_response().add_css_include('../../themes/auquotidien/admin.css')
if request.get_method() == 'POST':
invoice_id = request.form.get('id')
invoice = Invoice.get(invoice_id, ignore_errors=True)
if invoice:
if 'unpay' in request.form:
self.unpay(request, invoice)
elif 'pay' in request.form:
self.pay(request, invoice)
return redirect('')
try:
offset = int(request.form.get('offset', 0))
except ValueError:
offset = 0
'<table id="invoice-listing" borderspacing="0">'
'<thead>'
'<tr>'
'<td><a href="?sort_by=date&offset=%d">Creation</a></td>' % offset
'<td>Amount</td>'
'<td><a href="?sort_by=paid_date&offset=%d">Paid</a></td>' % offset
'<td><a href="?sort_by=username&offset=%d">User</a></td>' % offset
'<td>Titre</td>'
'<td></td>'
'</tr>'
'</thead>'
invoices = self.get_invoices()
for invoice in invoices[offset:offset+self.PAGINATION]:
'<tbody class="invoice-rows">'
'<tr class="invoice-row"><td>'
misc.localstrftime(invoice.date)
'</td><td class="amount">'
self.monetary_amount(invoice.amount)
'</td><td>'
if invoice.paid:
misc.localstrftime(invoice.paid_date)
else:
''
'</td><td>'
user = invoice.get_user()
if user:
user.name
'</td><td class="subject">%s</td>' % (invoice.subject or '')
'<td>'
'<form method="post">'
'<input type="hidden" name="id" value="%s"/> ' % invoice.id
if invoice.paid:
'<input type="submit" name="unpay" value="%s"/>' % _('Set unpaid')
else:
'<input type="submit" name="pay" value="%s"/>' % _('Set paid')
'</form>'

'</td></tr>'
transactions = Transaction.get_with_indexed_value('invoice_ids',
invoice.id)
for transaction in sorted(transactions, key=lambda x: x.start):
'<tr>'
'<td></td>'
'<td colspan="5">'
'OrderID: %s' % transaction.order_id
' Start: %s' % transaction.start
if transaction.end:
' End: %s' % transaction.end
if transaction.bank_data:
' Bank data: %r' % transaction.bank_data
'</td>'
'</tr>'
'</tbody>'
'</tbody></table>'
if offset != 0:
'<a href="?offset=%d>%s</a> ' % (
max(0, offset-self.PAGINATION), _('Previous'))
if offset + self.PAGINATION < len(invoices):
'<a href="?offset=%d>%s</a> ' % (
max(0, offset-self.PAGINATION), _('Previous'))


class RegiesDirectory(Directory):
_q_exports = ['', 'new']

def _q_traverse(self, path):
get_response().breadcrumb.append(('regie/', _('Regies')))
return Directory._q_traverse(self, path)

def _q_index [html] (self):
return redirect('..')

def new [html] (self):
regie_ui = RegieDirectory(Regie())

form = regie_ui.form()
if form.get_submit() == 'cancel':
return redirect('.')

if form.is_submitted() and not form.has_errors():
regie_ui.submit(form)
return redirect('%s/' % regie_ui.regie.id)

get_response().breadcrumb.append(('new', _('New Regie')))
html_top('payments', title = _('New Regie'))
'<h2>%s</h2>' % _('New Regie')
form.render()

def _q_lookup(self, component):
try:
regie = Regie.get(component)
except KeyError:
raise errors.TraversalError()
get_response().breadcrumb.append((str(regie.id), regie.label))
return RegieDirectory(regie)


class PaymentsDirectory(AccessControlled, Directory):
_q_exports = ['', 'regie']
label = N_('Payments')

regie = RegiesDirectory()

def _q_access(self):
user = get_request().user
if not user:
raise errors.AccessUnauthorizedError()
admin_role = get_cfg('aq-permissions', {}).get('payments', None)
if not (user.is_admin or admin_role in (user.roles or [])):
raise errors.AccessForbiddenError(
public_msg = _('You are not allowed to access Payments Management'),
location_hint = 'backoffice')

get_response().breadcrumb.append(('payments/', _('Payments')))


def _q_index [html] (self):
html_top('payments', _('Payments'))

'<ul id="main-actions">'
' <li><a class="new-item" href="regie/new">%s</a></li>' % _('New Regie')
'</ul>'

if not is_payment_supported:
'<p class="infonotice">'
_('Payment is not supported.')
'</p>'

regies = Regie.select()
'<h2>%s</h2>' % _('Regies')
if not regies:
'<p>'
_('There are no regies defined at the moment.')
'</p>'
'<ul class="biglist" id="regies-list">'
for l in regies:
regie_id = l.id
'<li class="biglistitem" id="itemId_%s">' % regie_id
'<strong class="label"><a href="regie/%s/">%s</a></strong>' % (regie_id, l.label)
'</li>'
'</ul>'


TextsDirectory.register('aq-invoice',
N_('Message on top of an invoice'),
category = N_('Invoices'))

EmailsDirectory.register('payment-new-invoice-email',
N_('New invoice'),
N_('Available variables: user, regie, invoice, invoice_url'),
category = N_('Invoices'),
default_subject = N_('New invoice'),
default_body = N_('''
A new invoice is available at [invoice_url].
'''))

EmailsDirectory.register('payment-invoice-paid-email',
N_('Paid invoice'),
N_('Available variables: user, regie, invoice, invoice_url'),
category = N_('Invoices'),
default_subject = N_('Paid invoice'),
default_body = N_('''
The invoice [invoice_url] has been paid.
'''))

EmailsDirectory.register('payment-invoice-canceled-email',
N_('Canceled invoice'),
N_('Available variables: user, regie, invoice, invoice_url'),
category = N_('Invoices'),
default_subject = N_('Canceled invoice'),
default_body = N_('''
The invoice [invoice.id] has been canceled.
'''))
(28-28/33)