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

Proposed by Virgil Dupras
Status: Work in progress
Proposed branch: lp:~savoirfairelinux-openerp/banking-addons/loose-coupling
Merge into: lp:banking-addons/bank-statement-reconcile-70
Diff against target: 2820 lines (+2616/-0)
38 files modified
customer_payment_request/__init__.py (+2/-0)
customer_payment_request/__openerp__.py (+71/-0)
customer_payment_request/customer_payment_request.py (+220/-0)
customer_payment_request/customer_payment_request_data.xml (+43/-0)
customer_payment_request/customer_payment_request_view.xml (+16/-0)
customer_payment_request/docs/Makefile (+153/-0)
customer_payment_request/docs/conf.py (+242/-0)
customer_payment_request/docs/design.rst (+59/-0)
customer_payment_request/docs/index.rst (+25/-0)
customer_payment_request/docs/installation.rst (+27/-0)
customer_payment_request/docs/modules.rst (+100/-0)
customer_payment_request/registry.py (+53/-0)
encrypted_credit_card/__init__.py (+1/-0)
encrypted_credit_card/__openerp__.py (+64/-0)
encrypted_credit_card/encrypted_credit_card.py (+216/-0)
encrypted_credit_card/encrypted_credit_card_data.xml (+11/-0)
encrypted_credit_card/encrypted_credit_card_view.xml (+27/-0)
encrypted_credit_card/i18n/encrypted_credit_card.pot (+69/-0)
payment_management_dd_desjardins/__init__.py (+22/-0)
payment_management_dd_desjardins/__openerp__.py (+40/-0)
payment_management_dd_desjardins/i18n/payment_management_dd_desjardins.pot (+27/-0)
payment_management_dd_desjardins/payment_management_dd_desjardins.py (+113/-0)
payment_management_dd_desjardins/payment_management_dd_desjardins_view.xml (+27/-0)
payment_management_dd_desjardins/record.py (+94/-0)
payment_management_desjardins/__init__.py (+22/-0)
payment_management_desjardins/__openerp__.py (+40/-0)
payment_management_desjardins/i18n/payment_management_desjardins.pot (+105/-0)
payment_management_desjardins/payment_management_desjardins.py (+97/-0)
payment_management_desjardins/payment_management_desjardins_view.xml (+22/-0)
payment_management_desjardins/record.py (+29/-0)
payment_management_visa_desjardins/__init__.py (+22/-0)
payment_management_visa_desjardins/__openerp__.py (+44/-0)
payment_management_visa_desjardins/i18n/payment_management_visa_desjardins.pot (+27/-0)
payment_management_visa_desjardins/payment_management_visa_desjardins.py (+181/-0)
payment_management_visa_desjardins/payment_management_visa_desjardins_view.xml (+35/-0)
payment_management_visa_desjardins/record.py (+108/-0)
payment_management_visa_desjardins/scripts/vdcoupon_watch_recv.py (+78/-0)
payment_management_visa_desjardins/scripts/vdcoupon_watch_send.py (+84/-0)
To merge this branch: bzr merge lp:~savoirfairelinux-openerp/banking-addons/loose-coupling
Reviewer Review Type Date Requested Status
Guewen Baconnier @ Camptocamp code review, no test Needs Fixing
Review via email: mp+185033@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Serpent Consulting Services (serpent-consulting-services) wrote :

Some quick observations, which might be useful.

1. 'auto_install' in __openerp__.py
2. def _default_customer_payment_request_info can be written in one line.
3. 'state' field should be kept readonly!
4. some methods you write need to pass context within(create, write, etc.)

Thanks.

Revision history for this message
Virgil Dupras (hsoft) wrote :

Thanks for the review. I'll commit a change for #4 shortly (context passing), but I'm a bit puzzled by other points.

1. By auto_install, you mean explicitly setting it to False, right? I didn't know it was considered good practice.
2. Why do you think one line is preferable to three? Readability?
3. I don't understand why 'state' should be read-only. Isn't this attribute relevant only in views? There's no view for that model.

Revision history for this message
Serpent Consulting Services (serpent-consulting-services) wrote :

Virgil,

1. This is a convention since v7. Earlier it was active:False
2. Readability + less usage of variables.
3. 'State' field has its own usage in OpenERP. Normally, they are not kept editable by end user.

Thanks.

Revision history for this message
Virgil Dupras (hsoft) wrote :

Alright, thanks for the explanations. I guess I'll make the state field read-only, even if I don't really get the point of it since there's not view for the model, but if it's a convention...

There's only #2 which I don't plan to address since it's a purely subjective matter. I'm a big fan of "Sparse is better than dense" and I find 3 sparse lines more readable than 1 dense line.

Revision history for this message
Serpent Consulting Services (serpent-consulting-services) wrote :

Sure Virgil,my friend!

Thank you for the reply.

Revision history for this message
Serpent Consulting Services (serpent-consulting-services) wrote :

Just looked more in details and found:

1. 'partner_id': fields.related('bank_account_id', 'partner_id'), Should better have 'relation','string','type' attributes.
2. 'batch_ref': fields.char(size=64) should have a string.
3. 'state' too missed string.
4. customer.payment.request model should have atleast one mandatory field.
5. Majority of your functions do not have a return statement. Should have return True atleast.

Thanks.

Revision history for this message
Ronald Portier (Therp) (rportier1962) wrote :

Just to say I support Virgil on #2, and sparse better then dense. In my experience this also makes not only for better reading and comprehension, but also for much easier debugging.

And I do not see the point of adding return True to functions where this does not have any meaning:
if a function contains (or detects) errors, it should throw an exception. No exception thrown means function did the work it was supposed to do.

5. By Virgil Dupras

Corrected a few problems with the code, namely that context wasn't passed over to every ORM method call and that field definitions weren't complete.

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

> And I do not see the point of adding return True to functions where this does
> not have any meaning:
> if a function contains (or detects) errors, it should throw an exception. No
> exception thrown means function did the work it was supposed to do.

That's related to the XML/RPC API of OpenERP.
OpenERP considers that all the public methods are accessible from the XML/RPC service, all public methods should have a return value that is valid for XML/RPC. As XML/RPC forbids None values, usually a True / False value is returned.

I see 2 figures:
 - if a method should be accessible from XML/RPC, it should never return None
 - if not, it should be declared private (prefixed with _) and does not need to return anything if it does not make sense.

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

Nice piece of work!

Some concerns:

l.192ff I still see calls to the ORM methods without propagation of the context
l.199,1668 instead of round, I think that you should use res_currency.is_zero()

235 + for handler_func in registry.SEND_PAYMENT_FUNCS:
I don't know if this can be an issue here but you have to be cautious with this global registry mechanism: you always have to consider that OpenERP may import the python code from uninstalled modules. Meaning that you will end up with entries in your registry that are not part of an installed module.

241 + handler_func(self, cr, uid, cprs, context=context)
If I understand this line, you'll call the send method for each handler registered, so if you have handler functions for 3 banks, you'll do a call to the 3 banks? Couldn't it be a problem?

1643 + company = company_obj.browse(cr, uid, 1, context=context)
1 should not be hard-coded, at least use the xmlid, ideally, get it from the user

review: Needs Fixing (code review, no test)
Revision history for this message
Virgil Dupras (hsoft) wrote :

Thanks Guewen for your explanations and tricks. I'm glad to finally know of that return value thing. A further fix is under way.

Yes, each handler is called for each bank. Normally, it's not a problem because each handler handle the payment they are able to handle and once that happen, the handler is supposed to flag the payment request as "sent" (only payment in "waiting" mode are sent to handlers). So double charging isn't supposed to happen.

Of course, if a handler misbehave and doesn't correctly set the payment request's state to "sent", then it can happen, but it would be a bug in the handler.

As for the possible import bug, yeah, I saw in C2C's new connector framework's comments that such thing can happen, but I'm not sure that the possibility of that happening is enough to warrant a complexification of the import mechanism. Do you think that this possibility is a deal breaker?

6. By Virgil Dupras

Further correction of the code following a review, namely:

* context passing which were still missing
* stop hardcoding company id
* ensure that public methods don't return None
* use of res_currency.is_zero() instead of round(amount*100) == 0

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

Thanks for the changes!

> Yes, each handler is called for each bank. Normally, it's not a problem
> because each handler handle the payment they are able to handle and once that
> happen, the handler is supposed to flag the payment request as "sent" (only
> payment in "waiting" mode are sent to handlers). So double charging isn't
> supposed to happen.

Still a concern for me or I didn't understood something:

If I install payment_management_visa_desjardins, it will send all the payment whose state is 'credit_card' according to

    toprocess = [cpr for cpr in customer_payment_requests if cpr.bank_account_id.state == 'credit_card']

But what happens if want some of them to be sent to desjardins and some of them to be sent to another bank?

> As for the possible import bug, yeah, I saw in C2C's new connector framework's
> comments that such thing can happen, but I'm not sure that the possibility of
> that happening is enough to warrant a complexification of the import
> mechanism. Do you think that this possibility is a deal breaker?

It happens at least when you install a module, then uninstall it.
I can't figure out if such a situation is a deal breaker for your module or not.

Revision history for this message
Pedro Manuel Baeza (pedro.baeza) wrote :

This project is now hosted on https://github.com/OCA/bank-statement-reconcile. Please move your proposal there. This guide may help you https://github.com/OCA/maintainers-tools/wiki/How-to-move-a-Merge-Proposal-to-GitHub

Unmerged revisions

6. By Virgil Dupras

Further correction of the code following a review, namely:

* context passing which were still missing
* stop hardcoding company id
* ensure that public methods don't return None
* use of res_currency.is_zero() instead of round(amount*100) == 0

5. By Virgil Dupras

Corrected a few problems with the code, namely that context wasn't passed over to every ORM method call and that field definitions weren't complete.

4. By Virgil Dupras

[IMP] Added sphinx docs for bank payment modules

3. By Virgil Dupras

[IMP] Added scripts to send and receive vdcoupon files to/from Desjardins

2. By Virgil Dupras

[IMP] Many improvements and fixes.

1. By Virgil Dupras

Initial commit

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'customer_payment_request'
2=== added file 'customer_payment_request/__init__.py'
3--- customer_payment_request/__init__.py 1970-01-01 00:00:00 +0000
4+++ customer_payment_request/__init__.py 2013-09-17 15:32:05 +0000
5@@ -0,0 +1,2 @@
6+import customer_payment_request
7+import registry
8
9=== added file 'customer_payment_request/__openerp__.py'
10--- customer_payment_request/__openerp__.py 1970-01-01 00:00:00 +0000
11+++ customer_payment_request/__openerp__.py 2013-09-17 15:32:05 +0000
12@@ -0,0 +1,71 @@
13+# -*- encoding: utf-8 -*-
14+##############################################################################
15+#
16+# OpenERP, Open Source Management Solution
17+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
18+#
19+# This program is free software: you can redistribute it and/or modify
20+# it under the terms of the GNU General Public License as
21+# published by the Free Software Foundation, either version 3 of the
22+# License, or (at your option) any later version.
23+#
24+# This program is distributed in the hope that it will be useful,
25+# but WITHOUT ANY WARRANTY; without even the implied warranty of
26+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27+# GNU General Public License for more details.
28+#
29+# You should have received a copy of the GNU General Public License
30+# along with this program. If not, see <http://www.gnu.org/licenses/>.
31+#
32+##############################################################################
33+
34+{
35+ "name" : "Customer Payment Requests",
36+ "version" : "1.0",
37+ "author" : "Savoir-faire Linux",
38+ "website" : "http://www.savoirfairelinux.com",
39+ "category" : "Accounting & Finance",
40+ "description" : """
41+Deals with mass payment requests
42+--------------------------------
43+
44+When dealing with mass payments, we typically have a bunch of customers that owe us money and
45+we want to charge their credit card, or bank account, or whatever. We have that information
46+in one or more res.partner.bank record.
47+
48+The workflow here is that for every partner owing us money, we create a payment request in
49+"waiting" mode, associating that payment with the first res.partner.bank associated with that
50+partner.
51+
52+Then, we notify our registered payment handlers and they handle the payment request when they
53+can handle the bank account associated with the request. The payment is then put in "sent" mode
54+so that it isn't sent again until we receive the banks' responses.
55+
56+Then, we wait for the responses from banks by periodically polling for it. Upon receiving those
57+responses, the bank handlers calls register_payment_refusal() or register_payment_acceptation().
58+
59+If accepted, the payment request is deleted and a validated voucher is created for the
60+customer payment.
61+
62+If refused, the payment request is put back in waiting mode. If the partner has more than one
63+bank account, we set the request's bank to the next in line.
64+
65+This module registers 3 cron jobs:
66+
67+1. send_customer_payment_requests(): Create a payment request for each partner owing us money and
68+ tell bank handlers to send those requests.
69+2. send_waiting_requests(): Retry every payment requests in "waiting" mode (being in this mode
70+ because the previous payment attempt failed).
71+3. receive_customer_payment_response(): Poll bank handlers for responses from banks. If there are
72+ any, the bank handlers are responsible for registering payment acceptation or refusal.
73+ """,
74+ "depends" : ['account_voucher'],
75+ "init_xml" : [],
76+ 'data' : [
77+ 'customer_payment_request_data.xml',
78+ 'customer_payment_request_view.xml',
79+ ],
80+ "demo_xml" : [],
81+ "installable" : True,
82+ "certificate" : ''
83+}
84
85=== added file 'customer_payment_request/customer_payment_request.py'
86--- customer_payment_request/customer_payment_request.py 1970-01-01 00:00:00 +0000
87+++ customer_payment_request/customer_payment_request.py 2013-09-17 15:32:05 +0000
88@@ -0,0 +1,220 @@
89+# -*- encoding: utf-8 -*-
90+##############################################################################
91+#
92+# OpenERP, Open Source Management Solution
93+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
94+#
95+# This program is free software: you can redistribute it and/or modify
96+# it under the terms of the GNU Affero General Public License as
97+# published by the Free Software Foundation, either version 3 of the
98+# License, or (at your option) any later version.
99+#
100+# This program is distributed in the hope that it will be useful,
101+# but WITHOUT ANY WARRANTY; without even the implied warranty of
102+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
103+# GNU Affero General Public License for more details.
104+#
105+# You should have received a copy of the GNU Affero General Public License
106+# along with this program. If not, see <http://www.gnu.org/licenses/>.
107+#
108+##############################################################################
109+
110+import logging
111+from datetime import date
112+
113+from openerp.osv import orm, fields
114+from openerp.tools.translate import _
115+
116+import registry
117+
118+logger = logging.getLogger(__name__)
119+
120+class res_company_cprinfo(orm.Model):
121+ _name = 'res.company.cprinfo'
122+ _columns = {
123+ 'process_payments': fields.boolean(
124+ string=u"Process Payments",
125+ help=u"Process partners with an outstanding payment",
126+ ),
127+ 'process_refunds': fields.boolean(
128+ string=u"Process Refunds",
129+ help=u"Process partners with an outstanding refund",
130+ ),
131+ }
132+ _defaults = {
133+ 'process_payments': True,
134+ 'process_refunds': True,
135+ }
136+
137+ def name_get(self, cr, uid, ids, context=None):
138+ return dict.fromkeys(ids, _("Customer Payment Request Info"))
139+
140+class res_company(orm.Model):
141+ _inherit = 'res.company'
142+ _columns = {
143+ 'customer_payment_request_info': fields.many2one('res.company.cprinfo', "Customer Payment Request Info"),
144+ }
145+
146+ def _default_customer_payment_request_info(self, cr, uid, context=None):
147+ res_company_cprinfo = self.pool.get('res.company.cprinfo')
148+ res = res_company_cprinfo.create(cr, uid, {}, context=context)
149+ return res
150+
151+ _defaults = {
152+ 'customer_payment_request_info': _default_customer_payment_request_info,
153+ }
154+
155+ def active_company(self, cr, uid, context=None):
156+ """Returns the company attached to our logged user.
157+ """
158+ res_users = self.pool.get('res.users')
159+ active_user = res_users.browse(cr, uid, uid, context=context)
160+ return active_user.company_id
161+
162+class customer_payment_request(orm.Model):
163+ _name = 'customer.payment.request'
164+
165+ _columns = {
166+ 'bank_account_id': fields.many2one(
167+ 'res.partner.bank',
168+ required=True,
169+ string="Bank account the request was made at",
170+ ),
171+ 'partner_id': fields.related(
172+ 'bank_account_id',
173+ 'partner_id',
174+ obj='res.partner',
175+ type='many2one',
176+ required=True,
177+ string="Customer to charge",
178+ ),
179+ 'amount': fields.float(string="Amount to charge", required=True),
180+ 'batch_ref': fields.char(
181+ size=64,
182+ string="Batch payment reference",
183+ ),
184+ 'state': fields.selection(
185+ [
186+ ('waiting', "Waiting to be sent"),
187+ ('sent', "Sent"),
188+ ],
189+ readonly=True,
190+ required=True,
191+ string="Payment status",
192+ ),
193+ }
194+
195+ def send_customer_payment_requests(self, cr, uid, ids=None, context=None):
196+ partner_pool = self.pool.get('res.partner')
197+ company_pool = self.pool.get('res.company')
198+ currency_pool = self.pool.get('res.currency')
199+ company = company_pool.active_company(cr, uid, context=context)
200+ cprinfo = company.customer_payment_request_info
201+ search_domain = [('company_id', '=', company.id)]
202+ partner_ids = partner_pool.search(cr, uid, search_domain, context=context)
203+ partners = partner_pool.browse(cr, uid, partner_ids, context=context)
204+
205+ def should_charge(partner):
206+ amount = p.credit - p.debit
207+ if currency_pool.is_zero(cr, uid, company.currency_id, amount):
208+ # Nothing to charge or refund
209+ return False
210+ if not cprinfo.process_payments and amount > 0:
211+ return False
212+ if not cprinfo.process_refunds and amount < 0:
213+ return False
214+ # Fetch the preferred payment type and see if it's a credit card. If it isn't, we
215+ # don't process it here.
216+ if not partner.bank_ids:
217+ return False
218+ search_domain = [('partner_id', '=', partner.id)]
219+ if self.search(cr, uid, search_domain, context=context):
220+ # We're already trying to charge this partner
221+ return False
222+ return True
223+
224+ toprocess = [p for p in partners if should_charge(p)]
225+ if not toprocess:
226+ return True
227+
228+ for p in toprocess:
229+ bank_account = p.bank_ids[0]
230+ amount = p.credit - p.debit
231+ vals = {
232+ 'bank_account_id': bank_account.id,
233+ 'amount': amount,
234+ 'state': 'waiting',
235+ }
236+ self.create(cr, uid, vals, context=context)
237+
238+ # Now that payment request records have been created, let's send them
239+ self.send_waiting_requests(cr, uid, context=context)
240+ return True
241+
242+ def send_waiting_requests(self, cr, uid, ids=None, context=None):
243+ # ping our registered handlers to process them.
244+ for handler_func in registry.SEND_PAYMENT_FUNCS:
245+ search_domain = [('state', '=', 'waiting')]
246+ cpr_ids = self.search(cr, uid, search_domain, context=context)
247+ if not cpr_ids:
248+ # everything is processed!
249+ break
250+ cprs = self.browse(cr, uid, cpr_ids, context=context)
251+ handler_func(self, cr, uid, cprs, context=context)
252+
253+ cpr_ids = self.search(cr, uid, [('state', '=', 'waiting')], context=context)
254+ if cpr_ids:
255+ # We still have waiting requests! We have a problem because they should all have been
256+ # handled. Eventually, we should have some kind of "problem review" UI, but for now, we
257+ # just log a warning.
258+ logger.warning("%d customer payment request(s) weren't processed by any handler", len(cpr_ids))
259+ return True
260+
261+ def receive_customer_payment_response(self, cr, uid, ids=None, context=None):
262+ for handler_func in registry.RECEIVE_PAYMENT_FUNCS:
263+ # handler_func will call register_payment_acceptation or register_payment_refusal
264+ handler_func(self, cr, uid, context=context)
265+ return True
266+
267+ def register_payment_refusal(self, cr, uid, partner_id, context=None):
268+ [cpr_id] = self.search(cr, uid, [('partner_id', '=', partner_id)])
269+ cpr = self.browse(cr, uid, cpr_id, context=context)
270+ partner = cpr.partner_id
271+ if len(partner.bank_ids) > 1:
272+ # We have multiple payment options, let's cycle through them
273+ try:
274+ index = [b.id for b in partner.bank_ids].index(cpr.bank_account_id.id) + 1
275+ except ValueError:
276+ # weird, our account isn't there. Maybe it has been removed. Let's go back to 0
277+ index = 0
278+ if index >= len(partner.bank_ids):
279+ index = 0
280+ cpr.write({'bank_account_id': partner.bank_ids[index].id}, context=context)
281+ cpr.write({'state': 'waiting'}, context=context)
282+ return True
283+
284+ def register_payment_acceptation(self, cr, uid, partner_id, amount, account_id, journal_id, leave_draft=False, context=None):
285+ logger.info("Importing payment of amount %d for partner %d", amount, partner_id)
286+ account_voucher_pool = self.pool.get('account.voucher')
287+ account_voucher_line_pool = self.pool.get('account.voucher.line')
288+ vals = {
289+ 'type': 'receipt',
290+ 'partner_id': partner_id,
291+ 'amount': amount,
292+ 'account_id': account_id,
293+ 'journal_id': journal_id,
294+ }
295+ voucher_id = account_voucher_pool.create(cr, uid, vals, context=context)
296+ line_vals = account_voucher_pool.recompute_voucher_lines(
297+ cr, uid, [voucher_id], partner_id, journal_id, amount, None, 'receipt', date.today(),
298+ context=context
299+ )
300+ all_lines = line_vals['value']['line_cr_ids'] + line_vals['value']['line_dr_ids']
301+ for line in all_lines:
302+ line['voucher_id'] = voucher_id
303+ account_voucher_line_pool.create(cr, uid, line, context=context)
304+ if not leave_draft:
305+ account_voucher_pool.action_move_line_create(cr, uid, [voucher_id], context=context)
306+ [cpr_id] = self.search(cr, uid, [('partner_id', '=', partner_id)], context=context)
307+ self.unlink(cr, uid, cpr_id)
308+ return True
309
310=== added file 'customer_payment_request/customer_payment_request_data.xml'
311--- customer_payment_request/customer_payment_request_data.xml 1970-01-01 00:00:00 +0000
312+++ customer_payment_request/customer_payment_request_data.xml 2013-09-17 15:32:05 +0000
313@@ -0,0 +1,43 @@
314+<?xml version="1.0" encoding="UTF-8"?>
315+<openerp>
316+ <data noupdate="1">
317+ <record id="ir_cron_export_customer_payment_requests" model="ir.cron">
318+ <field name="name">Export customer payment requests</field>
319+ <field name="user_id" ref="base.user_root"/>
320+ <field name="interval_number">1</field>
321+ <field name="interval_type">hours</field>
322+ <field name="numbercall">-1</field>
323+ <field eval="False" name="active"/>
324+ <field eval="False" name="doall"/>
325+ <field eval="'customer.payment.request'" name="model"/>
326+ <field eval="'send_customer_payment_requests'" name="function"/>
327+ <field name="args">()</field>
328+ </record>
329+
330+ <record id="ir_cron_retry_failed_payment_requests" model="ir.cron">
331+ <field name="name">Retry failed payment requests</field>
332+ <field name="user_id" ref="base.user_root"/>
333+ <field name="interval_number">1</field>
334+ <field name="interval_type">hours</field>
335+ <field name="numbercall">-1</field>
336+ <field eval="False" name="active"/>
337+ <field eval="False" name="doall"/>
338+ <field eval="'customer.payment.request'" name="model"/>
339+ <field eval="'send_waiting_requests'" name="function"/>
340+ <field name="args">()</field>
341+ </record>
342+
343+ <record id="ir_cron_import_customer_payment_requests" model="ir.cron">
344+ <field name="name">Import customer payment requests</field>
345+ <field name="user_id" ref="base.user_root"/>
346+ <field name="interval_number">1</field>
347+ <field name="interval_type">hours</field>
348+ <field name="numbercall">-1</field>
349+ <field eval="False" name="active"/>
350+ <field eval="False" name="doall"/>
351+ <field eval="'customer.payment.request'" name="model"/>
352+ <field eval="'receive_customer_payment_response'" name="function"/>
353+ <field name="args">()</field>
354+ </record>
355+ </data>
356+</openerp>
357\ No newline at end of file
358
359=== added file 'customer_payment_request/customer_payment_request_view.xml'
360--- customer_payment_request/customer_payment_request_view.xml 1970-01-01 00:00:00 +0000
361+++ customer_payment_request/customer_payment_request_view.xml 2013-09-17 15:32:05 +0000
362@@ -0,0 +1,16 @@
363+<?xml version="1.0" encoding="UTF-8"?>
364+<openerp>
365+ <data>
366+ <record id="view_company_form" model="ir.ui.view">
367+ <field name="name">res.company.form</field>
368+ <field name="model">res.company</field>
369+ <field name="inherit_id" ref="base.view_company_form"/>
370+ <field name="arch" type="xml">
371+ <xpath expr="//page[@string='General Information']/group/group" position="inside">
372+ <field name="customer_payment_request_info"/>
373+ </xpath>
374+ </field>
375+ </record>
376+ </data>
377+
378+</openerp>
379
380=== added directory 'customer_payment_request/docs'
381=== added file 'customer_payment_request/docs/Makefile'
382--- customer_payment_request/docs/Makefile 1970-01-01 00:00:00 +0000
383+++ customer_payment_request/docs/Makefile 2013-09-17 15:32:05 +0000
384@@ -0,0 +1,153 @@
385+# Makefile for Sphinx documentation
386+#
387+
388+# You can set these variables from the command line.
389+SPHINXOPTS =
390+SPHINXBUILD = sphinx-build
391+PAPER =
392+BUILDDIR = _build
393+
394+# Internal variables.
395+PAPEROPT_a4 = -D latex_paper_size=a4
396+PAPEROPT_letter = -D latex_paper_size=letter
397+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
398+# the i18n builder cannot share the environment and doctrees with the others
399+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
400+
401+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
402+
403+help:
404+ @echo "Please use \`make <target>' where <target> is one of"
405+ @echo " html to make standalone HTML files"
406+ @echo " dirhtml to make HTML files named index.html in directories"
407+ @echo " singlehtml to make a single large HTML file"
408+ @echo " pickle to make pickle files"
409+ @echo " json to make JSON files"
410+ @echo " htmlhelp to make HTML files and a HTML help project"
411+ @echo " qthelp to make HTML files and a qthelp project"
412+ @echo " devhelp to make HTML files and a Devhelp project"
413+ @echo " epub to make an epub"
414+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
415+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
416+ @echo " text to make text files"
417+ @echo " man to make manual pages"
418+ @echo " texinfo to make Texinfo files"
419+ @echo " info to make Texinfo files and run them through makeinfo"
420+ @echo " gettext to make PO message catalogs"
421+ @echo " changes to make an overview of all changed/added/deprecated items"
422+ @echo " linkcheck to check all external links for integrity"
423+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
424+
425+clean:
426+ -rm -rf $(BUILDDIR)/*
427+
428+html:
429+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
430+ @echo
431+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
432+
433+dirhtml:
434+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
435+ @echo
436+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
437+
438+singlehtml:
439+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
440+ @echo
441+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
442+
443+pickle:
444+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
445+ @echo
446+ @echo "Build finished; now you can process the pickle files."
447+
448+json:
449+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
450+ @echo
451+ @echo "Build finished; now you can process the JSON files."
452+
453+htmlhelp:
454+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
455+ @echo
456+ @echo "Build finished; now you can run HTML Help Workshop with the" \
457+ ".hhp project file in $(BUILDDIR)/htmlhelp."
458+
459+qthelp:
460+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
461+ @echo
462+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
463+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
464+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OpenERPBankPayments.qhcp"
465+ @echo "To view the help file:"
466+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OpenERPBankPayments.qhc"
467+
468+devhelp:
469+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
470+ @echo
471+ @echo "Build finished."
472+ @echo "To view the help file:"
473+ @echo "# mkdir -p $$HOME/.local/share/devhelp/OpenERPBankPayments"
474+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OpenERPBankPayments"
475+ @echo "# devhelp"
476+
477+epub:
478+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
479+ @echo
480+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
481+
482+latex:
483+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
484+ @echo
485+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
486+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
487+ "(use \`make latexpdf' here to do that automatically)."
488+
489+latexpdf:
490+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
491+ @echo "Running LaTeX files through pdflatex..."
492+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
493+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
494+
495+text:
496+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
497+ @echo
498+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
499+
500+man:
501+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
502+ @echo
503+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
504+
505+texinfo:
506+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
507+ @echo
508+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
509+ @echo "Run \`make' in that directory to run these through makeinfo" \
510+ "(use \`make info' here to do that automatically)."
511+
512+info:
513+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
514+ @echo "Running Texinfo files through makeinfo..."
515+ make -C $(BUILDDIR)/texinfo info
516+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
517+
518+gettext:
519+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
520+ @echo
521+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
522+
523+changes:
524+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
525+ @echo
526+ @echo "The overview file is in $(BUILDDIR)/changes."
527+
528+linkcheck:
529+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
530+ @echo
531+ @echo "Link check complete; look for any errors in the above output " \
532+ "or in $(BUILDDIR)/linkcheck/output.txt."
533+
534+doctest:
535+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
536+ @echo "Testing of doctests in the sources finished, look at the " \
537+ "results in $(BUILDDIR)/doctest/output.txt."
538
539=== added file 'customer_payment_request/docs/conf.py'
540--- customer_payment_request/docs/conf.py 1970-01-01 00:00:00 +0000
541+++ customer_payment_request/docs/conf.py 2013-09-17 15:32:05 +0000
542@@ -0,0 +1,242 @@
543+# -*- coding: utf-8 -*-
544+#
545+# OpenERP Bank Payments documentation build configuration file, created by
546+# sphinx-quickstart on Wed Sep 4 09:42:11 2013.
547+#
548+# This file is execfile()d with the current directory set to its containing dir.
549+#
550+# Note that not all possible configuration values are present in this
551+# autogenerated file.
552+#
553+# All configuration values have a default; values that are commented out
554+# serve to show the default.
555+
556+import sys, os
557+
558+# If extensions (or modules to document with autodoc) are in another directory,
559+# add these directories to sys.path here. If the directory is relative to the
560+# documentation root, use os.path.abspath to make it absolute, like shown here.
561+#sys.path.insert(0, os.path.abspath('.'))
562+
563+# -- General configuration -----------------------------------------------------
564+
565+# If your documentation needs a minimal Sphinx version, state it here.
566+#needs_sphinx = '1.0'
567+
568+# Add any Sphinx extension module names here, as strings. They can be extensions
569+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
570+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo']
571+
572+# Add any paths that contain templates here, relative to this directory.
573+templates_path = ['_templates']
574+
575+# The suffix of source filenames.
576+source_suffix = '.rst'
577+
578+# The encoding of source files.
579+#source_encoding = 'utf-8-sig'
580+
581+# The master toctree document.
582+master_doc = 'index'
583+
584+# General information about the project.
585+project = u'OpenERP Bank Payments'
586+copyright = u'2013, Savoir-faire Linux'
587+
588+# The version info for the project you're documenting, acts as replacement for
589+# |version| and |release|, also used in various other places throughout the
590+# built documents.
591+#
592+# The short X.Y version.
593+version = '1.0'
594+# The full version, including alpha/beta/rc tags.
595+release = '1.0'
596+
597+# The language for content autogenerated by Sphinx. Refer to documentation
598+# for a list of supported languages.
599+#language = None
600+
601+# There are two options for replacing |today|: either, you set today to some
602+# non-false value, then it is used:
603+#today = ''
604+# Else, today_fmt is used as the format for a strftime call.
605+#today_fmt = '%B %d, %Y'
606+
607+# List of patterns, relative to source directory, that match files and
608+# directories to ignore when looking for source files.
609+exclude_patterns = ['_build']
610+
611+# The reST default role (used for this markup: `text`) to use for all documents.
612+#default_role = None
613+
614+# If true, '()' will be appended to :func: etc. cross-reference text.
615+#add_function_parentheses = True
616+
617+# If true, the current module name will be prepended to all description
618+# unit titles (such as .. function::).
619+#add_module_names = True
620+
621+# If true, sectionauthor and moduleauthor directives will be shown in the
622+# output. They are ignored by default.
623+#show_authors = False
624+
625+# The name of the Pygments (syntax highlighting) style to use.
626+pygments_style = 'sphinx'
627+
628+# A list of ignored prefixes for module index sorting.
629+#modindex_common_prefix = []
630+
631+
632+# -- Options for HTML output ---------------------------------------------------
633+
634+# The theme to use for HTML and HTML Help pages. See the documentation for
635+# a list of builtin themes.
636+html_theme = 'default'
637+
638+# Theme options are theme-specific and customize the look and feel of a theme
639+# further. For a list of options available for each theme, see the
640+# documentation.
641+#html_theme_options = {}
642+
643+# Add any paths that contain custom themes here, relative to this directory.
644+#html_theme_path = []
645+
646+# The name for this set of Sphinx documents. If None, it defaults to
647+# "<project> v<release> documentation".
648+#html_title = None
649+
650+# A shorter title for the navigation bar. Default is the same as html_title.
651+#html_short_title = None
652+
653+# The name of an image file (relative to this directory) to place at the top
654+# of the sidebar.
655+#html_logo = None
656+
657+# The name of an image file (within the static path) to use as favicon of the
658+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
659+# pixels large.
660+#html_favicon = None
661+
662+# Add any paths that contain custom static files (such as style sheets) here,
663+# relative to this directory. They are copied after the builtin static files,
664+# so a file named "default.css" will overwrite the builtin "default.css".
665+html_static_path = ['_static']
666+
667+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
668+# using the given strftime format.
669+#html_last_updated_fmt = '%b %d, %Y'
670+
671+# If true, SmartyPants will be used to convert quotes and dashes to
672+# typographically correct entities.
673+#html_use_smartypants = True
674+
675+# Custom sidebar templates, maps document names to template names.
676+#html_sidebars = {}
677+
678+# Additional templates that should be rendered to pages, maps page names to
679+# template names.
680+#html_additional_pages = {}
681+
682+# If false, no module index is generated.
683+#html_domain_indices = True
684+
685+# If false, no index is generated.
686+#html_use_index = True
687+
688+# If true, the index is split into individual pages for each letter.
689+#html_split_index = False
690+
691+# If true, links to the reST sources are added to the pages.
692+#html_show_sourcelink = True
693+
694+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
695+#html_show_sphinx = True
696+
697+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
698+#html_show_copyright = True
699+
700+# If true, an OpenSearch description file will be output, and all pages will
701+# contain a <link> tag referring to it. The value of this option must be the
702+# base URL from which the finished HTML is served.
703+#html_use_opensearch = ''
704+
705+# This is the file name suffix for HTML files (e.g. ".xhtml").
706+#html_file_suffix = None
707+
708+# Output file base name for HTML help builder.
709+htmlhelp_basename = 'OpenERPBankPaymentsdoc'
710+
711+
712+# -- Options for LaTeX output --------------------------------------------------
713+
714+latex_elements = {
715+# The paper size ('letterpaper' or 'a4paper').
716+#'papersize': 'letterpaper',
717+
718+# The font size ('10pt', '11pt' or '12pt').
719+#'pointsize': '10pt',
720+
721+# Additional stuff for the LaTeX preamble.
722+#'preamble': '',
723+}
724+
725+# Grouping the document tree into LaTeX files. List of tuples
726+# (source start file, target name, title, author, documentclass [howto/manual]).
727+latex_documents = [
728+ ('index', 'OpenERPBankPayments.tex', u'OpenERP Bank Payments Documentation',
729+ u'Savoir-faire Linux', 'manual'),
730+]
731+
732+# The name of an image file (relative to this directory) to place at the top of
733+# the title page.
734+#latex_logo = None
735+
736+# For "manual" documents, if this is true, then toplevel headings are parts,
737+# not chapters.
738+#latex_use_parts = False
739+
740+# If true, show page references after internal links.
741+#latex_show_pagerefs = False
742+
743+# If true, show URL addresses after external links.
744+#latex_show_urls = False
745+
746+# Documents to append as an appendix to all manuals.
747+#latex_appendices = []
748+
749+# If false, no module index is generated.
750+#latex_domain_indices = True
751+
752+
753+# -- Options for manual page output --------------------------------------------
754+
755+# One entry per manual page. List of tuples
756+# (source start file, name, description, authors, manual section).
757+man_pages = [
758+ ('index', 'openerpbankpayments', u'OpenERP Bank Payments Documentation',
759+ [u'Savoir-faire Linux'], 1)
760+]
761+
762+# If true, show URL addresses after external links.
763+#man_show_urls = False
764+
765+
766+# -- Options for Texinfo output ------------------------------------------------
767+
768+# Grouping the document tree into Texinfo files. List of tuples
769+# (source start file, target name, title, author,
770+# dir menu entry, description, category)
771+texinfo_documents = [
772+ ('index', 'OpenERPBankPayments', u'OpenERP Bank Payments Documentation',
773+ u'Savoir-faire Linux', 'OpenERPBankPayments', 'One line description of project.',
774+ 'Miscellaneous'),
775+]
776+
777+# Documents to append as an appendix to all manuals.
778+#texinfo_appendices = []
779+
780+# If false, no module index is generated.
781+#texinfo_domain_indices = True
782+
783+# How to display URL addresses: 'footnote', 'no', or 'inline'.
784+#texinfo_show_urls = 'footnote'
785
786=== added file 'customer_payment_request/docs/design.rst'
787--- customer_payment_request/docs/design.rst 1970-01-01 00:00:00 +0000
788+++ customer_payment_request/docs/design.rst 2013-09-17 15:32:05 +0000
789@@ -0,0 +1,59 @@
790+Module Design
791+=============
792+
793+Modules in this project are organised in two groups, the :ref:`workflow` group and the
794+:ref:`protocol` group. Each of them is as loosely coupled as possible to allow mix-and-matching of
795+any of them together. This loose coupling is achieved through a :ref:`registry` mechanism.
796+
797+.. _workflow:
798+
799+Workflow modules
800+----------------
801+
802+The role of workflow modules is to determine what warrants a communication with banks and how. For
803+example, you have the :ref:`customer_payment_request` module which selects all ``partner`` owing us
804+any money and, for that amount, asks (through the :ref:`registry`) that any :ref:`protocol` module
805+that knows how to process payments with the bank associated with each of these partners to process
806+them. Then, it waits for an answer from these protocol modules to confirm the payment and then
807+create a ``voucher`` for it.
808+
809+But that's just one workflow. There could be another workflow which prefers to process payments by
810+invoices, and only under certain conditions, and this hypothetical workflow module could reuse.
811+Another workflow module could prefer avoiding vouchers and work directly with move lines. Another
812+workflow module could be integrated with shipping, stocks and inventory.
813+
814+.. _protocol:
815+
816+Protocol modules
817+----------------
818+
819+Protocol modules implement communication protocols with banks. The kind of transaction they support
820+can vary and they don't have to support every workflow modules in the project (sometimes, they just
821+can't). They broadcast their capabilities to the workflow modules through the :ref:`registry`.
822+
823+For example, :ref:`payment_management_visa_desjardins` can send payment requests for partner account
824+of the ``credit_card`` type (supplied by :ref:`encrypted_credit_card`) and thus fit with the
825+:ref:`customer_payment_request` workflow, so it registers with it through
826+``customer_payment_request.registry``.
827+
828+.. _registry:
829+
830+Registry
831+--------
832+
833+Registry mechanism allows for loose coupling between :ref:`workflow` and :ref:`protocol`. Its
834+implementation is very low-tech and is simply a list of registered functions hosted by the workflow
835+modules. When a protocol wants to support a particular workflow, it simply has to call that
836+workflow's registry function with adapter functions as parameter. These adapter functions then
837+adapt the information supplied by a particular workflow to the requirements of the communication
838+protocol.
839+
840+For example, we have :ref:`payment_management_visa_desjardins` which implements the communication
841+protocol for credit card processing. It hooks with :ref:`customer_payment_request`, which supplies
842+a list of partners and the amount they each owe. Fine, we create a communication file from these and
843+we're done.
844+
845+Later, let's say someone wants to create a new workflow which, instead of being partner-based, is
846+invoice-based. Someone wanting to re-use :ref:`payment_management_visa_desjardins` would simply have
847+to implement an additional adapter function which generates communication files from invoices (and
848+maybe add invoice-based metadata to the communication for reconciliation) and we're done.
849
850=== added file 'customer_payment_request/docs/index.rst'
851--- customer_payment_request/docs/index.rst 1970-01-01 00:00:00 +0000
852+++ customer_payment_request/docs/index.rst 2013-09-17 15:32:05 +0000
853@@ -0,0 +1,25 @@
854+Welcome to OpenERP Bank Payments's documentation!
855+=================================================
856+
857+This project is a collection of modules to help manage automatic payments with banks. For now, it
858+only handles customer payment requests and interfaces only with `Desjardins`_, but the project is
859+designed to loosely couple :ref:`workflows <workflow>` and :ref:`bank protocols <protocol>`, so it
860+should be possible to add stuff like supplier payments and bank protocols.
861+
862+Contents:
863+
864+.. toctree::
865+ :maxdepth: 2
866+
867+ installation
868+ design
869+ modules
870+
871+Indices and tables
872+==================
873+
874+* :ref:`genindex`
875+* :ref:`modindex`
876+* :ref:`search`
877+
878+.. _Desjardins: http://www.desjardins.com/en/
879
880=== added file 'customer_payment_request/docs/installation.rst'
881--- customer_payment_request/docs/installation.rst 1970-01-01 00:00:00 +0000
882+++ customer_payment_request/docs/installation.rst 2013-09-17 15:32:05 +0000
883@@ -0,0 +1,27 @@
884+Installation and usage
885+======================
886+
887+To use this project, you have to install at least one :ref:`workflow` and one :ref:`protocol`.
888+
889+The first step to do that, of course, is like any OpenERP project: Add the ``addons`` folder to your
890+addons path and update your module list.
891+
892+Then, choose a workflow that you want to install in your system. For example, if you want to send
893+requests to your bank for customers who owe you money to pay you, you'll install
894+:ref:`customer_payment_request`.
895+
896+Worflow modules usually come with scheduled tasks (export payments, import bank response, etc.)
897+which themselves are inactive by default. You'll have to go to your schedule task configuration
898+and activate those newly added tasks and set appropriate schedules for them.
899+
900+Then, you'll need to install a :ref:`bank protocol <protocol>` module and configure it. Usually,
901+bank protocol configuration is a by-company configuration, so you'll be able to access them through
902+your company details form.
903+
904+Once this is done, you should be good to go. How things work, usually, is that payments are
905+automatically sent and received through scheduled jobs and everything, such as payment vouchers, is
906+reconciled automatically. Some modules, however, may give you more latitude in this regard and you
907+might, for example, be able to tell them that you want to manually confirm payment vouchers.
908+
909+You should refer to the documentation specific to the modules you've installed for any details or
910+extra configuration.
911
912=== added file 'customer_payment_request/docs/modules.rst'
913--- customer_payment_request/docs/modules.rst 1970-01-01 00:00:00 +0000
914+++ customer_payment_request/docs/modules.rst 2013-09-17 15:32:05 +0000
915@@ -0,0 +1,100 @@
916+Module List
917+===========
918+
919+.. _customer_payment_request:
920+
921+customer_payment_request
922+^^^^^^^^^^^^^^^^^^^^^^^^
923+
924+A :ref:`workflow module <workflow>` allowing to send mass payment requests to each partner owing us
925+money (having a positive value in its "Total Receivable" field). Regularly, through scheduled
926+actions, it scans for these partners and, for each of these, create a ``customer.payment.request``
927+line in ``waiting`` mode.
928+
929+Then, it calls its ``send_payment_func()`` function. This function is called for each registered
930+:ref:`protocol`, in order. The protocol modules are then expected to process every customer payment
931+request that they can process (this usually depends on the type of the bank account associated with
932+the partner) and then set the state of the request to ``sent``. If they can't process the request,
933+they leave it to ``waiting``.
934+
935+Normally, when all protocols passed through the customer payment requests, all of them should be
936+in ``sent`` mode. If there are any ``waiting`` leftovers, a warning will be logged on the server.
937+
938+Then, regularly, ``receive_payment_func()`` will be called to registered protocols to tell them to
939+check for a response from the bank regarding the payment status. If there is any, the protocol
940+modules are expected to call ``register_payment_acceptation()`` or ``register_payment_refusal()``,
941+which will either remove the payment request and create a payment voucher or put it back in
942+``waiting`` mode.
943+
944+The module will never create a payment request for the same partner at once to avoid
945+double-charging.
946+
947+When ``register_payment_refusal()`` is called, this will enable a bank rotation mechanism and try
948+again. So, if the partner has more than one registered bank account, it will retry, but with the
949+next bank account in line. If it doesn't, the retry will be done on the same bank account.
950+
951+.. _payment_management_visa_desjardins:
952+
953+payment_management_visa_desjardins
954+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
955+
956+This :ref:`protocol module <protocol>` produces a text file in a format that, when sent to
957+Desjardins, allows you to request payments from credit cards into your bank account.
958+
959+This module depends on :ref:`encrypted_credit_card` modules which supplies the ``credit_card``
960+bank account type and manages encryption of credit card numbers (because it's illegal to hold
961+credit card numbers in plain text into a database such as OpenERP's).
962+
963+The file that it produce, ``vdcoupon.txt`` can be sent to Desjardins through SSH with the help of
964+``scripts/vdcoupon_watch_send.py`` and a response from Desjardins can be watched and managed with
965+``scripts/vdcoupon_watch_recv.py``.
966+
967+This module supports the :ref:`customer_payment_request` workflow and processes all partners having
968+a ``credit_card`` bank account.
969+
970+Using it requires Desjardins Merchant details to be set for your company, so you'll need to go fill
971+it up after you've installed the module. Because this information is very similar to the one used by
972+:ref:`payment_management_dd_desjardins`, there's a common module,
973+:ref:`payment_management_desjardins` which defines these structures.
974+
975+.. _payment_management_dd_desjardins:
976+
977+payment_management_dd_desjardins
978+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
979+
980+This :ref:`protocol module <protocol>` produces a text file in a format that, when sent to
981+Desjardins, allows you to request direct withdrawal from bank accounts.
982+
983+The file that it produce, ``ddcoupon.txt`` can be sent to Desjardins manually (for now).
984+
985+This module supports the :ref:`customer_payment_request` workflow and processes all partners having
986+a ``bank`` bank account.
987+
988+Unlike credit card processing, direct withdrawal request have no automated response and are
989+considered by the system to always succeed. For this reason payment requests are "accepted" right
990+upon file creation and a voucher is created. For this reason, you might want to enable the
991+"Leave vouchers as draft" option and manually confirm them.
992+
993+Nothing happens when the workflow module asks this protocol to poll for bank response because there
994+aren't any.
995+
996+.. _payment_management_desjardins:
997+
998+payment_management_desjardins
999+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1000+
1001+A helper module defining common preferences for :ref:`payment_management_visa_desjardins` and
1002+:ref:`payment_management_dd_desjardins`.
1003+
1004+.. _encrypted_credit_card:
1005+
1006+encrypted_credit_card
1007+^^^^^^^^^^^^^^^^^^^^^
1008+
1009+A helper module adding the ``credit_card`` bank type, doing credit card number validation and adding
1010+logic for credit card number encryption.
1011+
1012+Encryption is done asynchronously through RSA keypairs. Whenever a new credit card number is
1013+entered, it is encrypted with a public key (which is set in company preferences) and then, another
1014+server which is hopefully very secure holds the private key and decrypts them before sendin them to
1015+banks.
1016
1017=== added file 'customer_payment_request/registry.py'
1018--- customer_payment_request/registry.py 1970-01-01 00:00:00 +0000
1019+++ customer_payment_request/registry.py 2013-09-17 15:32:05 +0000
1020@@ -0,0 +1,53 @@
1021+# -*- encoding: utf-8 -*-
1022+##############################################################################
1023+#
1024+# OpenERP, Open Source Management Solution
1025+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
1026+#
1027+# This program is free software: you can redistribute it and/or modify
1028+# it under the terms of the GNU Affero General Public License as
1029+# published by the Free Software Foundation, either version 3 of the
1030+# License, or (at your option) any later version.
1031+#
1032+# This program is distributed in the hope that it will be useful,
1033+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1034+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1035+# GNU Affero General Public License for more details.
1036+#
1037+# You should have received a copy of the GNU Affero General Public License
1038+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1039+#
1040+##############################################################################
1041+
1042+# def send_payment_func_prototype(cpr_obj, cr, uid, customer_payment_requests, context=None):
1043+# batch_file = SomeBatchFile()
1044+# processed = []
1045+# for cpr in customer_payment_requests:
1046+# if can_process_request(cpr):
1047+# batch_file.add_request(cpr)
1048+# processed.append(cpr)
1049+# if batch_file.send_to_bank() == success:
1050+# cpr_ids = [cpr.id for cpr in processed]
1051+# cpr_obj.write(cr, uid, cpr_ids, {'state': 'sent'}, context=context)
1052+
1053+SEND_PAYMENT_FUNCS = []
1054+
1055+# def receive_payment_func_prototype(cpr_obj, cr, uid, context=None):
1056+# if not has_received_response_from_bank():
1057+# return
1058+# batch_response = parse_batch_response()
1059+# account_id = fetch_account_id_from_bank_prefs()
1060+# journal_id = fetch_journal_id_from_bank_prefs()
1061+# for line in batch_response:
1062+# if line.accepted:
1063+# cpr_obj.register_payment_acceptation(cr, uid, line.partner_id, line.amount, account_id, journal_id)
1064+# else:
1065+# cpr_obj.register_payment_refusal(cr, uid, line.partner_id)
1066+
1067+RECEIVE_PAYMENT_FUNCS = []
1068+
1069+def register(send_func, receive_func):
1070+ if send_func is not None:
1071+ SEND_PAYMENT_FUNCS.append(send_func)
1072+ if receive_func is not None:
1073+ RECEIVE_PAYMENT_FUNCS.append(receive_func)
1074
1075=== added directory 'encrypted_credit_card'
1076=== added file 'encrypted_credit_card/__init__.py'
1077--- encrypted_credit_card/__init__.py 1970-01-01 00:00:00 +0000
1078+++ encrypted_credit_card/__init__.py 2013-09-17 15:32:05 +0000
1079@@ -0,0 +1,1 @@
1080+import encrypted_credit_card
1081\ No newline at end of file
1082
1083=== added file 'encrypted_credit_card/__openerp__.py'
1084--- encrypted_credit_card/__openerp__.py 1970-01-01 00:00:00 +0000
1085+++ encrypted_credit_card/__openerp__.py 2013-09-17 15:32:05 +0000
1086@@ -0,0 +1,64 @@
1087+# -*- encoding: utf-8 -*-
1088+##############################################################################
1089+#
1090+# OpenERP, Open Source Management Solution
1091+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
1092+#
1093+# This program is free software: you can redistribute it and/or modify
1094+# it under the terms of the GNU General Public License as
1095+# published by the Free Software Foundation, either version 3 of the
1096+# License, or (at your option) any later version.
1097+#
1098+# This program is distributed in the hope that it will be useful,
1099+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1100+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1101+# GNU General Public License for more details.
1102+#
1103+# You should have received a copy of the GNU General Public License
1104+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1105+#
1106+##############################################################################
1107+
1108+{
1109+ "name" : "Encrypted Credit Cards",
1110+ "version" : "1.0",
1111+ "author" : "Savoir-faire Linux",
1112+ "website" : "http://www.savoirfairelinux.com",
1113+ "category" : "Accounting & Finance",
1114+ "description" : """
1115+Adds a new "Credit Card" bank type.
1116+
1117+This new bank type stores credit card numbers in an encrypted form, using a public key
1118+stored in res.company.
1119+
1120+To comply with PCI-DSS, we never ever store the credit card number in the DB, so the bank
1121+type record's 'acc_number' field contains stuff like "XXXX-XXXX-XXXX-1234". The actual
1122+credit card number is stored in "encrypted_cc_number", encrypted with the company's public
1123+key.
1124+
1125+The encryption method used is RSA. Because we store the encrypted CC number in a char()
1126+field and that encrypted data is binary, we encode that encrypted CC number in base64.
1127+
1128+This way, someone with full access to the DB is still unable to extract CC number unless he
1129+also has access to the private key, which hopefully is stored elsewhere, in a very secure
1130+place.
1131+
1132+We don't do any decryption here. It's up to another process to have access to the private
1133+key and decrypt those numbers.
1134+
1135+This module requires PyCrypto ( https://pypi.python.org/pypi/pycrypto )
1136+
1137+ """,
1138+ "depends" : ['sale'],
1139+ 'external_dependencies': {
1140+ 'python' : ['Crypto'],
1141+ },
1142+ "init_xml" : [],
1143+ 'data' : [
1144+ 'encrypted_credit_card_data.xml',
1145+ 'encrypted_credit_card_view.xml',
1146+ ],
1147+ "demo_xml" : [],
1148+ "installable" : True,
1149+ "certificate" : ''
1150+}
1151
1152=== added file 'encrypted_credit_card/encrypted_credit_card.py'
1153--- encrypted_credit_card/encrypted_credit_card.py 1970-01-01 00:00:00 +0000
1154+++ encrypted_credit_card/encrypted_credit_card.py 2013-09-17 15:32:05 +0000
1155@@ -0,0 +1,216 @@
1156+# -*- encoding: utf-8 -*-
1157+##############################################################################
1158+#
1159+# OpenERP, Open Source Management Solution
1160+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
1161+#
1162+# This program is free software: you can redistribute it and/or modify
1163+# it under the terms of the GNU Affero General Public License as
1164+# published by the Free Software Foundation, either version 3 of the
1165+# License, or (at your option) any later version.
1166+#
1167+# This program is distributed in the hope that it will be useful,
1168+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1169+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1170+# GNU Affero General Public License for more details.
1171+#
1172+# You should have received a copy of the GNU Affero General Public License
1173+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1174+#
1175+##############################################################################
1176+
1177+# About encryption in this module
1178+#
1179+# The goal of this module is to store CC numbers in an encrypted way using an assymetric encryption
1180+# method (we use RSA). This way, someone with full access to our DB won't be able to decrypt our
1181+# CC numbers unless he also has access to the private key, which of course isn't stored in the BD.
1182+#
1183+# Because the output of PyCrypto.PublicKey.RSA.encrypt() is binary data and that we store the
1184+# encrypted key in a char field, we encode that encrypted data with base64.
1185+
1186+import re
1187+import binascii
1188+from datetime import date
1189+from Crypto.PublicKey import RSA
1190+
1191+from openerp.tools.translate import _
1192+from openerp.osv import orm, fields
1193+
1194+def is_credit_card_number_valid(credit_card_number):
1195+ """Credit card number validation according to the MODULO-10 algorithm.
1196+ """
1197+ str_card_number = str(credit_card_number)
1198+ str_check_digit = str_card_number[-1]
1199+ str_validation_vect = ""
1200+ str_result_vect = []
1201+ result = 0
1202+ next_closest_ten = 10
1203+
1204+ # Make sure the credit card number consists of
1205+ # digits only
1206+ if not re.match(r"^[0-9]+$", str_card_number):
1207+ return False
1208+
1209+ # Build the validation vector '212121...'
1210+ for i in range(len(str_card_number)-1):
1211+ if i % 2 == 0:
1212+ str_validation_vect += '2'
1213+ else:
1214+ str_validation_vect += '1'
1215+
1216+ # Multiply each digit of the card number
1217+ # by the corresponding validation digit,
1218+ # except for the last digit
1219+ for i in range(len(str_validation_vect)):
1220+ res = int(str_card_number[i]) * int(str_validation_vect[i])
1221+ str_result_vect.append(res)
1222+
1223+ # Add the result of the above multiplication
1224+ # and consider a 2-digit number as 2 numbers
1225+ # of one digit
1226+ for number in str_result_vect:
1227+ if number < 10:
1228+ result += number
1229+ else:
1230+ str_number = str(number)
1231+ num_1 = int(str_number[0])
1232+ num_2 = int(str_number[1])
1233+ result += num_1 + num_2
1234+
1235+ # Compute the check digit and compare it
1236+ # with the last digit of the card number
1237+ while next_closest_ten < result:
1238+ next_closest_ten += 10
1239+
1240+ check_digit = next_closest_ten - result
1241+
1242+ if str(check_digit) == str_check_digit:
1243+ return True
1244+ else:
1245+ return False
1246+
1247+def fix_public_key(key):
1248+ # Copy/Pasting public key leads to formatting loss, and PyCrypto is sensitive on this matter.
1249+ # It wants all \n preserved, but in OpenERP's char field, those \n are going to be replaced
1250+ # with spaces. But don't try to naively replace spaces with newlines, because you're going to
1251+ # end up with BEGIN\nPUBLIC\KEY, which PyCrypto won't accept.
1252+ if key.strip().startswith('ssh-rsa'):
1253+ # This key is not of the type that starts with BEGIN PUBLIC KEY. Just return the stripped
1254+ # version
1255+ return key.strip()
1256+ stripped = re.sub(r'\s?-----[A-Z\s]+-----\s?', '', key)
1257+ stripped = stripped.replace(' ', '\n')
1258+ return '-----BEGIN PUBLIC KEY-----\n' + stripped + '\n-----END PUBLIC KEY-----'
1259+
1260+def encrypt_cc_number(cc_number, public_key):
1261+ key = RSA.importKey(fix_public_key(public_key))
1262+ encrypted_cc_number = key.encrypt(cc_number, 42)[0]
1263+ return binascii.b2a_base64(encrypted_cc_number).strip()
1264+
1265+def encrypt_cc_vals(partner_obj, vals):
1266+ if 'acc_number' not in vals:
1267+ # no acc_number, no problem
1268+ return
1269+ cc_number = vals['acc_number']
1270+ if cc_number.startswith('XXXX'):
1271+ # We're not actually changing the number, just remove it
1272+ del vals['acc_number']
1273+ elif is_credit_card_number_valid(cc_number):
1274+ # Ok, we're actually submitting a CC number here
1275+ # Never ever store CC number in clear
1276+ vals['acc_number'] = 'XXXX-XXXX-XXXX-' + cc_number[-4:]
1277+ vals['encrypted_cc_number'] = encrypt_cc_number(cc_number, partner_obj.company_id.cc_number_encrypt_key)
1278+
1279+class res_partner_bank(orm.Model):
1280+ _inherit = 'res.partner.bank'
1281+
1282+ _columns = {
1283+ # RSA-encrypted, bas64-encoded.
1284+ 'encrypted_cc_number': fields.char("Encrypted Credit Card Number", size=1024),
1285+ 'expiration_date': fields.char('Expiration date (YYMM)', size=4),
1286+ }
1287+
1288+ def check_credit_card_number(self, cr, uid, ids, context=None):
1289+ for bank_acc in self.browse(cr, uid, ids, context=context):
1290+ if bank_acc.state != 'credit_card':
1291+ continue
1292+ cc_number = bank_acc.acc_number
1293+ if cc_number.startswith('XXXX'):
1294+ # It's a hidden number, so we're not actually changing the encrypted CC number.
1295+ # Consider as valid
1296+ continue
1297+ if not is_credit_card_number_valid(cc_number):
1298+ return False
1299+ return True
1300+
1301+ def check_expiration_date(self, cr, uid, ids, context=None):
1302+ for bank_acc in self.browse(cr, uid, ids, context=context):
1303+ if bank_acc.state != 'credit_card':
1304+ continue
1305+ if not bank_acc.expiration_date:
1306+ return False
1307+ m = re.match(r"^([0-9]{2})([0-9]{2})$", bank_acc.expiration_date)
1308+ if m is None:
1309+ return False
1310+ year = int(m.group(1)) + 2000
1311+ month = int(m.group(2))
1312+ TODAY = date.today()
1313+ if year < TODAY.year:
1314+ return False
1315+ if not (1 <= month <= 12):
1316+ return False
1317+ if year == TODAY.year and month < TODAY.month:
1318+ return False
1319+ return True
1320+
1321+ def _construct_constraint_msg_card_number(self, cr, uid, ids, context=None):
1322+ return (_("Credit card number is invalid")), ()
1323+
1324+ def _construct_constraint_msg_expiration_date(self, cr, uid, ids, context=None):
1325+ return (_("Expiration date is invalid")), ()
1326+
1327+ _constraints = [
1328+ (check_credit_card_number, _construct_constraint_msg_card_number, ["acc_number"]),
1329+ (check_expiration_date, _construct_constraint_msg_expiration_date, ["expiration_date"]),
1330+ ]
1331+
1332+ def create(self, cr, uid, vals, context=None):
1333+ if vals.get('state') == 'credit_card':
1334+ partner_obj = self.pool.get('res.partner').browse(cr, uid, vals['partner_id'], context=context)
1335+ encrypt_cc_vals(partner_obj, vals)
1336+ return super(res_partner_bank, self).create(cr, uid, vals, context=context)
1337+
1338+ def write(self, cr, uid, ids, vals, context=None):
1339+ self_obj = self.browse(cr, uid, ids[0], context=context)
1340+ try:
1341+ state = vals['state']
1342+ except KeyError:
1343+ state = self_obj.state
1344+ if state == 'credit_card':
1345+ encrypt_cc_vals(self_obj.partner_id, vals)
1346+ return super(res_partner_bank, self).write(cr, uid, ids, vals, context=context)
1347+
1348+class res_company(orm.Model):
1349+ _inherit = 'res.company'
1350+
1351+ def _get_short_pubkey(self, cr, uid, ids, field_name, arg, context):
1352+ res = {}
1353+ for company in self.browse(cr, uid, ids, context=context):
1354+ pubkey = company.cc_number_encrypt_key
1355+ if pubkey:
1356+ res[company.id] = pubkey[:30] + "..."
1357+ else:
1358+ res[company.id] = ""
1359+ return res
1360+
1361+ def _set_short_pubkey(self, cr, uid, id, name, value, fnct_inv_arg, context=None):
1362+ if len(value) > 40:
1363+ # We only save the key if it's not the truncated value
1364+ self.write(cr, uid, id, {'cc_number_encrypt_key': value}, context=context)
1365+
1366+ _columns = {
1367+ 'cc_number_encrypt_key_short': fields.function(_get_short_pubkey, fnct_inv=_set_short_pubkey,
1368+ type='char', string='Credit Card Encryption Key'),
1369+ 'cc_number_encrypt_key': fields.char('Credit Card Encryption Key', size=2048,
1370+ help="Public key with which to encrypt our credit card number before writing them to the DB"),
1371+ }
1372
1373=== added file 'encrypted_credit_card/encrypted_credit_card_data.xml'
1374--- encrypted_credit_card/encrypted_credit_card_data.xml 1970-01-01 00:00:00 +0000
1375+++ encrypted_credit_card/encrypted_credit_card_data.xml 2013-09-17 15:32:05 +0000
1376@@ -0,0 +1,11 @@
1377+<?xml version="1.0" encoding="UTF-8"?>
1378+<openerp>
1379+ <data>
1380+ <record id="bank_credit_card" model="res.partner.bank.type">
1381+ <field name="name">Credit card Account</field>
1382+ <field name="code">credit_card</field>
1383+ <field name="format_layout">%(bank_name)s: CC %(acc_number)s - EXP %(expiration_date)s</field>
1384+ </record>
1385+ </data>
1386+
1387+</openerp>
1388
1389=== added file 'encrypted_credit_card/encrypted_credit_card_view.xml'
1390--- encrypted_credit_card/encrypted_credit_card_view.xml 1970-01-01 00:00:00 +0000
1391+++ encrypted_credit_card/encrypted_credit_card_view.xml 2013-09-17 15:32:05 +0000
1392@@ -0,0 +1,27 @@
1393+<?xml version="1.0" encoding="UTF-8"?>
1394+<openerp>
1395+ <data>
1396+ <record id="view_partner_bank_form" model="ir.ui.view">
1397+ <field name="name">res.partner.bank.form</field>
1398+ <field name="model">res.partner.bank</field>
1399+ <field name="inherit_id" ref="base.view_partner_bank_form"/>
1400+ <field name="arch" type="xml">
1401+ <field name="acc_number" position="after">
1402+ <field name="expiration_date" attrs="{'invisible':[('state','!=','credit_card')], 'required':[('state','=','credit_card')]}" />
1403+ </field>
1404+ </field>
1405+ </record>
1406+
1407+ <record id="view_company_form" model="ir.ui.view">
1408+ <field name="name">res.company.form</field>
1409+ <field name="model">res.company</field>
1410+ <field name="inherit_id" ref="base.view_company_form"/>
1411+ <field name="arch" type="xml">
1412+ <xpath expr="//field[@name='currency_id']" position="after">
1413+ <field name="cc_number_encrypt_key_short"/>
1414+ </xpath>
1415+ </field>
1416+ </record>
1417+ </data>
1418+
1419+</openerp>
1420
1421=== added directory 'encrypted_credit_card/i18n'
1422=== added file 'encrypted_credit_card/i18n/encrypted_credit_card.pot'
1423--- encrypted_credit_card/i18n/encrypted_credit_card.pot 1970-01-01 00:00:00 +0000
1424+++ encrypted_credit_card/i18n/encrypted_credit_card.pot 2013-09-17 15:32:05 +0000
1425@@ -0,0 +1,69 @@
1426+# Translation of OpenERP Server.
1427+# This file contains the translation of the following modules:
1428+# * encrypted_credit_card
1429+#
1430+msgid ""
1431+msgstr ""
1432+"Project-Id-Version: OpenERP Server 7.0\n"
1433+"Report-Msgid-Bugs-To: \n"
1434+"POT-Creation-Date: 2013-08-13 14:05+0000\n"
1435+"PO-Revision-Date: 2013-08-13 14:05+0000\n"
1436+"Last-Translator: <>\n"
1437+"Language-Team: \n"
1438+"MIME-Version: 1.0\n"
1439+"Content-Type: text/plain; charset=UTF-8\n"
1440+"Content-Transfer-Encoding: \n"
1441+"Plural-Forms: \n"
1442+
1443+#. module: encrypted_credit_card
1444+#: field:res.partner.bank,encrypted_cc_number:0
1445+msgid "Encrypted Credit Card Number"
1446+msgstr ""
1447+
1448+#. module: encrypted_credit_card
1449+#: code:addons/encrypted_credit_card/encrypted_credit_card.py:170
1450+#, python-format
1451+msgid "Expiration date is invalid"
1452+msgstr ""
1453+
1454+#. module: encrypted_credit_card
1455+#: code:addons/encrypted_credit_card/encrypted_credit_card.py:167
1456+#, python-format
1457+msgid "Credit card number is invalid"
1458+msgstr ""
1459+
1460+#. module: encrypted_credit_card
1461+#: model:ir.model,name:encrypted_credit_card.model_res_partner_bank
1462+msgid "Bank Accounts"
1463+msgstr ""
1464+
1465+#. module: encrypted_credit_card
1466+#: model:ir.model,name:encrypted_credit_card.model_res_company
1467+msgid "Companies"
1468+msgstr ""
1469+
1470+#. module: encrypted_credit_card
1471+#: field:res.partner.bank,expiration_date:0
1472+msgid "Expiration date (YYMM)"
1473+msgstr ""
1474+
1475+#. module: encrypted_credit_card
1476+#: help:res.company,cc_number_encrypt_key:0
1477+msgid "Public key with which to encrypt our credit card number before writing them to the DB"
1478+msgstr ""
1479+
1480+#. module: encrypted_credit_card
1481+#: model:res.partner.bank.type,format_layout:encrypted_credit_card.bank_credit_card
1482+msgid "%(bank_name)s: IBAN %(acc_number)s - BIC %(expiration_date)s"
1483+msgstr ""
1484+
1485+#. module: encrypted_credit_card
1486+#: model:res.partner.bank.type,name:encrypted_credit_card.bank_credit_card
1487+msgid "Credit card Account"
1488+msgstr ""
1489+
1490+#. module: encrypted_credit_card
1491+#: field:res.company,cc_number_encrypt_key:0
1492+msgid "Credit Card Encryption Key"
1493+msgstr ""
1494+
1495
1496=== added directory 'payment_management_dd_desjardins'
1497=== added file 'payment_management_dd_desjardins/__init__.py'
1498--- payment_management_dd_desjardins/__init__.py 1970-01-01 00:00:00 +0000
1499+++ payment_management_dd_desjardins/__init__.py 2013-09-17 15:32:05 +0000
1500@@ -0,0 +1,22 @@
1501+# -*- encoding: utf-8 -*-
1502+##############################################################################
1503+#
1504+# OpenERP, Open Source Management Solution
1505+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
1506+#
1507+# This program is free software: you can redistribute it and/or modify
1508+# it under the terms of the GNU General Public License as
1509+# published by the Free Software Foundation, either version 3 of the
1510+# License, or (at your option) any later version.
1511+#
1512+# This program is distributed in the hope that it will be useful,
1513+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1514+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1515+# GNU General Public License for more details.
1516+#
1517+# You should have received a copy of the GNU General Public License
1518+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1519+#
1520+##############################################################################
1521+
1522+import payment_management_dd_desjardins
1523
1524=== added file 'payment_management_dd_desjardins/__openerp__.py'
1525--- payment_management_dd_desjardins/__openerp__.py 1970-01-01 00:00:00 +0000
1526+++ payment_management_dd_desjardins/__openerp__.py 2013-09-17 15:32:05 +0000
1527@@ -0,0 +1,40 @@
1528+# -*- encoding: utf-8 -*-
1529+##############################################################################
1530+#
1531+# OpenERP, Open Source Management Solution
1532+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
1533+#
1534+# This program is free software: you can redistribute it and/or modify
1535+# it under the terms of the GNU General Public License as
1536+# published by the Free Software Foundation, either version 3 of the
1537+# License, or (at your option) any later version.
1538+#
1539+# This program is distributed in the hope that it will be useful,
1540+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1541+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1542+# GNU General Public License for more details.
1543+#
1544+# You should have received a copy of the GNU General Public License
1545+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1546+#
1547+##############################################################################
1548+
1549+{
1550+ "name" : "Payment management module for desjardins Direct Deposit",
1551+ "version" : "1.0",
1552+ "author" : "Savoir-faire Linux",
1553+ "website" : "http://www.savoirfairelinux.com",
1554+ "category" : "Accounting & Finance",
1555+ "description" : """
1556+ """,
1557+ "depends" : ['payment_management_desjardins'],
1558+ "init_xml" : [],
1559+ "update_xml" : [
1560+ ],
1561+ 'data' : [
1562+ 'payment_management_dd_desjardins_view.xml',
1563+ ],
1564+ "demo_xml" : [],
1565+ "installable" : True,
1566+ "certificate" : ''
1567+}
1568
1569=== added directory 'payment_management_dd_desjardins/i18n'
1570=== added file 'payment_management_dd_desjardins/i18n/payment_management_dd_desjardins.pot'
1571--- payment_management_dd_desjardins/i18n/payment_management_dd_desjardins.pot 1970-01-01 00:00:00 +0000
1572+++ payment_management_dd_desjardins/i18n/payment_management_dd_desjardins.pot 2013-09-17 15:32:05 +0000
1573@@ -0,0 +1,27 @@
1574+# Translation of OpenERP Server.
1575+# This file contains the translation of the following modules:
1576+# * payment_management_dd_desjardins
1577+#
1578+msgid ""
1579+msgstr ""
1580+"Project-Id-Version: OpenERP Server 7.0\n"
1581+"Report-Msgid-Bugs-To: \n"
1582+"POT-Creation-Date: 2013-08-21 13:53+0000\n"
1583+"PO-Revision-Date: 2013-08-21 13:53+0000\n"
1584+"Last-Translator: <>\n"
1585+"Language-Team: \n"
1586+"MIME-Version: 1.0\n"
1587+"Content-Type: text/plain; charset=UTF-8\n"
1588+"Content-Transfer-Encoding: \n"
1589+"Plural-Forms: \n"
1590+
1591+#. module: payment_management_dd_desjardins
1592+#: field:res.company,desjardins_dd_info:0
1593+msgid "Desjardins Direct Deposit Info"
1594+msgstr ""
1595+
1596+#. module: payment_management_dd_desjardins
1597+#: model:ir.model,name:payment_management_dd_desjardins.model_res_company
1598+msgid "Companies"
1599+msgstr ""
1600+
1601
1602=== added file 'payment_management_dd_desjardins/payment_management_dd_desjardins.py'
1603--- payment_management_dd_desjardins/payment_management_dd_desjardins.py 1970-01-01 00:00:00 +0000
1604+++ payment_management_dd_desjardins/payment_management_dd_desjardins.py 2013-09-17 15:32:05 +0000
1605@@ -0,0 +1,113 @@
1606+# -*- encoding: utf-8 -*-
1607+##############################################################################
1608+#
1609+# OpenERP, Open Source Management Solution
1610+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
1611+#
1612+# This program is free software: you can redistribute it and/or modify
1613+# it under the terms of the GNU General Public License as
1614+# published by the Free Software Foundation, either version 3 of the
1615+# License, or (at your option) any later version.
1616+#
1617+# This program is distributed in the hope that it will be useful,
1618+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1619+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1620+# GNU General Public License for more details.
1621+#
1622+# You should have received a copy of the GNU General Public License
1623+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1624+#
1625+##############################################################################
1626+
1627+from __future__ import division
1628+
1629+import os
1630+import logging
1631+import codecs
1632+
1633+from openerp.osv import orm, fields
1634+
1635+from .record import get_cd_record, get_a_record, get_z_record
1636+
1637+_logger = logging.getLogger(__name__)
1638+
1639+class res_company(orm.Model):
1640+ _inherit = 'res.company'
1641+ _columns = {
1642+ 'desjardins_dd_info': fields.many2one('res.company.desjardinsinfo', "Desjardins Direct Deposit Info"),
1643+ }
1644+
1645+ def _default_desjardins_dd_info(self, cr, uid, context=None):
1646+ res_company_desjardinsinfo = self.pool.get('res.company.desjardinsinfo')
1647+ res = res_company_desjardinsinfo.create(cr, uid, {}, context=context)
1648+ return res
1649+
1650+ _defaults = {
1651+ 'desjardins_dd_info': _default_desjardins_dd_info,
1652+ }
1653+
1654+def send_payment_func_dd_desjardins(cpr_obj, cr, uid, customer_payment_requests, context=None):
1655+ company_obj = cpr_obj.pool.get('res.company')
1656+ # active_company() is defined in customer_payment_request, which isn't directly in our
1657+ # dependencies, but for now, this function can't be called unless CPR is installed, so we're
1658+ # alright. This might have to change when our module starts supporting more workflow.
1659+ company = company_obj.active_company(cr, uid, context=context)
1660+ desjinfo = company.desjardins_dd_info
1661+ directory = desjinfo.workdir
1662+
1663+ if not desjinfo.is_complete():
1664+ return False
1665+
1666+ toprocess = [cpr for cpr in customer_payment_requests if cpr.bank_account_id.state == 'bank']
1667+ if not toprocess:
1668+ return False
1669+ total_number_of_credit = 0
1670+ total_number_of_debit = 0
1671+ total_value_of_credit = 0
1672+ total_value_of_debit = 0
1673+ lines = []
1674+
1675+ # Type A record
1676+ a_record = get_a_record(len(lines)+1, desjinfo)
1677+ lines.append(a_record)
1678+
1679+ # Type C/D records
1680+ for cpr in toprocess:
1681+ bank_account = cpr.bank_account_id
1682+ partner = bank_account.partner_id
1683+ # The amount is in cents
1684+ amount = round(cpr.amount * 100)
1685+ if amount > 0:
1686+ total_number_of_debit += 1
1687+ total_value_of_debit += amount
1688+ else:
1689+ total_number_of_credit += 1
1690+ total_value_of_credit += amount
1691+ cd_record = get_cd_record(
1692+ len(lines)+1, company, amount, bank_account, partner
1693+ )
1694+ lines.append(cd_record)
1695+ # With Desjardins RD, there's no response file, so we directly create the payment voucher
1696+ cpr_obj.register_payment_acceptation(
1697+ cr, uid, partner.id, cpr.amount, desjinfo.account_id.id,
1698+ desjinfo.bank_account_id.journal_id.id, leave_draft=desjinfo.leave_vouchers_draft,
1699+ context=context
1700+ )
1701+
1702+ # Type Z record
1703+ z_record = get_z_record(
1704+ len(lines)+1, desjinfo,
1705+ total_number_of_debit, total_value_of_debit,
1706+ total_number_of_credit, total_value_of_credit
1707+ )
1708+ lines.append(z_record)
1709+ with codecs.open(os.path.join(directory, 'ddcoupon.txt'), 'wt', encoding='latin-1') as fp:
1710+ fp.write(u'\n'.join(lines))
1711+ desjinfo.increase_file_number()
1712+ return True
1713+
1714+try:
1715+ from customer_payment_request import registry
1716+ registry.register(send_payment_func_dd_desjardins, None)
1717+except ImportError:
1718+ pass
1719
1720=== added file 'payment_management_dd_desjardins/payment_management_dd_desjardins_view.xml'
1721--- payment_management_dd_desjardins/payment_management_dd_desjardins_view.xml 1970-01-01 00:00:00 +0000
1722+++ payment_management_dd_desjardins/payment_management_dd_desjardins_view.xml 2013-09-17 15:32:05 +0000
1723@@ -0,0 +1,27 @@
1724+<?xml version="1.0" encoding="UTF-8"?>
1725+<openerp>
1726+ <data>
1727+ <record id="view_company_form" model="ir.ui.view">
1728+ <field name="name">res.company.form</field>
1729+ <field name="model">res.company</field>
1730+ <field name="inherit_id" ref="base.view_company_form"/>
1731+ <field name="arch" type="xml">
1732+ <xpath expr="//page[@string='General Information']/group/group" position="inside">
1733+ <field name="desjardins_dd_info"/>
1734+ </xpath>
1735+ </field>
1736+ </record>
1737+
1738+ <record id="view_partner_bank_form" model="ir.ui.view">
1739+ <field name="name">res.partner.bank.form</field>
1740+ <field name="model">res.partner.bank</field>
1741+ <field name="inherit_id" ref="base.view_partner_bank_form"/>
1742+ <field name="arch" type="xml">
1743+ <xpath expr="//field[@name='bank_bic']" position="attributes">
1744+ <attribute name="attrs">{'required':[('state', 'in', ['iban', 'bank'])]}</attribute>
1745+ </xpath>
1746+ </field>
1747+ </record>
1748+ </data>
1749+
1750+</openerp>
1751
1752=== added file 'payment_management_dd_desjardins/record.py'
1753--- payment_management_dd_desjardins/record.py 1970-01-01 00:00:00 +0000
1754+++ payment_management_dd_desjardins/record.py 2013-09-17 15:32:05 +0000
1755@@ -0,0 +1,94 @@
1756+# -*- encoding: utf-8 -*-
1757+##############################################################################
1758+#
1759+# OpenERP, Open Source Management Solution
1760+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
1761+#
1762+# This program is free software: you can redistribute it and/or modify
1763+# it under the terms of the GNU General Public License as
1764+# published by the Free Software Foundation, either version 3 of the
1765+# License, or (at your option) any later version.
1766+#
1767+# This program is distributed in the hope that it will be useful,
1768+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1769+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1770+# GNU General Public License for more details.
1771+#
1772+# You should have received a copy of the GNU General Public License
1773+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1774+#
1775+##############################################################################
1776+
1777+from __future__ import division, unicode_literals
1778+
1779+from datetime import date, datetime
1780+
1781+from payment_management_desjardins.record import yyyddd
1782+
1783+def ljust(s, num, filler):
1784+ return s[:num].ljust(num, filler)
1785+
1786+def rjust(s, num, filler):
1787+ return s[:num].rjust(num, filler)
1788+
1789+def get_a_record(seq, desjinfo):
1790+ return ''.join([
1791+ 'A',
1792+ '%09d' % seq,
1793+ desjinfo.originator_id[:10].upper().ljust(10, ' '),
1794+ '%04d' % desjinfo.file_creation_number,
1795+ yyyddd(datetime.now()),
1796+ '81510',
1797+ ' ' * 20,
1798+ 'CAD',
1799+ ' ' * 1406,
1800+ ])
1801+
1802+def get_cd_record(seq, company, amount, bank_account, partner):
1803+ desjinfo = company.desjardins_dd_info
1804+ # amount is an integer representing cents
1805+ if amount > 0:
1806+ firstchar = 'D'
1807+ txn_type = desjinfo.txn_code_debit
1808+ else:
1809+ firstchar = 'C'
1810+ txn_type = desjinfo.txn_code_credit
1811+ amount = abs(amount)
1812+ return ''.join([
1813+ firstchar,
1814+ '%09d' % seq,
1815+ ljust(desjinfo.originator_id.upper(), 10, ' '),
1816+ '%04d' % desjinfo.file_creation_number,
1817+ txn_type,
1818+ '%010d' % amount,
1819+ yyyddd(date.today()),
1820+ rjust(bank_account.bank_bic, 9, '0'),
1821+ ljust(bank_account.acc_number, 12, ' '),
1822+ '0' * 22,
1823+ '0' * 3,
1824+ ljust(company.name, 15, ' '),
1825+ ljust(partner.name, 30, ' '),
1826+ ljust(company.name, 30, ' '),
1827+ ljust(desjinfo.originator_id.upper(), 10, ' '),
1828+ '%019d' % partner.id,
1829+ rjust(desjinfo.bank_account_id.bank_bic, 9, '0'),
1830+ ljust(desjinfo.bank_account_id.acc_number, 12, ' '),
1831+ ' ' * 15,
1832+ ' ' * 22,
1833+ ' ' * 2,
1834+ '0' * 11,
1835+ ])
1836+
1837+def get_z_record(seq, desjinfo, num_debit, total_debit, num_credit, total_credit):
1838+ return ''.join([
1839+ 'Z',
1840+ '%09d' % seq,
1841+ desjinfo.originator_id[:10].upper().ljust(10, ' '),
1842+ '%04d' % desjinfo.file_creation_number,
1843+ '%014d' % total_debit,
1844+ '%08d' % num_debit,
1845+ '%014d' % total_credit,
1846+ '%08d' % num_credit,
1847+ '0' * 44,
1848+ ' ' * 1352,
1849+ ])
1850
1851=== added directory 'payment_management_desjardins'
1852=== added file 'payment_management_desjardins/__init__.py'
1853--- payment_management_desjardins/__init__.py 1970-01-01 00:00:00 +0000
1854+++ payment_management_desjardins/__init__.py 2013-09-17 15:32:05 +0000
1855@@ -0,0 +1,22 @@
1856+# -*- encoding: utf-8 -*-
1857+##############################################################################
1858+#
1859+# OpenERP, Open Source Management Solution
1860+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
1861+#
1862+# This program is free software: you can redistribute it and/or modify
1863+# it under the terms of the GNU General Public License as
1864+# published by the Free Software Foundation, either version 3 of the
1865+# License, or (at your option) any later version.
1866+#
1867+# This program is distributed in the hope that it will be useful,
1868+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1869+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1870+# GNU General Public License for more details.
1871+#
1872+# You should have received a copy of the GNU General Public License
1873+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1874+#
1875+##############################################################################
1876+
1877+import payment_management_desjardins
1878
1879=== added file 'payment_management_desjardins/__openerp__.py'
1880--- payment_management_desjardins/__openerp__.py 1970-01-01 00:00:00 +0000
1881+++ payment_management_desjardins/__openerp__.py 2013-09-17 15:32:05 +0000
1882@@ -0,0 +1,40 @@
1883+# -*- encoding: utf-8 -*-
1884+##############################################################################
1885+#
1886+# OpenERP, Open Source Management Solution
1887+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
1888+#
1889+# This program is free software: you can redistribute it and/or modify
1890+# it under the terms of the GNU General Public License as
1891+# published by the Free Software Foundation, either version 3 of the
1892+# License, or (at your option) any later version.
1893+#
1894+# This program is distributed in the hope that it will be useful,
1895+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1896+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1897+# GNU General Public License for more details.
1898+#
1899+# You should have received a copy of the GNU General Public License
1900+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1901+#
1902+##############################################################################
1903+
1904+{
1905+ "name" : "Payment management module for Desjardins",
1906+ "version" : "1.0",
1907+ "author" : "Savoir-faire Linux",
1908+ "website" : "http://www.savoirfairelinux.com",
1909+ "category" : "Accounting & Finance",
1910+ "description" : """
1911+ """,
1912+ "depends" : ['account_voucher', 'sale'],
1913+ "init_xml" : [],
1914+ "update_xml" : [
1915+ ],
1916+ 'data' : [
1917+ 'payment_management_desjardins_view.xml',
1918+ ],
1919+ "demo_xml" : [],
1920+ "installable" : True,
1921+ "certificate" : ''
1922+}
1923
1924=== added directory 'payment_management_desjardins/i18n'
1925=== added file 'payment_management_desjardins/i18n/payment_management_desjardins.pot'
1926--- payment_management_desjardins/i18n/payment_management_desjardins.pot 1970-01-01 00:00:00 +0000
1927+++ payment_management_desjardins/i18n/payment_management_desjardins.pot 2013-09-17 15:32:05 +0000
1928@@ -0,0 +1,105 @@
1929+# Translation of OpenERP Server.
1930+# This file contains the translation of the following modules:
1931+# * payment_management_desjardins
1932+#
1933+msgid ""
1934+msgstr ""
1935+"Project-Id-Version: OpenERP Server 7.0\n"
1936+"Report-Msgid-Bugs-To: \n"
1937+"POT-Creation-Date: 2013-08-21 13:52+0000\n"
1938+"PO-Revision-Date: 2013-08-21 13:52+0000\n"
1939+"Last-Translator: <>\n"
1940+"Language-Team: \n"
1941+"MIME-Version: 1.0\n"
1942+"Content-Type: text/plain; charset=UTF-8\n"
1943+"Content-Transfer-Encoding: \n"
1944+"Plural-Forms: \n"
1945+
1946+#. module: payment_management_desjardins
1947+#: model:ir.model,name:payment_management_desjardins.model_res_company_desjardinsinfo
1948+msgid "res.company.desjardinsinfo"
1949+msgstr ""
1950+
1951+#. module: payment_management_desjardins
1952+#: help:res.company.desjardinsinfo,merchant_number:0
1953+msgid "Merchant number assigned by DCS. Must be numeric."
1954+msgstr ""
1955+
1956+#. module: payment_management_desjardins
1957+#: field:res.company.desjardinsinfo,merchant_number:0
1958+msgid "Merchant number for Desjardins"
1959+msgstr ""
1960+
1961+#. module: payment_management_desjardins
1962+#: code:addons/payment_management_desjardins/payment_management_desjardins.py:88
1963+#, python-format
1964+msgid "Specified directory %s is not a valid directory. Desjardins payment exportation is aborted."
1965+msgstr ""
1966+
1967+#. module: payment_management_desjardins
1968+#: field:res.company.desjardinsinfo,workdir:0
1969+msgid "Storage Directory"
1970+msgstr ""
1971+
1972+#. module: payment_management_desjardins
1973+#: help:res.company.desjardinsinfo,originator_id:0
1974+msgid "Name of the directory into which the 'vdcoupon.txt' files will be placed."
1975+msgstr ""
1976+
1977+#. module: payment_management_desjardins
1978+#: field:res.company.desjardinsinfo,bank_account_id:0
1979+msgid "Your Desjardins bank account"
1980+msgstr ""
1981+
1982+#. module: payment_management_desjardins
1983+#: code:addons/payment_management_desjardins/payment_management_desjardins.py:78
1984+#, python-format
1985+msgid "Desjardins Merchant Info"
1986+msgstr ""
1987+
1988+#. module: payment_management_desjardins
1989+#: field:res.company.desjardinsinfo,originator_id:0
1990+msgid "Originator ID"
1991+msgstr ""
1992+
1993+#. module: payment_management_desjardins
1994+#: field:res.company.desjardinsinfo,txn_code_credit:0
1995+msgid "Transaction code for Credit"
1996+msgstr ""
1997+
1998+#. module: payment_management_desjardins
1999+#: view:res.company.desjardinsinfo:0
2000+msgid "Desjardins harmonization info"
2001+msgstr ""
2002+
2003+#. module: payment_management_desjardins
2004+#: field:res.company.desjardinsinfo,txn_code_debit:0
2005+msgid "Transaction code for Debit"
2006+msgstr ""
2007+
2008+#. module: payment_management_desjardins
2009+#: field:res.company.desjardinsinfo,file_creation_number:0
2010+msgid "Next file creation number value"
2011+msgstr ""
2012+
2013+#. module: payment_management_desjardins
2014+#: help:res.company.desjardinsinfo,file_creation_number:0
2015+msgid "File sequence number, to be incremented by 1 for each file sent without error."
2016+msgstr ""
2017+
2018+#. module: payment_management_desjardins
2019+#: field:res.company.desjardinsinfo,account_id:0
2020+msgid "Account for Desjardins payments"
2021+msgstr ""
2022+
2023+#. module: payment_management_desjardins
2024+#: help:res.company.desjardinsinfo,workdir:0
2025+msgid "Specify a directory to export to and import from."
2026+msgstr ""
2027+
2028+#. module: payment_management_desjardins
2029+#: code:addons/payment_management_desjardins/payment_management_desjardins.py:94
2030+#, python-format
2031+msgid "Could not export Desjardins payments because at least one of the following company parameters is missing : (merchant_number, file_creation_number, originator_id)."
2032+msgstr ""
2033+
2034
2035=== added file 'payment_management_desjardins/payment_management_desjardins.py'
2036--- payment_management_desjardins/payment_management_desjardins.py 1970-01-01 00:00:00 +0000
2037+++ payment_management_desjardins/payment_management_desjardins.py 2013-09-17 15:32:05 +0000
2038@@ -0,0 +1,97 @@
2039+# -*- encoding: utf-8 -*-
2040+##############################################################################
2041+#
2042+# OpenERP, Open Source Management Solution
2043+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
2044+#
2045+# This program is free software: you can redistribute it and/or modify
2046+# it under the terms of the GNU General Public License as
2047+# published by the Free Software Foundation, either version 3 of the
2048+# License, or (at your option) any later version.
2049+#
2050+# This program is distributed in the hope that it will be useful,
2051+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2052+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2053+# GNU General Public License for more details.
2054+#
2055+# You should have received a copy of the GNU General Public License
2056+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2057+#
2058+##############################################################################
2059+
2060+from __future__ import division
2061+
2062+import os
2063+import logging
2064+
2065+from openerp.tools.translate import _
2066+from openerp.osv import orm, fields
2067+
2068+_logger = logging.getLogger(__name__)
2069+
2070+class res_company_desjardinsinfo(orm.Model):
2071+ _name = 'res.company.desjardinsinfo'
2072+ _columns = {
2073+ 'file_creation_number': fields.integer(
2074+ 'Next file creation number value',
2075+ help="File sequence number, to be incremented by 1 for each file sent without error."
2076+ ),
2077+ 'originator_id': fields.char(
2078+ 'Originator ID',
2079+ size=10,
2080+ help="Name of the directory into which the 'vdcoupon.txt' files will be placed."
2081+ ),
2082+ 'workdir': fields.char(
2083+ 'Storage Directory',
2084+ size=255,
2085+ help="Specify a directory to export to and import from."
2086+ ),
2087+ 'bank_account_id':fields.many2one(
2088+ 'res.partner.bank',
2089+ 'Your Desjardins bank account',
2090+ ),
2091+ 'account_id':fields.many2one(
2092+ 'account.account',
2093+ 'Account for Desjardins payments',
2094+ ),
2095+ 'txn_code_debit': fields.char(
2096+ 'Transaction code for Debit',
2097+ size=3,
2098+ ),
2099+ 'txn_code_credit': fields.char(
2100+ 'Transaction code for Credit',
2101+ size=3,
2102+ ),
2103+ 'leave_vouchers_draft': fields.boolean(
2104+ string=u"Leave vouchers as draft",
2105+ help=u"When creating vouchers, don't validate them, leave then in a draft state",
2106+ )
2107+ }
2108+
2109+ _defaults = {
2110+ 'workdir': '/tmp',
2111+ 'file_creation_number' : 1,
2112+ }
2113+
2114+ def name_get(self, cr, uid, ids, context=None):
2115+ return dict.fromkeys(ids, _("Desjardins Merchant Info"))
2116+
2117+ def is_complete(self, cr, uid, ids, context=None):
2118+ """Returns whether our info record is complete.
2119+
2120+ Logs a warning if not.
2121+ """
2122+ id = ids[0] if isinstance(ids, list) else ids
2123+ desjinfo = self.browse(cr, uid, id)
2124+ if not os.path.isdir(desjinfo.workdir):
2125+ msg = _("Specified directory %s is not a valid directory. Desjardins payment exportation is aborted.")
2126+ _logger.warning(msg, desjinfo.workdir)
2127+ return False
2128+
2129+ return True
2130+
2131+ def increase_file_number(self, cr, uid, ids, context=None):
2132+ id = ids[0] if isinstance(ids, list) else ids
2133+ desjinfo = self.browse(cr, uid, id)
2134+ self.write(cr, uid, id, {'file_creation_number': desjinfo.file_creation_number+1})
2135+
2136
2137=== added file 'payment_management_desjardins/payment_management_desjardins_view.xml'
2138--- payment_management_desjardins/payment_management_desjardins_view.xml 1970-01-01 00:00:00 +0000
2139+++ payment_management_desjardins/payment_management_desjardins_view.xml 2013-09-17 15:32:05 +0000
2140@@ -0,0 +1,22 @@
2141+<?xml version="1.0" encoding="UTF-8"?>
2142+<openerp>
2143+ <data>
2144+ <record id="view_desjardinsinfo_form" model="ir.ui.view">
2145+ <field name="name">res.company.desjardinsinfo.form</field>
2146+ <field name="model">res.company.desjardinsinfo</field>
2147+ <field name="arch" type="xml">
2148+ <form string="Desjardins harmonization info">
2149+ <field name="file_creation_number" required="1"/>
2150+ <field name="originator_id" required="1"/>
2151+ <field name="workdir" required="1"/>
2152+ <field name="bank_account_id" domain="[('company_id', '!=', False)]" widget="selection" />
2153+ <field name="account_id"/>
2154+ <field name="txn_code_debit" required="1"/>
2155+ <field name="txn_code_credit" required="1"/>
2156+ <field name="leave_vouchers_draft"/>
2157+ </form>
2158+ </field>
2159+ </record>
2160+ </data>
2161+
2162+</openerp>
2163
2164=== added file 'payment_management_desjardins/record.py'
2165--- payment_management_desjardins/record.py 1970-01-01 00:00:00 +0000
2166+++ payment_management_desjardins/record.py 2013-09-17 15:32:05 +0000
2167@@ -0,0 +1,29 @@
2168+# -*- encoding: utf-8 -*-
2169+##############################################################################
2170+#
2171+# OpenERP, Open Source Management Solution
2172+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
2173+#
2174+# This program is free software: you can redistribute it and/or modify
2175+# it under the terms of the GNU General Public License as
2176+# published by the Free Software Foundation, either version 3 of the
2177+# License, or (at your option) any later version.
2178+#
2179+# This program is distributed in the hope that it will be useful,
2180+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2181+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2182+# GNU General Public License for more details.
2183+#
2184+# You should have received a copy of the GNU General Public License
2185+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2186+#
2187+##############################################################################
2188+
2189+from __future__ import division, unicode_literals
2190+
2191+from datetime import date, datetime
2192+
2193+def yyyddd(d):
2194+ three_digit_year = d.strftime('%Y')[1:4]
2195+ day_of_year = d.strftime('%j')
2196+ return three_digit_year + day_of_year
2197
2198=== added directory 'payment_management_visa_desjardins'
2199=== added file 'payment_management_visa_desjardins/__init__.py'
2200--- payment_management_visa_desjardins/__init__.py 1970-01-01 00:00:00 +0000
2201+++ payment_management_visa_desjardins/__init__.py 2013-09-17 15:32:05 +0000
2202@@ -0,0 +1,22 @@
2203+# -*- encoding: utf-8 -*-
2204+##############################################################################
2205+#
2206+# OpenERP, Open Source Management Solution
2207+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
2208+#
2209+# This program is free software: you can redistribute it and/or modify
2210+# it under the terms of the GNU General Public License as
2211+# published by the Free Software Foundation, either version 3 of the
2212+# License, or (at your option) any later version.
2213+#
2214+# This program is distributed in the hope that it will be useful,
2215+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2216+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2217+# GNU General Public License for more details.
2218+#
2219+# You should have received a copy of the GNU General Public License
2220+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2221+#
2222+##############################################################################
2223+
2224+import payment_management_visa_desjardins
2225
2226=== added file 'payment_management_visa_desjardins/__openerp__.py'
2227--- payment_management_visa_desjardins/__openerp__.py 1970-01-01 00:00:00 +0000
2228+++ payment_management_visa_desjardins/__openerp__.py 2013-09-17 15:32:05 +0000
2229@@ -0,0 +1,44 @@
2230+# -*- encoding: utf-8 -*-
2231+##############################################################################
2232+#
2233+# OpenERP, Open Source Management Solution
2234+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
2235+#
2236+# This program is free software: you can redistribute it and/or modify
2237+# it under the terms of the GNU General Public License as
2238+# published by the Free Software Foundation, either version 3 of the
2239+# License, or (at your option) any later version.
2240+#
2241+# This program is distributed in the hope that it will be useful,
2242+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2243+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2244+# GNU General Public License for more details.
2245+#
2246+# You should have received a copy of the GNU General Public License
2247+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2248+#
2249+##############################################################################
2250+
2251+{
2252+ "name" : "Payment management module for visa desjardins",
2253+ "version" : "1.0",
2254+ "author" : "Savoir-faire Linux",
2255+ "website" : "http://www.savoirfairelinux.com",
2256+ "category" : "Accounting & Finance",
2257+ "description" : """
2258+ This module generates 'vdcoupon.txt' files from invoices.
2259+ """,
2260+ "depends" : [
2261+ 'payment_management_desjardins',
2262+ 'encrypted_credit_card',
2263+ ],
2264+ "init_xml" : [],
2265+ "update_xml" : [
2266+ ],
2267+ 'data' : [
2268+ 'payment_management_visa_desjardins_view.xml',
2269+ ],
2270+ "demo_xml" : [],
2271+ "installable" : True,
2272+ "certificate" : ''
2273+}
2274
2275=== added directory 'payment_management_visa_desjardins/i18n'
2276=== added file 'payment_management_visa_desjardins/i18n/payment_management_visa_desjardins.pot'
2277--- payment_management_visa_desjardins/i18n/payment_management_visa_desjardins.pot 1970-01-01 00:00:00 +0000
2278+++ payment_management_visa_desjardins/i18n/payment_management_visa_desjardins.pot 2013-09-17 15:32:05 +0000
2279@@ -0,0 +1,27 @@
2280+# Translation of OpenERP Server.
2281+# This file contains the translation of the following modules:
2282+# * payment_management_visa_desjardins
2283+#
2284+msgid ""
2285+msgstr ""
2286+"Project-Id-Version: OpenERP Server 7.0\n"
2287+"Report-Msgid-Bugs-To: \n"
2288+"POT-Creation-Date: 2013-08-21 13:54+0000\n"
2289+"PO-Revision-Date: 2013-08-21 13:54+0000\n"
2290+"Last-Translator: <>\n"
2291+"Language-Team: \n"
2292+"MIME-Version: 1.0\n"
2293+"Content-Type: text/plain; charset=UTF-8\n"
2294+"Content-Transfer-Encoding: \n"
2295+"Plural-Forms: \n"
2296+
2297+#. module: payment_management_visa_desjardins
2298+#: model:ir.model,name:payment_management_visa_desjardins.model_res_company
2299+msgid "Companies"
2300+msgstr ""
2301+
2302+#. module: payment_management_visa_desjardins
2303+#: field:res.company,desjardins_cc_info:0
2304+msgid "Visa Desjardins Info"
2305+msgstr ""
2306+
2307
2308=== added file 'payment_management_visa_desjardins/payment_management_visa_desjardins.py'
2309--- payment_management_visa_desjardins/payment_management_visa_desjardins.py 1970-01-01 00:00:00 +0000
2310+++ payment_management_visa_desjardins/payment_management_visa_desjardins.py 2013-09-17 15:32:05 +0000
2311@@ -0,0 +1,181 @@
2312+# -*- encoding: utf-8 -*-
2313+##############################################################################
2314+#
2315+# OpenERP, Open Source Management Solution
2316+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
2317+#
2318+# This program is free software: you can redistribute it and/or modify
2319+# it under the terms of the GNU General Public License as
2320+# published by the Free Software Foundation, either version 3 of the
2321+# License, or (at your option) any later version.
2322+#
2323+# This program is distributed in the hope that it will be useful,
2324+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2325+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2326+# GNU General Public License for more details.
2327+#
2328+# You should have received a copy of the GNU General Public License
2329+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2330+#
2331+##############################################################################
2332+
2333+# ABOUT THIS MODULE AND ENCRYPTION
2334+#
2335+# This module doesn't handle plain text CC numbers, it only has access to encrypted CC number
2336+# (managed by encrypted_credit_card). However, the text file it produces, "vdcoupons", wants plain
2337+# text CC numbers.
2338+#
2339+# Because the vdcoupons file we produce isn't going to be sent directly to Desjardins, we don't need
2340+# it to be exactly correct, but we also want to minimise the work that the external process, which
2341+# is going to decrypt the CC numbers before sending it to Desjardins. Therefore, we build the rest
2342+# of the vdcoupons file exactly as it's going to be sent. Thus, all the external process is going to
2343+# have to do is to decrypt and replace.
2344+
2345+from __future__ import division
2346+
2347+import os
2348+import re
2349+import logging
2350+import codecs
2351+
2352+from openerp.osv import orm, fields
2353+
2354+from .record import get_g_record, get_a_record, get_h_record, get_z_record
2355+
2356+_logger = logging.getLogger(__name__)
2357+
2358+class res_company_desjardinsinfo_visa(orm.Model):
2359+ _name = 'res.company.desjardinsinfo.visa'
2360+ _inherit = 'res.company.desjardinsinfo'
2361+ _columns = {
2362+ 'merchant_number': fields.char(
2363+ 'Merchant number for Desjardins',
2364+ size=8,
2365+ help="Merchant number assigned by DCS. Must be numeric."
2366+ ),
2367+ }
2368+
2369+class res_company(orm.Model):
2370+ _inherit = 'res.company'
2371+ _columns = {
2372+ 'desjardins_cc_info': fields.many2one('res.company.desjardinsinfo.visa', "Visa Desjardins Info"),
2373+ }
2374+
2375+ def _default_desjardins_cc_info(self, cr, uid, context):
2376+ res_company_desjardinsinfo = self.pool.get('res.company.desjardinsinfo.visa')
2377+ vals = {
2378+ 'txn_code_debit': '555',
2379+ 'txn_code_credit': '506',
2380+ }
2381+ res = res_company_desjardinsinfo.create(cr, uid, vals, context=context)
2382+ return res
2383+
2384+ _defaults = {
2385+ 'desjardins_cc_info': _default_desjardins_cc_info,
2386+ }
2387+
2388+def send_payment_func_visa_desjardins(cpr_obj, cr, uid, customer_payment_requests, context=None):
2389+ company_obj = cpr_obj.pool.get('res.company')
2390+ # active_company() is defined in customer_payment_request, which isn't directly in our
2391+ # dependencies, but for now, this function can't be called unless CPR is installed, so we're
2392+ # alright. This might have to change when our module starts supporting more workflow.
2393+ company = company_obj.active_company(cr, uid, context=context)
2394+ desjinfo = company.desjardins_cc_info
2395+ directory = desjinfo.workdir
2396+
2397+ if not desjinfo.is_complete():
2398+ return False
2399+
2400+ toprocess = [cpr for cpr in customer_payment_requests if cpr.bank_account_id.state == 'credit_card']
2401+ if not toprocess:
2402+ return False
2403+ total_number_of_credit = 0
2404+ total_number_of_debit = 0
2405+ total_value_of_credit = 0
2406+ total_value_of_debit = 0
2407+ lines = []
2408+
2409+ # Type A record
2410+ a_record = get_a_record(len(lines)+1, company.desjardins_cc_info)
2411+ lines.append(a_record)
2412+
2413+ # Type G records
2414+ for cpr in toprocess:
2415+ bank_account = cpr.bank_account_id
2416+ partner = bank_account.partner_id
2417+ # The amount is in cents
2418+ amount = round(cpr.amount * 100)
2419+ if amount > 0:
2420+ total_number_of_debit += 1
2421+ total_value_of_debit += amount
2422+ else:
2423+ amount = abs(amount)
2424+ total_number_of_credit += 1
2425+ total_value_of_credit += amount
2426+ cc_number = '[RSA]%s[/RSA]' % bank_account.encrypted_cc_number.strip()
2427+ g_record = get_g_record(
2428+ len(lines)+1, company, amount, cc_number, bank_account.expiration_date, partner.id
2429+ )
2430+ lines.append(g_record)
2431+ cpr.write({'state': 'sent', 'batch_ref': 'visa_desjardins'}, context=context)
2432+
2433+ # Type H record
2434+ h_record = get_h_record(
2435+ len(lines)+1, company.desjardins_cc_info,
2436+ total_number_of_debit, total_value_of_debit,
2437+ total_number_of_credit, total_value_of_credit
2438+ )
2439+ lines.append(h_record)
2440+
2441+ # Type Z record
2442+ z_record = get_z_record(
2443+ len(lines)+1, company.desjardins_cc_info,
2444+ total_number_of_debit, total_value_of_debit,
2445+ total_number_of_credit, total_value_of_credit
2446+ )
2447+ lines.append(z_record)
2448+ with codecs.open(os.path.join(directory, 'vdcoupon.txt'), 'wt', encoding='latin-1') as fp:
2449+ fp.write(u'\n'.join(lines))
2450+ desjinfo.increase_file_number()
2451+ return True
2452+
2453+def receive_payment_func_visa_desjardins(cpr_obj, cr, uid, context=None):
2454+ company_obj = cpr_obj.pool.get('res.company')
2455+ # see comment about active_company() in send_payment_func_visa_desjardins()
2456+ company = company_obj.active_company(cr, uid, context=context)
2457+ desjinfo = company.desjardins_cc_info
2458+ directory = desjinfo.workdir
2459+ account_id = desjinfo.account_id.id
2460+ journal_id = desjinfo.bank_account_id.journal_id.id
2461+
2462+ filenames = os.listdir(directory)
2463+ filenames = [fn for fn in filenames if re.match(r"\d{8}-\d{4}-vdcoupon\.acc", fn)]
2464+ if not filenames:
2465+ # Nothing to process
2466+ return
2467+ for filename in filenames:
2468+ filepath = os.path.join(directory, filename)
2469+ lines = open(filepath, 'rt').readlines()
2470+ for line in lines:
2471+ if not line.startswith('G'):
2472+ continue
2473+ partner_id = int(line[118:118+19])
2474+ amount = int(line[27:27+10]) / 100
2475+ cpr_obj.register_payment_acceptation(
2476+ cr, uid, partner_id, amount, account_id, journal_id,
2477+ leave_draft=desjinfo.leave_vouchers_draft, context=context
2478+ )
2479+ os.rename(filepath, filepath[:-3] + 'imported')
2480+
2481+ # Any CPR with a "visa_desjardins" batch_ref can be considered as rejected
2482+ search_domain = [('batch_ref', '=', 'visa_desjardins'), ('state', '=', 'sent')]
2483+ rejected_cpr_ids = cpr_obj.search(cr, uid, search_domain, context=context)
2484+ for cpr in cpr_obj.browse(cr, uid, rejected_cpr_ids, context=context):
2485+ cpr_obj.register_payment_refusal(cr, uid, cpr.partner_id.id, context=context)
2486+ return True
2487+
2488+try:
2489+ from customer_payment_request import registry
2490+ registry.register(send_payment_func_visa_desjardins, receive_payment_func_visa_desjardins)
2491+except ImportError:
2492+ pass
2493
2494=== added file 'payment_management_visa_desjardins/payment_management_visa_desjardins_view.xml'
2495--- payment_management_visa_desjardins/payment_management_visa_desjardins_view.xml 1970-01-01 00:00:00 +0000
2496+++ payment_management_visa_desjardins/payment_management_visa_desjardins_view.xml 2013-09-17 15:32:05 +0000
2497@@ -0,0 +1,35 @@
2498+<?xml version="1.0" encoding="UTF-8"?>
2499+<openerp>
2500+ <data>
2501+ <!-- I've tried inheriting the view from res.company.desjardinsinfo, but it didn't work. -->
2502+ <record id="view_desjardinsinfo_visa_form" model="ir.ui.view">
2503+ <field name="name">res.company.desjardinsinfo.visa.form</field>
2504+ <field name="model">res.company.desjardinsinfo.visa</field>
2505+ <field name="arch" type="xml">
2506+ <form string="Desjardins harmonization info">
2507+ <field name="merchant_number" required="1"/>
2508+ <field name="file_creation_number" required="1"/>
2509+ <field name="originator_id" required="1"/>
2510+ <field name="workdir" required="1"/>
2511+ <field name="bank_account_id" domain="[('company_id', '!=', False)]" widget="selection" />
2512+ <field name="account_id"/>
2513+ <field name="txn_code_debit" required="1"/>
2514+ <field name="txn_code_credit" required="1"/>
2515+ <field name="leave_vouchers_draft"/>
2516+ </form>
2517+ </field>
2518+ </record>
2519+
2520+ <record id="view_company_form" model="ir.ui.view">
2521+ <field name="name">res.company.form</field>
2522+ <field name="model">res.company</field>
2523+ <field name="inherit_id" ref="base.view_company_form"/>
2524+ <field name="arch" type="xml">
2525+ <xpath expr="//page[@string='General Information']/group/group" position="inside">
2526+ <field name="desjardins_cc_info"/>
2527+ </xpath>
2528+ </field>
2529+ </record>
2530+ </data>
2531+
2532+</openerp>
2533
2534=== added file 'payment_management_visa_desjardins/record.py'
2535--- payment_management_visa_desjardins/record.py 1970-01-01 00:00:00 +0000
2536+++ payment_management_visa_desjardins/record.py 2013-09-17 15:32:05 +0000
2537@@ -0,0 +1,108 @@
2538+# -*- encoding: utf-8 -*-
2539+##############################################################################
2540+#
2541+# OpenERP, Open Source Management Solution
2542+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
2543+#
2544+# This program is free software: you can redistribute it and/or modify
2545+# it under the terms of the GNU General Public License as
2546+# published by the Free Software Foundation, either version 3 of the
2547+# License, or (at your option) any later version.
2548+#
2549+# This program is distributed in the hope that it will be useful,
2550+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2551+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2552+# GNU General Public License for more details.
2553+#
2554+# You should have received a copy of the GNU General Public License
2555+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2556+#
2557+##############################################################################
2558+
2559+from __future__ import division, unicode_literals
2560+
2561+from datetime import date, datetime
2562+
2563+from payment_management_desjardins.record import yyyddd
2564+
2565+def get_a_record(seq, desjinfo):
2566+ return ''.join([
2567+ 'A',
2568+ '%09d' % seq,
2569+ desjinfo.originator_id[:10].upper().ljust(10, ' '),
2570+ '%04d' % desjinfo.file_creation_number,
2571+ yyyddd(datetime.now()),
2572+ '81510',
2573+ ' ' * 1429,
2574+ ])
2575+
2576+def get_h_record(seq, desjinfo, num_debit, total_debit, num_credit, total_credit):
2577+ return ''.join([
2578+ 'H',
2579+ '%09d' % seq,
2580+ desjinfo.originator_id[:10].upper().ljust(10, ' '),
2581+ '%04d' % desjinfo.file_creation_number,
2582+ '%014d' % total_debit,
2583+ '%08d' % num_debit,
2584+ '%014d' % total_credit,
2585+ '%08d' % num_credit,
2586+ '1',
2587+ '0' * 10,
2588+ '0' * 4,
2589+ desjinfo.merchant_number[:8].rjust(8, '0'),
2590+ ' ' * 1373,
2591+ ])
2592+
2593+def get_z_record(seq, desjinfo, num_debit, total_debit, num_credit, total_credit):
2594+ return ''.join([
2595+ 'Z',
2596+ '%09d' % seq,
2597+ desjinfo.originator_id[:10].upper().ljust(10, ' '),
2598+ '%04d' % desjinfo.file_creation_number,
2599+ '%014d' % total_debit,
2600+ '%08d' % num_debit,
2601+ '%014d' % total_credit,
2602+ '%08d' % num_credit,
2603+ '1',
2604+ '0' * 10,
2605+ '0' * 4,
2606+ ' ' * 1381,
2607+ ])
2608+
2609+def get_g_record(seq, company, amount, cc_number, cc_expiration, partner_ref):
2610+ desjinfo = company.desjardins_cc_info
2611+ # amount is an integer representing cents
2612+ if amount > 0:
2613+ txn_type = desjinfo.txn_code_debit
2614+ else:
2615+ txn_type = desjinfo.txn_code_credit
2616+ amount = abs(amount)
2617+ return ''.join([
2618+ 'G', # Alpha 1
2619+ '%09d' % seq, # Num 9
2620+ desjinfo.originator_id[:10].upper().ljust(10, ' '), # Alpha 10
2621+ '%04d' % desjinfo.file_creation_number,# Num 4
2622+ txn_type, # Num 3
2623+ '%010d' % amount, # Num 10
2624+ date.today().strftime("%m%d%y"), # Num 6
2625+ cc_number.rjust(19, '0'),
2626+ '0' * 2, # Alpha 2
2627+ ' ' * 2, # Alpha 2
2628+ ' ' * 2, # Alpha 2
2629+ ' ', # Alpha 1
2630+ ' ', # Alpha 1
2631+ desjinfo.merchant_number[:8].rjust(8, '0'), # Num 8
2632+ company.name[:25].ljust(25, ' '), # Alpha 25
2633+ (company.city or '')[:13].ljust(13, ' '), # Alpha 13
2634+ company.state_id.code or ' ', # Alpha 2
2635+ '%019d' % partner_ref, # Num 19
2636+ '0' * 6, # Alpha 6
2637+ '0' * 9, # Alpha 9
2638+ '0' * 4, # Num 4
2639+ '0' * 4, # Num 4
2640+ ' ' * 5, # Alpha 5
2641+ '0', # Num 1
2642+ '0' * 8, # Alpha 8
2643+ cc_expiration, # Alpha 4
2644+ ' ' * 26, # Alpha 26
2645+ ])
2646
2647=== added directory 'payment_management_visa_desjardins/scripts'
2648=== added file 'payment_management_visa_desjardins/scripts/vdcoupon_watch_recv.py'
2649--- payment_management_visa_desjardins/scripts/vdcoupon_watch_recv.py 1970-01-01 00:00:00 +0000
2650+++ payment_management_visa_desjardins/scripts/vdcoupon_watch_recv.py 2013-09-17 15:32:05 +0000
2651@@ -0,0 +1,78 @@
2652+# -*- encoding: utf-8 -*-
2653+##############################################################################
2654+#
2655+# OpenERP, Open Source Management Solution
2656+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
2657+#
2658+# This program is free software: you can redistribute it and/or modify
2659+# it under the terms of the GNU Affero General Public License as
2660+# published by the Free Software Foundation, either version 3 of the
2661+# License, or (at your option) any later version.
2662+#
2663+# This program is distributed in the hope that it will be useful,
2664+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2665+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2666+# GNU Affero General Public License for more details.
2667+#
2668+# You should have received a copy of the GNU Affero General Public License
2669+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2670+#
2671+##############################################################################
2672+
2673+import argparse
2674+import time
2675+import os.path as op
2676+from datetime import datetime
2677+import re
2678+
2679+import paramiko
2680+
2681+def parse_options():
2682+ parser = argparse.ArgumentParser(description='Wait for vdcoupon.acc from Desjardins, remove CC numbers and timestamp it.')
2683+ parser.add_argument('dest_folder', help="Folder where vdcoupon is sent.")
2684+ parser.add_argument('ssh_cred', help="Desjardins SSH creds (user@host:path).")
2685+ return parser.parse_args()
2686+
2687+def process_file(sftp, source_path, dest_folder):
2688+ source_fp = sftp.open(source_path, 'rt')
2689+ lines = source_fp.readlines()
2690+ newlines = []
2691+ for line in lines:
2692+ if line.startswith('G'):
2693+ chars = list(line)
2694+ # credit card numbers are the 19 characters starting at index 1525
2695+ chars[43:43+19] = '0' * 19
2696+ line = ''.join(chars)
2697+ newlines.append(line)
2698+ source_fp.close()
2699+ timestamp = datetime.now().strftime('%Y%m%d-%H%M')
2700+ result_filename = op.join(dest_folder, '%s-vdcoupon.acc' % timestamp)
2701+ with open(result_filename, 'wt') as result_fp:
2702+ result_fp.writelines(newlines)
2703+ sftp.remove(source_path)
2704+
2705+def watch_forever(options):
2706+ m = re.match(r"(.+)@([^:]+):?(.*)", options.ssh_cred)
2707+ username = m.group(1)
2708+ hostname = m.group(2)
2709+ sshpath = m.group(3)
2710+ ssh = paramiko.SSHClient()
2711+ ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
2712+ ssh.load_system_host_keys()
2713+ ssh.connect(hostname, 22, username)
2714+ sftp = ssh.open_sftp()
2715+ while True:
2716+ if 'vdcoupon.acc' in sftp.listdir(sshpath):
2717+ print "File found! Processing"
2718+ source_path = op.join(sshpath, 'vdcoupon.acc')
2719+ process_file(sftp, source_path, options.dest_folder)
2720+ print "Process done, file deleted"
2721+ time.sleep(10)
2722+
2723+def main():
2724+ options = parse_options()
2725+ print "Starting to watch for vdcoupon.acc with dest {}...".format(options.dest_folder)
2726+ watch_forever(options)
2727+
2728+if __name__ == '__main__':
2729+ main()
2730\ No newline at end of file
2731
2732=== added file 'payment_management_visa_desjardins/scripts/vdcoupon_watch_send.py'
2733--- payment_management_visa_desjardins/scripts/vdcoupon_watch_send.py 1970-01-01 00:00:00 +0000
2734+++ payment_management_visa_desjardins/scripts/vdcoupon_watch_send.py 2013-09-17 15:32:05 +0000
2735@@ -0,0 +1,84 @@
2736+# -*- encoding: utf-8 -*-
2737+##############################################################################
2738+#
2739+# OpenERP, Open Source Management Solution
2740+# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
2741+#
2742+# This program is free software: you can redistribute it and/or modify
2743+# it under the terms of the GNU Affero General Public License as
2744+# published by the Free Software Foundation, either version 3 of the
2745+# License, or (at your option) any later version.
2746+#
2747+# This program is distributed in the hope that it will be useful,
2748+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2749+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2750+# GNU Affero General Public License for more details.
2751+#
2752+# You should have received a copy of the GNU Affero General Public License
2753+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2754+#
2755+##############################################################################
2756+
2757+import argparse
2758+import time
2759+import os
2760+import os.path as op
2761+import re
2762+import binascii
2763+
2764+import paramiko
2765+from Crypto.PublicKey import RSA
2766+
2767+def parse_options():
2768+ parser = argparse.ArgumentParser(description='Wait for vdcoupon.txt from OpenERP, decrypt it and send it to Desjardins.')
2769+ parser.add_argument('watched_folder', help="Folder where vdcoupon is sent.")
2770+ parser.add_argument('private_key', help="Private RSA key to decrypt CC numbers with.")
2771+ parser.add_argument('ssh_cred', help="Desjardins SSH creds (user@host:path).")
2772+ return parser.parse_args()
2773+
2774+def process_file(watched_file, private_key):
2775+ def repl(match):
2776+ encrypted_cc_base64 = match.group(1)
2777+ encrypted_cc = binascii.a2b_base64(encrypted_cc_base64)
2778+ decrypted_cc = private_key.decrypt(encrypted_cc)
2779+ return decrypted_cc.zfill(19)
2780+
2781+ with open(watched_file, 'rt') as source_fp:
2782+ contents = source_fp.read()
2783+ contents = re.sub(r"\[RSA\](.+?)\[/RSA\]", repl, contents)
2784+ return contents
2785+
2786+def send_contents(hostname, username, sshpath, contents):
2787+ ssh = paramiko.SSHClient()
2788+ ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
2789+ ssh.load_system_host_keys()
2790+ ssh.connect(hostname, 22, username)
2791+ sftp = ssh.open_sftp()
2792+ fp = sftp.open(op.join(sshpath, 'vdcoupon.txt'), 'wt')
2793+ fp.write(contents)
2794+ fp.close()
2795+
2796+def watch_forever(options):
2797+ watched_file = op.join(options.watched_folder, 'vdcoupon.txt')
2798+ with open(options.private_key, 'rt') as key_fp:
2799+ private_key = RSA.importKey(key_fp.read())
2800+ m = re.match(r"(.+)@([^:]+):?(.*)", options.ssh_cred)
2801+ username = m.group(1)
2802+ hostname = m.group(2)
2803+ sshpath = m.group(3)
2804+ while True:
2805+ if op.exists(watched_file):
2806+ print "File found! Processing"
2807+ contents = process_file(watched_file, private_key)
2808+ send_contents(hostname, username, sshpath, contents)
2809+ os.remove(watched_file)
2810+ print "Process done, file deleted"
2811+ time.sleep(10)
2812+
2813+def main():
2814+ options = parse_options()
2815+ print "Starting to watch for vdcoupon.txt in {}...".format(options.watched_folder)
2816+ watch_forever(options)
2817+
2818+if __name__ == '__main__':
2819+ main()
2820\ No newline at end of file

Subscribers

People subscribed via source and target branches