diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductItem.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductItem.js index 978926b825e..37ff4c0e930 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductItem.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductItem.js @@ -29,7 +29,7 @@ odoo.define('point_of_sale.ProductItem', function(require) { } get price() { const formattedUnitPrice = this.env.pos.format_currency( - this.props.product.get_price(this.pricelist, 1), + this.props.product.get_display_price(this.pricelist, 1), 'Product Price' ); if (this.props.product.to_weight) { diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 14abf961475..0fff014bc94 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -888,6 +888,186 @@ class PosGlobalState extends PosModel { } } + /** + * Mirror JS method of: + * _compute_amount in addons/account/models/account.py + */ + _compute_all(tax, base_amount, quantity, price_exclude) { + if(price_exclude === undefined) + var price_include = tax.price_include; + else + var price_include = !price_exclude; + if (tax.amount_type === 'fixed') { + // Use sign on base_amount and abs on quantity to take into account the sign of the base amount, + // which includes the sign of the quantity and the sign of the price_unit + // Amount is the fixed price for the tax, it can be negative + // Base amount included the sign of the quantity and the sign of the unit price and when + // a product is returned, it can be done either by changing the sign of quantity or by changing the + // sign of the price unit. + // When the price unit is equal to 0, the sign of the quantity is absorbed in base_amount then + // a "else" case is needed. + if (base_amount) + return Math.sign(base_amount) * Math.abs(quantity) * tax.amount; + else + return quantity * tax.amount; + } + if (tax.amount_type === 'percent' && !price_include){ + return base_amount * tax.amount / 100; + } + if (tax.amount_type === 'percent' && price_include){ + return base_amount - (base_amount / (1 + tax.amount / 100)); + } + if (tax.amount_type === 'division' && !price_include) { + return base_amount / (1 - tax.amount / 100) - base_amount; + } + if (tax.amount_type === 'division' && price_include) { + return base_amount - (base_amount * (tax.amount / 100)); + } + return false; + } + + /** + * Mirror JS method of: + * compute_all in addons/account/models/account.py + * + * Read comments in the python side method for more details about each sub-methods. + */ + compute_all(taxes, price_unit, quantity, currency_rounding, handle_price_include=true) { + var self = this; + + // 1) Flatten the taxes. + + var _collect_taxes = function(taxes, all_taxes){ + taxes = [...taxes].sort(function (tax1, tax2) { + return tax1.sequence - tax2.sequence; + }); + _(taxes).each(function(tax){ + if(tax.amount_type === 'group') + all_taxes = _collect_taxes(tax.children_tax_ids, all_taxes); + else + all_taxes.push(tax); + }); + return all_taxes; + } + var collect_taxes = function(taxes){ + return _collect_taxes(taxes, []); + } + + taxes = collect_taxes(taxes); + + // 2) Deal with the rounding methods + + var round_tax = this.company.tax_calculation_rounding_method != 'round_globally'; + + var initial_currency_rounding = currency_rounding; + if(!round_tax) + currency_rounding = currency_rounding * 0.00001; + + // 3) Iterate the taxes in the reversed sequence order to retrieve the initial base of the computation. + var recompute_base = function(base_amount, fixed_amount, percent_amount, division_amount){ + return (base_amount - fixed_amount) / (1.0 + percent_amount / 100.0) * (100 - division_amount) / 100; + } + + var base = round_pr(price_unit * quantity, initial_currency_rounding); + + var sign = 1; + if(base < 0){ + base = -base; + sign = -1; + } + + var total_included_checkpoints = {}; + var i = taxes.length - 1; + var store_included_tax_total = true; + + var incl_fixed_amount = 0.0; + var incl_percent_amount = 0.0; + var incl_division_amount = 0.0; + + var cached_tax_amounts = {}; + if (handle_price_include){ + _(taxes.reverse()).each(function(tax){ + if(tax.include_base_amount){ + base = recompute_base(base, incl_fixed_amount, incl_percent_amount, incl_division_amount); + incl_fixed_amount = 0.0; + incl_percent_amount = 0.0; + incl_division_amount = 0.0; + store_included_tax_total = true; + } + if(tax.price_include){ + if(tax.amount_type === 'percent') + incl_percent_amount += tax.amount; + else if(tax.amount_type === 'division') + incl_division_amount += tax.amount; + else if(tax.amount_type === 'fixed') + incl_fixed_amount += Math.abs(quantity) * tax.amount + else{ + var tax_amount = self._compute_all(tax, base, quantity); + incl_fixed_amount += tax_amount; + cached_tax_amounts[i] = tax_amount; + } + if(store_included_tax_total){ + total_included_checkpoints[i] = base; + store_included_tax_total = false; + } + } + i -= 1; + }); + } + + var total_excluded = round_pr(recompute_base(base, incl_fixed_amount, incl_percent_amount, incl_division_amount), initial_currency_rounding); + var total_included = total_excluded; + + // 4) Iterate the taxes in the sequence order to fill missing base/amount values. + + base = total_excluded; + + var skip_checkpoint = false; + + var taxes_vals = []; + i = 0; + var cumulated_tax_included_amount = 0; + _(taxes.reverse()).each(function(tax){ + if(tax.price_include || tax.is_base_affected) + var tax_base_amount = base; + else + var tax_base_amount = total_excluded; + + if(!skip_checkpoint && tax.price_include && total_included_checkpoints[i] !== undefined){ + var tax_amount = total_included_checkpoints[i] - (base + cumulated_tax_included_amount); + cumulated_tax_included_amount = 0; + }else + var tax_amount = self._compute_all(tax, tax_base_amount, quantity, true); + + tax_amount = round_pr(tax_amount, currency_rounding); + + if(tax.price_include && total_included_checkpoints[i] === undefined) + cumulated_tax_included_amount += tax_amount; + + taxes_vals.push({ + 'id': tax.id, + 'name': tax.name, + 'amount': sign * tax_amount, + 'base': sign * round_pr(tax_base_amount, currency_rounding), + }); + + if(tax.include_base_amount){ + base += tax_amount; + if(!tax.price_include) + skip_checkpoint = true; + } + + total_included += tax_amount; + i += 1; + }); + + return { + 'taxes': taxes_vals, + 'total_excluded': sign * round_pr(total_excluded, this.currency.rounding), + 'total_included': sign * round_pr(total_included, this.currency.rounding), + } + } + /** * TODO: We can probably remove this here and put it somewhere else. * And that somewhere else becomes the parent of the proxy. @@ -1132,6 +1312,15 @@ class Product extends PosModel { // pricelist that have base == 'pricelist'. return price; } + get_display_price(pricelist, quantity) { + if (this.pos.config.iface_tax_included === 'total') { + const taxes = this.taxes_id.map(id => this.pos.taxes_by_id[id]); + const allPrices = this.pos.compute_all(taxes, this.get_price(pricelist, quantity), 1, this.pos.currency.rounding); + return allPrices.total_included; + } else { + return this.get_price(pricelist, quantity); + } + } } Registries.Model.add(Product); @@ -1684,37 +1873,7 @@ class Orderline extends PosModel { * _compute_amount in addons/account/models/account.py */ _compute_all(tax, base_amount, quantity, price_exclude) { - if(price_exclude === undefined) - var price_include = tax.price_include; - else - var price_include = !price_exclude; - if (tax.amount_type === 'fixed') { - // Use sign on base_amount and abs on quantity to take into account the sign of the base amount, - // which includes the sign of the quantity and the sign of the price_unit - // Amount is the fixed price for the tax, it can be negative - // Base amount included the sign of the quantity and the sign of the unit price and when - // a product is returned, it can be done either by changing the sign of quantity or by changing the - // sign of the price unit. - // When the price unit is equal to 0, the sign of the quantity is absorbed in base_amount then - // a "else" case is needed. - if (base_amount) - return Math.sign(base_amount) * Math.abs(quantity) * tax.amount; - else - return quantity * tax.amount; - } - if (tax.amount_type === 'percent' && !price_include){ - return base_amount * tax.amount / 100; - } - if (tax.amount_type === 'percent' && price_include){ - return base_amount - (base_amount / (1 + tax.amount / 100)); - } - if (tax.amount_type === 'division' && !price_include) { - return base_amount / (1 - tax.amount / 100) - base_amount; - } - if (tax.amount_type === 'division' && price_include) { - return base_amount - (base_amount * (tax.amount / 100)); - } - return false; + return this.pos._compute_all(tax, base_amount, quantity, price_exclude); } /** * Mirror JS method of: @@ -1723,139 +1882,7 @@ class Orderline extends PosModel { * Read comments in the python side method for more details about each sub-methods. */ compute_all(taxes, price_unit, quantity, currency_rounding, handle_price_include=true) { - var self = this; - - // 1) Flatten the taxes. - - var _collect_taxes = function(taxes, all_taxes){ - taxes = [...taxes].sort(function (tax1, tax2) { - return tax1.sequence - tax2.sequence; - }); - _(taxes).each(function(tax){ - if(tax.amount_type === 'group') - all_taxes = _collect_taxes(tax.children_tax_ids, all_taxes); - else - all_taxes.push(tax); - }); - return all_taxes; - } - var collect_taxes = function(taxes){ - return _collect_taxes(taxes, []); - } - - taxes = collect_taxes(taxes); - - // 2) Deal with the rounding methods - - var round_tax = this.pos.company.tax_calculation_rounding_method != 'round_globally'; - - var initial_currency_rounding = currency_rounding; - if(!round_tax) - currency_rounding = currency_rounding * 0.00001; - - // 3) Iterate the taxes in the reversed sequence order to retrieve the initial base of the computation. - var recompute_base = function(base_amount, fixed_amount, percent_amount, division_amount){ - return (base_amount - fixed_amount) / (1.0 + percent_amount / 100.0) * (100 - division_amount) / 100; - } - - var base = round_pr(price_unit * quantity, initial_currency_rounding); - - var sign = 1; - if(base < 0){ - base = -base; - sign = -1; - } - - var total_included_checkpoints = {}; - var i = taxes.length - 1; - var store_included_tax_total = true; - - var incl_fixed_amount = 0.0; - var incl_percent_amount = 0.0; - var incl_division_amount = 0.0; - - var cached_tax_amounts = {}; - if (handle_price_include){ - _(taxes.reverse()).each(function(tax){ - if(tax.include_base_amount){ - base = recompute_base(base, incl_fixed_amount, incl_percent_amount, incl_division_amount); - incl_fixed_amount = 0.0; - incl_percent_amount = 0.0; - incl_division_amount = 0.0; - store_included_tax_total = true; - } - if(tax.price_include){ - if(tax.amount_type === 'percent') - incl_percent_amount += tax.amount; - else if(tax.amount_type === 'division') - incl_division_amount += tax.amount; - else if(tax.amount_type === 'fixed') - incl_fixed_amount += Math.abs(quantity) * tax.amount - else{ - var tax_amount = self._compute_all(tax, base, quantity); - incl_fixed_amount += tax_amount; - cached_tax_amounts[i] = tax_amount; - } - if(store_included_tax_total){ - total_included_checkpoints[i] = base; - store_included_tax_total = false; - } - } - i -= 1; - }); - } - - var total_excluded = round_pr(recompute_base(base, incl_fixed_amount, incl_percent_amount, incl_division_amount), initial_currency_rounding); - var total_included = total_excluded; - - // 4) Iterate the taxes in the sequence order to fill missing base/amount values. - - base = total_excluded; - - var skip_checkpoint = false; - - var taxes_vals = []; - i = 0; - var cumulated_tax_included_amount = 0; - _(taxes.reverse()).each(function(tax){ - if(tax.price_include || tax.is_base_affected) - var tax_base_amount = base; - else - var tax_base_amount = total_excluded; - - if(!skip_checkpoint && tax.price_include && total_included_checkpoints[i] !== undefined){ - var tax_amount = total_included_checkpoints[i] - (base + cumulated_tax_included_amount); - cumulated_tax_included_amount = 0; - }else - var tax_amount = self._compute_all(tax, tax_base_amount, quantity, true); - - tax_amount = round_pr(tax_amount, currency_rounding); - - if(tax.price_include && total_included_checkpoints[i] === undefined) - cumulated_tax_included_amount += tax_amount; - - taxes_vals.push({ - 'id': tax.id, - 'name': tax.name, - 'amount': sign * tax_amount, - 'base': sign * round_pr(tax_base_amount, currency_rounding), - }); - - if(tax.include_base_amount){ - base += tax_amount; - if(!tax.price_include) - skip_checkpoint = true; - } - - total_included += tax_amount; - i += 1; - }); - - return { - 'taxes': taxes_vals, - 'total_excluded': sign * round_pr(total_excluded, this.pos.currency.rounding), - 'total_included': sign * round_pr(total_included, this.pos.currency.rounding), - } + return this.pos.compute_all(taxes, price_unit, quantity, currency_rounding, handle_price_include); } get_all_prices(qty = this.get_quantity()){ var price_unit = this.get_unit_price() * (1.0 - (this.get_discount() / 100.0));