Merge lp:~camptocamp/banking-addons/bank-statement-reconcile_vre into lp:banking-addons/6.1

Proposed by Vincent Renaville@camptocamp
Status: Superseded
Proposed branch: lp:~camptocamp/banking-addons/bank-statement-reconcile_vre
Merge into: lp:banking-addons/6.1
Diff against target: 8071 lines (+7614/-0)
85 files modified
account_advanced_reconcile/__init__.py (+24/-0)
account_advanced_reconcile/__openerp__.py (+83/-0)
account_advanced_reconcile/advanced_reconciliation.py (+118/-0)
account_advanced_reconcile/base_advanced_reconciliation.py (+272/-0)
account_advanced_reconcile/easy_reconcile.py (+36/-0)
account_advanced_reconcile/easy_reconcile_view.xml (+19/-0)
account_advanced_reconcile/i18n/fr.po (+91/-0)
account_easy_reconcile/__init__.py (+25/-0)
account_easy_reconcile/__openerp__.py (+66/-0)
account_easy_reconcile/base_reconciliation.py (+208/-0)
account_easy_reconcile/easy_reconcile.py (+285/-0)
account_easy_reconcile/easy_reconcile.xml (+156/-0)
account_easy_reconcile/easy_reconcile_history.py (+153/-0)
account_easy_reconcile/easy_reconcile_history_view.xml (+98/-0)
account_easy_reconcile/i18n/fr.po (+425/-0)
account_easy_reconcile/security/ir.model.access.csv (+15/-0)
account_easy_reconcile/security/ir_rule.xml (+25/-0)
account_easy_reconcile/simple_reconciliation.py (+120/-0)
account_statement_base_completion/__init__.py (+23/-0)
account_statement_base_completion/__openerp__.py (+74/-0)
account_statement_base_completion/data.xml (+37/-0)
account_statement_base_completion/i18n/fr.po (+180/-0)
account_statement_base_completion/partner.py (+38/-0)
account_statement_base_completion/partner_view.xml (+22/-0)
account_statement_base_completion/security/ir.model.access.csv (+3/-0)
account_statement_base_completion/statement.py (+527/-0)
account_statement_base_completion/statement_view.xml (+104/-0)
account_statement_base_import/__init__.py (+23/-0)
account_statement_base_import/__openerp__.py (+70/-0)
account_statement_base_import/data/statement.csv (+4/-0)
account_statement_base_import/i18n/fr.po (+253/-0)
account_statement_base_import/parser/__init__.py (+25/-0)
account_statement_base_import/parser/file_parser.py (+218/-0)
account_statement_base_import/parser/generic_file_parser.py (+102/-0)
account_statement_base_import/parser/parser.py (+219/-0)
account_statement_base_import/statement.py (+303/-0)
account_statement_base_import/statement_view.xml (+46/-0)
account_statement_base_import/wizard/__init__.py (+20/-0)
account_statement_base_import/wizard/import_statement.py (+122/-0)
account_statement_base_import/wizard/import_statement_view.xml (+44/-0)
account_statement_completion_voucher/__init__.py (+20/-0)
account_statement_completion_voucher/__openerp__.py (+46/-0)
account_statement_completion_voucher/statement_view.xml (+21/-0)
account_statement_ext/__init__.py (+25/-0)
account_statement_ext/__openerp__.py (+88/-0)
account_statement_ext/account.py (+38/-0)
account_statement_ext/i18n/fr.po (+301/-0)
account_statement_ext/report.xml (+25/-0)
account_statement_ext/report/__init__.py (+26/-0)
account_statement_ext/report/bank_statement_report.mako (+69/-0)
account_statement_ext/report/bank_statement_report.py (+71/-0)
account_statement_ext/report/bank_statement_webkit_header.xml (+180/-0)
account_statement_ext/security/ir.model.access.csv (+3/-0)
account_statement_ext/security/ir_rule.xml (+10/-0)
account_statement_ext/statement.py (+678/-0)
account_statement_ext/statement_view.xml (+162/-0)
account_statement_ext/voucher.py (+49/-0)
account_statement_ext_voucher/__init__.py (+20/-0)
account_statement_ext_voucher/__openerp__.py (+52/-0)
account_statement_ext_voucher/statement_voucher.py (+51/-0)
account_statement_transactionid_completion/__init__.py (+22/-0)
account_statement_transactionid_completion/__openerp__.py (+56/-0)
account_statement_transactionid_completion/data.xml (+12/-0)
account_statement_transactionid_completion/statement.py (+94/-0)
account_statement_transactionid_completion/statement_view.xml (+22/-0)
account_statement_transactionid_import/__init__.py (+22/-0)
account_statement_transactionid_import/__openerp__.py (+60/-0)
account_statement_transactionid_import/data/statement.csv (+4/-0)
account_statement_transactionid_import/parser/__init__.py (+22/-0)
account_statement_transactionid_import/parser/transactionid_file_parser.py (+86/-0)
account_statement_transactionid_import/statement.py (+47/-0)
base_transaction_id/__init__.py (+24/-0)
base_transaction_id/__openerp__.py (+57/-0)
base_transaction_id/invoice.py (+36/-0)
base_transaction_id/invoice_view.xml (+30/-0)
base_transaction_id/sale.py (+43/-0)
base_transaction_id/sale_view.xml (+21/-0)
base_transaction_id/stock.py (+42/-0)
invoicing_voucher_killer/__init__.py (+20/-0)
invoicing_voucher_killer/__openerp__.py (+39/-0)
invoicing_voucher_killer/invoice_data.xml (+7/-0)
invoicing_voucher_killer/invoice_view.xml (+48/-0)
statement_voucher_killer/__init__.py (+21/-0)
statement_voucher_killer/__openerp__.py (+40/-0)
statement_voucher_killer/voucher.py (+128/-0)
To merge this branch: bzr merge lp:~camptocamp/banking-addons/bank-statement-reconcile_vre
Reviewer Review Type Date Requested Status
Banking Addons Core Editors Pending
Review via email: mp+168350@code.launchpad.net

This proposal has been superseded by a proposal from 2013-06-10.

Description of the change

This fix prevent account_advanced_reconcile to crash if ref in the move is not set

To post a comment you must log in.

Unmerged revisions

93. By Vincent Renaville@camptocamp

[FIX] prevent crash if move ref is null

92. By Nicolas Bessi - Camptocamp

[MRG] [FIX] Restore default period in bank statement by taking context in account

91. By Nicolas Bessi - Camptocamp

[MRG] [IMP] Refine partner label lookup error message
[FIX] Restore error message in log
[FIX] Fix partner label lookup regexp

90. By Nicolas Bessi - Camptocamp

[MRG] Major refactoring of statement import and completion:

Fix the way to look default account on partner:
- If master account is provided in profile it will be forced
 - If the customer checkbox is checked on the found partner, type and account will be customer and receivable
 - If the supplier checkbox is checked on the found partner, type and account will be supplier and payable
 - If both checkbox are checked or none of them, it'll be based on the amount :
    If amount is positif the type and account will be customer and receivable,
    If amount is negativ, the type and account will be supplier and payable

Completion:
-Fix and refactor the the invoices lookup for completion
-Various fixes in completion rules
- Non matches lines are not mark as completed.

Optimisation:

Refactoring of statement import:
We by pass ORM to increase performances.
TODO support of sparse field

Refactoring of completion.
We have done some structural changes in order to avoid a lot of un needed call to ORM.
Bypass orm when writing to database.

These merge is required in order to fix transaction id completion rules and import +
Handle the new semantic change in OpenERP partner model

89. By Alexandre Fayolle - camptocamp

[FIX] permissions on account.statement.profil: an accountant needs write access on the model to be able to import a bank statement

  otherwise, the message.post call fails

88. By Nicolas Bessi - Camptocamp

[MRG] Fixes performance trouble when using bank_statement_label based completion rules by using memoizer pattern.

Add lines in context to be able to acces them in completion rules. It is not
mandatory as we can do line.satement_id.line_ids but it is more efficient.

Some minor cleanup

87. By Nicolas Bessi - Camptocamp

[MRG] Improve statement import wizard by using mass writing of statement line in order to avoid store field loop computation.

86. By Nicolas Bessi - Camptocamp

[MRG] Improve statement import global usability by retuning usable error message while parsing files.
Allows empty value for float in parsed CSV.

Added note about the additional dependency on python-xlrd in the description.

85. By Nicolas Bessi - Camptocamp

[MRG] Add a completion rule to allows supplier invoice completion baser on invoice number

84. By Nicolas Bessi - Camptocamp

[MRG] Adds two add-ons that prevent voucher to interfer with the statement ext related add-ons flow

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'account_advanced_reconcile'
=== added file 'account_advanced_reconcile/__init__.py'
--- account_advanced_reconcile/__init__.py 1970-01-01 00:00:00 +0000
+++ account_advanced_reconcile/__init__.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,24 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Guewen Baconnier
5# Copyright 2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22import easy_reconcile
23import base_advanced_reconciliation
24import advanced_reconciliation
025
=== added file 'account_advanced_reconcile/__openerp__.py'
--- account_advanced_reconcile/__openerp__.py 1970-01-01 00:00:00 +0000
+++ account_advanced_reconcile/__openerp__.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,83 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Guewen Baconnier
5# Copyright 2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22{'name': "Advanced Reconcile",
23 'version': '1.0',
24 'author': 'Camptocamp',
25 'maintainer': 'Camptocamp',
26 'category': 'Finance',
27 'complexity': 'normal',
28 'depends': ['account_easy_reconcile',
29 ],
30 'description': """
31Advanced reconciliation methods for the module account_easy_reconcile.
32
33In addition to the features implemented in account_easy_reconcile, which are:
34 - reconciliation facilities for big volume of transactions
35 - setup different profiles of reconciliation by account
36 - each profile can use many methods of reconciliation
37 - this module is also a base to create others reconciliation methods
38 which can plug in the profiles
39 - a profile a reconciliation can be run manually or by a cron
40 - monitoring of reconcilation runs with an history
41
42It implements a basis to created advanced reconciliation methods in a few lines
43of code.
44
45Typically, such a method can be:
46 - Reconcile Journal items if the partner and the ref are equal
47 - Reconcile Journal items if the partner is equal and the ref
48 is the same than ref or name
49 - Reconcile Journal items if the partner is equal and the ref
50 match with a pattern
51
52And they allows:
53 - Reconciliations with multiple credit / multiple debit lines
54 - Partial reconciliations
55 - Write-off amount as well
56
57A method is already implemented in this module, it matches on items:
58 - Partner
59 - Ref on credit move lines should be case insensitive equals to the ref or
60 the name of the debit move line
61
62The base class to find the reconciliations is built to be as efficient as
63possible.
64
65So basically, if you have an invoice with 3 payments (one per month), the first
66month, it will partial reconcile the debit move line with the first payment, the second
67month, it will partial reconcile the debit move line with 2 first payments,
68the third month, it will make the full reconciliation.
69
70This module is perfectly adapted for E-Commerce business where a big volume of
71move lines and so, reconciliations, are involved and payments often come from
72many offices.
73
74 """,
75 'website': 'http://www.camptocamp.com',
76 'data': ['easy_reconcile_view.xml'],
77 'test': [],
78 'images': [],
79 'installable': True,
80 'auto_install': False,
81 'license': 'AGPL-3',
82 'application': True,
83}
084
=== added file 'account_advanced_reconcile/advanced_reconciliation.py'
--- account_advanced_reconcile/advanced_reconciliation.py 1970-01-01 00:00:00 +0000
+++ account_advanced_reconcile/advanced_reconciliation.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,118 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Guewen Baconnier
5# Copyright 2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from openerp.osv import orm
23
24
25class easy_reconcile_advanced_ref(orm.TransientModel):
26
27 _name = 'easy.reconcile.advanced.ref'
28 _inherit = 'easy.reconcile.advanced'
29
30 def _skip_line(self, cr, uid, rec, move_line, context=None):
31 """
32 When True is returned on some conditions, the credit move line
33 will be skipped for reconciliation. Can be inherited to
34 skip on some conditions. ie: ref or partner_id is empty.
35 """
36 return not (move_line.get('ref') and move_line.get('partner_id'))
37
38 def _matchers(self, cr, uid, rec, move_line, context=None):
39 """
40 Return the values used as matchers to find the opposite lines
41
42 All the matcher keys in the dict must have their equivalent in
43 the `_opposite_matchers`.
44
45 The values of each matcher key will be searched in the
46 one returned by the `_opposite_matchers`
47
48 Must be inherited to implement the matchers for one method
49
50 For instance, it can return:
51 return ('ref', move_line['rec'])
52
53 or
54 return (('partner_id', move_line['partner_id']),
55 ('ref', "prefix_%s" % move_line['rec']))
56
57 All the matchers have to be found in the opposite lines
58 to consider them as "opposite"
59
60 The matchers will be evaluated in the same order as declared
61 vs the the opposite matchers, so you can gain performance by
62 declaring first the partners with the less computation.
63
64 All matchers should match with their opposite to be considered
65 as "matching".
66 So with the previous example, partner_id and ref have to be
67 equals on the opposite line matchers.
68
69 :return: tuple of tuples (key, value) where the keys are
70 the matchers keys
71 (must be the same than `_opposite_matchers` returns,
72 and their values to match in the opposite lines.
73 A matching key can have multiples values.
74 """
75 return (('partner_id', move_line['partner_id']),
76 ('ref', move_line['ref'].lower().strip()))
77
78 def _opposite_matchers(self, cr, uid, rec, move_line, context=None):
79 """
80 Return the values of the opposite line used as matchers
81 so the line is matched
82
83 Must be inherited to implement the matchers for one method
84 It can be inherited to apply some formatting of fields
85 (strip(), lower() and so on)
86
87 This method is the counterpart of the `_matchers()` method.
88
89 Each matcher has to yield its value respecting the order
90 of the `_matchers()`.
91
92 When a matcher does not correspond, the next matchers won't
93 be evaluated so the ones which need the less computation
94 have to be executed first.
95
96 If the `_matchers()` returns:
97 (('partner_id', move_line['partner_id']),
98 ('ref', move_line['ref']))
99
100 Here, you should yield :
101 yield ('partner_id', move_line['partner_id'])
102 yield ('ref', move_line['ref'])
103
104 Note that a matcher can contain multiple values, as instance,
105 if for a move line, you want to search from its `ref` in the
106 `ref` or `name` fields of the opposite move lines, you have to
107 yield ('partner_id', move_line['partner_id'])
108 yield ('ref', (move_line['ref'], move_line['name'])
109
110 An OR is used between the values for the same key.
111 An AND is used between the differents keys.
112
113 :param dict move_line: values of the move_line
114 :yield: matchers as tuple ('matcher key', value(s))
115 """
116 yield ('partner_id', move_line['partner_id'])
117 yield ('ref', ((move_line['ref'] or '').lower().strip(),
118 move_line['name'].lower().strip()))
0119
=== added file 'account_advanced_reconcile/base_advanced_reconciliation.py'
--- account_advanced_reconcile/base_advanced_reconciliation.py 1970-01-01 00:00:00 +0000
+++ account_advanced_reconcile/base_advanced_reconciliation.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,272 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Guewen Baconnier
5# Copyright 2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from itertools import product
23from openerp.osv import orm
24
25
26class easy_reconcile_advanced(orm.AbstractModel):
27
28 _name = 'easy.reconcile.advanced'
29 _inherit = 'easy.reconcile.base'
30
31 def _query_debit(self, cr, uid, rec, context=None):
32 """Select all move (debit>0) as candidate. """
33 select = self._select(rec)
34 sql_from = self._from(rec)
35 where, params = self._where(rec)
36 where += " AND account_move_line.debit > 0 "
37
38 where2, params2 = self._get_filter(cr, uid, rec, context=context)
39
40 query = ' '.join((select, sql_from, where, where2))
41
42 cr.execute(query, params + params2)
43 return cr.dictfetchall()
44
45 def _query_credit(self, cr, uid, rec, context=None):
46 """Select all move (credit>0) as candidate. """
47 select = self._select(rec)
48 sql_from = self._from(rec)
49 where, params = self._where(rec)
50 where += " AND account_move_line.credit > 0 "
51
52 where2, params2 = self._get_filter(cr, uid, rec, context=context)
53
54 query = ' '.join((select, sql_from, where, where2))
55
56 cr.execute(query, params + params2)
57 return cr.dictfetchall()
58
59 def _matchers(self, cr, uid, rec, move_line, context=None):
60 """
61 Return the values used as matchers to find the opposite lines
62
63 All the matcher keys in the dict must have their equivalent in
64 the `_opposite_matchers`.
65
66 The values of each matcher key will be searched in the
67 one returned by the `_opposite_matchers`
68
69 Must be inherited to implement the matchers for one method
70
71 As instance, it can return:
72 return ('ref', move_line['rec'])
73
74 or
75 return (('partner_id', move_line['partner_id']),
76 ('ref', "prefix_%s" % move_line['rec']))
77
78 All the matchers have to be found in the opposite lines
79 to consider them as "opposite"
80
81 The matchers will be evaluated in the same order as declared
82 vs the the opposite matchers, so you can gain performance by
83 declaring first the partners with the less computation.
84
85 All matchers should match with their opposite to be considered
86 as "matching".
87 So with the previous example, partner_id and ref have to be
88 equals on the opposite line matchers.
89
90 :return: tuple of tuples (key, value) where the keys are
91 the matchers keys
92 (must be the same than `_opposite_matchers` returns,
93 and their values to match in the opposite lines.
94 A matching key can have multiples values.
95 """
96 raise NotImplementedError
97
98 def _opposite_matchers(self, cr, uid, rec, move_line, context=None):
99 """
100 Return the values of the opposite line used as matchers
101 so the line is matched
102
103 Must be inherited to implement the matchers for one method
104 It can be inherited to apply some formatting of fields
105 (strip(), lower() and so on)
106
107 This method is the counterpart of the `_matchers()` method.
108
109 Each matcher has to yield its value respecting the order
110 of the `_matchers()`.
111
112 When a matcher does not correspond, the next matchers won't
113 be evaluated so the ones which need the less computation
114 have to be executed first.
115
116 If the `_matchers()` returns:
117 (('partner_id', move_line['partner_id']),
118 ('ref', move_line['ref']))
119
120 Here, you should yield :
121 yield ('partner_id', move_line['partner_id'])
122 yield ('ref', move_line['ref'])
123
124 Note that a matcher can contain multiple values, as instance,
125 if for a move line, you want to search from its `ref` in the
126 `ref` or `name` fields of the opposite move lines, you have to
127 yield ('partner_id', move_line['partner_id'])
128 yield ('ref', (move_line['ref'], move_line['name'])
129
130 An OR is used between the values for the same key.
131 An AND is used between the differents keys.
132
133 :param dict move_line: values of the move_line
134 :yield: matchers as tuple ('matcher key', value(s))
135 """
136 raise NotImplementedError
137
138 @staticmethod
139 def _compare_values(key, value, opposite_value):
140 """Can be inherited to modify the equality condition
141 specifically according to the matcher key (maybe using
142 a like operator instead of equality on 'ref' as instance)
143 """
144 # consider that empty vals are not valid matchers
145 # it can still be inherited for some special cases
146 # where it would be allowed
147 if not (value and opposite_value):
148 return False
149
150 if value == opposite_value:
151 return True
152 return False
153
154 @staticmethod
155 def _compare_matcher_values(key, values, opposite_values):
156 """ Compare every values from a matcher vs an opposite matcher
157 and return True if it matches
158 """
159 for value, ovalue in product(values, opposite_values):
160 # we do not need to compare all values, if one matches
161 # we are done
162 if easy_reconcile_advanced._compare_values(key, value, ovalue):
163 return True
164 return False
165
166 @staticmethod
167 def _compare_matchers(matcher, opposite_matcher):
168 """
169 Prepare and check the matchers to compare
170 """
171 mkey, mvalue = matcher
172 omkey, omvalue = opposite_matcher
173 assert mkey == omkey, ("A matcher %s is compared with a matcher %s, "
174 " the _matchers and _opposite_matchers are probably wrong" %
175 (mkey, omkey))
176 if not isinstance(mvalue, (list, tuple)):
177 mvalue = mvalue,
178 if not isinstance(omvalue, (list, tuple)):
179 omvalue = omvalue,
180 return easy_reconcile_advanced._compare_matcher_values(mkey, mvalue, omvalue)
181
182 def _compare_opposite(self, cr, uid, rec, move_line, opposite_move_line,
183 matchers, context=None):
184 """ Iterate over the matchers of the move lines vs opposite move lines
185 and if they all match, return True.
186
187 If all the matchers match for a move line and an opposite move line,
188 they are candidate for a reconciliation.
189 """
190 opp_matchers = self._opposite_matchers(cr, uid, rec, opposite_move_line,
191 context=context)
192 for matcher in matchers:
193 try:
194 opp_matcher = opp_matchers.next()
195 except StopIteration:
196 # if you fall here, you probably missed to put a `yield`
197 # in `_opposite_matchers()`
198 raise ValueError("Missing _opposite_matcher: %s" % matcher[0])
199
200 if not self._compare_matchers(matcher, opp_matcher):
201 # if any of the matcher fails, the opposite line
202 # is not a valid counterpart
203 # directly returns so the next yield of _opposite_matchers
204 # are not evaluated
205 return False
206
207 return True
208
209 def _search_opposites(self, cr, uid, rec, move_line, opposite_move_lines, context=None):
210 """
211 Search the opposite move lines for a move line
212
213 :param dict move_line: the move line for which we search opposites
214 :param list opposite_move_lines: list of dict of move lines values, the move
215 lines we want to search for
216 :return: list of matching lines
217 """
218 matchers = self._matchers(cr, uid, rec, move_line, context=context)
219 return [op for op in opposite_move_lines if
220 self._compare_opposite(
221 cr, uid, rec, move_line, op, matchers, context=context)]
222
223 def _action_rec(self, cr, uid, rec, context=None):
224 credit_lines = self._query_credit(cr, uid, rec, context=context)
225 debit_lines = self._query_debit(cr, uid, rec, context=context)
226 return self._rec_auto_lines_advanced(
227 cr, uid, rec, credit_lines, debit_lines, context=context)
228
229 def _skip_line(self, cr, uid, rec, move_line, context=None):
230 """
231 When True is returned on some conditions, the credit move line
232 will be skipped for reconciliation. Can be inherited to
233 skip on some conditions. ie: ref or partner_id is empty.
234 """
235 return False
236
237 def _rec_auto_lines_advanced(self, cr, uid, rec, credit_lines, debit_lines, context=None):
238 """ Advanced reconciliation main loop """
239 reconciled_ids = []
240 partial_reconciled_ids = []
241 reconcile_groups = []
242
243 for credit_line in credit_lines:
244 if self._skip_line(cr, uid, rec, credit_line, context=context):
245 continue
246
247 opposite_lines = self._search_opposites(
248 cr, uid, rec, credit_line, debit_lines, context=context)
249
250 if not opposite_lines:
251 continue
252
253 opposite_ids = [l['id'] for l in opposite_lines]
254 line_ids = opposite_ids + [credit_line['id']]
255 for group in reconcile_groups:
256 if any([lid in group for lid in opposite_ids]):
257 group.update(line_ids)
258 break
259 else:
260 reconcile_groups.append(set(line_ids))
261
262 lines_by_id = dict([(l['id'], l) for l in credit_lines + debit_lines])
263 for reconcile_group_ids in reconcile_groups:
264 group_lines = [lines_by_id[lid] for lid in reconcile_group_ids]
265 reconciled, full = self._reconcile_lines(
266 cr, uid, rec, group_lines, allow_partial=True, context=context)
267 if reconciled and full:
268 reconciled_ids += reconcile_group_ids
269 elif reconciled:
270 partial_reconciled_ids += reconcile_group_ids
271
272 return reconciled_ids, partial_reconciled_ids
0273
=== added file 'account_advanced_reconcile/easy_reconcile.py'
--- account_advanced_reconcile/easy_reconcile.py 1970-01-01 00:00:00 +0000
+++ account_advanced_reconcile/easy_reconcile.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,36 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Guewen Baconnier
5# Copyright 2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from openerp.osv import orm
23
24
25class account_easy_reconcile_method(orm.Model):
26
27 _inherit = 'account.easy.reconcile.method'
28
29 def _get_all_rec_method(self, cr, uid, context=None):
30 methods = super(account_easy_reconcile_method, self).\
31 _get_all_rec_method(cr, uid, context=context)
32 methods += [
33 ('easy.reconcile.advanced.ref',
34 'Advanced. Partner and Ref.'),
35 ]
36 return methods
037
=== added file 'account_advanced_reconcile/easy_reconcile_view.xml'
--- account_advanced_reconcile/easy_reconcile_view.xml 1970-01-01 00:00:00 +0000
+++ account_advanced_reconcile/easy_reconcile_view.xml 2013-06-10 06:07:03 +0000
@@ -0,0 +1,19 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data noupdate="0">
4 <record id="view_easy_reconcile_form" model="ir.ui.view">
5 <field name="name">account.easy.reconcile.form</field>
6 <field name="model">account.easy.reconcile</field>
7 <field name="inherit_id" ref="account_easy_reconcile.account_easy_reconcile_form"/>
8 <field name="arch" type="xml">
9 <page name="information" position="inside">
10 <group colspan="2" col="2">
11 <separator colspan="4" string="Advanced. Partner and Ref"/>
12 <label string="Match multiple debit vs multiple credit entries. Allow partial reconciliation.
13The lines should have the partner, the credit entry ref. is matched vs the debit entry ref. or name." colspan="4"/>
14 </group>
15 </page>
16 </field>
17 </record>
18 </data>
19</openerp>
020
=== added directory 'account_advanced_reconcile/i18n'
=== added file 'account_advanced_reconcile/i18n/fr.po'
--- account_advanced_reconcile/i18n/fr.po 1970-01-01 00:00:00 +0000
+++ account_advanced_reconcile/i18n/fr.po 2013-06-10 06:07:03 +0000
@@ -0,0 +1,91 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * account_advanced_reconcile
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 6.1\n"
8"Report-Msgid-Bugs-To: \n"
9"POT-Creation-Date: 2013-01-04 08:25+0000\n"
10"PO-Revision-Date: 2013-01-04 09:27+0100\n"
11"Last-Translator: Guewen Baconnier <guewen.baconnier@camptocamp.com>\n"
12"Language-Team: \n"
13"Language: \n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: \n"
18
19#. module: account_advanced_reconcile
20#: field:easy.reconcile.advanced,partner_ids:0
21#: field:easy.reconcile.advanced.ref,partner_ids:0
22msgid "Restrict on partners"
23msgstr "Restriction sur les partenaires"
24
25#. module: account_advanced_reconcile
26#: field:easy.reconcile.advanced,account_id:0
27#: field:easy.reconcile.advanced.ref,account_id:0
28msgid "Account"
29msgstr "Compte"
30
31#. module: account_advanced_reconcile
32#: model:ir.model,name:account_advanced_reconcile.model_account_easy_reconcile_method
33msgid "reconcile method for account_easy_reconcile"
34msgstr "Méthode de lettrage pour le module account_easy_reconcile"
35
36#. module: account_advanced_reconcile
37#: field:easy.reconcile.advanced,journal_id:0
38#: field:easy.reconcile.advanced.ref,journal_id:0
39msgid "Journal"
40msgstr "Journal"
41
42#. module: account_advanced_reconcile
43#: field:easy.reconcile.advanced,account_profit_id:0
44#: field:easy.reconcile.advanced.ref,account_profit_id:0
45msgid "Account Profit"
46msgstr "Compte de produit"
47
48#. module: account_advanced_reconcile
49#: view:account.easy.reconcile:0
50msgid "Match multiple debit vs multiple credit entries. Allow partial reconciliation. The lines should have the partner, the credit entry ref. is matched vs the debit entry ref. or name."
51msgstr "Le Lettrage peut s'effectuer sur plusieurs écritures de débit et crédit. Le Lettrage partiel est autorisé. Les écritures doivent avoir le même partenaire et la référence sur les écritures de crédit doit se retrouver dans la référence ou la description sur les écritures de débit."
52
53#. module: account_advanced_reconcile
54#: field:easy.reconcile.advanced,filter:0
55#: field:easy.reconcile.advanced.ref,filter:0
56msgid "Filter"
57msgstr "Filtre"
58
59#. module: account_advanced_reconcile
60#: view:account.easy.reconcile:0
61msgid "Advanced. Partner and Ref"
62msgstr "Avancé. Partenaire et Réf."
63
64#. module: account_advanced_reconcile
65#: field:easy.reconcile.advanced,date_base_on:0
66#: field:easy.reconcile.advanced.ref,date_base_on:0
67msgid "Date of reconciliation"
68msgstr "Date de lettrage"
69
70#. module: account_advanced_reconcile
71#: model:ir.model,name:account_advanced_reconcile.model_easy_reconcile_advanced
72msgid "easy.reconcile.advanced"
73msgstr "easy.reconcile.advanced"
74
75#. module: account_advanced_reconcile
76#: field:easy.reconcile.advanced,account_lost_id:0
77#: field:easy.reconcile.advanced.ref,account_lost_id:0
78msgid "Account Lost"
79msgstr "Compte de charge"
80
81#. module: account_advanced_reconcile
82#: model:ir.model,name:account_advanced_reconcile.model_easy_reconcile_advanced_ref
83msgid "easy.reconcile.advanced.ref"
84msgstr "easy.reconcile.advanced.ref"
85
86#. module: account_advanced_reconcile
87#: field:easy.reconcile.advanced,write_off:0
88#: field:easy.reconcile.advanced.ref,write_off:0
89msgid "Write off allowed"
90msgstr "Écart autorisé"
91
092
=== added directory 'account_easy_reconcile'
=== added file 'account_easy_reconcile/__init__.py'
--- account_easy_reconcile/__init__.py 1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/__init__.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,25 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright 2012 Camptocamp SA (Guewen Baconnier)
5# Copyright (C) 2010 Sébastien Beau
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22import easy_reconcile
23import base_reconciliation
24import simple_reconciliation
25import easy_reconcile_history
026
=== added file 'account_easy_reconcile/__openerp__.py'
--- account_easy_reconcile/__openerp__.py 1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/__openerp__.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,66 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright 2012 Camptocamp SA (Guewen Baconnier)
5# Copyright (C) 2010 Sébastien Beau
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22{
23 "name": "Easy Reconcile",
24 "version": "1.3.0",
25 "depends": ["account"],
26 "author": "Akretion,Camptocamp",
27 "description": """
28Easy Reconcile
29==============
30
31This is a shared work between Akretion and Camptocamp
32in order to provide:
33 - reconciliation facilities for big volume of transactions
34 - setup different profiles of reconciliation by account
35 - each profile can use many methods of reconciliation
36 - this module is also a base to create others
37 reconciliation methods which can plug in the profiles
38 - a profile a reconciliation can be run manually
39 or by a cron
40 - monitoring of reconciliation runs with an history
41 which keep track of the reconciled Journal items
42
432 simple reconciliation methods are integrated
44in this module, the simple reconciliations works
45on 2 lines (1 debit / 1 credit) and do not allow
46partial reconcilation, they also match on 1 key,
47partner or Journal item name.
48
49You may be interested to install also the
50``account_advanced_reconciliation`` module.
51This latter add more complex reconciliations,
52allows multiple lines and partial.
53
54""",
55 "website": "http://www.akretion.com/",
56 "category": "Finance",
57 "demo_xml": [],
58 "data": ["easy_reconcile.xml",
59 "easy_reconcile_history_view.xml",
60 "security/ir_rule.xml",
61 "security/ir.model.access.csv"],
62 'license': 'AGPL-3',
63 "auto_install": False,
64 "installable": True,
65
66}
067
=== added file 'account_easy_reconcile/base_reconciliation.py'
--- account_easy_reconcile/base_reconciliation.py 1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/base_reconciliation.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,208 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright 2012-2013 Camptocamp SA (Guewen Baconnier)
5# Copyright (C) 2010 Sébastien Beau
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from openerp.osv import fields, orm
23from operator import itemgetter, attrgetter
24
25
26class easy_reconcile_base(orm.AbstractModel):
27 """Abstract Model for reconciliation methods"""
28
29 _name = 'easy.reconcile.base'
30
31 _inherit = 'easy.reconcile.options'
32
33 _columns = {
34 'account_id': fields.many2one(
35 'account.account', 'Account', required=True),
36 'partner_ids': fields.many2many(
37 'res.partner', string="Restrict on partners"),
38 # other columns are inherited from easy.reconcile.options
39 }
40
41 def automatic_reconcile(self, cr, uid, ids, context=None):
42 """ Reconciliation method called from the view.
43
44 :return: list of reconciled ids, list of partially reconciled items
45 """
46 if isinstance(ids, (int, long)):
47 ids = [ids]
48 assert len(ids) == 1, "Has to be called on one id"
49 rec = self.browse(cr, uid, ids[0], context=context)
50 return self._action_rec(cr, uid, rec, context=context)
51
52 def _action_rec(self, cr, uid, rec, context=None):
53 """ Must be inherited to implement the reconciliation
54
55 :return: list of reconciled ids
56 """
57 raise NotImplementedError
58
59 def _base_columns(self, rec):
60 """ Mandatory columns for move lines queries
61 An extra column aliased as ``key`` should be defined
62 in each query."""
63 aml_cols = (
64 'id',
65 'debit',
66 'credit',
67 'date',
68 'period_id',
69 'ref',
70 'name',
71 'partner_id',
72 'account_id',
73 'move_id')
74 return ["account_move_line.%s" % col for col in aml_cols]
75
76 def _select(self, rec, *args, **kwargs):
77 return "SELECT %s" % ', '.join(self._base_columns(rec))
78
79 def _from(self, rec, *args, **kwargs):
80 return "FROM account_move_line"
81
82 def _where(self, rec, *args, **kwargs):
83 where = ("WHERE account_move_line.account_id = %s "
84 "AND account_move_line.reconcile_id IS NULL ")
85 # it would be great to use dict for params
86 # but as we use _where_calc in _get_filter
87 # which returns a list, we have to
88 # accomodate with that
89 params = [rec.account_id.id]
90
91 if rec.partner_ids:
92 where += " AND account_move_line.partner_id IN %s"
93 params.append(tuple([l.id for l in rec.partner_ids]))
94 return where, params
95
96 def _get_filter(self, cr, uid, rec, context):
97 ml_obj = self.pool.get('account.move.line')
98 where = ''
99 params = []
100 if rec.filter:
101 dummy, where, params = ml_obj._where_calc(
102 cr, uid, eval(rec.filter), context=context).get_sql()
103 if where:
104 where = " AND %s" % where
105 return where, params
106
107 def _below_writeoff_limit(self, cr, uid, rec, lines,
108 writeoff_limit, context=None):
109 precision = self.pool.get('decimal.precision').precision_get(
110 cr, uid, 'Account')
111 keys = ('debit', 'credit')
112 sums = reduce(
113 lambda line, memo:
114 dict((key, value + memo[key])
115 for key, value
116 in line.iteritems()
117 if key in keys), lines)
118
119 debit, credit = sums['debit'], sums['credit']
120 writeoff_amount = round(debit - credit, precision)
121 return bool(writeoff_limit >= abs(writeoff_amount)), debit, credit
122
123 def _get_rec_date(self, cr, uid, rec, lines,
124 based_on='end_period_last_credit', context=None):
125 period_obj = self.pool.get('account.period')
126
127 def last_period(mlines):
128 period_ids = [ml['period_id'] for ml in mlines]
129 periods = period_obj.browse(
130 cr, uid, period_ids, context=context)
131 return max(periods, key=attrgetter('date_stop'))
132
133 def last_date(mlines):
134 return max(mlines, key=itemgetter('date'))
135
136 def credit(mlines):
137 return [l for l in mlines if l['credit'] > 0]
138
139 def debit(mlines):
140 return [l for l in mlines if l['debit'] > 0]
141
142 if based_on == 'end_period_last_credit':
143 return last_period(credit(lines)).date_stop
144 if based_on == 'end_period':
145 return last_period(lines).date_stop
146 elif based_on == 'newest':
147 return last_date(lines)['date']
148 elif based_on == 'newest_credit':
149 return last_date(credit(lines))['date']
150 elif based_on == 'newest_debit':
151 return last_date(debit(lines))['date']
152 # reconcilation date will be today
153 # when date is None
154 return None
155
156 def _reconcile_lines(self, cr, uid, rec, lines, allow_partial=False, context=None):
157 """ Try to reconcile given lines
158
159 :param list lines: list of dict of move lines, they must at least
160 contain values for : id, debit, credit
161 :param boolean allow_partial: if True, partial reconciliation will be
162 created, otherwise only Full
163 reconciliation will be created
164 :return: tuple of boolean values, first item is wether the items
165 have been reconciled or not,
166 the second is wether the reconciliation is full (True)
167 or partial (False)
168 """
169 if context is None:
170 context = {}
171
172 ml_obj = self.pool.get('account.move.line')
173 writeoff = rec.write_off
174
175 line_ids = [l['id'] for l in lines]
176 below_writeoff, sum_debit, sum_credit = self._below_writeoff_limit(
177 cr, uid, rec, lines, writeoff, context=context)
178 date = self._get_rec_date(
179 cr, uid, rec, lines, rec.date_base_on, context=context)
180
181 rec_ctx = dict(context, date_p=date)
182 if below_writeoff:
183 if sum_credit < sum_debit:
184 writeoff_account_id = rec.account_profit_id.id
185 else:
186 writeoff_account_id = rec.account_lost_id.id
187
188 period_id = self.pool.get('account.period').find(
189 cr, uid, dt=date, context=context)[0]
190
191 ml_obj.reconcile(
192 cr, uid,
193 line_ids,
194 type='auto',
195 writeoff_acc_id=writeoff_account_id,
196 writeoff_period_id=period_id,
197 writeoff_journal_id=rec.journal_id.id,
198 context=rec_ctx)
199 return True, True
200 elif allow_partial:
201 ml_obj.reconcile_partial(
202 cr, uid,
203 line_ids,
204 type='manual',
205 context=rec_ctx)
206 return True, False
207
208 return False, False
0209
=== added file 'account_easy_reconcile/easy_reconcile.py'
--- account_easy_reconcile/easy_reconcile.py 1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/easy_reconcile.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,285 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright 2012-2013 Camptocamp SA (Guewen Baconnier)
5# Copyright (C) 2010 Sébastien Beau
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from openerp.osv import fields, osv, orm
23from openerp.tools.translate import _
24from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
25
26
27class easy_reconcile_options(orm.AbstractModel):
28 """Options of a reconciliation profile
29
30 Columns shared by the configuration of methods
31 and by the reconciliation wizards.
32 This allows decoupling of the methods and the
33 wizards and allows to launch the wizards alone
34 """
35
36 _name = 'easy.reconcile.options'
37
38 def _get_rec_base_date(self, cr, uid, context=None):
39 return [('end_period_last_credit', 'End of period of most recent credit'),
40 ('newest', 'Most recent move line'),
41 ('actual', 'Today'),
42 ('end_period', 'End of period of most recent move line'),
43 ('newest_credit', 'Date of most recent credit'),
44 ('newest_debit', 'Date of most recent debit')]
45
46 _columns = {
47 'write_off': fields.float('Write off allowed'),
48 'account_lost_id': fields.many2one(
49 'account.account', 'Account Lost'),
50 'account_profit_id': fields.many2one(
51 'account.account', 'Account Profit'),
52 'journal_id': fields.many2one(
53 'account.journal', 'Journal'),
54 'date_base_on': fields.selection(
55 _get_rec_base_date,
56 required=True,
57 string='Date of reconciliation'),
58 'filter': fields.char('Filter', size=128),
59 }
60
61 _defaults = {
62 'write_off': 0.,
63 'date_base_on': 'end_period_last_credit',
64 }
65
66
67class account_easy_reconcile_method(orm.Model):
68
69 _name = 'account.easy.reconcile.method'
70 _description = 'reconcile method for account_easy_reconcile'
71
72 _inherit = 'easy.reconcile.options'
73
74 _order = 'sequence'
75
76 def _get_all_rec_method(self, cr, uid, context=None):
77 return [
78 ('easy.reconcile.simple.name', 'Simple. Amount and Name'),
79 ('easy.reconcile.simple.partner', 'Simple. Amount and Partner'),
80 ('easy.reconcile.simple.reference', 'Simple. Amount and Reference'),
81 ]
82
83 def _get_rec_method(self, cr, uid, context=None):
84 return self._get_all_rec_method(cr, uid, context=None)
85
86 _columns = {
87 'name': fields.selection(
88 _get_rec_method, 'Type', required=True),
89 'sequence': fields.integer(
90 'Sequence',
91 required=True,
92 help="The sequence field is used to order "
93 "the reconcile method"),
94 'task_id': fields.many2one(
95 'account.easy.reconcile',
96 string='Task',
97 required=True,
98 ondelete='cascade'),
99 'company_id': fields.related('task_id','company_id',
100 relation='res.company',
101 type='many2one',
102 string='Company',
103 store=True,
104 readonly=True),
105 }
106
107 _defaults = {
108 'sequence': 1,
109 }
110
111 def init(self, cr):
112 """ Migration stuff
113
114 Name is not anymore methods names but the name
115 of the model which does the reconciliation
116 """
117 cr.execute("""
118 UPDATE account_easy_reconcile_method
119 SET name = 'easy.reconcile.simple.partner'
120 WHERE name = 'action_rec_auto_partner'
121 """)
122 cr.execute("""
123 UPDATE account_easy_reconcile_method
124 SET name = 'easy.reconcile.simple.name'
125 WHERE name = 'action_rec_auto_name'
126 """)
127
128
129class account_easy_reconcile(orm.Model):
130
131 _name = 'account.easy.reconcile'
132 _description = 'account easy reconcile'
133
134 def _get_total_unrec(self, cr, uid, ids, name, arg, context=None):
135 obj_move_line = self.pool.get('account.move.line')
136 res = {}
137 for task in self.browse(cr, uid, ids, context=context):
138 res[task.id] = len(obj_move_line.search(
139 cr, uid,
140 [('account_id', '=', task.account.id),
141 ('reconcile_id', '=', False),
142 ('reconcile_partial_id', '=', False)],
143 context=context))
144 return res
145
146 def _get_partial_rec(self, cr, uid, ids, name, arg, context=None):
147 obj_move_line = self.pool.get('account.move.line')
148 res = {}
149 for task in self.browse(cr, uid, ids, context=context):
150 res[task.id] = len(obj_move_line.search(
151 cr, uid,
152 [('account_id', '=', task.account.id),
153 ('reconcile_id', '=', False),
154 ('reconcile_partial_id', '!=', False)],
155 context=context))
156 return res
157
158 def _last_history(self, cr, uid, ids, name, args, context=None):
159 result = {}
160 for history in self.browse(cr, uid, ids, context=context):
161 result[history.id] = False
162 if history.history_ids:
163 # history is sorted by date desc
164 result[history.id] = history.history_ids[0].id
165 return result
166
167 _columns = {
168 'name': fields.char('Name', required=True),
169 'account': fields.many2one(
170 'account.account', 'Account', required=True),
171 'reconcile_method': fields.one2many(
172 'account.easy.reconcile.method', 'task_id', 'Method'),
173 'unreconciled_count': fields.function(
174 _get_total_unrec, type='integer', string='Unreconciled Items'),
175 'reconciled_partial_count': fields.function(
176 _get_partial_rec,
177 type='integer',
178 string='Partially Reconciled Items'),
179 'history_ids': fields.one2many(
180 'easy.reconcile.history',
181 'easy_reconcile_id',
182 string='History',
183 readonly=True),
184 'last_history':
185 fields.function(
186 _last_history,
187 string='Last History',
188 type='many2one',
189 relation='easy.reconcile.history',
190 readonly=True),
191 'company_id': fields.many2one('res.company', 'Company'),
192 }
193
194 def _prepare_run_transient(self, cr, uid, rec_method, context=None):
195 return {'account_id': rec_method.task_id.account.id,
196 'write_off': rec_method.write_off,
197 'account_lost_id': (rec_method.account_lost_id and
198 rec_method.account_lost_id.id),
199 'account_profit_id': (rec_method.account_profit_id and
200 rec_method.account_profit_id.id),
201 'journal_id': (rec_method.journal_id and
202 rec_method.journal_id.id),
203 'date_base_on': rec_method.date_base_on,
204 'filter': rec_method.filter}
205
206 def run_reconcile(self, cr, uid, ids, context=None):
207 def find_reconcile_ids(fieldname, move_line_ids):
208 if not move_line_ids:
209 return []
210 sql = ("SELECT DISTINCT " + fieldname +
211 " FROM account_move_line "
212 " WHERE id in %s "
213 " AND " + fieldname + " IS NOT NULL")
214 cr.execute(sql, (tuple(move_line_ids),))
215 res = cr.fetchall()
216 return [row[0] for row in res]
217
218 for rec in self.browse(cr, uid, ids, context=context):
219 all_ml_rec_ids = []
220 all_ml_partial_ids = []
221
222 for method in rec.reconcile_method:
223 rec_model = self.pool.get(method.name)
224 auto_rec_id = rec_model.create(
225 cr, uid,
226 self._prepare_run_transient(
227 cr, uid, method, context=context),
228 context=context)
229
230 ml_rec_ids, ml_partial_ids = rec_model.automatic_reconcile(
231 cr, uid, auto_rec_id, context=context)
232
233 all_ml_rec_ids += ml_rec_ids
234 all_ml_partial_ids += ml_partial_ids
235
236 reconcile_ids = find_reconcile_ids(
237 'reconcile_id', all_ml_rec_ids)
238 partial_ids = find_reconcile_ids(
239 'reconcile_partial_id', all_ml_partial_ids)
240
241 self.pool.get('easy.reconcile.history').create(
242 cr,
243 uid,
244 {'easy_reconcile_id': rec.id,
245 'date': fields.datetime.now(),
246 'reconcile_ids': [(4, rid) for rid in reconcile_ids],
247 'reconcile_partial_ids': [(4, rid) for rid in partial_ids]},
248 context=context)
249 return True
250
251 def _no_history(self, cr, uid, rec, context=None):
252 """ Raise an `osv.except_osv` error, supposed to
253 be called when there is no history on the reconciliation
254 task.
255 """
256 raise osv.except_osv(
257 _('Error'),
258 _('There is no history of reconciled '
259 'items on the task: %s.') % rec.name)
260
261 def last_history_reconcile(self, cr, uid, rec_id, context=None):
262 """ Get the last history record for this reconciliation profile
263 and return the action which opens move lines reconciled
264 """
265 if isinstance(rec_id, (tuple, list)):
266 assert len(rec_id) == 1, \
267 "Only 1 id expected"
268 rec_id = rec_id[0]
269 rec = self.browse(cr, uid, rec_id, context=context)
270 if not rec.last_history:
271 self._no_history(cr, uid, rec, context=context)
272 return rec.last_history.open_reconcile()
273
274 def last_history_partial(self, cr, uid, rec_id, context=None):
275 """ Get the last history record for this reconciliation profile
276 and return the action which opens move lines reconciled
277 """
278 if isinstance(rec_id, (tuple, list)):
279 assert len(rec_id) == 1, \
280 "Only 1 id expected"
281 rec_id = rec_id[0]
282 rec = self.browse(cr, uid, rec_id, context=context)
283 if not rec.last_history:
284 self._no_history(cr, uid, rec, context=context)
285 return rec.last_history.open_partial()
0286
=== added file 'account_easy_reconcile/easy_reconcile.xml'
--- account_easy_reconcile/easy_reconcile.xml 1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/easy_reconcile.xml 2013-06-10 06:07:03 +0000
@@ -0,0 +1,156 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<openerp>
3<data>
4
5 <!-- account.easy.reconcile view -->
6 <record id="account_easy_reconcile_form" model="ir.ui.view">
7 <field name="name">account.easy.reconcile.form</field>
8 <field name="priority">20</field>
9 <field name="model">account.easy.reconcile</field>
10 <field name="arch" type="xml">
11 <form string="Automatic Easy Reconcile" version="7.0">
12 <header>
13 <button name="run_reconcile" class="oe_highlight"
14 string="Start Auto Reconciliation" type="object"/>
15 <button icon="STOCK_JUMP_TO" name="last_history_reconcile"
16 class="oe_highlight"
17 string="Display items reconciled on the last run"
18 type="object"/>
19 <button icon="STOCK_JUMP_TO" name="last_history_partial"
20 class="oe_highlight"
21 string="Display items partially reconciled on the last run"
22 type="object"/>
23 </header>
24 <sheet>
25 <separator colspan="4" string="Profile Information" />
26 <group>
27 <group>
28 <field name="name" select="1"/>
29 <field name="account"/>
30 <field name="company_id" groups="base.group_multi_company"/>
31 </group>
32 <group>
33 <field name="unreconciled_count"/>
34 <field name="reconciled_partial_count"/>
35 </group>
36 </group>
37 <notebook colspan="4">
38 <page name="methods" string="Configuration">
39 <field name="reconcile_method" colspan = "4" nolabel="1"/>
40 </page>
41 <page name="history" string="History">
42 <field name="history_ids" nolabel="1">
43 <tree string="Automatic Easy Reconcile History">
44 <field name="date"/>
45 <button icon="STOCK_JUMP_TO" name="open_reconcile"
46 string="Go to reconciled items" type="object"/>
47 <button icon="STOCK_JUMP_TO" name="open_partial"
48 string="Go to partially reconciled items" type="object"/>
49 </tree>
50 </field>
51 </page>
52 <page name="information" string="Information">
53 <separator colspan="4" string="Simple. Amount and Name"/>
54 <label string="Match one debit line vs one credit line. Do not allow partial reconciliation.
55The lines should have the same amount (with the write-off) and the same name to be reconciled." colspan="4"/>
56
57 <separator colspan="4" string="Simple. Amount and Partner"/>
58 <label string="Match one debit line vs one credit line. Do not allow partial reconciliation.
59The lines should have the same amount (with the write-off) and the same partner to be reconciled." colspan="4"/>
60
61 <separator colspan="4" string="Simple. Amount and Reference"/>
62 <label string="Match one debit line vs one credit line. Do not allow partial reconciliation.
63The lines should have the same amount (with the write-off) and the same reference to be reconciled." colspan="4"/>
64
65 </page>
66 </notebook>
67 </sheet>
68 </form>
69 </field>
70 </record>
71
72 <record id="account_easy_reconcile_tree" model="ir.ui.view">
73 <field name="name">account.easy.reconcile.tree</field>
74 <field name="priority">20</field>
75 <field name="model">account.easy.reconcile</field>
76 <field name="arch" type="xml">
77 <tree string="Automatic Easy Reconcile">
78 <field name="name"/>
79 <field name="account"/>
80 <field name="company_id" groups="base.group_multi_company"/>
81 <field name="unreconciled_count"/>
82 <field name="reconciled_partial_count"/>
83 <button icon="gtk-ok" name="run_reconcile" colspan="4"
84 string="Start Auto Reconcilation" type="object"/>
85 <button icon="STOCK_JUMP_TO" name="last_history_reconcile" colspan="2"
86 string="Display items reconciled on the last run" type="object"/>
87 <button icon="STOCK_JUMP_TO" name="last_history_partial" colspan="2"
88 string="Display items partially reconciled on the last run"
89 type="object"/>
90 </tree>
91 </field>
92 </record>
93
94 <record id="action_account_easy_reconcile" model="ir.actions.act_window">
95 <field name="name">Easy Automatic Reconcile</field>
96 <field name="type">ir.actions.act_window</field>
97 <field name="res_model">account.easy.reconcile</field>
98 <field name="view_type">form</field>
99 <field name="view_mode">tree,form</field>
100 <field name="help" type="html">
101 <p class="oe_view_nocontent_create">
102 Click to add a reconciliation profile.
103 </p><p>
104 A reconciliation profile specifies, for one account, how
105 the entries should be reconciled.
106 You can select one or many reconciliation methods which will
107 be run sequentially to match the entries between them.
108 </p>
109 </field>
110 </record>
111
112
113 <!-- account.easy.reconcile.method view -->
114
115 <record id="account_easy_reconcile_method_form" model="ir.ui.view">
116 <field name="name">account.easy.reconcile.method.form</field>
117 <field name="priority">20</field>
118 <field name="model">account.easy.reconcile.method</field>
119 <field name="arch" type="xml">
120 <form string="Automatic Easy Reconcile Method">
121 <field name="sequence"/>
122 <field name="name"/>
123 <field name="write_off"/>
124 <field name="account_lost_id" attrs="{'required':[('write_off','>',0)]}"/>
125 <field name="account_profit_id" attrs="{'required':[('write_off','>',0)]}"/>
126 <field name="journal_id" attrs="{'required':[('write_off','>',0)]}"/>
127 <field name="date_base_on"/>
128 </form>
129 </field>
130 </record>
131
132 <record id="account_easy_reconcile_method_tree" model="ir.ui.view">
133 <field name="name">account.easy.reconcile.method.tree</field>
134 <field name="priority">20</field>
135 <field name="model">account.easy.reconcile.method</field>
136 <field name="arch" type="xml">
137 <tree editable="top" string="Automatic Easy Reconcile Method">
138 <field name="sequence"/>
139 <field name="name"/>
140 <field name="write_off"/>
141 <field name="account_lost_id" attrs="{'required':[('write_off','>',0)]}"/>
142 <field name="account_profit_id" attrs="{'required':[('write_off','>',0)]}"/>
143 <field name="journal_id" attrs="{'required':[('write_off','>',0)]}"/>
144 <field name="date_base_on"/>
145 </tree>
146 </field>
147 </record>
148
149 <!-- menu item -->
150
151 <menuitem action="action_account_easy_reconcile"
152 id="menu_easy_reconcile"
153 parent="account.periodical_processing_reconciliation"/>
154
155</data>
156</openerp>
0157
=== added file 'account_easy_reconcile/easy_reconcile_history.py'
--- account_easy_reconcile/easy_reconcile_history.py 1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/easy_reconcile_history.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,153 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Guewen Baconnier
5# Copyright 2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from openerp.osv import orm, fields
23from openerp.tools.translate import _
24
25
26class easy_reconcile_history(orm.Model):
27 """ Store an history of the runs per profile
28 Each history stores the list of reconciliations done"""
29
30 _name = 'easy.reconcile.history'
31 _rec_name = 'easy_reconcile_id'
32 _order = 'date DESC'
33
34 def _reconcile_line_ids(self, cr, uid, ids, name, args, context=None):
35 result = {}
36
37 for history in self.browse(cr, uid, ids, context=context):
38 result[history.id] = {}
39
40 move_line_ids = []
41 for reconcile in history.reconcile_ids:
42 move_line_ids += [line.id
43 for line
44 in reconcile.line_id]
45 result[history.id]['reconcile_line_ids'] = move_line_ids
46
47 move_line_ids = []
48 for reconcile in history.reconcile_partial_ids:
49 move_line_ids += [line.id
50 for line
51 in reconcile.line_partial_ids]
52 result[history.id]['partial_line_ids'] = move_line_ids
53
54 return result
55
56 _columns = {
57 'easy_reconcile_id': fields.many2one(
58 'account.easy.reconcile', 'Reconcile Profile', readonly=True),
59 'date': fields.datetime('Run date', readonly=True),
60 'reconcile_ids': fields.many2many(
61 'account.move.reconcile',
62 'account_move_reconcile_history_rel',
63 string='Reconciliations', readonly=True),
64 'reconcile_partial_ids': fields.many2many(
65 'account.move.reconcile',
66 'account_move_reconcile_history_partial_rel',
67 string='Partial Reconciliations', readonly=True),
68 'reconcile_line_ids':
69 fields.function(
70 _reconcile_line_ids,
71 string='Reconciled Items',
72 type='many2many',
73 relation='account.move.line',
74 readonly=True,
75 multi='lines'),
76 'partial_line_ids':
77 fields.function(
78 _reconcile_line_ids,
79 string='Partially Reconciled Items',
80 type='many2many',
81 relation='account.move.line',
82 readonly=True,
83 multi='lines'),
84 'company_id': fields.related('easy_reconcile_id','company_id',
85 relation='res.company',
86 type='many2one',
87 string='Company',
88 store=True,
89 readonly=True),
90
91 }
92
93 def _open_move_lines(self, cr, uid, history_id, rec_type='full', context=None):
94 """ For an history record, open the view of move line with
95 the reconciled or partially reconciled move lines
96
97 :param history_id: id of the history
98 :param rec_type: 'full' or 'partial'
99 :return: action to open the move lines
100 """
101 assert rec_type in ('full', 'partial'), \
102 "rec_type must be 'full' or 'partial'"
103
104 history = self.browse(cr, uid, history_id, context=context)
105
106 if rec_type == 'full':
107 field = 'reconcile_line_ids'
108 name = _('Reconciliations')
109 else:
110 field = 'partial_line_ids'
111 name = _('Partial Reconciliations')
112
113 move_line_ids = [line.id for line in getattr(history, field)]
114
115 return {
116 'name': name,
117 'view_mode': 'tree,form',
118 'view_id': False,
119 'view_type': 'form',
120 'res_model': 'account.move.line',
121 'type': 'ir.actions.act_window',
122 'nodestroy': True,
123 'target': 'current',
124 'domain': unicode([('id', 'in', move_line_ids)]),
125 }
126
127 def open_reconcile(self, cr, uid, history_ids, context=None):
128 """ For an history record, open the view of move line
129 with the reconciled move lines
130
131 :param history_ids: id of the record as int or long
132 Accept a list with 1 id too to be
133 used from the client.
134 """
135 if isinstance(history_ids, (tuple, list)):
136 assert len(history_ids) == 1, "only 1 ID is accepted"
137 history_ids = history_ids[0]
138 return self._open_move_lines(
139 cr, uid, history_ids, rec_type='full', context=None)
140
141 def open_partial(self, cr, uid, history_ids, context=None):
142 """ For an history record, open the view of move line
143 with the partially reconciled move lines
144
145 :param history_ids: id of the record as int or long
146 Accept a list with 1 id too to be
147 used from the client.
148 """
149 if isinstance(history_ids, (tuple, list)):
150 assert len(history_ids) == 1, "only 1 ID is accepted"
151 history_ids = history_ids[0]
152 return self._open_move_lines(
153 cr, uid, history_ids, rec_type='partial', context=None)
0154
=== added file 'account_easy_reconcile/easy_reconcile_history_view.xml'
--- account_easy_reconcile/easy_reconcile_history_view.xml 1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/easy_reconcile_history_view.xml 2013-06-10 06:07:03 +0000
@@ -0,0 +1,98 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data noupdate="0">
4
5 <record id="view_easy_reconcile_history_search" model="ir.ui.view">
6 <field name="name">easy.reconcile.history.search</field>
7 <field name="model">easy.reconcile.history</field>
8 <field name="arch" type="xml">
9 <search string="Automatic Easy Reconcile History">
10 <filter icon="terp-go-today" string="Today"
11 domain="[('date','&lt;', time.strftime('%%Y-%%m-%%d 23:59:59')), ('date','&gt;=', time.strftime('%%Y-%%m-%%d 00:00:00'))]"
12 help="Todays' Reconcilations" />
13 <filter icon="terp-go-week" string="7 Days"
14 help="Reconciliations of last 7 days"
15 domain="[('date','&lt;', time.strftime('%%Y-%%m-%%d 23:59:59')),('date','&gt;=',(datetime.date.today()-datetime.timedelta(days=7)).strftime('%%Y-%%m-%%d 00:00:00'))]"
16 />
17
18 <separator orientation="vertical"/>
19 <field name="easy_reconcile_id"/>
20 <field name="date"/>
21
22 <newline/>
23 <group expand="0" string="Group By...">
24 <filter string="Reconciliation Profile"
25 icon="terp-stock_effects-object-colorize"
26 domain="[]" context="{'group_by': 'easy_reconcile_id'}"/>
27 <filter string="Date" icon="terp-go-month" domain="[]"
28 context="{'group_by': 'date'}"/>
29 </group>
30 </search>
31 </field>
32 </record>
33
34 <record id="easy_reconcile_history_form" model="ir.ui.view">
35 <field name="name">easy.reconcile.history.form</field>
36 <field name="model">easy.reconcile.history</field>
37 <field name="arch" type="xml">
38 <form string="Automatic Easy Reconcile History" version="7.0">
39 <header>
40 <button name="open_reconcile"
41 string="Go to reconciled items"
42 icon="STOCK_JUMP_TO" type="object"/>
43 <button name="open_partial"
44 string="Go to partially reconciled items"
45 icon="STOCK_JUMP_TO" type="object"/>
46 </header>
47 <sheet>
48 <group>
49 <field name="easy_reconcile_id"/>
50 <field name="date"/>
51 <field name="company_id" groups="base.group_multi_company"/>
52 </group>
53 <group col="2">
54 <separator colspan="2" string="Reconciliations"/>
55 <field name="reconcile_ids" nolabel="1"/>
56 </group>
57 <group col="2">
58 <separator colspan="2" string="Partial Reconciliations"/>
59 <field name="reconcile_partial_ids" nolabel="1"/>
60 </group>
61 </sheet>
62 </form>
63 </field>
64 </record>
65
66 <record id="easy_reconcile_history_tree" model="ir.ui.view">
67 <field name="name">easy.reconcile.history.tree</field>
68 <field name="model">easy.reconcile.history</field>
69 <field name="arch" type="xml">
70 <tree string="Automatic Easy Reconcile History">
71 <field name="easy_reconcile_id"/>
72 <field name="date"/>
73 <button icon="STOCK_JUMP_TO" name="open_reconcile"
74 string="Go to reconciled items" type="object"/>
75 <button icon="STOCK_JUMP_TO" name="open_partial"
76 string="Go to partially reconciled items" type="object"/>
77 </tree>
78 </field>
79 </record>
80
81 <record id="action_easy_reconcile_history" model="ir.actions.act_window">
82 <field name="name">Easy Automatic Reconcile History</field>
83 <field name="type">ir.actions.act_window</field>
84 <field name="res_model">easy.reconcile.history</field>
85 <field name="view_type">form</field>
86 <field name="view_mode">tree,form</field>
87 </record>
88
89 <act_window
90 context="{'search_default_easy_reconcile_id': [active_id], 'default_easy_reconcile_id': active_id}"
91 id="act_easy_reconcile_to_history"
92 name="History Details"
93 groups=""
94 res_model="easy.reconcile.history"
95 src_model="account.easy.reconcile"/>
96
97 </data>
98</openerp>
099
=== added directory 'account_easy_reconcile/i18n'
=== added file 'account_easy_reconcile/i18n/fr.po'
--- account_easy_reconcile/i18n/fr.po 1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/i18n/fr.po 2013-06-10 06:07:03 +0000
@@ -0,0 +1,425 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * account_easy_reconcile
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 6.1\n"
8"Report-Msgid-Bugs-To: \n"
9"POT-Creation-Date: 2013-01-04 08:39+0000\n"
10"PO-Revision-Date: 2013-01-04 09:55+0100\n"
11"Last-Translator: Guewen Baconnier <guewen.baconnier@camptocamp.com>\n"
12"Language-Team: \n"
13"Language: \n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: \n"
18
19#. module: account_easy_reconcile
20#: code:addons/account_easy_reconcile/easy_reconcile_history.py:101
21#: view:easy.reconcile.history:0
22#: field:easy.reconcile.history,reconcile_ids:0
23#, python-format
24msgid "Reconciliations"
25msgstr "Lettrages"
26
27#. module: account_easy_reconcile
28#: view:account.easy.reconcile:0
29#: view:easy.reconcile.history:0
30msgid "Automatic Easy Reconcile History"
31msgstr "Historique des lettrages automatisés"
32
33#. module: account_easy_reconcile
34#: view:account.easy.reconcile:0
35msgid "Information"
36msgstr "Information"
37
38#. module: account_easy_reconcile
39#: view:account.easy.reconcile:0
40#: view:easy.reconcile.history:0
41msgid "Go to partially reconciled items"
42msgstr "Voir les entrées partiellement lettrées"
43
44#. module: account_easy_reconcile
45#: help:account.easy.reconcile.method,sequence:0
46msgid "The sequence field is used to order the reconcile method"
47msgstr "La séquence détermine l'ordre des méthodes de lettrage"
48
49#. module: account_easy_reconcile
50#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_history
51msgid "easy.reconcile.history"
52msgstr "easy.reconcile.history"
53
54#. module: account_easy_reconcile
55#: model:ir.actions.act_window,help:account_easy_reconcile.action_account_easy_reconcile
56msgid ""
57"<p class=\"oe_view_nocontent_create\">\n"
58" Click to add a reconciliation profile.\n"
59" </p><p>\n"
60" A reconciliation profile specifies, for one account, how\n"
61" the entries should be reconciled.\n"
62" You can select one or many reconciliation methods which will\n"
63" be run sequentially to match the entries between them.\n"
64" </p>\n"
65" "
66msgstr ""
67"<p class=\"oe_view_nocontent_create\">\n"
68" Cliquez pour ajouter un profil de lettrage.\n"
69" </p><p>\n"
70" Un profil de lettrage spécifie, pour un compte, comment\n"
71" les écritures doivent être lettrées.\n"
72" Vous pouvez sélectionner une ou plusieurs méthodes de lettrage\n"
73" qui seront lancées successivement pour identifier les écritures\n"
74" devant être lettrées. </p>\n"
75" "
76
77#. module: account_easy_reconcile
78#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_options
79msgid "easy.reconcile.options"
80msgstr "easy.reconcile.options"
81
82#. module: account_easy_reconcile
83#: view:easy.reconcile.history:0
84msgid "Group By..."
85msgstr "Grouper par..."
86
87#. module: account_easy_reconcile
88#: field:account.easy.reconcile,unreconciled_count:0
89msgid "Unreconciled Items"
90msgstr "Écritures non lettrées"
91
92#. module: account_easy_reconcile
93#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_base
94msgid "easy.reconcile.base"
95msgstr "easy.reconcile.base"
96
97#. module: account_easy_reconcile
98#: field:easy.reconcile.history,reconcile_line_ids:0
99msgid "Reconciled Items"
100msgstr "Écritures lettrées"
101
102#. module: account_easy_reconcile
103#: field:account.easy.reconcile,reconcile_method:0
104msgid "Method"
105msgstr "Méthode"
106
107#. module: account_easy_reconcile
108#: view:easy.reconcile.history:0
109msgid "7 Days"
110msgstr "7 jours"
111
112#. module: account_easy_reconcile
113#: model:ir.actions.act_window,name:account_easy_reconcile.action_easy_reconcile_history
114msgid "Easy Automatic Reconcile History"
115msgstr "Lettrage automatisé"
116
117#. module: account_easy_reconcile
118#: field:easy.reconcile.history,date:0
119msgid "Run date"
120msgstr "Date de lancement"
121
122#. module: account_easy_reconcile
123#: view:account.easy.reconcile:0
124msgid "Match one debit line vs one credit line. Do not allow partial reconciliation. The lines should have the same amount (with the write-off) and the same reference to be reconciled."
125msgstr "Lettre un débit avec un crédit ayant le même montant et la même référence. Le lettrage ne peut être partiel (écriture d'ajustement en cas d'écart)."
126
127#. module: account_easy_reconcile
128#: model:ir.actions.act_window,name:account_easy_reconcile.act_easy_reconcile_to_history
129msgid "History Details"
130msgstr "Détails de l'historique"
131
132#. module: account_easy_reconcile
133#: view:account.easy.reconcile:0
134msgid "Display items reconciled on the last run"
135msgstr "Voir les entrées lettrées au dernier lettrage"
136
137#. module: account_easy_reconcile
138#: field:account.easy.reconcile.method,name:0
139msgid "Type"
140msgstr "Type"
141
142#. module: account_easy_reconcile
143#: field:account.easy.reconcile.method,journal_id:0
144#: field:easy.reconcile.base,journal_id:0
145#: field:easy.reconcile.options,journal_id:0
146#: field:easy.reconcile.simple,journal_id:0
147#: field:easy.reconcile.simple.name,journal_id:0
148#: field:easy.reconcile.simple.partner,journal_id:0
149#: field:easy.reconcile.simple.reference,journal_id:0
150msgid "Journal"
151msgstr "Journal"
152
153#. module: account_easy_reconcile
154#: field:account.easy.reconcile.method,account_profit_id:0
155#: field:easy.reconcile.base,account_profit_id:0
156#: field:easy.reconcile.options,account_profit_id:0
157#: field:easy.reconcile.simple,account_profit_id:0
158#: field:easy.reconcile.simple.name,account_profit_id:0
159#: field:easy.reconcile.simple.partner,account_profit_id:0
160#: field:easy.reconcile.simple.reference,account_profit_id:0
161msgid "Account Profit"
162msgstr "Compte de profits"
163
164#. module: account_easy_reconcile
165#: view:easy.reconcile.history:0
166msgid "Todays' Reconcilations"
167msgstr "Lettrages du jour"
168
169#. module: account_easy_reconcile
170#: view:account.easy.reconcile:0
171msgid "Simple. Amount and Name"
172msgstr "Simple. Montant et description"
173
174#. module: account_easy_reconcile
175#: field:easy.reconcile.base,partner_ids:0
176#: field:easy.reconcile.simple,partner_ids:0
177#: field:easy.reconcile.simple.name,partner_ids:0
178#: field:easy.reconcile.simple.partner,partner_ids:0
179#: field:easy.reconcile.simple.reference,partner_ids:0
180msgid "Restrict on partners"
181msgstr "Filtrer sur des partenaires"
182
183#. module: account_easy_reconcile
184#: model:ir.actions.act_window,name:account_easy_reconcile.action_account_easy_reconcile
185#: model:ir.ui.menu,name:account_easy_reconcile.menu_easy_reconcile
186msgid "Easy Automatic Reconcile"
187msgstr "Lettrage automatisé"
188
189#. module: account_easy_reconcile
190#: view:easy.reconcile.history:0
191msgid "Today"
192msgstr "Aujourd'hui"
193
194#. module: account_easy_reconcile
195#: view:easy.reconcile.history:0
196msgid "Date"
197msgstr "Date"
198
199#. module: account_easy_reconcile
200#: field:account.easy.reconcile,last_history:0
201msgid "Last History"
202msgstr "Dernier historique"
203
204#. module: account_easy_reconcile
205#: view:account.easy.reconcile:0
206msgid "Configuration"
207msgstr "Configuration"
208
209#. module: account_easy_reconcile
210#: field:account.easy.reconcile,reconciled_partial_count:0
211#: field:easy.reconcile.history,partial_line_ids:0
212msgid "Partially Reconciled Items"
213msgstr "Écritures partiellement lettrées"
214
215#. module: account_easy_reconcile
216#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple_partner
217msgid "easy.reconcile.simple.partner"
218msgstr "easy.reconcile.simple.partner"
219
220#. module: account_easy_reconcile
221#: field:account.easy.reconcile.method,write_off:0
222#: field:easy.reconcile.base,write_off:0
223#: field:easy.reconcile.options,write_off:0
224#: field:easy.reconcile.simple,write_off:0
225#: field:easy.reconcile.simple.name,write_off:0
226#: field:easy.reconcile.simple.partner,write_off:0
227#: field:easy.reconcile.simple.reference,write_off:0
228msgid "Write off allowed"
229msgstr "Écart autorisé"
230
231#. module: account_easy_reconcile
232#: view:account.easy.reconcile:0
233msgid "Automatic Easy Reconcile"
234msgstr "Lettrage automatisé"
235
236#. module: account_easy_reconcile
237#: field:account.easy.reconcile,account:0
238#: field:easy.reconcile.base,account_id:0
239#: field:easy.reconcile.simple,account_id:0
240#: field:easy.reconcile.simple.name,account_id:0
241#: field:easy.reconcile.simple.partner,account_id:0
242#: field:easy.reconcile.simple.reference,account_id:0
243msgid "Account"
244msgstr "Compte"
245
246#. module: account_easy_reconcile
247#: field:account.easy.reconcile.method,task_id:0
248msgid "Task"
249msgstr "Tâche"
250
251#. module: account_easy_reconcile
252#: field:account.easy.reconcile,name:0
253msgid "Name"
254msgstr "Nom"
255
256#. module: account_easy_reconcile
257#: view:account.easy.reconcile:0
258msgid "Simple. Amount and Partner"
259msgstr "Simple. Montant et partenaire"
260
261#. module: account_easy_reconcile
262#: view:account.easy.reconcile:0
263msgid "Start Auto Reconcilation"
264msgstr "Lancer le lettrage automatisé"
265
266#. module: account_easy_reconcile
267#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple_name
268msgid "easy.reconcile.simple.name"
269msgstr "easy.reconcile.simple.name"
270
271#. module: account_easy_reconcile
272#: field:account.easy.reconcile.method,filter:0
273#: field:easy.reconcile.base,filter:0
274#: field:easy.reconcile.options,filter:0
275#: field:easy.reconcile.simple,filter:0
276#: field:easy.reconcile.simple.name,filter:0
277#: field:easy.reconcile.simple.partner,filter:0
278#: field:easy.reconcile.simple.reference,filter:0
279msgid "Filter"
280msgstr "Filtre"
281
282#. module: account_easy_reconcile
283#: view:account.easy.reconcile:0
284msgid "Match one debit line vs one credit line. Do not allow partial reconciliation. The lines should have the same amount (with the write-off) and the same partner to be reconciled."
285msgstr "Lettre un débit avec un crédit ayant le même montant et le même partenaire. Le lettrage ne peut être partiel (écriture d'ajustement en cas d'écart)."
286
287#. module: account_easy_reconcile
288#: field:easy.reconcile.history,easy_reconcile_id:0
289msgid "Reconcile Profile"
290msgstr "Profil de réconciliation"
291
292#. module: account_easy_reconcile
293#: view:account.easy.reconcile:0
294msgid "Start Auto Reconciliation"
295msgstr "Lancer le lettrage automatisé"
296
297#. module: account_easy_reconcile
298#: code:addons/account_easy_reconcile/easy_reconcile.py:250
299#, python-format
300msgid "Error"
301msgstr "Erreur"
302
303#. module: account_easy_reconcile
304#: code:addons/account_easy_reconcile/easy_reconcile.py:251
305#, python-format
306msgid "There is no history of reconciled items on the task: %s."
307msgstr "Il n'y a pas d'historique d'écritures lettrées sur la tâche: %s."
308
309#. module: account_easy_reconcile
310#: view:account.easy.reconcile:0
311msgid "Match one debit line vs one credit line. Do not allow partial reconciliation. The lines should have the same amount (with the write-off) and the same name to be reconciled."
312msgstr "Lettre un débit avec un crédit ayant le même montant et la même description. Le lettrage ne peut être partiel (écriture d'ajustement en cas d'écart)."
313
314#. module: account_easy_reconcile
315#: field:account.easy.reconcile.method,account_lost_id:0
316#: field:easy.reconcile.base,account_lost_id:0
317#: field:easy.reconcile.options,account_lost_id:0
318#: field:easy.reconcile.simple,account_lost_id:0
319#: field:easy.reconcile.simple.name,account_lost_id:0
320#: field:easy.reconcile.simple.partner,account_lost_id:0
321#: field:easy.reconcile.simple.reference,account_lost_id:0
322msgid "Account Lost"
323msgstr "Compte de pertes"
324
325#. module: account_easy_reconcile
326#: view:easy.reconcile.history:0
327msgid "Reconciliation Profile"
328msgstr "Profil de réconciliation"
329
330#. module: account_easy_reconcile
331#: view:account.easy.reconcile:0
332#: field:account.easy.reconcile,history_ids:0
333msgid "History"
334msgstr "Historique"
335
336#. module: account_easy_reconcile
337#: view:account.easy.reconcile:0
338#: view:easy.reconcile.history:0
339msgid "Go to reconciled items"
340msgstr "Voir les entrées lettrées"
341
342#. module: account_easy_reconcile
343#: view:account.easy.reconcile:0
344msgid "Profile Information"
345msgstr "Information sur le profil"
346
347#. module: account_easy_reconcile
348#: view:account.easy.reconcile.method:0
349msgid "Automatic Easy Reconcile Method"
350msgstr "Méthode de lettrage automatisé"
351
352#. module: account_easy_reconcile
353#: view:account.easy.reconcile:0
354msgid "Simple. Amount and Reference"
355msgstr "Simple. Montant et référence"
356
357#. module: account_easy_reconcile
358#: view:account.easy.reconcile:0
359msgid "Display items partially reconciled on the last run"
360msgstr "Afficher les entrées partiellement lettrées au dernier lettrage"
361
362#. module: account_easy_reconcile
363#: field:account.easy.reconcile.method,sequence:0
364msgid "Sequence"
365msgstr "Séquence"
366
367#. module: account_easy_reconcile
368#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple
369msgid "easy.reconcile.simple"
370msgstr "easy.reconcile.simple"
371
372#. module: account_easy_reconcile
373#: view:easy.reconcile.history:0
374msgid "Reconciliations of last 7 days"
375msgstr "Lettrages des 7 derniers jours"
376
377#. module: account_easy_reconcile
378#: field:account.easy.reconcile.method,date_base_on:0
379#: field:easy.reconcile.base,date_base_on:0
380#: field:easy.reconcile.options,date_base_on:0
381#: field:easy.reconcile.simple,date_base_on:0
382#: field:easy.reconcile.simple.name,date_base_on:0
383#: field:easy.reconcile.simple.partner,date_base_on:0
384#: field:easy.reconcile.simple.reference,date_base_on:0
385msgid "Date of reconciliation"
386msgstr "Date de lettrage"
387
388#. module: account_easy_reconcile
389#: code:addons/account_easy_reconcile/easy_reconcile_history.py:104
390#: view:easy.reconcile.history:0
391#: field:easy.reconcile.history,reconcile_partial_ids:0
392#, python-format
393msgid "Partial Reconciliations"
394msgstr "Lettrages partiels"
395
396#. module: account_easy_reconcile
397#: model:ir.model,name:account_easy_reconcile.model_account_easy_reconcile_method
398msgid "reconcile method for account_easy_reconcile"
399msgstr "Méthode de lettrage"
400
401#. module: account_easy_reconcile
402#: model:ir.model,name:account_easy_reconcile.model_easy_reconcile_simple_reference
403msgid "easy.reconcile.simple.reference"
404msgstr "easy.reconcile.simple.reference"
405
406#. module: account_easy_reconcile
407#: model:ir.model,name:account_easy_reconcile.model_account_easy_reconcile
408msgid "account easy reconcile"
409msgstr "Lettrage automatisé"
410
411#~ msgid "Unreconciled Entries"
412#~ msgstr "Écritures non lettrées"
413
414#, fuzzy
415#~ msgid "Partially Reconciled Entries"
416#~ msgstr "Lettrages partiels"
417
418#~ msgid "Task Information"
419#~ msgstr "Information sur la tâche"
420
421#~ msgid "Reconcile Method"
422#~ msgstr "Méthode de lettrage"
423
424#~ msgid "Log"
425#~ msgstr "Historique"
0426
=== added directory 'account_easy_reconcile/security'
=== added file 'account_easy_reconcile/security/ir.model.access.csv'
--- account_easy_reconcile/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/security/ir.model.access.csv 2013-06-10 06:07:03 +0000
@@ -0,0 +1,15 @@
1id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2access_easy_reconcile_options_acc_user,easy.reconcile.options,model_easy_reconcile_options,account.group_account_user,1,0,0,0
3access_account_easy_reconcile_method_acc_user,account.easy.reconcile.method,model_account_easy_reconcile_method,account.group_account_user,1,0,0,0
4access_account_easy_reconcile_acc_user,account.easy.reconcile,model_account_easy_reconcile,account.group_account_user,1,1,0,0
5access_easy_reconcile_simple_name_acc_user,easy.reconcile.simple.name,model_easy_reconcile_simple_name,account.group_account_user,1,0,0,0
6access_easy_reconcile_simple_partner_acc_user,easy.reconcile.simple.partner,model_easy_reconcile_simple_partner,account.group_account_user,1,0,0,0
7access_easy_reconcile_simple_reference_acc_user,easy.reconcile.simple.reference,model_easy_reconcile_simple_reference,account.group_account_user,1,0,0,0
8access_easy_reconcile_options_acc_mgr,easy.reconcile.options,model_easy_reconcile_options,account.group_account_user,1,0,0,0
9access_account_easy_reconcile_method_acc_mgr,account.easy.reconcile.method,model_account_easy_reconcile_method,account.group_account_user,1,1,1,1
10access_account_easy_reconcile_acc_mgr,account.easy.reconcile,model_account_easy_reconcile,account.group_account_user,1,1,1,1
11access_easy_reconcile_simple_name_acc_mgr,easy.reconcile.simple.name,model_easy_reconcile_simple_name,account.group_account_user,1,0,0,0
12access_easy_reconcile_simple_partner_acc_mgr,easy.reconcile.simple.partner,model_easy_reconcile_simple_partner,account.group_account_user,1,0,0,0
13access_easy_reconcile_simple_reference_acc_mgr,easy.reconcile.simple.reference,model_easy_reconcile_simple_reference,account.group_account_user,1,0,0,0
14access_easy_reconcile_history_acc_user,easy.reconcile.history,model_easy_reconcile_history,account.group_account_user,1,1,1,0
15access_easy_reconcile_history_acc_mgr,easy.reconcile.history,model_easy_reconcile_history,account.group_account_manager,1,1,1,1
016
=== added file 'account_easy_reconcile/security/ir_rule.xml'
--- account_easy_reconcile/security/ir_rule.xml 1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/security/ir_rule.xml 2013-06-10 06:07:03 +0000
@@ -0,0 +1,25 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data noupdate="1">
4 <record id="easy_reconcile_rule" model="ir.rule">
5 <field name="name">Easy reconcile multi-company</field>
6 <field name="model_id" ref="model_account_easy_reconcile"/>
7 <field name="global" eval="True"/>
8 <field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
9 </record>
10
11 <record id="easy_reconcile_history_rule" model="ir.rule">
12 <field name="name">Easy reconcile history multi-company</field>
13 <field name="model_id" ref="model_easy_reconcile_history"/>
14 <field name="global" eval="True"/>
15 <field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
16 </record>
17
18 <record id="easy_reconcile_method_rule" model="ir.rule">
19 <field name="name">Easy reconcile method multi-company</field>
20 <field name="model_id" ref="model_account_easy_reconcile_method"/>
21 <field name="global" eval="True"/>
22 <field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
23 </record>
24 </data>
25</openerp>
026
=== added file 'account_easy_reconcile/simple_reconciliation.py'
--- account_easy_reconcile/simple_reconciliation.py 1970-01-01 00:00:00 +0000
+++ account_easy_reconcile/simple_reconciliation.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,120 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright 2012-2013 Camptocamp SA (Guewen Baconnier)
5# Copyright (C) 2010 Sébastien Beau
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from openerp.osv.orm import AbstractModel, TransientModel
23
24
25class easy_reconcile_simple(AbstractModel):
26
27 _name = 'easy.reconcile.simple'
28 _inherit = 'easy.reconcile.base'
29
30 # has to be subclassed
31 # field name used as key for matching the move lines
32 _key_field = None
33
34 def rec_auto_lines_simple(self, cr, uid, rec, lines, context=None):
35 if context is None:
36 context = {}
37
38 if self._key_field is None:
39 raise ValueError("_key_field has to be defined")
40
41 count = 0
42 res = []
43 while (count < len(lines)):
44 for i in xrange(count+1, len(lines)):
45 writeoff_account_id = False
46 if lines[count][self._key_field] != lines[i][self._key_field]:
47 break
48
49 check = False
50 if lines[count]['credit'] > 0 and lines[i]['debit'] > 0:
51 credit_line = lines[count]
52 debit_line = lines[i]
53 check = True
54 elif lines[i]['credit'] > 0 and lines[count]['debit'] > 0:
55 credit_line = lines[i]
56 debit_line = lines[count]
57 check = True
58 if not check:
59 continue
60
61 reconciled, dummy = self._reconcile_lines(
62 cr, uid, rec, [credit_line, debit_line],
63 allow_partial=False, context=context)
64 if reconciled:
65 res += [credit_line['id'], debit_line['id']]
66 del lines[i]
67 break
68 count += 1
69 return res, [] # empty list for partial, only full rec in "simple" rec
70
71 def _simple_order(self, rec, *args, **kwargs):
72 return "ORDER BY account_move_line.%s" % self._key_field
73
74 def _action_rec(self, cr, uid, rec, context=None):
75 """Match only 2 move lines, do not allow partial reconcile"""
76 select = self._select(rec)
77 select += ", account_move_line.%s " % self._key_field
78 where, params = self._where(rec)
79 where += " AND account_move_line.%s IS NOT NULL " % self._key_field
80
81 where2, params2 = self._get_filter(cr, uid, rec, context=context)
82 query = ' '.join((
83 select,
84 self._from(rec),
85 where, where2,
86 self._simple_order(rec)))
87
88 cr.execute(query, params + params2)
89 lines = cr.dictfetchall()
90 return self.rec_auto_lines_simple(cr, uid, rec, lines, context)
91
92
93class easy_reconcile_simple_name(TransientModel):
94
95 _name = 'easy.reconcile.simple.name'
96 _inherit = 'easy.reconcile.simple'
97
98 # has to be subclassed
99 # field name used as key for matching the move lines
100 _key_field = 'name'
101
102
103class easy_reconcile_simple_partner(TransientModel):
104
105 _name = 'easy.reconcile.simple.partner'
106 _inherit = 'easy.reconcile.simple'
107
108 # has to be subclassed
109 # field name used as key for matching the move lines
110 _key_field = 'partner_id'
111
112
113class easy_reconcile_simple_reference(TransientModel):
114
115 _name = 'easy.reconcile.simple.reference'
116 _inherit = 'easy.reconcile.simple'
117
118 # has to be subclassed
119 # field name used as key for matching the move lines
120 _key_field = 'ref'
0121
=== added directory 'account_statement_base_completion'
=== added file 'account_statement_base_completion/__init__.py'
--- account_statement_base_completion/__init__.py 1970-01-01 00:00:00 +0000
+++ account_statement_base_completion/__init__.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,23 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Joel Grand-Guillaume
5# Copyright 2011-2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22import statement
23import partner
024
=== added file 'account_statement_base_completion/__openerp__.py'
--- account_statement_base_completion/__openerp__.py 1970-01-01 00:00:00 +0000
+++ account_statement_base_completion/__openerp__.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,74 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Joel Grand-Guillaume
5# Copyright 2011-2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22{'name': "Bank statement base completion",
23 'version': '1.0',
24 'author': 'Camptocamp',
25 'maintainer': 'Camptocamp',
26 'category': 'Finance',
27 'complexity': 'normal',
28 'depends': ['account_statement_ext'],
29 'description': """
30 The goal of this module is to improve the basic bank statement, help dealing with huge volume of
31 reconciliation by providing basic rules to identify the partner of a bank statement line.
32 Each bank statement profile can have its own rules to be applied according to a sequence order.
33
34 Some basic rules are provided in this module:
35
36 1) Match from statement line label (based on partner field 'Bank Statement Label')
37 2) Match from statement line label (based on partner name)
38 3) Match from statement line reference (based on SO number)
39 3) Match from statement line reference (based on Invoice number)
40
41 You can easily override this module and add your own rules in your own one. The basic rules only
42 fill in the partner, but you can use them to fill in any value of the line (in the future, we will
43 add a rule to automatically match and reconcile the line).
44
45 It adds as well a label on the bank statement line (on which the pre-define rules can match) and
46 a char field on the partner called 'Bank Statement Label'. Using the pre-define rules, you will be
47 able to match various labels for a partner.
48
49 The reference of the line is always used by the reconciliation process. We're supposed to copy
50 there (or write manually) the matching string. This can be: the order Number or an invoice number,
51 or anything that will be found in the invoice accounting entry part to make the match.
52
53 You can use it with our account_advanced_reconcile module to automatize the reconciliation process.
54
55
56 TODO: The rules that look for invoices to find out the partner should take back the payable / receivable
57 account from there directly instead of retrieving it from partner properties !
58
59 """,
60 'website': 'http://www.camptocamp.com',
61 'init_xml': [],
62 'update_xml': [
63 'statement_view.xml',
64 'partner_view.xml',
65 'data.xml',
66 'security/ir.model.access.csv',
67 ],
68 'demo_xml': [],
69 'test': [],
70 'installable': True,
71 'images': [],
72 'auto_install': False,
73 'license': 'AGPL-3',
74}
075
=== added file 'account_statement_base_completion/data.xml'
--- account_statement_base_completion/data.xml 1970-01-01 00:00:00 +0000
+++ account_statement_base_completion/data.xml 2013-06-10 06:07:03 +0000
@@ -0,0 +1,37 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3<data noupdate="1">
4
5 <record id="bank_statement_completion_rule_2" model="account.statement.completion.rule">
6 <field name="name">Match from line label (based on partner field 'Bank Statement Label')</field>
7 <field name="sequence">60</field>
8 <field name="function_to_call">get_from_label_and_partner_field</field>
9 </record>
10
11 <record id="bank_statement_completion_rule_3" model="account.statement.completion.rule">
12 <field name="name">Match from line label (based on partner name)</field>
13 <field name="sequence">70</field>
14 <field name="function_to_call">get_from_label_and_partner_name</field>
15 </record>
16
17 <record id="bank_statement_completion_rule_1" model="account.statement.completion.rule">
18 <field name="name">Match from line reference (based on SO number)</field>
19 <field name="sequence">50</field>
20 <field name="function_to_call">get_from_ref_and_so</field>
21 </record>
22
23 <record id="bank_statement_completion_rule_4" model="account.statement.completion.rule">
24 <field name="name">Match from line reference (based on Invoice number)</field>
25 <field name="sequence">40</field>
26 <field name="function_to_call">get_from_ref_and_invoice</field>
27 </record>
28
29 <record id="bank_statement_completion_rule_5" model="account.statement.completion.rule">
30 <field name="name">Match from line reference (based on Invoice Supplier number)</field>
31 <field name="sequence">45</field>
32 <field name="function_to_call">get_from_ref_and_supplier_invoice</field>
33 </record>
34
35
36</data>
37</openerp>
038
=== added directory 'account_statement_base_completion/i18n'
=== added file 'account_statement_base_completion/i18n/fr.po'
--- account_statement_base_completion/i18n/fr.po 1970-01-01 00:00:00 +0000
+++ account_statement_base_completion/i18n/fr.po 2013-06-10 06:07:03 +0000
@@ -0,0 +1,180 @@
1#. module: account_statement_base_completion
2#: view:account.statement.completion.rule:0
3msgid "Related Profiles"
4msgstr "Profils liés"
5
6#. module: account_statement_base_completion
7#: help:account.statement.completion.rule,sequence:0
8msgid "Lower means parsed first."
9msgstr "Plus petite séquence analysée en premier."
10
11#. module: account_statement_base_completion
12#: code:addons/account_statement_base_completion/statement.py:150
13#: code:addons/account_statement_base_completion/statement.py:182
14#: code:addons/account_statement_base_completion/statement.py:219
15#: code:addons/account_statement_base_completion/statement.py:252
16#, python-format
17msgid "Line named \"%s\" (Ref:%s) was matched by more than one partner."
18msgstr "La ligne nommée \"%s\" (Ref:%s) correspond à plusieurs partenaires."
19
20#. module: account_statement_base_completion
21#: field:account.bank.statement,completion_logs:0
22msgid "Completion Log"
23msgstr "Journal des complétions"
24
25#. module: account_statement_base_completion
26#: field:account.bank.statement.line,label:0
27msgid "Label"
28msgstr "Description"
29
30#. module: account_statement_base_completion
31#: help:account.bank.statement.line,label:0
32msgid ""
33"Generiy field to store a label given from the bank/office on which we "
34"can base the default/standard providen rule."
35msgstr ""
36"Ce champs permet de stocker une description complémentaire fournie par la banque."
37"Le lettrage avancé pourra être effectué sur ce critère."
38
39#. module: account_statement_base_completion
40#: model:ir.model,name:account_statement_base_completion.model_account_bank_statement
41msgid "Bank Statement"
42msgstr "Relevé bancaire"
43
44#. module: account_statement_base_completion
45#: field:account.statement.completion.rule,function_to_call:0
46msgid "Method"
47msgstr "Méthode"
48
49#. module: account_statement_base_completion
50#: code:addons/account_statement_base_completion/statement.py:352
51#, python-format
52msgid "Bank Statement ID %s has %s lines completed by %s"
53msgstr "Le relevé bancaire avec l'ID %s a %s lignes completées par %s"
54
55#. module: account_statement_base_completion
56#: field:account.bank.statement.line,additionnal_bank_fields:0
57msgid "Additionnal infos from bank"
58msgstr "Informations additionnelles de la banque"
59
60#. module: account_statement_base_completion
61#: view:account.statement.profile:0
62msgid "Auto-Completion Rules"
63msgstr "Règles d'auto-complétion"
64
65#. module: account_statement_base_completion
66#: help:account.bank.statement.line,additionnal_bank_fields:0
67msgid ""
68"Used by completion and import system. Adds every field that is present in "
69"your bank/office statement file"
70msgstr ""
71"Utilisé au niveau de l'auto-complétion et de l'import. Permet l'ajout de n'importe quel "
72"champs additionnel communiqué par la banque"
73
74#. module: account_statement_base_completion
75#: view:account.bank.statement:0
76msgid "Importation related infos"
77msgstr "Importation des informations liées"
78
79#. module: account_statement_base_completion
80#: model:ir.model,name:account_statement_base_completion.model_account_statement_profile
81msgid "Statement Profil"
82msgstr "Profil de relevé"
83
84#. module: account_statement_base_completion
85#: field:account.statement.completion.rule,name:0
86msgid "Name"
87msgstr "Nom"
88
89#. module: account_statement_base_completion
90#: constraint:account.statement.profile:0
91msgid "You need to put a partner if you tic the 'Force partner on bank move' !"
92msgstr ""
93"Vous devez indiquer un partenaire si vous avez coché 'Indiquer un partenaire sur la ligne d'écriture de la banque' !"
94
95#. module: account_statement_base_completion
96#: model:ir.model,name:account_statement_base_completion.model_account_bank_statement_line
97msgid "Bank Statement Line"
98msgstr "Ligne de relevé bancaire"
99
100#. module: account_statement_base_completion
101#: view:account.statement.completion.rule:0
102#: model:ir.actions.act_window,name:account_statement_base_completion.action_st_completion_rule_tree
103#: model:ir.ui.menu,name:account_statement_base_completion.menu_action_st_completion_rule_tree_menu
104msgid "Statement Completion Rule"
105msgstr "Règle d'auto-complétion du relevé"
106
107#. module: account_statement_base_completion
108#: model:ir.model,name:account_statement_base_completion.model_account_statement_completion_rule
109msgid "account.statement.completion.rule"
110msgstr "account.statement.completion.rule"
111
112#. module: account_statement_base_completion
113#: field:account.statement.completion.rule,profile_ids:0
114#: field:account.statement.profile,rule_ids:0
115msgid "Related statement profiles"
116msgstr "Profils liés"
117
118#. module: account_statement_base_completion
119#: constraint:account.bank.statement.line:0
120msgid ""
121"The amount of the voucher must be the same amount as the one on the "
122"statement line"
123msgstr ""
124"Le montant du justificatif doit être identique à celui de la ligne le "
125"concernant sur le relevé"
126
127#. module: account_statement_base_completion
128#: field:account.bank.statement.line,already_completed:0
129msgid "Auto-Completed"
130msgstr "Auto-Completé"
131
132#. module: account_statement_base_completion
133#: view:account.bank.statement:0
134msgid "Auto Completion"
135msgstr "Auto-complétion"
136
137#. module: account_statement_base_completion
138#: field:account.statement.completion.rule,sequence:0
139msgid "Sequence"
140msgstr "Séquence"
141
142#. module: account_statement_base_completion
143#: constraint:account.bank.statement:0
144msgid "The journal and period chosen have to belong to the same company."
145msgstr "Le journal et la période doivent appartenir à la même société."
146
147#. module: account_statement_base_completion
148#: field:res.partner,bank_statement_label:0
149msgid "Bank Statement Label"
150msgstr "Description de relevé bancaire"
151
152#. module: account_statement_base_completion
153#: help:account.bank.statement.line,already_completed:0
154msgid ""
155"When this checkbox is ticked, the auto-completion process/button will ignore "
156"this line."
157msgstr ""
158"Les lignes cochées seront ignorées lorsque vous cliquez sur le bouton auto-complétion"
159
160#. module: account_statement_base_completion
161#: help:res.partner,bank_statement_label:0
162msgid ""
163"Enter the various label found on your bank statement separated by a ; "
164"If one of this label is include in the bank statement line, "
165"the partner will be automatically filled (as long as you "
166"use this method/rules in your statement profile)."
167msgstr ""
168"Entrez les différentes descriptions/informations sur votre relevé bancaire séparées par un ';' "
169"Si l'une d'entre elles figure dans la ligne du relevé, le partenaire correspondant pourra"
170"être automatiquement retrouvé (à condition d'utiliser un règle de lettrage dans le profil)."
171
172#. module: account_statement_base_completion
173#: model:ir.model,name:account_statement_base_completion.model_res_partner
174msgid "Partner"
175msgstr "Partenaire"
176
177#. module: account_statement_base_completion
178#: view:account.bank.statement:0
179msgid "Completion Logs"
180msgstr "Journaux d'auto-complétion"
0181
=== added file 'account_statement_base_completion/partner.py'
--- account_statement_base_completion/partner.py 1970-01-01 00:00:00 +0000
+++ account_statement_base_completion/partner.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,38 @@
1# -*- coding: utf-8 -*-
2#################################################################################
3# #
4# Copyright (C) 2011 Akretion & Camptocamp
5# Author : Sébastien BEAU, Joel Grand-Guillaume #
6# #
7# This program is free software: you can redistribute it and/or modify #
8# it under the terms of the GNU Affero General Public License as #
9# published by the Free Software Foundation, either version 3 of the #
10# License, or (at your option) any later version. #
11# #
12# This program is distributed in the hope that it will be useful, #
13# but WITHOUT ANY WARRANTY; without even the implied warranty of #
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15# GNU Affero General Public License for more details. #
16# #
17# You should have received a copy of the GNU Affero General Public License #
18# along with this program. If not, see <http://www.gnu.org/licenses/>. #
19# #
20#################################################################################
21
22from openerp.osv.orm import Model
23from openerp.osv import fields, osv
24
25
26class res_partner(Model):
27 """
28 Add a bank label on the partner so that we can use it to match
29 this partner when we found this in a statement line.
30 """
31 _inherit = 'res.partner'
32
33 _columns = {
34 'bank_statement_label': fields.char('Bank Statement Label', size=100,
35 help="Enter the various label found on your bank statement separated by a ; If \
36 one of this label is include in the bank statement line, the partner will be automatically \
37 filled (as long as you use this method/rules in your statement profile)."),
38 }
039
=== added file 'account_statement_base_completion/partner_view.xml'
--- account_statement_base_completion/partner_view.xml 1970-01-01 00:00:00 +0000
+++ account_statement_base_completion/partner_view.xml 2013-06-10 06:07:03 +0000
@@ -0,0 +1,22 @@
1<?xml version="1.0" encoding="UTF-8"?>
2
3
4<openerp>
5 <data>
6
7 <record id="bk_view_partner_form" model="ir.ui.view">
8 <field name="name">account_bank_statement_import.view.partner.form</field>
9 <field name="model">res.partner</field>
10 <field name="type">form</field>
11 <field name="priority">20</field>
12 <field name="inherit_id" ref="account.view_partner_property_form"/>
13 <field name="arch" type="xml">
14 <field name="property_account_payable" position="after">
15 <field name="bank_statement_label"/>
16 </field>
17 </field>
18 </record>
19
20
21 </data>
22</openerp>
023
=== added directory 'account_statement_base_completion/security'
=== added file 'account_statement_base_completion/security/ir.model.access.csv'
--- account_statement_base_completion/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ account_statement_base_completion/security/ir.model.access.csv 2013-06-10 06:07:03 +0000
@@ -0,0 +1,3 @@
1id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2access_account_bank_st_cmpl_user,account.statement.completion.rule,model_account_statement_completion_rule,account.group_account_user,1,0,0,0
3access_account_bank_st_cmpl_manager,account.statement.completion.rule,model_account_statement_completion_rule,account.group_account_manager,1,1,1,1
04
=== added file 'account_statement_base_completion/statement.py'
--- account_statement_base_completion/statement.py 1970-01-01 00:00:00 +0000
+++ account_statement_base_completion/statement.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,527 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Nicolas Bessi, Joel Grand-Guillaume
5# Copyright 2011-2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21# TODO replace customer supplier by package constant
22import traceback
23import sys
24import logging
25
26from collections import defaultdict
27import re
28from tools.translate import _
29from openerp.osv import osv, orm, fields
30from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
31from operator import attrgetter
32import datetime
33
34_logger = logging.getLogger(__name__)
35
36
37class ErrorTooManyPartner(Exception):
38 """
39 New Exception definition that is raised when more than one partner is matched by
40 the completion rule.
41 """
42 def __init__(self, value):
43 self.value = value
44
45 def __str__(self):
46 return repr(self.value)
47
48 def __repr__(self):
49 return repr(self.value)
50
51
52class AccountStatementProfil(orm.Model):
53 """
54 Extend the class to add rules per profile that will match at least the partner,
55 but it could also be used to match other values as well.
56 """
57
58 _inherit = "account.statement.profile"
59
60 _columns = {
61 # @Akretion: For now, we don't implement this features, but this would probably be there:
62 # 'auto_completion': fields.text('Auto Completion'),
63 # 'transferts_account_id':fields.many2one('account.account', 'Transferts Account'),
64 # => You can implement it in a module easily, we design it with your needs in mind
65 # as well!
66
67 'rule_ids': fields.many2many(
68 'account.statement.completion.rule',
69 string='Related statement profiles',
70 rel='as_rul_st_prof_rel'),
71 }
72
73 def _get_callable(self, cr, uid, profile, context=None):
74 if isinstance(profile, (int, long)):
75 prof = self.browse(cr, uid, profile, context=context)
76 else:
77 prof = profile
78 # We need to respect the sequence order
79 sorted_array = sorted(prof.rule_ids, key=attrgetter('sequence'))
80 return tuple((x.function_to_call for x in sorted_array))
81
82 def _find_values_from_rules(self, cr, uid, calls, line, context=None):
83 """
84 This method will execute all related rules, in their sequence order,
85 to retrieve all the values returned by the first rules that will match.
86 :param calls: list of lookup function name available in rules
87 :param dict line: read of the concerned account.bank.statement.line
88 :return:
89 A dict of value that can be passed directly to the write method of
90 the statement line or {}
91 {'partner_id': value,
92 'account_id: value,
93
94 ...}
95 """
96 if context is None:
97 context = {}
98 if not calls:
99 calls = self._get_callable(cr, uid, line['profile_id'], context=context)
100 rule_obj = self.pool.get('account.statement.completion.rule')
101
102 for call in calls:
103 method_to_call = getattr(rule_obj, call)
104 result = method_to_call(cr, uid, line, context)
105 if result:
106 result['already_completed'] = True
107 return result
108 return None
109
110
111class AccountStatementCompletionRule(orm.Model):
112 """
113 This will represent all the completion method that we can have to
114 fullfill the bank statement lines. You'll be able to extend them in you own module
115 and choose those to apply for every statement profile.
116 The goal of a rule is to fullfill at least the partner of the line, but
117 if possible also the reference because we'll use it in the reconciliation
118 process. The reference should contain the invoice number or the SO number
119 or any reference that will be matched by the invoice accounting move.
120 """
121
122 _name = "account.statement.completion.rule"
123 _order = "sequence asc"
124
125 def _get_functions(self, cr, uid, context=None):
126 """
127 List of available methods for rules. Override this to add you own.
128 """
129 return [
130 ('get_from_ref_and_invoice', 'From line reference (based on customer invoice number)'),
131 ('get_from_ref_and_supplier_invoice', 'From line reference (based on supplier invoice number)'),
132 ('get_from_ref_and_so', 'From line reference (based on SO number)'),
133 ('get_from_label_and_partner_field', 'From line label (based on partner field)'),
134 ('get_from_label_and_partner_name', 'From line label (based on partner name)')]
135
136 _columns = {
137 'sequence': fields.integer('Sequence', help="Lower means parsed first."),
138 'name': fields.char('Name', size=128),
139 'profile_ids': fields.many2many(
140 'account.statement.profile',
141 rel='as_rul_st_prof_rel',
142 string='Related statement profiles'),
143 'function_to_call': fields.selection(_get_functions, 'Method'),
144 }
145
146 def _find_invoice(self, cr, uid, st_line, inv_type, context=None):
147 """Find invoice related to statement line"""
148 inv_obj = self.pool.get('account.invoice')
149 if inv_type == 'supplier':
150 type_domain = ('in_invoice', 'in_refund')
151 number_field = 'supplier_invoice_number'
152 elif inv_type == 'customer':
153 type_domain = ('out_invoice', 'out_refund')
154 number_field = 'number'
155 else:
156 raise osv.except_osv(_('System error'),
157 _('Invalid invoice type for completion: %') % inv_type)
158
159 inv_id = inv_obj.search(cr, uid,
160 [(number_field, '=', st_line['ref'].strip()),
161 ('type', 'in', type_domain)],
162 context=context)
163 if inv_id:
164 if len(inv_id) == 1:
165 inv = inv_obj.browse(cr, uid, inv_id[0], context=context)
166 else:
167 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
168 'than one partner while looking on %s invoices') %
169 (st_line['name'], st_line['ref'], inv_type))
170 return inv
171 return False
172
173 def _from_invoice(self, cr, uid, line, inv_type, context):
174 """Populate statement line values"""
175 if not inv_type in ('supplier', 'customer'):
176 raise osv.except_osv(_('System error'),
177 _('Invalid invoice type for completion: %') % inv_type)
178 res = {}
179 inv = self._find_invoice(cr, uid, line, inv_type, context=context)
180 if inv:
181 res = {'partner_id': inv.partner_id.id,
182 'account_id': inv.account_id.id,
183 'type': inv_type}
184 override_acc = line['master_account_id']
185 if override_acc:
186 res['account_id'] = override_acc
187 return res
188
189 # Should be private but data are initialised with no update XML
190 def get_from_ref_and_supplier_invoice(self, cr, uid, line, context=None):
191 """
192 Match the partner based on the invoice supplier invoice number and the reference of the statement
193 line. Then, call the generic get_values_for_line method to complete other values.
194 If more than one partner matched, raise the ErrorTooManyPartner error.
195
196 :param dict line: read of the concerned account.bank.statement.line
197 :return:
198 A dict of value that can be passed directly to the write method of
199 the statement line or {}
200 {'partner_id': value,
201 'account_id': value,
202
203 ...}
204 """
205 return self._from_invoice(cr, uid, line, 'supplier', context=context)
206
207 # Should be private but data are initialised with no update XML
208 def get_from_ref_and_invoice(self, cr, uid, line, context=None):
209 """
210 Match the partner based on the invoice number and the reference of the statement
211 line. Then, call the generic get_values_for_line method to complete other values.
212 If more than one partner matched, raise the ErrorTooManyPartner error.
213
214 :param dict line: read of the concerned account.bank.statement.line
215 :return:
216 A dict of value that can be passed directly to the write method of
217 the statement line or {}
218 {'partner_id': value,
219 'account_id': value,
220 ...}
221 """
222 return self._from_invoice(cr, uid, line, 'customer', context=context)
223
224 # Should be private but data are initialised with no update XML
225 def get_from_ref_and_so(self, cr, uid, st_line, context=None):
226 """
227 Match the partner based on the SO number and the reference of the statement
228 line. Then, call the generic get_values_for_line method to complete other values.
229 If more than one partner matched, raise the ErrorTooManyPartner error.
230
231 :param int/long st_line: read of the concerned account.bank.statement.line
232 :return:
233 A dict of value that can be passed directly to the write method of
234 the statement line or {}
235 {'partner_id': value,
236 'account_id': value,
237
238 ...}
239 """
240 st_obj = self.pool.get('account.bank.statement.line')
241 res = {}
242 if st_line:
243 so_obj = self.pool.get('sale.order')
244 so_id = so_obj.search(cr,
245 uid,
246 [('name', '=', st_line['ref'])],
247 context=context)
248 if so_id:
249 if so_id and len(so_id) == 1:
250 so = so_obj.browse(cr, uid, so_id[0], context=context)
251 res['partner_id'] = so.partner_id.id
252 elif so_id and len(so_id) > 1:
253 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
254 'than one partner while looking on SO by ref.') %
255 (st_line['name'], st_line['ref']))
256 st_vals = st_obj.get_values_for_line(cr,
257 uid,
258 profile_id=st_line['profile_id'],
259 master_account_id=st_line['master_account_id'],
260 partner_id=res.get('partner_id', False),
261 line_type='customer',
262 amount=st_line['amount'] if st_line['amount'] else 0.0,
263 context=context)
264 res.update(st_vals)
265 return res
266
267 # Should be private but data are initialised with no update XML
268 def get_from_label_and_partner_field(self, cr, uid, st_line, context=None):
269 """
270 Match the partner based on the label field of the statement line
271 and the text defined in the 'bank_statement_label' field of the partner.
272 Remember that we can have values separated with ; Then, call the generic
273 get_values_for_line method to complete other values.
274 If more than one partner matched, raise the ErrorTooManyPartner error.
275
276 :param dict st_line: read of the concerned account.bank.statement.line
277 :return:
278 A dict of value that can be passed directly to the write method of
279 the statement line or {}
280 {'partner_id': value,
281 'account_id': value,
282
283 ...}
284 """
285 partner_obj = self.pool.get('res.partner')
286 st_obj = self.pool.get('account.bank.statement.line')
287 res = {}
288 # As we have to iterate on each partner for each line,
289 # we memoize the pair to avoid
290 # to redo computation for each line.
291 # Following code can be done by a single SQL query
292 # but this option is not really maintanable
293 if not context.get('label_memoizer'):
294 context['label_memoizer'] = defaultdict(list)
295 partner_ids = partner_obj.search(cr,
296 uid,
297 [('bank_statement_label', '!=', False)])
298 line_ids = context.get('line_ids', [])
299 for partner in partner_obj.browse(cr, uid, partner_ids, context=context):
300 vals = '|'.join(re.escape(x.strip()) for x in partner.bank_statement_label.split(';'))
301 or_regex = ".*%s.*" % vals
302 sql = ("SELECT id from account_bank_statement_line"
303 " WHERE id in %s"
304 " AND name ~* %s")
305 cr.execute(sql, (line_ids, or_regex))
306 pairs = cr.fetchall()
307 for pair in pairs:
308 context['label_memoizer'][pair[0]].append(partner)
309
310 if st_line['id'] in context['label_memoizer']:
311 found_partner = context['label_memoizer'][st_line['id']]
312 if len(found_partner) > 1:
313 msg = (_('Line named "%s" (Ref:%s) was matched by '
314 'more than one partner while looking on partner label: %s') %
315 (st_line['name'], st_line['ref'], ','.join([x.name for x in found_partner])))
316 raise ErrorTooManyPartner(msg)
317 res['partner_id'] = found_partner[0].id
318 st_vals = st_obj.get_values_for_line(cr,
319 uid,
320 profile_id=st_line['profile_id'],
321 master_account_id=st_line['master_account_id'],
322 partner_id=found_partner[0].id,
323 line_type=False,
324 amount=st_line['amount'] if st_line['amount'] else 0.0,
325 context=context)
326 res.update(st_vals)
327 return res
328
329 def get_from_label_and_partner_name(self, cr, uid, st_line, context=None):
330 """
331 Match the partner based on the label field of the statement line
332 and the name of the partner.
333 Then, call the generic get_values_for_line method to complete other values.
334 If more than one partner matched, raise the ErrorTooManyPartner error.
335
336 :param dict st_line: read of the concerned account.bank.statement.line
337 :return:
338 A dict of value that can be passed directly to the write method of
339 the statement line or {}
340 {'partner_id': value,
341 'account_id': value,
342
343 ...}
344 """
345 res = {}
346 # We memoize allowed partner
347 if not context.get('partner_memoizer'):
348 context['partner_memoizer'] = tuple(self.pool['res.partner'].search(cr, uid, []))
349 if not context['partner_memoizer']:
350 return res
351 st_obj = self.pool.get('account.bank.statement.line')
352 sql = "SELECT id FROM res_partner WHERE name ~* %s and id in %s"
353 pattern = ".*%s.*" % re.escape(st_line['name'])
354 cr.execute(sql, (pattern, context['partner_memoizer']))
355 result = cr.fetchall()
356 if not result:
357 return res
358 if len(result) > 1:
359 raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more '
360 'than one partner while looking on partner by name') %
361 (st_line['name'], st_line['ref']))
362 res['partner_id'] = result[0][0]
363 st_vals = st_obj.get_values_for_line(cr,
364 uid,
365 profile_id=st_line['porfile_id'],
366 master_account_id=st_line['master_account_id'],
367 partner_id=res['partner_id'],
368 line_type=False,
369 amount=st_line['amount'] if st_line['amount'] else 0.0,
370 context=context)
371 res.update(st_vals)
372 return res
373
374
375class AccountStatementLine(orm.Model):
376 """
377 Add sparse field on the statement line to allow to store all the
378 bank infos that are given by a bank/office. You can then add you own in your
379 module. The idea here is to store all bank/office infos in the additionnal_bank_fields
380 serialized field when importing the file. If many values, add a tab in the bank
381 statement line to store your specific one. Have a look in account_statement_base_import
382 module to see how we've done it.
383 """
384 _inherit = "account.bank.statement.line"
385
386 _columns = {
387 'additionnal_bank_fields': fields.serialized(
388 'Additionnal infos from bank',
389 help="Used by completion and import system. Adds every field that "
390 "is present in your bank/office statement file"),
391 'label': fields.sparse(
392 type='char',
393 string='Label',
394 serialization_field='additionnal_bank_fields',
395 help="Generic field to store a label given from the "
396 "bank/office on which we can base the default/standard "
397 "providen rule."),
398 'already_completed': fields.boolean(
399 "Auto-Completed",
400 help="When this checkbox is ticked, the auto-completion "
401 "process/button will ignore this line."),
402 }
403
404 _defaults = {
405 'already_completed': False,
406 }
407
408 def _get_line_values_from_rules(self, cr, uid, line, rules, context=None):
409 """
410 We'll try to find out the values related to the line based on rules setted on
411 the profile.. We will ignore line for which already_completed is ticked.
412
413 :return:
414 A dict of dict value that can be passed directly to the write method of
415 the statement line or {}. The first dict has statement line ID as a key:
416 {117009: {'partner_id': 100997, 'account_id': 489L}}
417 """
418 profile_obj = self.pool.get('account.statement.profile')
419 if line.get('already_completed'):
420 return {}
421 # Ask the rule
422 vals = profile_obj._find_values_from_rules(cr, uid, rules, line, context)
423 if vals:
424 vals['id'] = line['id']
425 return vals
426 return {}
427
428
429class AccountBankSatement(orm.Model):
430 """
431 We add a basic button and stuff to support the auto-completion
432 of the bank statement once line have been imported or manually fullfill.
433 """
434 _inherit = "account.bank.statement"
435
436 _columns = {
437 'completion_logs': fields.text('Completion Log', readonly=True),
438 }
439
440 def write_completion_log(self, cr, uid, stat_id, error_msg, number_imported, context=None):
441 """
442 Write the log in the completion_logs field of the bank statement to let the user
443 know what have been done. This is an append mode, so we don't overwrite what
444 already recoded.
445
446 :param int/long stat_id: ID of the account.bank.statement
447 :param char error_msg: Message to add
448 :number_imported int/long: Number of lines that have been completed
449 :return True
450 """
451 user_name = self.pool.get('res.users').read(cr, uid, uid,
452 ['name'], context=context)['name']
453
454 log = self.read(cr, uid, stat_id, ['completion_logs'],
455 context=context)['completion_logs']
456 log = log if log else ""
457
458 completion_date = datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
459 message = (_("%s Bank Statement ID %s has %s lines completed by %s \n%s\n%s\n") %
460 (completion_date, stat_id, number_imported, user_name, error_msg, log))
461 self.write(cr, uid, [stat_id], {'completion_logs': message}, context=context)
462
463 body = (_('Statement ID %s auto-completed for %s lines completed') %
464 (stat_id, number_imported)),
465 self.message_post(cr, uid,
466 [stat_id],
467 body=body,
468 context=context)
469 return True
470
471 def button_auto_completion(self, cr, uid, ids, context=None):
472 """
473 Complete line with values given by rules and tic the already_completed
474 checkbox so we won't compute them again unless the user untick them!
475 """
476 if context is None:
477 context = {}
478 stat_line_obj = self.pool['account.bank.statement.line']
479 profile_obj = self.pool.get('account.statement.profile')
480 compl_lines = 0
481 stat_line_obj.check_access_rule(cr, uid, [], 'create')
482 stat_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
483 for stat in self.browse(cr, uid, ids, context=context):
484 msg_lines = []
485 ctx = context.copy()
486 ctx['line_ids'] = tuple((x.id for x in stat.line_ids))
487 b_profile = stat.profile_id
488 rules = profile_obj._get_callable(cr, uid, b_profile, context=context)
489 profile_id = b_profile.id # Only for perfo even it gains almost nothing
490 master_account_id = b_profile.receivable_account_id
491 master_account_id = master_account_id.id if master_account_id else False
492 res = False
493 for line in stat_line_obj.read(cr, uid, ctx['line_ids']):
494 try:
495 # performance trick
496 line['master_account_id'] = master_account_id
497 line['profile_id'] = profile_id
498 res = stat_line_obj._get_line_values_from_rules(cr, uid, line,
499 rules, context=ctx)
500 if res:
501 compl_lines += 1
502 except ErrorTooManyPartner, exc:
503 msg_lines.append(repr(exc))
504 except Exception, exc:
505 msg_lines.append(repr(exc))
506 error_type, error_value, trbk = sys.exc_info()
507 st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
508 st += ''.join(traceback.format_tb(trbk, 30))
509 _logger.error(st)
510 if res:
511 #stat_line_obj.write(cr, uid, [line.id], vals, context=ctx)
512 try:
513 stat_line_obj._update_line(cr, uid, res, context=context)
514 except Exception as exc:
515 msg_lines.append(repr(exc))
516 error_type, error_value, trbk = sys.exc_info()
517 st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
518 st += ''.join(traceback.format_tb(trbk, 30))
519 _logger.error(st)
520 # we can commit as it is not needed to be atomic
521 # commiting here adds a nice perfo boost
522 if not compl_lines % 500:
523 cr.commit()
524 msg = u'\n'.join(msg_lines)
525 self.write_completion_log(cr, uid, stat.id,
526 msg, compl_lines, context=context)
527 return True
0528
=== added file 'account_statement_base_completion/statement_view.xml'
--- account_statement_base_completion/statement_view.xml 1970-01-01 00:00:00 +0000
+++ account_statement_base_completion/statement_view.xml 2013-06-10 06:07:03 +0000
@@ -0,0 +1,104 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3<data>
4
5 <record id="bank_statement_view_form" model="ir.ui.view">
6 <field name="name">account_bank_statement_import_base.bank_statement.view_form</field>
7 <field name="model">account.bank.statement</field>
8 <field name="inherit_id" ref="account.view_bank_statement_form" />
9 <field eval="16" name="priority"/>
10 <field name="type">form</field>
11 <field name="arch" type="xml">
12 <data>
13 <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/form/group/field[@name='sequence']" position="after">
14 <separator colspan="4" string="Importation related infos"/>
15 <field name="label" />
16 <field name="already_completed" />
17 </xpath>
18
19 <!-- <xpath expr="/form/group[2]" position="attributes">
20 <attribute name="col">10</attribute>
21 </xpath> -->
22
23 <xpath expr="/form/sheet/div[@name='import_buttons']" position="after">
24 <button name="button_auto_completion" string="Auto Completion" states='draft,open' type="object" colspan="1"/>
25 </xpath>
26
27 <xpath expr="/form/sheet/notebook/page[@string='Transactions']" position="after">
28 <page string="Completion Logs" attrs="{'invisible':[('completion_logs','=',False)]}">
29 <field name="completion_logs" colspan="4" nolabel="1" attrs="{'invisible':[('completion_logs','=',False)]}"/>
30 </page>
31 </xpath>
32
33 </data>
34 </field>
35 </record>
36 <record id="bank_statement_view_form2" model="ir.ui.view">
37 <field name="name">account_bank_statement_import_base.bank_statement.auto_cmpl</field>
38 <field name="model">account.bank.statement</field>
39 <field name="inherit_id" ref="account.view_bank_statement_form" />
40 <field name="type">form</field>
41 <field name="arch" type="xml">
42 <data>
43 <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/tree/field[@name='amount']" position="after">
44 <field name="already_completed" />
45 </xpath>
46 </data>
47 </field>
48 </record>
49
50 <record id="statement_rules_view_form" model="ir.ui.view">
51 <field name="name">account.statement.profile.view</field>
52 <field name="model">account.statement.profile</field>
53 <field name="inherit_id" ref="account_statement_ext.statement_importer_view_form"/>
54 <field name="type">form</field>
55 <field name="arch" type="xml">
56 <field name="bank_statement_prefix" position="after">
57 <separator colspan="4" string="Auto-Completion Rules"/>
58 <field name="rule_ids" colspan="4" nolabel="1"/>
59 </field>
60 </field>
61 </record>
62
63
64 <record id="statement_st_completion_rule_view_form" model="ir.ui.view">
65 <field name="name">account.statement.completion.rule.view</field>
66 <field name="model">account.statement.completion.rule</field>
67 <field name="type">form</field>
68 <field name="arch" type="xml">
69 <form string="Statement Completion Rule">
70 <field name="sequence"/>
71 <field name="name" select="1" />
72 <field name="function_to_call"/>
73 <separator colspan="4" string="Related Profiles"/>
74 <field name="profile_ids" nolabel="1" colspan="4"/>
75 </form>
76 </field>
77 </record>
78
79 <record id="statement_st_completion_rule_view_tree" model="ir.ui.view">
80 <field name="name">account.statement.completion.rule.view</field>
81 <field name="model">account.statement.completion.rule</field>
82 <field name="type">tree</field>
83 <field name="arch" type="xml">
84 <tree string="Statement Completion Rule">
85 <field name="sequence"/>
86 <field name="name" select="1" />
87 <field name="profile_ids" />
88 <field name="function_to_call"/>
89 </tree>
90 </field>
91 </record>
92 <record id="action_st_completion_rule_tree" model="ir.actions.act_window">
93 <field name="name">Statement Completion Rule</field>
94 <field name="res_model">account.statement.completion.rule</field>
95 <field name="view_type">form</field>
96 <field name="view_mode">tree,form</field>
97 </record>
98
99 <menuitem string="Statement Completion Rule" action="action_st_completion_rule_tree"
100 id="menu_action_st_completion_rule_tree_menu" parent="account.menu_configuration_misc"
101 sequence="30"/>
102
103</data>
104</openerp>
0105
=== added directory 'account_statement_base_import'
=== added file 'account_statement_base_import/__init__.py'
--- account_statement_base_import/__init__.py 1970-01-01 00:00:00 +0000
+++ account_statement_base_import/__init__.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,23 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Joel Grand-Guillaume
5# Copyright 2011-2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21import parser
22import wizard
23import statement
024
=== added file 'account_statement_base_import/__openerp__.py'
--- account_statement_base_import/__openerp__.py 1970-01-01 00:00:00 +0000
+++ account_statement_base_import/__openerp__.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,70 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Joel Grand-Guillaume
5# Copyright 2011-2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22{'name': "Bank statement base import",
23 'version': '1.0',
24 'author': 'Camptocamp',
25 'maintainer': 'Camptocamp',
26 'category': 'Finance',
27 'complexity': 'normal',
28 'depends': [
29 'account_statement_ext',
30 'account_statement_base_completion'
31 ],
32 'description': """
33 This module brings basic methods and fields on bank statement to deal with
34 the importation of different bank and offices. A generic abstract method is defined and an
35 example that gives you a basic way of importing bank statement through a standard file is provided.
36
37 This module improves the bank statement and allows you to import your bank transactions with
38 a standard .csv or .xls file (you'll find it in the 'data' folder). It respects the profile
39 (provided by the accouhnt_statement_ext module) to pass the entries. That means,
40 you'll have to choose a file format for each profile.
41 In order to achieve this it uses the `xlrd` Python module which you will need to install
42 separately in your environment.
43
44 This module can handle a commission taken by the payment office and has the following format:
45
46 * ref : the SO number, INV number or any matching ref found. It'll be used as reference
47 in the generated entries and will be useful for reconciliation process
48 * date : date of the payment
49 * amount : amount paid in the currency of the journal used in the importation profile
50 * commission_amount : amount of the comission for each line
51 * label : the comunication given by the payment office, used as communication in the
52 generated entries.
53
54 The goal is here to populate the statement lines of a bank statement with the infos that the
55 bank or office give you. Fell free to inherit from this module to add your own format.Then,
56 if you need to complete data from there, add your own account_statement_*_completion module and implement
57 the needed rules.
58
59 """,
60 'website': 'http://www.camptocamp.com',
61 'data': [
62 "wizard/import_statement_view.xml",
63 "statement_view.xml",
64 ],
65 'test': [],
66 'installable': True,
67 'images': [],
68 'auto_install': False,
69 'license': 'AGPL-3',
70}
071
=== added directory 'account_statement_base_import/data'
=== added file 'account_statement_base_import/data/statement.csv'
--- account_statement_base_import/data/statement.csv 1970-01-01 00:00:00 +0000
+++ account_statement_base_import/data/statement.csv 2013-06-10 06:07:03 +0000
@@ -0,0 +1,4 @@
1"ref";"date";"amount";"commission_amount";"label"
250969286;2011-03-07 13:45:14;118.4;-11.84;"label a"
351065326;2011-03-05 13:45:14;189;-15.12;"label b"
451179306;2011-03-02 17:45:14;189;-15.12;"label c"
05
=== added file 'account_statement_base_import/data/statement.xls'
1Binary files account_statement_base_import/data/statement.xls 1970-01-01 00:00:00 +0000 and account_statement_base_import/data/statement.xls 2013-06-10 06:07:03 +0000 differ6Binary files account_statement_base_import/data/statement.xls 1970-01-01 00:00:00 +0000 and account_statement_base_import/data/statement.xls 2013-06-10 06:07:03 +0000 differ
=== added directory 'account_statement_base_import/i18n'
=== added file 'account_statement_base_import/i18n/fr.po'
--- account_statement_base_import/i18n/fr.po 1970-01-01 00:00:00 +0000
+++ account_statement_base_import/i18n/fr.po 2013-06-10 06:07:03 +0000
@@ -0,0 +1,253 @@
1#. module: account_statement_base_import
2#: view:credit.statement.import:0
3#: model:ir.actions.act_window,name:account_statement_base_import.statement_importer_action
4msgid "Import statement"
5msgstr "Import de relevé"
6
7#. module: account_statement_base_import
8#: model:ir.model,name:account_statement_base_import.model_credit_statement_import
9msgid "credit.statement.import"
10msgstr "credit.statement.import"
11
12#. module: account_statement_base_import
13#: code:addons/account_statement_base_import/statement.py:218
14#, python-format
15msgid "The statement cannot be created : %s"
16msgstr "Le relevé ne peut être créé : %s"
17
18#. module: account_statement_base_import
19#: field:credit.statement.import,input_statement:0
20msgid "Statement file"
21msgstr "Fichier à importer"
22
23#. module: account_statement_base_import
24#: view:account.statement.profile:0
25msgid "Import Logs"
26msgstr "Journaux d'import"
27
28#. module: account_statement_base_import
29#: field:credit.statement.import,journal_id:0
30msgid "Financial journal to use transaction"
31msgstr "Journal"
32
33#. module: account_statement_base_import
34#: code:addons/account_statement_base_import/parser/file_parser.py:100
35#, python-format
36msgid "Column %s not present in file"
37msgstr "Colonne %s non présente dans le fichier"
38
39#. module: account_statement_base_import
40#: view:account.statement.profile:0
41#: model:ir.ui.menu,name:account_statement_base_import.statement_importer_menu
42msgid "Import Bank Statement"
43msgstr "Importation de relevé"
44
45#. module: account_statement_base_import
46#: model:ir.model,name:account_statement_base_import.model_account_statement_profile
47msgid "Statement Profil"
48msgstr "Profil du relevé"
49
50#. module: account_statement_base_import
51#: code:addons/account_statement_base_import/statement.py:100
52#, python-format
53msgid "Commission line"
54msgstr "Ligne de commission"
55
56#. module: account_statement_base_import
57#: field:credit.statement.import,commission_account_id:0
58msgid "Commission account"
59msgstr "Compte de commission"
60
61#. module: account_statement_base_import
62#: code:addons/account_statement_base_import/statement.py:172
63#, python-format
64msgid "Column %s you try to import is not present in the bank statement line !"
65msgstr ""
66"La colonne %s que vous essayez d'importer n'est pas présente dans la ligne de relevé !"
67
68#. module: account_statement_base_import
69#: code:addons/account_statement_base_import/statement.py:71
70#, python-format
71msgid "Bank Statement ID %s have been imported with %s lines "
72msgstr "Le relevé avec l'ID %s a été importé avec %s lignes "
73
74#. module: account_statement_base_import
75#: field:account.statement.profile,launch_import_completion:0
76msgid "Launch completion after import"
77msgstr "Lancer l'auto-complétion après import"
78
79#. module: account_statement_base_import
80#: field:credit.statement.import,partner_id:0
81msgid "Credit insitute partner"
82msgstr "Organisme bancaire"
83
84#. module: account_statement_base_import
85#: code:addons/account_statement_base_import/statement.py:160
86#, python-format
87msgid "No Profile !"
88msgstr "Aucun Profil !"
89
90#. module: account_statement_base_import
91#: view:account.statement.profile:0
92msgid "Import related infos"
93msgstr "Importation des informations liées"
94
95#. module: account_statement_base_import
96#: help:credit.statement.import,force_partner_on_bank:0
97msgid ""
98"Tic that box if you want to use the credit insitute "
99"partner in the "
100"counterpart of the treasury/banking move."
101msgstr ""
102"Cochez cette case si vous voulez utiliser comme partenaire "
103"l'organisme bancaire au niveau de la ligne de banque"
104
105#. module: account_statement_base_import
106#: code:addons/account_statement_base_import/wizard/import_statement.py:93
107#, python-format
108msgid "Please use a file with an extention"
109msgstr "Veuillez sélectionner un fichier avec une extension"
110
111#. module: account_statement_base_import
112#: code:addons/account_statement_base_import/statement.py:161
113#, python-format
114msgid "You must provide a valid profile to import a bank statement !"
115msgstr "Vous devez fournir un profil valide pour importer un relevé !"
116
117#. module: account_statement_base_import
118#: help:credit.statement.import,balance_check:0
119msgid ""
120"Tic that box if you want OpenERP to control the start/end "
121"balance before confirming "
122"a bank statement. If don't ticked, no balance control will be done."
123msgstr ""
124"Cochez cette case si vous souhaitez un contrôle du solde final "
125"avant la validation du relevé."
126
127#. module: account_statement_base_import
128#: code:addons/account_statement_base_import/parser/file_parser.py:31
129#: code:addons/account_statement_base_import/parser/generic_file_parser.py:31
130#, python-format
131msgid "Please install python lib xlrd"
132msgstr "Veuillez installer la bibliothèque python xlrd"
133
134#. module: account_statement_base_import
135#: code:addons/account_statement_base_import/parser/file_parser.py:58
136#, python-format
137msgid "Invalide file type %s. please use csv or xls"
138msgstr "Type de fichier invalide : %s. Veuillez utiliser un csv ou un xls"
139
140#. module: account_statement_base_import
141#: field:account.statement.profile,last_import_date:0
142msgid "Last Import Date"
143msgstr "Date de dernier import"
144
145#. module: account_statement_base_import
146#: field:credit.statement.import,commission_analytic_id:0
147msgid "Commission analytic account"
148msgstr "Compte analytique de la commission"
149
150#. module: account_statement_base_import
151#: constraint:account.statement.profile:0
152msgid "You need to put a partner if you tic the 'Force partner on bank move' !"
153msgstr ""
154"Vous devez spécifier un partenaire si vous avez coché 'Forcer un partenaire sur la ligne de la banque' !"
155
156#. module: account_statement_base_import
157#: code:addons/account_statement_base_import/statement.py:217
158#, python-format
159msgid "Statement import error"
160msgstr "Erreur d'import de relevé"
161
162#. module: account_statement_base_import
163#: field:account.bank.statement.line,commission_amount:0
164msgid "Line Commission Amount"
165msgstr "Montant de la commission de la ligne"
166
167#. module: account_statement_base_import
168#: code:addons/account_statement_base_import/statement.py:171
169#, python-format
170msgid "Missing column !"
171msgstr "Colonne manquante !"
172
173#. module: account_statement_base_import
174#: field:account.statement.profile,rec_log:0
175msgid "log"
176msgstr "journal"
177
178#. module: account_statement_base_import
179#: field:account.statement.profile,import_type:0
180msgid "Type of import"
181msgstr "Type d'import"
182
183#. module: account_statement_base_import
184#: constraint:account.bank.statement.line:0
185msgid ""
186"The amount of the voucher must be the same amount as the one on the "
187"statement line"
188msgstr ""
189"Le montant du justificatif doit être identique à celui de la ligne le "
190"concernant sur le relevé"
191
192#. module: account_statement_base_import
193#: help:account.statement.profile,launch_import_completion:0
194msgid ""
195"Tic that box to automatically launch the completion on each "
196"imported file using this profile."
197msgstr ""
198"Cocher cette case pour lancer automatiquement l'auto-complétion sur"
199"chaque fichier importé avec ce profil."
200
201#. module: account_statement_base_import
202#: field:credit.statement.import,profile_id:0
203msgid "Import configuration parameter"
204msgstr "Paramètres de configuration d'import"
205
206#. module: account_statement_base_import
207#: code:addons/account_statement_base_import/parser/parser.py:156
208#, python-format
209msgid "No buffer file given."
210msgstr "Pas de fichier tampon donné."
211
212#. module: account_statement_base_import
213#: view:credit.statement.import:0
214msgid "Import Parameters Summary"
215msgstr "Résumé des paramètres d'import"
216
217#. module: account_statement_base_import
218#: field:credit.statement.import,balance_check:0
219msgid "Balance check"
220msgstr "Vérification des soldes"
221
222#. module: account_statement_base_import
223#: model:ir.model,name:account_statement_base_import.model_account_bank_statement_line
224msgid "Bank Statement Line"
225msgstr "Ligne de relevé bancaire"
226
227#. module: account_statement_base_import
228#: field:credit.statement.import,force_partner_on_bank:0
229msgid "Force partner on bank move"
230msgstr "Forcer un partenaire sur la ligne du compte de banque"
231
232#. module: account_statement_base_import
233#: field:credit.statement.import,file_name:0
234msgid "File Name"
235msgstr "Nom du fichier"
236
237#. module: account_statement_base_import
238#: field:credit.statement.import,receivable_account_id:0
239msgid "Force Receivable/Payable Account"
240msgstr "Forcer le compte Client/Fournisseur"
241
242#. module: account_statement_base_import
243#: help:account.statement.profile,import_type:0
244msgid ""
245"Choose here the method by which you want to import bank statement for this "
246"profile."
247msgstr ""
248"Choisissez la méthode d'import de relevé pour ce profil."
249
250#. module: account_statement_base_import
251#: view:credit.statement.import:0
252msgid "Cancel"
253msgstr "Annulation"
0254
=== added directory 'account_statement_base_import/parser'
=== added file 'account_statement_base_import/parser/__init__.py'
--- account_statement_base_import/parser/__init__.py 1970-01-01 00:00:00 +0000
+++ account_statement_base_import/parser/__init__.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,25 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Nicolas Bessi, Joel Grand-Guillaume
5# Copyright 2011-2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22from parser import new_bank_statement_parser
23from parser import BankStatementImportParser
24import file_parser
25import generic_file_parser
026
=== added file 'account_statement_base_import/parser/file_parser.py'
--- account_statement_base_import/parser/file_parser.py 1970-01-01 00:00:00 +0000
+++ account_statement_base_import/parser/file_parser.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,218 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright Camptocamp SA
5# Author Nicolas Bessi, Joel Grand-Guillaume
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19##############################################################################
20from openerp.tools.translate import _
21from openerp.osv.osv import except_osv
22import tempfile
23import datetime
24from parser import BankStatementImportParser
25from parser import UnicodeDictReader
26try:
27 import xlrd
28except:
29 raise Exception(_('Please install python lib xlrd'))
30
31
32class FileParser(BankStatementImportParser):
33 """
34 Generic abstract class for defining parser for .csv or .xls file format.
35 """
36
37 def __init__(self, parse_name, keys_to_validate=None, ftype='csv', conversion_dict=None,
38 header=None, *args, **kwargs):
39 """
40 :param char: parse_name : The name of the parser
41 :param list: keys_to_validate : contain the key that need to be present in the file
42 :param char ftype: extension of the file (could be csv or xls)
43 :param: conversion_dict : keys and type to convert of every column in the file like
44 {
45 'ref': unicode,
46 'label': unicode,
47 'date': datetime.datetime,
48 'amount': float,
49 'commission_amount': float
50 }
51 :param list: header : specify header fields if the csv file has no header
52 """
53
54 super(FileParser, self).__init__(parse_name, *args, **kwargs)
55 if ftype in ('csv', 'xls'):
56 self.ftype = ftype
57 else:
58 raise except_osv(_('User Error'),
59 _('Invalid file type %s. Please use csv or xls') % ftype)
60 self.keys_to_validate = keys_to_validate if keys_to_validate is not None else []
61 self.conversion_dict = conversion_dict
62 self.fieldnames = header
63 self._datemode = 0 # used only for xls documents,
64 # 0 means Windows mode (1900 based dates).
65 # Set in _parse_xls, from the contents of the file
66
67 def _custom_format(self, *args, **kwargs):
68 """
69 No other work on data are needed in this parser.
70 """
71 return True
72
73 def _pre(self, *args, **kwargs):
74 """
75 No pre-treatment needed for this parser.
76 """
77 return True
78
79 def _parse(self, *args, **kwargs):
80 """
81 Launch the parsing through .csv or .xls depending on the
82 given ftype
83 """
84
85 res = None
86 if self.ftype == 'csv':
87 res = self._parse_csv()
88 else:
89 res = self._parse_xls()
90 self.result_row_list = res
91 return True
92
93 def _validate(self, *args, **kwargs):
94 """
95 We check that all the key of the given file (means header) are present
96 in the validation key provided. Otherwise, we raise an Exception.
97 We skip the validation step if the file header is provided separately
98 (in the field: fieldnames).
99 """
100 if self.fieldnames is None:
101 parsed_cols = self.result_row_list[0].keys()
102 for col in self.keys_to_validate:
103 if col not in parsed_cols:
104 raise except_osv(_('Invalid data'),
105 _('Column %s not present in file') % col)
106 return True
107
108 def _post(self, *args, **kwargs):
109 """
110 Cast row type depending on the file format .csv or .xls after parsing the file.
111 """
112 self.result_row_list = self._cast_rows(*args, **kwargs)
113 return True
114
115 def _parse_csv(self):
116 """
117 :return: list of dict from csv file (line/rows)
118 """
119 csv_file = tempfile.NamedTemporaryFile()
120 csv_file.write(self.filebuffer)
121 csv_file.flush()
122 with open(csv_file.name, 'rU') as fobj:
123 reader = UnicodeDictReader(fobj, fieldnames=self.fieldnames)
124 return list(reader)
125
126 def _parse_xls(self):
127 """
128 :return: dict of dict from xls file (line/rows)
129 """
130 wb_file = tempfile.NamedTemporaryFile()
131 wb_file.write(self.filebuffer)
132 # We ensure that cursor is at beginig of file
133 wb_file.seek(0)
134 with xlrd.open_workbook(wb_file.name) as wb:
135 self._datemode = wb.datemode
136 sheet = wb.sheet_by_index(0)
137 header = sheet.row_values(0)
138 res = []
139 for rownum in range(1, sheet.nrows):
140 res.append(dict(zip(header, sheet.row_values(rownum))))
141 return res
142
143 def _from_csv(self, result_set, conversion_rules):
144 """
145 Handle the converstion from the dict and handle date format from
146 an .csv file.
147 """
148 for line in result_set:
149 for rule in conversion_rules:
150 if conversion_rules[rule] == datetime.datetime:
151 try:
152 date_string = line[rule].split(' ')[0]
153 line[rule] = datetime.datetime.strptime(date_string,
154 '%Y-%m-%d')
155 except ValueError as err:
156 raise except_osv(_("Date format is not valid."),
157 _(" It should be YYYY-MM-DD for column: %s"
158 " value: %s \n \n"
159 " \n Please check the line with ref: %s"
160 " \n \n Detail: %s") % (rule,
161 line.get(rule, _('Missing')),
162 line.get('ref', line),
163 repr(err)))
164 else:
165 try:
166 line[rule] = conversion_rules[rule](line[rule])
167 except Exception as err:
168 raise except_osv(_('Invalid data'),
169 _("Value %s of column %s is not valid."
170 "\n Please check the line with ref %s:"
171 "\n \n Detail: %s") % (line.get(rule, _('Missing')),
172 rule,
173 line.get('ref', line),
174 repr(err)))
175 return result_set
176
177 def _from_xls(self, result_set, conversion_rules):
178 """
179 Handle the converstion from the dict and handle date format from
180 an .xls file.
181 """
182 for line in result_set:
183 for rule in conversion_rules:
184 if conversion_rules[rule] == datetime.datetime:
185 try:
186 t_tuple = xlrd.xldate_as_tuple(line[rule], self._datemode)
187 line[rule] = datetime.datetime(*t_tuple)
188 except Exception as err:
189 raise except_osv(_("Date format is not valid"),
190 _("Please modify the cell formatting to date format"
191 " for column: %s"
192 " value: %s"
193 "\n Please check the line with ref: %s"
194 "\n \n Detail: %s") % (rule,
195 line.get(rule, _('Missing')),
196 line.get('ref', line),
197 repr(err)))
198 else:
199 try:
200 line[rule] = conversion_rules[rule](line[rule])
201 except Exception as err:
202 raise except_osv(_('Invalid data'),
203 _("Value %s of column %s is not valid."
204 "\n Please check the line with ref %s:"
205 "\n \n Detail: %s") % (line.get(rule, _('Missing')),
206 rule,
207 line.get('ref', line),
208 repr(err)))
209 return result_set
210
211 def _cast_rows(self, *args, **kwargs):
212 """
213 Convert the self.result_row_list using the self.conversion_dict providen.
214 We call here _from_xls or _from_csv depending on the self.ftype variable.
215 """
216 func = getattr(self, '_from_%s' % self.ftype)
217 res = func(self.result_row_list, self.conversion_dict)
218 return res
0219
=== added file 'account_statement_base_import/parser/generic_file_parser.py'
--- account_statement_base_import/parser/generic_file_parser.py 1970-01-01 00:00:00 +0000
+++ account_statement_base_import/parser/generic_file_parser.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,102 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright Camptocamp SA
5# Author Joel Grand-Guillaume
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19##############################################################################
20
21from openerp.tools.translate import _
22import base64
23import csv
24import tempfile
25import datetime
26from file_parser import FileParser
27try:
28 import xlrd
29except:
30 raise Exception(_('Please install python lib xlrd'))
31
32
33def float_or_zero(val):
34 """ Conversion function used to manage
35 empty string into float usecase"""
36 return float(val) if val else 0.0
37
38
39class GenericFileParser(FileParser):
40 """
41 Standard parser that use a define format in csv or xls to import into a
42 bank statement. This is mostely an example of how to proceed to create a new
43 parser, but will also be useful as it allow to import a basic flat file.
44 """
45
46 def __init__(self, parse_name, ftype='csv'):
47 conversion_dict = {
48 'ref': unicode,
49 'label': unicode,
50 'date': datetime.datetime,
51 'amount': float_or_zero,
52 'commission_amount': float_or_zero
53 }
54 # Order of cols does not matter but first row of the file has to be header
55 keys_to_validate = ['ref', 'label', 'date', 'amount', 'commission_amount']
56 super(GenericFileParser, self).__init__(parse_name, keys_to_validate=keys_to_validate, ftype=ftype, conversion_dict=conversion_dict)
57
58 @classmethod
59 def parser_for(cls, parser_name):
60 """
61 Used by the new_bank_statement_parser class factory. Return true if
62 the providen name is generic_csvxls_so
63 """
64 return parser_name == 'generic_csvxls_so'
65
66 def get_st_line_vals(self, line, *args, **kwargs):
67 """
68 This method must return a dict of vals that can be passed to create
69 method of statement line in order to record it. It is the responsibility
70 of every parser to give this dict of vals, so each one can implement his
71 own way of recording the lines.
72 :param: line: a dict of vals that represent a line of result_row_list
73 :return: dict of values to give to the create method of statement line,
74 it MUST contain at least:
75 {
76 'name':value,
77 'date':value,
78 'amount':value,
79 'ref':value,
80 'label':value,
81 'commission_amount':value,
82 }
83 In this generic parser, the commission is given for every line, so we store it
84 for each one.
85 """
86 return {'name': line.get('label', line.get('ref', '/')),
87 'date': line.get('date', datetime.datetime.now().date()),
88 'amount': line.get('amount', 0.0),
89 'ref': line.get('ref', '/'),
90 'label': line.get('label', ''),
91 'commission_amount': line.get('commission_amount', 0.0)}
92
93 def _post(self, *args, **kwargs):
94 """
95 Compute the commission from value of each line
96 """
97 res = super(GenericFileParser, self)._post(*args, **kwargs)
98 val = 0.0
99 for row in self.result_row_list:
100 val += row.get('commission_amount', 0.0)
101 self.commission_global_amount = val
102 return res
0103
=== added file 'account_statement_base_import/parser/parser.py'
--- account_statement_base_import/parser/parser.py 1970-01-01 00:00:00 +0000
+++ account_statement_base_import/parser/parser.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,219 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Joel Grand-Guillaume
5# Copyright 2011-2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21import base64
22import csv
23
24
25def UnicodeDictReader(utf8_data, **kwargs):
26 sniffer = csv.Sniffer()
27 pos = utf8_data.tell()
28 sample_data = utf8_data.read(1024)
29 utf8_data.seek(pos)
30 dialect = sniffer.sniff(sample_data, delimiters=',;\t')
31 csv_reader = csv.DictReader(utf8_data, dialect=dialect, **kwargs)
32 for row in csv_reader:
33 yield dict([(key, unicode(value, 'utf-8')) for key, value in row.iteritems()])
34
35
36class BankStatementImportParser(object):
37 """
38 Generic abstract class for defining parser for different files and
39 format to import in a bank statement. Inherit from it to create your
40 own. If your file is a .csv or .xls format, you should consider inheirt
41 from the FileParser instead.
42 """
43
44 def __init__(self, parser_name, *args, **kwargs):
45 # The name of the parser as it will be called
46 self.parser_name = parser_name
47 # The result as a list of row. One row per line of data in the file, but
48 # not the commission one !
49 self.result_row_list = None
50 # The file buffer on which to work on
51 self.filebuffer = None
52 # Concatenate here the global commission taken by the bank/office
53 # for this statement.
54 self.commission_global_amount = None
55
56 @classmethod
57 def parser_for(cls, parser_name):
58 """
59 Override this method for every new parser, so that new_bank_statement_parser can
60 return the good class from his name.
61 """
62 return False
63
64 def _decode_64b_stream(self):
65 """
66 Decode self.filebuffer in base 64 and override it
67 """
68 self.filebuffer = base64.b64decode(self.filebuffer)
69 return True
70
71 def _format(self, decode_base_64=True, **kwargs):
72 """
73 Decode into base 64 if asked and Format the given filebuffer by calling
74 _custom_format method.
75 """
76 if decode_base_64:
77 self._decode_64b_stream()
78 self._custom_format(kwargs)
79 return True
80
81 def _custom_format(self, *args, **kwargs):
82 """
83 Implement a method in your parser to convert format, encoding and so on before
84 starting to work on datas. Work on self.filebuffer
85 """
86 return NotImplementedError
87
88 def _pre(self, *args, **kwargs):
89 """
90 Implement a method in your parser to make a pre-treatment on datas before parsing
91 them, like concatenate stuff, and so... Work on self.filebuffer
92 """
93 return NotImplementedError
94
95 def _parse(self, *args, **kwargs):
96 """
97 Implement a method in your parser to save the result of parsing self.filebuffer
98 in self.result_row_list instance property.
99 """
100 return NotImplementedError
101
102 def _validate(self, *args, **kwargs):
103 """
104 Implement a method in your parser to validate the self.result_row_list instance
105 property and raise an error if not valid.
106 """
107 return NotImplementedError
108
109 def _post(self, *args, **kwargs):
110 """
111 Implement a method in your parser to make some last changes on the result of parsing
112 the datas, like converting dates, computing commission, ...
113 Work on self.result_row_list and put the commission global amount if any
114 in the self.commission_global_amount one.
115 """
116 return NotImplementedError
117
118 def get_st_line_vals(self, line, *args, **kwargs):
119 """
120 Implement a method in your parser that must return a dict of vals that can be
121 passed to create method of statement line in order to record it. It is the responsibility
122 of every parser to give this dict of vals, so each one can implement his
123 own way of recording the lines.
124 :param: line: a dict of vals that represent a line of result_row_list
125 :return: dict of values to give to the create method of statement line,
126 it MUST contain at least:
127 {
128 'name':value,
129 'date':value,
130 'amount':value,
131 'ref':value,
132 }
133 """
134 return NotImplementedError
135
136 def get_st_line_commision(self, *args, **kwargs):
137 """
138 This is called by the importation method to create the commission line in
139 the bank statement. We will always create one line for the commission in the
140 bank statement, but it could be computated from a value of each line, or given
141 in a single line for the whole file.
142 return: float of the whole commission (self.commission_global_amount)
143 """
144 return self.commission_global_amount
145
146 def parse(self, filebuffer, *args, **kwargs):
147 """
148 This will be the method that will be called by wizard, button and so
149 to parse a filebuffer by calling successively all the private method
150 that need to be define for each parser.
151 Return:
152 [] of rows as {'key':value}
153
154 Note: The row_list must contain only value that are present in the account.
155 bank.statement.line object !!!
156 """
157 if filebuffer:
158 self.filebuffer = filebuffer
159 else:
160 raise Exception(_('No buffer file given.'))
161 self._format(*args, **kwargs)
162 self._pre(*args, **kwargs)
163 self._parse(*args, **kwargs)
164 self._validate(*args, **kwargs)
165 self._post(*args, **kwargs)
166 return self.result_row_list
167
168
169def itersubclasses(cls, _seen=None):
170 """
171 itersubclasses(cls)
172
173 Generator over all subclasses of a given class, in depth first order.
174
175 >>> list(itersubclasses(int)) == [bool]
176 True
177 >>> class A(object): pass
178 >>> class B(A): pass
179 >>> class C(A): pass
180 >>> class D(B,C): pass
181 >>> class E(D): pass
182 >>>
183 >>> for cls in itersubclasses(A):
184 ... print(cls.__name__)
185 B
186 D
187 E
188 C
189 >>> # get ALL (new-style) classes currently defined
190 >>> [cls.__name__ for cls in itersubclasses(object)] #doctest: +ELLIPSIS
191 ['type', ...'tuple', ...]
192 """
193 if not isinstance(cls, type):
194 raise TypeError('itersubclasses must be called with '
195 'new-style classes, not %.100r' % cls)
196 if _seen is None:
197 _seen = set()
198 try:
199 subs = cls.__subclasses__()
200 except TypeError: # fails only when cls is type
201 subs = cls.__subclasses__(cls)
202 for sub in subs:
203 if sub not in _seen:
204 _seen.add(sub)
205 yield sub
206 for sub in itersubclasses(sub, _seen):
207 yield sub
208
209
210def new_bank_statement_parser(parser_name, *args, **kwargs):
211 """
212 Return an instance of the good parser class base on the providen name
213 :param char: parser_name
214 :return: class instance of parser_name providen.
215 """
216 for cls in itersubclasses(BankStatementImportParser):
217 if cls.parser_for(parser_name):
218 return cls(parser_name, *args, **kwargs)
219 raise ValueError
0220
=== added file 'account_statement_base_import/statement.py'
--- account_statement_base_import/statement.py 1970-01-01 00:00:00 +0000
+++ account_statement_base_import/statement.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,303 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Joel Grand-Guillaume
5# Copyright 2011-2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21import sys
22import traceback
23
24import psycopg2
25
26from openerp.tools.translate import _
27import datetime
28from openerp.osv.orm import Model
29from openerp.osv import fields, osv
30from parser import new_bank_statement_parser
31
32
33class AccountStatementProfil(Model):
34 _inherit = "account.statement.profile"
35
36 def get_import_type_selection(self, cr, uid, context=None):
37 """
38 Has to be inherited to add parser
39 """
40 return [('generic_csvxls_so', 'Generic .csv/.xls based on SO Name')]
41
42 _columns = {
43 'launch_import_completion': fields.boolean(
44 "Launch completion after import",
45 help="Tic that box to automatically launch the completion "
46 "on each imported file using this profile."),
47 'last_import_date': fields.datetime("Last Import Date"),
48 # we remove deprecated as it floods logs in standard/warning level sob...
49 'rec_log': fields.text('log', readonly=True), # Deprecated
50 'import_type': fields.selection(
51 get_import_type_selection,
52 'Type of import',
53 required=True,
54 help="Choose here the method by which you want to import bank"
55 "statement for this profile."),
56
57 }
58
59 def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines, context):
60 """
61 Write the log in the logger
62
63 :param int/long statement_id: ID of the concerned account.bank.statement
64 :param int/long num_lines: Number of line that have been parsed
65 :return: True
66 """
67 self.message_post(cr,
68 uid,
69 ids,
70 body=_('Statement ID %s have been imported with %s lines.') %
71 (statement_id, num_lines),
72 context=context)
73 return True
74
75 def prepare_global_commission_line_vals(
76 self, cr, uid, parser, result_row_list, profile, statement_id, context):
77 """
78 Prepare the global commission line if there is one. The global
79 commission is computed by by calling the get_st_line_commision
80 of the parser. Feel free to override the method to compute
81 your own commission line from the result_row_list.
82
83 :param: browse_record of the current parser
84 :param: result_row_list: [{'key':value}]
85 :param: profile: browserecord of account.statement.profile
86 :param: statement_id: int/long of the current importing statement ID
87 :param: context: global context
88 return: dict of vals that will be passed to create method of statement line.
89 """
90 comm_values = False
91 if parser.get_st_line_commision():
92 partner_id = profile.partner_id and profile.partner_id.id or False
93 commission_account_id = profile.commission_account_id and profile.commission_account_id.id or False
94 commission_analytic_id = profile.commission_analytic_id and profile.commission_analytic_id.id or False
95 comm_values = {
96 'name': 'IN ' + _('Commission line'),
97 'date': datetime.datetime.now().date(),
98 'amount': parser.get_st_line_commision(),
99 'partner_id': partner_id,
100 'type': 'general',
101 'statement_id': statement_id,
102 'account_id': commission_account_id,
103 'ref': 'commission',
104 'analytic_account_id': commission_analytic_id,
105 # !! We set the already_completed so auto-completion will not update those values!
106 'already_completed': True,
107 }
108 return comm_values
109
110 def prepare_statetement_lines_vals(
111 self, cr, uid, parser_vals, account_payable, account_receivable,
112 statement_id, context):
113 """
114 Hook to build the values of a line from the parser returned values. At
115 least it fullfill the statement_id and account_id. Overide it to add your
116 own completion if needed.
117
118 :param dict of vals from parser for account.bank.statement.line (called by
119 parser.get_st_line_vals)
120 :param int/long account_payable: ID of the receivable account to use
121 :param int/long account_receivable: ID of the payable account to use
122 :param int/long statement_id: ID of the concerned account.bank.statement
123 :return: dict of vals that will be passed to create method of statement line.
124 """
125 statement_obj = self.pool.get('account.bank.statement')
126 values = parser_vals
127 values['statement_id'] = statement_id
128 values['account_id'] = statement_obj.get_account_for_counterpart(cr,
129 uid,
130 parser_vals['amount'],
131 account_receivable,
132 account_payable)
133
134 date = values.get('date')
135 period_memoizer = context.get('period_memoizer')
136 if not period_memoizer:
137 period_memoizer = {}
138 context['period_memoizer'] = period_memoizer
139 if period_memoizer.get(date):
140 values['period_id'] = period_memoizer[date]
141 else:
142 # This is awfully slow...
143 periods = self.pool.get('account.period').find(cr, uid,
144 dt=values.get('date'),
145 context=context)
146 values['period_id'] = periods[0]
147 period_memoizer[date] = periods[0]
148 values['type'] = 'general'
149 return values
150
151 def statement_import(self, cr, uid, ids, profile_id, file_stream, ftype="csv", context=None):
152 """
153 Create a bank statement with the given profile and parser. It will fullfill the bank statement
154 with the values of the file providen, but will not complete data (like finding the partner, or
155 the right account). This will be done in a second step with the completion rules.
156 It will also create the commission line if it apply and record the providen file as
157 an attachement of the bank statement.
158
159 :param int/long profile_id: ID of the profile used to import the file
160 :param filebuffer file_stream: binary of the providen file
161 :param char: ftype represent the file exstension (csv by default)
162 :return: ID of the created account.bank.statemênt
163 """
164 statement_obj = self.pool.get('account.bank.statement')
165 statement_line_obj = self.pool.get('account.bank.statement.line')
166 attachment_obj = self.pool.get('ir.attachment')
167 prof_obj = self.pool.get("account.statement.profile")
168 if not profile_id:
169 raise osv.except_osv(_("No Profile!"),
170 _("You must provide a valid profile to import a bank statement!"))
171 prof = prof_obj.browse(cr, uid, profile_id, context=context)
172
173 parser = new_bank_statement_parser(prof.import_type, ftype=ftype)
174 result_row_list = parser.parse(file_stream)
175 # Check all key are present in account.bank.statement.line!!
176 if not result_row_list:
177 raise osv.except_osv(_("Nothing to import"),
178 _("The file is empty"))
179 parsed_cols = parser.get_st_line_vals(result_row_list[0]).keys()
180 for col in parsed_cols:
181 if col not in statement_line_obj._columns:
182 raise osv.except_osv(_("Missing column!"),
183 _("Column %s you try to import is not "
184 "present in the bank statement line!") % col)
185
186 statement_id = statement_obj.create(cr, uid,
187 {'profile_id': prof.id},
188 context=context)
189 if prof.receivable_account_id:
190 account_receivable = account_payable = prof.receivable_account_id.id
191 else:
192 account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts(
193 cr, uid, context)
194 try:
195 # Record every line in the bank statement and compute the global commission
196 # based on the commission_amount column
197 statement_store = []
198 for line in result_row_list:
199 parser_vals = parser.get_st_line_vals(line)
200 values = self.prepare_statetement_lines_vals(cr, uid, parser_vals, account_payable,
201 account_receivable, statement_id, context)
202 statement_store.append(values)
203 # Hack to bypass ORM poor perfomance. Sob...
204 statement_line_obj._insert_lines(cr, uid, statement_store, context=context)
205
206 # Build and create the global commission line for the whole statement
207 comm_vals = self.prepare_global_commission_line_vals(cr, uid, parser, result_row_list,
208 prof, statement_id, context)
209 if comm_vals:
210 statement_line_obj.create(cr, uid, comm_vals, context=context)
211 else:
212 # Trigger store field computation if someone has better idea
213 start_bal = statement_obj.read(cr, uid, statement_id,
214 ['balance_start'],
215 context=context)
216 start_bal = start_bal['balance_start']
217 statement_obj.write(cr, uid, [statement_id],
218 {'balance_start': start_bal})
219
220 attachment_obj.create(cr,
221 uid,
222 {'name': 'statement file',
223 'datas': file_stream,
224 'datas_fname': "%s.%s" % (
225 datetime.datetime.now().date(),
226 ftype),
227 'res_model': 'account.bank.statement',
228 'res_id': statement_id},
229 context=context)
230
231 # If user ask to launch completion at end of import, do it!
232 if prof.launch_import_completion:
233 statement_obj.button_auto_completion(cr, uid, [statement_id], context)
234
235 # Write the needed log infos on profile
236 self.write_logs_after_import(cr, uid, prof.id,
237 statement_id,
238 len(result_row_list),
239 context)
240
241 except Exception:
242 statement_obj.unlink(cr, uid, [statement_id], context=context)
243 error_type, error_value, trbk = sys.exc_info()
244 st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
245 st += ''.join(traceback.format_tb(trbk, 30))
246 raise osv.except_osv(_("Statement import error"),
247 _("The statement cannot be created: %s") % st)
248 return statement_id
249
250
251class AccountStatementLine(Model):
252 """
253 Add sparse field on the statement line to allow to store all the
254 bank infos that are given by an office. In this basic sample case
255 it concern only commission_amount.
256 """
257 _inherit = "account.bank.statement.line"
258
259 def _get_available_columns(self, statement_store):
260 """Return writeable by SQL columns"""
261 statement_line_obj = self.pool['account.bank.statement.line']
262 model_cols = statement_line_obj._columns
263 avail = [k for k, col in model_cols.iteritems() if not hasattr(col, '_fnct')]
264 keys = [k for k in statement_store[0].keys() if k in avail]
265 keys.sort()
266 return keys
267
268 def _insert_lines(self, cr, uid, statement_store, context=None):
269 """ Do raw insert into database because ORM is awfully slow
270 when doing batch write. It is a shame that batch function
271 does not exist"""
272 statement_line_obj = self.pool['account.bank.statement.line']
273 statement_line_obj.check_access_rule(cr, uid, [], 'create')
274 statement_line_obj.check_access_rights(cr, uid, 'create', raise_exception=True)
275 cols = self._get_available_columns(statement_store)
276 tmp_vals = (', '.join(cols), ', '.join(['%%(%s)s' % i for i in cols]))
277 sql = "INSERT INTO account_bank_statement_line (%s) VALUES (%s);" % tmp_vals
278 try:
279 cr.executemany(sql, tuple(statement_store))
280 except psycopg2.Error as sql_err:
281 cr.rollback()
282 raise osv.except_osv(_("ORM bypass error"),
283 sql_err.pgerror)
284
285 def _update_line(self, cr, uid, vals, context=None):
286 """ Do raw update into database because ORM is awfully slow
287 when cheking security."""
288 cols = self._get_available_columns([vals])
289 tmp_vals = (', '.join(['%s = %%(%s)s' % (i, i) for i in cols]))
290 sql = "UPDATE account_bank_statement_line SET %s where id = %%(id)s;" % tmp_vals
291 try:
292 cr.execute(sql, vals)
293 except psycopg2.Error as sql_err:
294 cr.rollback()
295 raise osv.except_osv(_("ORM bypass error"),
296 sql_err.pgerror)
297
298 _columns = {
299 'commission_amount': fields.sparse(
300 type='float',
301 string='Line Commission Amount',
302 serialization_field='additionnal_bank_fields'),
303 }
0304
=== added file 'account_statement_base_import/statement_view.xml'
--- account_statement_base_import/statement_view.xml 1970-01-01 00:00:00 +0000
+++ account_statement_base_import/statement_view.xml 2013-06-10 06:07:03 +0000
@@ -0,0 +1,46 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3<data>
4
5
6 <record id="statement_importer_view_form" model="ir.ui.view">
7 <field name="name">account.statement.profile.view</field>
8 <field name="model">account.statement.profile</field>
9 <field name="inherit_id" ref="account_statement_ext.statement_importer_view_form"/>
10 <field name="type">form</field>
11 <field name="arch" type="xml">
12 <field name="bank_statement_prefix" position="after">
13 <separator colspan="4" string="Import related infos"/>
14 <field name="launch_import_completion"/>
15 <field name="last_import_date"/>
16 <field name="import_type"/>
17 <button name="%(account_statement_base_import.statement_importer_action)d"
18 string="Import Bank Statement"
19 type="action" icon="gtk-ok"
20 colspan = "2"/>
21 <group attrs="{'invisible': [('rec_log', '=', False)]}">
22 <separator colspan="4" string="Historical Import Logs"/>
23 <field name="rec_log" colspan="4" nolabel="1" />
24 </group>
25 </field>
26 </field>
27 </record>
28
29 <record id="bank_statement_view_form" model="ir.ui.view">
30 <field name="name">account_bank_statement.bank_statement.view_form</field>
31 <field name="model">account.bank.statement</field>
32 <field name="inherit_id" ref="account_statement_base_completion.bank_statement_view_form" />
33 <field name="type">form</field>
34 <field eval="20" name="priority"/>
35 <field name="arch" type="xml">
36 <data>
37 <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/form/group/field[@name='label']" position="after">
38 <field name="commission_amount" />
39 </xpath>
40 </data>
41 </field>
42 </record>
43
44
45</data>
46</openerp>
047
=== added directory 'account_statement_base_import/wizard'
=== added file 'account_statement_base_import/wizard/__init__.py'
--- account_statement_base_import/wizard/__init__.py 1970-01-01 00:00:00 +0000
+++ account_statement_base_import/wizard/__init__.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,20 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author Nicolas Bessi, Joel Grand-Guillaume. Copyright Camptocamp SA
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19##############################################################################
20import import_statement
021
=== added file 'account_statement_base_import/wizard/import_statement.py'
--- account_statement_base_import/wizard/import_statement.py 1970-01-01 00:00:00 +0000
+++ account_statement_base_import/wizard/import_statement.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,122 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Nicolas Bessi, Joel Grand-Guillaume
5# Copyright 2011-2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22"""
23Wizard to import financial institute date in bank statement
24"""
25
26from openerp.osv import orm, fields
27
28from openerp.tools.translate import _
29import os
30
31
32class CreditPartnerStatementImporter(orm.TransientModel):
33 _name = "credit.statement.import"
34
35 def default_get(self, cr, uid, fields, context=None):
36 if context is None:
37 context = {}
38 res = {}
39 if (context.get('active_model', False) == 'account.statement.profile' and
40 context.get('active_ids', False)):
41 ids = context['active_ids']
42 assert len(ids) == 1, 'You cannot use this on more than one profile !'
43 res['profile_id'] = ids[0]
44 other_vals = self.onchange_profile_id(cr, uid, [], res['profile_id'], context=context)
45 res.update(other_vals.get('value', {}))
46 return res
47
48 _columns = {
49 'profile_id': fields.many2one('account.statement.profile',
50 'Import configuration parameter',
51 required=True),
52 'input_statement': fields.binary('Statement file', required=True),
53 'partner_id': fields.many2one('res.partner',
54 'Credit insitute partner'),
55 'journal_id': fields.many2one('account.journal',
56 'Financial journal to use transaction'),
57 'file_name': fields.char('File Name', size=128),
58 'commission_account_id': fields.many2one('account.account',
59 'Commission account'),
60 'commission_analytic_id': fields.many2one('account.analytic.account',
61 'Commission analytic account'),
62 'receivable_account_id': fields.many2one('account.account',
63 'Force Receivable/Payable Account'),
64 'force_partner_on_bank': fields.boolean(
65 'Force partner on bank move',
66 help="Tic that box if you want to use the credit insitute partner "
67 "in the counterpart of the treasury/banking move."),
68 'balance_check': fields.boolean(
69 'Balance check',
70 help="Tic that box if you want OpenERP to control the "
71 "start/end balance before confirming a bank statement. "
72 "If don't ticked, no balance control will be done."),
73 }
74
75 def onchange_profile_id(self, cr, uid, ids, profile_id, context=None):
76 res = {}
77 if profile_id:
78 c = self.pool.get("account.statement.profile").browse(
79 cr, uid, profile_id, context=context)
80 res = {'value':
81 {'partner_id': c.partner_id and c.partner_id.id or False,
82 'journal_id': c.journal_id and c.journal_id.id or False,
83 'commission_account_id':
84 c.commission_account_id and c.commission_account_id.id or False,
85 'receivable_account_id': c.receivable_account_id and c.receivable_account_id.id or False,
86 'commission_a': c.commission_analytic_id and c.commission_analytic_id.id or False,
87 'force_partner_on_bank': c.force_partner_on_bank,
88 'balance_check': c.balance_check,
89 }
90 }
91 return res
92
93 def _check_extension(self, filename):
94 (__, ftype) = os.path.splitext(filename)
95 if not ftype:
96 #We do not use osv exception we do not want to have it logged
97 raise Exception(_('Please use a file with an extention'))
98 return ftype
99
100 def import_statement(self, cr, uid, req_id, context=None):
101 """This Function import credit card agency statement"""
102 context = context or {}
103 if isinstance(req_id, list):
104 req_id = req_id[0]
105 importer = self.browse(cr, uid, req_id, context)
106 ftype = self._check_extension(importer.file_name)
107 sid = self.pool.get(
108 'account.statement.profile').statement_import(
109 cr,
110 uid,
111 False,
112 importer.profile_id.id,
113 importer.input_statement,
114 ftype.replace('.', ''),
115 context=context
116 )
117 model_obj = self.pool.get('ir.model.data')
118 action_obj = self.pool.get('ir.actions.act_window')
119 action_id = model_obj.get_object_reference(cr, uid, 'account', 'action_bank_statement_tree')[1]
120 res = action_obj.read(cr, uid, action_id)
121 res['domain'] = res['domain'][:-1] + ",('id', '=', %d)]" % sid
122 return res
0123
=== added file 'account_statement_base_import/wizard/import_statement_view.xml'
--- account_statement_base_import/wizard/import_statement_view.xml 1970-01-01 00:00:00 +0000
+++ account_statement_base_import/wizard/import_statement_view.xml 2013-06-10 06:07:03 +0000
@@ -0,0 +1,44 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4 <record id="statement_importer_view" model="ir.ui.view">
5 <field name="name">credit.statement.import.config.view</field>
6 <field name="model">credit.statement.import</field>
7 <field name="type">form</field>
8 <field name="arch" type="xml">
9 <form string="Import statement">
10 <group colspan="4" >
11 <field name="profile_id" on_change="onchange_profile_id(profile_id)"/>
12 <field name="input_statement" filename="file_name" colspan="2"/>
13 <field name="file_name" colspan="2" invisible="1"/>
14 <separator string="Import Parameters Summary" colspan="4"/>
15 <field name="partner_id" readonly="1"/>
16 <field name="journal_id" readonly="1"/>
17 <field name="commission_account_id" readonly="1"/>
18 <field name="commission_analytic_id" readonly="1"/>
19 <field name="receivable_account_id" readonly="1"/>
20 <field name="force_partner_on_bank" readonly="1"/>
21 <field name="balance_check" readonly="1"/>
22 </group>
23 <separator string="" colspan="4"/>
24 <group colspan="4" col="6">
25 <button icon="gtk-cancel" special="cancel" string="Cancel"/>
26 <button icon="gtk-ok" name="import_statement" string="Import statement" type="object"/>
27 </group>
28 </form>
29 </field>
30 </record>
31
32 <record id="statement_importer_action" model="ir.actions.act_window">
33 <field name="name">Import statement</field>
34 <field name="res_model">credit.statement.import</field>
35 <field name="view_type">form</field>
36 <field name="view_mode">tree,form</field>
37 <field name="view_id" ref="statement_importer_view"/>
38 <field name="target">new</field>
39 </record>
40
41 <menuitem id="statement_importer_menu" name="Import Bank Statement" action="statement_importer_action" parent="account.menu_finance_bank_and_cash"/>
42
43 </data>
44</openerp>
045
=== added directory 'account_statement_completion_voucher'
=== added file 'account_statement_completion_voucher/__init__.py'
--- account_statement_completion_voucher/__init__.py 1970-01-01 00:00:00 +0000
+++ account_statement_completion_voucher/__init__.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,20 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Joel Grand-Guillaume
5# Copyright 2011-2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
021
=== added file 'account_statement_completion_voucher/__openerp__.py'
--- account_statement_completion_voucher/__openerp__.py 1970-01-01 00:00:00 +0000
+++ account_statement_completion_voucher/__openerp__.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,46 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Author: Joel Grand-Guillaume
5# Copyright 2011-2012 Camptocamp SA
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22{'name': "Bank statement extension with voucher",
23 'version': '1.0',
24 'author': 'Camptocamp',
25 'maintainer': 'Camptocamp',
26 'category': 'Finance',
27 'complexity': 'normal',
28 'depends': [
29 'account_statement_base_completion',
30 'account_voucher'
31 ],
32 'description': """
33 This module is only needed when using account_statement_base_completion with voucher in order adapt the view correctly.
34 """,
35 'website': 'http://www.camptocamp.com',
36 'init_xml': [],
37 'update_xml': [
38 "statement_view.xml",
39 ],
40 'demo_xml': [],
41 'test': [],
42 'installable': False,
43 'images': [],
44 'auto_install': False,
45 'license': 'AGPL-3',
46}
047
=== added file 'account_statement_completion_voucher/statement_view.xml'
--- account_statement_completion_voucher/statement_view.xml 1970-01-01 00:00:00 +0000
+++ account_statement_completion_voucher/statement_view.xml 2013-06-10 06:07:03 +0000
@@ -0,0 +1,21 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3<data>
4
5 <!-- Override what we have in account_statement_base_completion to replace the one in the voucher -->
6 <!-- <record id="account_statement_base_completion.bank_statement_view_form2" model="ir.ui.view">
7 <field name="name">account_bank_statement_import_base.bank_statement.auto_cmpl</field>
8 <field name="model">account.bank.statement</field>
9 <field name="inherit_id" ref="account_voucher.view_bank_statement_tree_voucher" />
10 <field name="type">form</field>
11 <field name="arch" type="xml">
12 <data>
13 <xpath expr="/form/sheet/notebook/page/field[@name='line_ids']/tree/field[@name='voucher_id']" position="after">
14 <field name="already_completed" />
15 </xpath>
16 </data>
17 </field>
18 </record> -->
19
20</data>
21</openerp>
022
=== added directory 'account_statement_ext'
=== added file 'account_statement_ext/__init__.py'
--- account_statement_ext/__init__.py 1970-01-01 00:00:00 +0000
+++ account_statement_ext/__init__.py 2013-06-10 06:07:03 +0000
@@ -0,0 +1,25 @@
1# -*- coding: utf-8 -*-
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches