Merge lp:~alexis-via/openobject-addons/extensible-stock-action_invoice_create into lp:openobject-addons
- extensible-stock-action_invoice_create
- Merge into trunk
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 |
Related bugs: |
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 |
Commit message
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:/
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.
Raphaël Valyi - http://www.akretion.com (rvalyi) wrote : | # |
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_
Nhomar - Vauxoo (nhomar) wrote : | # |
I tried Basic enviroments and it works as expected.
Just some comments:
Method should be documented as:
http://
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.
Alexis de Lattre (alexis-via) wrote : | # |
In my commit 6274, I introduced the standard syntax to document the new method I introduced.
Nhomar - Vauxoo (nhomar) : | # |
Raphael Collet (OpenERP) (rco-openerp) wrote : | # |
I tried it, and it seems to work fine.
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:/
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.
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.
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!
Raphael Collet (OpenERP) (rco-openerp) wrote : | # |
On its way to be merged.
Thanks,
Raphael
Preview Diff
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'))) |
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: /code.launchpad .net/~akretion- team/openobject -addons/ addons- stock-extensibl e-action- invoice- create
https:/
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.