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
=== added directory 'customer_payment_request'
=== added file 'customer_payment_request/__init__.py'
--- customer_payment_request/__init__.py 1970-01-01 00:00:00 +0000
+++ customer_payment_request/__init__.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,2 @@
1import customer_payment_request
2import registry
03
=== added file 'customer_payment_request/__openerp__.py'
--- customer_payment_request/__openerp__.py 1970-01-01 00:00:00 +0000
+++ customer_payment_request/__openerp__.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,71 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22{
23 "name" : "Customer Payment Requests",
24 "version" : "1.0",
25 "author" : "Savoir-faire Linux",
26 "website" : "http://www.savoirfairelinux.com",
27 "category" : "Accounting & Finance",
28 "description" : """
29Deals with mass payment requests
30--------------------------------
31
32When dealing with mass payments, we typically have a bunch of customers that owe us money and
33we want to charge their credit card, or bank account, or whatever. We have that information
34in one or more res.partner.bank record.
35
36The workflow here is that for every partner owing us money, we create a payment request in
37"waiting" mode, associating that payment with the first res.partner.bank associated with that
38partner.
39
40Then, we notify our registered payment handlers and they handle the payment request when they
41can handle the bank account associated with the request. The payment is then put in "sent" mode
42so that it isn't sent again until we receive the banks' responses.
43
44Then, we wait for the responses from banks by periodically polling for it. Upon receiving those
45responses, the bank handlers calls register_payment_refusal() or register_payment_acceptation().
46
47If accepted, the payment request is deleted and a validated voucher is created for the
48customer payment.
49
50If refused, the payment request is put back in waiting mode. If the partner has more than one
51bank account, we set the request's bank to the next in line.
52
53This module registers 3 cron jobs:
54
551. send_customer_payment_requests(): Create a payment request for each partner owing us money and
56 tell bank handlers to send those requests.
572. send_waiting_requests(): Retry every payment requests in "waiting" mode (being in this mode
58 because the previous payment attempt failed).
593. receive_customer_payment_response(): Poll bank handlers for responses from banks. If there are
60 any, the bank handlers are responsible for registering payment acceptation or refusal.
61 """,
62 "depends" : ['account_voucher'],
63 "init_xml" : [],
64 'data' : [
65 'customer_payment_request_data.xml',
66 'customer_payment_request_view.xml',
67 ],
68 "demo_xml" : [],
69 "installable" : True,
70 "certificate" : ''
71}
072
=== added file 'customer_payment_request/customer_payment_request.py'
--- customer_payment_request/customer_payment_request.py 1970-01-01 00:00:00 +0000
+++ customer_payment_request/customer_payment_request.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,220 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22import logging
23from datetime import date
24
25from openerp.osv import orm, fields
26from openerp.tools.translate import _
27
28import registry
29
30logger = logging.getLogger(__name__)
31
32class res_company_cprinfo(orm.Model):
33 _name = 'res.company.cprinfo'
34 _columns = {
35 'process_payments': fields.boolean(
36 string=u"Process Payments",
37 help=u"Process partners with an outstanding payment",
38 ),
39 'process_refunds': fields.boolean(
40 string=u"Process Refunds",
41 help=u"Process partners with an outstanding refund",
42 ),
43 }
44 _defaults = {
45 'process_payments': True,
46 'process_refunds': True,
47 }
48
49 def name_get(self, cr, uid, ids, context=None):
50 return dict.fromkeys(ids, _("Customer Payment Request Info"))
51
52class res_company(orm.Model):
53 _inherit = 'res.company'
54 _columns = {
55 'customer_payment_request_info': fields.many2one('res.company.cprinfo', "Customer Payment Request Info"),
56 }
57
58 def _default_customer_payment_request_info(self, cr, uid, context=None):
59 res_company_cprinfo = self.pool.get('res.company.cprinfo')
60 res = res_company_cprinfo.create(cr, uid, {}, context=context)
61 return res
62
63 _defaults = {
64 'customer_payment_request_info': _default_customer_payment_request_info,
65 }
66
67 def active_company(self, cr, uid, context=None):
68 """Returns the company attached to our logged user.
69 """
70 res_users = self.pool.get('res.users')
71 active_user = res_users.browse(cr, uid, uid, context=context)
72 return active_user.company_id
73
74class customer_payment_request(orm.Model):
75 _name = 'customer.payment.request'
76
77 _columns = {
78 'bank_account_id': fields.many2one(
79 'res.partner.bank',
80 required=True,
81 string="Bank account the request was made at",
82 ),
83 'partner_id': fields.related(
84 'bank_account_id',
85 'partner_id',
86 obj='res.partner',
87 type='many2one',
88 required=True,
89 string="Customer to charge",
90 ),
91 'amount': fields.float(string="Amount to charge", required=True),
92 'batch_ref': fields.char(
93 size=64,
94 string="Batch payment reference",
95 ),
96 'state': fields.selection(
97 [
98 ('waiting', "Waiting to be sent"),
99 ('sent', "Sent"),
100 ],
101 readonly=True,
102 required=True,
103 string="Payment status",
104 ),
105 }
106
107 def send_customer_payment_requests(self, cr, uid, ids=None, context=None):
108 partner_pool = self.pool.get('res.partner')
109 company_pool = self.pool.get('res.company')
110 currency_pool = self.pool.get('res.currency')
111 company = company_pool.active_company(cr, uid, context=context)
112 cprinfo = company.customer_payment_request_info
113 search_domain = [('company_id', '=', company.id)]
114 partner_ids = partner_pool.search(cr, uid, search_domain, context=context)
115 partners = partner_pool.browse(cr, uid, partner_ids, context=context)
116
117 def should_charge(partner):
118 amount = p.credit - p.debit
119 if currency_pool.is_zero(cr, uid, company.currency_id, amount):
120 # Nothing to charge or refund
121 return False
122 if not cprinfo.process_payments and amount > 0:
123 return False
124 if not cprinfo.process_refunds and amount < 0:
125 return False
126 # Fetch the preferred payment type and see if it's a credit card. If it isn't, we
127 # don't process it here.
128 if not partner.bank_ids:
129 return False
130 search_domain = [('partner_id', '=', partner.id)]
131 if self.search(cr, uid, search_domain, context=context):
132 # We're already trying to charge this partner
133 return False
134 return True
135
136 toprocess = [p for p in partners if should_charge(p)]
137 if not toprocess:
138 return True
139
140 for p in toprocess:
141 bank_account = p.bank_ids[0]
142 amount = p.credit - p.debit
143 vals = {
144 'bank_account_id': bank_account.id,
145 'amount': amount,
146 'state': 'waiting',
147 }
148 self.create(cr, uid, vals, context=context)
149
150 # Now that payment request records have been created, let's send them
151 self.send_waiting_requests(cr, uid, context=context)
152 return True
153
154 def send_waiting_requests(self, cr, uid, ids=None, context=None):
155 # ping our registered handlers to process them.
156 for handler_func in registry.SEND_PAYMENT_FUNCS:
157 search_domain = [('state', '=', 'waiting')]
158 cpr_ids = self.search(cr, uid, search_domain, context=context)
159 if not cpr_ids:
160 # everything is processed!
161 break
162 cprs = self.browse(cr, uid, cpr_ids, context=context)
163 handler_func(self, cr, uid, cprs, context=context)
164
165 cpr_ids = self.search(cr, uid, [('state', '=', 'waiting')], context=context)
166 if cpr_ids:
167 # We still have waiting requests! We have a problem because they should all have been
168 # handled. Eventually, we should have some kind of "problem review" UI, but for now, we
169 # just log a warning.
170 logger.warning("%d customer payment request(s) weren't processed by any handler", len(cpr_ids))
171 return True
172
173 def receive_customer_payment_response(self, cr, uid, ids=None, context=None):
174 for handler_func in registry.RECEIVE_PAYMENT_FUNCS:
175 # handler_func will call register_payment_acceptation or register_payment_refusal
176 handler_func(self, cr, uid, context=context)
177 return True
178
179 def register_payment_refusal(self, cr, uid, partner_id, context=None):
180 [cpr_id] = self.search(cr, uid, [('partner_id', '=', partner_id)])
181 cpr = self.browse(cr, uid, cpr_id, context=context)
182 partner = cpr.partner_id
183 if len(partner.bank_ids) > 1:
184 # We have multiple payment options, let's cycle through them
185 try:
186 index = [b.id for b in partner.bank_ids].index(cpr.bank_account_id.id) + 1
187 except ValueError:
188 # weird, our account isn't there. Maybe it has been removed. Let's go back to 0
189 index = 0
190 if index >= len(partner.bank_ids):
191 index = 0
192 cpr.write({'bank_account_id': partner.bank_ids[index].id}, context=context)
193 cpr.write({'state': 'waiting'}, context=context)
194 return True
195
196 def register_payment_acceptation(self, cr, uid, partner_id, amount, account_id, journal_id, leave_draft=False, context=None):
197 logger.info("Importing payment of amount %d for partner %d", amount, partner_id)
198 account_voucher_pool = self.pool.get('account.voucher')
199 account_voucher_line_pool = self.pool.get('account.voucher.line')
200 vals = {
201 'type': 'receipt',
202 'partner_id': partner_id,
203 'amount': amount,
204 'account_id': account_id,
205 'journal_id': journal_id,
206 }
207 voucher_id = account_voucher_pool.create(cr, uid, vals, context=context)
208 line_vals = account_voucher_pool.recompute_voucher_lines(
209 cr, uid, [voucher_id], partner_id, journal_id, amount, None, 'receipt', date.today(),
210 context=context
211 )
212 all_lines = line_vals['value']['line_cr_ids'] + line_vals['value']['line_dr_ids']
213 for line in all_lines:
214 line['voucher_id'] = voucher_id
215 account_voucher_line_pool.create(cr, uid, line, context=context)
216 if not leave_draft:
217 account_voucher_pool.action_move_line_create(cr, uid, [voucher_id], context=context)
218 [cpr_id] = self.search(cr, uid, [('partner_id', '=', partner_id)], context=context)
219 self.unlink(cr, uid, cpr_id)
220 return True
0221
=== added file 'customer_payment_request/customer_payment_request_data.xml'
--- customer_payment_request/customer_payment_request_data.xml 1970-01-01 00:00:00 +0000
+++ customer_payment_request/customer_payment_request_data.xml 2013-09-17 15:32:05 +0000
@@ -0,0 +1,43 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3 <data noupdate="1">
4 <record id="ir_cron_export_customer_payment_requests" model="ir.cron">
5 <field name="name">Export customer payment requests</field>
6 <field name="user_id" ref="base.user_root"/>
7 <field name="interval_number">1</field>
8 <field name="interval_type">hours</field>
9 <field name="numbercall">-1</field>
10 <field eval="False" name="active"/>
11 <field eval="False" name="doall"/>
12 <field eval="'customer.payment.request'" name="model"/>
13 <field eval="'send_customer_payment_requests'" name="function"/>
14 <field name="args">()</field>
15 </record>
16
17 <record id="ir_cron_retry_failed_payment_requests" model="ir.cron">
18 <field name="name">Retry failed payment requests</field>
19 <field name="user_id" ref="base.user_root"/>
20 <field name="interval_number">1</field>
21 <field name="interval_type">hours</field>
22 <field name="numbercall">-1</field>
23 <field eval="False" name="active"/>
24 <field eval="False" name="doall"/>
25 <field eval="'customer.payment.request'" name="model"/>
26 <field eval="'send_waiting_requests'" name="function"/>
27 <field name="args">()</field>
28 </record>
29
30 <record id="ir_cron_import_customer_payment_requests" model="ir.cron">
31 <field name="name">Import customer payment requests</field>
32 <field name="user_id" ref="base.user_root"/>
33 <field name="interval_number">1</field>
34 <field name="interval_type">hours</field>
35 <field name="numbercall">-1</field>
36 <field eval="False" name="active"/>
37 <field eval="False" name="doall"/>
38 <field eval="'customer.payment.request'" name="model"/>
39 <field eval="'receive_customer_payment_response'" name="function"/>
40 <field name="args">()</field>
41 </record>
42 </data>
43</openerp>
0\ No newline at end of file44\ No newline at end of file
145
=== added file 'customer_payment_request/customer_payment_request_view.xml'
--- customer_payment_request/customer_payment_request_view.xml 1970-01-01 00:00:00 +0000
+++ customer_payment_request/customer_payment_request_view.xml 2013-09-17 15:32:05 +0000
@@ -0,0 +1,16 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3 <data>
4 <record id="view_company_form" model="ir.ui.view">
5 <field name="name">res.company.form</field>
6 <field name="model">res.company</field>
7 <field name="inherit_id" ref="base.view_company_form"/>
8 <field name="arch" type="xml">
9 <xpath expr="//page[@string='General Information']/group/group" position="inside">
10 <field name="customer_payment_request_info"/>
11 </xpath>
12 </field>
13 </record>
14 </data>
15
16</openerp>
017
=== added directory 'customer_payment_request/docs'
=== added file 'customer_payment_request/docs/Makefile'
--- customer_payment_request/docs/Makefile 1970-01-01 00:00:00 +0000
+++ customer_payment_request/docs/Makefile 2013-09-17 15:32:05 +0000
@@ -0,0 +1,153 @@
1# Makefile for Sphinx documentation
2#
3
4# You can set these variables from the command line.
5SPHINXOPTS =
6SPHINXBUILD = sphinx-build
7PAPER =
8BUILDDIR = _build
9
10# Internal variables.
11PAPEROPT_a4 = -D latex_paper_size=a4
12PAPEROPT_letter = -D latex_paper_size=letter
13ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14# the i18n builder cannot share the environment and doctrees with the others
15I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
16
17.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18
19help:
20 @echo "Please use \`make <target>' where <target> is one of"
21 @echo " html to make standalone HTML files"
22 @echo " dirhtml to make HTML files named index.html in directories"
23 @echo " singlehtml to make a single large HTML file"
24 @echo " pickle to make pickle files"
25 @echo " json to make JSON files"
26 @echo " htmlhelp to make HTML files and a HTML help project"
27 @echo " qthelp to make HTML files and a qthelp project"
28 @echo " devhelp to make HTML files and a Devhelp project"
29 @echo " epub to make an epub"
30 @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 @echo " text to make text files"
33 @echo " man to make manual pages"
34 @echo " texinfo to make Texinfo files"
35 @echo " info to make Texinfo files and run them through makeinfo"
36 @echo " gettext to make PO message catalogs"
37 @echo " changes to make an overview of all changed/added/deprecated items"
38 @echo " linkcheck to check all external links for integrity"
39 @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40
41clean:
42 -rm -rf $(BUILDDIR)/*
43
44html:
45 $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 @echo
47 @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48
49dirhtml:
50 $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 @echo
52 @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53
54singlehtml:
55 $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 @echo
57 @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58
59pickle:
60 $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 @echo
62 @echo "Build finished; now you can process the pickle files."
63
64json:
65 $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 @echo
67 @echo "Build finished; now you can process the JSON files."
68
69htmlhelp:
70 $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 @echo
72 @echo "Build finished; now you can run HTML Help Workshop with the" \
73 ".hhp project file in $(BUILDDIR)/htmlhelp."
74
75qthelp:
76 $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 @echo
78 @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OpenERPBankPayments.qhcp"
81 @echo "To view the help file:"
82 @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OpenERPBankPayments.qhc"
83
84devhelp:
85 $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 @echo
87 @echo "Build finished."
88 @echo "To view the help file:"
89 @echo "# mkdir -p $$HOME/.local/share/devhelp/OpenERPBankPayments"
90 @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OpenERPBankPayments"
91 @echo "# devhelp"
92
93epub:
94 $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 @echo
96 @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97
98latex:
99 $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 @echo
101 @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 "(use \`make latexpdf' here to do that automatically)."
104
105latexpdf:
106 $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 @echo "Running LaTeX files through pdflatex..."
108 $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110
111text:
112 $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 @echo
114 @echo "Build finished. The text files are in $(BUILDDIR)/text."
115
116man:
117 $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 @echo
119 @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120
121texinfo:
122 $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 @echo
124 @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 @echo "Run \`make' in that directory to run these through makeinfo" \
126 "(use \`make info' here to do that automatically)."
127
128info:
129 $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 @echo "Running Texinfo files through makeinfo..."
131 make -C $(BUILDDIR)/texinfo info
132 @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133
134gettext:
135 $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 @echo
137 @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138
139changes:
140 $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 @echo
142 @echo "The overview file is in $(BUILDDIR)/changes."
143
144linkcheck:
145 $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 @echo
147 @echo "Link check complete; look for any errors in the above output " \
148 "or in $(BUILDDIR)/linkcheck/output.txt."
149
150doctest:
151 $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 @echo "Testing of doctests in the sources finished, look at the " \
153 "results in $(BUILDDIR)/doctest/output.txt."
0154
=== added file 'customer_payment_request/docs/conf.py'
--- customer_payment_request/docs/conf.py 1970-01-01 00:00:00 +0000
+++ customer_payment_request/docs/conf.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,242 @@
1# -*- coding: utf-8 -*-
2#
3# OpenERP Bank Payments documentation build configuration file, created by
4# sphinx-quickstart on Wed Sep 4 09:42:11 2013.
5#
6# This file is execfile()d with the current directory set to its containing dir.
7#
8# Note that not all possible configuration values are present in this
9# autogenerated file.
10#
11# All configuration values have a default; values that are commented out
12# serve to show the default.
13
14import sys, os
15
16# If extensions (or modules to document with autodoc) are in another directory,
17# add these directories to sys.path here. If the directory is relative to the
18# documentation root, use os.path.abspath to make it absolute, like shown here.
19#sys.path.insert(0, os.path.abspath('.'))
20
21# -- General configuration -----------------------------------------------------
22
23# If your documentation needs a minimal Sphinx version, state it here.
24#needs_sphinx = '1.0'
25
26# Add any Sphinx extension module names here, as strings. They can be extensions
27# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
28extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo']
29
30# Add any paths that contain templates here, relative to this directory.
31templates_path = ['_templates']
32
33# The suffix of source filenames.
34source_suffix = '.rst'
35
36# The encoding of source files.
37#source_encoding = 'utf-8-sig'
38
39# The master toctree document.
40master_doc = 'index'
41
42# General information about the project.
43project = u'OpenERP Bank Payments'
44copyright = u'2013, Savoir-faire Linux'
45
46# The version info for the project you're documenting, acts as replacement for
47# |version| and |release|, also used in various other places throughout the
48# built documents.
49#
50# The short X.Y version.
51version = '1.0'
52# The full version, including alpha/beta/rc tags.
53release = '1.0'
54
55# The language for content autogenerated by Sphinx. Refer to documentation
56# for a list of supported languages.
57#language = None
58
59# There are two options for replacing |today|: either, you set today to some
60# non-false value, then it is used:
61#today = ''
62# Else, today_fmt is used as the format for a strftime call.
63#today_fmt = '%B %d, %Y'
64
65# List of patterns, relative to source directory, that match files and
66# directories to ignore when looking for source files.
67exclude_patterns = ['_build']
68
69# The reST default role (used for this markup: `text`) to use for all documents.
70#default_role = None
71
72# If true, '()' will be appended to :func: etc. cross-reference text.
73#add_function_parentheses = True
74
75# If true, the current module name will be prepended to all description
76# unit titles (such as .. function::).
77#add_module_names = True
78
79# If true, sectionauthor and moduleauthor directives will be shown in the
80# output. They are ignored by default.
81#show_authors = False
82
83# The name of the Pygments (syntax highlighting) style to use.
84pygments_style = 'sphinx'
85
86# A list of ignored prefixes for module index sorting.
87#modindex_common_prefix = []
88
89
90# -- Options for HTML output ---------------------------------------------------
91
92# The theme to use for HTML and HTML Help pages. See the documentation for
93# a list of builtin themes.
94html_theme = 'default'
95
96# Theme options are theme-specific and customize the look and feel of a theme
97# further. For a list of options available for each theme, see the
98# documentation.
99#html_theme_options = {}
100
101# Add any paths that contain custom themes here, relative to this directory.
102#html_theme_path = []
103
104# The name for this set of Sphinx documents. If None, it defaults to
105# "<project> v<release> documentation".
106#html_title = None
107
108# A shorter title for the navigation bar. Default is the same as html_title.
109#html_short_title = None
110
111# The name of an image file (relative to this directory) to place at the top
112# of the sidebar.
113#html_logo = None
114
115# The name of an image file (within the static path) to use as favicon of the
116# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
117# pixels large.
118#html_favicon = None
119
120# Add any paths that contain custom static files (such as style sheets) here,
121# relative to this directory. They are copied after the builtin static files,
122# so a file named "default.css" will overwrite the builtin "default.css".
123html_static_path = ['_static']
124
125# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
126# using the given strftime format.
127#html_last_updated_fmt = '%b %d, %Y'
128
129# If true, SmartyPants will be used to convert quotes and dashes to
130# typographically correct entities.
131#html_use_smartypants = True
132
133# Custom sidebar templates, maps document names to template names.
134#html_sidebars = {}
135
136# Additional templates that should be rendered to pages, maps page names to
137# template names.
138#html_additional_pages = {}
139
140# If false, no module index is generated.
141#html_domain_indices = True
142
143# If false, no index is generated.
144#html_use_index = True
145
146# If true, the index is split into individual pages for each letter.
147#html_split_index = False
148
149# If true, links to the reST sources are added to the pages.
150#html_show_sourcelink = True
151
152# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
153#html_show_sphinx = True
154
155# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
156#html_show_copyright = True
157
158# If true, an OpenSearch description file will be output, and all pages will
159# contain a <link> tag referring to it. The value of this option must be the
160# base URL from which the finished HTML is served.
161#html_use_opensearch = ''
162
163# This is the file name suffix for HTML files (e.g. ".xhtml").
164#html_file_suffix = None
165
166# Output file base name for HTML help builder.
167htmlhelp_basename = 'OpenERPBankPaymentsdoc'
168
169
170# -- Options for LaTeX output --------------------------------------------------
171
172latex_elements = {
173# The paper size ('letterpaper' or 'a4paper').
174#'papersize': 'letterpaper',
175
176# The font size ('10pt', '11pt' or '12pt').
177#'pointsize': '10pt',
178
179# Additional stuff for the LaTeX preamble.
180#'preamble': '',
181}
182
183# Grouping the document tree into LaTeX files. List of tuples
184# (source start file, target name, title, author, documentclass [howto/manual]).
185latex_documents = [
186 ('index', 'OpenERPBankPayments.tex', u'OpenERP Bank Payments Documentation',
187 u'Savoir-faire Linux', 'manual'),
188]
189
190# The name of an image file (relative to this directory) to place at the top of
191# the title page.
192#latex_logo = None
193
194# For "manual" documents, if this is true, then toplevel headings are parts,
195# not chapters.
196#latex_use_parts = False
197
198# If true, show page references after internal links.
199#latex_show_pagerefs = False
200
201# If true, show URL addresses after external links.
202#latex_show_urls = False
203
204# Documents to append as an appendix to all manuals.
205#latex_appendices = []
206
207# If false, no module index is generated.
208#latex_domain_indices = True
209
210
211# -- Options for manual page output --------------------------------------------
212
213# One entry per manual page. List of tuples
214# (source start file, name, description, authors, manual section).
215man_pages = [
216 ('index', 'openerpbankpayments', u'OpenERP Bank Payments Documentation',
217 [u'Savoir-faire Linux'], 1)
218]
219
220# If true, show URL addresses after external links.
221#man_show_urls = False
222
223
224# -- Options for Texinfo output ------------------------------------------------
225
226# Grouping the document tree into Texinfo files. List of tuples
227# (source start file, target name, title, author,
228# dir menu entry, description, category)
229texinfo_documents = [
230 ('index', 'OpenERPBankPayments', u'OpenERP Bank Payments Documentation',
231 u'Savoir-faire Linux', 'OpenERPBankPayments', 'One line description of project.',
232 'Miscellaneous'),
233]
234
235# Documents to append as an appendix to all manuals.
236#texinfo_appendices = []
237
238# If false, no module index is generated.
239#texinfo_domain_indices = True
240
241# How to display URL addresses: 'footnote', 'no', or 'inline'.
242#texinfo_show_urls = 'footnote'
0243
=== added file 'customer_payment_request/docs/design.rst'
--- customer_payment_request/docs/design.rst 1970-01-01 00:00:00 +0000
+++ customer_payment_request/docs/design.rst 2013-09-17 15:32:05 +0000
@@ -0,0 +1,59 @@
1Module Design
2=============
3
4Modules in this project are organised in two groups, the :ref:`workflow` group and the
5:ref:`protocol` group. Each of them is as loosely coupled as possible to allow mix-and-matching of
6any of them together. This loose coupling is achieved through a :ref:`registry` mechanism.
7
8.. _workflow:
9
10Workflow modules
11----------------
12
13The role of workflow modules is to determine what warrants a communication with banks and how. For
14example, you have the :ref:`customer_payment_request` module which selects all ``partner`` owing us
15any money and, for that amount, asks (through the :ref:`registry`) that any :ref:`protocol` module
16that knows how to process payments with the bank associated with each of these partners to process
17them. Then, it waits for an answer from these protocol modules to confirm the payment and then
18create a ``voucher`` for it.
19
20But that's just one workflow. There could be another workflow which prefers to process payments by
21invoices, and only under certain conditions, and this hypothetical workflow module could reuse.
22Another workflow module could prefer avoiding vouchers and work directly with move lines. Another
23workflow module could be integrated with shipping, stocks and inventory.
24
25.. _protocol:
26
27Protocol modules
28----------------
29
30Protocol modules implement communication protocols with banks. The kind of transaction they support
31can vary and they don't have to support every workflow modules in the project (sometimes, they just
32can't). They broadcast their capabilities to the workflow modules through the :ref:`registry`.
33
34For example, :ref:`payment_management_visa_desjardins` can send payment requests for partner account
35of the ``credit_card`` type (supplied by :ref:`encrypted_credit_card`) and thus fit with the
36:ref:`customer_payment_request` workflow, so it registers with it through
37``customer_payment_request.registry``.
38
39.. _registry:
40
41Registry
42--------
43
44Registry mechanism allows for loose coupling between :ref:`workflow` and :ref:`protocol`. Its
45implementation is very low-tech and is simply a list of registered functions hosted by the workflow
46modules. When a protocol wants to support a particular workflow, it simply has to call that
47workflow's registry function with adapter functions as parameter. These adapter functions then
48adapt the information supplied by a particular workflow to the requirements of the communication
49protocol.
50
51For example, we have :ref:`payment_management_visa_desjardins` which implements the communication
52protocol for credit card processing. It hooks with :ref:`customer_payment_request`, which supplies
53a list of partners and the amount they each owe. Fine, we create a communication file from these and
54we're done.
55
56Later, let's say someone wants to create a new workflow which, instead of being partner-based, is
57invoice-based. Someone wanting to re-use :ref:`payment_management_visa_desjardins` would simply have
58to implement an additional adapter function which generates communication files from invoices (and
59maybe add invoice-based metadata to the communication for reconciliation) and we're done.
060
=== added file 'customer_payment_request/docs/index.rst'
--- customer_payment_request/docs/index.rst 1970-01-01 00:00:00 +0000
+++ customer_payment_request/docs/index.rst 2013-09-17 15:32:05 +0000
@@ -0,0 +1,25 @@
1Welcome to OpenERP Bank Payments's documentation!
2=================================================
3
4This project is a collection of modules to help manage automatic payments with banks. For now, it
5only handles customer payment requests and interfaces only with `Desjardins`_, but the project is
6designed to loosely couple :ref:`workflows <workflow>` and :ref:`bank protocols <protocol>`, so it
7should be possible to add stuff like supplier payments and bank protocols.
8
9Contents:
10
11.. toctree::
12 :maxdepth: 2
13
14 installation
15 design
16 modules
17
18Indices and tables
19==================
20
21* :ref:`genindex`
22* :ref:`modindex`
23* :ref:`search`
24
25.. _Desjardins: http://www.desjardins.com/en/
026
=== added file 'customer_payment_request/docs/installation.rst'
--- customer_payment_request/docs/installation.rst 1970-01-01 00:00:00 +0000
+++ customer_payment_request/docs/installation.rst 2013-09-17 15:32:05 +0000
@@ -0,0 +1,27 @@
1Installation and usage
2======================
3
4To use this project, you have to install at least one :ref:`workflow` and one :ref:`protocol`.
5
6The first step to do that, of course, is like any OpenERP project: Add the ``addons`` folder to your
7addons path and update your module list.
8
9Then, choose a workflow that you want to install in your system. For example, if you want to send
10requests to your bank for customers who owe you money to pay you, you'll install
11:ref:`customer_payment_request`.
12
13Worflow modules usually come with scheduled tasks (export payments, import bank response, etc.)
14which themselves are inactive by default. You'll have to go to your schedule task configuration
15and activate those newly added tasks and set appropriate schedules for them.
16
17Then, you'll need to install a :ref:`bank protocol <protocol>` module and configure it. Usually,
18bank protocol configuration is a by-company configuration, so you'll be able to access them through
19your company details form.
20
21Once this is done, you should be good to go. How things work, usually, is that payments are
22automatically sent and received through scheduled jobs and everything, such as payment vouchers, is
23reconciled automatically. Some modules, however, may give you more latitude in this regard and you
24might, for example, be able to tell them that you want to manually confirm payment vouchers.
25
26You should refer to the documentation specific to the modules you've installed for any details or
27extra configuration.
028
=== added file 'customer_payment_request/docs/modules.rst'
--- customer_payment_request/docs/modules.rst 1970-01-01 00:00:00 +0000
+++ customer_payment_request/docs/modules.rst 2013-09-17 15:32:05 +0000
@@ -0,0 +1,100 @@
1Module List
2===========
3
4.. _customer_payment_request:
5
6customer_payment_request
7^^^^^^^^^^^^^^^^^^^^^^^^
8
9A :ref:`workflow module <workflow>` allowing to send mass payment requests to each partner owing us
10money (having a positive value in its "Total Receivable" field). Regularly, through scheduled
11actions, it scans for these partners and, for each of these, create a ``customer.payment.request``
12line in ``waiting`` mode.
13
14Then, it calls its ``send_payment_func()`` function. This function is called for each registered
15:ref:`protocol`, in order. The protocol modules are then expected to process every customer payment
16request that they can process (this usually depends on the type of the bank account associated with
17the partner) and then set the state of the request to ``sent``. If they can't process the request,
18they leave it to ``waiting``.
19
20Normally, when all protocols passed through the customer payment requests, all of them should be
21in ``sent`` mode. If there are any ``waiting`` leftovers, a warning will be logged on the server.
22
23Then, regularly, ``receive_payment_func()`` will be called to registered protocols to tell them to
24check for a response from the bank regarding the payment status. If there is any, the protocol
25modules are expected to call ``register_payment_acceptation()`` or ``register_payment_refusal()``,
26which will either remove the payment request and create a payment voucher or put it back in
27``waiting`` mode.
28
29The module will never create a payment request for the same partner at once to avoid
30double-charging.
31
32When ``register_payment_refusal()`` is called, this will enable a bank rotation mechanism and try
33again. So, if the partner has more than one registered bank account, it will retry, but with the
34next bank account in line. If it doesn't, the retry will be done on the same bank account.
35
36.. _payment_management_visa_desjardins:
37
38payment_management_visa_desjardins
39^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40
41This :ref:`protocol module <protocol>` produces a text file in a format that, when sent to
42Desjardins, allows you to request payments from credit cards into your bank account.
43
44This module depends on :ref:`encrypted_credit_card` modules which supplies the ``credit_card``
45bank account type and manages encryption of credit card numbers (because it's illegal to hold
46credit card numbers in plain text into a database such as OpenERP's).
47
48The file that it produce, ``vdcoupon.txt`` can be sent to Desjardins through SSH with the help of
49``scripts/vdcoupon_watch_send.py`` and a response from Desjardins can be watched and managed with
50``scripts/vdcoupon_watch_recv.py``.
51
52This module supports the :ref:`customer_payment_request` workflow and processes all partners having
53a ``credit_card`` bank account.
54
55Using it requires Desjardins Merchant details to be set for your company, so you'll need to go fill
56it up after you've installed the module. Because this information is very similar to the one used by
57:ref:`payment_management_dd_desjardins`, there's a common module,
58:ref:`payment_management_desjardins` which defines these structures.
59
60.. _payment_management_dd_desjardins:
61
62payment_management_dd_desjardins
63^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
64
65This :ref:`protocol module <protocol>` produces a text file in a format that, when sent to
66Desjardins, allows you to request direct withdrawal from bank accounts.
67
68The file that it produce, ``ddcoupon.txt`` can be sent to Desjardins manually (for now).
69
70This module supports the :ref:`customer_payment_request` workflow and processes all partners having
71a ``bank`` bank account.
72
73Unlike credit card processing, direct withdrawal request have no automated response and are
74considered by the system to always succeed. For this reason payment requests are "accepted" right
75upon file creation and a voucher is created. For this reason, you might want to enable the
76"Leave vouchers as draft" option and manually confirm them.
77
78Nothing happens when the workflow module asks this protocol to poll for bank response because there
79aren't any.
80
81.. _payment_management_desjardins:
82
83payment_management_desjardins
84^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
85
86A helper module defining common preferences for :ref:`payment_management_visa_desjardins` and
87:ref:`payment_management_dd_desjardins`.
88
89.. _encrypted_credit_card:
90
91encrypted_credit_card
92^^^^^^^^^^^^^^^^^^^^^
93
94A helper module adding the ``credit_card`` bank type, doing credit card number validation and adding
95logic for credit card number encryption.
96
97Encryption is done asynchronously through RSA keypairs. Whenever a new credit card number is
98entered, it is encrypted with a public key (which is set in company preferences) and then, another
99server which is hopefully very secure holds the private key and decrypts them before sendin them to
100banks.
0101
=== added file 'customer_payment_request/registry.py'
--- customer_payment_request/registry.py 1970-01-01 00:00:00 +0000
+++ customer_payment_request/registry.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,53 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22# def send_payment_func_prototype(cpr_obj, cr, uid, customer_payment_requests, context=None):
23# batch_file = SomeBatchFile()
24# processed = []
25# for cpr in customer_payment_requests:
26# if can_process_request(cpr):
27# batch_file.add_request(cpr)
28# processed.append(cpr)
29# if batch_file.send_to_bank() == success:
30# cpr_ids = [cpr.id for cpr in processed]
31# cpr_obj.write(cr, uid, cpr_ids, {'state': 'sent'}, context=context)
32
33SEND_PAYMENT_FUNCS = []
34
35# def receive_payment_func_prototype(cpr_obj, cr, uid, context=None):
36# if not has_received_response_from_bank():
37# return
38# batch_response = parse_batch_response()
39# account_id = fetch_account_id_from_bank_prefs()
40# journal_id = fetch_journal_id_from_bank_prefs()
41# for line in batch_response:
42# if line.accepted:
43# cpr_obj.register_payment_acceptation(cr, uid, line.partner_id, line.amount, account_id, journal_id)
44# else:
45# cpr_obj.register_payment_refusal(cr, uid, line.partner_id)
46
47RECEIVE_PAYMENT_FUNCS = []
48
49def register(send_func, receive_func):
50 if send_func is not None:
51 SEND_PAYMENT_FUNCS.append(send_func)
52 if receive_func is not None:
53 RECEIVE_PAYMENT_FUNCS.append(receive_func)
054
=== added directory 'encrypted_credit_card'
=== added file 'encrypted_credit_card/__init__.py'
--- encrypted_credit_card/__init__.py 1970-01-01 00:00:00 +0000
+++ encrypted_credit_card/__init__.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,1 @@
1import encrypted_credit_card
0\ No newline at end of file2\ No newline at end of file
13
=== added file 'encrypted_credit_card/__openerp__.py'
--- encrypted_credit_card/__openerp__.py 1970-01-01 00:00:00 +0000
+++ encrypted_credit_card/__openerp__.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,64 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22{
23 "name" : "Encrypted Credit Cards",
24 "version" : "1.0",
25 "author" : "Savoir-faire Linux",
26 "website" : "http://www.savoirfairelinux.com",
27 "category" : "Accounting & Finance",
28 "description" : """
29Adds a new "Credit Card" bank type.
30
31This new bank type stores credit card numbers in an encrypted form, using a public key
32stored in res.company.
33
34To comply with PCI-DSS, we never ever store the credit card number in the DB, so the bank
35type record's 'acc_number' field contains stuff like "XXXX-XXXX-XXXX-1234". The actual
36credit card number is stored in "encrypted_cc_number", encrypted with the company's public
37key.
38
39The encryption method used is RSA. Because we store the encrypted CC number in a char()
40field and that encrypted data is binary, we encode that encrypted CC number in base64.
41
42This way, someone with full access to the DB is still unable to extract CC number unless he
43also has access to the private key, which hopefully is stored elsewhere, in a very secure
44place.
45
46We don't do any decryption here. It's up to another process to have access to the private
47key and decrypt those numbers.
48
49This module requires PyCrypto ( https://pypi.python.org/pypi/pycrypto )
50
51 """,
52 "depends" : ['sale'],
53 'external_dependencies': {
54 'python' : ['Crypto'],
55 },
56 "init_xml" : [],
57 'data' : [
58 'encrypted_credit_card_data.xml',
59 'encrypted_credit_card_view.xml',
60 ],
61 "demo_xml" : [],
62 "installable" : True,
63 "certificate" : ''
64}
065
=== added file 'encrypted_credit_card/encrypted_credit_card.py'
--- encrypted_credit_card/encrypted_credit_card.py 1970-01-01 00:00:00 +0000
+++ encrypted_credit_card/encrypted_credit_card.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,216 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22# About encryption in this module
23#
24# The goal of this module is to store CC numbers in an encrypted way using an assymetric encryption
25# method (we use RSA). This way, someone with full access to our DB won't be able to decrypt our
26# CC numbers unless he also has access to the private key, which of course isn't stored in the BD.
27#
28# Because the output of PyCrypto.PublicKey.RSA.encrypt() is binary data and that we store the
29# encrypted key in a char field, we encode that encrypted data with base64.
30
31import re
32import binascii
33from datetime import date
34from Crypto.PublicKey import RSA
35
36from openerp.tools.translate import _
37from openerp.osv import orm, fields
38
39def is_credit_card_number_valid(credit_card_number):
40 """Credit card number validation according to the MODULO-10 algorithm.
41 """
42 str_card_number = str(credit_card_number)
43 str_check_digit = str_card_number[-1]
44 str_validation_vect = ""
45 str_result_vect = []
46 result = 0
47 next_closest_ten = 10
48
49 # Make sure the credit card number consists of
50 # digits only
51 if not re.match(r"^[0-9]+$", str_card_number):
52 return False
53
54 # Build the validation vector '212121...'
55 for i in range(len(str_card_number)-1):
56 if i % 2 == 0:
57 str_validation_vect += '2'
58 else:
59 str_validation_vect += '1'
60
61 # Multiply each digit of the card number
62 # by the corresponding validation digit,
63 # except for the last digit
64 for i in range(len(str_validation_vect)):
65 res = int(str_card_number[i]) * int(str_validation_vect[i])
66 str_result_vect.append(res)
67
68 # Add the result of the above multiplication
69 # and consider a 2-digit number as 2 numbers
70 # of one digit
71 for number in str_result_vect:
72 if number < 10:
73 result += number
74 else:
75 str_number = str(number)
76 num_1 = int(str_number[0])
77 num_2 = int(str_number[1])
78 result += num_1 + num_2
79
80 # Compute the check digit and compare it
81 # with the last digit of the card number
82 while next_closest_ten < result:
83 next_closest_ten += 10
84
85 check_digit = next_closest_ten - result
86
87 if str(check_digit) == str_check_digit:
88 return True
89 else:
90 return False
91
92def fix_public_key(key):
93 # Copy/Pasting public key leads to formatting loss, and PyCrypto is sensitive on this matter.
94 # It wants all \n preserved, but in OpenERP's char field, those \n are going to be replaced
95 # with spaces. But don't try to naively replace spaces with newlines, because you're going to
96 # end up with BEGIN\nPUBLIC\KEY, which PyCrypto won't accept.
97 if key.strip().startswith('ssh-rsa'):
98 # This key is not of the type that starts with BEGIN PUBLIC KEY. Just return the stripped
99 # version
100 return key.strip()
101 stripped = re.sub(r'\s?-----[A-Z\s]+-----\s?', '', key)
102 stripped = stripped.replace(' ', '\n')
103 return '-----BEGIN PUBLIC KEY-----\n' + stripped + '\n-----END PUBLIC KEY-----'
104
105def encrypt_cc_number(cc_number, public_key):
106 key = RSA.importKey(fix_public_key(public_key))
107 encrypted_cc_number = key.encrypt(cc_number, 42)[0]
108 return binascii.b2a_base64(encrypted_cc_number).strip()
109
110def encrypt_cc_vals(partner_obj, vals):
111 if 'acc_number' not in vals:
112 # no acc_number, no problem
113 return
114 cc_number = vals['acc_number']
115 if cc_number.startswith('XXXX'):
116 # We're not actually changing the number, just remove it
117 del vals['acc_number']
118 elif is_credit_card_number_valid(cc_number):
119 # Ok, we're actually submitting a CC number here
120 # Never ever store CC number in clear
121 vals['acc_number'] = 'XXXX-XXXX-XXXX-' + cc_number[-4:]
122 vals['encrypted_cc_number'] = encrypt_cc_number(cc_number, partner_obj.company_id.cc_number_encrypt_key)
123
124class res_partner_bank(orm.Model):
125 _inherit = 'res.partner.bank'
126
127 _columns = {
128 # RSA-encrypted, bas64-encoded.
129 'encrypted_cc_number': fields.char("Encrypted Credit Card Number", size=1024),
130 'expiration_date': fields.char('Expiration date (YYMM)', size=4),
131 }
132
133 def check_credit_card_number(self, cr, uid, ids, context=None):
134 for bank_acc in self.browse(cr, uid, ids, context=context):
135 if bank_acc.state != 'credit_card':
136 continue
137 cc_number = bank_acc.acc_number
138 if cc_number.startswith('XXXX'):
139 # It's a hidden number, so we're not actually changing the encrypted CC number.
140 # Consider as valid
141 continue
142 if not is_credit_card_number_valid(cc_number):
143 return False
144 return True
145
146 def check_expiration_date(self, cr, uid, ids, context=None):
147 for bank_acc in self.browse(cr, uid, ids, context=context):
148 if bank_acc.state != 'credit_card':
149 continue
150 if not bank_acc.expiration_date:
151 return False
152 m = re.match(r"^([0-9]{2})([0-9]{2})$", bank_acc.expiration_date)
153 if m is None:
154 return False
155 year = int(m.group(1)) + 2000
156 month = int(m.group(2))
157 TODAY = date.today()
158 if year < TODAY.year:
159 return False
160 if not (1 <= month <= 12):
161 return False
162 if year == TODAY.year and month < TODAY.month:
163 return False
164 return True
165
166 def _construct_constraint_msg_card_number(self, cr, uid, ids, context=None):
167 return (_("Credit card number is invalid")), ()
168
169 def _construct_constraint_msg_expiration_date(self, cr, uid, ids, context=None):
170 return (_("Expiration date is invalid")), ()
171
172 _constraints = [
173 (check_credit_card_number, _construct_constraint_msg_card_number, ["acc_number"]),
174 (check_expiration_date, _construct_constraint_msg_expiration_date, ["expiration_date"]),
175 ]
176
177 def create(self, cr, uid, vals, context=None):
178 if vals.get('state') == 'credit_card':
179 partner_obj = self.pool.get('res.partner').browse(cr, uid, vals['partner_id'], context=context)
180 encrypt_cc_vals(partner_obj, vals)
181 return super(res_partner_bank, self).create(cr, uid, vals, context=context)
182
183 def write(self, cr, uid, ids, vals, context=None):
184 self_obj = self.browse(cr, uid, ids[0], context=context)
185 try:
186 state = vals['state']
187 except KeyError:
188 state = self_obj.state
189 if state == 'credit_card':
190 encrypt_cc_vals(self_obj.partner_id, vals)
191 return super(res_partner_bank, self).write(cr, uid, ids, vals, context=context)
192
193class res_company(orm.Model):
194 _inherit = 'res.company'
195
196 def _get_short_pubkey(self, cr, uid, ids, field_name, arg, context):
197 res = {}
198 for company in self.browse(cr, uid, ids, context=context):
199 pubkey = company.cc_number_encrypt_key
200 if pubkey:
201 res[company.id] = pubkey[:30] + "..."
202 else:
203 res[company.id] = ""
204 return res
205
206 def _set_short_pubkey(self, cr, uid, id, name, value, fnct_inv_arg, context=None):
207 if len(value) > 40:
208 # We only save the key if it's not the truncated value
209 self.write(cr, uid, id, {'cc_number_encrypt_key': value}, context=context)
210
211 _columns = {
212 'cc_number_encrypt_key_short': fields.function(_get_short_pubkey, fnct_inv=_set_short_pubkey,
213 type='char', string='Credit Card Encryption Key'),
214 'cc_number_encrypt_key': fields.char('Credit Card Encryption Key', size=2048,
215 help="Public key with which to encrypt our credit card number before writing them to the DB"),
216 }
0217
=== added file 'encrypted_credit_card/encrypted_credit_card_data.xml'
--- encrypted_credit_card/encrypted_credit_card_data.xml 1970-01-01 00:00:00 +0000
+++ encrypted_credit_card/encrypted_credit_card_data.xml 2013-09-17 15:32:05 +0000
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3 <data>
4 <record id="bank_credit_card" model="res.partner.bank.type">
5 <field name="name">Credit card Account</field>
6 <field name="code">credit_card</field>
7 <field name="format_layout">%(bank_name)s: CC %(acc_number)s - EXP %(expiration_date)s</field>
8 </record>
9 </data>
10
11</openerp>
012
=== added file 'encrypted_credit_card/encrypted_credit_card_view.xml'
--- encrypted_credit_card/encrypted_credit_card_view.xml 1970-01-01 00:00:00 +0000
+++ encrypted_credit_card/encrypted_credit_card_view.xml 2013-09-17 15:32:05 +0000
@@ -0,0 +1,27 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3 <data>
4 <record id="view_partner_bank_form" model="ir.ui.view">
5 <field name="name">res.partner.bank.form</field>
6 <field name="model">res.partner.bank</field>
7 <field name="inherit_id" ref="base.view_partner_bank_form"/>
8 <field name="arch" type="xml">
9 <field name="acc_number" position="after">
10 <field name="expiration_date" attrs="{'invisible':[('state','!=','credit_card')], 'required':[('state','=','credit_card')]}" />
11 </field>
12 </field>
13 </record>
14
15 <record id="view_company_form" model="ir.ui.view">
16 <field name="name">res.company.form</field>
17 <field name="model">res.company</field>
18 <field name="inherit_id" ref="base.view_company_form"/>
19 <field name="arch" type="xml">
20 <xpath expr="//field[@name='currency_id']" position="after">
21 <field name="cc_number_encrypt_key_short"/>
22 </xpath>
23 </field>
24 </record>
25 </data>
26
27</openerp>
028
=== added directory 'encrypted_credit_card/i18n'
=== added file 'encrypted_credit_card/i18n/encrypted_credit_card.pot'
--- encrypted_credit_card/i18n/encrypted_credit_card.pot 1970-01-01 00:00:00 +0000
+++ encrypted_credit_card/i18n/encrypted_credit_card.pot 2013-09-17 15:32:05 +0000
@@ -0,0 +1,69 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * encrypted_credit_card
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 7.0\n"
8"Report-Msgid-Bugs-To: \n"
9"POT-Creation-Date: 2013-08-13 14:05+0000\n"
10"PO-Revision-Date: 2013-08-13 14:05+0000\n"
11"Last-Translator: <>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: \n"
16"Plural-Forms: \n"
17
18#. module: encrypted_credit_card
19#: field:res.partner.bank,encrypted_cc_number:0
20msgid "Encrypted Credit Card Number"
21msgstr ""
22
23#. module: encrypted_credit_card
24#: code:addons/encrypted_credit_card/encrypted_credit_card.py:170
25#, python-format
26msgid "Expiration date is invalid"
27msgstr ""
28
29#. module: encrypted_credit_card
30#: code:addons/encrypted_credit_card/encrypted_credit_card.py:167
31#, python-format
32msgid "Credit card number is invalid"
33msgstr ""
34
35#. module: encrypted_credit_card
36#: model:ir.model,name:encrypted_credit_card.model_res_partner_bank
37msgid "Bank Accounts"
38msgstr ""
39
40#. module: encrypted_credit_card
41#: model:ir.model,name:encrypted_credit_card.model_res_company
42msgid "Companies"
43msgstr ""
44
45#. module: encrypted_credit_card
46#: field:res.partner.bank,expiration_date:0
47msgid "Expiration date (YYMM)"
48msgstr ""
49
50#. module: encrypted_credit_card
51#: help:res.company,cc_number_encrypt_key:0
52msgid "Public key with which to encrypt our credit card number before writing them to the DB"
53msgstr ""
54
55#. module: encrypted_credit_card
56#: model:res.partner.bank.type,format_layout:encrypted_credit_card.bank_credit_card
57msgid "%(bank_name)s: IBAN %(acc_number)s - BIC %(expiration_date)s"
58msgstr ""
59
60#. module: encrypted_credit_card
61#: model:res.partner.bank.type,name:encrypted_credit_card.bank_credit_card
62msgid "Credit card Account"
63msgstr ""
64
65#. module: encrypted_credit_card
66#: field:res.company,cc_number_encrypt_key:0
67msgid "Credit Card Encryption Key"
68msgstr ""
69
070
=== added directory 'payment_management_dd_desjardins'
=== added file 'payment_management_dd_desjardins/__init__.py'
--- payment_management_dd_desjardins/__init__.py 1970-01-01 00:00:00 +0000
+++ payment_management_dd_desjardins/__init__.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,22 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22import payment_management_dd_desjardins
023
=== added file 'payment_management_dd_desjardins/__openerp__.py'
--- payment_management_dd_desjardins/__openerp__.py 1970-01-01 00:00:00 +0000
+++ payment_management_dd_desjardins/__openerp__.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,40 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22{
23 "name" : "Payment management module for desjardins Direct Deposit",
24 "version" : "1.0",
25 "author" : "Savoir-faire Linux",
26 "website" : "http://www.savoirfairelinux.com",
27 "category" : "Accounting & Finance",
28 "description" : """
29 """,
30 "depends" : ['payment_management_desjardins'],
31 "init_xml" : [],
32 "update_xml" : [
33 ],
34 'data' : [
35 'payment_management_dd_desjardins_view.xml',
36 ],
37 "demo_xml" : [],
38 "installable" : True,
39 "certificate" : ''
40}
041
=== added directory 'payment_management_dd_desjardins/i18n'
=== added file 'payment_management_dd_desjardins/i18n/payment_management_dd_desjardins.pot'
--- payment_management_dd_desjardins/i18n/payment_management_dd_desjardins.pot 1970-01-01 00:00:00 +0000
+++ payment_management_dd_desjardins/i18n/payment_management_dd_desjardins.pot 2013-09-17 15:32:05 +0000
@@ -0,0 +1,27 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * payment_management_dd_desjardins
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 7.0\n"
8"Report-Msgid-Bugs-To: \n"
9"POT-Creation-Date: 2013-08-21 13:53+0000\n"
10"PO-Revision-Date: 2013-08-21 13:53+0000\n"
11"Last-Translator: <>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: \n"
16"Plural-Forms: \n"
17
18#. module: payment_management_dd_desjardins
19#: field:res.company,desjardins_dd_info:0
20msgid "Desjardins Direct Deposit Info"
21msgstr ""
22
23#. module: payment_management_dd_desjardins
24#: model:ir.model,name:payment_management_dd_desjardins.model_res_company
25msgid "Companies"
26msgstr ""
27
028
=== added file 'payment_management_dd_desjardins/payment_management_dd_desjardins.py'
--- payment_management_dd_desjardins/payment_management_dd_desjardins.py 1970-01-01 00:00:00 +0000
+++ payment_management_dd_desjardins/payment_management_dd_desjardins.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,113 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from __future__ import division
23
24import os
25import logging
26import codecs
27
28from openerp.osv import orm, fields
29
30from .record import get_cd_record, get_a_record, get_z_record
31
32_logger = logging.getLogger(__name__)
33
34class res_company(orm.Model):
35 _inherit = 'res.company'
36 _columns = {
37 'desjardins_dd_info': fields.many2one('res.company.desjardinsinfo', "Desjardins Direct Deposit Info"),
38 }
39
40 def _default_desjardins_dd_info(self, cr, uid, context=None):
41 res_company_desjardinsinfo = self.pool.get('res.company.desjardinsinfo')
42 res = res_company_desjardinsinfo.create(cr, uid, {}, context=context)
43 return res
44
45 _defaults = {
46 'desjardins_dd_info': _default_desjardins_dd_info,
47 }
48
49def send_payment_func_dd_desjardins(cpr_obj, cr, uid, customer_payment_requests, context=None):
50 company_obj = cpr_obj.pool.get('res.company')
51 # active_company() is defined in customer_payment_request, which isn't directly in our
52 # dependencies, but for now, this function can't be called unless CPR is installed, so we're
53 # alright. This might have to change when our module starts supporting more workflow.
54 company = company_obj.active_company(cr, uid, context=context)
55 desjinfo = company.desjardins_dd_info
56 directory = desjinfo.workdir
57
58 if not desjinfo.is_complete():
59 return False
60
61 toprocess = [cpr for cpr in customer_payment_requests if cpr.bank_account_id.state == 'bank']
62 if not toprocess:
63 return False
64 total_number_of_credit = 0
65 total_number_of_debit = 0
66 total_value_of_credit = 0
67 total_value_of_debit = 0
68 lines = []
69
70 # Type A record
71 a_record = get_a_record(len(lines)+1, desjinfo)
72 lines.append(a_record)
73
74 # Type C/D records
75 for cpr in toprocess:
76 bank_account = cpr.bank_account_id
77 partner = bank_account.partner_id
78 # The amount is in cents
79 amount = round(cpr.amount * 100)
80 if amount > 0:
81 total_number_of_debit += 1
82 total_value_of_debit += amount
83 else:
84 total_number_of_credit += 1
85 total_value_of_credit += amount
86 cd_record = get_cd_record(
87 len(lines)+1, company, amount, bank_account, partner
88 )
89 lines.append(cd_record)
90 # With Desjardins RD, there's no response file, so we directly create the payment voucher
91 cpr_obj.register_payment_acceptation(
92 cr, uid, partner.id, cpr.amount, desjinfo.account_id.id,
93 desjinfo.bank_account_id.journal_id.id, leave_draft=desjinfo.leave_vouchers_draft,
94 context=context
95 )
96
97 # Type Z record
98 z_record = get_z_record(
99 len(lines)+1, desjinfo,
100 total_number_of_debit, total_value_of_debit,
101 total_number_of_credit, total_value_of_credit
102 )
103 lines.append(z_record)
104 with codecs.open(os.path.join(directory, 'ddcoupon.txt'), 'wt', encoding='latin-1') as fp:
105 fp.write(u'\n'.join(lines))
106 desjinfo.increase_file_number()
107 return True
108
109try:
110 from customer_payment_request import registry
111 registry.register(send_payment_func_dd_desjardins, None)
112except ImportError:
113 pass
0114
=== added file 'payment_management_dd_desjardins/payment_management_dd_desjardins_view.xml'
--- payment_management_dd_desjardins/payment_management_dd_desjardins_view.xml 1970-01-01 00:00:00 +0000
+++ payment_management_dd_desjardins/payment_management_dd_desjardins_view.xml 2013-09-17 15:32:05 +0000
@@ -0,0 +1,27 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3 <data>
4 <record id="view_company_form" model="ir.ui.view">
5 <field name="name">res.company.form</field>
6 <field name="model">res.company</field>
7 <field name="inherit_id" ref="base.view_company_form"/>
8 <field name="arch" type="xml">
9 <xpath expr="//page[@string='General Information']/group/group" position="inside">
10 <field name="desjardins_dd_info"/>
11 </xpath>
12 </field>
13 </record>
14
15 <record id="view_partner_bank_form" model="ir.ui.view">
16 <field name="name">res.partner.bank.form</field>
17 <field name="model">res.partner.bank</field>
18 <field name="inherit_id" ref="base.view_partner_bank_form"/>
19 <field name="arch" type="xml">
20 <xpath expr="//field[@name='bank_bic']" position="attributes">
21 <attribute name="attrs">{'required':[('state', 'in', ['iban', 'bank'])]}</attribute>
22 </xpath>
23 </field>
24 </record>
25 </data>
26
27</openerp>
028
=== added file 'payment_management_dd_desjardins/record.py'
--- payment_management_dd_desjardins/record.py 1970-01-01 00:00:00 +0000
+++ payment_management_dd_desjardins/record.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,94 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from __future__ import division, unicode_literals
23
24from datetime import date, datetime
25
26from payment_management_desjardins.record import yyyddd
27
28def ljust(s, num, filler):
29 return s[:num].ljust(num, filler)
30
31def rjust(s, num, filler):
32 return s[:num].rjust(num, filler)
33
34def get_a_record(seq, desjinfo):
35 return ''.join([
36 'A',
37 '%09d' % seq,
38 desjinfo.originator_id[:10].upper().ljust(10, ' '),
39 '%04d' % desjinfo.file_creation_number,
40 yyyddd(datetime.now()),
41 '81510',
42 ' ' * 20,
43 'CAD',
44 ' ' * 1406,
45 ])
46
47def get_cd_record(seq, company, amount, bank_account, partner):
48 desjinfo = company.desjardins_dd_info
49 # amount is an integer representing cents
50 if amount > 0:
51 firstchar = 'D'
52 txn_type = desjinfo.txn_code_debit
53 else:
54 firstchar = 'C'
55 txn_type = desjinfo.txn_code_credit
56 amount = abs(amount)
57 return ''.join([
58 firstchar,
59 '%09d' % seq,
60 ljust(desjinfo.originator_id.upper(), 10, ' '),
61 '%04d' % desjinfo.file_creation_number,
62 txn_type,
63 '%010d' % amount,
64 yyyddd(date.today()),
65 rjust(bank_account.bank_bic, 9, '0'),
66 ljust(bank_account.acc_number, 12, ' '),
67 '0' * 22,
68 '0' * 3,
69 ljust(company.name, 15, ' '),
70 ljust(partner.name, 30, ' '),
71 ljust(company.name, 30, ' '),
72 ljust(desjinfo.originator_id.upper(), 10, ' '),
73 '%019d' % partner.id,
74 rjust(desjinfo.bank_account_id.bank_bic, 9, '0'),
75 ljust(desjinfo.bank_account_id.acc_number, 12, ' '),
76 ' ' * 15,
77 ' ' * 22,
78 ' ' * 2,
79 '0' * 11,
80 ])
81
82def get_z_record(seq, desjinfo, num_debit, total_debit, num_credit, total_credit):
83 return ''.join([
84 'Z',
85 '%09d' % seq,
86 desjinfo.originator_id[:10].upper().ljust(10, ' '),
87 '%04d' % desjinfo.file_creation_number,
88 '%014d' % total_debit,
89 '%08d' % num_debit,
90 '%014d' % total_credit,
91 '%08d' % num_credit,
92 '0' * 44,
93 ' ' * 1352,
94 ])
095
=== added directory 'payment_management_desjardins'
=== added file 'payment_management_desjardins/__init__.py'
--- payment_management_desjardins/__init__.py 1970-01-01 00:00:00 +0000
+++ payment_management_desjardins/__init__.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,22 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22import payment_management_desjardins
023
=== added file 'payment_management_desjardins/__openerp__.py'
--- payment_management_desjardins/__openerp__.py 1970-01-01 00:00:00 +0000
+++ payment_management_desjardins/__openerp__.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,40 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22{
23 "name" : "Payment management module for Desjardins",
24 "version" : "1.0",
25 "author" : "Savoir-faire Linux",
26 "website" : "http://www.savoirfairelinux.com",
27 "category" : "Accounting & Finance",
28 "description" : """
29 """,
30 "depends" : ['account_voucher', 'sale'],
31 "init_xml" : [],
32 "update_xml" : [
33 ],
34 'data' : [
35 'payment_management_desjardins_view.xml',
36 ],
37 "demo_xml" : [],
38 "installable" : True,
39 "certificate" : ''
40}
041
=== added directory 'payment_management_desjardins/i18n'
=== added file 'payment_management_desjardins/i18n/payment_management_desjardins.pot'
--- payment_management_desjardins/i18n/payment_management_desjardins.pot 1970-01-01 00:00:00 +0000
+++ payment_management_desjardins/i18n/payment_management_desjardins.pot 2013-09-17 15:32:05 +0000
@@ -0,0 +1,105 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * payment_management_desjardins
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 7.0\n"
8"Report-Msgid-Bugs-To: \n"
9"POT-Creation-Date: 2013-08-21 13:52+0000\n"
10"PO-Revision-Date: 2013-08-21 13:52+0000\n"
11"Last-Translator: <>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: \n"
16"Plural-Forms: \n"
17
18#. module: payment_management_desjardins
19#: model:ir.model,name:payment_management_desjardins.model_res_company_desjardinsinfo
20msgid "res.company.desjardinsinfo"
21msgstr ""
22
23#. module: payment_management_desjardins
24#: help:res.company.desjardinsinfo,merchant_number:0
25msgid "Merchant number assigned by DCS. Must be numeric."
26msgstr ""
27
28#. module: payment_management_desjardins
29#: field:res.company.desjardinsinfo,merchant_number:0
30msgid "Merchant number for Desjardins"
31msgstr ""
32
33#. module: payment_management_desjardins
34#: code:addons/payment_management_desjardins/payment_management_desjardins.py:88
35#, python-format
36msgid "Specified directory %s is not a valid directory. Desjardins payment exportation is aborted."
37msgstr ""
38
39#. module: payment_management_desjardins
40#: field:res.company.desjardinsinfo,workdir:0
41msgid "Storage Directory"
42msgstr ""
43
44#. module: payment_management_desjardins
45#: help:res.company.desjardinsinfo,originator_id:0
46msgid "Name of the directory into which the 'vdcoupon.txt' files will be placed."
47msgstr ""
48
49#. module: payment_management_desjardins
50#: field:res.company.desjardinsinfo,bank_account_id:0
51msgid "Your Desjardins bank account"
52msgstr ""
53
54#. module: payment_management_desjardins
55#: code:addons/payment_management_desjardins/payment_management_desjardins.py:78
56#, python-format
57msgid "Desjardins Merchant Info"
58msgstr ""
59
60#. module: payment_management_desjardins
61#: field:res.company.desjardinsinfo,originator_id:0
62msgid "Originator ID"
63msgstr ""
64
65#. module: payment_management_desjardins
66#: field:res.company.desjardinsinfo,txn_code_credit:0
67msgid "Transaction code for Credit"
68msgstr ""
69
70#. module: payment_management_desjardins
71#: view:res.company.desjardinsinfo:0
72msgid "Desjardins harmonization info"
73msgstr ""
74
75#. module: payment_management_desjardins
76#: field:res.company.desjardinsinfo,txn_code_debit:0
77msgid "Transaction code for Debit"
78msgstr ""
79
80#. module: payment_management_desjardins
81#: field:res.company.desjardinsinfo,file_creation_number:0
82msgid "Next file creation number value"
83msgstr ""
84
85#. module: payment_management_desjardins
86#: help:res.company.desjardinsinfo,file_creation_number:0
87msgid "File sequence number, to be incremented by 1 for each file sent without error."
88msgstr ""
89
90#. module: payment_management_desjardins
91#: field:res.company.desjardinsinfo,account_id:0
92msgid "Account for Desjardins payments"
93msgstr ""
94
95#. module: payment_management_desjardins
96#: help:res.company.desjardinsinfo,workdir:0
97msgid "Specify a directory to export to and import from."
98msgstr ""
99
100#. module: payment_management_desjardins
101#: code:addons/payment_management_desjardins/payment_management_desjardins.py:94
102#, python-format
103msgid "Could not export Desjardins payments because at least one of the following company parameters is missing : (merchant_number, file_creation_number, originator_id)."
104msgstr ""
105
0106
=== added file 'payment_management_desjardins/payment_management_desjardins.py'
--- payment_management_desjardins/payment_management_desjardins.py 1970-01-01 00:00:00 +0000
+++ payment_management_desjardins/payment_management_desjardins.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,97 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from __future__ import division
23
24import os
25import logging
26
27from openerp.tools.translate import _
28from openerp.osv import orm, fields
29
30_logger = logging.getLogger(__name__)
31
32class res_company_desjardinsinfo(orm.Model):
33 _name = 'res.company.desjardinsinfo'
34 _columns = {
35 'file_creation_number': fields.integer(
36 'Next file creation number value',
37 help="File sequence number, to be incremented by 1 for each file sent without error."
38 ),
39 'originator_id': fields.char(
40 'Originator ID',
41 size=10,
42 help="Name of the directory into which the 'vdcoupon.txt' files will be placed."
43 ),
44 'workdir': fields.char(
45 'Storage Directory',
46 size=255,
47 help="Specify a directory to export to and import from."
48 ),
49 'bank_account_id':fields.many2one(
50 'res.partner.bank',
51 'Your Desjardins bank account',
52 ),
53 'account_id':fields.many2one(
54 'account.account',
55 'Account for Desjardins payments',
56 ),
57 'txn_code_debit': fields.char(
58 'Transaction code for Debit',
59 size=3,
60 ),
61 'txn_code_credit': fields.char(
62 'Transaction code for Credit',
63 size=3,
64 ),
65 'leave_vouchers_draft': fields.boolean(
66 string=u"Leave vouchers as draft",
67 help=u"When creating vouchers, don't validate them, leave then in a draft state",
68 )
69 }
70
71 _defaults = {
72 'workdir': '/tmp',
73 'file_creation_number' : 1,
74 }
75
76 def name_get(self, cr, uid, ids, context=None):
77 return dict.fromkeys(ids, _("Desjardins Merchant Info"))
78
79 def is_complete(self, cr, uid, ids, context=None):
80 """Returns whether our info record is complete.
81
82 Logs a warning if not.
83 """
84 id = ids[0] if isinstance(ids, list) else ids
85 desjinfo = self.browse(cr, uid, id)
86 if not os.path.isdir(desjinfo.workdir):
87 msg = _("Specified directory %s is not a valid directory. Desjardins payment exportation is aborted.")
88 _logger.warning(msg, desjinfo.workdir)
89 return False
90
91 return True
92
93 def increase_file_number(self, cr, uid, ids, context=None):
94 id = ids[0] if isinstance(ids, list) else ids
95 desjinfo = self.browse(cr, uid, id)
96 self.write(cr, uid, id, {'file_creation_number': desjinfo.file_creation_number+1})
97
098
=== added file 'payment_management_desjardins/payment_management_desjardins_view.xml'
--- payment_management_desjardins/payment_management_desjardins_view.xml 1970-01-01 00:00:00 +0000
+++ payment_management_desjardins/payment_management_desjardins_view.xml 2013-09-17 15:32:05 +0000
@@ -0,0 +1,22 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3 <data>
4 <record id="view_desjardinsinfo_form" model="ir.ui.view">
5 <field name="name">res.company.desjardinsinfo.form</field>
6 <field name="model">res.company.desjardinsinfo</field>
7 <field name="arch" type="xml">
8 <form string="Desjardins harmonization info">
9 <field name="file_creation_number" required="1"/>
10 <field name="originator_id" required="1"/>
11 <field name="workdir" required="1"/>
12 <field name="bank_account_id" domain="[('company_id', '!=', False)]" widget="selection" />
13 <field name="account_id"/>
14 <field name="txn_code_debit" required="1"/>
15 <field name="txn_code_credit" required="1"/>
16 <field name="leave_vouchers_draft"/>
17 </form>
18 </field>
19 </record>
20 </data>
21
22</openerp>
023
=== added file 'payment_management_desjardins/record.py'
--- payment_management_desjardins/record.py 1970-01-01 00:00:00 +0000
+++ payment_management_desjardins/record.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,29 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from __future__ import division, unicode_literals
23
24from datetime import date, datetime
25
26def yyyddd(d):
27 three_digit_year = d.strftime('%Y')[1:4]
28 day_of_year = d.strftime('%j')
29 return three_digit_year + day_of_year
030
=== added directory 'payment_management_visa_desjardins'
=== added file 'payment_management_visa_desjardins/__init__.py'
--- payment_management_visa_desjardins/__init__.py 1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/__init__.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,22 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22import payment_management_visa_desjardins
023
=== added file 'payment_management_visa_desjardins/__openerp__.py'
--- payment_management_visa_desjardins/__openerp__.py 1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/__openerp__.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,44 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22{
23 "name" : "Payment management module for visa desjardins",
24 "version" : "1.0",
25 "author" : "Savoir-faire Linux",
26 "website" : "http://www.savoirfairelinux.com",
27 "category" : "Accounting & Finance",
28 "description" : """
29 This module generates 'vdcoupon.txt' files from invoices.
30 """,
31 "depends" : [
32 'payment_management_desjardins',
33 'encrypted_credit_card',
34 ],
35 "init_xml" : [],
36 "update_xml" : [
37 ],
38 'data' : [
39 'payment_management_visa_desjardins_view.xml',
40 ],
41 "demo_xml" : [],
42 "installable" : True,
43 "certificate" : ''
44}
045
=== added directory 'payment_management_visa_desjardins/i18n'
=== added file 'payment_management_visa_desjardins/i18n/payment_management_visa_desjardins.pot'
--- payment_management_visa_desjardins/i18n/payment_management_visa_desjardins.pot 1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/i18n/payment_management_visa_desjardins.pot 2013-09-17 15:32:05 +0000
@@ -0,0 +1,27 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * payment_management_visa_desjardins
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 7.0\n"
8"Report-Msgid-Bugs-To: \n"
9"POT-Creation-Date: 2013-08-21 13:54+0000\n"
10"PO-Revision-Date: 2013-08-21 13:54+0000\n"
11"Last-Translator: <>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: \n"
16"Plural-Forms: \n"
17
18#. module: payment_management_visa_desjardins
19#: model:ir.model,name:payment_management_visa_desjardins.model_res_company
20msgid "Companies"
21msgstr ""
22
23#. module: payment_management_visa_desjardins
24#: field:res.company,desjardins_cc_info:0
25msgid "Visa Desjardins Info"
26msgstr ""
27
028
=== added file 'payment_management_visa_desjardins/payment_management_visa_desjardins.py'
--- payment_management_visa_desjardins/payment_management_visa_desjardins.py 1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/payment_management_visa_desjardins.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,181 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22# ABOUT THIS MODULE AND ENCRYPTION
23#
24# This module doesn't handle plain text CC numbers, it only has access to encrypted CC number
25# (managed by encrypted_credit_card). However, the text file it produces, "vdcoupons", wants plain
26# text CC numbers.
27#
28# Because the vdcoupons file we produce isn't going to be sent directly to Desjardins, we don't need
29# it to be exactly correct, but we also want to minimise the work that the external process, which
30# is going to decrypt the CC numbers before sending it to Desjardins. Therefore, we build the rest
31# of the vdcoupons file exactly as it's going to be sent. Thus, all the external process is going to
32# have to do is to decrypt and replace.
33
34from __future__ import division
35
36import os
37import re
38import logging
39import codecs
40
41from openerp.osv import orm, fields
42
43from .record import get_g_record, get_a_record, get_h_record, get_z_record
44
45_logger = logging.getLogger(__name__)
46
47class res_company_desjardinsinfo_visa(orm.Model):
48 _name = 'res.company.desjardinsinfo.visa'
49 _inherit = 'res.company.desjardinsinfo'
50 _columns = {
51 'merchant_number': fields.char(
52 'Merchant number for Desjardins',
53 size=8,
54 help="Merchant number assigned by DCS. Must be numeric."
55 ),
56 }
57
58class res_company(orm.Model):
59 _inherit = 'res.company'
60 _columns = {
61 'desjardins_cc_info': fields.many2one('res.company.desjardinsinfo.visa', "Visa Desjardins Info"),
62 }
63
64 def _default_desjardins_cc_info(self, cr, uid, context):
65 res_company_desjardinsinfo = self.pool.get('res.company.desjardinsinfo.visa')
66 vals = {
67 'txn_code_debit': '555',
68 'txn_code_credit': '506',
69 }
70 res = res_company_desjardinsinfo.create(cr, uid, vals, context=context)
71 return res
72
73 _defaults = {
74 'desjardins_cc_info': _default_desjardins_cc_info,
75 }
76
77def send_payment_func_visa_desjardins(cpr_obj, cr, uid, customer_payment_requests, context=None):
78 company_obj = cpr_obj.pool.get('res.company')
79 # active_company() is defined in customer_payment_request, which isn't directly in our
80 # dependencies, but for now, this function can't be called unless CPR is installed, so we're
81 # alright. This might have to change when our module starts supporting more workflow.
82 company = company_obj.active_company(cr, uid, context=context)
83 desjinfo = company.desjardins_cc_info
84 directory = desjinfo.workdir
85
86 if not desjinfo.is_complete():
87 return False
88
89 toprocess = [cpr for cpr in customer_payment_requests if cpr.bank_account_id.state == 'credit_card']
90 if not toprocess:
91 return False
92 total_number_of_credit = 0
93 total_number_of_debit = 0
94 total_value_of_credit = 0
95 total_value_of_debit = 0
96 lines = []
97
98 # Type A record
99 a_record = get_a_record(len(lines)+1, company.desjardins_cc_info)
100 lines.append(a_record)
101
102 # Type G records
103 for cpr in toprocess:
104 bank_account = cpr.bank_account_id
105 partner = bank_account.partner_id
106 # The amount is in cents
107 amount = round(cpr.amount * 100)
108 if amount > 0:
109 total_number_of_debit += 1
110 total_value_of_debit += amount
111 else:
112 amount = abs(amount)
113 total_number_of_credit += 1
114 total_value_of_credit += amount
115 cc_number = '[RSA]%s[/RSA]' % bank_account.encrypted_cc_number.strip()
116 g_record = get_g_record(
117 len(lines)+1, company, amount, cc_number, bank_account.expiration_date, partner.id
118 )
119 lines.append(g_record)
120 cpr.write({'state': 'sent', 'batch_ref': 'visa_desjardins'}, context=context)
121
122 # Type H record
123 h_record = get_h_record(
124 len(lines)+1, company.desjardins_cc_info,
125 total_number_of_debit, total_value_of_debit,
126 total_number_of_credit, total_value_of_credit
127 )
128 lines.append(h_record)
129
130 # Type Z record
131 z_record = get_z_record(
132 len(lines)+1, company.desjardins_cc_info,
133 total_number_of_debit, total_value_of_debit,
134 total_number_of_credit, total_value_of_credit
135 )
136 lines.append(z_record)
137 with codecs.open(os.path.join(directory, 'vdcoupon.txt'), 'wt', encoding='latin-1') as fp:
138 fp.write(u'\n'.join(lines))
139 desjinfo.increase_file_number()
140 return True
141
142def receive_payment_func_visa_desjardins(cpr_obj, cr, uid, context=None):
143 company_obj = cpr_obj.pool.get('res.company')
144 # see comment about active_company() in send_payment_func_visa_desjardins()
145 company = company_obj.active_company(cr, uid, context=context)
146 desjinfo = company.desjardins_cc_info
147 directory = desjinfo.workdir
148 account_id = desjinfo.account_id.id
149 journal_id = desjinfo.bank_account_id.journal_id.id
150
151 filenames = os.listdir(directory)
152 filenames = [fn for fn in filenames if re.match(r"\d{8}-\d{4}-vdcoupon\.acc", fn)]
153 if not filenames:
154 # Nothing to process
155 return
156 for filename in filenames:
157 filepath = os.path.join(directory, filename)
158 lines = open(filepath, 'rt').readlines()
159 for line in lines:
160 if not line.startswith('G'):
161 continue
162 partner_id = int(line[118:118+19])
163 amount = int(line[27:27+10]) / 100
164 cpr_obj.register_payment_acceptation(
165 cr, uid, partner_id, amount, account_id, journal_id,
166 leave_draft=desjinfo.leave_vouchers_draft, context=context
167 )
168 os.rename(filepath, filepath[:-3] + 'imported')
169
170 # Any CPR with a "visa_desjardins" batch_ref can be considered as rejected
171 search_domain = [('batch_ref', '=', 'visa_desjardins'), ('state', '=', 'sent')]
172 rejected_cpr_ids = cpr_obj.search(cr, uid, search_domain, context=context)
173 for cpr in cpr_obj.browse(cr, uid, rejected_cpr_ids, context=context):
174 cpr_obj.register_payment_refusal(cr, uid, cpr.partner_id.id, context=context)
175 return True
176
177try:
178 from customer_payment_request import registry
179 registry.register(send_payment_func_visa_desjardins, receive_payment_func_visa_desjardins)
180except ImportError:
181 pass
0182
=== added file 'payment_management_visa_desjardins/payment_management_visa_desjardins_view.xml'
--- payment_management_visa_desjardins/payment_management_visa_desjardins_view.xml 1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/payment_management_visa_desjardins_view.xml 2013-09-17 15:32:05 +0000
@@ -0,0 +1,35 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3 <data>
4 <!-- I've tried inheriting the view from res.company.desjardinsinfo, but it didn't work. -->
5 <record id="view_desjardinsinfo_visa_form" model="ir.ui.view">
6 <field name="name">res.company.desjardinsinfo.visa.form</field>
7 <field name="model">res.company.desjardinsinfo.visa</field>
8 <field name="arch" type="xml">
9 <form string="Desjardins harmonization info">
10 <field name="merchant_number" required="1"/>
11 <field name="file_creation_number" required="1"/>
12 <field name="originator_id" required="1"/>
13 <field name="workdir" required="1"/>
14 <field name="bank_account_id" domain="[('company_id', '!=', False)]" widget="selection" />
15 <field name="account_id"/>
16 <field name="txn_code_debit" required="1"/>
17 <field name="txn_code_credit" required="1"/>
18 <field name="leave_vouchers_draft"/>
19 </form>
20 </field>
21 </record>
22
23 <record id="view_company_form" model="ir.ui.view">
24 <field name="name">res.company.form</field>
25 <field name="model">res.company</field>
26 <field name="inherit_id" ref="base.view_company_form"/>
27 <field name="arch" type="xml">
28 <xpath expr="//page[@string='General Information']/group/group" position="inside">
29 <field name="desjardins_cc_info"/>
30 </xpath>
31 </field>
32 </record>
33 </data>
34
35</openerp>
036
=== added file 'payment_management_visa_desjardins/record.py'
--- payment_management_visa_desjardins/record.py 1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/record.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,108 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from __future__ import division, unicode_literals
23
24from datetime import date, datetime
25
26from payment_management_desjardins.record import yyyddd
27
28def get_a_record(seq, desjinfo):
29 return ''.join([
30 'A',
31 '%09d' % seq,
32 desjinfo.originator_id[:10].upper().ljust(10, ' '),
33 '%04d' % desjinfo.file_creation_number,
34 yyyddd(datetime.now()),
35 '81510',
36 ' ' * 1429,
37 ])
38
39def get_h_record(seq, desjinfo, num_debit, total_debit, num_credit, total_credit):
40 return ''.join([
41 'H',
42 '%09d' % seq,
43 desjinfo.originator_id[:10].upper().ljust(10, ' '),
44 '%04d' % desjinfo.file_creation_number,
45 '%014d' % total_debit,
46 '%08d' % num_debit,
47 '%014d' % total_credit,
48 '%08d' % num_credit,
49 '1',
50 '0' * 10,
51 '0' * 4,
52 desjinfo.merchant_number[:8].rjust(8, '0'),
53 ' ' * 1373,
54 ])
55
56def get_z_record(seq, desjinfo, num_debit, total_debit, num_credit, total_credit):
57 return ''.join([
58 'Z',
59 '%09d' % seq,
60 desjinfo.originator_id[:10].upper().ljust(10, ' '),
61 '%04d' % desjinfo.file_creation_number,
62 '%014d' % total_debit,
63 '%08d' % num_debit,
64 '%014d' % total_credit,
65 '%08d' % num_credit,
66 '1',
67 '0' * 10,
68 '0' * 4,
69 ' ' * 1381,
70 ])
71
72def get_g_record(seq, company, amount, cc_number, cc_expiration, partner_ref):
73 desjinfo = company.desjardins_cc_info
74 # amount is an integer representing cents
75 if amount > 0:
76 txn_type = desjinfo.txn_code_debit
77 else:
78 txn_type = desjinfo.txn_code_credit
79 amount = abs(amount)
80 return ''.join([
81 'G', # Alpha 1
82 '%09d' % seq, # Num 9
83 desjinfo.originator_id[:10].upper().ljust(10, ' '), # Alpha 10
84 '%04d' % desjinfo.file_creation_number,# Num 4
85 txn_type, # Num 3
86 '%010d' % amount, # Num 10
87 date.today().strftime("%m%d%y"), # Num 6
88 cc_number.rjust(19, '0'),
89 '0' * 2, # Alpha 2
90 ' ' * 2, # Alpha 2
91 ' ' * 2, # Alpha 2
92 ' ', # Alpha 1
93 ' ', # Alpha 1
94 desjinfo.merchant_number[:8].rjust(8, '0'), # Num 8
95 company.name[:25].ljust(25, ' '), # Alpha 25
96 (company.city or '')[:13].ljust(13, ' '), # Alpha 13
97 company.state_id.code or ' ', # Alpha 2
98 '%019d' % partner_ref, # Num 19
99 '0' * 6, # Alpha 6
100 '0' * 9, # Alpha 9
101 '0' * 4, # Num 4
102 '0' * 4, # Num 4
103 ' ' * 5, # Alpha 5
104 '0', # Num 1
105 '0' * 8, # Alpha 8
106 cc_expiration, # Alpha 4
107 ' ' * 26, # Alpha 26
108 ])
0109
=== added directory 'payment_management_visa_desjardins/scripts'
=== added file 'payment_management_visa_desjardins/scripts/vdcoupon_watch_recv.py'
--- payment_management_visa_desjardins/scripts/vdcoupon_watch_recv.py 1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/scripts/vdcoupon_watch_recv.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,78 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22import argparse
23import time
24import os.path as op
25from datetime import datetime
26import re
27
28import paramiko
29
30def parse_options():
31 parser = argparse.ArgumentParser(description='Wait for vdcoupon.acc from Desjardins, remove CC numbers and timestamp it.')
32 parser.add_argument('dest_folder', help="Folder where vdcoupon is sent.")
33 parser.add_argument('ssh_cred', help="Desjardins SSH creds (user@host:path).")
34 return parser.parse_args()
35
36def process_file(sftp, source_path, dest_folder):
37 source_fp = sftp.open(source_path, 'rt')
38 lines = source_fp.readlines()
39 newlines = []
40 for line in lines:
41 if line.startswith('G'):
42 chars = list(line)
43 # credit card numbers are the 19 characters starting at index 1525
44 chars[43:43+19] = '0' * 19
45 line = ''.join(chars)
46 newlines.append(line)
47 source_fp.close()
48 timestamp = datetime.now().strftime('%Y%m%d-%H%M')
49 result_filename = op.join(dest_folder, '%s-vdcoupon.acc' % timestamp)
50 with open(result_filename, 'wt') as result_fp:
51 result_fp.writelines(newlines)
52 sftp.remove(source_path)
53
54def watch_forever(options):
55 m = re.match(r"(.+)@([^:]+):?(.*)", options.ssh_cred)
56 username = m.group(1)
57 hostname = m.group(2)
58 sshpath = m.group(3)
59 ssh = paramiko.SSHClient()
60 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
61 ssh.load_system_host_keys()
62 ssh.connect(hostname, 22, username)
63 sftp = ssh.open_sftp()
64 while True:
65 if 'vdcoupon.acc' in sftp.listdir(sshpath):
66 print "File found! Processing"
67 source_path = op.join(sshpath, 'vdcoupon.acc')
68 process_file(sftp, source_path, options.dest_folder)
69 print "Process done, file deleted"
70 time.sleep(10)
71
72def main():
73 options = parse_options()
74 print "Starting to watch for vdcoupon.acc with dest {}...".format(options.dest_folder)
75 watch_forever(options)
76
77if __name__ == '__main__':
78 main()
0\ No newline at end of file79\ No newline at end of file
180
=== added file 'payment_management_visa_desjardins/scripts/vdcoupon_watch_send.py'
--- payment_management_visa_desjardins/scripts/vdcoupon_watch_send.py 1970-01-01 00:00:00 +0000
+++ payment_management_visa_desjardins/scripts/vdcoupon_watch_send.py 2013-09-17 15:32:05 +0000
@@ -0,0 +1,84 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2013 Savoir-faire Linux (<http://www.savoirfairelinux.com>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22import argparse
23import time
24import os
25import os.path as op
26import re
27import binascii
28
29import paramiko
30from Crypto.PublicKey import RSA
31
32def parse_options():
33 parser = argparse.ArgumentParser(description='Wait for vdcoupon.txt from OpenERP, decrypt it and send it to Desjardins.')
34 parser.add_argument('watched_folder', help="Folder where vdcoupon is sent.")
35 parser.add_argument('private_key', help="Private RSA key to decrypt CC numbers with.")
36 parser.add_argument('ssh_cred', help="Desjardins SSH creds (user@host:path).")
37 return parser.parse_args()
38
39def process_file(watched_file, private_key):
40 def repl(match):
41 encrypted_cc_base64 = match.group(1)
42 encrypted_cc = binascii.a2b_base64(encrypted_cc_base64)
43 decrypted_cc = private_key.decrypt(encrypted_cc)
44 return decrypted_cc.zfill(19)
45
46 with open(watched_file, 'rt') as source_fp:
47 contents = source_fp.read()
48 contents = re.sub(r"\[RSA\](.+?)\[/RSA\]", repl, contents)
49 return contents
50
51def send_contents(hostname, username, sshpath, contents):
52 ssh = paramiko.SSHClient()
53 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
54 ssh.load_system_host_keys()
55 ssh.connect(hostname, 22, username)
56 sftp = ssh.open_sftp()
57 fp = sftp.open(op.join(sshpath, 'vdcoupon.txt'), 'wt')
58 fp.write(contents)
59 fp.close()
60
61def watch_forever(options):
62 watched_file = op.join(options.watched_folder, 'vdcoupon.txt')
63 with open(options.private_key, 'rt') as key_fp:
64 private_key = RSA.importKey(key_fp.read())
65 m = re.match(r"(.+)@([^:]+):?(.*)", options.ssh_cred)
66 username = m.group(1)
67 hostname = m.group(2)
68 sshpath = m.group(3)
69 while True:
70 if op.exists(watched_file):
71 print "File found! Processing"
72 contents = process_file(watched_file, private_key)
73 send_contents(hostname, username, sshpath, contents)
74 os.remove(watched_file)
75 print "Process done, file deleted"
76 time.sleep(10)
77
78def main():
79 options = parse_options()
80 print "Starting to watch for vdcoupon.txt in {}...".format(options.watched_folder)
81 watch_forever(options)
82
83if __name__ == '__main__':
84 main()
0\ No newline at end of file85\ No newline at end of file

Subscribers

People subscribed via source and target branches