From 2e4777bb3288b8c56b2e747d07c0c2d049fa007e Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Thu, 31 Mar 2016 12:02:04 +0200 Subject: [PATCH 1/5] [FIX] account, purchase: price_unit as Float The price unit on the account move line is defined as a monetary field. However, on a Sales Order or on a Purchase Order, the price unit is defined as a float with precision 'Product Price'. Therefore, when an invoice is generated from a SO or a PO, inconsistencies arise if the product price precision is larger than the currency precision. opw-673251 --- addons/account/models/account.py | 11 ++++------- addons/account/models/account_invoice.py | 5 +++-- addons/account_voucher/account_voucher.py | 2 +- addons/purchase/invoice.py | 15 +++++++++------ addons/purchase/purchase.py | 2 +- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/addons/account/models/account.py b/addons/account/models/account.py index ef521dc7ea3..5802b66fedb 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -635,7 +635,7 @@ class AccountTax(models.Model): # the 'Account' decimal precision + 5), and that way it's like # rounding after the sum of the tax amounts of each line prec = currency.decimal_places - if company_id.tax_calculation_rounding_method == 'round_globally': + if company_id.tax_calculation_rounding_method == 'round_globally' or not bool(self.env.context.get("round", True)): prec += 5 total_excluded = total_included = base = round(price_unit * quantity, prec) @@ -650,7 +650,7 @@ class AccountTax(models.Model): continue tax_amount = tax._compute_amount(base, price_unit, quantity, product, partner) - if company_id.tax_calculation_rounding_method == 'round_globally': + if company_id.tax_calculation_rounding_method == 'round_globally' or not bool(self.env.context.get("round", True)): tax_amount = round(tax_amount, prec) else: tax_amount = currency.round(tax_amount) @@ -676,8 +676,8 @@ class AccountTax(models.Model): return { 'taxes': sorted(taxes, key=lambda k: k['sequence']), - 'total_excluded': currency.round(total_excluded), - 'total_included': currency.round(total_included), + 'total_excluded': currency.round(total_excluded) if bool(self.env.context.get("round", True)) else total_excluded, + 'total_included': currency.round(total_included) if bool(self.env.context.get("round", True)) else total_included, 'base': base, } @@ -733,6 +733,3 @@ class AccountOperationTemplate(models.Model): @api.onchange('name') def onchange_name(self): self.label = self.name - - - diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 787fc536d45..c428b8ba571 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1068,7 +1068,7 @@ class AccountInvoiceLine(models.Model): required=True, domain=[('deprecated', '=', False)], default=_default_account, help="The income or expense account related to the selected product.") - price_unit = fields.Monetary(string='Unit Price', required=True) + price_unit = fields.Float(string='Unit Price', required=True, digits=dp.get_precision('Product Price')) price_subtotal = fields.Monetary(string='Amount', store=True, readonly=True, compute='_compute_price') price_subtotal_signed = fields.Monetary(string='Amount Signed', currency_field='company_currency_id', @@ -1126,7 +1126,8 @@ class AccountInvoiceLine(models.Model): fix_price = self.env['account.tax']._fix_tax_included_price if self.invoice_id.type in ('in_invoice', 'in_refund'): - if not self.price_unit or float_compare(self.price_unit, self.product_id.standard_price, precision_digits=self.currency_id.rounding) == 0: + prec = self.env['decimal.precision'].precision_get('Product Price') + if not self.price_unit or float_compare(self.price_unit, self.product_id.standard_price, precision_digits=prec) == 0: self.price_unit = fix_price(self.product_id.standard_price, taxes, fp_taxes) else: self.price_unit = fix_price(self.product_id.lst_price, taxes, fp_taxes) diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py index d9073b0c737..79110eb2041 100644 --- a/addons/account_voucher/account_voucher.py +++ b/addons/account_voucher/account_voucher.py @@ -322,7 +322,7 @@ class account_voucher_line(models.Model): account_id = fields.Many2one('account.account', string='Account', required=True, domain=[('deprecated', '=', False)], help="The income or expense account related to the selected product.") - price_unit = fields.Monetary(string='Unit Price', required=True, oldname='amount') + price_unit = fields.Float(string='Unit Price', required=True, digits=dp.get_precision('Product Price'), oldname='amount') price_subtotal = fields.Monetary(string='Amount', store=True, readonly=True, compute='_compute_subtotal') quantity = fields.Float(digits=dp.get_precision('Product Unit of Measure'), diff --git a/addons/purchase/invoice.py b/addons/purchase/invoice.py index 43841099487..e310127936d 100644 --- a/addons/purchase/invoice.py +++ b/addons/purchase/invoice.py @@ -58,7 +58,7 @@ class AccountInvoice(models.Model): 'uom_id': line.product_uom.id, 'product_id': line.product_id.id, 'account_id': self.env['account.invoice.line'].with_context({'journal_id': self.journal_id.id, 'type': 'in_invoice'})._default_account(), - 'price_unit': line.order_id.currency_id.compute(line.price_unit, self.currency_id), + 'price_unit': line.order_id.currency_id.compute(line.price_unit, self.currency_id, round=False), 'quantity': qty, 'discount': 0.0, 'account_analytic_id': line.account_analytic_id.id, @@ -79,7 +79,7 @@ class AccountInvoice(models.Model): def _onchange_currency_id(self): if self.currency_id: for line in self.invoice_line_ids.filtered(lambda r: r.purchase_line_id): - line.price_unit = line.purchase_id.currency_id.compute(line.purchase_line_id.price_unit, self.currency_id) + line.price_unit = line.purchase_id.currency_id.compute(line.purchase_line_id.price_unit, self.currency_id, round=False) @api.onchange('invoice_line_ids') def _onchange_origin(self): @@ -120,6 +120,7 @@ class AccountInvoice(models.Model): reference_account_id = i_line.product_id.product_tmpl_id.get_product_accounts(fiscal_pos=fpos)['stock_input'].id diff_res = [] account_prec = inv.company_id.currency_id.decimal_places + product_prec = self.env['decimal.precision'].precision_get('Product Price') # 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']: @@ -131,19 +132,21 @@ class AccountInvoice(models.Model): if valuation_stock_move: valuation_price_unit = valuation_stock_move[0].price_unit if inv.currency_id.id != company_currency.id: - valuation_price_unit = company_currency.with_context(date=inv.date_invoice).compute(valuation_price_unit, inv.currency_id) - if valuation_price_unit != i_line.price_unit and line['price_unit'] == i_line.price_unit and acc: + valuation_price_unit = company_currency.with_context(date=inv.date_invoice).compute(valuation_price_unit, inv.currency_id, round=False) + if float_compare(valuation_price_unit, i_line.price_unit, precision_digits=product_prec) != 0\ + and float_compare(line['price_unit'], i_line.price_unit, precision_digits=product_prec) == 0\ + and acc: # price with discount and without tax included price_unit = i_line.price_unit * (1 - (i_line.discount or 0.0) / 100.0) 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'] + price_unit = taxes.with_context(round=False).compute_all(price_unit, currency=inv.currency_id, quantity=1.0)['total_excluded'] line.update({'price': round(valuation_price_unit * line['quantity'], account_prec)}) diff_res.append({ 'type': 'src', 'name': i_line.name[:64], - 'price_unit': round(price_unit - valuation_price_unit, account_prec), + 'price_unit': round(price_unit - valuation_price_unit, product_prec), 'quantity': line['quantity'], 'price': round((price_unit - valuation_price_unit) * line['quantity'], account_prec), 'account_id': acc, diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 063b74ce698..611568d302b 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -535,7 +535,7 @@ class PurchaseOrderLine(models.Model): order = line.order_id price_unit = line.price_unit if line.taxes_id: - price_unit = line.taxes_id.compute_all(price_unit, currency=line.order_id.currency_id, quantity=1.0)['total_excluded'] + price_unit = line.taxes_id.with_context(round=False).compute_all(price_unit, currency=line.order_id.currency_id, quantity=1.0)['total_excluded'] if line.product_uom.id != line.product_id.uom_id.id: price_unit *= line.product_uom.factor / line.product_id.uom_id.factor if order.currency_id != order.company_id.currency_id: From 2e0f55537fc7bc150b810fcaad585370ee1c5246 Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Fri, 1 Apr 2016 11:17:21 +0200 Subject: [PATCH 2/5] [IMP] website_hr_recruitment: valid email address on jobs/apply page A public user is currenlty able to enter 'georges.pompidou' as email address which is obviously not valid. We change the input type ffrom text to email and let the browser manager the email format validity --- addons/website_hr_recruitment/views/templates.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website_hr_recruitment/views/templates.xml b/addons/website_hr_recruitment/views/templates.xml index 9165c7992f4..3ac6736a7e2 100644 --- a/addons/website_hr_recruitment/views/templates.xml +++ b/addons/website_hr_recruitment/views/templates.xml @@ -166,7 +166,7 @@
- +
From b3cfec189bf42abcb89b5c157c08f92ce45a3d01 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Fri, 1 Apr 2016 11:33:08 +0200 Subject: [PATCH 3/5] [FIX] project_issue: link to issues On the project dashboard, a user needs to click exactly on the number or on the label in order to open the issue list. If the user clicks outside the text (but still inside the box), the project is open instead. The reason is because the link is specifically defined on the number/label. The fix defines the link on the box itself. Fixes #11396 opw-672380 --- addons/project_issue/project_dashboard.xml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/addons/project_issue/project_dashboard.xml b/addons/project_issue/project_dashboard.xml index 381a6173026..b7197fa086d 100644 --- a/addons/project_issue/project_dashboard.xml +++ b/addons/project_issue/project_dashboard.xml @@ -10,15 +10,13 @@ - + @@ -29,4 +27,4 @@ - \ No newline at end of file + From 5de3900258b458a4f5af964c78c0c644518bd9d7 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Fri, 1 Apr 2016 11:55:18 +0200 Subject: [PATCH 4/5] [FIX] project_issue: count issues If an issue is created without default stage, it is not counted in the total of issues. However, a task without default stage is counted (see commit 432c01c0). Fixes #11398 opw-672380 --- addons/project_issue/project_issue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py index f25eb43e9d9..91beb819294 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -464,7 +464,7 @@ class project(osv.Model): _columns = { 'issue_count': fields.function(_issue_count, type='integer', string="Issues",), 'issue_ids': fields.one2many('project.issue', 'project_id', string="Issues", - domain=[('stage_id.fold', '=', False)]), + domain=['|', ('stage_id.fold', '=', False), ('stage_id', '=', False)]), 'issue_needaction_count': fields.function(_issue_needaction_count, type='integer', string="Issues",), } From 845b1bde7b327272ad9a6e954223268ecca3c6b2 Mon Sep 17 00:00:00 2001 From: qdp-odoo Date: Fri, 1 Apr 2016 14:02:33 +0200 Subject: [PATCH 5/5] [FIX] purchase: empty the stock interim account in case of purchase in foreign currency with a fluctuating exchange rate --- addons/purchase/invoice.py | 9 +++++++-- addons/purchase/purchase.py | 23 +++++++++++++++-------- addons/purchase/stock.py | 11 +++++++++++ 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/addons/purchase/invoice.py b/addons/purchase/invoice.py index e310127936d..4d478a99437 100644 --- a/addons/purchase/invoice.py +++ b/addons/purchase/invoice.py @@ -128,9 +128,14 @@ class AccountInvoice(models.Model): if i_line.product_id.cost_method != 'standard' and i_line.purchase_line_id: #for average/fifo/lifo costing method, fetch real cost price from incomming moves stock_move_obj = self.env['stock.move'] - valuation_stock_move = stock_move_obj.search([('purchase_line_id', '=', i_line.purchase_line_id.id)], limit=1) + valuation_stock_move = stock_move_obj.search([('purchase_line_id', '=', i_line.purchase_line_id.id), ('state', '=', 'done')]) if valuation_stock_move: - valuation_price_unit = valuation_stock_move[0].price_unit + valuation_price_unit_total = 0 + valuation_total_qty = 0 + for val_stock_move in valuation_stock_move: + valuation_price_unit_total += val_stock_move.price_unit * val_stock_move.product_qty + valuation_total_qty += val_stock_move.product_qty + valuation_price_unit = valuation_price_unit_total / valuation_total_qty if inv.currency_id.id != company_currency.id: valuation_price_unit = company_currency.with_context(date=inv.date_invoice).compute(valuation_price_unit, inv.currency_id, round=False) if float_compare(valuation_price_unit, i_line.price_unit, precision_digits=product_prec) != 0\ diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 611568d302b..662e63c84aa 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -527,19 +527,26 @@ class PurchaseOrderLine(models.Model): date_order = fields.Datetime(related='order_id.date_order', string='Order Date', readonly=True) procurement_ids = fields.One2many('procurement.order', 'purchase_line_id', string='Associated Procurements', copy=False) + @api.multi + def _get_stock_move_price_unit(self): + self.ensure_one() + line = self[0] + order = line.order_id + price_unit = line.price_unit + if line.taxes_id: + price_unit = line.taxes_id.with_context(round=False).compute_all(price_unit, currency=line.order_id.currency_id, quantity=1.0)['total_excluded'] + if line.product_uom.id != line.product_id.uom_id.id: + price_unit *= line.product_uom.factor / line.product_id.uom_id.factor + if order.currency_id != order.company_id.currency_id: + price_unit = order.currency_id.compute(price_unit, order.company_id.currency_id, round=False) + return price_unit + @api.multi def _create_stock_moves(self, picking): moves = self.env['stock.move'] done = self.env['stock.move'].browse() for line in self: - order = line.order_id - price_unit = line.price_unit - if line.taxes_id: - price_unit = line.taxes_id.with_context(round=False).compute_all(price_unit, currency=line.order_id.currency_id, quantity=1.0)['total_excluded'] - if line.product_uom.id != line.product_id.uom_id.id: - price_unit *= line.product_uom.factor / line.product_id.uom_id.factor - if order.currency_id != order.company_id.currency_id: - price_unit = order.currency_id.compute(price_unit, order.company_id.currency_id, round=False) + price_unit = line._get_stock_move_price_unit() template = { 'name': line.name or '', diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index 3f8faa53d3c..38f656bc913 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -32,6 +32,17 @@ class stock_move(osv.osv): def get_price_unit(self, cr, uid, move, context=None): """ Returns the unit price to store on the quant """ if move.purchase_line_id: + order = move.purchase_line_id.order_id + #if the currency of the PO is different than the company one, the price_unit on the move must be reevaluated + #(was created at the rate of the PO confirmation, but must be valuated at the rate of stock move execution) + if order.currency_id != move.company_id.currency_id: + #we don't pass the move.date in the compute() for the currency rate on purpose because + # 1) get_price_unit() is supposed to be called only through move.action_done(), + # 2) the move hasn't yet the correct date (currently it is the expected date, after + # completion of action_done() it will be now() ) + price_unit = move.purchase_line_id._get_stock_move_price_unit() + move.write({'price_unit': price_unit}) + return price_unit return move.price_unit return super(stock_move, self).get_price_unit(cr, uid, move, context=context)