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