Merge lp:~alexis-via/openobject-addons/extensible-stock-action_invoice_create into lp:openobject-addons

Proposed by Alexis de Lattre
Status: Merged
Merged at revision: 6455
Proposed branch: lp:~alexis-via/openobject-addons/extensible-stock-action_invoice_create
Merge into: lp:openobject-addons
Diff against target: 544 lines (+234/-192)
4 files modified
purchase/stock.py (+25/-12)
sale/sale.py (+52/-39)
sale/stock.py (+29/-38)
stock/stock.py (+128/-103)
To merge this branch: bzr merge lp:~alexis-via/openobject-addons/extensible-stock-action_invoice_create
Reviewer Review Type Date Requested Status
Raphael Collet (OpenERP) (community) Approve
Raphaël Valyi - http://www.akretion.com (community) Approve
Nhomar - Vauxoo (community) aproved Approve
qdp (OpenERP) Pending
Review via email: mp+87689@code.launchpad.net

Description of the change

This merge proposal is a refactoring to make it easier and cleaner to override invoice creation from deliveries in order to customize the invoice creation process.
It is similar to other merge proposals developped by Akretion this last months and merged in trunk (like this one : https://code.launchpad.net/~akretion-team/openobject-addons/addons-sale-extensible-action-ship-create/+merge/76609)

It also fixes the following (example) scenario :
Customer A is ordering some goods for a construction project and would like to have the goods delivered directly on the construction site. The address of the construction site is linked to partner B. On the sale order, the Invoice policy is "Invoice based on deliveries".
So far, when the invoice was created from the picking, the invoice would have all the information of partner A, except the fiscal position and the partner account which would be the one of partner B ! This merge proposal fixes this, by a clean handling of the "partner" variable (which fixes the account_id as a consequence) and a proper handling of the fiscal position.

To post a comment you must log in.
Revision history for this message
Raphaël Valyi - http://www.akretion.com (rvalyi) wrote :

Hello I didn't take time to look at the detail of the proposal yet, bu I should say I strongly recommend to do something about the lack of modularity here. Hopefully I take some time Friday to look at it.

Also I recommend looking at the first attempt by Renato Lima a few months ago:
https://code.launchpad.net/~akretion-team/openobject-addons/addons-stock-extensible-action-invoice-create

And also I would like to point that additionally we could try to refactor also the invoice line creation. In our Brazilian localization for instance, service lines should go to a different invoice than product invoices and delivery cost is a global tax instead of an invoice line. So making line creation modular would improve a lot the performance here and compatibility between modules hacking this currently.

Revision history for this message
Alexis de Lattre (alexis-via) wrote :

The first attempt by Renato Lima (unfortunately, I was not aware of it) was rejected by Fabien "as it crashed the buildbot when installing stock_invoice_directly". I tested the installation of stock_invoice_directly with my merge proposal : it installs fine. I tested a sale order + delivery with this module : it crashes... but it also crashes with a standard addons/trunk, so it's not caused by the changes introduced in my merge proposal.

Revision history for this message
Nhomar - Vauxoo (nhomar) wrote :

I tried Basic enviroments and it works as expected.

Just some comments:

Method should be documented as:

http://doc.openerp.com/v6.0/contribute/15_guidelines/documentation_guidelines.html

def action_send(self, cr, uid, ids, context=None):
    """ This sends an email to ALL the addresses of the selected partners.
    @param self: The object pointer
    @param cr: the current row, from the database cursor,
    @param uid: the current user ID for security checks
    @param ids: List of Phonecall to Opportunity's IDs
    @param context: A standard dictionary for contextual values
    """

With this little change everybody can use and inherit your methods with less code reading.

IMHO. Great Job...

I will mark as needs fixing just for logistic reason, in the moment you put this improvement in comments, i will approve.

review: Needs Fixing (pre-aproved)
Revision history for this message
Alexis de Lattre (alexis-via) wrote :

In my commit 6274, I introduced the standard syntax to document the new method I introduced.

Revision history for this message
Nhomar - Vauxoo (nhomar) :
review: Approve (aproved)
Revision history for this message
Raphael Collet (OpenERP) (rco-openerp) wrote :

I tried it, and it seems to work fine.

review: Approve
Revision history for this message
Raphaël Valyi - http://www.akretion.com (rvalyi) wrote :

Hello Alexis,

first kudos for all this work! For me it makes total sense in all aspects (and we badly need that for our Brazilian localization).
However I wanted to complete your work a bit, but couldn't do it as you set you as the owner of the branch. So I created this branch for akretion-team where I made further commits:
https://code.launchpad.net/~akretion-team/openobject-addons/extensible-stock-action_invoice_create2

What you could do is is merge my branch in yours and OpenERP SA could merge the whole if they agree.

Basically my 1st commit was to allow grouping invoice lines.
Then I did the same job as you (extracted only one method) for invoicing upon sale order.
Then I reused that newly extracted method in the invoicing of picking including services, as you can see aside from reducing the code size, I also fixed a bug with fiscal positions.
Then I wanted to see if we could deprecate the invoice_hook and _invoice_line_hooks as they are those nasty methods rebrowsing save records and recomputing the world. But it turns out we should keep them as they are used to link source and target records together (so can only work after target record creation).
Still, as you can see in my last commit, I found a bug in the purchase module invoicing and fixed it.

So Alexis, could you please merge my branch if you agree? Then we ask OpenERP SA if they still agree to merge that.

Overall that's cool, in 6.1 we modularized the picking creation previously, now with this merge we will modularize the invoice creation in the most common cases so overall this is an excellent news for localizations and other daily customizations that use to touch those objects a lot and need information to be propagated from orders to pickings to invoices without killing the database or the modularity.

review: Needs Fixing
Revision history for this message
Raphaël Valyi - http://www.akretion.com (rvalyi) wrote :

BTW, I technically said need fixing till I get a position about my further merges. Aside from that I'm for the merge.

Revision history for this message
Raphaël Valyi - http://www.akretion.com (rvalyi) wrote :

Hello, so now I feel like it's all quite consistent and a good leap forward. Thank you for having merged my further refactored related to invoice on picking. I approve for a merge into 6.1 addons now!

review: Approve
Revision history for this message
Raphael Collet (OpenERP) (rco-openerp) wrote :

On its way to be merged.

Thanks,
Raphael

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'purchase/stock.py'
2--- purchase/stock.py 2011-12-19 16:54:40 +0000
3+++ purchase/stock.py 2012-01-27 15:50:31 +0000
4@@ -58,18 +58,25 @@
5 'purchase_id': False,
6 }
7
8- def _get_address_invoice(self, cr, uid, picking):
9- """ Gets invoice address of a partner
10- @return {'contact': address, 'invoice': address} for invoice
11- """
12- res = super(stock_picking, self)._get_address_invoice(cr, uid, picking)
13- if picking.purchase_id:
14- partner_obj = self.pool.get('res.partner')
15- partner = picking.purchase_id.partner_id or picking.address_id.partner_id
16- data = partner_obj.address_get(cr, uid, [partner.id],
17- ['contact', 'invoice'])
18- res.update(data)
19- return res
20+ def _get_partner_to_invoice(self, cr, uid, picking, context=None):
21+ """Inherit the original function of the 'stock' module
22+ We select the partner of the sale order as the partner of the customer invoice
23+ """
24+ if picking.purchase_id:
25+ return picking.purchase_id.partner_id
26+ return super(stock_picking, self)._get_partner_to_invoice(cr, uid, picking, context=context)
27+
28+ def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None):
29+ """Inherit the original function of the 'stock' module in order to override some
30+ values if the picking has been generated by a purchase order
31+ """
32+ invoice_vals = super(stock_picking, self)._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context)
33+ if picking.purchase_id:
34+ invoice_vals['address_contact_id'], invoice_vals['address_invoice_id'] = \
35+ self.pool.get('res.partner').address_get(cr, uid, [partner.id],
36+ ['contact', 'invoice']).values()
37+ invoice_vals['fiscal_position'] = picking.purchase_id.fiscal_position.id
38+ return invoice_vals
39
40 def get_currency_id(self, cursor, user, picking):
41 if picking.purchase_id:
42@@ -111,6 +118,12 @@
43 def _invoice_line_hook(self, cursor, user, move_line, invoice_line_id):
44 if move_line.purchase_line_id:
45 invoice_line_obj = self.pool.get('account.invoice.line')
46+ purchase_line_obj = self.pool.get('purchase.order.line')
47+ purchase_line_obj.write(cursor, user, [move_line.purchase_line_id.id],
48+ {
49+ 'invoiced': True,
50+ 'invoice_lines': [(4, invoice_line_id)],
51+ })
52 invoice_line_obj.write(cursor, user, [invoice_line_id], {'note': move_line.purchase_line_id.notes,})
53 return super(stock_picking, self)._invoice_line_hook(cursor, user, move_line, invoice_line_id)
54
55
56=== modified file 'sale/sale.py'
57--- sale/sale.py 2012-01-05 08:57:53 +0000
58+++ sale/sale.py 2012-01-27 15:50:31 +0000
59@@ -981,10 +981,12 @@
60 'price_unit': 0.0,
61 }
62
63- def invoice_line_create(self, cr, uid, ids, context=None):
64- if context is None:
65- context = {}
66-
67+ def _prepare_order_line_invoice_line(self, cr, uid, ids, line, account_id=False, context=None):
68+ """Builds the invoice line dict from a sale order line
69+ @param line: sale order line object
70+ @param account_id: the id of the account to force eventually (the method is used for picking return including service)
71+ @return: dict that will be used to create the invoice line"""
72+
73 def _get_line_qty(line):
74 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
75 if line.product_uos:
76@@ -1003,48 +1005,59 @@
77 return self.pool.get('procurement.order').uom_get(cr, uid,
78 line.procurement_id.id, context=context)
79
80- create_ids = []
81- sales = {}
82- for line in self.browse(cr, uid, ids, context=context):
83- if not line.invoiced:
84+ if not line.invoiced:
85+ if not account_id:
86 if line.product_id:
87- a = line.product_id.product_tmpl_id.property_account_income.id
88- if not a:
89- a = line.product_id.categ_id.property_account_income_categ.id
90- if not a:
91+ account_id = line.product_id.product_tmpl_id.property_account_income.id
92+ if not account_id:
93+ account_id = line.product_id.categ_id.property_account_income_categ.id
94+ if not account_id:
95 raise osv.except_osv(_('Error !'),
96 _('There is no income account defined ' \
97- 'for this product: "%s" (id:%d)') % \
98- (line.product_id.name, line.product_id.id,))
99+ 'for this product: "%s" (id:%d)') % \
100+ (line.product_id.name, line.product_id.id,))
101 else:
102 prop = self.pool.get('ir.property').get(cr, uid,
103 'property_account_income_categ', 'product.category',
104 context=context)
105- a = prop and prop.id or False
106- uosqty = _get_line_qty(line)
107- uos_id = _get_line_uom(line)
108- pu = 0.0
109- if uosqty:
110- pu = round(line.price_unit * line.product_uom_qty / uosqty,
111- self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
112- fpos = line.order_id.fiscal_position or False
113- a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
114- if not a:
115- raise osv.except_osv(_('Error !'),
116- _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
117- inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
118- 'name': line.name,
119- 'origin': line.order_id.name,
120- 'account_id': a,
121- 'price_unit': pu,
122- 'quantity': uosqty,
123- 'discount': line.discount,
124- 'uos_id': uos_id,
125- 'product_id': line.product_id.id or False,
126- 'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
127- 'note': line.notes,
128- 'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
129- })
130+ account_id = prop and prop.id or False
131+ uosqty = _get_line_qty(line)
132+ uos_id = _get_line_uom(line)
133+ pu = 0.0
134+ if uosqty:
135+ pu = round(line.price_unit * line.product_uom_qty / uosqty,
136+ self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
137+ fpos = line.order_id.fiscal_position or False
138+ account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, account_id)
139+ if not account_id:
140+ raise osv.except_osv(_('Error !'),
141+ _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
142+ return {
143+ 'name': line.name,
144+ 'origin': line.order_id.name,
145+ 'account_id': account_id,
146+ 'price_unit': pu,
147+ 'quantity': uosqty,
148+ 'discount': line.discount,
149+ 'uos_id': uos_id,
150+ 'product_id': line.product_id.id or False,
151+ 'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
152+ 'note': line.notes,
153+ 'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
154+ }
155+ else:
156+ return False
157+
158+ def invoice_line_create(self, cr, uid, ids, context=None):
159+ if context is None:
160+ context = {}
161+
162+ create_ids = []
163+ sales = {}
164+ for line in self.browse(cr, uid, ids, context=context):
165+ vals = self._prepare_order_line_invoice_line(cr, uid, ids, line, False, context)
166+ if vals:
167+ inv_id = self.pool.get('account.invoice.line').create(cr, uid, vals, context=context)
168 cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
169 self.write(cr, uid, [line.id], {'invoiced': True})
170 sales[line.order_id.id] = True
171
172=== modified file 'sale/stock.py'
173--- sale/stock.py 2011-12-28 13:25:20 +0000
174+++ sale/stock.py 2012-01-27 15:50:31 +0000
175@@ -48,24 +48,32 @@
176 else:
177 return super(stock_picking, self).get_currency_id(cursor, user, picking)
178
179- def _get_payment_term(self, cursor, user, picking):
180- if picking.sale_id and picking.sale_id.payment_term:
181- return picking.sale_id.payment_term.id
182- return super(stock_picking, self)._get_payment_term(cursor, user, picking)
183-
184- def _get_address_invoice(self, cursor, user, picking):
185- res = {}
186+ def _get_partner_to_invoice(self, cr, uid, picking, context=None):
187+ """Inherit the original function of the 'stock' module
188+ We select the partner of the sale order as the partner of the customer invoice
189+ """
190 if picking.sale_id:
191- res['contact'] = picking.sale_id.partner_order_id.id
192- res['invoice'] = picking.sale_id.partner_invoice_id.id
193- return res
194- return super(stock_picking, self)._get_address_invoice(cursor, user, picking)
195+ return picking.sale_id.partner_id
196+ return super(stock_picking, self)._get_partner_to_invoice(cr, uid, picking, context=context)
197
198 def _get_comment_invoice(self, cursor, user, picking):
199 if picking.note or (picking.sale_id and picking.sale_id.note):
200 return picking.note or picking.sale_id.note
201 return super(stock_picking, self)._get_comment_invoice(cursor, user, picking)
202
203+ def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None):
204+ """Inherit the original function of the 'stock' module in order to override some
205+ values if the picking has been generated by a sale order
206+ """
207+ invoice_vals = super(stock_picking, self)._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context)
208+ if picking.sale_id:
209+ invoice_vals['address_contact_id'] = picking.sale_id.partner_order_id.id
210+ invoice_vals['address_invoice_id'] = picking.sale_id.partner_invoice_id.id
211+ invoice_vals['fiscal_position'] = picking.sale_id.fiscal_position.id
212+ invoice_vals['payment_term'] = picking.sale_id.payment_term.id
213+ invoice_vals['user_id'] = picking.sale_id.user_id.id
214+ return invoice_vals
215+
216 def _get_price_unit_invoice(self, cursor, user, move_line, type):
217 if move_line.sale_line_id and move_line.sale_line_id.product_id.id == move_line.product_id.id:
218 uom_id = move_line.product_id.uom_id.id
219@@ -167,32 +175,15 @@
220 if not account_id:
221 account_id = sale_line.product_id.categ_id.\
222 property_account_expense_categ.id
223- price_unit = sale_line.price_unit
224- discount = sale_line.discount
225- tax_ids = sale_line.tax_id
226- tax_ids = map(lambda x: x.id, tax_ids)
227-
228- account_analytic_id = self._get_account_analytic_invoice(cursor,
229- user, picking, sale_line)
230-
231- account_id = fiscal_position_obj.map_account(cursor, user, picking.sale_id.partner_id.property_account_position, account_id)
232- invoice = invoices[result[picking.id]]
233- invoice_line_id = invoice_line_obj.create(cursor, user, {
234- 'name': name,
235- 'invoice_id': invoice.id,
236- 'uos_id': sale_line.product_uos.id or sale_line.product_uom.id,
237- 'product_id': sale_line.product_id.id,
238- 'account_id': account_id,
239- 'price_unit': price_unit,
240- 'discount': discount,
241- 'quantity': sale_line.product_uos_qty,
242- 'invoice_line_tax_id': [(6, 0, tax_ids)],
243- 'account_analytic_id': account_analytic_id,
244- 'notes':sale_line.notes
245- }, context=context)
246- order_line_obj.write(cursor, user, [sale_line.id], {'invoiced': True,
247- 'invoice_lines': [(6, 0, [invoice_line_id])],
248- })
249+
250+ vals = order_line_obj._prepare_order_line_invoice_line(cursor, user, ids, sale_line, account_id, context)
251+ if vals: #note: in some cases we may not want to include all service lines as invoice lines
252+ vals['name'] = name
253+ vals['account_analytic_id'] = self._get_account_analytic_invoice(cursor, user, picking, sale_line)
254+ vals['invoice_id'] = invoices[result[picking.id]].id
255+ invoice_line_id = invoice_line_obj.create(cursor, user, vals, context=context)
256+ order_line_obj.write(cursor, user, [sale_line.id], {'invoiced': True,
257+ 'invoice_lines': [(6, 0, [invoice_line_id])]})
258 return result
259
260-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
261\ No newline at end of file
262+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
263
264=== modified file 'stock/stock.py'
265--- stock/stock.py 2012-01-27 13:55:37 +0000
266+++ stock/stock.py 2012-01-27 15:50:31 +0000
267@@ -1,3 +1,4 @@
268+# -*- coding: utf-8 -*-
269 ##############################################################################
270 #
271 # OpenERP, Open Source Management Solution
272@@ -864,21 +865,13 @@
273 def get_currency_id(self, cr, uid, picking):
274 return False
275
276- def _get_payment_term(self, cr, uid, picking):
277- """ Gets payment term from partner.
278- @return: Payment term
279- """
280- partner = picking.address_id.partner_id
281- return partner.property_payment_term and partner.property_payment_term.id or False
282-
283- def _get_address_invoice(self, cr, uid, picking):
284- """ Gets invoice address of a partner
285- @return {'contact': address, 'invoice': address} for invoice
286- """
287- partner_obj = self.pool.get('res.partner')
288- partner = picking.address_id.partner_id
289- return partner_obj.address_get(cr, uid, [partner.id],
290- ['contact', 'invoice'])
291+ def _get_partner_to_invoice(self, cr, uid, picking, context=None):
292+ """ Gets the partner that will be invoiced
293+ Note that this function is inherited in the sale module
294+ @param picking: object of the picking for which we are selecting the partner to invoice
295+ @return: object of the partner to invoice
296+ """
297+ return picking.address_id and picking.address_id.partner_id
298
299 def _get_comment_invoice(self, cr, uid, picking):
300 """
301@@ -958,6 +951,116 @@
302 inv_type = 'out_invoice'
303 return inv_type
304
305+ def _prepare_invoice_group(self, cr, uid, picking, partner, invoice, context=None):
306+ """Builds the dict for grouped invoices
307+ @param picking: picking object
308+ @param partner: object of the partner to invoice (not used here, but may be usefull if this function is inherited)
309+ @param invoice: object of the invoice that we are updating
310+ @return: dict that will be used to update the invoice
311+ """
312+ comment = self._get_comment_invoice(cr, uid, picking)
313+
314+ return {
315+ 'name': (invoice.name or '') + ', ' + (picking.name or ''),
316+ 'origin': (invoice.origin or '') + ', ' + (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
317+ 'comment': (comment and (invoice.comment and invoice.comment + "\n" + comment or comment)) or (invoice.comment and invoice.comment or ''),
318+ 'date_invoice': context.get('date_inv', False),
319+ 'user_id': uid
320+ }
321+
322+ def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None):
323+ """Builds the dict containing the values for the invoice
324+ @param picking: picking object
325+ @param partner: object of the partner to invoice
326+ @param inv_type: type of the invoice ('out_invoice', 'in_invoice', ...)
327+ @param journal_id: ID of the accounting journal
328+ @return: dict that will be used to create the invoice object
329+ """
330+ if inv_type in ('out_invoice', 'out_refund'):
331+ account_id = partner.property_account_receivable.id
332+ else:
333+ account_id = partner.property_account_payable.id
334+ address_contact_id, address_invoice_id = \
335+ self.pool.get('res.partner').address_get(cr, uid, [partner.id],
336+ ['contact', 'invoice']).values()
337+ comment = self._get_comment_invoice(cr, uid, picking)
338+ invoice_vals = {
339+ 'name': picking.name,
340+ 'origin': (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
341+ 'type': inv_type,
342+ 'account_id': account_id,
343+ 'partner_id': partner.id,
344+ 'address_invoice_id': address_invoice_id,
345+ 'address_contact_id': address_contact_id,
346+ 'comment': comment,
347+ 'payment_term': partner.property_payment_term and partner.property_payment_term.id
348+ or False,
349+ 'fiscal_position': partner.property_account_position.id,
350+ 'date_invoice': context.get('date_inv', False),
351+ 'company_id': picking.company_id.id,
352+ 'user_id': uid
353+ }
354+ cur_id = self.get_currency_id(cr, uid, picking)
355+ if cur_id:
356+ invoice_vals['currency_id'] = cur_id
357+ if journal_id:
358+ invoice_vals['journal_id'] = journal_id
359+ return invoice_vals
360+
361+ def _prepare_invoice_line(self, cr, uid, group, picking, move_line, invoice_id,
362+ invoice_vals, context=None):
363+ """Builds the dict containing the values for the invoice line
364+ @param group: True or False
365+ @param picking: picking object
366+ @param: move_line: move_line object
367+ @param: invoice_id: ID of the related invoice
368+ @param: invoice_vals: dict used to created the invoice
369+ @return: dict that will be used to create the invoice line
370+ """
371+ if group:
372+ name = (picking.name or '') + '-' + move_line.name
373+ else:
374+ name = move_line.name
375+ origin = move_line.picking_id.name or ''
376+ if move_line.picking_id.origin:
377+ origin += ':' + move_line.picking_id.origin
378+
379+ if invoice_vals['type'] in ('out_invoice', 'out_refund'):
380+ account_id = move_line.product_id.product_tmpl_id.\
381+ property_account_income.id
382+ if not account_id:
383+ account_id = move_line.product_id.categ_id.\
384+ property_account_income_categ.id
385+ else:
386+ account_id = move_line.product_id.product_tmpl_id.\
387+ property_account_expense.id
388+ if not account_id:
389+ account_id = move_line.product_id.categ_id.\
390+ property_account_expense_categ.id
391+ if invoice_vals['fiscal_position']:
392+ fp_obj = self.pool.get('account.fiscal.position')
393+ fiscal_position = fp_obj.browse(cr, uid, invoice_vals['fiscal_position'], context=context)
394+ account_id = fp_obj.map_account(cr, uid, fiscal_position, account_id)
395+ # set UoS if it's a sale and the picking doesn't have one
396+ uos_id = move_line.product_uos and move_line.product_uos.id or False
397+ if not uos_id and invoice_vals['type'] in ('out_invoice', 'out_refund'):
398+ uos_id = move_line.product_uom.id
399+
400+ return {
401+ 'name': name,
402+ 'origin': origin,
403+ 'invoice_id': invoice_id,
404+ 'uos_id': uos_id,
405+ 'product_id': move_line.product_id.id,
406+ 'account_id': account_id,
407+ 'price_unit': self._get_price_unit_invoice(cr, uid, move_line, invoice_vals['type']),
408+ 'discount': self._get_discount_invoice(cr, uid, move_line),
409+ 'quantity': move_line.product_uos_qty or move_line.product_qty,
410+ 'invoice_line_tax_id': [(6, 0,
411+ self._get_taxes_invoice(cr, uid, move_line, invoice_vals['type']))],
412+ 'account_analytic_id': self._get_account_analytic_invoice(cr, uid, picking, move_line),
413+ }
414+
415 def action_invoice_create(self, cr, uid, ids, journal_id=False,
416 group=False, type='out_invoice', context=None):
417 """ Creates invoice based on the invoice state selected for picking.
418@@ -971,14 +1074,13 @@
419
420 invoice_obj = self.pool.get('account.invoice')
421 invoice_line_obj = self.pool.get('account.invoice.line')
422- address_obj = self.pool.get('res.partner.address')
423 invoices_group = {}
424 res = {}
425 inv_type = type
426 for picking in self.browse(cr, uid, ids, context=context):
427 if picking.invoice_state != '2binvoiced':
428 continue
429- partner = picking.address_id and picking.address_id.partner_id
430+ partner = self._get_partner_to_invoice(cr, uid, picking, context=context)
431 if not partner:
432 raise osv.except_osv(_('Error, no partner !'),
433 _('Please put a partner on the picking list if you want to generate invoice.'))
434@@ -986,101 +1088,24 @@
435 if not inv_type:
436 inv_type = self._get_invoice_type(picking)
437
438- if inv_type in ('out_invoice', 'out_refund'):
439- account_id = partner.property_account_receivable.id
440- else:
441- account_id = partner.property_account_payable.id
442- address_contact_id, address_invoice_id = \
443- self._get_address_invoice(cr, uid, picking).values()
444- address = address_obj.browse(cr, uid, address_contact_id, context=context)
445-
446- comment = self._get_comment_invoice(cr, uid, picking)
447 if group and partner.id in invoices_group:
448 invoice_id = invoices_group[partner.id]
449 invoice = invoice_obj.browse(cr, uid, invoice_id)
450- invoice_vals = {
451- 'name': (invoice.name or '') + ', ' + (picking.name or ''),
452- 'origin': (invoice.origin or '') + ', ' + (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
453- 'comment': (comment and (invoice.comment and invoice.comment+"\n"+comment or comment)) or (invoice.comment and invoice.comment or ''),
454- 'date_invoice':context.get('date_inv',False),
455- 'user_id':uid
456- }
457- invoice_obj.write(cr, uid, [invoice_id], invoice_vals, context=context)
458+ invoice_vals_group = self._prepare_invoice_group(cr, uid, picking, partner, invoice, context=context)
459+ invoice_obj.write(cr, uid, [invoice_id], invoice_vals_group, context=context)
460 else:
461- invoice_vals = {
462- 'name': picking.name,
463- 'origin': (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
464- 'type': inv_type,
465- 'account_id': account_id,
466- 'partner_id': address.partner_id.id,
467- 'address_invoice_id': address_invoice_id,
468- 'address_contact_id': address_contact_id,
469- 'comment': comment,
470- 'payment_term': self._get_payment_term(cr, uid, picking),
471- 'fiscal_position': partner.property_account_position.id,
472- 'date_invoice': context.get('date_inv',False),
473- 'company_id': picking.company_id.id,
474- 'user_id':uid
475- }
476- cur_id = self.get_currency_id(cr, uid, picking)
477- if cur_id:
478- invoice_vals['currency_id'] = cur_id
479- if journal_id:
480- invoice_vals['journal_id'] = journal_id
481- invoice_id = invoice_obj.create(cr, uid, invoice_vals,
482- context=context)
483+ invoice_vals = self._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context)
484+ invoice_id = invoice_obj.create(cr, uid, invoice_vals, context=context)
485 invoices_group[partner.id] = invoice_id
486 res[picking.id] = invoice_id
487 for move_line in picking.move_lines:
488 if move_line.state == 'cancel':
489 continue
490- origin = move_line.picking_id.name or ''
491- if move_line.picking_id.origin:
492- origin += ':' + move_line.picking_id.origin
493- if group:
494- name = (picking.name or '') + '-' + move_line.name
495- else:
496- name = move_line.name
497-
498- if inv_type in ('out_invoice', 'out_refund'):
499- account_id = move_line.product_id.product_tmpl_id.\
500- property_account_income.id
501- if not account_id:
502- account_id = move_line.product_id.categ_id.\
503- property_account_income_categ.id
504- else:
505- account_id = move_line.product_id.product_tmpl_id.\
506- property_account_expense.id
507- if not account_id:
508- account_id = move_line.product_id.categ_id.\
509- property_account_expense_categ.id
510-
511- price_unit = self._get_price_unit_invoice(cr, uid,
512- move_line, inv_type)
513- discount = self._get_discount_invoice(cr, uid, move_line)
514- tax_ids = self._get_taxes_invoice(cr, uid, move_line, inv_type)
515- account_analytic_id = self._get_account_analytic_invoice(cr, uid, picking, move_line)
516-
517- #set UoS if it's a sale and the picking doesn't have one
518- uos_id = move_line.product_uos and move_line.product_uos.id or False
519- if not uos_id and inv_type in ('out_invoice', 'out_refund'):
520- uos_id = move_line.product_uom.id
521-
522- account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, partner.property_account_position, account_id)
523- invoice_line_id = invoice_line_obj.create(cr, uid, {
524- 'name': name,
525- 'origin': origin,
526- 'invoice_id': invoice_id,
527- 'uos_id': uos_id,
528- 'product_id': move_line.product_id.id,
529- 'account_id': account_id,
530- 'price_unit': price_unit,
531- 'discount': discount,
532- 'quantity': move_line.product_uos_qty or move_line.product_qty,
533- 'invoice_line_tax_id': [(6, 0, tax_ids)],
534- 'account_analytic_id': account_analytic_id,
535- }, context=context)
536- self._invoice_line_hook(cr, uid, move_line, invoice_line_id)
537+ vals = self._prepare_invoice_line(cr, uid, group, picking, move_line,
538+ invoice_id, invoice_vals, context=context)
539+ if vals:
540+ invoice_line_id = invoice_line_obj.create(cr, uid, vals, context=context)
541+ self._invoice_line_hook(cr, uid, move_line, invoice_line_id)
542
543 invoice_obj.button_compute(cr, uid, [invoice_id], context=context,
544 set_total=(inv_type in ('in_invoice', 'in_refund')))

Subscribers

People subscribed via source and target branches

to all changes: