Merge lp:~akretion-team/openobject-addons/trunk_prepare_refund into lp:openobject-addons

Proposed by Benoit Guillot - http://www.akretion.com
Status: Merged
Merged at revision: 8395
Proposed branch: lp:~akretion-team/openobject-addons/trunk_prepare_refund
Merge into: lp:openobject-addons
Diff against target: 188 lines (+88/-64)
3 files modified
account/account_invoice.py (+86/-62)
account/wizard/account_invoice_refund.py (+1/-1)
point_of_sale/wizard/pos_return.py (+1/-1)
To merge this branch: bzr merge lp:~akretion-team/openobject-addons/trunk_prepare_refund
Reviewer Review Type Date Requested Status
Olivier Dony (Odoo) Approve
Nhomar - Vauxoo (community) Approve
Review via email: mp+122024@code.launchpad.net

Description of the change

This merge add a method prepare_refund to help the customization of the creation of a refund with the wizard. It also add the context as arguments of the methods def refund and _refund_cleanup_lines.

To post a comment you must log in.
Revision history for this message
Nhomar - Vauxoo (nhomar) wrote :

Wao.

Dude, yesterday we fix a lot of thing in ur both localization writting all the original method.

For V7 this is a must to have ;-)

review: Approve
Revision history for this message
Olivier Dony (Odoo) (odo-openerp) wrote :

Nice work, thanks!

It looks good, except what is the purpose of the "refund_id" parameter passed to _prepare_refund(), and seemingly unused?

It would also be a nice touch to add docstrings explaining the meaning and types of the various parameters (not cr, uid, and context, but the others), similarly to what was done in the other _prepare_* methods, like here:
   http://bazaar.launchpad.net/~openerp/openobject-addons/trunk/view/7743/sale/sale.py#L310

Thanks!

review: Needs Information
Revision history for this message
Benoit Guillot - http://www.akretion.com (benoit-guillot-z) wrote :

Thanks Nhomar for the approval and Olivier for the comments !

I have added the dosctring for the method prepare.

Concerning the parameter "refund_id", it is a typo, I meant "invoice_id". I have fixed it.
I think it can be useful to have the id of the invoice in the method prepare.

Moreover, I would like to add a new prepare method : _prepare_cleanup_fields()
This method allows us to add new fields for the creation of the refund lines.
Tell me what you think about it please :)

Thank you.

Benoît

Revision history for this message
Olivier Dony (Odoo) (odo-openerp) wrote :

On 10/10/2012 05:56 PM, Benoit Guillot - http://www.akretion.com wrote:
> I have added the dosctring for the method prepare.

Thanks! The param type in docstrings is actually meant to be a Python type
(int/str/etc.), but we can fix it when merging, so no big deal.

> Concerning the parameter "refund_id", it is a typo, I meant "invoice_id". I have fixed it.
> I think it can be useful to have the id of the invoice in the method prepare.

Hmm, the thing is this parameter is unused, and redundant because the `invoice`
parameter contains the ID. In general we avoid passing unused parameters
because they risk being removed in the future, and prefer passing higher level
data structures that give you indirect access to more info. In this case the
`invoice` param plays that role.

Now if you prefer having an even higher-level param, you could replace the
`invoice` dict by a browse_record (and update the code accordingly). Not sure
if that's really useful, but it could save extra read() calls in inheriting
methods that need to access more columns of the invoice.

> Moreover, I would like to add a new prepare method : _prepare_cleanup_fields()
> This method allows us to add new fields for the creation of the refund lines.
> Tell me what you think about it please :)

It looks a bit overkill to me, as the main purpose of _refund_cleanup_lines()
is to fix the lines data, no need for a separate one. If your issue is simply
to have this method support custom m2o fields as well, why not remove the
hardcoded list of fields and instead check the type of the fields (via
account.invoice.line._all_columns) and handle all m2o ones properly?

Revision history for this message
Benoit Guillot - http://www.akretion.com (benoit-guillot-z) wrote :

Hello Olivier,

I agree with you about the invoice dict. I am ready to spend more time on this to replace it by a browse_record if you think it's worth it. As far as I'm concerned, I think it does, as you said it can save extra read() calls. Then I just have to manage also the refund_cleanup_lines that will change.

Otherwise, if we should keep the dict instead of the browse_record, I can manage the invoice_id in the dict and remove it from the parameters. It is indeed redundant. Moreover, about the method _refund_cleanup_lines(), using "account.invoice.line._all_columns" seems a better idea than using a _prepare() method.

So I can propose the two solutions, but maybe it worth to use this proposal to improve the refund creation with a browse_record instead of several reads.

Revision history for this message
Olivier Dony (Odoo) (odo-openerp) wrote :

On 10/11/2012 12:57 PM, Benoit Guillot - http://www.akretion.com wrote:
> I agree with you about the invoice dict. I am ready to spend more time on
> this to replace it by a browse_record if you think it's worth it. As far as
> I'm concerned, I think it does, as you said it can save extra read() calls.
> Then I just have to manage also the refund_cleanup_lines that will change.

Sure, go ahead, this should not break the current API and should be safe :-)

The fields that were read() from the invoice were limited, so it should be easy
to craft a new dictionary from a browse_record instead of a dict, taking care
of fixing m2o values as you go (based on the type coming from _all_columns)

Tip: browse_records support __getitem__ just like dict, so you can simply use
``invoice[f]`` to get the value for field ``f``, without having to use
getattr(invoice, f)

Revision history for this message
Benoit Guillot - http://www.akretion.com (benoit-guillot-z) wrote :

Thanks for the tip, it has helped me !

I have removed some fields from the dict of the invoice:
     - 'type', 'number' because theses are overwriten
     - 'partner_contact', 'partner_insite', 'partner_ref' because theses seem not exist anymore

I have refactored the method _refund_cleanup_lines(), but maybe it can be useful to create a generic method for instance : _browse_to_dict() which can be something like that (it's just an idea it can be improved):

        def _browse_to_dict(browse):
            for field in browse._all_columns.keys():
                field_type = browse._all_columns[field].column._type
                if field_type == 'many2one':
                    dict_from_browse[field] = browse[field].id
                elif field_type == 'many2many':
                    m2m_list = []
                    for link in browse[field]:
                        m2m_list.append(link.id)
                    dict_from_browse[field] = [(6,0, m2m_list)]
                elif field_type == 'one2many':
                    o2m_list = []
                    for o2m in browse[field]
                        o2m_dict = o2m._browse_to_dict(o2m)
                        o2m_list.append((0, 0, o2m_dict))
                     dict_from_browse[field] = o2m_list
                else:
                    dict_from_browse[field] = browse[field]
            return dict_from_browse

This method can be used in copy() and probably in other cases.

About the merge, I have tested, it seems to work. I haven't try it on the runbot, because akretion has been kicked out from the runbot (probably because of the 'a' of akretion).

Revision history for this message
Olivier Dony (Odoo) (odo-openerp) wrote :

On 10/12/2012 02:37 PM, Benoit Guillot - http://www.akretion.com wrote:
> Thanks for the tip, it has helped me !
>
> I have removed some fields from the dict of the invoice:
> - 'type', 'number' because theses are overwriten
> - 'partner_contact', 'partner_insite', 'partner_ref' because theses seem not exist anymore

Looks good, and how about the `journal_id` field, it seems overwritten as well
if I'm not mistaken?

> I have refactored the method _refund_cleanup_lines(), but maybe it can be
> useful to create a generic method for instance : _browse_to_dict() which can
> be something like that (it's just an idea it can be improved):
<SNIP>

It might be useful in other circumstances, however it should not be located in
the account module. In order to speed up the merge I suggest not doing it now.

Also you have an issue with o2m fields now (and m2m to some extent), because
you have removed the list of fields to copy in _refund_cleanup_lines.
I suppose this is to make the code simpler and possibly avoid having to inherit
this method when you add extra fields to the lines. However this may have
dangerous side effects. A module could track individual payment lines or some
special analytic lines and link them to the invoice line using a o2m or m2m -
but these should not be copied over to the refund line!
You do not handle the o2m case at all at the moment, so it will simply crash if
a module ever does this.
I think it would be much safer to keep a list of fields to copy, or at least
skip all o2m/m2m by default and handle `invoice_line_tax_id` as a special case.

As you see, even with a _browse_to_dict() helper method you'd still need to
iterate on the fields to filter them based on their type (to fix the above
problem), so the code might not even be shorter...

> About the merge, I have tested, it seems to work. I haven't try it on the
> runbot, because akretion has been kicked out from the runbot (probably
> because of the 'a' of akretion).

Akretion has not been kicked out of runbot, but the list of registered teams
was recently wiped out by mistake[1]. Sorry about it, you just need to
re-register your team. It would be great to check the status of the tests on
your branch indeed!

Great job, thanks again for your efforts and dedication!

[1] Already happened in the past:
https://twitter.com/antonylesuisse/status/192290768348651520

Revision history for this message
Benoit Guillot - http://www.akretion.com (benoit-guillot-z) wrote :

Yes indeed the 'journal_id' field is overwritten, so I removed it.
I have changed also the method refund_cleanup_lines() to skip the o2m and the m2m and handle just the field 'invoice_line_tax_id'.

About the browse_to_dict() method, I was also thinking to put it somewhere else than in the module account, but the purpose of that was just to have a feedback ;)

Thanks for your time spent to discuss about it !

Revision history for this message
Benoit Guillot - http://www.akretion.com (benoit-guillot-z) wrote :

Hello Olivier,

Have you verified my last changes ?
What do you think of them ?

I insist because I think we really need this merge for the V7 !

Thanks again for the time you spend on this merge proposal.

Regards,

Benoît

Revision history for this message
Olivier Dony (Odoo) (odo-openerp) wrote :

Looks very good to me now, I'm merging it after resolving the latest conflicts.

Thanks for your patience and your excellent contribution! :-)

review: Approve
Revision history for this message
Benoit Guillot - http://www.akretion.com (benoit-guillot-z) wrote :

Thanks a lot for the merge !

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'account/account_invoice.py'
2--- account/account_invoice.py 2012-10-08 12:17:45 +0000
3+++ account/account_invoice.py 2012-10-16 16:41:28 +0000
4@@ -1124,72 +1124,96 @@
5 ids = self.search(cr, user, [('name',operator,name)] + args, limit=limit, context=context)
6 return self.name_get(cr, user, ids, context)
7
8- def _refund_cleanup_lines(self, cr, uid, lines):
9+ def _refund_cleanup_lines(self, cr, uid, lines, context=None):
10+ clean_lines = []
11 for line in lines:
12- del line['id']
13- del line['invoice_id']
14- for field in ('company_id', 'partner_id', 'account_id', 'product_id',
15- 'uos_id', 'account_analytic_id', 'tax_code_id', 'base_code_id'):
16- if line.get(field):
17- line[field] = line[field][0]
18- if 'invoice_line_tax_id' in line:
19- line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
20- return map(lambda x: (0,0,x), lines)
21-
22- def refund(self, cr, uid, ids, date=None, period_id=None, description=None, journal_id=None):
23- invoices = self.read(cr, uid, ids, ['name', 'type', 'number', 'reference', 'comment', 'date_due', 'partner_id', 'partner_contact', 'partner_insite', 'partner_ref', 'payment_term', 'account_id', 'currency_id', 'invoice_line', 'tax_line', 'journal_id', 'company_id'])
24- obj_invoice_line = self.pool.get('account.invoice.line')
25- obj_invoice_tax = self.pool.get('account.invoice.tax')
26+ clean_line = {}
27+ for field in line._all_columns.keys():
28+ if line._all_columns[field].column._type == 'many2one':
29+ clean_line[field] = line[field].id
30+ elif line._all_columns[field].column._type not in ['many2many','one2many']:
31+ clean_line[field] = line[field]
32+ elif field == 'invoice_line_tax_id':
33+ tax_list = []
34+ for tax in line[field]:
35+ tax_list.append(tax.id)
36+ clean_line[field] = [(6,0, tax_list)]
37+ clean_lines.append(clean_line)
38+ return map(lambda x: (0,0,x), clean_lines)
39+
40+ def _prepare_refund(self, cr, uid, invoice, date=None, period_id=None, description=None, journal_id=None, context=None):
41+ """Prepare the dict of values to create the new refund from the invoice.
42+ This method may be overridden to implement custom
43+ refund generation (making sure to call super() to establish
44+ a clean extension chain).
45+
46+ :param integer invoice_id: id of the invoice to refund
47+ :param dict invoice: read of the invoice to refund
48+ :param string date: refund creation date from the wizard
49+ :param integer period_id: force account.period from the wizard
50+ :param string description: description of the refund from the wizard
51+ :param integer journal_id: account.journal from the wizard
52+ :return: dict of value to create() the refund
53+ """
54 obj_journal = self.pool.get('account.journal')
55+
56+ type_dict = {
57+ 'out_invoice': 'out_refund', # Customer Invoice
58+ 'in_invoice': 'in_refund', # Supplier Invoice
59+ 'out_refund': 'out_invoice', # Customer Refund
60+ 'in_refund': 'in_invoice', # Supplier Refund
61+ }
62+ invoice_data = {}
63+ for field in ['name', 'reference', 'comment', 'date_due', 'partner_id', 'company_id',
64+ 'account_id', 'currency_id', 'payment_term']:
65+ if invoice._all_columns[field].column._type == 'many2one':
66+ invoice_data[field] = invoice[field].id
67+ else:
68+ invoice_data[field] = invoice[field] and invoice[field] or False
69+
70+ invoice_lines = self._refund_cleanup_lines(cr, uid, invoice.invoice_line, context=context)
71+
72+ tax_lines = filter(lambda l: l['manual'], invoice.tax_line)
73+ tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines, context=context)
74+ if journal_id:
75+ refund_journal_ids = [journal_id]
76+ elif invoice['type'] == 'in_invoice':
77+ refund_journal_ids = obj_journal.search(cr, uid, [('type','=','purchase_refund')], context=context)
78+ else:
79+ refund_journal_ids = obj_journal.search(cr, uid, [('type','=','sale_refund')], context=context)
80+
81+ if not date:
82+ date = time.strftime('%Y-%m-%d')
83+ invoice_data.update({
84+ 'type': type_dict[invoice['type']],
85+ 'date_invoice': date,
86+ 'state': 'draft',
87+ 'number': False,
88+ 'invoice_line': invoice_lines,
89+ 'tax_line': tax_lines,
90+ 'journal_id': refund_journal_ids and refund_journal_ids[0] or False,
91+ })
92+ if period_id:
93+ invoice_data.update({
94+ 'period_id': period_id,
95+ })
96+ if description:
97+ invoice_data.update({
98+ 'name': description,
99+ })
100+ return invoice_data
101+
102+ def refund(self, cr, uid, ids, date=None, period_id=None, description=None, journal_id=None, context=None):
103 new_ids = []
104- for invoice in invoices:
105- del invoice['id']
106-
107- type_dict = {
108- 'out_invoice': 'out_refund', # Customer Invoice
109- 'in_invoice': 'in_refund', # Supplier Invoice
110- 'out_refund': 'out_invoice', # Customer Refund
111- 'in_refund': 'in_invoice', # Supplier Refund
112- }
113-
114- invoice_lines = obj_invoice_line.read(cr, uid, invoice['invoice_line'])
115- invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
116-
117- tax_lines = obj_invoice_tax.read(cr, uid, invoice['tax_line'])
118- tax_lines = filter(lambda l: l['manual'], tax_lines)
119- tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
120- if journal_id:
121- refund_journal_ids = [journal_id]
122- elif invoice['type'] == 'in_invoice':
123- refund_journal_ids = obj_journal.search(cr, uid, [('type','=','purchase_refund')])
124- else:
125- refund_journal_ids = obj_journal.search(cr, uid, [('type','=','sale_refund')])
126-
127- if not date:
128- date = time.strftime('%Y-%m-%d')
129- invoice.update({
130- 'type': type_dict[invoice['type']],
131- 'date_invoice': date,
132- 'state': 'draft',
133- 'number': False,
134- 'invoice_line': invoice_lines,
135- 'tax_line': tax_lines,
136- 'journal_id': refund_journal_ids
137- })
138- if period_id:
139- invoice.update({
140- 'period_id': period_id,
141- })
142- if description:
143- invoice.update({
144- 'name': description,
145- })
146- # take the id part of the tuple returned for many2one fields
147- for field in ('partner_id', 'company_id',
148- 'account_id', 'currency_id', 'payment_term', 'journal_id'):
149- invoice[field] = invoice[field] and invoice[field][0]
150+ for invoice in self.browse(cr, uid, ids, context=context):
151+ invoice = self._prepare_refund(cr, uid, invoice,
152+ date=date,
153+ period_id=period_id,
154+ description=description,
155+ journal_id=journal_id,
156+ context=context)
157 # create the new invoice
158- new_ids.append(self.create(cr, uid, invoice))
159+ new_ids.append(self.create(cr, uid, invoice, context=context))
160
161 return new_ids
162
163
164=== modified file 'account/wizard/account_invoice_refund.py'
165--- account/wizard/account_invoice_refund.py 2012-09-30 17:02:56 +0000
166+++ account/wizard/account_invoice_refund.py 2012-10-16 16:41:28 +0000
167@@ -146,7 +146,7 @@
168 raise osv.except_osv(_('Insufficient Data!'), \
169 _('No period found on the invoice.'))
170
171- refund_id = inv_obj.refund(cr, uid, [inv.id], date, period, description, journal_id)
172+ refund_id = inv_obj.refund(cr, uid, [inv.id], date, period, description, journal_id, context=context)
173 refund = inv_obj.browse(cr, uid, refund_id[0], context=context)
174 inv_obj.write(cr, uid, [refund.id], {'date_due': date,
175 'check_total': inv.check_total})
176
177=== modified file 'point_of_sale/wizard/pos_return.py'
178--- point_of_sale/wizard/pos_return.py 2012-03-30 08:27:51 +0000
179+++ point_of_sale/wizard/pos_return.py 2012-10-16 16:41:28 +0000
180@@ -278,7 +278,7 @@
181 location_id=res and res[0] or None
182
183 if order_id.invoice_id:
184- invoice_obj.refund(cr, uid, [order_id.invoice_id.id], time.strftime('%Y-%m-%d'), False, order_id.name)
185+ invoice_obj.refund(cr, uid, [order_id.invoice_id.id], time.strftime('%Y-%m-%d'), False, order_id.name, context=context)
186 new_picking=picking_obj.create(cr, uid, {
187 'name':'%s (return)' %order_id.name,
188 'move_lines':[], 'state':'draft',

Subscribers

People subscribed via source and target branches

to all changes: