Files
odoo_source/addons/purchase_stock/models/account_invoice.py
T
Goffin Simon 2c702108b8 [FIX] purchase_stock: Wrong journal entry created from vendor bill
Steps to reproduce:

- Create a storable product P with a vendor V and a purchase price = 100€
- Set the category of the prodcut to automated and AVCO and set a price diff account on it
- Create PO for V with 10 P with a price_unit = 110€
- Confirm the PO and deliver 7 P with no backorder
- Change the ordered quantity of P to 9 on PO (and the unit_price will be set to 100€)
- Go on the delivery order and deliver 2 remainig P
- Create the invoice and validate it

Bug:

The amount due on the invoice was 900.02€ instead of 900.00€

PS: The debit and credit of an aml are computed with the price (defined in function
_anglo_saxon_purchase_move_lines) with the function _convert_prepared_anglosaxon_line
defined on model account.invoice. So rounding the price unit after the computation
of the price allows to keep the same total as the one computed for the PO.

opw:1951465

closes odoo/odoo#32345

Signed-off-by: Simon Goffin (sig) <sig@openerp.com>
2019-04-03 08:49:57 +00:00

152 lines
9.4 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.tools.float_utils import float_compare
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
@api.model
def invoice_line_move_line_get(self):
res = super(AccountInvoice, self).invoice_line_move_line_get()
if self.env.user.company_id.anglo_saxon_accounting:
if self.type in ['in_invoice', 'in_refund']:
for i_line in self.invoice_line_ids:
res.extend(self._anglo_saxon_purchase_move_lines(i_line, res))
return res
@api.model
def _anglo_saxon_purchase_move_lines(self, i_line, res):
"""Return the additional move lines for purchase invoices and refunds.
i_line: An account.invoice.line object.
res: The move line entries produced so far by the parent move_line_get.
"""
inv = i_line.invoice_id
company_currency = inv.company_id.currency_id
if i_line.product_id and i_line.product_id.valuation == 'real_time' and i_line.product_id.type == 'product':
# get the fiscal position
fpos = i_line.invoice_id.fiscal_position_id
# get the price difference account at the product
acc = i_line.product_id.property_account_creditor_price_difference
if not acc:
# if not found on the product get the price difference account at the category
acc = i_line.product_id.categ_id.property_account_creditor_price_difference_categ
acc = fpos.map_account(acc).id
# reference_account_id is the stock input account
reference_account_id = i_line.product_id.product_tmpl_id.get_product_accounts(fiscal_pos=fpos)['stock_input'].id
diff_res = []
# calculate and write down the possible price difference between invoice price and product price
for line in res:
if line.get('invl_id', 0) == i_line.id and reference_account_id == line['account_id']:
# valuation_price unit is always expressed in invoice currency, so that it can always be computed with the good rate
valuation_price_unit = company_currency._convert(
i_line.product_id.uom_id._compute_price(i_line.product_id.standard_price, i_line.uom_id),
inv.currency_id,
company=inv.company_id, date=fields.Date.today(), round=False,
)
line_quantity = line['quantity']
if i_line.product_id.cost_method != 'standard' and i_line.purchase_line_id:
po_currency = i_line.purchase_id.currency_id
po_company = i_line.purchase_id.company_id
#for average/fifo/lifo costing method, fetch real cost price from incomming moves
valuation_price_unit = po_currency._convert(
i_line.purchase_line_id.product_uom._compute_price(i_line.purchase_line_id.price_unit, i_line.uom_id),
inv.currency_id,
company=po_company, date=inv.date or inv.date_invoice, round=False,
)
stock_move_obj = self.env['stock.move']
valuation_stock_move = stock_move_obj.search([
('purchase_line_id', '=', i_line.purchase_line_id.id),
('state', '=', 'done'), ('product_qty', '!=', 0.0)
])
if self.type == 'in_refund':
valuation_stock_move = valuation_stock_move.filtered(lambda m: m._is_out())
elif self.type == 'in_invoice':
valuation_stock_move = valuation_stock_move.filtered(lambda m: m._is_in())
if valuation_stock_move:
valuation_price_unit_total = 0
valuation_total_qty = 0
for val_stock_move in valuation_stock_move:
# In case val_stock_move is a return move, its valuation entries have been made with the
# currency rate corresponding to the original stock move
valuation_date = val_stock_move.origin_returned_move_id.date or val_stock_move.date_expected
valuation_price_unit_total += company_currency._convert(
abs(val_stock_move.price_unit) * val_stock_move.product_qty,
inv.currency_id,
company=inv.company_id, date=valuation_date, round=False,
)
valuation_total_qty += val_stock_move.product_qty
valuation_price_unit = valuation_price_unit_total / valuation_total_qty
valuation_price_unit = i_line.product_id.uom_id._compute_price(valuation_price_unit, i_line.uom_id)
line_quantity = valuation_total_qty
elif i_line.product_id.cost_method == 'fifo':
# In this condition, we have a real price-valuated product which has not yet been received
valuation_price_unit = po_currency._convert(
i_line.purchase_line_id.price_unit, inv.currency_id,
company=po_company, date=inv.date or inv.date_invoice, round=False,
)
interim_account_price = valuation_price_unit * line_quantity
invoice_cur_prec = inv.currency_id.decimal_places
if float_compare(valuation_price_unit, i_line.price_unit, precision_digits=invoice_cur_prec) != 0 and float_compare(line['price_unit'], i_line.price_unit, precision_digits=invoice_cur_prec) == 0:
# price with discount and without tax included
price_unit = i_line.price_unit * (1 - (i_line.discount or 0.0) / 100.0)
tax_ids = []
if line['tax_ids']:
#line['tax_ids'] is like [(4, tax_id, None), (4, tax_id2, None)...]
taxes = self.env['account.tax'].browse([x[1] for x in line['tax_ids']])
price_unit = taxes.compute_all(price_unit, currency=inv.currency_id, quantity=1.0)['total_excluded']
for tax in taxes:
tax_ids.append((4, tax.id, None))
for child in tax.children_tax_ids:
if child.type_tax_use != 'none':
tax_ids.append((4, child.id, None))
price_before = line.get('price', 0.0)
price_unit_val_dif = price_unit - valuation_price_unit
price_val_dif = price_before - interim_account_price
if inv.currency_id.compare_amounts(i_line.price_unit, valuation_price_unit) != 0 and acc:
# If the unit prices have not changed and we have a
# valuation difference, it means this difference is due to exchange rates,
# so we don't create anything, the exchange rate entries will
# be processed automatically by the rest of the code.
diff_line = {
'type': 'src',
'name': i_line.name[:64],
'price_unit': price_unit_val_dif,
'quantity': line_quantity,
'price': inv.currency_id.round(price_val_dif),
'account_id': acc,
'product_id': line['product_id'],
'uom_id': line['uom_id'],
'account_analytic_id': line['account_analytic_id'],
'tax_ids': tax_ids,
}
# We update the original line accordingly
line['price_unit'] = line['price_unit'] - diff_line['price_unit']
line['price'] = inv.currency_id.round(line['quantity'] * line['price_unit'])
line['price_unit'] = inv.currency_id.round(line['price_unit'])
diff_res.append(diff_line)
return diff_res
return []
def _get_last_step_stock_moves(self):
""" Overridden from stock_account.
Returns the stock moves associated to this invoice."""
rslt = super(AccountInvoice, self)._get_last_step_stock_moves()
for invoice in self.filtered(lambda x: x.type == 'in_invoice'):
rslt += invoice.mapped('invoice_line_ids.purchase_line_id.move_ids').filtered(lambda x: x.state == 'done' and x.location_id.usage == 'supplier')
for invoice in self.filtered(lambda x: x.type == 'in_refund'):
rslt += invoice.mapped('invoice_line_ids.purchase_line_id.move_ids').filtered(lambda x: x.state == 'done' and x.location_dest_id.usage == 'supplier')
return rslt