[MERGE] forward port branch 10.0 up to aaa7566141
This commit is contained in:
@@ -4,7 +4,7 @@ import time
|
||||
import math
|
||||
|
||||
from odoo.osv import expression
|
||||
from odoo.tools.float_utils import float_round as round
|
||||
from odoo.tools.float_utils import float_round as round, float_is_zero as is_zero
|
||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo import api, fields, models, _
|
||||
@@ -785,6 +785,23 @@ class AccountTax(models.Model):
|
||||
# || ... | .. | .. |
|
||||
# ----------------------------
|
||||
|
||||
base = round(price_unit * quantity, prec)
|
||||
|
||||
# Keep track of subsequent recomputed bases in order to avoid some rounding issues.
|
||||
# For example, 399.99 computed with a tax 20% price_include leads to
|
||||
# base = 399.99 / 1.2 = 333.32500000000005
|
||||
# tax_amount = base * 0.2 = 66.665
|
||||
# round(base) + round(tax_amount) = 333.33 + 66.67 = 400.0 (!= 399.99: WRONG)
|
||||
#
|
||||
# To fix such issues, base_gaps will contains amount between two bases.
|
||||
# In our example, the gap between 333.32500000000005 and 399.99 is 66.66499999999996
|
||||
#
|
||||
# Then, when processing the tax and because 66.665 - 66.66499999999996 is close to zero,
|
||||
# the real gap is returned and so:
|
||||
# tax_amount = 66.66499999999996
|
||||
# round(base) + round(tax_amount) = 333.33 + 66.66 = 399.99 (CORRECT)
|
||||
base_gaps = []
|
||||
|
||||
def recompute_base(base_amount, fixed_amount, percent_amount):
|
||||
# Recompute the new base amount based on included fixed/percent amount and the current base amount.
|
||||
# Example:
|
||||
@@ -798,9 +815,9 @@ class AccountTax(models.Model):
|
||||
# (145 - 15) / (1.0 + ((10 + 20) / 100.0)) = 130 / 1.3 = 100
|
||||
if fixed_amount == 0.0 and percent_amount == 0.0:
|
||||
return base_amount
|
||||
return (base_amount - fixed_amount) / (1.0 + percent_amount / 100.0)
|
||||
|
||||
base = round(price_unit * quantity, prec)
|
||||
new_base = (base_amount - fixed_amount) / (1.0 + percent_amount / 100.0)
|
||||
base_gaps.append(base_amount - new_base)
|
||||
return new_base
|
||||
|
||||
# For the computation of move lines, we could have a negative base value.
|
||||
# In this case, compute all with positive values and negative them at the end.
|
||||
@@ -830,15 +847,38 @@ class AccountTax(models.Model):
|
||||
# || tax_1 | OK | XXXX |
|
||||
# || tax_2 | XXXX | XXXX |
|
||||
# || tax_3 | XXXX | XXXX |
|
||||
# \/ ... | .. | .. |
|
||||
# \/ ... | .. | .. |
|
||||
# ----------------------------
|
||||
taxes_vals = []
|
||||
for tax in taxes:
|
||||
|
||||
def compute_amount(tax):
|
||||
# Compute the amount of the tax but don't deal with the price_include because it's already
|
||||
# took into account on the base amount except for 'division' tax:
|
||||
# (tax.amount_type == 'percent' && not tax.price_include)
|
||||
# == (tax.amount_type == 'division' && tax.price_include)
|
||||
tax_amount = tax.with_context(force_price_include=False)._compute_amount(base, price_unit, quantity, product, partner)
|
||||
#
|
||||
# In case of price_included tax, subtract the amount to the corresponding
|
||||
# gap between the current base and the next one.
|
||||
amount = tax.with_context(force_price_include=False)._compute_amount(
|
||||
base, price_unit, quantity, product, partner)
|
||||
|
||||
if not tax.price_include or not base_gaps:
|
||||
return amount
|
||||
|
||||
# Compute the new gap after subtracting of the tax amount
|
||||
new_gap = base_gaps[-1] - amount
|
||||
|
||||
# If the newly computed gap is very close of zero, return the current gap to avoid
|
||||
# rounding issues (see comments above base_gaps).
|
||||
if is_zero(new_gap, prec):
|
||||
return base_gaps.pop()
|
||||
|
||||
# Update the current gap with the new one
|
||||
base_gaps[-1] = new_gap
|
||||
return amount
|
||||
|
||||
taxes_vals = []
|
||||
for tax in taxes:
|
||||
tax_amount = compute_amount(tax)
|
||||
if not round_tax:
|
||||
tax_amount = round(tax_amount, prec)
|
||||
else:
|
||||
|
||||
@@ -188,7 +188,7 @@ class TestTax(AccountTestUsers):
|
||||
[
|
||||
# base , amount | seq | amount | incl | incl_base
|
||||
# ---------------------------------------------------
|
||||
(5.79, 1.21), # | 3 | 21% | t | t
|
||||
(5.79, 1.21), # | 3 | 21% | t | t
|
||||
# ---------------------------------------------------
|
||||
],
|
||||
res_percent
|
||||
@@ -232,6 +232,40 @@ class TestTax(AccountTestUsers):
|
||||
res
|
||||
)
|
||||
|
||||
def test_tax_include_base_amount_2(self):
|
||||
self.percent_tax.price_include = True
|
||||
self.percent_tax.amount = 21.0
|
||||
res = self.percent_tax.compute_all(7.0)
|
||||
self._check_compute_all_results(
|
||||
5.79, # 'base'
|
||||
7.0, # 'total_included'
|
||||
5.79, # 'total_excluded'
|
||||
[
|
||||
# base , amount | seq | amount | incl | incl_base
|
||||
# ---------------------------------------------------
|
||||
(5.79, 1.21), # | 3 | 10% | t |
|
||||
# ---------------------------------------------------
|
||||
],
|
||||
res
|
||||
)
|
||||
|
||||
def test_tax_include_base_amount_3(self):
|
||||
self.percent_tax.price_include = True
|
||||
self.percent_tax.amount = 20.0
|
||||
res = self.percent_tax.compute_all(399.99)
|
||||
self._check_compute_all_results(
|
||||
333.33, # 'base'
|
||||
399.99, # 'total_included'
|
||||
333.33, # 'total_excluded'
|
||||
[
|
||||
# base , amount | seq | amount | incl | incl_base
|
||||
# ---------------------------------------------------
|
||||
(333.33, 66.66), # | 3 | 10% | t |
|
||||
# ---------------------------------------------------
|
||||
],
|
||||
res
|
||||
)
|
||||
|
||||
def test_tax_currency(self):
|
||||
self.division_tax.amount = 15.0
|
||||
res = self.division_tax.compute_all(200.0, currency=self.env.ref('base.VEF'))
|
||||
@@ -338,10 +372,10 @@ class TestTax(AccountTestUsers):
|
||||
# base , amount | seq | amount | incl | incl_base
|
||||
# ---------------------------------------------------
|
||||
(100.0, 10.0), # | 1 | 10% | t | t
|
||||
(110.0, 11.0), # | 3 | 10% | |
|
||||
(110.0, 11.0), # | 3 | 10% | t |
|
||||
(110.0, 11.0), # | 3 | 10% | |
|
||||
(110.0, 11.0), # | 2 | 10% | |
|
||||
(110.0, 11.0), # | 3 | 10% | t |
|
||||
(110.0, 11.0), # | 4 | 10% | |
|
||||
(110.0, 11.0), # | 5 | 10% | t |
|
||||
# ---------------------------------------------------
|
||||
],
|
||||
res
|
||||
|
||||
@@ -14,11 +14,6 @@ class AccountTaxTemplate(models.Model):
|
||||
help="Um percentual decimal em % entre 0-1.", default=0)
|
||||
amount_mva = fields.Float(string='MVA Percent', digits=0, required=True,
|
||||
help="Um percentual decimal em % entre 0-1.", default=0)
|
||||
amount_type = fields.Selection([('group', 'Group of Taxes'),
|
||||
('fixed', 'Fixed'),
|
||||
('percent', 'Percentage of Price'),
|
||||
('division', 'Percentage of Price Tax Included')],
|
||||
string='Tax Computation', required=True, default='percent')
|
||||
|
||||
|
||||
class AccountTax(models.Model):
|
||||
@@ -31,8 +26,3 @@ class AccountTax(models.Model):
|
||||
help="Um percentual decimal em % entre 0-1.", default=0)
|
||||
amount_mva = fields.Float(string='MVA Percent', digits=0, required=True,
|
||||
help="Um percentual decimal em % entre 0-1.", default=0)
|
||||
amount_type = fields.Selection([('group', 'Group of Taxes'),
|
||||
('fixed', 'Fixed'),
|
||||
('percent', 'Percentage of Price'),
|
||||
('division', 'Percentage of Price Tax Included')],
|
||||
string='Tax Computation', required=True, default='percent')
|
||||
|
||||
@@ -326,7 +326,7 @@
|
||||
<page string="Product Information">
|
||||
<group>
|
||||
<group>
|
||||
<field name="partner_id"/>
|
||||
<field name="partner_id" context="{'default_supplier': True, 'default_customer': False}"/>
|
||||
<field name="partner_ref"/>
|
||||
<field name="model"/>
|
||||
<field name="serial_no"/>
|
||||
|
||||
@@ -293,7 +293,7 @@ class StockMove(models.Model):
|
||||
self.env['stock.quant'].quants_move(quants, move, move.location_dest_id, lot_id = movelot.lot_id.id)
|
||||
moves_to_unreserve |= move
|
||||
# Next move in production order
|
||||
if move.move_dest_id:
|
||||
if move.move_dest_id and move.move_dest_id.state not in ('done', 'cancel'):
|
||||
move.move_dest_id.action_assign()
|
||||
moves_to_unreserve.quants_unreserve()
|
||||
moves_todo.write({'state': 'done', 'date': fields.Datetime.now()})
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
<group attrs="{'invisible': [('provider', '!=', 'buckaroo')]}">
|
||||
<field name="brq_websitekey"/>
|
||||
<field name="brq_secretkey"/>
|
||||
<a colspan="2" href="http://www.buckaroo-payments.com/business/customers/online-payment/" target="_blank">How to configure your Buckaroo account?</a>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
@@ -87,7 +87,7 @@ exports.PosModel = Backbone.Model.extend({
|
||||
|
||||
// We fetch the backend data on the server asynchronously. this is done only when the pos user interface is launched,
|
||||
// Any change on this data made on the server is thus not reflected on the point of sale until it is relaunched.
|
||||
// when all the data has loaded, we compute some stuff, and declare the Pos ready to be used.
|
||||
// when all the data has loaded, we compute some stuff, and declare the Pos ready to be used.
|
||||
this.ready = this.load_server_data().then(function(){
|
||||
return self.after_load_server_data();
|
||||
});
|
||||
@@ -1472,15 +1472,20 @@ exports.Orderline = Backbone.Model.extend({
|
||||
currency_rounding = currency_rounding * 0.00001;
|
||||
}
|
||||
|
||||
var recompute_base = function(base_amount, fixed_amount, percent_amount){
|
||||
if(fixed_amount === 0.0 && percent_amount === 0.0)
|
||||
return base_amount;
|
||||
return (base_amount - fixed_amount) / (1.0 + percent_amount / 100.0);
|
||||
}
|
||||
|
||||
// 4) Iterate the taxes in the reversed sequence order to retrieve the initial base of the computation.
|
||||
|
||||
var base = round_pr(price_unit * quantity, currency_rounding);
|
||||
|
||||
var base_gaps = [];
|
||||
|
||||
var recompute_base = function(base_amount, fixed_amount, percent_amount){
|
||||
if(fixed_amount === 0.0 && percent_amount === 0.0)
|
||||
return base_amount;
|
||||
var new_base = (base_amount - fixed_amount) / (1.0 + percent_amount / 100.0);
|
||||
base_gaps.push(base_amount - new_base);
|
||||
return new_base;
|
||||
}
|
||||
|
||||
var sign = 1;
|
||||
if(base < 0){
|
||||
base = -base;
|
||||
@@ -1509,9 +1514,24 @@ exports.Orderline = Backbone.Model.extend({
|
||||
|
||||
// 5) Iterate the taxes in the sequence order to fill missing base/amount values.
|
||||
|
||||
var compute_amount = function(tax){
|
||||
var amount = self._compute_all(tax, base, quantity, false);
|
||||
|
||||
if(!tax.price_include || base_gaps.length == 0)
|
||||
return tax_amount;
|
||||
|
||||
var new_gap = base_gaps[base_gaps.length - 1] - amount;
|
||||
|
||||
if(round_pr(new_gap, currency_rounding) === 0.0)
|
||||
return base_gaps.pop();
|
||||
|
||||
base_gaps[base_gaps.length - 1] = new_gap;
|
||||
return amount;
|
||||
};
|
||||
|
||||
var taxes_vals = [];
|
||||
_(taxes.reverse()).each(function(tax){
|
||||
var tax_amount = self._compute_all(tax, base, quantity, false);
|
||||
var tax_amount = compute_amount(tax);
|
||||
tax_amount = round_pr(tax_amount, currency_rounding);
|
||||
|
||||
var tax_base = base;
|
||||
|
||||
@@ -59,6 +59,7 @@ class TestPointOfSaleCommon(common.TransactionCase):
|
||||
'company_id': self.ref('stock.res_company_1')
|
||||
})
|
||||
|
||||
self.product4.company_id = False
|
||||
# I assign those 5 percent taxes on the PCSC349 product as a sale taxes
|
||||
self.product4.write(
|
||||
{'taxes_id': [(6, 0, [account_tax_05_incl.id, account_tax_05_incl_chicago.id])]})
|
||||
|
||||
@@ -373,7 +373,7 @@ class InventoryLine(models.Model):
|
||||
self._compute_theoretical_qty()
|
||||
self.product_qty = self.theoretical_qty
|
||||
|
||||
@api.model
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
values.pop('product_name', False)
|
||||
res = super(InventoryLine, self).write(values)
|
||||
|
||||
@@ -159,9 +159,8 @@
|
||||
<field name="location_id"/>
|
||||
<field name="packaging_id"/>
|
||||
<group expand='0' string='Group by...'>
|
||||
<filter string='Location' domain="[]" context="{'group_by' : 'location_id'}"/>
|
||||
<!-- FORWARD-PORT UP TO v11 -->
|
||||
<filter string='Packaging' domain="[]" context="{'group_by' : 'packaging_id'}"/>
|
||||
<filter string='Company' domain="[]" context="{'group_by' : 'company_id'}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<a class="js_disable_optimization" href="#">Upload image without optimization</a>
|
||||
<a class="js_disable_optimization">Upload image without optimization</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -201,7 +201,7 @@ class Partner(models.Model):
|
||||
# company_type is only an interface field, do not use it in business logic
|
||||
company_type = fields.Selection(string='Company Type',
|
||||
selection=[('person', 'Individual'), ('company', 'Company')],
|
||||
compute='_compute_company_type', readonly=False)
|
||||
compute='_compute_company_type', inverse='_write_company_type')
|
||||
company_id = fields.Many2one('res.company', 'Company', index=True, default=_default_company)
|
||||
color = fields.Integer(string='Color Index', default=0)
|
||||
user_ids = fields.One2many('res.users', 'partner_id', string='Users', auto_join=True)
|
||||
@@ -373,6 +373,10 @@ class Partner(models.Model):
|
||||
for partner in self:
|
||||
partner.company_type = 'company' if partner.is_company else 'person'
|
||||
|
||||
def _write_company_type(self):
|
||||
for partner in self:
|
||||
partner.is_company = partner.company_type == 'company'
|
||||
|
||||
@api.onchange('company_type')
|
||||
def onchange_company_type(self):
|
||||
self.is_company = (self.company_type == 'company')
|
||||
|
||||
Reference in New Issue
Block a user