diff --git a/addons/pos_sale/models/pos_order.py b/addons/pos_sale/models/pos_order.py index 3a8300834c6..3378b9ffeeb 100644 --- a/addons/pos_sale/models/pos_order.py +++ b/addons/pos_sale/models/pos_order.py @@ -32,13 +32,16 @@ class PosOrder(models.Model): invoice_vals = super(PosOrder, self)._prepare_invoice_vals() invoice_vals['team_id'] = self.crm_team_id.id sale_orders = self.lines.mapped('sale_order_origin_id') - if sale_orders and (sale_orders.partner_invoice_id.id != sale_orders.partner_shipping_id.id): - invoice_vals['partner_shipping_id'] = sale_orders.partner_shipping_id.id - else: - addr = self.partner_id.address_get(['delivery']) - invoice_vals['partner_shipping_id'] = addr['delivery'] - if sale_orders and sale_orders[0].payment_term_id: - invoice_vals['invoice_payment_term_id'] = sale_orders[0].payment_term_id.id, + if sale_orders: + if sale_orders[0].partner_invoice_id.id != sale_orders[0].partner_shipping_id.id: + invoice_vals['partner_shipping_id'] = sale_orders[0].partner_shipping_id.id + else: + addr = self.partner_id.address_get(['delivery']) + invoice_vals['partner_shipping_id'] = addr['delivery'] + if sale_orders[0].payment_term_id: + invoice_vals['invoice_payment_term_id'] = sale_orders[0].payment_term_id.id + if sale_orders[0].partner_invoice_id != sale_orders[0].partner_id: + invoice_vals['partner_id'] = sale_orders[0].partner_invoice_id.id return invoice_vals @api.model diff --git a/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js b/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js index d95413aeedd..c25fe940e73 100644 --- a/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js +++ b/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js @@ -18,6 +18,15 @@ import { SaleOrderList } from "./SaleOrderList"; import { SaleOrderManagementControlPanel } from "./SaleOrderManagementControlPanel"; import { onMounted, useRef, useState } from "@odoo/owl"; +/** + * ID getter to take into account falsy many2one value. + * @param {[id: number, display_name: string] | false} fieldVal many2one field value + * @returns {number | false} + */ +function getId(fieldVal) { + return fieldVal && fieldVal[0]; +} + export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentToOrderScreen) { static components = { SaleOrderList, SaleOrderManagementControlPanel }; static template = "SaleOrderManagementScreen"; @@ -29,6 +38,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo this.root = useRef("root"); this.numberBuffer = useService("number_buffer"); this.saleOrderFetcher = useService("sale_order_fetcher"); + this.notification = useService("pos_notification"); useBus(this.saleOrderFetcher, "update", this.render); this.orderManagementContext = useState(orderManagement); @@ -48,6 +58,14 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo ); this.saleOrderFetcher.setNPerPage(val); this.saleOrderFetcher.fetch(); + } + _getSaleOrderOrigin(order) { + for (const line of order.get_orderlines()) { + if (line.sale_order_origin_id) { + return line.sale_order_origin_id + } + } + return false; } get selectedPartner() { const order = this.orderManagementContext.selectedOrder; @@ -82,9 +100,25 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo }); if (confirmed) { - const currentPOSOrder = this.env.pos.get_order(); + let currentPOSOrder = this.env.pos.get_order(); const sale_order = await this._getSaleOrder(clickedOrder.id); clickedOrder.shipping_date = sale_order.shipping_date; + + const currentSaleOrigin = this._getSaleOrderOrigin(currentPOSOrder); + const currentSaleOriginId = currentSaleOrigin && currentSaleOrigin.id; + + if (currentSaleOriginId) { + const linkedSO = await this._getSaleOrder(currentSaleOriginId); + if ( + getId(linkedSO.partner_id) !== getId(sale_order.partner_id) || + getId(linkedSO.partner_invoice_id) !== getId(sale_order.partner_invoice_id) || + getId(linkedSO.partner_shipping_id) !== getId(sale_order.partner_shipping_id) + ) { + currentPOSOrder = this.env.pos.add_new_order(); + this.notification.add(this.env._t("A new order has been created."), 4000); + } + } + try { await this.env.pos.load_new_partners(); } catch { @@ -318,6 +352,8 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo "amount_untaxed", "amount_unpaid", "picking_ids", + "partner_shipping_id", + "partner_invoice_id" ] ); diff --git a/addons/pos_sale/static/tests/helpers/ProductScreenTourMethods.js b/addons/pos_sale/static/tests/helpers/ProductScreenTourMethods.js index 0f7b2dd9b46..63791a0c3ba 100644 --- a/addons/pos_sale/static/tests/helpers/ProductScreenTourMethods.js +++ b/addons/pos_sale/static/tests/helpers/ProductScreenTourMethods.js @@ -25,6 +25,18 @@ class DoExt extends Do { } ]; } + selectNthOrder(n) { + return [ + { + content: `select order`, + trigger: `.order-list .order-row:nth-child(${n})`, + }, + { + content: `click on select the order`, + trigger: `.selection-item:contains('Settle the order')`, + } + ]; + } } class CheckExt extends Check {} diff --git a/addons/pos_sale/static/tests/tours/PosSaleTour.js b/addons/pos_sale/static/tests/tours/PosSaleTour.js index 74ff427b08a..0b42116be3c 100644 --- a/addons/pos_sale/static/tests/tours/PosSaleTour.js +++ b/addons/pos_sale/static/tests/tours/PosSaleTour.js @@ -25,3 +25,22 @@ Chrome.do.clickMenuButton(); Chrome.do.clickTicketButton(); registry.category("web_tour.tours").add('PosSettleOrder', { test: true, url: '/pos/ui', steps: getSteps() }); + +startSteps(); + +ProductScreen.do.confirmOpeningPopup(); +ProductScreen.do.clickQuotationButton(); +// The second item in the list is the first sale.order. +ProductScreen.do.selectNthOrder(2); +ProductScreen.check.selectedOrderlineHas('product1', 1); +ProductScreen.check.totalAmountIs("10.00"); + +ProductScreen.do.clickQuotationButton(); +// The first item in the list is the second sale.order. +// Selecting the 2nd sale.order should use a new order, +// therefore, the total amount will change. +ProductScreen.do.selectNthOrder(1); +ProductScreen.check.selectedOrderlineHas('product2', 1); +ProductScreen.check.totalAmountIs("11.00"); + +registry.category("web_tour.tours").add('PosSettleOrderIncompatiblePartner', { test: true, url: '/pos/ui', steps: getSteps() }); diff --git a/addons/pos_sale/tests/test_pos_sale_flow.py b/addons/pos_sale/tests/test_pos_sale_flow.py index 9c9a7225510..8c416441663 100644 --- a/addons/pos_sale/tests/test_pos_sale_flow.py +++ b/addons/pos_sale/tests/test_pos_sale_flow.py @@ -70,3 +70,35 @@ class TestPoSSale(TestPointOfSaleHttpCommon): self.assertEqual(sale_order.picking_ids[0].move_ids.quantity_done, 0) self.assertEqual(sale_order.picking_ids[1].move_ids.product_qty, 300) self.assertEqual(sale_order.picking_ids[1].move_ids.quantity_done, 300) # 1 delivered => 300 * 2 = 600 + + def test_settle_order_with_incompatible_partner(self): + """ If the partner of the sale order is not compatible with the current pos order, + then a new pos order should be to settle the newly selected sale order. + """ + + product1 = self.env['product.product'].create({ + 'name': 'product1', + 'available_in_pos': True, + 'type': 'product', + 'lst_price': 10, + 'taxes_id': [odoo.Command.clear()], + }) + product2 = self.env['product.product'].create({ + 'name': 'product2', + 'available_in_pos': True, + 'type': 'product', + 'lst_price': 11, + 'taxes_id': [odoo.Command.clear()], + }) + self.env['sale.order'].create({ + 'partner_id': self.env.ref('base.res_partner_1').id, + 'partner_shipping_id': self.env.ref('base.res_partner_2').id, + 'order_line': [(0, 0, {'product_id': product1.id})], + }) + self.env['sale.order'].create({ + 'partner_id': self.env.ref('base.res_partner_1').id, + 'partner_shipping_id': self.env.ref('base.res_partner_1').id, + 'order_line': [(0, 0, {'product_id': product2.id})], + }) + self.main_pos_config.open_ui() + self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'PosSettleOrderIncompatiblePartner', login="accountman")