← Back to team overview

savoirfairelinux-openerp team mailing list archive

[Merge] lp:~savoirfairelinux-openerp/banking-addons/loose-coupling into lp:banking-addons/bank-statement-reconcile-7.0

 

Virgil Dupras has proposed merging lp:~savoirfairelinux-openerp/banking-addons/loose-coupling into lp:banking-addons/bank-statement-reconcile-7.0.

Requested reviews:
  Banking Addons Core Editors (banking-addons-team)

For more details, see:
https://code.launchpad.net/~savoirfairelinux-openerp/banking-addons/loose-coupling/+merge/185033
-- 
https://code.launchpad.net/~savoirfairelinux-openerp/banking-addons/loose-coupling/+merge/185033
Your team Savoir-faire Linux' OpenERP is subscribed to branch lp:~savoirfairelinux-openerp/banking-addons/loose-coupling.
=== added directory 'customer_payment_request'
=== added file 'customer_payment_request/__init__.py'
--- customer_payment_request/__init__.py	1970-01-01 00:00:00 +0000
+++ customer_payment_request/__init__.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,2 @@
+import customer_payment_request
+import registry

=== added file 'customer_payment_request/__openerp__.py'
--- customer_payment_request/__openerp__.py	1970-01-01 00:00:00 +0000
+++ customer_payment_request/__openerp__.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,71 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    "name"          : "Customer Payment Requests",
+    "version"       : "1.0",
+    "author"        : "Savoir-faire Linux",
+    "website"       : "http://www.savoirfairelinux.com";,
+    "category"      : "Accounting & Finance",
+    "description"   : """
+Deals with mass payment requests
+--------------------------------
+        
+When dealing with mass payments, we typically have a bunch of customers that owe us money and
+we want to charge their credit card, or bank account, or whatever. We have that information
+in one or more res.partner.bank record.
+
+The workflow here is that for every partner owing us money, we create a payment request in
+"waiting" mode, associating that payment with the first res.partner.bank associated with that
+partner.
+
+Then, we notify our registered payment handlers and they handle the payment request when they
+can handle the bank account associated with the request. The payment is then put in "sent" mode
+so that it isn't sent again until we receive the banks' responses.
+
+Then, we wait for the responses from banks by periodically polling for it. Upon receiving those
+responses, the bank handlers calls register_payment_refusal() or register_payment_acceptation().
+
+If accepted, the payment request is deleted and a validated voucher is created for the
+customer payment.
+
+If refused, the payment request is put back in waiting mode. If the partner has more than one
+bank account, we set the request's bank to the next in line.
+
+This module registers 3 cron jobs:
+
+1. send_customer_payment_requests(): Create a payment request for each partner owing us money and
+   tell bank handlers to send those requests.
+2. send_waiting_requests(): Retry every payment requests in "waiting" mode (being in this mode
+   because the previous payment attempt failed).
+3. receive_customer_payment_response(): Poll bank handlers for responses from banks. If there are
+   any, the bank handlers are responsible for registering payment acceptation or refusal.
+    """,
+    "depends"       : ['account_voucher'],
+    "init_xml"      : [],
+    'data'          : [
+        'customer_payment_request_data.xml',
+        'customer_payment_request_view.xml',
+    ],
+    "demo_xml"      : [],
+    "installable"   : True,
+    "certificate"   : ''
+}

=== added file 'customer_payment_request/customer_payment_request.py'
--- customer_payment_request/customer_payment_request.py	1970-01-01 00:00:00 +0000
+++ customer_payment_request/customer_payment_request.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,188 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#    
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.  
+#
+##############################################################################
+
+import logging
+from datetime import date
+
+from openerp.osv import orm, fields
+from openerp.tools.translate import _
+
+import registry
+
+logger = logging.getLogger(__name__)
+
+class res_company_cprinfo(orm.Model):
+    _name = 'res.company.cprinfo'
+    _columns = {
+        'process_payments': fields.boolean(
+            string=u"Process Payments",
+            help=u"Process partners with an outstanding payment",
+        ),
+        'process_refunds': fields.boolean(
+            string=u"Process Refunds",
+            help=u"Process partners with an outstanding refund",
+        ),
+    }
+    _defaults = {
+        'process_payments': True,
+        'process_refunds': True,
+    }
+    
+    def name_get(self, cr, uid, ids, context=None):
+        return dict.fromkeys(ids, _("Customer Payment Request Info"))
+
+class res_company(orm.Model):
+    _inherit = 'res.company'
+    _columns = {
+        'customer_payment_request_info': fields.many2one('res.company.cprinfo', "Customer Payment Request Info"),
+    }
+    
+    def _default_customer_payment_request_info(self, cr, uid, context=None):
+        res_company_cprinfo = self.pool.get('res.company.cprinfo')
+        res = res_company_cprinfo.create(cr, uid, {}, context=context)
+        return res
+    
+    _defaults = {
+        'customer_payment_request_info': _default_customer_payment_request_info,
+    }
+
+class customer_payment_request(orm.Model):
+    _name = 'customer.payment.request'
+    
+    _columns = {
+        'bank_account_id': fields.many2one(
+            'res.partner.bank',
+            "Bank account the request was made at",
+        ),
+        'partner_id': fields.related('bank_account_id', 'partner_id'),
+        'amount': fields.float("Amount to charge"),
+        'batch_ref': fields.char(size=64),
+        'state': fields.selection([
+            ('waiting', "Waiting to be sent"),
+            ('sent', "Sent"),
+        ]),
+    }
+    
+    def send_customer_payment_requests(self, cr, uid, ids=None, context=None):
+        partner_pool = self.pool.get('res.partner')
+        company_pool = self.pool.get('res.company')
+        # TODO: support multi-company
+        company = company_pool.browse(cr, uid, 1)
+        cprinfo = company.customer_payment_request_info
+        partner_ids = partner_pool.search(cr, uid, [('company_id', '=', company.id)])
+        partners = partner_pool.browse(cr, uid, partner_ids)
+        
+        def should_charge(partner):
+            amount = p.credit - p.debit
+            if round(amount*100) == 0:
+                # Nothing to charge or refund
+                return False
+            if not cprinfo.process_payments and amount > 0:
+                return False
+            if not cprinfo.process_refunds and amount < 0:
+                return False
+            # Fetch the preferred payment type and see if it's a credit card. If it isn't, we
+            # don't process it here.
+            if not partner.bank_ids:
+                return False
+            if self.search(cr, uid, [('partner_id', '=', partner.id)]):
+                # We're already trying to charge this partner
+                return False
+            return True
+        
+        toprocess = [p for p in partners if should_charge(p)]
+        if not toprocess:
+            return True
+        
+        for p in toprocess:
+            bank_account = p.bank_ids[0]
+            amount = p.credit - p.debit
+            vals = {
+                'bank_account_id': bank_account.id,
+                'amount': amount,
+                'state': 'waiting',
+            }
+            self.create(cr, uid, vals)
+        
+        # Now that payment request records have been created, let's send them
+        self.send_waiting_requests(cr, uid)
+    
+    def send_waiting_requests(self, cr, uid, ids=None, context=None):
+        # ping our registered handlers to process them.
+        for handler_func in registry.SEND_PAYMENT_FUNCS:
+            cpr_ids = self.search(cr, uid, [('state', '=', 'waiting')])
+            if not cpr_ids:
+                # everything is processed!
+                break
+            cprs = self.browse(cr, uid, cpr_ids)
+            handler_func(self, cr, uid, cprs)
+        
+        cpr_ids = self.search(cr, uid, [('state', '=', 'waiting')])
+        if cpr_ids:
+            # We still have waiting requests! We have a problem because they should all have been
+            # handled. Eventually, we should have some kind of "problem review" UI, but for now, we
+            # just log a warning.
+            logger.warning("%d customer payment request(s) weren't processed by any handler", len(cpr_ids))
+    
+    def receive_customer_payment_response(self, cr, uid, ids=None, context=None):
+        for handler_func in registry.RECEIVE_PAYMENT_FUNCS:
+            # handler_func will call register_payment_acceptation or register_payment_refusal
+            handler_func(self, cr, uid)
+    
+    def register_payment_refusal(self, cr, uid, partner_id):
+        [cpr_id] = self.search(cr, uid, [('partner_id', '=', partner_id)])
+        cpr = self.browse(cr, uid, cpr_id)
+        partner = cpr.partner_id
+        if len(partner.bank_ids) > 1:
+            # We have multiple payment options, let's cycle through them
+            try:
+                index = [b.id for b in partner.bank_ids].index(cpr.bank_account_id.id) + 1
+            except ValueError:
+                # weird, our account isn't there. Maybe it has been removed. Let's go back to 0
+                index = 0
+            if index >= len(partner.bank_ids):
+                index = 0
+            cpr.write({'bank_account_id': partner.bank_ids[index].id})
+        cpr.write({'state': 'waiting'})
+    
+    def register_payment_acceptation(self, cr, uid, partner_id, amount, account_id, journal_id, leave_draft=False):
+        logger.info("Importing payment of amount %d for partner %d", amount, partner_id)
+        account_voucher_pool = self.pool.get('account.voucher')
+        account_voucher_line_pool = self.pool.get('account.voucher.line')
+        vals = {
+            'type': 'receipt',
+            'partner_id': partner_id,
+            'amount': amount,
+            'account_id': account_id,
+            'journal_id': journal_id,
+        }
+        voucher_id = account_voucher_pool.create(cr, uid, vals)
+        line_vals = account_voucher_pool.recompute_voucher_lines(
+            cr, uid, [voucher_id], partner_id, journal_id, amount, None, 'receipt', date.today()
+        )
+        all_lines = line_vals['value']['line_cr_ids'] + line_vals['value']['line_dr_ids']
+        for line in all_lines:
+            line['voucher_id'] = voucher_id
+            account_voucher_line_pool.create(cr, uid, line)
+        if not leave_draft:
+            account_voucher_pool.action_move_line_create(cr, uid, [voucher_id])
+        [cpr_id] = self.search(cr, uid, [('partner_id', '=', partner_id)])
+        self.unlink(cr, uid, cpr_id)

=== added file 'customer_payment_request/customer_payment_request_data.xml'
--- customer_payment_request/customer_payment_request_data.xml	1970-01-01 00:00:00 +0000
+++ customer_payment_request/customer_payment_request_data.xml	2013-09-11 12:29:57 +0000
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+    <data noupdate="1">
+        <record id="ir_cron_export_customer_payment_requests" model="ir.cron">
+            <field name="name">Export customer payment requests</field>
+            <field name="user_id" ref="base.user_root"/>
+            <field name="interval_number">1</field>
+            <field name="interval_type">hours</field>
+            <field name="numbercall">-1</field>
+            <field eval="False" name="active"/>
+            <field eval="False" name="doall"/>
+            <field eval="'customer.payment.request'" name="model"/>
+            <field eval="'send_customer_payment_requests'" name="function"/>
+            <field name="args">()</field>
+        </record>
+        
+        <record id="ir_cron_retry_failed_payment_requests" model="ir.cron">
+            <field name="name">Retry failed payment requests</field>
+            <field name="user_id" ref="base.user_root"/>
+            <field name="interval_number">1</field>
+            <field name="interval_type">hours</field>
+            <field name="numbercall">-1</field>
+            <field eval="False" name="active"/>
+            <field eval="False" name="doall"/>
+            <field eval="'customer.payment.request'" name="model"/>
+            <field eval="'send_waiting_requests'" name="function"/>
+            <field name="args">()</field>
+        </record>
+        
+        <record id="ir_cron_import_customer_payment_requests" model="ir.cron">
+            <field name="name">Import customer payment requests</field>
+            <field name="user_id" ref="base.user_root"/>
+            <field name="interval_number">1</field>
+            <field name="interval_type">hours</field>
+            <field name="numbercall">-1</field>
+            <field eval="False" name="active"/>
+            <field eval="False" name="doall"/>
+            <field eval="'customer.payment.request'" name="model"/>
+            <field eval="'receive_customer_payment_response'" name="function"/>
+            <field name="args">()</field>
+        </record>
+    </data>
+</openerp>
\ No newline at end of file

=== added file 'customer_payment_request/customer_payment_request_view.xml'
--- customer_payment_request/customer_payment_request_view.xml	1970-01-01 00:00:00 +0000
+++ customer_payment_request/customer_payment_request_view.xml	2013-09-11 12:29:57 +0000
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+    <data>
+        <record id="view_company_form" model="ir.ui.view">
+            <field name="name">res.company.form</field>
+            <field name="model">res.company</field>
+            <field name="inherit_id" ref="base.view_company_form"/>
+            <field name="arch" type="xml">
+                <xpath expr="//page[@string='General Information']/group/group" position="inside">
+                    <field name="customer_payment_request_info"/>
+                </xpath>
+            </field>
+        </record>
+    </data>
+
+</openerp>

=== added directory 'customer_payment_request/docs'
=== added file 'customer_payment_request/docs/Makefile'
--- customer_payment_request/docs/Makefile	1970-01-01 00:00:00 +0000
+++ customer_payment_request/docs/Makefile	2013-09-11 12:29:57 +0000
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OpenERPBankPayments.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OpenERPBankPayments.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/OpenERPBankPayments"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OpenERPBankPayments"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."

=== added file 'customer_payment_request/docs/conf.py'
--- customer_payment_request/docs/conf.py	1970-01-01 00:00:00 +0000
+++ customer_payment_request/docs/conf.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,242 @@
+# -*- coding: utf-8 -*-
+#
+# OpenERP Bank Payments documentation build configuration file, created by
+# sphinx-quickstart on Wed Sep  4 09:42:11 2013.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'OpenERP Bank Payments'
+copyright = u'2013, Savoir-faire Linux'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.0'
+# The full version, including alpha/beta/rc tags.
+release = '1.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'OpenERPBankPaymentsdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'OpenERPBankPayments.tex', u'OpenERP Bank Payments Documentation',
+   u'Savoir-faire Linux', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'openerpbankpayments', u'OpenERP Bank Payments Documentation',
+     [u'Savoir-faire Linux'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'OpenERPBankPayments', u'OpenERP Bank Payments Documentation',
+   u'Savoir-faire Linux', 'OpenERPBankPayments', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'

=== added file 'customer_payment_request/docs/design.rst'
--- customer_payment_request/docs/design.rst	1970-01-01 00:00:00 +0000
+++ customer_payment_request/docs/design.rst	2013-09-11 12:29:57 +0000
@@ -0,0 +1,59 @@
+Module Design
+=============
+
+Modules in this project are organised in two groups, the :ref:`workflow` group and the
+:ref:`protocol` group. Each of them is as loosely coupled as possible to allow mix-and-matching of
+any of them together. This loose coupling is achieved through a :ref:`registry` mechanism.
+
+.. _workflow:
+
+Workflow modules
+----------------
+
+The role of workflow modules is to determine what warrants a communication with banks and how. For
+example, you have the :ref:`customer_payment_request` module which selects all ``partner`` owing us
+any money and, for that amount, asks (through the :ref:`registry`) that any :ref:`protocol` module
+that knows how to process payments with the bank associated with each of these partners to process
+them. Then, it waits for an answer from these protocol modules to confirm the payment and then
+create a ``voucher`` for it.
+
+But that's just one workflow. There could be another workflow which prefers to process payments by
+invoices, and only under certain conditions, and this hypothetical workflow module could reuse.
+Another workflow module could prefer avoiding vouchers and work directly with move lines. Another
+workflow module could be integrated with shipping, stocks and inventory.
+
+.. _protocol:
+
+Protocol modules
+----------------
+
+Protocol modules implement communication protocols with banks. The kind of transaction they support
+can vary and they don't have to support every workflow modules in the project (sometimes, they just
+can't). They broadcast their capabilities to the workflow modules through the :ref:`registry`.
+
+For example, :ref:`payment_management_visa_desjardins` can send payment requests for partner account
+of the ``credit_card`` type (supplied by :ref:`encrypted_credit_card`) and thus fit with the
+:ref:`customer_payment_request` workflow, so it registers with it through
+``customer_payment_request.registry``.
+
+.. _registry:
+
+Registry
+--------
+
+Registry mechanism allows for loose coupling between :ref:`workflow` and :ref:`protocol`. Its
+implementation is very low-tech and is simply a list of registered functions hosted by the workflow
+modules. When a protocol wants to support a particular workflow, it simply has to call that
+workflow's registry function with adapter functions as parameter. These adapter functions then
+adapt the information supplied by a particular workflow to the requirements of the communication
+protocol.
+
+For example, we have :ref:`payment_management_visa_desjardins` which implements the communication
+protocol for credit card processing. It hooks with :ref:`customer_payment_request`, which supplies
+a list of partners and the amount they each owe. Fine, we create a communication file from these and
+we're done.
+
+Later, let's say someone wants to create a new workflow which, instead of being partner-based, is
+invoice-based. Someone wanting to re-use :ref:`payment_management_visa_desjardins` would simply have
+to implement an additional adapter function which generates communication files from invoices (and
+maybe add invoice-based metadata to the communication for reconciliation) and we're done.

=== added file 'customer_payment_request/docs/index.rst'
--- customer_payment_request/docs/index.rst	1970-01-01 00:00:00 +0000
+++ customer_payment_request/docs/index.rst	2013-09-11 12:29:57 +0000
@@ -0,0 +1,25 @@
+Welcome to OpenERP Bank Payments's documentation!
+=================================================
+
+This project is a collection of modules to help manage automatic payments with banks. For now, it
+only handles customer payment requests and interfaces only with `Desjardins`_, but the project is
+designed to loosely couple :ref:`workflows <workflow>` and :ref:`bank protocols <protocol>`, so it
+should be possible to add stuff like supplier payments and bank protocols.
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+   
+   installation
+   design
+   modules
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
+.. _Desjardins: http://www.desjardins.com/en/

=== added file 'customer_payment_request/docs/installation.rst'
--- customer_payment_request/docs/installation.rst	1970-01-01 00:00:00 +0000
+++ customer_payment_request/docs/installation.rst	2013-09-11 12:29:57 +0000
@@ -0,0 +1,27 @@
+Installation and usage
+======================
+
+To use this project, you have to install at least one :ref:`workflow` and one :ref:`protocol`.
+
+The first step to do that, of course, is like any OpenERP project: Add the ``addons`` folder to your
+addons path and update your module list.
+
+Then, choose a workflow that you want to install in your system. For example, if you want to send
+requests to your bank for customers who owe you money to pay you, you'll install
+:ref:`customer_payment_request`.
+
+Worflow modules usually come with scheduled tasks (export payments, import bank response, etc.)
+which themselves are inactive by default. You'll have to go to your schedule task configuration
+and activate those newly added tasks and set appropriate schedules for them.
+
+Then, you'll need to install a :ref:`bank protocol <protocol>` module and configure it. Usually,
+bank protocol configuration is a by-company configuration, so you'll be able to access them through
+your company details form.
+
+Once this is done, you should be good to go. How things work, usually, is that payments are
+automatically sent and received through scheduled jobs and everything, such as payment vouchers, is
+reconciled automatically. Some modules, however, may give you more latitude in this regard and you
+might, for example, be able to tell them that you want to manually confirm payment vouchers.
+
+You should refer to the documentation specific to the modules you've installed for any details or
+extra configuration.

=== added file 'customer_payment_request/docs/modules.rst'
--- customer_payment_request/docs/modules.rst	1970-01-01 00:00:00 +0000
+++ customer_payment_request/docs/modules.rst	2013-09-11 12:29:57 +0000
@@ -0,0 +1,100 @@
+Module List
+===========
+
+.. _customer_payment_request:
+
+customer_payment_request
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+A :ref:`workflow module <workflow>` allowing to send mass payment requests to each partner owing us
+money (having a positive value in its "Total Receivable" field). Regularly, through scheduled
+actions, it scans for these partners and, for each of these, create a ``customer.payment.request``
+line in ``waiting`` mode. 
+
+Then, it calls its ``send_payment_func()`` function. This function is called for each registered
+:ref:`protocol`, in order. The protocol modules are then expected to process every customer payment
+request that they can process (this usually depends on the type of the bank account associated with
+the partner) and then set the state of the request to ``sent``. If they can't process the request,
+they leave it to ``waiting``.
+
+Normally, when all protocols passed through the customer payment requests, all of them should be
+in ``sent`` mode. If there are any ``waiting`` leftovers, a warning will be logged on the server.
+
+Then, regularly, ``receive_payment_func()`` will be called to registered protocols to tell them to
+check for a response from the bank regarding the payment status. If there is any, the protocol
+modules are expected to call ``register_payment_acceptation()`` or ``register_payment_refusal()``,
+which will either remove the payment request and create a payment voucher or put it back in
+``waiting`` mode.
+
+The module will never create a payment request for the same partner at once to avoid
+double-charging.
+
+When ``register_payment_refusal()`` is called, this will enable a bank rotation mechanism and try
+again. So, if the partner has more than one registered bank account, it will retry, but with the
+next bank account in line. If it doesn't, the retry will be done on the same bank account.
+
+.. _payment_management_visa_desjardins:
+
+payment_management_visa_desjardins
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This :ref:`protocol module <protocol>` produces a text file in a format that, when sent to
+Desjardins, allows you to request payments from credit cards into your bank account.
+
+This module depends on :ref:`encrypted_credit_card` modules which supplies the ``credit_card``
+bank account type and manages encryption of credit card numbers (because it's illegal to hold
+credit card numbers in plain text into a database such as OpenERP's).
+
+The file that it produce, ``vdcoupon.txt`` can be sent to Desjardins through SSH with the help of
+``scripts/vdcoupon_watch_send.py`` and a response from Desjardins can be watched and managed with
+``scripts/vdcoupon_watch_recv.py``.
+
+This module supports the :ref:`customer_payment_request` workflow and processes all partners having
+a ``credit_card`` bank account.
+
+Using it requires Desjardins Merchant details to be set for your company, so you'll need to go fill
+it up after you've installed the module. Because this information is very similar to the one used by
+:ref:`payment_management_dd_desjardins`, there's a common module,
+:ref:`payment_management_desjardins` which defines these structures.
+
+.. _payment_management_dd_desjardins:
+
+payment_management_dd_desjardins
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This :ref:`protocol module <protocol>` produces a text file in a format that, when sent to
+Desjardins, allows you to request direct withdrawal from bank accounts.
+
+The file that it produce, ``ddcoupon.txt`` can be sent to Desjardins manually (for now).
+
+This module supports the :ref:`customer_payment_request` workflow and processes all partners having
+a ``bank`` bank account.
+
+Unlike credit card processing, direct withdrawal request have no automated response and are
+considered by the system to always succeed. For this reason payment requests are "accepted" right
+upon file creation and a voucher is created. For this reason, you might want to enable the
+"Leave vouchers as draft" option and manually confirm them.
+
+Nothing happens when the workflow module asks this protocol to poll for bank response because there
+aren't any.
+
+.. _payment_management_desjardins:
+
+payment_management_desjardins
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A helper module defining common preferences for :ref:`payment_management_visa_desjardins` and
+:ref:`payment_management_dd_desjardins`.
+
+.. _encrypted_credit_card:
+
+encrypted_credit_card
+^^^^^^^^^^^^^^^^^^^^^
+
+A helper module adding the ``credit_card`` bank type, doing credit card number validation and adding
+logic for credit card number encryption.
+
+Encryption is done asynchronously through RSA keypairs. Whenever a new credit card number is
+entered, it is encrypted with a public key (which is set in company preferences) and then, another
+server which is hopefully very secure holds the private key and decrypts them before sendin them to
+banks.

=== added file 'customer_payment_request/registry.py'
--- customer_payment_request/registry.py	1970-01-01 00:00:00 +0000
+++ customer_payment_request/registry.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,53 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#    
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.  
+#
+##############################################################################
+
+# def send_payment_func_prototype(cpr_obj, cr, uid, customer_payment_requests):
+#     batch_file = SomeBatchFile()
+#     processed = []
+#     for cpr in customer_payment_requests:
+#         if can_process_request(cpr):
+#             batch_file.add_request(cpr)
+#             processed.append(cpr)
+#     if batch_file.send_to_bank() == success:
+#         cpr_ids = [cpr.id for cpr in processed]
+#         cpr_obj.write(cr, uid, cpr_ids, {'state': 'sent'})
+
+SEND_PAYMENT_FUNCS = []
+
+# def receive_payment_func_prototype(cpr_obj, cr, uid):
+#     if not has_received_response_from_bank():
+#         return
+#     batch_response = parse_batch_response()
+#     account_id = fetch_account_id_from_bank_prefs()
+#     journal_id = fetch_journal_id_from_bank_prefs()
+#     for line in batch_response:
+#         if line.accepted:
+#             cpr_obj.register_payment_acceptation(cr, uid, line.partner_id, line.amount, account_id, journal_id)
+#         else:
+#             cpr_obj.register_payment_refusal(cr, uid, line.partner_id)
+
+RECEIVE_PAYMENT_FUNCS = []
+
+def register(send_func, receive_func):
+    if send_func is not None:
+        SEND_PAYMENT_FUNCS.append(send_func)
+    if receive_func is not None:
+        RECEIVE_PAYMENT_FUNCS.append(receive_func)

=== added directory 'encrypted_credit_card'
=== added file 'encrypted_credit_card/__init__.py'
--- encrypted_credit_card/__init__.py	1970-01-01 00:00:00 +0000
+++ encrypted_credit_card/__init__.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,1 @@
+import encrypted_credit_card
\ No newline at end of file

=== added file 'encrypted_credit_card/__openerp__.py'
--- encrypted_credit_card/__openerp__.py	1970-01-01 00:00:00 +0000
+++ encrypted_credit_card/__openerp__.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,64 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    "name"          : "Encrypted Credit Cards",
+    "version"       : "1.0",
+    "author"        : "Savoir-faire Linux",
+    "website"       : "http://www.savoirfairelinux.com";,
+    "category"      : "Accounting & Finance",
+    "description"   : """
+Adds a new "Credit Card" bank type.
+
+This new bank type stores credit card numbers in an encrypted form, using a public key
+stored in res.company.
+
+To comply with PCI-DSS, we never ever store the credit card number in the DB, so the bank
+type record's 'acc_number' field contains stuff like "XXXX-XXXX-XXXX-1234". The actual
+credit card number is stored in "encrypted_cc_number", encrypted with the company's public
+key.
+
+The encryption method used is RSA. Because we store the encrypted CC number in a char()
+field and that encrypted data is binary, we encode that encrypted CC number in base64.
+
+This way, someone with full access to the DB is still unable to extract CC number unless he
+also has access to the private key, which hopefully is stored elsewhere, in a very secure
+place.
+
+We don't do any decryption here. It's up to another process to have access to the private
+key and decrypt those numbers.
+
+This module requires PyCrypto ( https://pypi.python.org/pypi/pycrypto )
+
+    """,
+    "depends"       : ['sale'],
+    'external_dependencies': {
+        'python' : ['Crypto'],
+    },
+    "init_xml"      : [],
+    'data'          : [
+        'encrypted_credit_card_data.xml',
+        'encrypted_credit_card_view.xml',
+    ],
+    "demo_xml"      : [],
+    "installable"   : True,
+    "certificate"   : ''
+}

=== added file 'encrypted_credit_card/encrypted_credit_card.py'
--- encrypted_credit_card/encrypted_credit_card.py	1970-01-01 00:00:00 +0000
+++ encrypted_credit_card/encrypted_credit_card.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,216 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#    
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.  
+#
+##############################################################################
+
+# About encryption in this module
+#
+# The goal of this module is to store CC numbers in an encrypted way using an assymetric encryption
+# method (we use RSA). This way, someone with full access to our DB won't be able to decrypt our
+# CC numbers unless he also has access to the private key, which of course isn't stored in the BD.
+#
+# Because the output of PyCrypto.PublicKey.RSA.encrypt() is binary data and that we store the
+# encrypted key in a char field, we encode that encrypted data with base64.
+
+import re
+import binascii
+from datetime import date
+from Crypto.PublicKey import RSA
+
+from openerp.tools.translate import _
+from openerp.osv import orm, fields
+
+def is_credit_card_number_valid(credit_card_number):
+    """Credit card number validation according to the MODULO-10 algorithm.
+    """
+    str_card_number     = str(credit_card_number)
+    str_check_digit     = str_card_number[-1]
+    str_validation_vect = ""
+    str_result_vect     = []
+    result              = 0
+    next_closest_ten    = 10
+
+    # Make sure the credit card number consists of
+    # digits only
+    if not re.match(r"^[0-9]+$", str_card_number):
+        return False
+
+    # Build the validation vector '212121...'
+    for i in range(len(str_card_number)-1):
+        if i % 2 == 0:
+            str_validation_vect += '2'
+        else:
+            str_validation_vect += '1'
+
+    # Multiply each digit of the card number
+    # by the corresponding validation digit,
+    # except for the last digit
+    for i in range(len(str_validation_vect)):
+        res = int(str_card_number[i]) * int(str_validation_vect[i])
+        str_result_vect.append(res)
+
+    # Add the result of the above multiplication
+    # and consider a 2-digit number as 2 numbers
+    # of one digit
+    for number in str_result_vect:
+        if number < 10:
+            result += number
+        else:
+            str_number  = str(number)
+            num_1       = int(str_number[0])
+            num_2       = int(str_number[1])
+            result     += num_1 + num_2
+
+    # Compute the check digit and compare it
+    # with the last digit of the card number
+    while next_closest_ten < result:
+        next_closest_ten += 10
+
+    check_digit = next_closest_ten - result
+
+    if str(check_digit) == str_check_digit:
+        return True
+    else:
+        return False
+
+def fix_public_key(key):
+    # Copy/Pasting public key leads to formatting loss, and PyCrypto is sensitive on this matter.
+    # It wants all \n preserved, but in OpenERP's char field, those \n are going to be replaced
+    # with spaces. But don't try to naively replace spaces with newlines, because you're going to
+    # end up with BEGIN\nPUBLIC\KEY, which PyCrypto won't accept.
+    if key.strip().startswith('ssh-rsa'):
+        # This key is not of the type that starts with BEGIN PUBLIC KEY. Just return the stripped
+        # version
+        return key.strip()
+    stripped = re.sub(r'\s?-----[A-Z\s]+-----\s?', '', key)
+    stripped = stripped.replace(' ', '\n')
+    return '-----BEGIN PUBLIC KEY-----\n' + stripped + '\n-----END PUBLIC KEY-----'
+
+def encrypt_cc_number(cc_number, public_key):
+    key = RSA.importKey(fix_public_key(public_key))
+    encrypted_cc_number = key.encrypt(cc_number, 42)[0]
+    return binascii.b2a_base64(encrypted_cc_number).strip()
+
+def encrypt_cc_vals(partner_obj, vals):
+    if 'acc_number' not in vals:
+        # no acc_number, no problem
+        return
+    cc_number = vals['acc_number']
+    if cc_number.startswith('XXXX'):
+        # We're not actually changing the number, just remove it
+        del vals['acc_number']
+    elif is_credit_card_number_valid(cc_number):
+        # Ok, we're actually submitting a CC number here
+        # Never ever store CC number in clear
+        vals['acc_number'] = 'XXXX-XXXX-XXXX-' + cc_number[-4:]
+        vals['encrypted_cc_number'] = encrypt_cc_number(cc_number, partner_obj.company_id.cc_number_encrypt_key)
+
+class res_partner_bank(orm.Model):
+    _inherit = 'res.partner.bank'
+
+    _columns = {
+        # RSA-encrypted, bas64-encoded.
+        'encrypted_cc_number': fields.char("Encrypted Credit Card Number", size=1024),
+        'expiration_date': fields.char('Expiration date (YYMM)', size=4),
+    }
+
+    def check_credit_card_number(self, cr, uid, ids, context=None):
+        for bank_acc in self.browse(cr, uid, ids, context=context):
+            if bank_acc.state != 'credit_card':
+                continue
+            cc_number = bank_acc.acc_number
+            if cc_number.startswith('XXXX'):
+                # It's a hidden number, so we're not actually changing the encrypted CC number.
+                # Consider as valid
+                continue
+            if not is_credit_card_number_valid(cc_number):
+                return False
+        return True
+
+    def check_expiration_date(self, cr, uid, ids, context=None):
+        for bank_acc in self.browse(cr, uid, ids, context=context):
+            if bank_acc.state != 'credit_card':
+                continue
+            if not bank_acc.expiration_date:
+                return False
+            m = re.match(r"^([0-9]{2})([0-9]{2})$", bank_acc.expiration_date)
+            if m is None:
+                return False
+            year = int(m.group(1)) + 2000
+            month = int(m.group(2))
+            TODAY = date.today()
+            if year < TODAY.year:
+                return False
+            if not (1 <= month <= 12):
+                return False
+            if year == TODAY.year and month < TODAY.month:
+                return False
+        return True
+
+    def _construct_constraint_msg_card_number(self, cr, uid, ids, context=None):
+        return (_("Credit card number is invalid")), ()
+
+    def _construct_constraint_msg_expiration_date(self, cr, uid, ids, context=None):
+        return (_("Expiration date is invalid")), ()
+
+    _constraints = [
+        (check_credit_card_number, _construct_constraint_msg_card_number, ["acc_number"]),
+        (check_expiration_date, _construct_constraint_msg_expiration_date, ["expiration_date"]),
+    ]
+
+    def create(self, cr, uid, vals, context=None):
+        if vals.get('state') == 'credit_card':
+            partner_obj = self.pool.get('res.partner').browse(cr, uid, vals['partner_id'], context=context)
+            encrypt_cc_vals(partner_obj, vals)
+        return super(res_partner_bank, self).create(cr, uid, vals, context=context)
+
+    def write(self, cr, uid, ids, vals, context=None):
+        self_obj = self.browse(cr, uid, ids[0], context=context)
+        try:
+            state = vals['state']
+        except KeyError:
+            state = self_obj.state
+        if state == 'credit_card':
+            encrypt_cc_vals(self_obj.partner_id, vals)
+        return super(res_partner_bank, self).write(cr, uid, ids, vals, context=context)
+
+class res_company(orm.Model):
+    _inherit = 'res.company'
+
+    def _get_short_pubkey(self, cr, uid, ids, field_name, arg, context):
+        res = {}
+        for company in self.browse(cr, uid, ids, context=context):
+            pubkey = company.cc_number_encrypt_key
+            if pubkey:
+                res[company.id] = pubkey[:30] + "..."
+            else:
+                res[company.id] = ""
+        return res
+
+    def _set_short_pubkey(self, cr, uid, id, name, value, fnct_inv_arg, context):
+        if len(value) > 40:
+            # We only save the key if it's not the truncated value
+            self.write(cr, uid, id, {'cc_number_encrypt_key': value})
+
+    _columns = {
+        'cc_number_encrypt_key_short': fields.function(_get_short_pubkey, fnct_inv=_set_short_pubkey,
+            type='char', string='Credit Card Encryption Key'),
+        'cc_number_encrypt_key': fields.char('Credit Card Encryption Key', size=2048,
+            help="Public key with which to encrypt our credit card number before writing them to the DB"),
+    }

=== added file 'encrypted_credit_card/encrypted_credit_card_data.xml'
--- encrypted_credit_card/encrypted_credit_card_data.xml	1970-01-01 00:00:00 +0000
+++ encrypted_credit_card/encrypted_credit_card_data.xml	2013-09-11 12:29:57 +0000
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+    <data>
+        <record id="bank_credit_card" model="res.partner.bank.type">
+            <field name="name">Credit card Account</field>
+            <field name="code">credit_card</field>
+            <field name="format_layout">%(bank_name)s: CC %(acc_number)s - EXP %(expiration_date)s</field>
+        </record>
+    </data>
+
+</openerp>

=== added file 'encrypted_credit_card/encrypted_credit_card_view.xml'
--- encrypted_credit_card/encrypted_credit_card_view.xml	1970-01-01 00:00:00 +0000
+++ encrypted_credit_card/encrypted_credit_card_view.xml	2013-09-11 12:29:57 +0000
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+    <data>
+        <record id="view_partner_bank_form" model="ir.ui.view">
+            <field name="name">res.partner.bank.form</field>
+            <field name="model">res.partner.bank</field>
+            <field name="inherit_id" ref="base.view_partner_bank_form"/>
+            <field name="arch" type="xml">
+                    <field name="acc_number" position="after">
+                        <field name="expiration_date" attrs="{'invisible':[('state','!=','credit_card')], 'required':[('state','=','credit_card')]}" />
+                    </field>
+            </field>
+        </record>
+
+        <record id="view_company_form" model="ir.ui.view">
+            <field name="name">res.company.form</field>
+            <field name="model">res.company</field>
+            <field name="inherit_id" ref="base.view_company_form"/>
+            <field name="arch" type="xml">
+                <xpath expr="//field[@name='currency_id']" position="after">
+                    <field name="cc_number_encrypt_key_short"/>
+                </xpath>
+            </field>
+        </record>
+    </data>
+
+</openerp>

=== added directory 'encrypted_credit_card/i18n'
=== added file 'encrypted_credit_card/i18n/encrypted_credit_card.pot'
--- encrypted_credit_card/i18n/encrypted_credit_card.pot	1970-01-01 00:00:00 +0000
+++ encrypted_credit_card/i18n/encrypted_credit_card.pot	2013-09-11 12:29:57 +0000
@@ -0,0 +1,69 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* encrypted_credit_card
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-08-13 14:05+0000\n"
+"PO-Revision-Date: 2013-08-13 14:05+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: encrypted_credit_card
+#: field:res.partner.bank,encrypted_cc_number:0
+msgid "Encrypted Credit Card Number"
+msgstr ""
+
+#. module: encrypted_credit_card
+#: code:addons/encrypted_credit_card/encrypted_credit_card.py:170
+#, python-format
+msgid "Expiration date is invalid"
+msgstr ""
+
+#. module: encrypted_credit_card
+#: code:addons/encrypted_credit_card/encrypted_credit_card.py:167
+#, python-format
+msgid "Credit card number is invalid"
+msgstr ""
+
+#. module: encrypted_credit_card
+#: model:ir.model,name:encrypted_credit_card.model_res_partner_bank
+msgid "Bank Accounts"
+msgstr ""
+
+#. module: encrypted_credit_card
+#: model:ir.model,name:encrypted_credit_card.model_res_company
+msgid "Companies"
+msgstr ""
+
+#. module: encrypted_credit_card
+#: field:res.partner.bank,expiration_date:0
+msgid "Expiration date (YYMM)"
+msgstr ""
+
+#. module: encrypted_credit_card
+#: help:res.company,cc_number_encrypt_key:0
+msgid "Public key with which to encrypt our credit card number before writing them to the DB"
+msgstr ""
+
+#. module: encrypted_credit_card
+#: model:res.partner.bank.type,format_layout:encrypted_credit_card.bank_credit_card
+msgid "%(bank_name)s: IBAN %(acc_number)s - BIC %(expiration_date)s"
+msgstr ""
+
+#. module: encrypted_credit_card
+#: model:res.partner.bank.type,name:encrypted_credit_card.bank_credit_card
+msgid "Credit card Account"
+msgstr ""
+
+#. module: encrypted_credit_card
+#: field:res.company,cc_number_encrypt_key:0
+msgid "Credit Card Encryption Key"
+msgstr ""
+

=== added directory 'payment_management_dd_desjardins'
=== added file 'payment_management_dd_desjardins/__init__.py'
--- payment_management_dd_desjardins/__init__.py	1970-01-01 00:00:00 +0000
+++ payment_management_dd_desjardins/__init__.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,22 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import payment_management_dd_desjardins

=== added file 'payment_management_dd_desjardins/__openerp__.py'
--- payment_management_dd_desjardins/__openerp__.py	1970-01-01 00:00:00 +0000
+++ payment_management_dd_desjardins/__openerp__.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,40 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    "name"          : "Payment management module for desjardins Direct Deposit",
+    "version"       : "1.0",
+    "author"        : "Savoir-faire Linux",
+    "website"       : "http://www.savoirfairelinux.com";,
+    "category"      : "Accounting & Finance",
+    "description"   : """
+    """,
+    "depends"       : ['payment_management_desjardins'],
+    "init_xml"      : [],
+    "update_xml"    : [
+    ],
+    'data'          : [
+        'payment_management_dd_desjardins_view.xml',
+    ],
+    "demo_xml"      : [],
+    "installable"   : True,
+    "certificate"   : ''
+}

=== added directory 'payment_management_dd_desjardins/i18n'
=== added file 'payment_management_dd_desjardins/i18n/payment_management_dd_desjardins.pot'
--- payment_management_dd_desjardins/i18n/payment_management_dd_desjardins.pot	1970-01-01 00:00:00 +0000
+++ payment_management_dd_desjardins/i18n/payment_management_dd_desjardins.pot	2013-09-11 12:29:57 +0000
@@ -0,0 +1,27 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* payment_management_dd_desjardins
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-08-21 13:53+0000\n"
+"PO-Revision-Date: 2013-08-21 13:53+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: payment_management_dd_desjardins
+#: field:res.company,desjardins_dd_info:0
+msgid "Desjardins Direct Deposit Info"
+msgstr ""
+
+#. module: payment_management_dd_desjardins
+#: model:ir.model,name:payment_management_dd_desjardins.model_res_company
+msgid "Companies"
+msgstr ""
+

=== added file 'payment_management_dd_desjardins/payment_management_dd_desjardins.py'
--- payment_management_dd_desjardins/payment_management_dd_desjardins.py	1970-01-01 00:00:00 +0000
+++ payment_management_dd_desjardins/payment_management_dd_desjardins.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,109 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from __future__ import division
+
+import os
+import logging
+import codecs
+
+from openerp.osv import orm, fields
+
+from .record import get_cd_record, get_a_record, get_z_record
+
+_logger = logging.getLogger(__name__)
+
+class res_company(orm.Model):
+    _inherit = 'res.company'
+    _columns = {
+        'desjardins_dd_info': fields.many2one('res.company.desjardinsinfo', "Desjardins Direct Deposit Info"),
+    }
+    
+    def _default_desjardins_dd_info(self, cr, uid, context=None):
+        res_company_desjardinsinfo = self.pool.get('res.company.desjardinsinfo')
+        res = res_company_desjardinsinfo.create(cr, uid, {}, context=context)
+        return res
+    
+    _defaults = {
+        'desjardins_dd_info': _default_desjardins_dd_info,
+    }
+    
+def send_payment_func_dd_desjardins(cpr_obj, cr, uid, customer_payment_requests):
+    company_obj = cpr_obj.pool.get('res.company')
+    # TODO: support multi-company
+    company = company_obj.browse(cr, uid, 1)
+    desjinfo = company.desjardins_dd_info
+    directory = desjinfo.workdir
+    
+    if not desjinfo.is_complete():
+        return False
+    
+    toprocess = [cpr for cpr in customer_payment_requests if cpr.bank_account_id.state == 'bank']
+    if not toprocess:
+        return False
+    total_number_of_credit = 0
+    total_number_of_debit = 0
+    total_value_of_credit = 0
+    total_value_of_debit = 0
+    lines = []
+    
+    # Type A record
+    a_record = get_a_record(len(lines)+1, desjinfo)
+    lines.append(a_record)
+
+    # Type C/D records
+    for cpr in toprocess:
+        bank_account = cpr.bank_account_id
+        partner = bank_account.partner_id
+        # The amount is in cents
+        amount = round(cpr.amount * 100)
+        if amount > 0:
+            total_number_of_debit += 1
+            total_value_of_debit += amount
+        else:
+            total_number_of_credit += 1
+            total_value_of_credit += amount
+        cd_record = get_cd_record(
+            len(lines)+1, company, amount, bank_account, partner
+        )
+        lines.append(cd_record)
+        # With Desjardins RD, there's no response file, so we directly create the payment voucher
+        cpr_obj.register_payment_acceptation(
+            cr, uid, partner.id, cpr.amount, desjinfo.account_id.id,
+            desjinfo.bank_account_id.journal_id.id, leave_draft=desjinfo.leave_vouchers_draft
+        )
+
+    # Type Z record
+    z_record = get_z_record(
+        len(lines)+1, desjinfo,
+        total_number_of_debit, total_value_of_debit, 
+        total_number_of_credit, total_value_of_credit
+    )
+    lines.append(z_record)
+    with codecs.open(os.path.join(directory, 'ddcoupon.txt'), 'wt', encoding='latin-1') as fp:
+        fp.write(u'\n'.join(lines))
+    desjinfo.increase_file_number()
+
+try:
+    from customer_payment_request import registry
+    registry.register(send_payment_func_dd_desjardins, None)
+except ImportError:
+    pass

