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
=== modified file 'purchase/stock.py'
--- purchase/stock.py 2011-12-19 16:54:40 +0000
+++ purchase/stock.py 2012-01-27 15:50:31 +0000
@@ -58,18 +58,25 @@
58 'purchase_id': False,58 'purchase_id': False,
59 }59 }
6060
61 def _get_address_invoice(self, cr, uid, picking):61 def _get_partner_to_invoice(self, cr, uid, picking, context=None):
62 """ Gets invoice address of a partner62 """Inherit the original function of the 'stock' module
63 @return {'contact': address, 'invoice': address} for invoice63 We select the partner of the sale order as the partner of the customer invoice
64 """64 """
65 res = super(stock_picking, self)._get_address_invoice(cr, uid, picking)65 if picking.purchase_id:
66 if picking.purchase_id:66 return picking.purchase_id.partner_id
67 partner_obj = self.pool.get('res.partner')67 return super(stock_picking, self)._get_partner_to_invoice(cr, uid, picking, context=context)
68 partner = picking.purchase_id.partner_id or picking.address_id.partner_id68
69 data = partner_obj.address_get(cr, uid, [partner.id],69 def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None):
70 ['contact', 'invoice'])70 """Inherit the original function of the 'stock' module in order to override some
71 res.update(data)71 values if the picking has been generated by a purchase order
72 return res72 """
73 invoice_vals = super(stock_picking, self)._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context)
74 if picking.purchase_id:
75 invoice_vals['address_contact_id'], invoice_vals['address_invoice_id'] = \
76 self.pool.get('res.partner').address_get(cr, uid, [partner.id],
77 ['contact', 'invoice']).values()
78 invoice_vals['fiscal_position'] = picking.purchase_id.fiscal_position.id
79 return invoice_vals
7380
74 def get_currency_id(self, cursor, user, picking):81 def get_currency_id(self, cursor, user, picking):
75 if picking.purchase_id:82 if picking.purchase_id:
@@ -111,6 +118,12 @@
111 def _invoice_line_hook(self, cursor, user, move_line, invoice_line_id):118 def _invoice_line_hook(self, cursor, user, move_line, invoice_line_id):
112 if move_line.purchase_line_id:119 if move_line.purchase_line_id:
113 invoice_line_obj = self.pool.get('account.invoice.line')120 invoice_line_obj = self.pool.get('account.invoice.line')
121 purchase_line_obj = self.pool.get('purchase.order.line')
122 purchase_line_obj.write(cursor, user, [move_line.purchase_line_id.id],
123 {
124 'invoiced': True,
125 'invoice_lines': [(4, invoice_line_id)],
126 })
114 invoice_line_obj.write(cursor, user, [invoice_line_id], {'note': move_line.purchase_line_id.notes,})127 invoice_line_obj.write(cursor, user, [invoice_line_id], {'note': move_line.purchase_line_id.notes,})
115 return super(stock_picking, self)._invoice_line_hook(cursor, user, move_line, invoice_line_id)128 return super(stock_picking, self)._invoice_line_hook(cursor, user, move_line, invoice_line_id)
116129
117130
=== modified file 'sale/sale.py'
--- sale/sale.py 2012-01-05 08:57:53 +0000
+++ sale/sale.py 2012-01-27 15:50:31 +0000
@@ -981,10 +981,12 @@
981 'price_unit': 0.0,981 'price_unit': 0.0,
982 }982 }
983983
984 def invoice_line_create(self, cr, uid, ids, context=None):984 def _prepare_order_line_invoice_line(self, cr, uid, ids, line, account_id=False, context=None):
985 if context is None:985 """Builds the invoice line dict from a sale order line
986 context = {}986 @param line: sale order line object
987987 @param account_id: the id of the account to force eventually (the method is used for picking return including service)
988 @return: dict that will be used to create the invoice line"""
989
988 def _get_line_qty(line):990 def _get_line_qty(line):
989 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:991 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
990 if line.product_uos:992 if line.product_uos:
@@ -1003,48 +1005,59 @@
1003 return self.pool.get('procurement.order').uom_get(cr, uid,1005 return self.pool.get('procurement.order').uom_get(cr, uid,
1004 line.procurement_id.id, context=context)1006 line.procurement_id.id, context=context)
10051007
1006 create_ids = []1008 if not line.invoiced:
1007 sales = {}1009 if not account_id:
1008 for line in self.browse(cr, uid, ids, context=context):
1009 if not line.invoiced:
1010 if line.product_id:1010 if line.product_id:
1011 a = line.product_id.product_tmpl_id.property_account_income.id1011 account_id = line.product_id.product_tmpl_id.property_account_income.id
1012 if not a:1012 if not account_id:
1013 a = line.product_id.categ_id.property_account_income_categ.id1013 account_id = line.product_id.categ_id.property_account_income_categ.id
1014 if not a:1014 if not account_id:
1015 raise osv.except_osv(_('Error !'),1015 raise osv.except_osv(_('Error !'),
1016 _('There is no income account defined ' \1016 _('There is no income account defined ' \
1017 'for this product: "%s" (id:%d)') % \1017 'for this product: "%s" (id:%d)') % \
1018 (line.product_id.name, line.product_id.id,))1018 (line.product_id.name, line.product_id.id,))
1019 else:1019 else:
1020 prop = self.pool.get('ir.property').get(cr, uid,1020 prop = self.pool.get('ir.property').get(cr, uid,
1021 'property_account_income_categ', 'product.category',1021 'property_account_income_categ', 'product.category',
1022 context=context)1022 context=context)
1023 a = prop and prop.id or False1023 account_id = prop and prop.id or False
1024 uosqty = _get_line_qty(line)1024 uosqty = _get_line_qty(line)
1025 uos_id = _get_line_uom(line)1025 uos_id = _get_line_uom(line)
1026 pu = 0.01026 pu = 0.0
1027 if uosqty:1027 if uosqty:
1028 pu = round(line.price_unit * line.product_uom_qty / uosqty,1028 pu = round(line.price_unit * line.product_uom_qty / uosqty,
1029 self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))1029 self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
1030 fpos = line.order_id.fiscal_position or False1030 fpos = line.order_id.fiscal_position or False
1031 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)1031 account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, account_id)
1032 if not a:1032 if not account_id:
1033 raise osv.except_osv(_('Error !'),1033 raise osv.except_osv(_('Error !'),
1034 _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))1034 _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
1035 inv_id = self.pool.get('account.invoice.line').create(cr, uid, {1035 return {
1036 'name': line.name,1036 'name': line.name,
1037 'origin': line.order_id.name,1037 'origin': line.order_id.name,
1038 'account_id': a,1038 'account_id': account_id,
1039 'price_unit': pu,1039 'price_unit': pu,
1040 'quantity': uosqty,1040 'quantity': uosqty,
1041 'discount': line.discount,1041 'discount': line.discount,
1042 'uos_id': uos_id,1042 'uos_id': uos_id,
1043 'product_id': line.product_id.id or False,1043 'product_id': line.product_id.id or False,
1044 'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],1044 'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
1045 'note': line.notes,1045 'note': line.notes,
1046 'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,1046 'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
1047 })1047 }
1048 else:
1049 return False
1050
1051 def invoice_line_create(self, cr, uid, ids, context=None):
1052 if context is None:
1053 context = {}
1054
1055 create_ids = []
1056 sales = {}
1057 for line in self.browse(cr, uid, ids, context=context):
1058 vals = self._prepare_order_line_invoice_line(cr, uid, ids, line, False, context)
1059 if vals:
1060 inv_id = self.pool.get('account.invoice.line').create(cr, uid, vals, context=context)
1048 cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))1061 cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
1049 self.write(cr, uid, [line.id], {'invoiced': True})1062 self.write(cr, uid, [line.id], {'invoiced': True})
1050 sales[line.order_id.id] = True1063 sales[line.order_id.id] = True
10511064
=== modified file 'sale/stock.py'
--- sale/stock.py 2011-12-28 13:25:20 +0000
+++ sale/stock.py 2012-01-27 15:50:31 +0000
@@ -48,24 +48,32 @@
48 else:48 else:
49 return super(stock_picking, self).get_currency_id(cursor, user, picking)49 return super(stock_picking, self).get_currency_id(cursor, user, picking)
5050
51 def _get_payment_term(self, cursor, user, picking):51 def _get_partner_to_invoice(self, cr, uid, picking, context=None):
52 if picking.sale_id and picking.sale_id.payment_term:52 """Inherit the original function of the 'stock' module
53 return picking.sale_id.payment_term.id53 We select the partner of the sale order as the partner of the customer invoice
54 return super(stock_picking, self)._get_payment_term(cursor, user, picking)54 """
55
56 def _get_address_invoice(self, cursor, user, picking):
57 res = {}
58 if picking.sale_id:55 if picking.sale_id:
59 res['contact'] = picking.sale_id.partner_order_id.id56 return picking.sale_id.partner_id
60 res['invoice'] = picking.sale_id.partner_invoice_id.id57 return super(stock_picking, self)._get_partner_to_invoice(cr, uid, picking, context=context)
61 return res
62 return super(stock_picking, self)._get_address_invoice(cursor, user, picking)
6358
64 def _get_comment_invoice(self, cursor, user, picking):59 def _get_comment_invoice(self, cursor, user, picking):
65 if picking.note or (picking.sale_id and picking.sale_id.note):60 if picking.note or (picking.sale_id and picking.sale_id.note):
66 return picking.note or picking.sale_id.note61 return picking.note or picking.sale_id.note
67 return super(stock_picking, self)._get_comment_invoice(cursor, user, picking)62 return super(stock_picking, self)._get_comment_invoice(cursor, user, picking)
6863
64 def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None):
65 """Inherit the original function of the 'stock' module in order to override some
66 values if the picking has been generated by a sale order
67 """
68 invoice_vals = super(stock_picking, self)._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context)
69 if picking.sale_id:
70 invoice_vals['address_contact_id'] = picking.sale_id.partner_order_id.id
71 invoice_vals['address_invoice_id'] = picking.sale_id.partner_invoice_id.id
72 invoice_vals['fiscal_position'] = picking.sale_id.fiscal_position.id
73 invoice_vals['payment_term'] = picking.sale_id.payment_term.id
74 invoice_vals['user_id'] = picking.sale_id.user_id.id
75 return invoice_vals
76
69 def _get_price_unit_invoice(self, cursor, user, move_line, type):77 def _get_price_unit_invoice(self, cursor, user, move_line, type):
70 if move_line.sale_line_id and move_line.sale_line_id.product_id.id == move_line.product_id.id:78 if move_line.sale_line_id and move_line.sale_line_id.product_id.id == move_line.product_id.id:
71 uom_id = move_line.product_id.uom_id.id79 uom_id = move_line.product_id.uom_id.id
@@ -167,32 +175,15 @@
167 if not account_id:175 if not account_id:
168 account_id = sale_line.product_id.categ_id.\176 account_id = sale_line.product_id.categ_id.\
169 property_account_expense_categ.id177 property_account_expense_categ.id
170 price_unit = sale_line.price_unit178
171 discount = sale_line.discount179 vals = order_line_obj._prepare_order_line_invoice_line(cursor, user, ids, sale_line, account_id, context)
172 tax_ids = sale_line.tax_id180 if vals: #note: in some cases we may not want to include all service lines as invoice lines
173 tax_ids = map(lambda x: x.id, tax_ids)181 vals['name'] = name
174182 vals['account_analytic_id'] = self._get_account_analytic_invoice(cursor, user, picking, sale_line)
175 account_analytic_id = self._get_account_analytic_invoice(cursor,183 vals['invoice_id'] = invoices[result[picking.id]].id
176 user, picking, sale_line)184 invoice_line_id = invoice_line_obj.create(cursor, user, vals, context=context)
177185 order_line_obj.write(cursor, user, [sale_line.id], {'invoiced': True,
178 account_id = fiscal_position_obj.map_account(cursor, user, picking.sale_id.partner_id.property_account_position, account_id)186 'invoice_lines': [(6, 0, [invoice_line_id])]})
179 invoice = invoices[result[picking.id]]
180 invoice_line_id = invoice_line_obj.create(cursor, user, {
181 'name': name,
182 'invoice_id': invoice.id,
183 'uos_id': sale_line.product_uos.id or sale_line.product_uom.id,
184 'product_id': sale_line.product_id.id,
185 'account_id': account_id,
186 'price_unit': price_unit,
187 'discount': discount,
188 'quantity': sale_line.product_uos_qty,
189 'invoice_line_tax_id': [(6, 0, tax_ids)],
190 'account_analytic_id': account_analytic_id,
191 'notes':sale_line.notes
192 }, context=context)
193 order_line_obj.write(cursor, user, [sale_line.id], {'invoiced': True,
194 'invoice_lines': [(6, 0, [invoice_line_id])],
195 })
196 return result187 return result
197188
198# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
199\ No newline at end of file189\ No newline at end of file
190# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
200191
=== modified file 'stock/stock.py'
--- stock/stock.py 2012-01-27 13:55:37 +0000
+++ stock/stock.py 2012-01-27 15:50:31 +0000
@@ -1,3 +1,4 @@
1# -*- coding: utf-8 -*-
1##############################################################################2##############################################################################
2#3#
3# OpenERP, Open Source Management Solution4# OpenERP, Open Source Management Solution
@@ -864,21 +865,13 @@
864 def get_currency_id(self, cr, uid, picking):865 def get_currency_id(self, cr, uid, picking):
865 return False866 return False
866867
867 def _get_payment_term(self, cr, uid, picking):868 def _get_partner_to_invoice(self, cr, uid, picking, context=None):
868 """ Gets payment term from partner.869 """ Gets the partner that will be invoiced
869 @return: Payment term870 Note that this function is inherited in the sale module
870 """871 @param picking: object of the picking for which we are selecting the partner to invoice
871 partner = picking.address_id.partner_id872 @return: object of the partner to invoice
872 return partner.property_payment_term and partner.property_payment_term.id or False873 """
873874 return picking.address_id and picking.address_id.partner_id
874 def _get_address_invoice(self, cr, uid, picking):
875 """ Gets invoice address of a partner
876 @return {'contact': address, 'invoice': address} for invoice
877 """
878 partner_obj = self.pool.get('res.partner')
879 partner = picking.address_id.partner_id
880 return partner_obj.address_get(cr, uid, [partner.id],
881 ['contact', 'invoice'])
882875
883 def _get_comment_invoice(self, cr, uid, picking):876 def _get_comment_invoice(self, cr, uid, picking):
884 """877 """
@@ -958,6 +951,116 @@
958 inv_type = 'out_invoice'951 inv_type = 'out_invoice'
959 return inv_type952 return inv_type
960953
954 def _prepare_invoice_group(self, cr, uid, picking, partner, invoice, context=None):
955 """Builds the dict for grouped invoices
956 @param picking: picking object
957 @param partner: object of the partner to invoice (not used here, but may be usefull if this function is inherited)
958 @param invoice: object of the invoice that we are updating
959 @return: dict that will be used to update the invoice
960 """
961 comment = self._get_comment_invoice(cr, uid, picking)
962
963 return {
964 'name': (invoice.name or '') + ', ' + (picking.name or ''),
965 'origin': (invoice.origin or '') + ', ' + (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
966 'comment': (comment and (invoice.comment and invoice.comment + "\n" + comment or comment)) or (invoice.comment and invoice.comment or ''),
967 'date_invoice': context.get('date_inv', False),
968 'user_id': uid
969 }
970
971 def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None):
972 """Builds the dict containing the values for the invoice
973 @param picking: picking object
974 @param partner: object of the partner to invoice
975 @param inv_type: type of the invoice ('out_invoice', 'in_invoice', ...)
976 @param journal_id: ID of the accounting journal
977 @return: dict that will be used to create the invoice object
978 """
979 if inv_type in ('out_invoice', 'out_refund'):
980 account_id = partner.property_account_receivable.id
981 else:
982 account_id = partner.property_account_payable.id
983 address_contact_id, address_invoice_id = \
984 self.pool.get('res.partner').address_get(cr, uid, [partner.id],
985 ['contact', 'invoice']).values()
986 comment = self._get_comment_invoice(cr, uid, picking)
987 invoice_vals = {
988 'name': picking.name,
989 'origin': (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
990 'type': inv_type,
991 'account_id': account_id,
992 'partner_id': partner.id,
993 'address_invoice_id': address_invoice_id,
994 'address_contact_id': address_contact_id,
995 'comment': comment,
996 'payment_term': partner.property_payment_term and partner.property_payment_term.id
997 or False,
998 'fiscal_position': partner.property_account_position.id,
999 'date_invoice': context.get('date_inv', False),
1000 'company_id': picking.company_id.id,
1001 'user_id': uid
1002 }
1003 cur_id = self.get_currency_id(cr, uid, picking)
1004 if cur_id:
1005 invoice_vals['currency_id'] = cur_id
1006 if journal_id:
1007 invoice_vals['journal_id'] = journal_id
1008 return invoice_vals
1009
1010 def _prepare_invoice_line(self, cr, uid, group, picking, move_line, invoice_id,
1011 invoice_vals, context=None):
1012 """Builds the dict containing the values for the invoice line
1013 @param group: True or False
1014 @param picking: picking object
1015 @param: move_line: move_line object
1016 @param: invoice_id: ID of the related invoice
1017 @param: invoice_vals: dict used to created the invoice
1018 @return: dict that will be used to create the invoice line
1019 """
1020 if group:
1021 name = (picking.name or '') + '-' + move_line.name
1022 else:
1023 name = move_line.name
1024 origin = move_line.picking_id.name or ''
1025 if move_line.picking_id.origin:
1026 origin += ':' + move_line.picking_id.origin
1027
1028 if invoice_vals['type'] in ('out_invoice', 'out_refund'):
1029 account_id = move_line.product_id.product_tmpl_id.\
1030 property_account_income.id
1031 if not account_id:
1032 account_id = move_line.product_id.categ_id.\
1033 property_account_income_categ.id
1034 else:
1035 account_id = move_line.product_id.product_tmpl_id.\
1036 property_account_expense.id
1037 if not account_id:
1038 account_id = move_line.product_id.categ_id.\
1039 property_account_expense_categ.id
1040 if invoice_vals['fiscal_position']:
1041 fp_obj = self.pool.get('account.fiscal.position')
1042 fiscal_position = fp_obj.browse(cr, uid, invoice_vals['fiscal_position'], context=context)
1043 account_id = fp_obj.map_account(cr, uid, fiscal_position, account_id)
1044 # set UoS if it's a sale and the picking doesn't have one
1045 uos_id = move_line.product_uos and move_line.product_uos.id or False
1046 if not uos_id and invoice_vals['type'] in ('out_invoice', 'out_refund'):
1047 uos_id = move_line.product_uom.id
1048
1049 return {
1050 'name': name,
1051 'origin': origin,
1052 'invoice_id': invoice_id,
1053 'uos_id': uos_id,
1054 'product_id': move_line.product_id.id,
1055 'account_id': account_id,
1056 'price_unit': self._get_price_unit_invoice(cr, uid, move_line, invoice_vals['type']),
1057 'discount': self._get_discount_invoice(cr, uid, move_line),
1058 'quantity': move_line.product_uos_qty or move_line.product_qty,
1059 'invoice_line_tax_id': [(6, 0,
1060 self._get_taxes_invoice(cr, uid, move_line, invoice_vals['type']))],
1061 'account_analytic_id': self._get_account_analytic_invoice(cr, uid, picking, move_line),
1062 }
1063
961 def action_invoice_create(self, cr, uid, ids, journal_id=False,1064 def action_invoice_create(self, cr, uid, ids, journal_id=False,
962 group=False, type='out_invoice', context=None):1065 group=False, type='out_invoice', context=None):
963 """ Creates invoice based on the invoice state selected for picking.1066 """ Creates invoice based on the invoice state selected for picking.
@@ -971,14 +1074,13 @@
9711074
972 invoice_obj = self.pool.get('account.invoice')1075 invoice_obj = self.pool.get('account.invoice')
973 invoice_line_obj = self.pool.get('account.invoice.line')1076 invoice_line_obj = self.pool.get('account.invoice.line')
974 address_obj = self.pool.get('res.partner.address')
975 invoices_group = {}1077 invoices_group = {}
976 res = {}1078 res = {}
977 inv_type = type1079 inv_type = type
978 for picking in self.browse(cr, uid, ids, context=context):1080 for picking in self.browse(cr, uid, ids, context=context):
979 if picking.invoice_state != '2binvoiced':1081 if picking.invoice_state != '2binvoiced':
980 continue1082 continue
981 partner = picking.address_id and picking.address_id.partner_id1083 partner = self._get_partner_to_invoice(cr, uid, picking, context=context)
982 if not partner:1084 if not partner:
983 raise osv.except_osv(_('Error, no partner !'),1085 raise osv.except_osv(_('Error, no partner !'),
984 _('Please put a partner on the picking list if you want to generate invoice.'))1086 _('Please put a partner on the picking list if you want to generate invoice.'))
@@ -986,101 +1088,24 @@
986 if not inv_type:1088 if not inv_type:
987 inv_type = self._get_invoice_type(picking)1089 inv_type = self._get_invoice_type(picking)
9881090
989 if inv_type in ('out_invoice', 'out_refund'):
990 account_id = partner.property_account_receivable.id
991 else:
992 account_id = partner.property_account_payable.id
993 address_contact_id, address_invoice_id = \
994 self._get_address_invoice(cr, uid, picking).values()
995 address = address_obj.browse(cr, uid, address_contact_id, context=context)
996
997 comment = self._get_comment_invoice(cr, uid, picking)
998 if group and partner.id in invoices_group:1091 if group and partner.id in invoices_group:
999 invoice_id = invoices_group[partner.id]1092 invoice_id = invoices_group[partner.id]
1000 invoice = invoice_obj.browse(cr, uid, invoice_id)1093 invoice = invoice_obj.browse(cr, uid, invoice_id)
1001 invoice_vals = {1094 invoice_vals_group = self._prepare_invoice_group(cr, uid, picking, partner, invoice, context=context)
1002 'name': (invoice.name or '') + ', ' + (picking.name or ''),1095 invoice_obj.write(cr, uid, [invoice_id], invoice_vals_group, context=context)
1003 'origin': (invoice.origin or '') + ', ' + (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
1004 'comment': (comment and (invoice.comment and invoice.comment+"\n"+comment or comment)) or (invoice.comment and invoice.comment or ''),
1005 'date_invoice':context.get('date_inv',False),
1006 'user_id':uid
1007 }
1008 invoice_obj.write(cr, uid, [invoice_id], invoice_vals, context=context)
1009 else:1096 else:
1010 invoice_vals = {1097 invoice_vals = self._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context)
1011 'name': picking.name,1098 invoice_id = invoice_obj.create(cr, uid, invoice_vals, context=context)
1012 'origin': (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
1013 'type': inv_type,
1014 'account_id': account_id,
1015 'partner_id': address.partner_id.id,
1016 'address_invoice_id': address_invoice_id,
1017 'address_contact_id': address_contact_id,
1018 'comment': comment,
1019 'payment_term': self._get_payment_term(cr, uid, picking),
1020 'fiscal_position': partner.property_account_position.id,
1021 'date_invoice': context.get('date_inv',False),
1022 'company_id': picking.company_id.id,
1023 'user_id':uid
1024 }
1025 cur_id = self.get_currency_id(cr, uid, picking)
1026 if cur_id:
1027 invoice_vals['currency_id'] = cur_id
1028 if journal_id:
1029 invoice_vals['journal_id'] = journal_id
1030 invoice_id = invoice_obj.create(cr, uid, invoice_vals,
1031 context=context)
1032 invoices_group[partner.id] = invoice_id1099 invoices_group[partner.id] = invoice_id
1033 res[picking.id] = invoice_id1100 res[picking.id] = invoice_id
1034 for move_line in picking.move_lines:1101 for move_line in picking.move_lines:
1035 if move_line.state == 'cancel':1102 if move_line.state == 'cancel':
1036 continue1103 continue
1037 origin = move_line.picking_id.name or ''1104 vals = self._prepare_invoice_line(cr, uid, group, picking, move_line,
1038 if move_line.picking_id.origin:1105 invoice_id, invoice_vals, context=context)
1039 origin += ':' + move_line.picking_id.origin1106 if vals:
1040 if group:1107 invoice_line_id = invoice_line_obj.create(cr, uid, vals, context=context)
1041 name = (picking.name or '') + '-' + move_line.name1108 self._invoice_line_hook(cr, uid, move_line, invoice_line_id)
1042 else:
1043 name = move_line.name
1044
1045 if inv_type in ('out_invoice', 'out_refund'):
1046 account_id = move_line.product_id.product_tmpl_id.\
1047 property_account_income.id
1048 if not account_id:
1049 account_id = move_line.product_id.categ_id.\
1050 property_account_income_categ.id
1051 else:
1052 account_id = move_line.product_id.product_tmpl_id.\
1053 property_account_expense.id
1054 if not account_id:
1055 account_id = move_line.product_id.categ_id.\
1056 property_account_expense_categ.id
1057
1058 price_unit = self._get_price_unit_invoice(cr, uid,
1059 move_line, inv_type)
1060 discount = self._get_discount_invoice(cr, uid, move_line)
1061 tax_ids = self._get_taxes_invoice(cr, uid, move_line, inv_type)
1062 account_analytic_id = self._get_account_analytic_invoice(cr, uid, picking, move_line)
1063
1064 #set UoS if it's a sale and the picking doesn't have one
1065 uos_id = move_line.product_uos and move_line.product_uos.id or False
1066 if not uos_id and inv_type in ('out_invoice', 'out_refund'):
1067 uos_id = move_line.product_uom.id
1068
1069 account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, partner.property_account_position, account_id)
1070 invoice_line_id = invoice_line_obj.create(cr, uid, {
1071 'name': name,
1072 'origin': origin,
1073 'invoice_id': invoice_id,
1074 'uos_id': uos_id,
1075 'product_id': move_line.product_id.id,
1076 'account_id': account_id,
1077 'price_unit': price_unit,
1078 'discount': discount,
1079 'quantity': move_line.product_uos_qty or move_line.product_qty,
1080 'invoice_line_tax_id': [(6, 0, tax_ids)],
1081 'account_analytic_id': account_analytic_id,
1082 }, context=context)
1083 self._invoice_line_hook(cr, uid, move_line, invoice_line_id)
10841109
1085 invoice_obj.button_compute(cr, uid, [invoice_id], context=context,1110 invoice_obj.button_compute(cr, uid, [invoice_id], context=context,
1086 set_total=(inv_type in ('in_invoice', 'in_refund')))1111 set_total=(inv_type in ('in_invoice', 'in_refund')))

Subscribers

People subscribed via source and target branches

to all changes: