diff --git a/addons/point_of_sale/static/src/app/store/models.js b/addons/point_of_sale/static/src/app/store/models.js index 7e542d7b73c..9f717b4f814 100644 --- a/addons/point_of_sale/static/src/app/store/models.js +++ b/addons/point_of_sale/static/src/app/store/models.js @@ -309,21 +309,26 @@ export class Product extends PosModel { return price; } get_display_price(pricelist, quantity) { - if (this.pos.config.iface_tax_included === "total") { - const order = this.pos.get_order(); - const taxes = this.pos.get_taxes_after_fp( - this.taxes_id, - order && order.fiscal_position - ); - const allPrices = this.pos.compute_all( - taxes, - this.get_price(pricelist, quantity), - 1, - this.pos.currency.rounding - ); + const order = this.pos.get_order(); + const taxes = this.pos.get_taxes_after_fp( + this.taxes_id, + order && order.fiscal_position + ); + const currentTaxes = this.pos.getTaxesByIds(this.taxes_id); + const priceAfterFp = this.pos.computePriceAfterFp( + this.get_price(pricelist, quantity), + currentTaxes + ); + const allPrices = this.pos.compute_all( + taxes, + priceAfterFp, + 1, + this.pos.currency.rounding + ); + if (this.pos.config.iface_tax_included === 'total') { return allPrices.total_included; } else { - return this.get_price(pricelist, quantity); + return allPrices.total_excluded; } } } @@ -835,7 +840,14 @@ export class Orderline extends PosModel { if (this.pos.config.iface_tax_included === "total") { return this.get_all_prices(1).priceWithTax; } else { - return this.get_unit_price(); + return this.get_all_prices(1).priceWithoutTax; + } + } + getUnitDisplayPriceBeforeDiscount(){ + if (this.pos.config.iface_tax_included === 'total') { + return this.get_all_prices(1).priceWithTaxBeforeDiscount; + } else { + return this.get_all_prices(1).priceWithoutTaxBeforeDiscount; } } get_base_price() { @@ -868,20 +880,20 @@ export class Orderline extends PosModel { if (this.pos.config.iface_tax_included === "total") { return this.get_price_with_tax(); } else { - return this.get_base_price(); + return this.get_price_without_tax(); } } - get_taxed_lst_unit_price() { - var lst_price = this.compute_fixed_price(this.get_lst_price()); - if (this.pos.config.iface_tax_included === "total") { - var product = this.get_product(); - var taxes_ids = product.taxes_id; - var product_taxes = this.pos.get_taxes_after_fp(taxes_ids); - return this.compute_all(product_taxes, lst_price, 1, this.pos.currency.rounding) - .total_included; + get_taxed_lst_unit_price(){ + const lstPrice = this.compute_fixed_price(this.get_lst_price()); + const product = this.get_product(); + const taxesIds = product.taxes_id; + const productTaxes = this.pos.get_taxes_after_fp(taxesIds, this.order.fiscal_position); + const unitPrices = this.compute_all(productTaxes, lstPrice, 1, this.pos.currency.rounding); + if (this.pos.config.iface_tax_included === 'total') { + return unitPrices.total_included; + } else { + return unitPrices.total_excluded; } - var digits = this.pos.dp["Product Price"]; - return lst_price.toFixed(digits); } get_price_without_tax() { return this.get_all_prices().priceWithoutTax; @@ -917,13 +929,7 @@ export class Orderline extends PosModel { } get_taxes() { var taxes_ids = this.tax_ids || this.get_product().taxes_id; - var taxes = []; - for (var i = 0; i < taxes_ids.length; i++) { - if (this.pos.taxes_by_id[taxes_ids[i]]) { - taxes.push(this.pos.taxes_by_id[taxes_ids[i]]); - } - } - return taxes; + return this.pos.getTaxesByIds(taxes_ids); } /** * Calculate the amount of taxes of a specific Orderline, that are included in the price. @@ -1006,6 +1012,7 @@ export class Orderline extends PosModel { priceWithTax: all_taxes.total_included, priceWithoutTax: all_taxes.total_excluded, priceWithTaxBeforeDiscount: all_taxes_before_discount.total_included, + priceWithoutTaxBeforeDiscount: all_taxes_before_discount.total_excluded, tax: taxtotal, taxDetails: taxdetail, }; @@ -1013,51 +1020,8 @@ export class Orderline extends PosModel { display_discount_policy() { return this.order.pricelist ? this.order.pricelist.discount_policy : "with_discount"; } - compute_fixed_price(price) { - var order = this.order; - if (order.fiscal_position) { - var taxes = this.get_taxes(); - var mapped_included_taxes = []; - var new_included_taxes = []; - var self = this; - taxes.forEach((tax) => { - var line_taxes = self.pos.get_taxes_after_fp([tax.id], order.fiscal_position); - if (line_taxes.length && line_taxes[0].price_include) { - new_included_taxes = new_included_taxes.concat(line_taxes); - } - if (tax.price_include && !line_taxes.includes(tax)) { - mapped_included_taxes.push(tax); - } - }); - - if (mapped_included_taxes.length > 0) { - if (new_included_taxes.length > 0) { - var price_without_taxes = this.compute_all( - mapped_included_taxes, - price, - 1, - order.pos.currency.rounding, - true - ).total_excluded; - return this.compute_all( - new_included_taxes, - price_without_taxes, - 1, - order.pos.currency.rounding, - false - ).total_included; - } else { - return this.compute_all( - mapped_included_taxes, - price, - 1, - order.pos.currency.rounding, - true - ).total_excluded; - } - } - } - return price; + compute_fixed_price (price) { + return this.pos.computePriceAfterFp(price, this.get_taxes()); } get_fixed_lst_price() { return this.compute_fixed_price(this.get_lst_price()); @@ -2221,17 +2185,12 @@ export class Order extends PosModel { _get_ignored_product_ids_total_discount() { return []; } - _reduce_total_discount_callback(sum, orderLine) { - sum += - orderLine.get_unit_price() * - (orderLine.get_discount() / 100) * - orderLine.get_quantity(); - if (orderLine.display_discount_policy() === "without_discount") { - sum += - (orderLine.get_taxed_lst_unit_price() - orderLine.get_unit_price()) * - orderLine.get_quantity(); + _reduce_total_discount_callback(sum, orderLine){ + let discountUnitPrice = orderLine.getUnitDisplayPriceBeforeDiscount() * (orderLine.get_discount()/100); + if (orderLine.display_discount_policy() === 'without_discount'){ + discountUnitPrice += orderLine.get_taxed_lst_unit_price() - orderLine.getUnitDisplayPriceBeforeDiscount(); } - return sum; + return sum + discountUnitPrice * orderLine.get_quantity(); } get_total_discount() { const ignored_product_ids = this._get_ignored_product_ids_total_discount(); @@ -2239,12 +2198,12 @@ export class Order extends PosModel { this.orderlines.reduce((sum, orderLine) => { if (!ignored_product_ids.includes(orderLine.product.id)) { sum += - orderLine.get_unit_price() * + orderLine.getUnitDisplayPriceBeforeDiscount() * (orderLine.get_discount() / 100) * orderLine.get_quantity(); if (orderLine.display_discount_policy() === "without_discount") { sum += - (orderLine.get_lst_price() - orderLine.get_unit_price()) * + (orderLine.get_taxed_lst_unit_price() - orderLine.getUnitDisplayPriceBeforeDiscount()) * orderLine.get_quantity(); } } diff --git a/addons/point_of_sale/static/src/app/store/pos_store.js b/addons/point_of_sale/static/src/app/store/pos_store.js index 5d71d1490e4..4bc1940ed41 100644 --- a/addons/point_of_sale/static/src/app/store/pos_store.js +++ b/addons/point_of_sale/static/src/app/store/pos_store.js @@ -957,6 +957,62 @@ export class PosStore extends Reactive { return this.orders; } + computePriceAfterFp(price, taxes){ + const order = this.get_order(); + if(order && order.fiscal_position) { + let mapped_included_taxes = []; + let new_included_taxes = []; + taxes.forEach(tax => { + const line_taxes = this.get_taxes_after_fp([tax.id], order.fiscal_position); + if (line_taxes.length && line_taxes[0].price_include){ + new_included_taxes = new_included_taxes.concat(line_taxes); + } + if(tax.price_include && !line_taxes.includes(tax)){ + mapped_included_taxes.push(tax); + } + }); + + if (mapped_included_taxes.length > 0) { + if (new_included_taxes.length > 0) { + const price_without_taxes = this.compute_all( + mapped_included_taxes, + price, + 1, + this.currency.rounding, + true + ).total_excluded + return this.compute_all( + new_included_taxes, + price_without_taxes, + 1, + this.currency.rounding, + false + ).total_included + } + else{ + return this.compute_all( + mapped_included_taxes, + price, + 1, + this.currency.rounding, + true + ).total_excluded; + } + } + } + return price; + } + + getTaxesByIds(taxIds) { + let taxes = []; + for (let i = 0; i < taxIds.length; i++) { + if (this.taxes_by_id[taxIds[i]]) { + taxes.push(this.taxes_by_id[taxIds[i]]); + } + } + return taxes; + } + /** * Renders the HTML for the customer display and returns it as a string. * diff --git a/addons/point_of_sale/static/tests/tours/ProductScreen.tour.js b/addons/point_of_sale/static/tests/tours/ProductScreen.tour.js index 98e63ae639e..734fc39d847 100644 --- a/addons/point_of_sale/static/tests/tours/ProductScreen.tour.js +++ b/addons/point_of_sale/static/tests/tours/ProductScreen.tour.js @@ -175,3 +175,20 @@ registry.category("web_tour.tours").add("CashClosingDetails", { return getSteps() } }); + +registry.category("web_tour.tours").add("ShowTaxExcludedTour", { + test: true, + url: "/pos/ui", + steps: () => { + startSteps(); + + ProductScreen.do.confirmOpeningPopup(); + + ProductScreen.do.clickHomeCategory(); + + ProductScreen.do.clickDisplayedProduct('Test Product'); + ProductScreen.check.selectedOrderlineHas('Test Product', '1.0', '100.0'); + ProductScreen.check.totalAmountIs('110.0'); + return getSteps() + } +}); diff --git a/addons/point_of_sale/tests/test_frontend.py b/addons/point_of_sale/tests/test_frontend.py index 1971cf189ae..ceca57ce456 100644 --- a/addons/point_of_sale/tests/test_frontend.py +++ b/addons/point_of_sale/tests/test_frontend.py @@ -856,6 +856,31 @@ class TestUi(TestPointOfSaleHttpCommon): self.main_pos_config.with_user(self.pos_user).open_ui() self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'ReceiptScreenDiscountWithPricelistTour', login="pos_user") + def test_08_show_tax_excluded(self): + # define a tax included tax record + tax = self.env['account.tax'].create({ + 'name': 'Tax 10% Included', + 'amount_type': 'percent', + 'amount': 10, + 'price_include': True, + }) + + # define a product record with the tax + self.env['product.product'].create({ + 'name': 'Test Product', + 'list_price': 110, + 'taxes_id': [(6, 0, [tax.id])], + 'available_in_pos': True, + }) + + # set Tax-Excluded Price + self.main_pos_config.write({ + 'iface_tax_included': 'subtotal' + }) + + self.main_pos_config.with_user(self.pos_user).open_ui() + self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'ShowTaxExcludedTour', login="pos_user") + # This class just runs the same tests as above but with mobile emulation class MobileTestUi(TestUi): browser_size = '375x667'