=== added file 'payment_management_dd_desjardins/payment_management_dd_desjardins_view.xml'
--- payment_management_dd_desjardins/payment_management_dd_desjardins_view.xml	1970-01-01 00:00:00 +0000
+++ payment_management_dd_desjardins/payment_management_dd_desjardins_view.xml	2013-09-11 12:29:57 +0000
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+    <data>
+        <record id="view_company_form" model="ir.ui.view">
+            <field name="name">res.company.form</field>
+            <field name="model">res.company</field>
+            <field name="inherit_id" ref="base.view_company_form"/>
+            <field name="arch" type="xml">
+                <xpath expr="//page[@string='General Information']/group/group" position="inside">
+                    <field name="desjardins_dd_info"/>
+                </xpath>
+            </field>
+        </record>
+        
+        <record id="view_partner_bank_form" model="ir.ui.view">
+            <field name="name">res.partner.bank.form</field>
+            <field name="model">res.partner.bank</field>
+            <field name="inherit_id" ref="base.view_partner_bank_form"/>
+            <field name="arch" type="xml">
+                <xpath expr="//field[@name='bank_bic']" position="attributes">
+                    <attribute name="attrs">{'required':[('state', 'in', ['iban', 'bank'])]}</attribute>
+                </xpath>
+            </field>
+        </record>
+    </data>
+
+</openerp>

=== added file 'payment_management_dd_desjardins/record.py'
--- payment_management_dd_desjardins/record.py	1970-01-01 00:00:00 +0000
+++ payment_management_dd_desjardins/record.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,94 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from __future__ import division, unicode_literals
+
+from datetime import date, datetime
+
+from payment_management_desjardins.record import yyyddd
+
+def ljust(s, num, filler):
+    return s[:num].ljust(num, filler)
+
+def rjust(s, num, filler):
+    return s[:num].rjust(num, filler)
+
+def get_a_record(seq, desjinfo):
+    return ''.join([
+        'A',
+        '%09d' % seq,
+        desjinfo.originator_id[:10].upper().ljust(10, ' '),
+        '%04d' % desjinfo.file_creation_number,
+        yyyddd(datetime.now()),
+        '81510',
+        ' ' * 20,
+        'CAD',
+        ' ' * 1406,
+    ])
+
+def get_cd_record(seq, company, amount, bank_account, partner):
+    desjinfo = company.desjardins_dd_info
+    # amount is an integer representing cents
+    if amount > 0:
+        firstchar = 'D'
+        txn_type = desjinfo.txn_code_debit
+    else:
+        firstchar = 'C'
+        txn_type = desjinfo.txn_code_credit
+        amount  = abs(amount)
+    return ''.join([
+        firstchar,
+        '%09d' % seq,
+        ljust(desjinfo.originator_id.upper(), 10, ' '),
+        '%04d' % desjinfo.file_creation_number,
+        txn_type,
+        '%010d' % amount,
+        yyyddd(date.today()),
+        rjust(bank_account.bank_bic, 9, '0'),
+        ljust(bank_account.acc_number, 12, ' '),
+        '0' * 22,
+        '0' * 3,
+        ljust(company.name, 15, ' '),
+        ljust(partner.name, 30, ' '),
+        ljust(company.name, 30, ' '),
+        ljust(desjinfo.originator_id.upper(), 10, ' '),
+        '%019d' % partner.id,
+        rjust(desjinfo.bank_account_id.bank_bic, 9, '0'),
+        ljust(desjinfo.bank_account_id.acc_number, 12, ' '),
+        ' ' * 15,
+        ' ' * 22,
+        ' ' * 2,
+        '0' * 11,
+    ])
+
+def get_z_record(seq, desjinfo, num_debit, total_debit, num_credit, total_credit):
+    return ''.join([
+        'Z',
+        '%09d' % seq,
+        desjinfo.originator_id[:10].upper().ljust(10, ' '),
+        '%04d' % desjinfo.file_creation_number,
+        '%014d' % total_debit,
+        '%08d' % num_debit,
+        '%014d' % total_credit,
+        '%08d' % num_credit,
+        '0' * 44,
+        ' ' * 1352,
+    ])

=== added directory 'payment_management_desjardins'
=== added file 'payment_management_desjardins/__init__.py'
--- payment_management_desjardins/__init__.py	1970-01-01 00:00:00 +0000
+++ payment_management_desjardins/__init__.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,22 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import payment_management_desjardins

=== added file 'payment_management_desjardins/__openerp__.py'
--- payment_management_desjardins/__openerp__.py	1970-01-01 00:00:00 +0000
+++ payment_management_desjardins/__openerp__.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,40 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    "name"          : "Payment management module for Desjardins",
+    "version"       : "1.0",
+    "author"        : "Savoir-faire Linux",
+    "website"       : "http://www.savoirfairelinux.com";,
+    "category"      : "Accounting & Finance",
+    "description"   : """
+    """,
+    "depends"       : ['account_voucher', 'sale'],
+    "init_xml"      : [],
+    "update_xml"    : [
+    ],
+    'data'          : [
+        'payment_management_desjardins_view.xml',
+    ],
+    "demo_xml"      : [],
+    "installable"   : True,
+    "certificate"   : ''
+}

=== added directory 'payment_management_desjardins/i18n'
=== added file 'payment_management_desjardins/i18n/payment_management_desjardins.pot'
--- payment_management_desjardins/i18n/payment_management_desjardins.pot	1970-01-01 00:00:00 +0000
+++ payment_management_desjardins/i18n/payment_management_desjardins.pot	2013-09-11 12:29:57 +0000
@@ -0,0 +1,105 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* payment_management_desjardins
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-08-21 13:52+0000\n"
+"PO-Revision-Date: 2013-08-21 13:52+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: payment_management_desjardins
+#: model:ir.model,name:payment_management_desjardins.model_res_company_desjardinsinfo
+msgid "res.company.desjardinsinfo"
+msgstr ""
+
+#. module: payment_management_desjardins
+#: help:res.company.desjardinsinfo,merchant_number:0
+msgid "Merchant number assigned by DCS. Must be numeric."
+msgstr ""
+
+#. module: payment_management_desjardins
+#: field:res.company.desjardinsinfo,merchant_number:0
+msgid "Merchant number for Desjardins"
+msgstr ""
+
+#. module: payment_management_desjardins
+#: code:addons/payment_management_desjardins/payment_management_desjardins.py:88
+#, python-format
+msgid "Specified directory %s is not a valid directory. Desjardins payment exportation is aborted."
+msgstr ""
+
+#. module: payment_management_desjardins
+#: field:res.company.desjardinsinfo,workdir:0
+msgid "Storage Directory"
+msgstr ""
+
+#. module: payment_management_desjardins
+#: help:res.company.desjardinsinfo,originator_id:0
+msgid "Name of the directory into which the 'vdcoupon.txt' files will be placed."
+msgstr ""
+
+#. module: payment_management_desjardins
+#: field:res.company.desjardinsinfo,bank_account_id:0
+msgid "Your Desjardins bank account"
+msgstr ""
+
+#. module: payment_management_desjardins
+#: code:addons/payment_management_desjardins/payment_management_desjardins.py:78
+#, python-format
+msgid "Desjardins Merchant Info"
+msgstr ""
+
+#. module: payment_management_desjardins
+#: field:res.company.desjardinsinfo,originator_id:0
+msgid "Originator ID"
+msgstr ""
+
+#. module: payment_management_desjardins
+#: field:res.company.desjardinsinfo,txn_code_credit:0
+msgid "Transaction code for Credit"
+msgstr ""
+
+#. module: payment_management_desjardins
+#: view:res.company.desjardinsinfo:0
+msgid "Desjardins harmonization info"
+msgstr ""
+
+#. module: payment_management_desjardins
+#: field:res.company.desjardinsinfo,txn_code_debit:0
+msgid "Transaction code for Debit"
+msgstr ""
+
+#. module: payment_management_desjardins
+#: field:res.company.desjardinsinfo,file_creation_number:0
+msgid "Next file creation number value"
+msgstr ""
+
+#. module: payment_management_desjardins
+#: help:res.company.desjardinsinfo,file_creation_number:0
+msgid "File sequence number, to be incremented by 1 for each file sent without error."
+msgstr ""
+
+#. module: payment_management_desjardins
+#: field:res.company.desjardinsinfo,account_id:0
+msgid "Account for Desjardins payments"
+msgstr ""
+
+#. module: payment_management_desjardins
+#: help:res.company.desjardinsinfo,workdir:0
+msgid "Specify a directory to export to and import from."
+msgstr ""
+
+#. module: payment_management_desjardins
+#: code:addons/payment_management_desjardins/payment_management_desjardins.py:94
+#, python-format
+msgid "Could not export Desjardins payments because at least one of the following company parameters is missing : (merchant_number, file_creation_number, originator_id)."
+msgstr ""
+

=== added file 'payment_management_desjardins/payment_management_desjardins.py'
--- payment_management_desjardins/payment_management_desjardins.py	1970-01-01 00:00:00 +0000
+++ payment_management_desjardins/payment_management_desjardins.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,97 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from __future__ import division
+
+import os
+import logging
+
+from openerp.tools.translate import _
+from openerp.osv import orm, fields
+
+_logger = logging.getLogger(__name__)
+
+class res_company_desjardinsinfo(orm.Model):
+    _name = 'res.company.desjardinsinfo'
+    _columns = {
+        'file_creation_number': fields.integer(
+            'Next file creation number value',
+            help="File sequence number, to be incremented by 1 for each file sent without error."
+        ),
+        'originator_id': fields.char(
+            'Originator ID',
+            size=10,
+            help="Name of the directory into which the 'vdcoupon.txt' files will be placed."
+        ),
+        'workdir': fields.char(
+            'Storage Directory',
+            size=255,
+            help="Specify a directory to export to and import from."
+        ),
+        'bank_account_id':fields.many2one(
+            'res.partner.bank',
+            'Your Desjardins bank account',
+        ),
+        'account_id':fields.many2one(
+            'account.account',
+            'Account for Desjardins payments',
+        ),
+        'txn_code_debit': fields.char(
+            'Transaction code for Debit',
+            size=3,
+        ),
+        'txn_code_credit': fields.char(
+            'Transaction code for Credit',
+            size=3,
+        ),
+        'leave_vouchers_draft': fields.boolean(
+            string=u"Leave vouchers as draft",
+            help=u"When creating vouchers, don't validate them, leave then in a draft state",
+        )
+    }
+    
+    _defaults = {
+        'workdir': '/tmp',
+        'file_creation_number' : 1,
+    }
+    
+    def name_get(self, cr, uid, ids, context=None):
+        return dict.fromkeys(ids, _("Desjardins Merchant Info"))
+    
+    def is_complete(self, cr, uid, ids, context=None):
+        """Returns whether our info record is complete.
+        
+        Logs a warning if not.
+        """
+        id = ids[0] if isinstance(ids, list) else ids
+        desjinfo = self.browse(cr, uid, id)
+        if not os.path.isdir(desjinfo.workdir):
+            msg = _("Specified directory %s is not a valid directory. Desjardins payment exportation is aborted.")
+            _logger.warning(msg, desjinfo.workdir)
+            return False
+        
+        return True
+    
+    def increase_file_number(self, cr, uid, ids, context=None):
+        id = ids[0] if isinstance(ids, list) else ids
+        desjinfo = self.browse(cr, uid, id)
+        self.write(cr, uid, id, {'file_creation_number': desjinfo.file_creation_number+1})
+    

=== added file 'payment_management_desjardins/payment_management_desjardins_view.xml'
--- payment_management_desjardins/payment_management_desjardins_view.xml	1970-01-01 00:00:00 +0000
+++ payment_management_desjardins/payment_management_desjardins_view.xml	2013-09-11 12:29:57 +0000
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+    <data>
+        <record id="view_desjardinsinfo_form" model="ir.ui.view">
+            <field name="name">res.company.desjardinsinfo.form</field>
+            <field name="model">res.company.desjardinsinfo</field>
+            <field name="arch" type="xml">
+                <form string="Desjardins harmonization info">
+                    <field name="file_creation_number" required="1"/>
+                    <field name="originator_id" required="1"/>
+                    <field name="workdir" required="1"/>
+                    <field name="bank_account_id" domain="[('company_id', '!=', False)]" widget="selection" />
+                    <field name="account_id"/>
+                    <field name="txn_code_debit" required="1"/>
+                    <field name="txn_code_credit" required="1"/>
+                    <field name="leave_vouchers_draft"/>
+                </form>
+            </field>
+        </record>
+    </data>
+
+</openerp>

=== added file 'payment_management_desjardins/record.py'
--- payment_management_desjardins/record.py	1970-01-01 00:00:00 +0000
+++ payment_management_desjardins/record.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,29 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from __future__ import division, unicode_literals
+
+from datetime import date, datetime
+
+def yyyddd(d):
+    three_digit_year = d.strftime('%Y')[1:4]
+    day_of_year = d.strftime('%j')
+    return three_digit_year + day_of_year

=== added directory 'payment_management_visa_desjardins'
=== added file 'payment_management_visa_desjardins/__init__.py'
--- payment_management_visa_desjardins/__init__.py	1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/__init__.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,22 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import payment_management_visa_desjardins

=== added file 'payment_management_visa_desjardins/__openerp__.py'
--- payment_management_visa_desjardins/__openerp__.py	1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/__openerp__.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,44 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    "name"          : "Payment management module for visa desjardins",
+    "version"       : "1.0",
+    "author"        : "Savoir-faire Linux",
+    "website"       : "http://www.savoirfairelinux.com";,
+    "category"      : "Accounting & Finance",
+    "description"   : """
+        This module generates 'vdcoupon.txt' files from invoices.
+    """,
+    "depends"       : [
+        'payment_management_desjardins',
+        'encrypted_credit_card',
+    ],
+    "init_xml"      : [],
+    "update_xml"    : [
+    ],
+    'data'          : [
+        'payment_management_visa_desjardins_view.xml',
+    ],
+    "demo_xml"      : [],
+    "installable"   : True,
+    "certificate"   : ''
+}

=== added directory 'payment_management_visa_desjardins/i18n'
=== added file 'payment_management_visa_desjardins/i18n/payment_management_visa_desjardins.pot'
--- payment_management_visa_desjardins/i18n/payment_management_visa_desjardins.pot	1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/i18n/payment_management_visa_desjardins.pot	2013-09-11 12:29:57 +0000
@@ -0,0 +1,27 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* payment_management_visa_desjardins
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-08-21 13:54+0000\n"
+"PO-Revision-Date: 2013-08-21 13:54+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: payment_management_visa_desjardins
+#: model:ir.model,name:payment_management_visa_desjardins.model_res_company
+msgid "Companies"
+msgstr ""
+
+#. module: payment_management_visa_desjardins
+#: field:res.company,desjardins_cc_info:0
+msgid "Visa Desjardins Info"
+msgstr ""
+

=== added file 'payment_management_visa_desjardins/payment_management_visa_desjardins.py'
--- payment_management_visa_desjardins/payment_management_visa_desjardins.py	1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/payment_management_visa_desjardins.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,175 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+# ABOUT THIS MODULE AND ENCRYPTION
+#
+# This module doesn't handle plain text CC numbers, it only has access to encrypted CC number
+# (managed by encrypted_credit_card). However, the text file it produces, "vdcoupons", wants plain
+# text CC numbers.
+#
+# Because the vdcoupons file we produce isn't going to be sent directly to Desjardins, we don't need
+# it to be exactly correct, but we also want to minimise the work that the external process, which
+# is going to decrypt the CC numbers before sending it to Desjardins. Therefore, we build the rest
+# of the vdcoupons file exactly as it's going to be sent. Thus, all the external process is going to
+# have to do is to decrypt and replace.
+
+from __future__ import division
+
+import os
+import re
+import logging
+import codecs
+
+from openerp.osv import orm, fields
+
+from .record import get_g_record, get_a_record, get_h_record, get_z_record
+
+_logger = logging.getLogger(__name__)
+
+class res_company_desjardinsinfo_visa(orm.Model):
+    _name = 'res.company.desjardinsinfo.visa'
+    _inherit = 'res.company.desjardinsinfo'
+    _columns = {
+        'merchant_number': fields.char(
+            'Merchant number for Desjardins',
+            size=8,
+            help="Merchant number assigned by DCS. Must be numeric."
+        ),
+    }
+
+class res_company(orm.Model):
+    _inherit = 'res.company'
+    _columns = {
+        'desjardins_cc_info': fields.many2one('res.company.desjardinsinfo.visa', "Visa Desjardins Info"),
+    }
+    
+    def _default_desjardins_cc_info(self, cr, uid, context):
+        res_company_desjardinsinfo = self.pool.get('res.company.desjardinsinfo.visa')
+        vals = {
+            'txn_code_debit': '555',
+            'txn_code_credit': '506',
+        }
+        res = res_company_desjardinsinfo.create(cr, uid, vals, context=context)
+        return res
+    
+    _defaults = {
+        'desjardins_cc_info': _default_desjardins_cc_info,
+    }
+
+def send_payment_func_visa_desjardins(cpr_obj, cr, uid, customer_payment_requests):
+    company_obj = cpr_obj.pool.get('res.company')
+    # TODO: support multi-company
+    company = company_obj.browse(cr, uid, 1)
+    desjinfo = company.desjardins_cc_info
+    directory = desjinfo.workdir
+    
+    if not desjinfo.is_complete():
+        return False
+    
+    toprocess = [cpr for cpr in customer_payment_requests if cpr.bank_account_id.state == 'credit_card']
+    if not toprocess:
+        return False
+    total_number_of_credit = 0
+    total_number_of_debit = 0
+    total_value_of_credit = 0
+    total_value_of_debit = 0
+    lines = []
+    
+    # Type A record
+    a_record = get_a_record(len(lines)+1, company.desjardins_cc_info)
+    lines.append(a_record)
+
+    # Type G records
+    for cpr in toprocess:
+        bank_account = cpr.bank_account_id
+        partner = bank_account.partner_id
+        # The amount is in cents
+        amount = round(cpr.amount * 100)
+        if amount > 0:
+            total_number_of_debit += 1
+            total_value_of_debit += amount
+        else:
+            amount  = abs(amount)
+            total_number_of_credit += 1
+            total_value_of_credit += amount
+        cc_number = '[RSA]%s[/RSA]' % bank_account.encrypted_cc_number.strip()
+        g_record = get_g_record(
+            len(lines)+1, company, amount, cc_number, bank_account.expiration_date, partner.id
+        )
+        lines.append(g_record)
+        cpr.write({'state': 'sent', 'batch_ref': 'visa_desjardins'})
+    
+    # Type H record
+    h_record = get_h_record(
+        len(lines)+1, company.desjardins_cc_info,
+        total_number_of_debit, total_value_of_debit, 
+        total_number_of_credit, total_value_of_credit
+    )
+    lines.append(h_record)
+
+    # Type Z record
+    z_record = get_z_record(
+        len(lines)+1, company.desjardins_cc_info,
+        total_number_of_debit, total_value_of_debit, 
+        total_number_of_credit, total_value_of_credit
+    )
+    lines.append(z_record)
+    with codecs.open(os.path.join(directory, 'vdcoupon.txt'), 'wt', encoding='latin-1') as fp:
+        fp.write(u'\n'.join(lines))
+    desjinfo.increase_file_number()
+
+def receive_payment_func_visa_desjardins(cpr_obj, cr, uid):
+    company_obj = cpr_obj.pool.get('res.company')
+    company = company_obj.browse(cr, uid, 1)
+    desjinfo = company.desjardins_cc_info
+    directory = desjinfo.workdir
+    account_id = desjinfo.account_id.id
+    journal_id = desjinfo.bank_account_id.journal_id.id
+    
+    filenames = os.listdir(directory)
+    filenames = [fn for fn in filenames if re.match(r"\d{8}-\d{4}-vdcoupon\.acc", fn)]
+    if not filenames:
+        # Nothing to process
+        return
+    for filename in filenames:
+        filepath = os.path.join(directory, filename)
+        lines = open(filepath, 'rt').readlines()
+        for line in lines:
+            if not line.startswith('G'):
+                continue
+            partner_id = int(line[118:118+19])
+            amount = int(line[27:27+10]) / 100
+            cpr_obj.register_payment_acceptation(
+                cr, uid, partner_id, amount, account_id, journal_id,
+                leave_draft=desjinfo.leave_vouchers_draft
+            )
+        os.rename(filepath, filepath[:-3] + 'imported')
+    
+    # Any CPR with a "visa_desjardins" batch_ref can be considered as rejected
+    rejected_cpr_ids = cpr_obj.search(cr, uid, [('batch_ref', '=', 'visa_desjardins'), ('state', '=', 'sent')])
+    for cpr in cpr_obj.browse(cr, uid, rejected_cpr_ids):
+        cpr_obj.register_payment_refusal(cr, uid, cpr.partner_id.id)
+
+try:
+    from customer_payment_request import registry
+    registry.register(send_payment_func_visa_desjardins, receive_payment_func_visa_desjardins)
+except ImportError:
+    pass

=== added file 'payment_management_visa_desjardins/payment_management_visa_desjardins_view.xml'
--- payment_management_visa_desjardins/payment_management_visa_desjardins_view.xml	1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/payment_management_visa_desjardins_view.xml	2013-09-11 12:29:57 +0000
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+    <data>
+        <!-- I've tried inheriting the view from res.company.desjardinsinfo, but it didn't work. -->
+        <record id="view_desjardinsinfo_visa_form" model="ir.ui.view">
+            <field name="name">res.company.desjardinsinfo.visa.form</field>
+            <field name="model">res.company.desjardinsinfo.visa</field>
+            <field name="arch" type="xml">
+                <form string="Desjardins harmonization info">
+                    <field name="merchant_number" required="1"/>
+                    <field name="file_creation_number" required="1"/>
+                    <field name="originator_id" required="1"/>
+                    <field name="workdir" required="1"/>
+                    <field name="bank_account_id" domain="[('company_id', '!=', False)]" widget="selection" />
+                    <field name="account_id"/>
+                    <field name="txn_code_debit" required="1"/>
+                    <field name="txn_code_credit" required="1"/>
+                    <field name="leave_vouchers_draft"/>
+                </form>
+            </field>
+        </record>
+        
+        <record id="view_company_form" model="ir.ui.view">
+            <field name="name">res.company.form</field>
+            <field name="model">res.company</field>
+            <field name="inherit_id" ref="base.view_company_form"/>
+            <field name="arch" type="xml">
+                <xpath expr="//page[@string='General Information']/group/group" position="inside">
+                    <field name="desjardins_cc_info"/>
+                </xpath>
+            </field>
+        </record>
+    </data>
+
+</openerp>

=== added file 'payment_management_visa_desjardins/record.py'
--- payment_management_visa_desjardins/record.py	1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/record.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,108 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from __future__ import division, unicode_literals
+
+from datetime import date, datetime
+
+from payment_management_desjardins.record import yyyddd
+
+def get_a_record(seq, desjinfo):
+    return ''.join([
+        'A',
+        '%09d' % seq,
+        desjinfo.originator_id[:10].upper().ljust(10, ' '),
+        '%04d' % desjinfo.file_creation_number,
+        yyyddd(datetime.now()),
+        '81510',
+        ' ' * 1429,
+    ])
+
+def get_h_record(seq, desjinfo, num_debit, total_debit, num_credit, total_credit):
+    return ''.join([
+        'H',
+        '%09d' % seq,
+        desjinfo.originator_id[:10].upper().ljust(10, ' '),
+        '%04d' % desjinfo.file_creation_number,
+        '%014d' % total_debit,
+        '%08d' % num_debit,
+        '%014d' % total_credit,
+        '%08d' % num_credit,
+        '1',
+        '0' * 10,
+        '0' * 4,
+        desjinfo.merchant_number[:8].rjust(8, '0'),
+        ' ' * 1373,
+    ])
+
+def get_z_record(seq, desjinfo, num_debit, total_debit, num_credit, total_credit):
+    return ''.join([
+        'Z',
+        '%09d' % seq,
+        desjinfo.originator_id[:10].upper().ljust(10, ' '),
+        '%04d' % desjinfo.file_creation_number,
+        '%014d' % total_debit,
+        '%08d' % num_debit,
+        '%014d' % total_credit,
+        '%08d' % num_credit,
+        '1',
+        '0' * 10,
+        '0' * 4,
+        ' ' * 1381,
+    ])
+
+def get_g_record(seq, company, amount, cc_number, cc_expiration, partner_ref):
+    desjinfo = company.desjardins_cc_info
+    # amount is an integer representing cents
+    if amount > 0:
+        txn_type = desjinfo.txn_code_debit
+    else:
+        txn_type = desjinfo.txn_code_credit
+        amount  = abs(amount)
+    return ''.join([
+        'G',                                   # Alpha 1
+        '%09d' % seq,                          # Num 9
+        desjinfo.originator_id[:10].upper().ljust(10, ' '), # Alpha 10
+        '%04d' % desjinfo.file_creation_number,# Num 4
+        txn_type,                              # Num 3
+        '%010d' % amount,                      # Num 10
+        date.today().strftime("%m%d%y"),       # Num 6
+        cc_number.rjust(19, '0'),
+        '0' * 2,                               # Alpha 2
+        ' ' * 2,                               # Alpha 2
+        ' ' * 2,                               # Alpha 2
+        ' ',                                   # Alpha 1
+        ' ',                                   # Alpha 1
+        desjinfo.merchant_number[:8].rjust(8, '0'), # Num 8
+        company.name[:25].ljust(25, ' '),      # Alpha 25
+        (company.city or '')[:13].ljust(13, ' '), # Alpha 13
+        company.state_id.code or '  ',         # Alpha 2
+        '%019d' % partner_ref,                 # Num 19
+        '0' * 6,                               # Alpha 6
+        '0' * 9,                               # Alpha 9
+        '0' * 4,                               # Num 4
+        '0' * 4,                               # Num 4
+        ' ' * 5,                               # Alpha 5
+        '0',                                   # Num 1
+        '0' * 8,                               # Alpha 8
+        cc_expiration,                         # Alpha 4
+        ' ' * 26,                              # Alpha 26
+    ])

=== added directory 'payment_management_visa_desjardins/scripts'
=== added file 'payment_management_visa_desjardins/scripts/vdcoupon_watch_recv.py'
--- payment_management_visa_desjardins/scripts/vdcoupon_watch_recv.py	1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/scripts/vdcoupon_watch_recv.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,78 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#    
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.  
+#
+##############################################################################
+
+import argparse
+import time
+import os.path as op
+from datetime import datetime
+import re
+
+import paramiko
+
+def parse_options():
+    parser = argparse.ArgumentParser(description='Wait for vdcoupon.acc from Desjardins, remove CC numbers and timestamp it.')
+    parser.add_argument('dest_folder', help="Folder where vdcoupon is sent.")
+    parser.add_argument('ssh_cred', help="Desjardins SSH creds (user@host:path).")
+    return parser.parse_args()
+
+def process_file(sftp, source_path, dest_folder):
+    source_fp = sftp.open(source_path, 'rt')
+    lines = source_fp.readlines()
+    newlines = []
+    for line in lines:
+        if line.startswith('G'):
+            chars = list(line)
+            # credit card numbers are the 19 characters starting at index 1525
+            chars[43:43+19] = '0' * 19
+            line = ''.join(chars)
+        newlines.append(line)
+    source_fp.close()
+    timestamp = datetime.now().strftime('%Y%m%d-%H%M')
+    result_filename = op.join(dest_folder, '%s-vdcoupon.acc' % timestamp)
+    with open(result_filename, 'wt') as result_fp:
+        result_fp.writelines(newlines)
+    sftp.remove(source_path)
+
+def watch_forever(options):
+    m = re.match(r"(.+)@([^:]+):?(.*)", options.ssh_cred)
+    username = m.group(1)
+    hostname = m.group(2)
+    sshpath = m.group(3)
+    ssh = paramiko.SSHClient()
+    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+    ssh.load_system_host_keys()
+    ssh.connect(hostname, 22, username)
+    sftp = ssh.open_sftp()
+    while True:
+        if 'vdcoupon.acc' in sftp.listdir(sshpath):
+            print "File found! Processing"
+            source_path = op.join(sshpath, 'vdcoupon.acc')
+            process_file(sftp, source_path, options.dest_folder)
+            print "Process done, file deleted"
+        time.sleep(10)
+
+def main():
+    options = parse_options()
+    print "Starting to watch for vdcoupon.acc with dest {}...".format(options.dest_folder)
+    watch_forever(options)
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file

=== added file 'payment_management_visa_desjardins/scripts/vdcoupon_watch_send.py'
--- payment_management_visa_desjardins/scripts/vdcoupon_watch_send.py	1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/scripts/vdcoupon_watch_send.py	2013-09-11 12:29:57 +0000
@@ -0,0 +1,84 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#    
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.  
+#
+##############################################################################
+
+import argparse
+import time
+import os
+import os.path as op
+import re
+import binascii
+
+import paramiko
+from Crypto.PublicKey import RSA
+
+def parse_options():
+    parser = argparse.ArgumentParser(description='Wait for vdcoupon.txt from OpenERP, decrypt it and send it to Desjardins.')
+    parser.add_argument('watched_folder', help="Folder where vdcoupon is sent.")
+    parser.add_argument('private_key', help="Private RSA key to decrypt CC numbers with.")
+    parser.add_argument('ssh_cred', help="Desjardins SSH creds (user@host:path).")
+    return parser.parse_args()
+
+def process_file(watched_file, private_key):
+    def repl(match):
+        encrypted_cc_base64 = match.group(1)
+        encrypted_cc = binascii.a2b_base64(encrypted_cc_base64)
+        decrypted_cc = private_key.decrypt(encrypted_cc)
+        return decrypted_cc.zfill(19)
+
+    with open(watched_file, 'rt') as source_fp:
+        contents = source_fp.read()
+        contents = re.sub(r"\[RSA\](.+?)\[/RSA\]", repl, contents)
+    return contents
+
+def send_contents(hostname, username, sshpath, contents):
+    ssh = paramiko.SSHClient()
+    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+    ssh.load_system_host_keys()
+    ssh.connect(hostname, 22, username)
+    sftp = ssh.open_sftp()
+    fp = sftp.open(op.join(sshpath, 'vdcoupon.txt'), 'wt')
+    fp.write(contents)
+    fp.close()
+
+def watch_forever(options):
+    watched_file = op.join(options.watched_folder, 'vdcoupon.txt')
+    with open(options.private_key, 'rt') as key_fp:
+        private_key = RSA.importKey(key_fp.read())
+    m = re.match(r"(.+)@([^:]+):?(.*)", options.ssh_cred)
+    username = m.group(1)
+    hostname = m.group(2)
+    sshpath = m.group(3)
+    while True:
+        if op.exists(watched_file):
+            print "File found! Processing"
+            contents = process_file(watched_file, private_key)
+            send_contents(hostname, username, sshpath, contents)
+            os.remove(watched_file)
+            print "Process done, file deleted"
+        time.sleep(10)
+
+def main():
+    options = parse_options()
+    print "Starting to watch for vdcoupon.txt in {}...".format(options.watched_folder)
+    watch_forever(options)
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file


Follow ups