Skip to content

Commit 90292e6

Browse files
committed
[FIX] point_of_sale, pos_sale: Ensure fp matches with sale order
When a sales order is imported into the PoS, the tax position does not match if the sales order has a different tax position than the one assigned to the partner. This commit ensures that the tax position will always be the one assigned to the sales order and not that of the partner. closes odoo#220709 Taskid: 4963118 X-original-commit: 1309058 Signed-off-by: Stéphane Vanmeerhaeghe (stva) <stva@odoo.com> Signed-off-by: David Monnom (moda) <moda@odoo.com>
1 parent 1f87e3d commit 90292e6

6 files changed

Lines changed: 147 additions & 12 deletions

File tree

addons/point_of_sale/static/tests/pos/tours/utils/product_screen_util.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,18 @@ export function clickFiscalPosition(name, checkIsNeeded = false) {
375375

376376
return [...step, { ...back(), isActive: ["mobile"] }];
377377
}
378+
export function checkFiscalPosition(name) {
379+
return [
380+
clickReview(),
381+
...clickControlButtonMore(),
382+
{
383+
content: `check fiscal position '${name}' is selected`,
384+
trigger: `.o_fiscal_position_button:contains("${name}")`,
385+
run: () => {},
386+
},
387+
Dialog.cancel(),
388+
];
389+
}
378390
export function closeWithCashAmount(val) {
379391
return [
380392
{

addons/pos_sale/models/sale_order.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,14 @@ def load_sale_order_from_pos(self, config_id):
3737
sale_order_read = self.read(sale_order_fields, load=False)
3838
sale_order_line_fields = self.order_line._load_pos_data_fields(config_id)
3939
sale_order_line_read = self.order_line.read(sale_order_line_fields, load=False)
40+
sale_order_fp_fields = self.env['account.fiscal.position']._load_pos_data_fields(config_id)
41+
sale_order_fp_read = self.fiscal_position_id.read(sale_order_fp_fields, load=False)
4042
partner_fields = self.env['res.partner']._load_pos_data_fields(config_id)
4143

4244
return {
4345
'sale.order': sale_order_read,
4446
'sale.order.line': sale_order_line_read,
47+
'account.fiscal.position': sale_order_fp_read,
4548
'res.partner': self.partner_id.read(partner_fields, load=False),
4649
**product_tmpls,
4750
}

addons/pos_sale/static/src/app/services/pos_store.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { enhancedButtons } from "@point_of_sale/app/components/numpad/numpad";
99
import { patch } from "@web/core/utils/patch";
1010
import { PosStore } from "@point_of_sale/app/services/pos_store";
1111
import { accountTaxHelpers } from "@account/helpers/account_tax";
12+
import { getTaxesAfterFiscalPosition } from "@point_of_sale/app/models/utils/tax_utils";
1213

1314
patch(PosStore.prototype, {
1415
async onClickSaleOrder(clickedOrderId) {
@@ -49,17 +50,18 @@ patch(PosStore.prototype, {
4950
this.notification.add(_t("A new order has been created."));
5051
}
5152
}
52-
const orderFiscalPos =
53-
sale_order.fiscal_position_id &&
54-
this.models["account.fiscal.position"].find(
55-
(position) => position.id === sale_order.fiscal_position_id
56-
);
57-
if (orderFiscalPos) {
58-
this.getOrder().fiscal_position_id = orderFiscalPos;
59-
}
6053
if (sale_order.partner_id) {
6154
this.getOrder().setPartner(sale_order.partner_id);
6255
}
56+
57+
// Fiscal position should be set after the partner is set
58+
// to ensure that the fiscal position is correctly computed
59+
// based on sale order.
60+
const orderFiscalPos = sale_order.fiscal_position_id;
61+
this.getOrder().update({
62+
fiscal_position_id: orderFiscalPos,
63+
});
64+
6365
selectedOption == "settle"
6466
? await this.settleSO(sale_order, orderFiscalPos)
6567
: await this.downPaymentSO(sale_order, selectedOption == "dpPercentage");
@@ -99,16 +101,14 @@ patch(PosStore.prototype, {
99101
line.product_id = this.config.down_payment_product_id;
100102
}
101103

104+
const taxes = getTaxesAfterFiscalPosition(line.tax_ids, orderFiscalPos, this.models);
102105
const newLineValues = {
103106
product_tmpl_id: line.product_id?.product_tmpl_id,
104107
product_id: line.product_id,
105108
qty: line.product_uom_qty,
106109
price_unit: line.price_unit,
107110
price_type: "automatic",
108-
tax_ids:
109-
orderFiscalPos || !line.tax_ids
110-
? undefined
111-
: line.tax_ids.map((t) => ["link", t]),
111+
tax_ids: taxes.map((tax) => ["link", tax]),
112112
sale_order_origin_id: sale_order,
113113
sale_order_line_id: line,
114114
customer_note: line.customer_note,

addons/pos_sale/static/tests/tours/pos_sale_tour.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,3 +459,27 @@ registry.category("web_tour.tours").add("test_down_payment_displayed", {
459459
}),
460460
].flat(),
461461
});
462+
463+
registry.category("web_tour.tours").add("test_sale_order_fp_different_from_partner_one", {
464+
steps: () =>
465+
[
466+
Chrome.startPoS(),
467+
Dialog.confirm("Open Register"),
468+
PosSale.settleSaleOrderByPrice("20.00"),
469+
ProductScreen.checkTaxAmount("10.00"),
470+
ProductScreen.checkFiscalPosition("Partner FP"),
471+
ProductScreen.clickPayButton(),
472+
PaymentScreen.clickPaymentMethod("Bank"),
473+
PaymentScreen.clickValidate(),
474+
ReceiptScreen.receiptIsThere(),
475+
ReceiptScreen.clickNextOrder(),
476+
PosSale.settleSaleOrderByPrice("10.00"),
477+
ProductScreen.checkTaxAmount("0.00"),
478+
ProductScreen.checkFiscalPosition("Sale Order FP"),
479+
ProductScreen.clickPayButton(),
480+
PaymentScreen.clickPaymentMethod("Bank"),
481+
PaymentScreen.clickValidate(),
482+
ReceiptScreen.receiptIsThere(),
483+
ReceiptScreen.clickNextOrder(),
484+
].flat(),
485+
});

addons/pos_sale/static/tests/tours/utils/pos_sale_utils.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,22 @@ export function selectNthOrder(n) {
1313
];
1414
}
1515

16+
export function settleSaleOrderByPrice(price) {
17+
return [
18+
...ProductScreen.clickControlButton("Quotation/Order"),
19+
{
20+
content: `select sale order with price ${price}`,
21+
trigger: `.modal:not(.o_inactive_modal) table.o_list_table tbody tr.o_data_row td:contains('${price}')`,
22+
run: "click",
23+
},
24+
{
25+
content: `Choose to settle the order`,
26+
trigger: `.modal:not(.o_inactive_modal) .selection-item:contains('Settle the order')`,
27+
run: "click",
28+
},
29+
];
30+
}
31+
1632
export function settleNthOrder(n, options = {}) {
1733
const { loadSN } = options;
1834
const step = [

addons/pos_sale/tests/test_pos_sale_flow.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,3 +1334,83 @@ def test_payment_terms_with_early_discount(self):
13341334
pos_order_id = self.env['pos.order'].sync_from_ui([pos_order])['pos.order'][0]['id']
13351335
pos_order = self.env['pos.order'].browse(pos_order_id)
13361336
self.assertFalse(pos_order.account_move.invoice_payment_term_id)
1337+
1338+
def test_sale_order_fp_different_from_partner_one(self):
1339+
"""
1340+
Tests that the fiscal position of the sale order is not the same as the partner's fiscal position.
1341+
The PoS should always use the fiscal position of the sale order when settling it.
1342+
"""
1343+
self.env.user.group_ids += self.quick_ref('sales_team.group_sale_salesman')
1344+
tax = self.env['account.tax'].create({
1345+
'name': 'Base Tax',
1346+
'amount': 15,
1347+
})
1348+
fp_1 = self.env['account.fiscal.position'].create({
1349+
'name': "Partner FP",
1350+
})
1351+
fp_2 = self.env['account.fiscal.position'].create({
1352+
'name': "Sale Order FP",
1353+
})
1354+
tax_override_1 = self.env['account.tax'].create({
1355+
'name': 'Tax Override 1',
1356+
'amount': 100,
1357+
'amount_type': 'percent',
1358+
'fiscal_position_ids': [fp_1.id],
1359+
'original_tax_ids': [tax.id],
1360+
})
1361+
tax_override_2 = self.env['account.tax'].create({
1362+
'name': 'Tax Override 2',
1363+
'amount': 0,
1364+
'amount_type': 'percent',
1365+
'fiscal_position_ids': [fp_2.id],
1366+
'original_tax_ids': [tax.id],
1367+
})
1368+
product_a = self.env['product.product'].create({
1369+
'name': 'Product A',
1370+
'available_in_pos': True,
1371+
'lst_price': 10.0,
1372+
'taxes_id': [tax.id],
1373+
})
1374+
partner_test = self.env['res.partner'].create({
1375+
'name': 'Test Partner',
1376+
'property_account_position_id': fp_1.id,
1377+
})
1378+
sale_a = self.env['sale.order'].create({
1379+
'partner_id': partner_test.id,
1380+
'order_line': [(0, 0, {
1381+
'product_id': product_a.id,
1382+
'product_uom_qty': 1,
1383+
'price_unit': product_a.lst_price,
1384+
})]
1385+
})
1386+
sale_b = self.env['sale.order'].create({
1387+
'partner_id': partner_test.id,
1388+
'fiscal_position_id': fp_2.id,
1389+
'order_line': [(0, 0, {
1390+
'product_id': product_a.id,
1391+
'product_uom_qty': 1,
1392+
'price_unit': product_a.lst_price,
1393+
})]
1394+
})
1395+
# Disable fiscal position in POS, it should works anyway.
1396+
self.main_pos_config.write({
1397+
'tax_regime_selection': False,
1398+
'default_fiscal_position_id': False,
1399+
'fiscal_position_ids': [Command.clear()],
1400+
})
1401+
self.assertEqual(sale_a.fiscal_position_id, fp_1, "Sale order should have the fiscal position of the partner")
1402+
self.assertEqual(sale_a.amount_total, 20, "Sale order amount should be 20 with the tax override 1")
1403+
self.assertEqual(sale_a.amount_untaxed, 10, "Sale order untaxed amount should be 10 with the tax override 1")
1404+
self.assertEqual(sale_b.fiscal_position_id, fp_2, "Sale order should have the fiscal position set on the sale order")
1405+
self.assertEqual(sale_b.amount_total, 10, "Sale order amount should be 10 with the tax override 2")
1406+
self.assertEqual(sale_b.amount_untaxed, 10, "Sale order untaxed amount should be 10 with the tax override 2")
1407+
self.start_pos_tour("test_sale_order_fp_different_from_partner_one", login="accountman")
1408+
1409+
pos_order_a = self.env['pos.order'].search([('fiscal_position_id', '=', fp_1.id)], limit=1, order='id desc')
1410+
pos_order_b = self.env['pos.order'].search([('fiscal_position_id', '=', fp_2.id)], limit=1, order='id desc')
1411+
self.assertEqual(pos_order_a.amount_total, 20, "PoS order amount should be 20 with the tax override 1")
1412+
self.assertEqual(pos_order_a.amount_tax, 10, "PoS order untaxed amount should be 10 with the tax override 1")
1413+
self.assertEqual(pos_order_a.lines[0].tax_ids, tax_override_1, "PoS order should have the tax override 1")
1414+
self.assertEqual(pos_order_b.amount_total, 10, "PoS order amount should be 10 with the tax override 2")
1415+
self.assertEqual(pos_order_b.amount_tax, 0, "PoS order untaxed amount should be 10 with the tax override 2")
1416+
self.assertEqual(pos_order_b.lines[0].tax_ids, tax_override_2, "PoS order should have the tax override 2")

0 commit comments

Comments
 (0)