Merge lp:~sebastien.beau/c2c-financial-addons/c2c-financial-addons-fix-rule-m2m into lp:c2c-financial-addons

Proposed by Sébastien BEAU - http://www.akretion.com
Status: Superseded
Proposed branch: lp:~sebastien.beau/c2c-financial-addons/c2c-financial-addons-fix-rule-m2m
Merge into: lp:c2c-financial-addons
Diff against target: 3107 lines (+2882/-2)
41 files modified
account_credit_management/__init__.py (+28/-0)
account_credit_management/__openerp__.py (+47/-0)
account_credit_management/credit_management_account.py (+220/-0)
account_credit_management/credit_management_account_view.xml (+41/-0)
account_credit_management/credit_management_company.py (+28/-0)
account_credit_management/credit_management_company_view.xml (+15/-0)
account_credit_management/credit_management_demo.xml (+33/-0)
account_credit_management/credit_management_line.py (+181/-0)
account_credit_management/credit_management_line_view.xml (+145/-0)
account_credit_management/credit_management_partner.py (+36/-0)
account_credit_management/credit_management_partner_view.xml (+25/-0)
account_credit_management/credit_management_profile.py (+292/-0)
account_credit_management/credit_management_profile_view.xml (+118/-0)
account_credit_management/credit_management_run.py (+150/-0)
account_credit_management/credit_management_run_view.xml (+65/-0)
account_credit_management/data.xml (+173/-0)
account_credit_management/report/__init__.py (+1/-0)
account_credit_management/report/credit_management_summary.html.mako (+19/-0)
account_credit_management/report/credit_management_summary.py (+37/-0)
account_credit_management/report/report.xml (+12/-0)
account_credit_management/scenarios/credit_management/00_credit_management_param.feature (+23/-0)
account_credit_management/scenarios/credit_management/01_credit_management_partners.feature (+60/-0)
account_credit_management/scenarios/credit_management/credit_management_01_data.feature (+224/-0)
account_credit_management/scenarios/credit_management/credit_management_02_run.feature (+33/-0)
account_credit_management/scenarios/credit_management/credit_management_03_run.feature (+26/-0)
account_credit_management/scenarios/credit_management/step_definitions/credit_management.rb (+115/-0)
account_credit_management/scenarios/run_command.txt (+1/-0)
account_credit_management/security/ir.model.access.csv (+27/-0)
account_credit_management/wizard/__init__.py (+24/-0)
account_credit_management/wizard/credit_management_communication.py (+178/-0)
account_credit_management/wizard/credit_management_mailer.py (+64/-0)
account_credit_management/wizard/credit_management_mailer_view.xml (+44/-0)
account_credit_management/wizard/credit_management_marker.py (+83/-0)
account_credit_management/wizard/credit_management_marker_view.xml (+44/-0)
account_credit_management/wizard/credit_management_printer.py (+74/-0)
account_credit_management/wizard/credit_management_printer_view.xml (+47/-0)
account_fiscal_position_rule_m2m/fiscal_rules.py (+2/-2)
account_payment_ext/__init__.py (+32/-0)
account_payment_ext/__openerp__.py (+50/-0)
account_payment_ext/account_payment.py (+47/-0)
account_payment_ext/account_payment_view.xml (+18/-0)
To merge this branch: bzr merge lp:~sebastien.beau/c2c-financial-addons/c2c-financial-addons-fix-rule-m2m
Reviewer Review Type Date Requested Status
Guewen Baconnier @ Camptocamp Approve
Alexandre Fayolle - camptocamp Needs Fixing
Review via email: mp+129642@code.launchpad.net

This proposal has been superseded by a proposal from 2012-10-23.

Description of the change

FIX a bug

To post a comment you must log in.
Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote :

The merge is huge for "a bug". Can you check you requested the merge on the right branch + describe the bug you're trying to fix (or link to a bug on the tracker)?

Thanks

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

Hi Sébastien,

Please create your merge proposal into lp:c2c-financial-addons/6.1

Thanks

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

Hi Sébastien,

I merged your fix (slightly modified) from the revision 90 in our 6.1 branch.

Thanks

Guewen

review: Approve

Unmerged revisions

90. By Sébastien BEAU - http://www.akretion.com

[FIX] account_fiscal_position_rule_m2m: fix wizard to generate rule from template

89. By Vincent Renaville@camptocamp

[ADD] account_payment ext

88. By Vincent Renaville@camptocamp

[MERGE] credit management

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'account_credit_management'
2=== added file 'account_credit_management/__init__.py'
3--- account_credit_management/__init__.py 1970-01-01 00:00:00 +0000
4+++ account_credit_management/__init__.py 2012-10-15 10:14:35 +0000
5@@ -0,0 +1,28 @@
6+# -*- coding: utf-8 -*-
7+##############################################################################
8+#
9+# Author: Nicolas Bessi
10+# Copyright 2012 Camptocamp SA
11+#
12+# This program is free software: you can redistribute it and/or modify
13+# it under the terms of the GNU Affero General Public License as
14+# published by the Free Software Foundation, either version 3 of the
15+# License, or (at your option) any later version.
16+#
17+# This program is distributed in the hope that it will be useful,
18+# but WITHOUT ANY WARRANTY; without even the implied warranty of
19+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+# GNU Affero General Public License for more details.
21+#
22+# You should have received a copy of the GNU Affero General Public License
23+# along with this program. If not, see <http://www.gnu.org/licenses/>.
24+#
25+##############################################################################
26+from . import credit_management_run
27+from . import credit_management_line
28+from . import credit_management_account
29+from . import credit_management_partner
30+from . import credit_management_profile
31+from . import credit_management_company
32+import wizard
33+import report
34
35=== added file 'account_credit_management/__openerp__.py'
36--- account_credit_management/__openerp__.py 1970-01-01 00:00:00 +0000
37+++ account_credit_management/__openerp__.py 2012-10-15 10:14:35 +0000
38@@ -0,0 +1,47 @@
39+# -*- coding: utf-8 -*-
40+##############################################################################
41+#
42+# Author: Nicolas Bessi
43+# Copyright 2012 Camptocamp SA
44+#
45+# This program is free software: you can redistribute it and/or modify
46+# it under the terms of the GNU Affero General Public License as
47+# published by the Free Software Foundation, either version 3 of the
48+# License, or (at your option) any later version.
49+#
50+# This program is distributed in the hope that it will be useful,
51+# but WITHOUT ANY WARRANTY; without even the implied warranty of
52+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
53+# GNU Affero General Public License for more details.
54+#
55+# You should have received a copy of the GNU Affero General Public License
56+# along with this program. If not, see <http://www.gnu.org/licenses/>.
57+#
58+##############################################################################
59+{'name' : 'Account Credit Management',
60+ 'version' : '0.1',
61+ 'author' : 'Camptocamp',
62+ 'maintainer': 'Camptocamp',
63+ 'category': 'Finance',
64+ 'complexity': "normal", # easy, normal, expert
65+ 'depends' : ['base', 'account', 'email_template', 'report_webkit'],
66+ 'description': """Credit control management TODO""",
67+ 'website': 'http://www.camptocamp.com',
68+ 'init_xml': ["data.xml"],
69+ 'update_xml': ["credit_management_line_view.xml",
70+ "credit_management_account_view.xml",
71+ "credit_management_partner_view.xml",
72+ "credit_management_profile_view.xml",
73+ "credit_management_run_view.xml",
74+ "credit_management_company_view.xml",
75+ "wizard/credit_management_mailer_view.xml",
76+ "wizard/credit_management_marker_view.xml",
77+ "wizard/credit_management_printer_view.xml",
78+ "report/report.xml",
79+ "security/ir.model.access.csv",],
80+ #"credit_management_demo.xml"],
81+ 'demo_xml': ["credit_management_demo.xml"],
82+ 'tests': [],
83+ 'installable': True,
84+ 'license': 'AGPL-3',
85+ 'application': True}
86
87=== added file 'account_credit_management/credit_management_account.py'
88--- account_credit_management/credit_management_account.py 1970-01-01 00:00:00 +0000
89+++ account_credit_management/credit_management_account.py 2012-10-15 10:14:35 +0000
90@@ -0,0 +1,220 @@
91+# -*- coding: utf-8 -*-
92+##############################################################################
93+#
94+# Author: Nicolas Bessi
95+# Copyright 2012 Camptocamp SA
96+#
97+# This program is free software: you can redistribute it and/or modify
98+# it under the terms of the GNU Affero General Public License as
99+# published by the Free Software Foundation, either version 3 of the
100+# License, or (at your option) any later version.
101+#
102+# This program is distributed in the hope that it will be useful,
103+# but WITHOUT ANY WARRANTY; without even the implied warranty of
104+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
105+# GNU Affero General Public License for more details.
106+#
107+# You should have received a copy of the GNU Affero General Public License
108+# along with this program. If not, see <http://www.gnu.org/licenses/>.
109+#
110+##############################################################################
111+from datetime import datetime
112+import operator
113+from openerp.osv.orm import Model, fields
114+from openerp.tools.translate import _
115+from openerp.addons.account_credit_management import credit_management_run
116+
117+class AccountAccount(Model):
118+ """Add a link to a credit management profile on account account"""
119+
120+
121+ def _check_account_type_compatibility(self, cursor, uid, acc_ids, context=None):
122+ """We check that account is of type reconcile"""
123+ if not isinstance(acc_ids, list):
124+ acc_ids = [acc_ids]
125+ for acc in self.browse(cursor, uid, acc_ids, context):
126+ if acc.credit_profile_id and not acc.reconcile:
127+ return False
128+ return True
129+
130+ _inherit = "account.account"
131+ _description = """Add a link to a credit profile"""
132+ _columns = {'credit_profile_id': fields.many2one('credit.management.profile',
133+ 'Credit management profile',
134+ help=("Define global credit profile"
135+ "order is account partner invoice")),
136+
137+ 'credit_management_line_ids': fields.one2many('credit.management.line',
138+ 'account_id',
139+ string='Credit Lines',
140+ readonly=True)}
141+
142+ _constraints = [(_check_account_type_compatibility,
143+ _('You can not set a credit profile on a non reconciliable account'),
144+ ['credit_profile_id'])]
145+
146+class AccountInvoice(Model):
147+ """Add a link to a credit management profile on account account"""
148+
149+ _inherit = "account.invoice"
150+ _description = """Add a link to a credit profile"""
151+ _columns = {'credit_profile_id': fields.many2one('credit.management.profile',
152+ 'Credit management profile',
153+ help=("Define global credit profile"
154+ "order is account partner invoice")),
155+
156+ 'credit_management_line_ids': fields.one2many('credit.management.line',
157+ 'account_id',
158+ string='Credit Lines',
159+ readonly=True)}
160+
161+ def action_move_create(self, cursor, uid, ids, context=None):
162+ """We ensure writing of invoice id in move line because
163+ Trigger field may not work without account_voucher addon"""
164+ res = super(AccountInvoice, self).action_move_create(cursor, uid, ids, context=context)
165+ for inv in self.browse(cursor, uid, ids, context=context):
166+ if inv.move_id:
167+ for line in inv.move_id.line_id:
168+ line.write({'invoice_id': inv.id})
169+ return res
170+
171+
172+class AccountMoveLine(Model):
173+ """Add a function that compute the residual amount using a follow up date
174+ Add relation between move line and invoicex"""
175+
176+ _inherit = "account.move.line"
177+ # Store fields has strange behavior with voucher module we had to overwrite invoice
178+
179+
180+ # def _invoice_id(self, cursor, user, ids, name, arg, context=None):
181+ # #Code taken from OpenERP account addon
182+ # invoice_obj = self.pool.get('account.invoice')
183+ # res = {}
184+ # for line_id in ids:
185+ # res[line_id] = False
186+ # cursor.execute('SELECT l.id, i.id ' \
187+ # 'FROM account_move_line l, account_invoice i ' \
188+ # 'WHERE l.move_id = i.move_id ' \
189+ # 'AND l.id IN %s',
190+ # (tuple(ids),))
191+ # invoice_ids = []
192+ # for line_id, invoice_id in cursor.fetchall():
193+ # res[line_id] = invoice_id
194+ # invoice_ids.append(invoice_id)
195+ # invoice_names = {False: ''}
196+ # for invoice_id, name in invoice_obj.name_get(cursor, user, invoice_ids, context=context):
197+ # invoice_names[invoice_id] = name
198+ # for line_id in res.keys():
199+ # invoice_id = res[line_id]
200+ # res[line_id] = (invoice_id, invoice_names[invoice_id])
201+ # return res
202+
203+ # def _get_invoice(self, cursor, uid, ids, context=None):
204+ # result = set()
205+ # for line in self.pool.get('account.invoice').browse(cursor, uid, ids, context=context):
206+ # if line.move_id:
207+ # ids = [x.id for x in line.move_id.line_id or []]
208+ # return list(result)
209+
210+ # _columns = {'invoice_id': fields.function(_invoice_id, string='Invoice',
211+ # type='many2one', relation='account.invoice',
212+ # store={'account.invoice': (_get_invoice, ['move_id'], 20)})}
213+
214+ _columns = {'invoice_id': fields.many2one('account.invoice', 'Invoice')}
215+
216+ def _get_payment_and_credit_lines(self, moveline_array, lookup_date):
217+ credit_lines = []
218+ payment_lines = []
219+ for line in moveline_array:
220+ if self._should_exlude_line(line):
221+ continue
222+ if line.account_id.type == 'receivable' and line.debit:
223+ credit_lines.append(line)
224+ else:
225+ if line.reconcile_partial_id:
226+ payment_lines.append(line)
227+ credit_lines.sort(key=operator.attrgetter('date'))
228+ payment_lines.sort(key=operator.attrgetter('date'))
229+ return (credit_lines, payment_lines)
230+
231+ def _validate_line_currencies(self, credit_lines):
232+ """Raise an excpetion if there is lines with different currency"""
233+ if len(credit_lines) == 0:
234+ return True
235+ currency = credit_lines[0].currency_id.id
236+ if not all(obj.currency_id.id == currency for obj in credit_lines):
237+ raise Exception('Not all line of move line are in the same currency')
238+
239+ def _get_value_amount(self, mv_line_br):
240+ if mv_line_br.currency_id:
241+ return mv_line_br.amount_currency
242+ else:
243+ return mv_line_br.debit - mv_line_br.credit
244+
245+ def _validate_partial(self, credit_lines):
246+ if len(credit_lines) == 0:
247+ return True
248+ else:
249+ line_with_partial = 0
250+ for line in credit_lines:
251+ if not line.reconcile_partial_id:
252+ line_with_partial += 1
253+ if line_with_partial and line_with_partial != len(credit_lines):
254+ raise Exception('Can not compute credit line if multiple'
255+ ' lines are not all linked to a partial')
256+
257+ def _get_applicable_payment_lines(self, credit_line, payment_lines):
258+ applicable_payment = []
259+ for pay_line in payment_lines:
260+ if datetime.strptime(pay_line.date, "%Y-%m-%d").date() \
261+ <= datetime.strptime(credit_line.date, "%Y-%m-%d").date():
262+ applicable_payment.append(pay_line)
263+ return applicable_payment
264+
265+ def _compute_partial_reconcile_residual(self, move_lines, lookup_date, move_id, memoizer):
266+ """ Compute open amount of multiple credit lines linked to multiple payment lines"""
267+ credit_lines, payment_lines = self._get_payment_and_credit_lines(move_lines, lookup_date, memoizer)
268+ self._validate_line_currencies(credit_lines)
269+ self._validate_line_currencies(payment_lines)
270+ self._validate_partial(credit_lines)
271+ # memoizer structure move_id : {move_line_id: open_amount}
272+ # paymnent line and credit line are sorted by date
273+ rest = 0.0
274+ for credit_line in credit_lines:
275+ applicable_payment = self._get_applicable_payment_lines(credit_line, payment_lines)
276+ paid_amount = 0.0
277+ for pay_line in applicable_payment:
278+ paid_amount += self._get_value_amount(pay_line)
279+ balance_amount = self._get_value_amount(credit_lines) - (paid_amount + rest)
280+ memoizer[move_id][credit_line.id] = balance_amount
281+ if balance_amount < 0.0:
282+ rest = balance_amount
283+ else:
284+ rest = 0.0
285+ return memoizer
286+
287+ def _compute_fully_open_amount(self, move_lines, lookup_date, move_id, memoizer):
288+ for move_line in move_lines:
289+ memoizer[move_id][move_line.id] = self._get_value_amount(move_line)
290+ return memoizer
291+
292+
293+ def _amount_residual_from_date(self, cursor, uid, mv_line_br, lookup_date, context=None):
294+ """
295+ Code from function _amount_residual of account/account_move_line.py does not take
296+ in account mulitple line payment and reconciliation. We have to rewrite it
297+ Code computes residual amount at lookup date for mv_line_br in entry
298+ """
299+ memoizer = credit_management_run.memoizers['credit_line_residuals']
300+ move_id = mv_line_br.move_id.id
301+ if mv_line_br.move_id.id in memoizer:
302+ pass # get back value
303+ else:
304+ memoizer[move_id] = {}
305+ move_lines = mv_line_br.move_id.line_id
306+ if mv_line_br.reconcile_partial_id:
307+ self._compute_partial_reconcile_residual(move_lines, lookup_date, move_id, memoizer)
308+ else:
309+ self._compute_fully_open_amount(move_lines, lookup_date, move_id, memoizer)
310+ return memoizer[move_id][mv_line_br.id]
311
312=== added file 'account_credit_management/credit_management_account_view.xml'
313--- account_credit_management/credit_management_account_view.xml 1970-01-01 00:00:00 +0000
314+++ account_credit_management/credit_management_account_view.xml 2012-10-15 10:14:35 +0000
315@@ -0,0 +1,41 @@
316+<openerp>
317+ <data>
318+
319+ <record id="account_followup_form_view" model="ir.ui.view">
320+ <field name="name">account.followup.form.view</field>
321+ <field name="model">account.account</field>
322+ <field name="inherit_id" ref="account.view_account_form" />
323+ <field name="type">form</field>
324+ <field name="arch" type="xml">
325+ <field name="reconcile" position="after">
326+ <field name="credit_profile_id" groups="account_credit_management.group_account_credit_management_manager,account_credit_management.group_account_credit_management_user"/>
327+ </field>
328+ </field>
329+ </record>
330+
331+ <act_window
332+ id="act_account_credit_relation_relation"
333+ name="Credit lines"
334+ groups="account_credit_management.group_account_credit_management_manager,account_credit_management.group_account_credit_management_user"
335+ domain="[('account_id', '=', active_id)]"
336+ res_model="credit.management.line"
337+ src_model="account.account"/>
338+
339+ <record id="invoice_followup_form_view" model="ir.ui.view">
340+ <field name="name">invoice.followup.form.view</field>
341+ <field name="model">account.invoice</field>
342+ <field name="inherit_id" ref="account.invoice_form" />
343+ <field name="type">form</field>
344+ <field name="arch" type="xml">
345+ <notebook position="inside">
346+ <page string="Credit Management"
347+ groups="account_credit_management.group_account_credit_management_manager,account_credit_management.group_account_credit_management_user,group_account_credit_management_info">
348+ <field name="credit_profile_id"/>
349+ <field name="credit_management_line_ids" colspan="4" nolabel="1"/>
350+ </page>
351+ </notebook>
352+ </field>
353+ </record>
354+
355+ </data>
356+</openerp>
357
358=== added file 'account_credit_management/credit_management_company.py'
359--- account_credit_management/credit_management_company.py 1970-01-01 00:00:00 +0000
360+++ account_credit_management/credit_management_company.py 2012-10-15 10:14:35 +0000
361@@ -0,0 +1,28 @@
362+# -*- coding: utf-8 -*-
363+##############################################################################
364+#
365+# Author: Nicolas Bessi
366+# Copyright 2012 Camptocamp SA
367+#
368+# This program is free software: you can redistribute it and/or modify
369+# it under the terms of the GNU Affero General Public License as
370+# published by the Free Software Foundation, either version 3 of the
371+# License, or (at your option) any later version.
372+#
373+# This program is distributed in the hope that it will be useful,
374+# but WITHOUT ANY WARRANTY; without even the implied warranty of
375+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
376+# GNU Affero General Public License for more details.
377+#
378+# You should have received a copy of the GNU Affero General Public License
379+# along with this program. If not, see <http://www.gnu.org/licenses/>.
380+#
381+##############################################################################
382+from openerp.osv.orm import Model, fields
383+
384+class ResCompany(Model):
385+ _inherit = "res.company"
386+
387+ _columns = {"credit_management_tolerance": fields.float('Credit Tolerance')}
388+
389+ _defaults = {"credit_management_tolerance": 0.1}
390
391=== added file 'account_credit_management/credit_management_company_view.xml'
392--- account_credit_management/credit_management_company_view.xml 1970-01-01 00:00:00 +0000
393+++ account_credit_management/credit_management_company_view.xml 2012-10-15 10:14:35 +0000
394@@ -0,0 +1,15 @@
395+<openerp>
396+ <data>
397+ <record id="credit_management_company_form" model="ir.ui.view">
398+ <field name="name">credit.management.company.form</field>
399+ <field name="model">res.company</field>
400+ <field name="type">form</field>
401+ <field name="inherit_id" ref="base.view_company_form"/>
402+ <field name="arch" type="xml">
403+ <field name="currency_id" position="after">
404+ <field name="credit_management_tolerance"/>
405+ </field>
406+ </field>
407+ </record>
408+ </data>
409+</openerp>
410
411=== added file 'account_credit_management/credit_management_demo.xml'
412--- account_credit_management/credit_management_demo.xml 1970-01-01 00:00:00 +0000
413+++ account_credit_management/credit_management_demo.xml 2012-10-15 10:14:35 +0000
414@@ -0,0 +1,33 @@
415+<openerp>
416+ <data>
417+ <record id="a_recv_1" model="account.account">
418+ <field name="code">X11002-a</field>
419+ <field name="name">B2B Debtors - (test)</field>
420+ <field ref="account.cas" name="parent_id"/>
421+ <field name="type">receivable</field>
422+ <field eval="True" name="reconcile"/>
423+ <field name="credit_profile_id" ref="credit_management_no_follow"/>
424+ <field name="user_type" ref="account.data_account_type_receivable"/>
425+ </record>
426+
427+ <record id="a_recv_2" model="account.account">
428+ <field name="code">X11002-b</field>
429+ <field name="name">B2C Debtors - (test)</field>
430+ <field ref="account.cas" name="parent_id"/>
431+ <field name="type">receivable</field>
432+ <field eval="True" name="reconcile"/>
433+ <field name="credit_profile_id" ref="credit_management_2_time"/>
434+ <field name="user_type" ref="account.data_account_type_receivable"/>
435+ </record>
436+
437+ <record id="a_recv_3" model="account.account">
438+ <field name="code">X11002-c</field>
439+ <field name="name">New Debtors - (test)</field>
440+ <field ref="account.cas" name="parent_id"/>
441+ <field name="type">receivable</field>
442+ <field eval="True" name="reconcile"/>
443+ <field name="credit_profile_id" ref="credit_management_3_time"/>
444+ <field name="user_type" ref="account.data_account_type_receivable"/>
445+ </record>
446+ </data>
447+</openerp>
448
449=== added file 'account_credit_management/credit_management_line.py'
450--- account_credit_management/credit_management_line.py 1970-01-01 00:00:00 +0000
451+++ account_credit_management/credit_management_line.py 2012-10-15 10:14:35 +0000
452@@ -0,0 +1,181 @@
453+# -*- coding: utf-8 -*-
454+##############################################################################
455+#
456+# Author: Nicolas Bessi
457+# Copyright 2012 Camptocamp SA
458+#
459+# This program is free software: you can redistribute it and/or modify
460+# it under the terms of the GNU Affero General Public License as
461+# published by the Free Software Foundation, either version 3 of the
462+# License, or (at your option) any later version.
463+#
464+# This program is distributed in the hope that it will be useful,
465+# but WITHOUT ANY WARRANTY; without even the implied warranty of
466+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
467+# GNU Affero General Public License for more details.
468+#
469+# You should have received a copy of the GNU Affero General Public License
470+# along with this program. If not, see <http://www.gnu.org/licenses/>.
471+#
472+##############################################################################
473+import logging
474+
475+from openerp.osv.orm import Model, fields
476+import pooler
477+#from datetime import datetime
478+
479+logger = logging.getLogger('credit.line.management')
480+
481+class CreditManagementLine (Model):
482+ """A credit Management line decribe a line of amount due by a customer.
483+ It is linked to all required financial account.
484+ It has various state draft open to be send send. For more information about
485+ usage please read __openerp__.py file"""
486+
487+ _name = "credit.management.line"
488+ _description = """A credit Management line"""
489+ _rec_name = "id"
490+
491+ _columns = {'date': fields.date('Controlling date', required=True),
492+ # maturity date of related move line we do not use a related field in order to
493+ # allow manual changes
494+ 'date_due': fields.date('Due date',
495+ required=True,
496+ readonly=True,
497+ states={'draft': [('readonly', False)]}),
498+
499+ 'date_sent': fields.date('Sent date',
500+ readonly=True,
501+ states={'draft': [('readonly', False)]}),
502+
503+ 'state': fields.selection([('draft', 'Draft'),
504+ ('to_be_sent', 'To be sent'),
505+ ('sent', 'Done'),
506+ ('error', 'Error'),
507+ ('mail_error', 'Mailing Error')],
508+ 'State', required=True, readonly=True),
509+
510+ 'canal': fields.selection([('manual', 'Manual'),
511+ ('mail', 'Mail')],
512+ 'Canal', required=True,
513+ readonly=True,
514+ states={'draft': [('readonly', False)]}),
515+
516+ 'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True),
517+ 'partner_id': fields.many2one('res.partner', "Partner", required=True),
518+ 'amount_due': fields.float('Due Amount Tax inc.', required=True, readonly=True),
519+ 'balance_due': fields.float('Due balance', required=True, readonly=True),
520+ 'mail_message_id': fields.many2one('mail.message', 'Sent mail', readonly=True),
521+
522+ 'move_line_id': fields.many2one('account.move.line', 'Move line',
523+ required=True, readonly=True),
524+
525+ 'account_id': fields.related('move_line_id', 'account_id', type='many2one',
526+ relation='account.account', string='Account',
527+ store=True, readonly=True),
528+
529+ 'currency_id': fields.related('move_line_id', 'currency_id', type='many2one',
530+ relation='res.currency', string='Currency',
531+ store=True, readonly=True),
532+
533+ 'company_id': fields.related('move_line_id', 'company_id', type='many2one',
534+ relation='res.company', string='Company',
535+ store=True, readonly=True),
536+
537+ # we can allow a manual change of profile in draft state
538+ 'profile_rule_id':fields.many2one('credit.management.profile.rule',
539+ 'Overdue Rule', required=True, readonly=True,
540+ states={'draft': [('readonly', False)]}),
541+
542+ 'profile_id': fields.related('profile_rule_id', 'profile_id', type='many2one',
543+ relation='credit.management.profile', string='Profile',
544+ store=True, readonly=True),
545+
546+ 'level': fields.related('profile_rule_id', 'level', type='float',
547+ relation='credit.management.profile', string='Level',
548+ store=True, readonly=True),}
549+
550+
551+ _defaults = {'state': 'draft'}
552+
553+ def _update_from_mv_line(self, cursor, uid, ids, mv_line_br, rule_br,
554+ lookup_date, context=None):
555+ """hook function to update line if required"""
556+ context = context or {}
557+ return []
558+
559+ def _create_from_mv_line(self, cursor, uid, ids, mv_line_br,
560+ rule_br, lookup_date, context=None):
561+ """Create credit line"""
562+ acc_line_obj = self.pool.get('account.move.line')
563+ context = context or {}
564+ data_dict = {}
565+ data_dict['date'] = lookup_date
566+ data_dict['date_due'] = mv_line_br.date_maturity
567+ data_dict['state'] = 'draft'
568+ data_dict['canal'] = rule_br.canal
569+ data_dict['invoice_id'] = (mv_line_br.invoice_id and mv_line_br.invoice_id.id
570+ or False)
571+ data_dict['partner_id'] = mv_line_br.partner_id.id
572+ data_dict['amount_due'] = (mv_line_br.amount_currency or mv_line_br.debit
573+ or mv_line_br.credit)
574+ data_dict['balance_due'] = acc_line_obj._amount_residual_from_date(cursor, uid, mv_line_br,
575+ lookup_date, context=context)
576+ data_dict['profile_rule_id'] = rule_br.id
577+ data_dict['company_id'] = mv_line_br.company_id.id
578+ data_dict['move_line_id'] = mv_line_br.id
579+ return [self.create(cursor, uid, data_dict)]
580+
581+
582+ def create_or_update_from_mv_lines(self, cursor, uid, ids, lines,
583+ rule_id, lookup_date, errors=None, context=None):
584+ """Create or update line base on rules"""
585+ context = context or {}
586+ currency_obj = self.pool.get('res.currency')
587+ rule_obj = self.pool.get('credit.management.profile.rule')
588+ ml_obj = self.pool.get('account.move.line')
589+ rule = rule_obj.browse(cursor, uid, rule_id, context)
590+ current_lvl = rule.level
591+ credit_line_ids = []
592+ user = self.pool.get('res.users').browse(cursor, uid, uid)
593+ tolerance_base = user.company_id.credit_management_tolerance
594+ tolerance = {}
595+ currency_ids = currency_obj.search(cursor, uid, [])
596+
597+ acc_line_obj = self.pool.get('account.move.line')
598+ for c_id in currency_ids:
599+ tmp = currency_obj.compute(cursor, uid, c_id,
600+ user.company_id.currency_id.id, tolerance_base)
601+ tolerance[c_id] = tmp
602+
603+ existings = self.search(cursor, uid, [('move_line_id', 'in', lines),
604+ ('level', '=', current_lvl)])
605+ db, pool = pooler.get_db_and_pool(cursor.dbname)
606+ for line in ml_obj.browse(cursor, uid, lines, context):
607+ # we want to create as many line as possible
608+ local_cr = db.cursor()
609+ try:
610+ if line.id in existings:
611+ # does nothing just a hook
612+ credit_line_ids += self._update_from_mv_line(local_cr, uid, ids,
613+ line, rule, lookup_date,
614+ context=context)
615+ else:
616+ # as we use memoizer pattern this has almost no cost to get it
617+ # multiple time
618+ open_amount = acc_line_obj._amount_residual_from_date(cursor, uid, line,
619+ lookup_date, context=context)
620+
621+ if open_amount > tolerance.get(line.currency_id.id, tolerance_base):
622+ credit_line_ids += self._create_from_mv_line(local_cr, uid, ids,
623+ line, rule, lookup_date,
624+ context=context)
625+ except Exception, exc:
626+ logger.error(exc)
627+ if errors:
628+ errors.append(unicode(exc)) #obj-c common pattern
629+ local_cr.rollback()
630+ finally:
631+ local_cr.commit()
632+ local_cr.close()
633+ return credit_line_ids
634
635=== added file 'account_credit_management/credit_management_line_view.xml'
636--- account_credit_management/credit_management_line_view.xml 1970-01-01 00:00:00 +0000
637+++ account_credit_management/credit_management_line_view.xml 2012-10-15 10:14:35 +0000
638@@ -0,0 +1,145 @@
639+<openerp>
640+ <data>
641+ <record id="credit_management_line_form" model="ir.ui.view">
642+ <field name="name">credit.management.line.form</field>
643+ <field name="model">credit.management.line</field>
644+ <field name="type">form</field>
645+ <field name="arch" type="xml">
646+ <form>
647+ <field name="date"/>
648+ <field name="date_due"/>
649+ <field name="date_sent"/>
650+ <field name="level"/>
651+ <field name="state"/>
652+ <field name="canal"/>
653+ <field name="invoice_id"/>
654+ <field name="partner_id"/>
655+ <!-- <field name="address_id" domain="[('partner_id', '=', partner_id)]"/> -->
656+ <field name="amount_due"/>
657+ <field name="balance_due"/>
658+ <field name="currency_id"/>
659+ <field name="move_line_id"/>
660+ <field name="account_id"/>
661+ <field name="profile_rule_id"/>
662+ <field name="profile_id"/>
663+ <field name="mail_message_id"/>
664+ </form>
665+ </field>
666+ </record>
667+
668+ <record id="credit_management_line_search" model="ir.ui.view">
669+ <field name="name">Credit lines</field>
670+ <field name="model">credit.management.lines</field>
671+ <field name="type">search</field>
672+ <field name="arch" type="xml">
673+ <search string="Search credit lines Items">
674+ <group>
675+ <filter icon="terp-document-new" string="New"
676+ domain="[('state', '=', 'draft')]"
677+ help="New lines"/>
678+ <filter icon="terp-dolar_ok!" string="To be sent"
679+ domain="[('state', '=', 'to_be_sent')]"
680+ help="New lines"/>
681+ <filter icon="terp-check" string="Sent"
682+ domain="[('state', '=', 'sent')]"
683+ help="New lines"/>
684+ <separator orientation="vertical"/>
685+ <filter icon="terp-gtk-stop" string="Error"
686+ domain="[('state', 'in', ('error', 'mail_error'))]"
687+ help="New lines"/>
688+ <separator orientation="vertical"/>
689+
690+ <field name="date"/>
691+ <field name="level"/>
692+ <field name="partner_id"/>
693+ <field name="account_id"/>
694+ <newline/>
695+ <field name="invoice_id"/>
696+ <field name="profile_id"/>
697+ <field name="profile_rule_id"/>
698+ <field name ="canal" />
699+ </group>
700+ <newline/>
701+
702+ <group expand="0" string="Group By...">
703+ <separator orientation="vertical"/>
704+ <filter domain='[]' context="{'group_by': 'date'}"
705+ icon="terp-go-month" string="Run date"/>
706+ <separator orientation="vertical"/>
707+ <filter domain='[]' context="{'group_by': 'level'}"
708+ icon="terp-gtk-jump-to-rtl" string="Level"/>
709+ <separator orientation="vertical"/>
710+ <filter domain='[]' context="{'group_by': 'partner_id'}"
711+ icon="terp-partner" string="Partner"/>
712+ <separator orientation="vertical"/>
713+ <filter domain='[]' context="{'group_by': 'account_id'}"
714+ icon="terp-folder-green" string="Account"/>
715+ <separator orientation="vertical"/>
716+ <filter domain='[]' context="{'group_by': 'invoice_id'}"
717+ icon="terp-document-new" string="Invoice"/>
718+ <separator orientation="vertical"/>
719+ <filter domain='[]' context="{'group_by': 'profile_id'}"
720+ icon="terp-document-new" string="Credit Profile"/>
721+ <separator orientation="vertical"/>
722+ <filter domain='[]' context="{'group_by': 'profile_rule_id'}"
723+ icon="terp-document-new" string="Credit Profile rule"/>
724+ <separator orientation="vertical"/>
725+ <filter domain='[]' context="{'group_by': 'canal'}"
726+ icon="terp-document-new" string="Canal"/>
727+ </group>
728+ <newline/>
729+ </search>
730+ </field>
731+ </record>
732+
733+ <record id="credit_management_line_tree" model="ir.ui.view">
734+ <field name="name">credit.management.line.tree</field>
735+ <field name="model">credit.management.line</field>
736+ <field name="type">tree</field>
737+ <field name="arch" type="xml">
738+ <tree editable="bottom" colors="green:state == 'sent';red:state in ('error', 'mail_error');">
739+ <field name="date"/>
740+ <field name="date_due"/>
741+ <field name="level"/>
742+ <field name="state"/>
743+ <field name="canal"/>
744+ <field name="invoice_id"/>
745+ <field name="partner_id"/>
746+ <field name="amount_due"/>
747+ <field name="balance_due"/>
748+ <field name="currency_id"/>
749+ <field name="move_line_id"/>
750+ <field name="account_id"/>
751+ <field name="profile_rule_id"/>
752+ <field name="profile_id"/>
753+ <field name="mail_message_id"/>
754+ </tree>
755+ </field>
756+ </record>
757+
758+ <menuitem
759+ name="Credit management"
760+ parent="account.menu_finance_periodical_processing"
761+ id="base_credit_management_menu"/>
762+
763+ <record model="ir.actions.act_window" id="credit_management_line_action">
764+ <field name="name">Credit lines</field>
765+ <field name="type">ir.actions.act_window</field>
766+ <field name="res_model">credit.management.line</field>
767+ <field name="domain"></field>
768+ <field name="view_type">form</field>
769+ <field name="view_mode">tree,form</field>
770+ <field name="view_id" ref="credit_management_line_tree"/>
771+ <field name="search_view_id" ref="credit_management_line_search"/>
772+ </record>
773+
774+
775+ <menuitem
776+ name="Credit lines"
777+ parent="base_credit_management_menu"
778+ action="credit_management_line_action"
779+ id="credit_management_line_action_menu"/>
780+
781+
782+ </data>
783+</openerp>
784
785=== added file 'account_credit_management/credit_management_partner.py'
786--- account_credit_management/credit_management_partner.py 1970-01-01 00:00:00 +0000
787+++ account_credit_management/credit_management_partner.py 2012-10-15 10:14:35 +0000
788@@ -0,0 +1,36 @@
789+# -*- coding: utf-8 -*-
790+##############################################################################
791+#
792+# Author: Nicolas Bessi
793+# Copyright 2012 Camptocamp SA
794+#
795+# This program is free software: you can redistribute it and/or modify
796+# it under the terms of the GNU Affero General Public License as
797+# published by the Free Software Foundation, either version 3 of the
798+# License, or (at your option) any later version.
799+#
800+# This program is distributed in the hope that it will be useful,
801+# but WITHOUT ANY WARRANTY; without even the implied warranty of
802+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
803+# GNU Affero General Public License for more details.
804+#
805+# You should have received a copy of the GNU Affero General Public License
806+# along with this program. If not, see <http://www.gnu.org/licenses/>.
807+#
808+##############################################################################
809+from openerp.osv.orm import Model, fields
810+
811+class ResPartner(Model):
812+ """Add a link to a credit management profile on account account"""
813+
814+ _inherit = "res.partner"
815+ _description = """Add a link to a credit profile"""
816+ _columns = {'credit_profile_id': fields.many2one('credit.management.profile',
817+ 'Credit management profile',
818+ help=("Define global credit profile"
819+ "order is account partner invoice")),
820+
821+ 'credit_management_line_ids': fields.one2many('credit.management.line',
822+ 'invoice_id',
823+ string='Credit Lines',
824+ readonly=True)}
825
826=== added file 'account_credit_management/credit_management_partner_view.xml'
827--- account_credit_management/credit_management_partner_view.xml 1970-01-01 00:00:00 +0000
828+++ account_credit_management/credit_management_partner_view.xml 2012-10-15 10:14:35 +0000
829@@ -0,0 +1,25 @@
830+<openerp>
831+<data>
832+<record id="partner_followup_form_view" model="ir.ui.view">
833+ <field name="name">partner.credit_management.form.view</field>
834+ <field name="model">res.partner</field>
835+ <field name="inherit_id" ref="base.view_partner_form" />
836+ <field name="type">form</field>
837+ <field name="arch" type="xml">
838+ <field name="last_reconciliation_date" position="after">
839+ <field name="credit_profile_id"
840+ groups="account_credit_management.group_account_credit_management_manager,account_credit_management.group_account_credit_management_use"/>
841+ </field>
842+ </field>
843+</record>
844+
845+ <act_window
846+ id="act_partner_credit_relation_relation"
847+ name="Credit lines"
848+ groups="account_credit_management.group_account_credit_management_manager,account_credit_management.group_account_credit_management_user"
849+ domain="[('partner_id', '=', active_id)]"
850+ res_model="credit.management.line"
851+ src_model="res.partner"/>
852+
853+</data>
854+</openerp>
855
856=== added file 'account_credit_management/credit_management_profile.py'
857--- account_credit_management/credit_management_profile.py 1970-01-01 00:00:00 +0000
858+++ account_credit_management/credit_management_profile.py 2012-10-15 10:14:35 +0000
859@@ -0,0 +1,292 @@
860+# -*- coding: utf-8 -*-
861+##############################################################################
862+#
863+# Author: Nicolas Bessi
864+# Copyright 2012 Camptocamp SA
865+#
866+# This program is free software: you can redistribute it and/or modify
867+# it under the terms of the GNU Affero General Public License as
868+# published by the Free Software Foundation, either version 3 of the
869+# License, or (at your option) any later version.
870+#
871+# This program is distributed in the hope that it will be useful,
872+# but WITHOUT ANY WARRANTY; without even the implied warranty of
873+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
874+# GNU Affero General Public License for more details.
875+#
876+# You should have received a copy of the GNU Affero General Public License
877+# along with this program. If not, see <http://www.gnu.org/licenses/>.
878+#
879+##############################################################################
880+from openerp.osv.orm import Model, fields
881+from openerp.tools.translate import _
882+
883+class CreditManagementProfile(Model):
884+ """Define a profile of reminder"""
885+
886+ _name = "credit.management.profile"
887+ _description = """Define a reminder profile"""
888+ _columns = {'name': fields.char('Name', required=True, size=128),
889+
890+ 'profile_rule_ids' : fields.one2many('credit.management.profile.rule',
891+ 'profile_id',
892+ 'Profile Rules'),
893+
894+ 'do_nothing' : fields.boolean('Do nothing',
895+ help=('For profiling who should not '
896+ 'generate lines or are obsolete')),
897+
898+ 'company_id' : fields.many2one('res.company', 'Company')
899+ }
900+
901+
902+ def _get_account_related_lines(self, cursor, uid, profile_id, lookup_date, lines, context=None):
903+ """ We get all the lines related to accounts with given credit profile.
904+ We try not to use direct SQL in order to respect security rules.
905+ As we define the first set it is important, The date is used to do a prefilter.
906+ !!!We take the asumption that only receivable lines have a maturity date
907+ and account must be reconcillable"""
908+ context = context or {}
909+ move_l_obj = self.pool.get('account.move.line')
910+ account_obj = self.pool.get('account.account')
911+ acc_ids = account_obj.search(cursor, uid, [('credit_profile_id', '=', profile_id)])
912+ if not acc_ids:
913+ return lines
914+ move_ids = move_l_obj.search(cursor, uid, [('account_id', 'in', acc_ids),
915+ ('date_maturity', '<=', lookup_date),
916+ ('reconcile_id', '=', False),
917+ ('partner_id', '!=', False)])
918+
919+ lines += move_ids
920+ return lines
921+
922+
923+ def _get_sum_reduce_range(self, cursor, uid, profile_id, lookup_date, lines, model,
924+ move_relation_field, context=None):
925+ """ We get all the lines related to the model with given credit profile.
926+ We also reduce from the global set (lines) the move line to be excluded.
927+ We try not to use direct SQL in order to respect security rules.
928+ As we define the first set it is important.
929+ The profile relation field MUST be named credit_profile_id
930+ and the model must have a relation
931+ with account move line.
932+ !!! We take the asumption that only receivable lines have a maturity date
933+ and account must be reconcillable"""
934+ # MARK possible place for a good optimisation
935+ context = context or {}
936+ my_obj = self.pool.get(model)
937+ move_l_obj = self.pool.get('account.move.line')
938+ add_obj_ids = my_obj.search(cursor, uid, [('credit_profile_id', '=', profile_id)])
939+ if add_obj_ids:
940+ add_lines = move_l_obj.search(cursor, uid, [(move_relation_field, 'in', add_obj_ids),
941+ ('date_maturity', '<=', lookup_date),
942+ ('partner_id', '!=', False),
943+ ('reconcile_id', '=', False)])
944+ lines = list(set(lines + add_lines))
945+ # we get all the lines that must be excluded at partner_level
946+ # from the global set (even the one included at account level)
947+ neg_obj_ids = my_obj.search(cursor, uid, [('credit_profile_id', '!=', profile_id),
948+ ('credit_profile_id', '!=', False)])
949+ if neg_obj_ids:
950+ # should we add ('id', 'in', lines) in domain ? it may give a veeery long SQL...
951+ neg_lines = move_l_obj.search(cursor, uid, [(move_relation_field, 'in', neg_obj_ids),
952+ ('date_maturity', '<=', lookup_date),
953+ ('partner_id', '!=', False),
954+ ('reconcile_id', '=', False)])
955+ if neg_lines:
956+ lines = list(set(lines) - set(neg_lines))
957+ return lines
958+
959+
960+ def _get_partner_related_lines(self, cursor, uid, profile_id, lookup_date, lines, context=None):
961+ return self._get_sum_reduce_range(cursor, uid, profile_id, lookup_date, lines,
962+ 'res.partner', 'partner_id', context=context)
963+
964+
965+ def _get_invoice_related_lines(self, cursor, uid, profile_id, lookup_date, lines, context=None):
966+ return self._get_sum_reduce_range(cursor, uid, profile_id, lookup_date, lines,
967+ 'account.invoice', 'invoice', context=context)
968+
969+
970+ def _get_moves_line_to_process(self, cursor, uid, profile_id, lookup_date, context=None):
971+ """Retrive all the move line to be procces for current profile.
972+ This function is planned to be use only on one id.
973+ Priority of inclustion, exlusion is account, partner, invoice"""
974+ context = context or {}
975+ lines = []
976+ if isinstance(profile_id, list):
977+ profile_id = profile_id[0]
978+ # order of call MUST be respected priority is account, partner, invoice
979+ lines = self._get_account_related_lines(cursor, uid, profile_id,
980+ lookup_date, lines, context=context)
981+ lines = self._get_partner_related_lines(cursor, uid, profile_id,
982+ lookup_date, lines, context=context)
983+ lines = self._get_invoice_related_lines(cursor, uid, profile_id,
984+ lookup_date, lines, context=context)
985+ return lines
986+
987+ def _check_lines_profiles(self, cursor, uid, profile_id, lines, context=None):
988+ """ Check if there is credit line related to same move line but
989+ related to an other profile"""
990+ context = context or {}
991+ if not lines:
992+ return []
993+ if isinstance(profile_id, list):
994+ profile_id = profile_id[0]
995+ cursor.execute("SELECT move_line_id FROM credit_management_line"
996+ " WHERE profile_id != %s and move_line_id in %s",
997+ (profile_id, tuple(lines)))
998+ res = cursor.fetchall()
999+ if res:
1000+ return [x[0] for x in res]
1001+ else:
1002+ return []
1003+
1004+
1005+
1006+class CreditManagementProfileRule (Model):
1007+ """Define a profile rule. A rule allows to determine if
1008+ a move line is due and the level of overdue of the line"""
1009+
1010+ _name = "credit.management.profile.rule"
1011+ _order = 'level'
1012+ _description = """A credit management profile rule"""
1013+ _columns = {'profile_id': fields.many2one('credit.management.profile',
1014+ 'Related Policy', required=True),
1015+ 'name': fields.char('Name', size=128, required=True),
1016+ 'level': fields.float('level', required=True),
1017+
1018+ 'computation_mode': fields.selection([('net_days', 'Due date'),
1019+ ('end_of_month', 'Due Date: end of Month'),
1020+ ('previous_date', 'Previous reminder')],
1021+ 'Compute mode',
1022+ required=True),
1023+
1024+ 'delay_days': fields.integer('Delay in day', required='True'),
1025+ 'mail_template_id': fields.many2one('email.template', 'Mail template',
1026+ required=True),
1027+ 'canal': fields.selection([('manual', 'Manual'),
1028+ ('mail', 'Mail')],
1029+ 'Canal', required=True),
1030+ 'custom_text': fields.text('Custom message', required=True, translate=True),
1031+ }
1032+
1033+
1034+ def _check_level_mode(self, cursor, uid, rids, context=None):
1035+ """We check that the smallest level is not based
1036+ on a rule using previous_date mode"""
1037+ if not isinstance(rids, list):
1038+ rids = [rids]
1039+ for rule in self.browse(cursor, uid, rids, context):
1040+ smallest_rule_id = self.search(cursor, uid, [('profile_id', '=', rule.profile_id.id)],
1041+ order='level asc', limit=1, context=context)
1042+ smallest_rule = self.browse(cursor, uid, smallest_rule_id[0], context)
1043+ if smallest_rule.computation_mode == 'previous_date':
1044+ return False
1045+ return True
1046+
1047+
1048+
1049+ _sql_constraint = [('unique level',
1050+ 'UNIQUE (profile_id, level)',
1051+ 'Level must be unique per profile')]
1052+
1053+ _constraints = [(_check_level_mode,
1054+ 'The smallest level can not be of type Previous reminder',
1055+ ['level'])]
1056+
1057+ def _is_first_level(self, cursor, uid, rule_br, context=None):
1058+ """Check if rule has the smallest priority"""
1059+ first_rule = self.search(cursor, uid, [('profile_id', '=', rule_br.profile_id.id)],
1060+ order='level asc', limit=1, context=context)
1061+ return first_rule[0] == rule_br.id
1062+ # ----- time related functions ---------
1063+
1064+ def _net_days_get_boundary(self):
1065+ return " (mv_line.date_maturity + %(delay)s)::date <= date(%(lookup_date)s)"
1066+
1067+ def _end_of_month_get_boundary(self):
1068+ return ("(date_trunc('MONTH', (mv_line.date_maturity + %(delay)s))+INTERVAL '1 MONTH - 1 day')::date"
1069+ "<= date(%(lookup_date)s)")
1070+
1071+ def _previous_date_get_boundary(self):
1072+ return "(cr_line.date + %(delay)s)::date <= date(%(lookup_date)s)"
1073+
1074+ def _get_sql_date_boundary_for_computation_mode(self, cursor, uid, rule_br, lookup_date, context=None):
1075+ """Return a where clauses statement for the given
1076+ lookup date and computation mode of the rule"""
1077+ fname = "_%s_get_boundary" % (rule_br.computation_mode,)
1078+ if hasattr(self, fname):
1079+ fnc = getattr(self, fname)
1080+ return fnc()
1081+ else:
1082+ raise NotImplementedError(_('Can not get function for computation mode: '
1083+ '%s is not implemented') % (fname,))
1084+
1085+ # -----------------------------------------
1086+
1087+ def _get_first_level_lines(self, cursor, uid, rule_br, lookup_date, lines, context=None):
1088+ if not lines:
1089+ return []
1090+ """Retrieve all the line that are linked to a frist level rules.
1091+ We use Raw SQL for perf. Security rule where applied in
1092+ profile object when line where retrieved"""
1093+ sql = ("SELECT DISTINCT mv_line.id\n"
1094+ " FROM account_move_line mv_line\n"
1095+ " WHERE mv_line.id in %(line_ids)s\n"
1096+ " AND NOT EXISTS (SELECT cr_line.id from credit_management_line cr_line\n"
1097+ " WHERE cr_line.move_line_id = mv_line.id)")
1098+ sql += " AND" + self._get_sql_date_boundary_for_computation_mode(cursor,
1099+ uid, rule_br,
1100+ lookup_date, context)
1101+ data_dict = {'lookup_date': lookup_date, 'line_ids': tuple(lines),
1102+ 'delay': rule_br.delay_days}
1103+
1104+ cursor.execute(sql, data_dict)
1105+ res = cursor.fetchall()
1106+ if not res:
1107+ return []
1108+ return [x[0] for x in res]
1109+
1110+
1111+ def _get_other_level_lines(self, cursor, uid, rule_br, lookup_date, lines, context=None):
1112+ # We filter line that have a level smaller than current one
1113+ # TODO if code fits need refactor _get_first_level_lines and _get_other_level_lines
1114+ # Code is not DRY
1115+ if not lines:
1116+ return []
1117+ sql = ("SELECT mv_line.id\n"
1118+ " FROM account_move_line mv_line\n"
1119+ " JOIN credit_management_line cr_line\n"
1120+ " ON (mv_line.id = cr_line.move_line_id)\n"
1121+ " WHERE cr_line.id = (SELECT credit_management_line.id FROM credit_management_line\n"
1122+ " WHERE credit_management_line.move_line_id = mv_line.id\n"
1123+ " ORDER BY credit_management_line.level desc limit 1)\n"
1124+ " AND cr_line.level < %(level)s\n"
1125+ " AND mv_line.id in %(line_ids)s\n")
1126+ sql += " AND " + self._get_sql_date_boundary_for_computation_mode(cursor,
1127+ uid, rule_br,
1128+ lookup_date, context)
1129+ data_dict = {'lookup_date': lookup_date, 'line_ids': tuple(lines),
1130+ 'delay': rule_br.delay_days, 'level': rule_br.level}
1131+
1132+ cursor.execute(sql, data_dict)
1133+ res = cursor.fetchall()
1134+ if not res:
1135+ return []
1136+ return [x[0] for x in res]
1137+
1138+ def get_rule_lines(self, cursor, uid, rule_id, lookup_date, lines, context=None):
1139+ """get all move lines in entry lines that match the current rule"""
1140+ if isinstance(rule_id, list):
1141+ rule_id = rule_id[0]
1142+ matching_lines = []
1143+ rule = self.browse(cursor, uid, rule_id, context=context)
1144+ if self._is_first_level(cursor, uid, rule):
1145+ matching_lines += self._get_first_level_lines(cursor, uid, rule, lookup_date,
1146+ lines, context=context)
1147+ else:
1148+ matching_lines += self._get_other_level_lines(cursor, uid, rule, lookup_date,
1149+ lines, context=context)
1150+
1151+ return matching_lines
1152
1153=== added file 'account_credit_management/credit_management_profile_view.xml'
1154--- account_credit_management/credit_management_profile_view.xml 1970-01-01 00:00:00 +0000
1155+++ account_credit_management/credit_management_profile_view.xml 2012-10-15 10:14:35 +0000
1156@@ -0,0 +1,118 @@
1157+<openerp>
1158+ <data>
1159+
1160+ <record id="credit_management_profile_form" model="ir.ui.view">
1161+ <field name="name">credit.management.profile.form</field>
1162+ <field name="model">credit.management.profile</field>
1163+ <field name="type">form</field>
1164+ <field name="arch" type="xml">
1165+ <form> <!-- editable="bottom" -->
1166+ <field name="name"/>
1167+ <field name="do_nothing"/>
1168+ <field name="company_id"/>
1169+ <notebook colspan="4">
1170+ <page string="Rules">
1171+ <field name="profile_rule_ids" colspan="4" >
1172+ <tree editable="bottom">
1173+ <field name="name"/>
1174+ <field name="level"/>
1175+ <field name="canal"/>
1176+ <field name="delay_days"/>
1177+ <field name="computation_mode"/>
1178+ <field name="mail_template_id"/>
1179+ </tree>
1180+ <form>
1181+ <field name="name"/>
1182+ <notebook colspan="4">
1183+ <page string="Delay Setting">
1184+ <field name="level"/>
1185+ <field name="canal"/>
1186+ <field name="delay_days"/>
1187+ <field name="computation_mode"/>
1188+ </page>
1189+ <page string="Mail and reporting">
1190+ <field name="mail_template_id"/>
1191+ <field name="custom_text"/>
1192+ </page>
1193+ </notebook>
1194+ </form>
1195+ </field>
1196+ </page>
1197+ </notebook>
1198+ </form>
1199+ </field>
1200+ </record>
1201+
1202+ <record id="credit_management_profile_tree" model="ir.ui.view">
1203+ <field name="name">credit.management.profile.tree</field>
1204+ <field name="model">credit.management.profile</field>
1205+ <field name="type">tree</field>
1206+ <field name="arch" type="xml">
1207+ <tree> <!-- editable="bottom" -->
1208+ <field name="name"/>
1209+ <field name="do_nothing"/>
1210+ </tree>
1211+ </field>
1212+ </record>
1213+
1214+ <menuitem
1215+ name="Credit management configuration"
1216+ parent="account.menu_finance_configuration"
1217+ id="base_credit_management_configuration_menu"/>
1218+
1219+ <record model="ir.actions.act_window" id="credit_profile_configuration_action">
1220+ <field name="name">Credit profiles</field>
1221+ <field name="type">ir.actions.act_window</field>
1222+ <field name="res_model">credit.management.profile</field>
1223+ <field name="domain"></field>
1224+ <field name="view_type">form</field>
1225+ <field name="view_mode">tree,form</field>
1226+ <field name="view_id" ref="credit_management_profile_tree"/>
1227+ </record>
1228+
1229+ <menuitem
1230+ name="Credit profiles"
1231+ parent="base_credit_management_configuration_menu"
1232+ action="credit_profile_configuration_action"
1233+ id="credit_profile_configuration_action_menu"/>
1234+
1235+ <record id="credit_mangement_profile_rule_form" model="ir.ui.view">
1236+ <field name="name">credit.mangement.profile.rule.form</field>
1237+ <field name="model">credit.management.profile.rule</field>
1238+ <field name="type">form</field>
1239+ <field name="arch" type="xml">
1240+ <form> <!-- editable="bottom" -->
1241+ <field name="name"/>
1242+ <notebook colspan="4">
1243+ <page string="Delay Setting">
1244+ <field name="level"/>
1245+ <field name="canal"/>
1246+ <field name="delay_days"/>
1247+ <field name="computation_mode"/>
1248+ </page>
1249+ <page string="Mail and reporting">
1250+ <field name="mail_template_id"/>
1251+ <field name="custom_text"/>
1252+ </page>
1253+ </notebook>
1254+ </form>
1255+ </field>
1256+ </record>
1257+
1258+ <record id="credit_management_profile_rule_tree" model="ir.ui.view">
1259+ <field name="name">credit.management.profile.rule.tree</field>
1260+ <field name="model">credit.management.profile.rule</field>
1261+ <field name="type">tree</field>
1262+ <field name="arch" type="xml">
1263+ <tree editable="bottom">
1264+ <field name="name"/>
1265+ <field name="level"/>
1266+ <field name="canal"/>
1267+ <field name="delay_days"/>
1268+ <field name="computation_mode"/>
1269+ <field name="mail_template_id"/>
1270+ </tree>
1271+ </field>
1272+ </record>
1273+ </data>
1274+</openerp>
1275
1276=== added file 'account_credit_management/credit_management_run.py'
1277--- account_credit_management/credit_management_run.py 1970-01-01 00:00:00 +0000
1278+++ account_credit_management/credit_management_run.py 2012-10-15 10:14:35 +0000
1279@@ -0,0 +1,150 @@
1280+# -*- coding: utf-8 -*-
1281+##############################################################################
1282+#
1283+# Author: Nicolas Bessi
1284+# Copyright 2012 Camptocamp SA
1285+#
1286+# This program is free software: you can redistribute it and/or modify
1287+# it under the terms of the GNU Affero General Public License as
1288+# published by the Free Software Foundation, either version 3 of the
1289+# License, or (at your option) any later version.
1290+#
1291+# This program is distributed in the hope that it will be useful,
1292+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1293+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1294+# GNU Affero General Public License for more details.
1295+#
1296+# You should have received a copy of the GNU Affero General Public License
1297+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1298+#
1299+##############################################################################
1300+import sys
1301+import traceback
1302+import logging
1303+
1304+from openerp.osv.orm import Model, fields
1305+from openerp.tools.translate import _
1306+from openerp.osv.osv import except_osv
1307+
1308+logger = logging.getLogger('Credit management run')
1309+
1310+memoizers = {}
1311+
1312+
1313+class CreditManagementRun(Model):
1314+ """Credit management run generate all credit management lines and reject"""
1315+
1316+ _name = "credit.management.run"
1317+ _rec_name = 'date'
1318+ _description = """Credit management line generator"""
1319+ _columns = {'date': fields.date('Lookup date', required=True),
1320+ 'profile_ids': fields.many2many('credit.management.profile',
1321+ rel="credit_run_profile_rel",
1322+ string='Profiles',
1323+ readonly=True,
1324+ help="If nothing set all profile will be used",
1325+
1326+ states={'draft': [('readonly', False)]}),
1327+
1328+ 'report': fields.text('Report', readonly=True),
1329+
1330+ 'state': fields.selection([('draft', 'Draft'),
1331+ ('running', 'Running'),
1332+ ('done', 'Done'),
1333+ ('error', 'Error')],
1334+ string='State',
1335+ required=True,
1336+ readonly=True),
1337+
1338+ 'manual_ids': fields.many2many('account.move.line',
1339+ rel="credit_runreject_rel",
1340+ string='Line to be handled manually',
1341+ readonly=True),
1342+ }
1343+
1344+ _defaults = {'state': 'draft'}
1345+
1346+ def check_run_date(self, cursor, uid, ids, lookup_date, context=None):
1347+ """Ensure that there is no credit line in the future using lookup_date"""
1348+ line_obj = self.pool.get('credit.management.line')
1349+ lines = line_obj.search(cursor, uid, [('date', '>', lookup_date)],
1350+ order='date DESC', limit=1)
1351+ if lines:
1352+ line = line_obj.browse(cursor, uid, lines[0])
1353+ raise except_osv(_('A run was already executed in a greater date'),
1354+ _('Run date should be >= %s') % (line.date))
1355+
1356+
1357+ def _generate_credit_lines(self, cursor, uid, run_id, context=None):
1358+ """ Generate credit line. Function can be a little dryer but
1359+ it does almost noting, initalise variable maange error and call
1360+ real know how method"""
1361+ memoizers['credit_line_residuals'] = {}
1362+ cr_line_obj = self.pool.get('credit.management.line')
1363+ if isinstance(run_id, list):
1364+ run_id = run_id[0]
1365+ run = self.browse(cursor, uid, run_id, context=context)
1366+ errors = []
1367+ manualy_managed_lines = [] #line who changed profile
1368+ credit_line_ids = [] # generated lines
1369+ run.check_run_date(run.date, context=context)
1370+ profile_ids = run.profile_ids
1371+ if not profile_ids:
1372+ profile_obj = self.pool.get('credit.management.profile')
1373+ profile_ids_ids = profile_obj.search(cursor, uid, [])
1374+ profile_ids = profile_obj.browse(cursor, uid, profile_ids_ids)
1375+ for profile in profile_ids:
1376+ if profile.do_nothing:
1377+ continue
1378+ try:
1379+ lines = profile._get_moves_line_to_process(run.date, context=context)
1380+ tmp_manual = profile._check_lines_profiles(lines, context=context)
1381+ lines = list(set(lines) - set(tmp_manual))
1382+ manualy_managed_lines += tmp_manual
1383+ if not lines:
1384+ continue
1385+ # profile rules are sorted by level so iteration is in the correct order
1386+ for rule in profile.profile_rule_ids:
1387+ rule_lines = rule.get_rule_lines(run.date, lines)
1388+ #only this write action own a separate cursor
1389+ credit_line_ids += cr_line_obj.create_or_update_from_mv_lines(cursor, uid, [],
1390+ rule_lines, rule.id,
1391+ run.date, errors=errors,
1392+ context=context)
1393+ lines = list(set(lines) - set(rule_lines))
1394+ except Exception, exc:
1395+ cursor.rollback()
1396+ error_type, error_value, trbk = sys.exc_info()
1397+ st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
1398+ st += ''.join(traceback.format_tb(trbk, 30))
1399+ logger.error(st)
1400+ self.write(cursor, uid, [run.id], {'report':st, 'state': 'error'})
1401+ return False
1402+ vals = {'report': u"Number of generated lines : %s \n" % (len(credit_line_ids),),
1403+ 'state': 'done',
1404+ 'manual_ids': [(6, 0, manualy_managed_lines)]}
1405+ if errors:
1406+ vals['report'] += u"Following line generation errors appends:"
1407+ vals['report'] += u"----\n".join(errors)
1408+ vals['state'] = 'done'
1409+ run.write(vals)
1410+ # lines will correspond to line that where not treated
1411+ return lines
1412+
1413+
1414+
1415+ def generate_credit_lines(self, cursor, uid, run_id, context=None):
1416+ """Generate credit management lines"""
1417+ context = context or {}
1418+ # we do a little magical tips in order to ensure non concurrent run
1419+ # of the function generate_credit_lines
1420+ try:
1421+ cursor.execute('SELECT id FROM credit_management_run'
1422+ ' LIMIT 1 FOR UPDATE NOWAIT' )
1423+ except Exception, exc:
1424+ cursor.rollback()
1425+ raise except_osv(_('A credit management run is allready running'
1426+ ' in background please try later'),
1427+ str(exc))
1428+ # in case of exception openerp will do a rollback for us and free the lock
1429+ return self._generate_credit_lines(cursor, uid, run_id, context)
1430
1431=== added file 'account_credit_management/credit_management_run_view.xml'
1432--- account_credit_management/credit_management_run_view.xml 1970-01-01 00:00:00 +0000
1433+++ account_credit_management/credit_management_run_view.xml 2012-10-15 10:14:35 +0000
1434@@ -0,0 +1,65 @@
1435+<openerp>
1436+ <data>
1437+
1438+ <record id="credit_management_run_tree" model="ir.ui.view">
1439+ <field name="name">credit.management.run.tree</field>
1440+ <field name="model">credit.management.run</field>
1441+ <field name="type">tree</field>
1442+ <field name="arch" type="xml">
1443+ <tree> <!-- editable="bottom" -->
1444+ <field name="date"/>
1445+ <field name="state"/>
1446+ </tree>
1447+ </field>
1448+ </record>
1449+
1450+ <record id="credit_management_run_form" model="ir.ui.view">
1451+ <field name="name">credit.management.run.form</field>
1452+ <field name="model">credit.management.run</field>
1453+ <field name="type">form</field>
1454+ <field name="arch" type="xml">
1455+ <form> <!-- editable="bottom" -->
1456+ <field name="date"/>
1457+ <newline/>
1458+ <notebook colspan="4">
1459+ <page string="Profile">
1460+ <field name="profile_ids" colspan="4" nolabel="1"/>
1461+ </page>
1462+ <page string="Report and Errors">
1463+ <field name="report" colspan="4" nolabel="1"/>
1464+ <separator string="Move lines To be treated manually"/>
1465+ <field name="manual_ids" colspan="4" nolabel="1"/>
1466+ </page>
1467+ </notebook>
1468+ <group col="3" colspan="4">
1469+ <button name="generate_credit_lines"
1470+ string="Compute credit lines"
1471+ colspan="1"
1472+ type="object" icon="gtk-execute"
1473+ attrs="{'invisible': [('state', '!=', 'draft')]}"/>
1474+ </group>
1475+ <field name="state"/>
1476+ </form>
1477+ </field>
1478+ </record>
1479+
1480+
1481+ <record model="ir.actions.act_window" id="credit_management_run">
1482+ <field name="name">Credit management run</field>
1483+ <field name="type">ir.actions.act_window</field>
1484+ <field name="res_model">credit.management.run</field>
1485+ <field name="domain"></field>
1486+ <field name="view_type">form</field>
1487+ <field name="view_mode">tree,form</field>
1488+ <field name="view_id" ref="credit_management_run_tree"/>
1489+ </record>
1490+
1491+
1492+ <menuitem
1493+ name="Credit management run"
1494+ parent="base_credit_management_menu"
1495+ action="credit_management_run"
1496+ id="credit_management_run_menu"/>
1497+
1498+ </data>
1499+</openerp>
1500
1501=== added file 'account_credit_management/data.xml'
1502--- account_credit_management/data.xml 1970-01-01 00:00:00 +0000
1503+++ account_credit_management/data.xml 2012-10-15 10:14:35 +0000
1504@@ -0,0 +1,173 @@
1505+<openerp>
1506+ <data noupdate="1">
1507+ <!--Email template -->
1508+ <record id="email_template_credit_management_base" model="email.template">
1509+ <field name="name">Credit Management demo mail</field>
1510+ <field name="email_from">noreply@localhost</field>
1511+ <field name="subject">Credit Management Invoice (${object.current_profile_rule.level or 'n/a' })</field>
1512+ <field name="email_to">${object.get_mail() or ''}</field>
1513+ <field name="model_id" ref="model_credit_management_communication"/>
1514+ <field name="auto_delete" eval="True"/>
1515+ <field name="body_html"><![CDATA[
1516+ <%page args="object, mode" />
1517+ %if mode != 'pdf':
1518+ <!-- your css here -->
1519+ <style type="text/css">
1520+ </style>
1521+ %endif
1522+ <div>
1523+
1524+ <p>Dear ${object.partner_id.name or ''},</p>
1525+
1526+ <pre class="custom_text">${object.current_profile_rule.level.custom_text}</pre>
1527+
1528+ <table style="border: 1px solid" width="100%">
1529+ <caption><b>Summary</b></caption>
1530+ <tr>
1531+ <th>date due</th>
1532+ <th>Amount due</th>
1533+ <th>Amount balance</th>
1534+ <th>Invoice number</th>
1535+ </tr>
1536+%for line in object.credit_lines:
1537+ <tr>
1538+ <td>${line.date_due}</td>
1539+ <td>${line.amount_due}</td>
1540+ <td>${line.balance_due}</td>
1541+ %if line.invoice_id:
1542+ <td>${line.invoice_id.number}</td>
1543+ %else:
1544+ <td>n/a</td>
1545+ %endif
1546+%endfor
1547+ </table>
1548+ <br/>
1549+ <br/>
1550+
1551+ <p> If you have any question, do not hesitate to contact us.</p>
1552+
1553+
1554+ <p>Thank you for choosing ${object.company_id.name}! </p>
1555+
1556+ -- more info here --
1557+ <p>${object.user_id.name} ${object.user_id.user_email and '<%s>'%(object.user_id.user_email) or ''}<br/>
1558+ ${object.company_id.name}<br/>
1559+ % if object.company_id.street:
1560+ ${object.company_id.street or ''}<br/>
1561+
1562+ % endif
1563+
1564+ % if object.company_id.street2:
1565+ ${object.company_id.street2}<br/>
1566+ % endif
1567+ % if object.company_id.city or object.company_id.zip:
1568+ ${object.company_id.zip or ''} ${object.company_id.city or ''}<br/>
1569+ % endif
1570+ % if object.company_id.country_id:
1571+ ${object.company_id.state_id and ('%s, ' % object.company_id.state_id.name) or ''} ${object.company_id.country_id.name or ''}<br/>
1572+ % endif
1573+ % if object.company_id.phone:
1574+ Phone: ${object.company_id.phone}<br/>
1575+ % endif
1576+ % if object.company_id.website:
1577+ ${object.company_id.website or ''}<br/>
1578+ % endif
1579+ ]]></field>
1580+ </record>
1581+
1582+ <!-- profile no follow -->
1583+ <record model="credit.management.profile"
1584+ id="credit_management_no_follow">
1585+ <field name="name">No follow</field>
1586+ <field name="do_nothing" eval="1"/>
1587+ </record>
1588+
1589+ <!-- profile 1 -->
1590+ <record model="credit.management.profile"
1591+ id="credit_management_3_time">
1592+ <field name="name">3 time policy</field>
1593+ </record>
1594+
1595+ <record model="credit.management.profile.rule"
1596+ id="3_time_1">
1597+ <field name="name">10 days net</field>
1598+ <field name="level" eval="1"/>
1599+ <field name="computation_mode">net_days</field>
1600+ <field name="delay_days" eval="10"/>
1601+ <field name="mail_template_id" ref="email_template_credit_management_base"/>
1602+ <field name="profile_id" ref="credit_management_3_time"/>
1603+ <field name="canal">mail</field>
1604+ <field name="custom_text">Replace this text in rule by your message</field>
1605+ </record>
1606+
1607+ <record model="credit.management.profile.rule"
1608+ id="3_time_2">
1609+ <field name="name">30 days end of month</field>
1610+ <field name="level" eval="2"/>
1611+ <field name="computation_mode">end_of_month</field>
1612+ <field name="delay_days" eval="30"/>
1613+ <field name="mail_template_id" ref="email_template_credit_management_base"/>
1614+ <field name="profile_id" ref="credit_management_3_time"/>
1615+ <field name="canal">mail</field>
1616+ <field name="custom_text">Replace this text in rule by your message</field>
1617+ </record>
1618+
1619+ <record model="credit.management.profile.rule"
1620+ id="3_time_3">
1621+ <field name="name">10 days sommation</field>
1622+ <field name="level" eval="3"/>
1623+ <field name="computation_mode">previous_date</field>
1624+ <field name="delay_days" eval="10"/>
1625+ <field name="mail_template_id" ref="email_template_credit_management_base"/>
1626+ <field name="profile_id" ref="credit_management_3_time"/>
1627+ <field name="canal">manual</field>
1628+ <field name="custom_text">Replace this text in rule by your message</field>
1629+ </record>
1630+
1631+ <!-- profile 2 -->
1632+ <record model="credit.management.profile"
1633+ id="credit_management_2_time">
1634+ <field name="name">2 time policy</field>
1635+ </record>
1636+
1637+ <record model="credit.management.profile.rule"
1638+ id="2_time_1">
1639+ <field name="name">30 days end of month</field>
1640+ <field name="level" eval="1"/>
1641+ <field name="computation_mode">end_of_month</field>
1642+ <field name="delay_days" eval="30"/>
1643+ <field name="mail_template_id" ref="email_template_credit_management_base"/>
1644+ <field name="profile_id" ref="credit_management_2_time"/>
1645+ <field name="canal">mail</field>
1646+ <field name="custom_text">Replace this text in rule by your message</field>
1647+ </record>
1648+
1649+ <record model="credit.management.profile.rule"
1650+ id="2_time_2">
1651+ <field name="name">60 days sommation</field>
1652+ <field name="level" eval="2"/>
1653+ <field name="computation_mode">previous_date</field>
1654+ <field name="delay_days" eval="60"/>
1655+ <field name="mail_template_id" ref="email_template_credit_management_base"/>
1656+ <field name="profile_id" ref="credit_management_2_time"/>
1657+ <field name="canal">manual</field>
1658+ <field name="custom_text">Replace this text in rule by your message</field>
1659+ </record>
1660+
1661+ <record id="group_account_credit_management_manager" model="res.groups">
1662+ <field name="name">Credit Management manager</field>
1663+ <field name="category_id" ref="base.module_category_accounting_and_finance"/>
1664+ </record>
1665+
1666+ <record id="group_account_credit_management_user" model="res.groups" context="{'noadmin':True}">
1667+ <field name="name">Credit Management user</field>
1668+ <field name="category_id" ref="base.module_category_accounting_and_finance"/>
1669+ </record>
1670+
1671+ <record id="group_account_credit_management_info" model="res.groups" context="{'noadmin':True}">
1672+ <field name="name">Credit Management info</field>
1673+ <field name="category_id" ref="base.module_category_accounting_and_finance"/>
1674+ </record>
1675+
1676+ </data>
1677+</openerp>
1678
1679=== added directory 'account_credit_management/i18n'
1680=== added directory 'account_credit_management/report'
1681=== added file 'account_credit_management/report/__init__.py'
1682--- account_credit_management/report/__init__.py 1970-01-01 00:00:00 +0000
1683+++ account_credit_management/report/__init__.py 2012-10-15 10:14:35 +0000
1684@@ -0,0 +1,1 @@
1685+from . import credit_management_summary
1686
1687=== added file 'account_credit_management/report/credit_management_summary.html.mako'
1688--- account_credit_management/report/credit_management_summary.html.mako 1970-01-01 00:00:00 +0000
1689+++ account_credit_management/report/credit_management_summary.html.mako 2012-10-15 10:14:35 +0000
1690@@ -0,0 +1,19 @@
1691+<html>
1692+<head>
1693+ <style type="text/css">
1694+ ${css}
1695+ </style>
1696+</head>
1697+<body>
1698+ %for comm in objects :
1699+ ${setLang(comm.partner_id.lang)}
1700+ <%
1701+ current_uri = '%s_profile_template' % (comm.partner_id.lang)
1702+ if not context.lookup.has_template(current_uri):
1703+ context.lookup.put_string(current_uri, comm.current_profile_rule.mail_template_id.body_html)
1704+ %>
1705+ <%include file="${current_uri}" args="object=comm,mode='pdf'"/>
1706+ </br>
1707+ %endfor
1708+</body>
1709+</html>
1710
1711=== added file 'account_credit_management/report/credit_management_summary.py'
1712--- account_credit_management/report/credit_management_summary.py 1970-01-01 00:00:00 +0000
1713+++ account_credit_management/report/credit_management_summary.py 2012-10-15 10:14:35 +0000
1714@@ -0,0 +1,37 @@
1715+# -*- coding: utf-8 -*-
1716+##############################################################################
1717+#
1718+# Author: Nicolas Bessi
1719+# Copyright 2012 Camptocamp SA
1720+#
1721+# This program is free software: you can redistribute it and/or modify
1722+# it under the terms of the GNU Affero General Public License as
1723+# published by the Free Software Foundation, either version 3 of the
1724+# License, or (at your option) any later version.
1725+#
1726+# This program is distributed in the hope that it will be useful,
1727+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1728+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1729+# GNU Affero General Public License for more details.
1730+#
1731+# You should have received a copy of the GNU Affero General Public License
1732+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1733+#
1734+##############################################################################
1735+import time
1736+
1737+from openerp.report import report_sxw
1738+
1739+class CreditSummaryReport(report_sxw.rml_parse):
1740+ def __init__(self, cr, uid, name, context):
1741+ super(CreditSummaryReport, self).__init__(cr, uid, name, context=context)
1742+ self.localcontext.update({
1743+ 'time': time,
1744+ 'cr':cr,
1745+ 'uid': uid,
1746+ })
1747+
1748+report_sxw.report_sxw('report.credit_management_summary',
1749+ 'credit.management.communication',
1750+ 'addons/account_credit_management/report/credit_management_summary.html.mako',
1751+ parser=CreditSummaryReport)
1752
1753=== added file 'account_credit_management/report/report.xml'
1754--- account_credit_management/report/report.xml 1970-01-01 00:00:00 +0000
1755+++ account_credit_management/report/report.xml 2012-10-15 10:14:35 +0000
1756@@ -0,0 +1,12 @@
1757+<openerp>
1758+ <data>
1759+ <report auto="False"
1760+ id="report_webkit_html"
1761+ model="credit.management.communication"
1762+ name="credit_management_summary"
1763+ file="account_credit_management/report/credit_management_summary.html.mako"
1764+ string="Credit Summary"
1765+ report_type="webkit"
1766+ webkit_header="report_webkit.ir_header_webkit_basesample0"/>
1767+ </data>
1768+</openerp>
1769
1770=== added directory 'account_credit_management/scenarios'
1771=== added directory 'account_credit_management/scenarios/credit_management'
1772=== added file 'account_credit_management/scenarios/credit_management/00_credit_management_param.feature'
1773--- account_credit_management/scenarios/credit_management/00_credit_management_param.feature 1970-01-01 00:00:00 +0000
1774+++ account_credit_management/scenarios/credit_management/00_credit_management_param.feature 2012-10-15 10:14:35 +0000
1775@@ -0,0 +1,23 @@
1776+###############################################################################
1777+#
1778+# OERPScenario, OpenERP Functional Tests
1779+# Copyright 2009 Camptocamp SA
1780+#
1781+##############################################################################
1782+##############################################################################
1783+# Branch # Module # Processes # System
1784+@credit_management_module @credit_management_param
1785+
1786+Feature: In order to validate account voucher behavious as an admin user I prepare data
1787+ @credit_management_addon_install
1788+ Scenario: Install module
1789+ Given I need a "ir.module.module" with name: account_voucher
1790+ And having:
1791+ |name | value |
1792+ | demo | 0 |
1793+
1794+ Given I do not want all demo data to be loaded on install
1795+ And I install the required modules with dependencies:
1796+ | name |
1797+ | account_credit_management |
1798+ Then my modules should have been installed and models reloaded
1799\ No newline at end of file
1800
1801=== added file 'account_credit_management/scenarios/credit_management/01_credit_management_partners.feature'
1802--- account_credit_management/scenarios/credit_management/01_credit_management_partners.feature 1970-01-01 00:00:00 +0000
1803+++ account_credit_management/scenarios/credit_management/01_credit_management_partners.feature 2012-10-15 10:14:35 +0000
1804@@ -0,0 +1,60 @@
1805+###############################################################################
1806+#
1807+# OERPScenario, OpenERP Functional Tests
1808+# Copyright 2009 Camptocamp SA
1809+#
1810+##############################################################################
1811+##############################################################################
1812+# Branch # Module # Processes # System
1813+@credit_management_module @credit_management_partner
1814+
1815+Feature: I add profile to partners already created
1816+ @credit_management_partner_1
1817+ Scenario: Partner_1
1818+ Given I need a "res.partner" with oid: scen.partner_1
1819+ And having:
1820+ | name | value |
1821+ | name | partner_1 |
1822+ | credit_profile_id | by name: No follow |
1823+
1824+ @credit_management_customer_1
1825+ Scenario: Customer_1
1826+ Given I need a "res.partner" with oid: scen.customer_1
1827+ And having:
1828+ | name | value |
1829+ | name | customer_1 |
1830+ | credit_profile_id | by name: 2 time policy|
1831+
1832+ @credit_management_customer_2
1833+ Scenario: Customer_2
1834+ Given I need a "res.partner" with oid: scen.customer_2
1835+ And having:
1836+ | name | value |
1837+ | name | customer_2 |
1838+ | credit_profile_id | by name: 2 time policy|
1839+
1840+ @credit_management_customer_3
1841+ Scenario: Customer_3
1842+ Given I need a "res.partner" with oid: scen.customer_3
1843+ And having:
1844+ | name | value |
1845+ | name | customer_3 |
1846+ | credit_profile_id | by name: 2 time policy|
1847+
1848+ @credit_management_customer_4
1849+ Scenario: Customer_4
1850+ Given I need a "res.partner" with oid: scen.customer_4
1851+ And having:
1852+ | name | value |
1853+ | name | customer_4 |
1854+ | credit_profile_id | by name: 3 time policy|
1855+
1856+ @credit_management_customer_5
1857+ Scenario: Customer_5
1858+ Given I need a "res.partner" with oid: scen.customer_5
1859+ And having:
1860+ | name | value |
1861+ | name | customer_5_usd |
1862+ | credit_profile_id | by name: 3 time policy|
1863+
1864+
1865
1866=== added file 'account_credit_management/scenarios/credit_management/credit_management_01_data.feature'
1867--- account_credit_management/scenarios/credit_management/credit_management_01_data.feature 1970-01-01 00:00:00 +0000
1868+++ account_credit_management/scenarios/credit_management/credit_management_01_data.feature 2012-10-15 10:14:35 +0000
1869@@ -0,0 +1,224 @@
1870+###############################################################################
1871+#
1872+# OERPScenario, OpenERP Functional Tests
1873+# Copyright 2012 Camptocamp SA
1874+# Author Nicolas Bessi
1875+##############################################################################
1876+
1877+# Features Generic tags (none for all)
1878+##############################################################################
1879+
1880+@credit_management_module @credit_management_base_data
1881+
1882+Feature: Ensure that mail credit management is correct
1883+
1884+
1885+ @credit_management_data
1886+ Scenario: Create data
1887+ Given I need a "ir.module.module" with name: account_credit_management
1888+ And having:
1889+ |name|value|
1890+ | demo | 1 |
1891+ Given I install the required modules with dependencies:
1892+ | name |
1893+ | account_credit_management |
1894+ Given I need a "res.partner" with oid: credit_management.trusted_partner
1895+ And having:
1896+ | name | value |
1897+ | name | Credit m. trusted partner |
1898+ | customer | 1 |
1899+ | credit_profile_id | by name: No follow |
1900+
1901+ Given I need a "res.partner.address" with oid: credit_management.trusted_address
1902+ And having:
1903+ | name | value |
1904+ | name | Luc Maurer |
1905+ | zip | 1015 |
1906+ | city | lausanne |
1907+ | email | openerp@locahost.dummy |
1908+ | phone | +41 21 619 10 12 |
1909+ | street | PSE-A, EPF |
1910+ | partner_id | by oid: credit_management.trusted_partner |
1911+
1912+
1913+ Given I need a "res.partner" with oid: credit_management.not_so_trusted_partner
1914+ And having:
1915+ | name | value |
1916+ | name | Credit m. not so trusted partner |
1917+ | customer | 1 |
1918+ | credit_profile_id | by name: 2 time policy |
1919+
1920+ Given I need a "res.partner.address" with oid: credit_management.not_so_trusted_address
1921+ And having:
1922+ | name | value |
1923+ | name | Not so trusted |
1924+ | zip | 1015 |
1925+ | city | lausanne |
1926+ | email | openerp@locahost.dummy |
1927+ | phone | +41 21 619 10 12 |
1928+ | street | PSE-A, EPF |
1929+ | partner_id | by oid: credit_management.not_so_trusted_partner |
1930+
1931+
1932+ Given I need a "res.partner" with oid: credit_management.untrusted_partner
1933+ And having:
1934+ | name | value |
1935+ | name | Credit m. untrusted partner |
1936+ | customer | 1 |
1937+ | credit_profile_id | by name: 3 time policy |
1938+
1939+ Given I need a "res.partner.address" with oid: credit_management.untrusted_address
1940+ And having:
1941+ | name | value |
1942+ | name | Untrusted |
1943+ | zip | 1015 |
1944+ | city | lausanne |
1945+ | email | openerp@locahost.dummy |
1946+ | phone | +41 21 619 10 12 |
1947+ | street | PSE-A, EPF |
1948+ | partner_id | by oid: credit_management.untrusted_partner |
1949+
1950+
1951+ Given I need a "res.partner" with oid: credit_management.lambda_partner
1952+ And having:
1953+ | name | value |
1954+ | name | Credit m. lambda partner |
1955+ | customer | 1 |
1956+
1957+ Given I need a "res.partner.address" with oid: credit_management.lambda_address
1958+ And having:
1959+ | name | value |
1960+ | name | Lambda |
1961+ | zip | 1015 |
1962+ | city | lausanne |
1963+ | email | openerp@locahost.dummy |
1964+ | phone | +41 21 619 10 12 |
1965+ | street | PSE-A, EPF |
1966+ | partner_id | by oid: credit_management.lambda_partner |
1967+
1968+
1969+
1970+ @inv1
1971+ Scenario: invoices
1972+ # trusted invoice on trusted partner no follow
1973+ Given I need a "account.invoice" with oid: credit_management.inv1
1974+ And having:
1975+ | name | value |
1976+ | name | trusted invoice 1 |
1977+ | date_invoice | 2012-01-01 |
1978+ | date_due | 2012-02-15 |
1979+ | address_invoice_id | by oid: credit_management.trusted_address |
1980+ | partner_id | by oid: credit_management.trusted_partner |
1981+ | account_id | by name: Trusted Debtors - (test) |
1982+ | journal_id | by name: Sales Journal - (test) |
1983+
1984+
1985+ Given I need a "account.invoice.line" with oid: credit_management.inv_line1
1986+ And having:
1987+ | name | value |
1988+ | name | trusted invoice line 1 |
1989+ | quantity | 1 |
1990+ | price_unit | 100 |
1991+ | account_id | by name: Product Sales - (test) |
1992+ | invoice_id | by oid: credit_management.inv1 |
1993+ Given I find a "account.invoice" with oid: credit_management.inv1
1994+ And I open the credit invoice
1995+
1996+
1997+
1998+ Given I need a "account.invoice" with oid: credit_management.inv2
1999+ And having:
2000+ | name | value |
2001+ | name | lambda invoice 1 |
2002+ | date_invoice | 2012-01-01 |
2003+ | date_due | 2012-02-15 |
2004+ | address_invoice_id | by oid: credit_management.lambda_address |
2005+ | partner_id | by oid: credit_management.lambda_partner |
2006+ | account_id | by name: Not so trusted Debtors - (test) |
2007+ | journal_id | by name: Sales Journal - (test) |
2008+
2009+ Given I need a "account.invoice.line" with oid: credit_management.inv_line2
2010+ And having:
2011+ | name | value |
2012+ | name | lambda invoice line 1 |
2013+ | quantity | 1 |
2014+ | price_unit | 130 |
2015+ | account_id | by name: Product Sales - (test) |
2016+ | invoice_id | by oid: credit_management.inv2 |
2017+ Given I find a "account.invoice" with oid: credit_management.inv2
2018+ And I open the credit invoice
2019+
2020+
2021+ Given I need a "account.invoice" with oid: credit_management.inv3
2022+ And having:
2023+ | name | value |
2024+ | name | Not so trusted invoice |
2025+ | date_invoice | 2012-01-01 |
2026+ | date_due | 2012-02-15 |
2027+ | address_invoice_id | by oid: credit_management.not_so_trusted_address |
2028+ | partner_id | by oid: credit_management.not_so_trusted_partner |
2029+ | account_id | by name: Not so trusted Debtors - (test) |
2030+ | journal_id | by name: Sales Journal - (test) |
2031+
2032+
2033+ Given I need a "account.invoice.line" with oid: credit_management.inv_line3
2034+ And having:
2035+ | name | value |
2036+ | name | Not so trusted invoice line 1 |
2037+ | quantity | 1 |
2038+ | price_unit | 150 |
2039+ | account_id | by name: Product Sales - (test) |
2040+ | invoice_id | by oid: credit_management.inv3 |
2041+ Given I find a "account.invoice" with oid: credit_management.inv3
2042+ And I open the credit invoice
2043+
2044+
2045+
2046+
2047+ Given I need a "account.invoice" with oid: credit_management.inv4
2048+ And having:
2049+ | name | value |
2050+ | name | Untrusted invoice |
2051+ | date_invoice | 2012-01-01 |
2052+ | date_due | 2012-02-15 |
2053+ | address_invoice_id | by oid: credit_management.untrusted_address |
2054+ | partner_id | by oid: credit_management.untrusted_partner |
2055+ | account_id | by name: Not so trusted Debtors - (test) |
2056+ | journal_id | by name: Sales Journal - (test) |
2057+
2058+
2059+ Given I need a "account.invoice.line" with oid: credit_management.inv_line4
2060+ And having:
2061+ | name | value |
2062+ | name | Un trusted invoice line 1 |
2063+ | quantity | 1 |
2064+ | price_unit | 170 |
2065+ | account_id | by name: Product Sales - (test) |
2066+ | invoice_id | by oid: credit_management.inv4 |
2067+ Given I find a "account.invoice" with oid: credit_management.inv4
2068+ And I open the credit invoice
2069+
2070+
2071+ Given I need a "account.invoice" with oid: credit_management.inv5
2072+ And having:
2073+ | name | value |
2074+ | name | lamba invoice with untrusted policy |
2075+ | date_invoice | 2012-01-01 |
2076+ | date_due | 2012-02-15 |
2077+ | address_invoice_id | by oid: credit_management.lambda_address |
2078+ | partner_id | by oid: credit_management.lambda_partner |
2079+ | account_id | by name: Not so trusted Debtors - (test) |
2080+ | journal_id | by name: Sales Journal - (test) |
2081+ | credit_profile_id | by name: 3 time policy |
2082+
2083+
2084+ Given I need a "account.invoice.line" with oid: credit_management.inv_line5
2085+ And having:
2086+ | name | value |
2087+ | name | Un trusted invoice line 1 |
2088+ | quantity | 1 |
2089+ | price_unit | 200 |
2090+ | account_id | by name: Product Sales - (test) |
2091+ | invoice_id | by oid: credit_management.inv5 |
2092+ Given I find a "account.invoice" with oid: credit_management.inv5
2093+ And I open the credit invoice
2094
2095=== added file 'account_credit_management/scenarios/credit_management/credit_management_02_run.feature'
2096--- account_credit_management/scenarios/credit_management/credit_management_02_run.feature 1970-01-01 00:00:00 +0000
2097+++ account_credit_management/scenarios/credit_management/credit_management_02_run.feature 2012-10-15 10:14:35 +0000
2098@@ -0,0 +1,33 @@
2099+###############################################################################
2100+#
2101+# OERPScenario, OpenERP Functional Tests
2102+# Copyright 2012 Camptocamp SA
2103+# Author Nicolas Bessi
2104+##############################################################################
2105+
2106+# Features Generic tags (none for all)
2107+##############################################################################
2108+
2109+@credit_management_module
2110+
2111+Feature: Ensure that mail credit line generation first pass is correct
2112+
2113+
2114+ @credit_management_first_run
2115+ Scenario: clean data
2116+ Given I clean all the credit lines
2117+ #Given I unreconcile and clean all move line
2118+
2119+ @credit_management_first_run
2120+ Scenario: Create run
2121+ Given I need a "credit.management.run" with oid: credit_management.run1
2122+ And having:
2123+ | name | value |
2124+ | date | 2012-03-01 |
2125+ When I launch the credit run
2126+ Then my credit run should be in state "done"
2127+ And I should have "2" credit lines of level "1"
2128+ And credit lines should have following values:
2129+ | balance | date due | account | profile | date | partner | canal | level | move line | profile rule | state | amount due | currency |
2130+ | 170 | 2012-02-15 | B2C Debtors - (test) | 3 time policy | 2012-03-01 | Credit m. untrusted partner | mail | 1.00 | Untrusted invoice | 10 days net | draft | 170 | |
2131+ | 200 | 2012-02-15 | B2C Debtors - (test) | 3 time policy | 2012-03-01 | Credit m. lambda partner | mail | 1.00 | lamba invoice with untrusted policy | 10 days net | draft | 200 | |
2132
2133=== added file 'account_credit_management/scenarios/credit_management/credit_management_03_run.feature'
2134--- account_credit_management/scenarios/credit_management/credit_management_03_run.feature 1970-01-01 00:00:00 +0000
2135+++ account_credit_management/scenarios/credit_management/credit_management_03_run.feature 2012-10-15 10:14:35 +0000
2136@@ -0,0 +1,26 @@
2137+###############################################################################
2138+#
2139+# OERPScenario, OpenERP Functional Tests
2140+# Copyright 2012 Camptocamp SA
2141+# Author Nicolas Bessi
2142+##############################################################################
2143+
2144+# Features Generic tags (none for all)
2145+##############################################################################
2146+
2147+@credit_management_mark_mail
2148+
2149+Feature: Ensure that mail credit line generation first pass is correct
2150+
2151+
2152+ @credit_management_mark
2153+ Scenario: mark lines
2154+ Given there is "draft" credit lines
2155+ And I mark all draft mail to state "to_be_sent"
2156+ Then the draft line should be in state "to_be_sent"
2157+
2158+ @credit_management_mail
2159+ Scenario: mail lines
2160+ Given there is "to_be_sent" credit lines
2161+ And I mail all ready lines
2162+ Then All sent lines should be linked to a mail and in mail status "sent"
2163
2164=== added directory 'account_credit_management/scenarios/credit_management/step_definitions'
2165=== added file 'account_credit_management/scenarios/credit_management/step_definitions/credit_management.rb'
2166--- account_credit_management/scenarios/credit_management/step_definitions/credit_management.rb 1970-01-01 00:00:00 +0000
2167+++ account_credit_management/scenarios/credit_management/step_definitions/credit_management.rb 2012-10-15 10:14:35 +0000
2168@@ -0,0 +1,115 @@
2169+# Given /^I open the credit invoice$/ do
2170+# @found_item.should_not be_nil,
2171+# "no invoice found"
2172+# ['draft', 'open'].should include(@found_item.state),
2173+# "Invoice is not draf or open"
2174+# if @found_item.state == 'draft'
2175+# @found_item.wkf_action('invoice_open')
2176+# end
2177+# end
2178+
2179+Then /^I launch the credit run$/ do
2180+ @found_item.should_not be_nil,
2181+ "no run found"
2182+ @found_item.generate_credit_lines()
2183+end
2184+
2185+Given /^there is "(.*?)" credit lines$/ do |state|
2186+ @credit_lines = CreditManagementLine.find_all_by_state(state)
2187+ @credit_lines.should_not be_empty,
2188+ "not #{state} lines found"
2189+end
2190+
2191+Given /^I mark all draft mail to state "(.*?)"$/ do | state |
2192+ wiz = CreditManagementMarker.new
2193+ wiz.name = state
2194+ wiz.mark_all = true
2195+ wiz.save
2196+ wiz.mark_lines
2197+end
2198+
2199+Then /^the draft line should be in state "(.*?)"$/ do | state |
2200+ @credit_lines.should_not be_nil,
2201+ "no line where stored"
2202+ @credit_lines.each do |line|
2203+ line = CreditManagementLine.find(line.id)
2204+ line.state.should eql(state),
2205+ "The line #{line.id} is not in state #{state} he is is in state #{line.state} "
2206+ end
2207+
2208+end
2209+
2210+Given /^I mail all ready lines$/ do
2211+ @credit_lines.should_not be_nil,
2212+ "no line where stored"
2213+ wiz = CreditManagementMailer.new
2214+ wiz.mail_all = true
2215+ wiz.save
2216+ wiz.mail_lines
2217+end
2218+
2219+Given /^I clean all the credit lines$/ do
2220+ CreditManagementLine.find(:all).each do | line |
2221+ line.destroy
2222+ end
2223+end
2224+
2225+Then /^my credit run should be in state "(.*?)"$/ do |state|
2226+ @found_item.should_not be nil
2227+ @run = CreditManagementRun.find(@found_item.id)
2228+ @run.state.should eq(state),
2229+ "Run state is in state #{@run.state} instead of #{state}"
2230+end
2231+
2232+
2233+Then /^All sent lines should be linked to a mail and in mail status "(.*?)"$/ do |status|
2234+ @credit_lines.should_not be_nil,
2235+ "no line where stored"
2236+ @credit_lines.each do |line|
2237+ line = CreditManagementLine.find(line.id)
2238+ line.state.should eql(status),
2239+ "The line #{line.id} is has no mail status #{status} but #{line.state}"
2240+ end
2241+end
2242+
2243+Then /^I should have "(.*?)" credit lines of level "(.*?)"$/ do |number, level|
2244+ CreditManagementLine.find_all_by_level(level).length.should eq (number.to_i)
2245+end
2246+
2247+Then /^credit lines should have following values:$/ do |table|
2248+ h_list = table.hashes
2249+ h_list.each do | h |
2250+ h.delete_if {|k, v| v.empty?}
2251+ end
2252+ errors = []
2253+ h_list.each do | row |
2254+ account = AccountAccount.find_by_name(row['account'], :fields=>['id'])
2255+ account.should_not be_nil, "no account named #{row['account']} found"
2256+
2257+ profile = CreditManagementProfile.find_by_name(row['profile'], :fields=>['id'])
2258+ profile.should_not be_nil, "No account #{row['account']} found"
2259+
2260+ partner = ResPartner.find_by_name(row['partner'], :fields=>['id'])
2261+ partner.should_not be_nil, "No partner #{row['partner']} found"
2262+
2263+ move_line = AccountMoveLine.find_by_name(row['move line'], :fields=>['id'])
2264+ move_line.should_not be_nil, "No move line #{row['move line']} found"
2265+
2266+ rule = CreditManagementProfileRule.find_by_name(row['profile rule'], :fields=>['id'])
2267+ rule.should_not be_nil, "No profile rune #{row['profile rule']} found"
2268+
2269+ domain = [['account_id', '=', account.id], ['profile_id', '=', profile.id],
2270+ ['partner_id', '=', partner.id], ['move_line_id', '=', move_line.id],
2271+ ['profile_rule_id', '=', rule.id], ['amount_due', '=', row.fetch('amount due', 0.0)],
2272+ ['state', '=', row.fetch('state')], ['level', '=', row.fetch('level', 0.0)],
2273+ ['canal', '=', row.fetch('canal')], ['balance_due', '=', row.fetch('balance', 0.0)],
2274+ ['date_due', '=', row.fetch('date due')], ['date', '=', row.fetch('date')]]
2275+ if row['currency']
2276+ curreny = ResCurrency.find_by_name(row['currency'], :fields=>['id'])
2277+ domain.push ['currency_id', '=', curreny.id]
2278+ end
2279+ pp domain
2280+ line = CreditManagementLine.find(:first, :domain=>domain)
2281+ line.should_not be_nil, "Can not find line #{row.inspect}"
2282+ end
2283+end
2284
2285=== added file 'account_credit_management/scenarios/run_command.txt'
2286--- account_credit_management/scenarios/run_command.txt 1970-01-01 00:00:00 +0000
2287+++ account_credit_management/scenarios/run_command.txt 2012-10-15 10:14:35 +0000
2288@@ -0,0 +1,1 @@
2289+cucumber CONF=nbessi.conf --tags=@credit_management_first_run features /opt/openerp_geoengine/src/c2c-financial-addons/account_credit_management/scenarios/
2290
2291=== added directory 'account_credit_management/security'
2292=== added file 'account_credit_management/security/ir.model.access.csv'
2293--- account_credit_management/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
2294+++ account_credit_management/security/ir.model.access.csv 2012-10-15 10:14:35 +0000
2295@@ -0,0 +1,27 @@
2296+"id","perm_create","perm_unlink","group_id/id","name","model_id/id","perm_read","perm_write"
2297+"account_credit_management.ir_model_access_270",1,1,"group_account_credit_management_manager","credit_management_manager_line","account_credit_management.model_credit_management_line",1,1
2298+"account_credit_management.ir_model_access_271",1,1,"group_account_credit_management_user","credit_management_user_line","account_credit_management.model_credit_management_line",1,1
2299+"account_credit_management.ir_model_access_272",0,0,"group_account_credit_management_info","credit_management_info_line","account_credit_management.model_credit_management_line",1,0
2300+"account_credit_management.ir_model_access_273",1,1,"group_account_credit_management_manager","credit_management_manager_mail_template","email_template.model_email_template",1,1
2301+"account_credit_management.ir_model_access_274",1,1,"group_account_credit_management_manager","credit_management_manager_mail_template_preview","email_template.model_email_template_preview",1,1
2302+"account_credit_management.ir_model_access_275",1,1,"group_account_credit_management_manager","credit_management_manager_mail_message","mail.model_mail_message",1,1
2303+"account_credit_management.ir_model_access_276",1,0,"group_account_credit_management_user","credit_management_user_mail_message","mail.model_mail_message",1,1
2304+"account_credit_management.ir_model_access_277",0,0,"group_account_credit_management_info","credit_management_info_mail_message","mail.model_mail_message",1,0
2305+"account_credit_management.ir_model_access_278",1,1,"group_account_credit_management_manager","credit_management_manager_comm","account_credit_management.model_credit_management_communication",1,1
2306+"account_credit_management.ir_model_access_279",1,1,"base.group_sale_salesman","credit_management_user_comm","account_credit_management.model_credit_management_communication",1,1
2307+"account_credit_management.ir_model_access_280",0,0,"group_account_credit_management_info","credit_management_info_comm","account_credit_management.model_credit_management_communication",1,0
2308+"account_credit_management.ir_model_access_281",1,1,"group_account_credit_management_manager","credit_management_mananger_run","account_credit_management.model_credit_management_run",1,1
2309+"account_credit_management.ir_model_access_282",1,1,"group_account_credit_management_user","credit_management_user_run","account_credit_management.model_credit_management_run",1,1
2310+"account_credit_management.ir_model_access_283",0,0,"group_account_credit_management_info","credit_management_info_run","account_credit_management.model_credit_management_run",1,0
2311+"account_credit_management.ir_model_access_284",1,1,"group_account_credit_management_manager","credit_management_manager_profile","account_credit_management.model_credit_management_profile",1,1
2312+"account_credit_management.ir_model_access_285",0,0,"group_account_credit_management_user","credit_management_user_profile","account_credit_management.model_credit_management_profile",1,0
2313+"account_credit_management.ir_model_access_286",0,0,"group_account_credit_management_info","credit_management_info_profile","account_credit_management.model_credit_management_profile",1,0
2314+"account_credit_management.ir_model_access_287",1,1,"group_account_credit_management_manager","credit_management_manager_rule","account_credit_management.model_credit_management_profile_rule",1,1
2315+"account_credit_management.ir_model_access_288",0,0,"group_account_credit_management_user","credit_management_user_rule","account_credit_management.model_credit_management_profile_rule",1,0
2316+"account_credit_management.ir_model_access_289",0,0,"group_account_credit_management_info","credit_management_info_rule","account_credit_management.model_credit_management_profile_rule",1,0
2317+"account_credit_management.ir_model_access_290",1,1,"group_account_credit_management_manager","credit_management_manager_mailer","account_credit_management.model_credit_management_mailer",1,1
2318+"account_credit_management.ir_model_access_291",1,1,"group_account_credit_management_user","credit_management_user_mailer","account_credit_management.model_credit_management_mailer",1,1
2319+"account_credit_management.ir_model_access_292",1,1,"group_account_credit_management_manager","credit_management_manager_marker","account_credit_management.model_credit_management_marker",1,1
2320+"account_credit_management.ir_model_access_293",1,1,"group_account_credit_management_user","credit_management_user_marker","account_credit_management.model_credit_management_marker",1,1
2321+"account_credit_management.ir_model_access_294",1,1,"group_account_credit_management_manager","credit_management_manager_printer","account_credit_management.model_credit_management_printer",1,1
2322+"account_credit_management.ir_model_access_295",1,1,"group_account_credit_management_user","credit_management_user_printer","account_credit_management.model_credit_management_printer",1,1
2323
2324=== added directory 'account_credit_management/wizard'
2325=== added file 'account_credit_management/wizard/__init__.py'
2326--- account_credit_management/wizard/__init__.py 1970-01-01 00:00:00 +0000
2327+++ account_credit_management/wizard/__init__.py 2012-10-15 10:14:35 +0000
2328@@ -0,0 +1,24 @@
2329+# -*- coding: utf-8 -*-
2330+##############################################################################
2331+#
2332+# Author: Nicolas Bessi
2333+# Copyright 2012 Camptocamp SA
2334+#
2335+# This program is free software: you can redistribute it and/or modify
2336+# it under the terms of the GNU Affero General Public License as
2337+# published by the Free Software Foundation, either version 3 of the
2338+# License, or (at your option) any later version.
2339+#
2340+# This program is distributed in the hope that it will be useful,
2341+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2342+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2343+# GNU Affero General Public License for more details.
2344+#
2345+# You should have received a copy of the GNU Affero General Public License
2346+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2347+#
2348+##############################################################################
2349+from . import credit_management_mailer
2350+from . import credit_management_marker
2351+from . import credit_management_printer
2352+from . import credit_management_communication
2353
2354=== added file 'account_credit_management/wizard/credit_management_communication.py'
2355--- account_credit_management/wizard/credit_management_communication.py 1970-01-01 00:00:00 +0000
2356+++ account_credit_management/wizard/credit_management_communication.py 2012-10-15 10:14:35 +0000
2357@@ -0,0 +1,178 @@
2358+# -*- coding: utf-8 -*-
2359+##############################################################################
2360+#
2361+# Author: Nicolas Bessi
2362+# Copyright 2012 Camptocamp SA
2363+#
2364+# This program is free software: you can redistribute it and/or modify
2365+# it under the terms of the GNU Affero General Public License as
2366+# published by the Free Software Foundation, either version 3 of the
2367+# License, or (at your option) any later version.
2368+#
2369+# This program is distributed in the hope that it will be useful,
2370+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2371+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2372+# GNU Affero General Public License for more details.
2373+#
2374+# You should have received a copy of the GNU Affero General Public License
2375+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2376+#
2377+##############################################################################
2378+from openerp.osv.orm import TransientModel, fields
2379+from openerp.osv.osv import except_osv
2380+from openerp.tools.translate import _
2381+import netsvc
2382+import logging
2383+
2384+logger = logging.getLogger('credit.line.management mailing')
2385+
2386+
2387+class CreditCommunication(TransientModel):
2388+ """Shell calss used to provide a base model to email template and reporting.
2389+ Il use this approche in version 7 a browse record will exist even if not saved"""
2390+ _name = "credit.management.communication"
2391+ _description = "credit management communication"
2392+ _rec_name = 'partner_id'
2393+ _columns = {'partner_id': fields.many2one('res.partner', 'Partner', required=True),
2394+
2395+ 'current_profile_rule': fields.many2one('credit.management.profile.rule',
2396+ 'Rule', required=True),
2397+
2398+ 'credit_lines': fields.many2many('credit.management.line',
2399+ rel='comm_credit_rel',
2400+ string='Credit Lines'),
2401+
2402+ 'company_id': fields.many2one('res.company', 'Company',
2403+ required=True),
2404+
2405+ 'user_id': fields.many2one('res.users', 'User')}
2406+
2407+ _defaults = {'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(
2408+ cr, uid, 'credit.management.profile', context=c),
2409+ 'user_id': lambda s, cr, uid, c: uid}
2410+
2411+ def get_address(self, cursor, uid, com_id, context=None):
2412+ """Return a valid address for customer"""
2413+
2414+ context = context or {}
2415+ if isinstance(com_id, list):
2416+ com_id = com_id[0]
2417+ current = self.browse(cursor, uid, com_id, context=context)
2418+ part_obj = self.pool.get('res.partner')
2419+ adds = part_obj.address_get(cursor, uid, [current.partner_id.id],
2420+ adr_pref=['invoice', 'default'])
2421+
2422+ add = adds.get('invoice', adds.get('default'))
2423+ if not add:
2424+ raise except_osv(_('No address for partner %s') % (current.partner_id.name),
2425+ _('Please set one'))
2426+ add_obj = self.pool.get('res.partner.address')
2427+ return add_obj.browse(cursor, uid, add, context=context)
2428+
2429+
2430+ def get_mail(self, cursor, uid, com_id, context=None):
2431+ """Return a valid email for customer"""
2432+ context = context or {}
2433+ if isinstance(com_id, list):
2434+ com_id = com_id[0]
2435+ current = self.browse(cursor, uid, com_id, context=context)
2436+ email = current.get_address().email
2437+ if not email:
2438+ raise except_osv(_('No invoicing or default email for partner %s') %
2439+ (current.partner_id.name),
2440+ _('Please set one'))
2441+ return email
2442+
2443+ def _get_credit_lines(self, cursor, uid, line_ids, partner_id, rule_id, context=None):
2444+ """Return credit lines related to a partner and a profile rule"""
2445+ cr_line_obj = self.pool.get('credit.management.line')
2446+ cr_l_ids = cr_line_obj.search(cursor,
2447+ uid,
2448+ [('id', 'in', line_ids),
2449+ ('partner_id', '=', partner_id),
2450+ ('profile_rule_id', '=', rule_id)],
2451+ context=context)
2452+ #return cr_line_obj.browse(cursor, uid, cr_l_ids, context=context)
2453+ return cr_l_ids
2454+
2455+ def _generate_comm_from_credit_line_ids(self, cursor, uid, line_ids, context=None):
2456+ if not line_ids:
2457+ return []
2458+ comms = []
2459+ sql = ("SELECT distinct partner_id, profile_rule_id, credit_management_profile_rule.level"
2460+ " FROM credit_management_line JOIN credit_management_profile_rule "
2461+ " ON (credit_management_line.profile_rule_id = credit_management_profile_rule.id)"
2462+ " WHERE credit_management_line.id in %s"
2463+ " ORDER by credit_management_profile_rule.level")
2464+
2465+ cursor.execute(sql, (tuple(line_ids),))
2466+ res = cursor.dictfetchall()
2467+ for rule_assoc in res:
2468+ data = {}
2469+ data['credit_lines'] = [(6, 0, self._get_credit_lines(cursor, uid, line_ids,
2470+ rule_assoc['partner_id'],
2471+ rule_assoc['profile_rule_id'],
2472+ context=context))]
2473+ data['partner_id'] = rule_assoc['partner_id']
2474+ data['current_profile_rule'] = rule_assoc['profile_rule_id']
2475+ comm_id = self.create(cursor, uid, data, context=context)
2476+
2477+
2478+ comms.append(self.browse(cursor, uid, comm_id, context=context))
2479+ return comms
2480+
2481+ def _generate_mails(self, cursor, uid, comms, context=None):
2482+ """Generate mail message using template related to rule"""
2483+ cr_line_obj = self.pool.get('credit.management.line')
2484+ mail_temp_obj = self.pool.get('email.template')
2485+ mail_message_obj = self.pool.get('mail.message')
2486+ mail_ids = []
2487+ for comm in comms:
2488+ # we want to use a local cursor in order to send the maximum
2489+ # of email
2490+ try:
2491+ template = comm.current_profile_rule.mail_template_id.id
2492+
2493+ mvalues = mail_temp_obj.generate_email(cursor, uid,
2494+ template,
2495+ comm.id,
2496+ context=context)
2497+ essential_values = ['subject', 'body_html',
2498+ 'email_from', 'email_to']
2499+ for val in essential_values:
2500+ if not mvalues.get(val):
2501+ raise Exception('Mail generation error with %s', val)
2502+ mail_id = mail_message_obj.create(cursor, uid, mvalues, context=context)
2503+
2504+ cl_ids = [cl.id for cl in comm.credit_lines]
2505+
2506+ # we do not use local cusros else we have a lock
2507+ cr_line_obj.write(cursor, uid, cl_ids,
2508+ {'mail_message_id': mail_id,
2509+ 'state': 'sent'})
2510+ mail_ids.append(mail_id)
2511+ except Exception, exc:
2512+ logger.error(exc)
2513+ cursor.rollback()
2514+ cl_ids = [cl.id for cl in comm.credit_lines]
2515+ # we do not use local cusros else we have a lock
2516+ cr_line_obj.write(cursor, uid, cl_ids,
2517+ {'state': 'mail_error'})
2518+ finally:
2519+ cursor.commit()
2520+ return mail_ids
2521+
2522+ def _generate_report(self, cursor, uid, comms, context=None):
2523+ """Will generate a report by inserting mako template of related profile template"""
2524+ service = netsvc.LocalService('report.credit_management_summary')
2525+ ids = [x.id for x in comms]
2526+ result, format = service.create(cursor, uid, ids, {}, {})
2527+ return result
2528+
2529+ def _mark_credit_line_as_sent(self, cursor, uid, comms, context=None):
2530+ line_ids = []
2531+ for comm in comms:
2532+ line_ids += [x.id for x in comm.credit_lines]
2533+ l_obj = self.pool.get('credit.management.line')
2534+ l_obj.write(cursor, uid, line_ids, {'state': 'sent'}, context=context)
2535+ return line_ids
2536
2537=== added file 'account_credit_management/wizard/credit_management_mailer.py'
2538--- account_credit_management/wizard/credit_management_mailer.py 1970-01-01 00:00:00 +0000
2539+++ account_credit_management/wizard/credit_management_mailer.py 2012-10-15 10:14:35 +0000
2540@@ -0,0 +1,64 @@
2541+# -*- coding: utf-8 -*-
2542+##############################################################################
2543+#
2544+# Author: Nicolas Bessi
2545+# Copyright 2012 Camptocamp SA
2546+#
2547+# This program is free software: you can redistribute it and/or modify
2548+# it under the terms of the GNU Affero General Public License as
2549+# published by the Free Software Foundation, either version 3 of the
2550+# License, or (at your option) any later version.
2551+#
2552+# This program is distributed in the hope that it will be useful,
2553+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2554+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2555+# GNU Affero General Public License for more details.
2556+#
2557+# You should have received a copy of the GNU Affero General Public License
2558+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2559+#
2560+##############################################################################
2561+from openerp.osv.orm import TransientModel, fields
2562+from openerp.osv.osv import except_osv
2563+from openerp.tools.translate import _
2564+
2565+class CreditManagementMailer(TransientModel):
2566+ """Change the state of lines in mass"""
2567+
2568+ _name = "credit.management.mailer"
2569+ _description = """Mass credit line mailer"""
2570+ _rec_name = 'id'
2571+
2572+ _columns = {'mail_all': fields.boolean('Mail all ready lines')}
2573+
2574+
2575+ def _get_lids(self, cursor, uid, mail_all, active_ids, context=None):
2576+ """get line to be marked filter done lines"""
2577+ # TODO DRY with printer
2578+ line_obj = self.pool.get('credit.management.line')
2579+ if mail_all:
2580+ domain = [('state', '=', 'to_be_sent'),
2581+ ('canal', '=', 'mail')]
2582+ else:
2583+ domain = [('state', '=', 'to_be_sent'),
2584+ ('id', 'in', active_ids),
2585+ ('canal', '=', 'mail')]
2586+ return line_obj.search(cursor, uid, domain, context=context)
2587+
2588+
2589+ def mail_lines(self, cursor, uid, wiz_id, context=None):
2590+ comm_obj = self.pool.get('credit.management.communication')
2591+ context = context or {}
2592+ if isinstance(wiz_id, list):
2593+ wiz_id = wiz_id[0]
2594+ current = self.browse(cursor, uid, wiz_id, context)
2595+ lines_ids = context.get('active_ids')
2596+
2597+ if not lines_ids and not current.mail_all:
2598+ raise except_osv(_('Not lines ids are selected'),
2599+ _('You may check "Mail all ready lines"'))
2600+ filtered_ids = self._get_lids(cursor, uid, current.mail_all, lines_ids, context)
2601+ comms = comm_obj._generate_comm_from_credit_line_ids(cursor, uid, filtered_ids,
2602+ context=context)
2603+ comm_obj._generate_mails(cursor, uid, comms, context=context)
2604+ return {}
2605
2606=== added file 'account_credit_management/wizard/credit_management_mailer_view.xml'
2607--- account_credit_management/wizard/credit_management_mailer_view.xml 1970-01-01 00:00:00 +0000
2608+++ account_credit_management/wizard/credit_management_mailer_view.xml 2012-10-15 10:14:35 +0000
2609@@ -0,0 +1,44 @@
2610+<openerp>
2611+ <data>
2612+
2613+ <record id="credit_line_mailer_form" model="ir.ui.view">
2614+ <field name="name">credit.line.mailer.form</field>
2615+ <field name="model">credit.management.mailer</field>
2616+ <field name="type">form</field>
2617+ <field name="arch" type="xml">
2618+ <form> <!-- editable="bottom" -->
2619+ <separator string="Mail selected lines. Sucessfuly mailed lines will be in state done. Related email will be linked to the line" colspan="4"/>
2620+ <newline/>
2621+ <field name="mail_all" colspan="4"/>
2622+ <newline/>
2623+ <group colspan="4">
2624+ <button name="mail_lines" string="Mail lines" type="object" icon="gtk-execute"/>
2625+ <button special="cancel" string="Cancel" icon='gtk-cancel'/>
2626+ </group>
2627+ </form>
2628+ </field>
2629+ </record>
2630+
2631+ <!-- for menu -->
2632+ <act_window name="Mail lines"
2633+ res_model="credit.management.mailer"
2634+ src_model="credit.management.line"
2635+ view_mode="form"
2636+ target="new"
2637+ key2="client_action_multi"
2638+ id="open_credit_line_mailer_wizard_menu_action"/>
2639+
2640+ <!-- for button -->
2641+ <record id="open_credit_line_mailer_wizard" model="ir.actions.act_window">
2642+ <field name="name">Mail lines</field>
2643+ <field name="res_model">credit.management.mailer</field>
2644+ <field name="view_type">form</field>
2645+ <field name="view_mode">form</field>
2646+ <field name="view_id" ref="credit_line_mailer_form"/>
2647+ <field name="target">new</field>
2648+ <field name="help">Mail all marked lines</field>
2649+ </record>
2650+
2651+
2652+ </data>
2653+</openerp>
2654
2655=== added file 'account_credit_management/wizard/credit_management_marker.py'
2656--- account_credit_management/wizard/credit_management_marker.py 1970-01-01 00:00:00 +0000
2657+++ account_credit_management/wizard/credit_management_marker.py 2012-10-15 10:14:35 +0000
2658@@ -0,0 +1,83 @@
2659+# -*- coding: utf-8 -*-
2660+##############################################################################
2661+#
2662+# Author: Nicolas Bessi
2663+# Copyright 2012 Camptocamp SA
2664+#
2665+# This program is free software: you can redistribute it and/or modify
2666+# it under the terms of the GNU Affero General Public License as
2667+# published by the Free Software Foundation, either version 3 of the
2668+# License, or (at your option) any later version.
2669+#
2670+# This program is distributed in the hope that it will be useful,
2671+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2672+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2673+# GNU Affero General Public License for more details.
2674+#
2675+# You should have received a copy of the GNU Affero General Public License
2676+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2677+#
2678+##############################################################################
2679+from openerp.osv.orm import TransientModel, fields
2680+from openerp.osv.osv import except_osv
2681+from openerp.tools.translate import _
2682+
2683+class CreditManagementMarker(TransientModel):
2684+ """Change the state of lines in mass"""
2685+
2686+ _name = "credit.management.marker"
2687+ _description = """Mass marker"""
2688+ _columns = {'name': fields.selection([('to_be_sent', 'To be sent'),
2689+ ('sent', 'Done')],
2690+ 'Mark as', required=True),
2691+
2692+ 'mark_all': fields.boolean('Mark all draft lines')}
2693+
2694+ _defaults = {'name': 'to_be_sent'}
2695+
2696+ def _get_lids(self, cursor, uid, mark_all, active_ids, context=None):
2697+ """get line to be marked filter done lines"""
2698+ line_obj = self.pool.get('credit.management.line')
2699+ if mark_all:
2700+ domain = [('state', '=', 'draft')]
2701+ else:
2702+ domain = [('state', '!=', 'sent'), ('id', 'in', active_ids)]
2703+ return line_obj.search(cursor, uid, domain, context=context)
2704+
2705+ def _mark_lines(self, cursor, uid, filtered_ids, state, context=None):
2706+ """write hook"""
2707+ line_obj = self.pool.get('credit.management.line')
2708+ if not state:
2709+ raise ValueError(_('state can not be empty'))
2710+ line_obj.write(cursor, uid, filtered_ids, {'state': state})
2711+ return filtered_ids
2712+
2713+
2714+
2715+ def mark_lines(self, cursor, uid, wiz_id, context=None):
2716+ """Write state of selected credit lines to the one in entry
2717+ done credit line will be ignored"""
2718+ context = context or {}
2719+ if isinstance(wiz_id, list):
2720+ wiz_id = wiz_id[0]
2721+ current = self.browse(cursor, uid, wiz_id, context)
2722+ lines_ids = context.get('active_ids')
2723+
2724+ if not lines_ids and not current.mark_all:
2725+ raise except_osv(_('Not lines ids are selected'),
2726+ _('You may check "Mark all draft lines"'))
2727+ filtered_ids = self._get_lids(cursor, uid, current.mark_all, lines_ids, context)
2728+ if not filtered_ids:
2729+ raise except_osv(_('No lines will be changed'),
2730+ _('All selected lines are allready done'))
2731+
2732+ # hook function a simple write should be enought
2733+ self._mark_lines(cursor, uid, filtered_ids, current.name, context)
2734+
2735+ return {'domain': "[('id','in',%s)]" % (filtered_ids,),
2736+ 'name': _('%s marked line') % (current.name,),
2737+ 'view_type': 'form',
2738+ 'view_mode': 'tree,form',
2739+ 'view_id': False,
2740+ 'res_model': 'credit.management.line',
2741+ 'type': 'ir.actions.act_window'}
2742
2743=== added file 'account_credit_management/wizard/credit_management_marker_view.xml'
2744--- account_credit_management/wizard/credit_management_marker_view.xml 1970-01-01 00:00:00 +0000
2745+++ account_credit_management/wizard/credit_management_marker_view.xml 2012-10-15 10:14:35 +0000
2746@@ -0,0 +1,44 @@
2747+<openerp>
2748+ <data>
2749+
2750+ <record id="credit_line_marker_form" model="ir.ui.view">
2751+ <field name="name">credit.line.marker.form</field>
2752+ <field name="model">credit.management.marker</field>
2753+ <field name="type">form</field>
2754+ <field name="arch" type="xml">
2755+ <form> <!-- editable="bottom" -->
2756+ <separator string="This wizard will mark selected draft lines to the selected state. Done Lines will be ignored " colspan="4"/>
2757+ <newline/>
2758+ <field name="name"/>
2759+ <field name="mark_all"/>
2760+ <newline/>
2761+ <group colspan="4">
2762+ <button name="mark_lines" string="Mark lines" type="object" icon="gtk-execute"/>
2763+ <button special="cancel" string="Cancel" icon='gtk-cancel'/>
2764+ </group>
2765+ </form>
2766+ </field>
2767+ </record>
2768+
2769+ <!-- for menu -->
2770+ <act_window name="Mark lines"
2771+ res_model="credit.management.marker"
2772+ src_model="credit.management.line"
2773+ view_mode="form"
2774+ target="new"
2775+ key2="client_action_multi"
2776+ id="open_credit_line_marker_wizard_menu_action"/>
2777+
2778+ <record id="open_credit_line_marker_wizard" model="ir.actions.act_window">
2779+ <field name="name">Mark lines</field>
2780+ <field name="res_model">credit.management.marker</field>
2781+ <field name="src_model">credit.management.line</field>
2782+ <field name="view_type">form</field>
2783+ <field name="view_mode">form</field>
2784+ <field name="view_id" ref="credit_line_marker_form"/>
2785+ <field name="target">new</field>
2786+ <field name="help">Mark all lines. You can the send marked lines</field>
2787+ </record>
2788+
2789+ </data>
2790+</openerp>
2791
2792=== added file 'account_credit_management/wizard/credit_management_printer.py'
2793--- account_credit_management/wizard/credit_management_printer.py 1970-01-01 00:00:00 +0000
2794+++ account_credit_management/wizard/credit_management_printer.py 2012-10-15 10:14:35 +0000
2795@@ -0,0 +1,74 @@
2796+# -*- coding: utf-8 -*-
2797+##############################################################################
2798+#
2799+# Author: Nicolas Bessi
2800+# Copyright 2012 Camptocamp SA
2801+#
2802+# This program is free software: you can redistribute it and/or modify
2803+# it under the terms of the GNU Affero General Public License as
2804+# published by the Free Software Foundation, either version 3 of the
2805+# License, or (at your option) any later version.
2806+#
2807+# This program is distributed in the hope that it will be useful,
2808+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2809+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2810+# GNU Affero General Public License for more details.
2811+#
2812+# You should have received a copy of the GNU Affero General Public License
2813+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2814+#
2815+##############################################################################
2816+import base64
2817+
2818+from openerp.osv.orm import TransientModel, fields
2819+from openerp.osv.osv import except_osv
2820+from openerp.tools.translate import _
2821+
2822+class CreditManagementPrinter(TransientModel):
2823+ """Change the state of lines in mass"""
2824+
2825+ _name = "credit.management.printer"
2826+ _rec_name = 'id'
2827+ _description = """Mass printer"""
2828+ _columns = {'mark_as_sent': fields.boolean('Mark lines as send',
2829+ help="Lines to emailed will be ignored"),
2830+ 'print_all': fields.boolean('Print all ready lines'),
2831+ 'report_file': fields.binary('Generated Report'),
2832+ 'state': fields.char('state', size=32)}
2833+
2834+ def _get_lids(self, cursor, uid, print_all, active_ids, context=None):
2835+ """get line to be marked filter done lines"""
2836+ # TODO Dry with mailer maybe in comm
2837+ line_obj = self.pool.get('credit.management.line')
2838+ if print_all:
2839+ domain = [('state', '=', 'to_be_sent'),
2840+ ('canal', '=', 'manual')]
2841+ else:
2842+ domain = [('state', '=', 'to_be_sent'),
2843+ ('id', 'in', active_ids),
2844+ ('canal', '=', 'manual')]
2845+ return line_obj.search(cursor, uid, domain, context=context)
2846+
2847+
2848+ def print_lines(self, cursor, uid, wiz_id, context=None):
2849+ comm_obj = self.pool.get('credit.management.communication')
2850+ context = context or {}
2851+ if isinstance(wiz_id, list):
2852+ wiz_id = wiz_id[0]
2853+ current = self.browse(cursor, uid, wiz_id, context)
2854+ lines_ids = context.get('active_ids')
2855+ if not lines_ids and not current.print_all:
2856+ raise except_osv(_('Not lines ids are selected'),
2857+ _('You may check "Print all ready lines"'))
2858+ if current.print_all:
2859+ filtered_ids = self._get_lids(cursor, uid, current.print_all, lines_ids, context)
2860+ else:
2861+ filtered_ids = lines_ids
2862+ comms = comm_obj._generate_comm_from_credit_line_ids(cursor, uid, filtered_ids,
2863+ context=context)
2864+ report_file = comm_obj._generate_report(cursor, uid, comms, context=context)
2865+ current.write({'report_file': base64.b64encode(report_file), 'state': 'done'})
2866+ if current.mark_as_sent:
2867+ filtered_ids = self._get_lids(cursor, uid, False, lines_ids, context)
2868+ comm_obj._mark_credit_line_as_sent(cursor, uid, comms, context=context)
2869+ return False
2870
2871=== added file 'account_credit_management/wizard/credit_management_printer_view.xml'
2872--- account_credit_management/wizard/credit_management_printer_view.xml 1970-01-01 00:00:00 +0000
2873+++ account_credit_management/wizard/credit_management_printer_view.xml 2012-10-15 10:14:35 +0000
2874@@ -0,0 +1,47 @@
2875+<openerp>
2876+ <data>
2877+
2878+ <record id="credit_line_printer_form" model="ir.ui.view">
2879+ <field name="name">credit.line.printer.form</field>
2880+ <field name="model">credit.management.printer</field>
2881+ <field name="type">form</field>
2882+ <field name="arch" type="xml">
2883+ <form> <!-- editable="bottom" -->
2884+ <separator colspan="4" string="This wizard will print all manual lines. Mark lines, lines will pass lines to state done. Lines with canal email will not be passed to state done"/>
2885+ <newline/>
2886+ <field name="mark_as_sent"/>
2887+ <field name="print_all"/>
2888+ <newline/>
2889+ <field name="report_file" attrs="{'readonly': [('state', '!=', 'done')]}" colspan="4"/>
2890+ <field name="state" invisible="1" />
2891+ <newline/>
2892+ <group colspan="4">
2893+ <button name="print_lines" string="Print lines" type="object" icon="gtk-execute"/>
2894+ <button special="cancel" string="Cancel" icon='gtk-cancel'/>
2895+ </group>
2896+ </form>
2897+ </field>
2898+ </record>
2899+
2900+ <!-- for menu -->
2901+ <act_window name="Print lines"
2902+ res_model="credit.management.printer"
2903+ src_model="credit.management.line"
2904+ view_mode="form"
2905+ target="new"
2906+ key2="client_action_multi"
2907+ id="open_credit_line_printer_wizard_menu_action"/>
2908+
2909+ <record id="open_credit_line_printer_wizard" model="ir.actions.act_window">
2910+ <field name="name">Print lines</field>
2911+ <field name="res_model">credit.management.printer</field>
2912+ <field name="src_model">credit.management.line</field>
2913+ <field name="view_type">form</field>
2914+ <field name="view_mode">form</field>
2915+ <field name="view_id" ref="credit_line_printer_form"/>
2916+ <field name="target">new</field>
2917+ <field name="help">Print all lines</field>
2918+ </record>
2919+
2920+ </data>
2921+</openerp>
2922
2923=== modified file 'account_fiscal_position_rule_m2m/fiscal_rules.py'
2924--- account_fiscal_position_rule_m2m/fiscal_rules.py 2012-04-03 14:27:22 +0000
2925+++ account_fiscal_position_rule_m2m/fiscal_rules.py 2012-10-15 10:14:35 +0000
2926@@ -177,8 +177,8 @@
2927 )
2928
2929 for field in account_fiscal_position_rule.m2o_replaced_fields:
2930- field_ids = [item.id for item in template[field]]
2931- vals[field] = [(6, 0, field_ids)]
2932+ field_ids = [item.id for item in template["%s_ids"%field]]
2933+ vals["%s_ids"%field] = [(6, 0, field_ids)]
2934
2935 return vals
2936
2937
2938=== added directory 'account_payment_ext'
2939=== added file 'account_payment_ext/__init__.py'
2940--- account_payment_ext/__init__.py 1970-01-01 00:00:00 +0000
2941+++ account_payment_ext/__init__.py 2012-10-15 10:14:35 +0000
2942@@ -0,0 +1,32 @@
2943+# -*- encoding: utf-8 -*-
2944+#
2945+# c2c_scan_bvr
2946+#
2947+# Created by Nicolas Bessi and Vincent Renaville
2948+#
2949+# Copyright (c) 2012 CamptoCamp. All rights reserved.
2950+##############################################################################
2951+#
2952+# WARNING: This program as such is intended to be used by professional
2953+# programmers who take the whole responsability of assessing all potential
2954+# consequences resulting from its eventual inadequacies and bugs
2955+# End users who are looking for a ready-to-use solution with commercial
2956+# garantees and support are strongly adviced to contract a Free Software
2957+# Service Company
2958+#
2959+# This program is Free Software; you can redistribute it and/or
2960+# modify it under the terms of the GNU General Public License
2961+# as published by the Free Software Foundation; either version 2
2962+# of the License, or (at your option) any later version.
2963+#
2964+# This program is distributed in the hope that it will be useful,
2965+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2966+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2967+# GNU General Public License for more details.
2968+#
2969+# You should have received a copy of the GNU General Public License
2970+# along with this program; if not, write to the Free Software
2971+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
2972+#
2973+##############################################################################
2974+import account_payment
2975\ No newline at end of file
2976
2977=== added file 'account_payment_ext/__openerp__.py'
2978--- account_payment_ext/__openerp__.py 1970-01-01 00:00:00 +0000
2979+++ account_payment_ext/__openerp__.py 2012-10-15 10:14:35 +0000
2980@@ -0,0 +1,50 @@
2981+# -*- encoding: utf-8 -*-
2982+#
2983+# c2c_scan_bvr
2984+#
2985+# Created by Nicolas Bessi and Vincent Renaville
2986+#
2987+# Copyright (c) 2012 CamptoCamp. All rights reserved.
2988+##############################################################################
2989+#
2990+# WARNING: This program as such is intended to be used by professional
2991+# programmers who take the whole responsability of assessing all potential
2992+# consequences resulting from its eventual inadequacies and bugs
2993+# End users who are looking for a ready-to-use solution with commercial
2994+# garantees and support are strongly adviced to contract a Free Software
2995+# Service Company
2996+#
2997+# This program is Free Software; you can redistribute it and/or
2998+# modify it under the terms of the GNU General Public License
2999+# as published by the Free Software Foundation; either version 2
3000+# of the License, or (at your option) any later version.
3001+#
3002+# This program is distributed in the hope that it will be useful,
3003+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3004+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3005+# GNU General Public License for more details.
3006+#
3007+# You should have received a copy of the GNU General Public License
3008+# along with this program; if not, write to the Free Software
3009+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
3010+#
3011+##############################################################################
3012+{
3013+ "name" : "Account Payment improvement",
3014+ "description" : """In case of a payment order
3015+ set to pay directly , when the payment will pass to stade done, it will set payment line date to the current date
3016+ """,
3017+ "version" : "1.0",
3018+ "author" : "Camptocamp",
3019+ "category" : "Generic Modules/Others",
3020+ "website": "http://www.camptocamp.com",
3021+ "depends" : [
3022+ "account_payment"
3023+ ],
3024+ "init_xml" : [],
3025+ "update_xml" : [
3026+ 'account_payment_view.xml'
3027+ ],
3028+ "active": False,
3029+ "installable": True
3030+}
3031
3032=== added file 'account_payment_ext/account_payment.py'
3033--- account_payment_ext/account_payment.py 1970-01-01 00:00:00 +0000
3034+++ account_payment_ext/account_payment.py 2012-10-15 10:14:35 +0000
3035@@ -0,0 +1,47 @@
3036+# -*- coding: utf-8 -*-
3037+##############################################################################
3038+#
3039+# OpenERP, Open Source Management Solution
3040+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
3041+#
3042+# This program is free software: you can redistribute it and/or modify
3043+# it under the terms of the GNU Affero General Public License as
3044+# published by the Free Software Foundation, either version 3 of the
3045+# License, or (at your option) any later version.
3046+#
3047+# This program is distributed in the hope that it will be useful,
3048+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3049+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3050+# GNU Affero General Public License for more details.
3051+#
3052+# You should have received a copy of the GNU Affero General Public License
3053+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3054+#
3055+##############################################################################
3056+
3057+import time
3058+
3059+from osv import osv, fields
3060+import netsvc
3061+
3062+
3063+class payment_order(osv.osv):
3064+ _inherit = 'payment.order'
3065+ ### It will set the date
3066+ def action_open(self, cr, uid, ids, *args):
3067+ return_code = super(payment_order,self).action_open(cr, uid, ids, args)
3068+ payment_line_obj = self.pool.get('payment.line')
3069+ if return_code:
3070+ for order in self.browse(cr,uid,ids):
3071+ ### In the case of a date prefered to Directy , we set the current date into the bank statement
3072+ if order.date_prefered == 'now':
3073+ for line in order.line_ids:
3074+ ## in case of no date defined on line
3075+ if not line.date:
3076+ payment_line_obj.write(cr, uid, [line.id], {'date': time.strftime('%Y-%m-%d')})
3077+ return return_code
3078+
3079+payment_order()
3080+
3081+
3082+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
3083
3084=== added file 'account_payment_ext/account_payment_view.xml'
3085--- account_payment_ext/account_payment_view.xml 1970-01-01 00:00:00 +0000
3086+++ account_payment_ext/account_payment_view.xml 2012-10-15 10:14:35 +0000
3087@@ -0,0 +1,18 @@
3088+<?xml version="1.0" encoding="utf-8"?>
3089+<openerp>
3090+ <data>
3091+
3092+ <record id="view_payment_order_form_date_required" model="ir.ui.view">
3093+ <field name="name">payment.order.form</field>
3094+ <field name="model">payment.order</field>
3095+ <field name="inherit_id" ref="account_payment.view_payment_order_form"/>
3096+ <field name="type">form</field>
3097+ <field name="arch" type="xml">
3098+ <xpath expr='//field[@name="date_scheduled"]' position='replace'>
3099+ <field name="date_scheduled" select="1" attrs="{'readonly':[('date_prefered','!=','fixed')],'required':[('date_prefered','=','fixed')]}" />
3100+ </xpath>
3101+ </field>
3102+ </record>
3103+
3104+ </data>
3105+</openerp>
3106
3107=== added directory 'account_payment_ext/i18n'

Subscribers

People subscribed via source and target branches