From 288e2e072729cd435a6b422c8dde80aa1b3338c5 Mon Sep 17 00:00:00 2001 From: danal-odoo Date: Tue, 2 Dec 2025 18:15:56 +0530 Subject: [PATCH] [ADD] pos_order_salesperson: add salesperson selection in POS Before, POS orders were linked to the user logged into the session or the active cashier. There was no functionality to assign a specific salesperson to the transaction. This limitation made it difficult to calculate commissions or audit sales performance by employee, particularly in scenarios where the person operating the register is different from the person who advised the customer. This adds the ability to select a specific salesperson directly from the Point of Sale interface. This selection is stored on the order and is visible in the backend views. Technical details: - Model (`pos.order`): Added `salesperson_id` field to store the selected employee on the order record. - Session (`pos_session`): Inherited to load `hr.employee` data into the frontend context upon initialization. - UI Components: - Added `SalespersonList` and `SalespersonLine` to display available employees in a selection modal. - Added a 'Salesperson' button to the `ControlButtons` area of the `ProductScreen`. - Views: Added `salesperson_id` to the `pos.order` form and list views to ensure the data is visible in the backend. task-4680845 --- pos_order_salesperson/__init__.py | 1 + pos_order_salesperson/__manifest__.py | 19 +++++++ pos_order_salesperson/models/__init__.py | 4 ++ pos_order_salesperson/models/pos_order.py | 7 +++ pos_order_salesperson/models/pos_session.py | 11 ++++ .../select_salesperson_button.js | 32 +++++++++++ .../select_salesperson_button.xml | 13 +++++ .../src/salesperson_line/salesperson_line.js | 23 ++++++++ .../src/salesperson_line/salesperson_line.xml | 53 +++++++++++++++++++ .../src/salesperson_list/salesperson_list.js | 45 ++++++++++++++++ .../src/salesperson_list/salesperson_list.xml | 38 +++++++++++++ .../views/pos_order_view.xml | 24 +++++++++ 12 files changed, 270 insertions(+) create mode 100644 pos_order_salesperson/__init__.py create mode 100644 pos_order_salesperson/__manifest__.py create mode 100644 pos_order_salesperson/models/__init__.py create mode 100644 pos_order_salesperson/models/pos_order.py create mode 100644 pos_order_salesperson/models/pos_session.py create mode 100644 pos_order_salesperson/static/src/salesperson_button/select_salesperson_button.js create mode 100644 pos_order_salesperson/static/src/salesperson_button/select_salesperson_button.xml create mode 100644 pos_order_salesperson/static/src/salesperson_line/salesperson_line.js create mode 100644 pos_order_salesperson/static/src/salesperson_line/salesperson_line.xml create mode 100644 pos_order_salesperson/static/src/salesperson_list/salesperson_list.js create mode 100644 pos_order_salesperson/static/src/salesperson_list/salesperson_list.xml create mode 100644 pos_order_salesperson/views/pos_order_view.xml diff --git a/pos_order_salesperson/__init__.py b/pos_order_salesperson/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/pos_order_salesperson/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_order_salesperson/__manifest__.py b/pos_order_salesperson/__manifest__.py new file mode 100644 index 00000000000..2cbb49a3984 --- /dev/null +++ b/pos_order_salesperson/__manifest__.py @@ -0,0 +1,19 @@ +{ + "name": "Point of Sale Salesperson", + "description": """ + Point of Sale Salesperson Module to add Salesperson in pos order, form and billing in session. + """, + "version": "1.0", + "depends": ["pos_hr"], + "author": "danal", + "category": "Category", + "license": "LGPL-3", + "data": [ + "views/pos_order_view.xml", + ], + "assets": { + "point_of_sale._assets_pos": [ + "pos_order_salesperson/static/src/**/*" + ] + }, +} diff --git a/pos_order_salesperson/models/__init__.py b/pos_order_salesperson/models/__init__.py new file mode 100644 index 00000000000..b111786fe6e --- /dev/null +++ b/pos_order_salesperson/models/__init__.py @@ -0,0 +1,4 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import pos_order +from . import pos_session diff --git a/pos_order_salesperson/models/pos_order.py b/pos_order_salesperson/models/pos_order.py new file mode 100644 index 00000000000..25df2df54c7 --- /dev/null +++ b/pos_order_salesperson/models/pos_order.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class PosOrder(models.Model): + _inherit = 'pos.order' + + salesperson_id = fields.Many2one("hr.employee", string="Salesperson") diff --git a/pos_order_salesperson/models/pos_session.py b/pos_order_salesperson/models/pos_session.py new file mode 100644 index 00000000000..403a690783d --- /dev/null +++ b/pos_order_salesperson/models/pos_session.py @@ -0,0 +1,11 @@ +from odoo import api, models + + +class PosSession(models.Model): + _inherit = 'pos.session' + + @api.model + def _load_pos_data_models(self, config_id): + data = super()._load_pos_data_models(config_id) + data += ["hr.employee"] + return data diff --git a/pos_order_salesperson/static/src/salesperson_button/select_salesperson_button.js b/pos_order_salesperson/static/src/salesperson_button/select_salesperson_button.js new file mode 100644 index 00000000000..13b70a47e3a --- /dev/null +++ b/pos_order_salesperson/static/src/salesperson_button/select_salesperson_button.js @@ -0,0 +1,32 @@ +import { SalespersonList } from '../salesperson_list/salesperson_list'; +import { ControlButtons } from '@point_of_sale/app/screens/product_screen/control_buttons/control_buttons'; +import { makeAwaitable } from "@point_of_sale/app/utils/make_awaitable_dialog"; +import { patch } from '@web/core/utils/patch'; +import { useState } from '@odoo/owl'; + +patch(ControlButtons.prototype, { + setup() { + super.setup(); + const order = this.pos.getOrder(); + this.state = useState({ + salesperson_id: order ? order.salesperson_id : null, + }); + + }, + + async selectSalesperson() { + const currentOrder = this.pos.getOrder(); + if (!currentOrder) { + return; + } + + const currentSalesperson = currentOrder.salesperson_id || null; + const payload = await makeAwaitable(this.dialog, SalespersonList, { + salesperson: currentSalesperson, + getPayload: (newSalesperson) => newSalesperson || null, + }); + this.state.salesperson_id = payload || null; + currentOrder.salesperson_id = payload || null; + return currentOrder.salesperson_id; + } +}) diff --git a/pos_order_salesperson/static/src/salesperson_button/select_salesperson_button.xml b/pos_order_salesperson/static/src/salesperson_button/select_salesperson_button.xml new file mode 100644 index 00000000000..1f433858e80 --- /dev/null +++ b/pos_order_salesperson/static/src/salesperson_button/select_salesperson_button.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/pos_order_salesperson/static/src/salesperson_line/salesperson_line.js b/pos_order_salesperson/static/src/salesperson_line/salesperson_line.js new file mode 100644 index 00000000000..3d0c78f60eb --- /dev/null +++ b/pos_order_salesperson/static/src/salesperson_line/salesperson_line.js @@ -0,0 +1,23 @@ +import { Component } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; +import { Dropdown } from "@web/core/dropdown/dropdown"; +import { DropdownItem } from "@web/core/dropdown/dropdown_item"; +import { usePos } from "@point_of_sale/app/hooks/pos_hook"; + +export class SalespersonLine extends Component { + static template = "point_of_sale.SalespersonLine"; + static components = { Dropdown, DropdownItem }; + static props = [ + "close", + "salesperson", + "isSelected", + "onClickEdit", + "onClickUnselect", + "clickSalesPerson" + ]; + + setup() { + this.pos = usePos(); + this.ui = useService("ui"); + } +} diff --git a/pos_order_salesperson/static/src/salesperson_line/salesperson_line.xml b/pos_order_salesperson/static/src/salesperson_line/salesperson_line.xml new file mode 100644 index 00000000000..39c4a38c281 --- /dev/null +++ b/pos_order_salesperson/static/src/salesperson_line/salesperson_line.xml @@ -0,0 +1,53 @@ + + + + + + + +
+ + + +
+ + + +
+ +
+ + + + + + + + + + + + + + Select + + + + + unselect + + + + + + + + diff --git a/pos_order_salesperson/static/src/salesperson_list/salesperson_list.js b/pos_order_salesperson/static/src/salesperson_list/salesperson_list.js new file mode 100644 index 00000000000..869f8ce824b --- /dev/null +++ b/pos_order_salesperson/static/src/salesperson_list/salesperson_list.js @@ -0,0 +1,45 @@ +import { useService } from "@web/core/utils/hooks"; +import { Dialog } from "@web/core/dialog/dialog"; +import { usePos } from "@point_of_sale/app/hooks/pos_hook"; +import { Component, useState } from "@odoo/owl"; +import { Dropdown } from "@web/core/dropdown/dropdown"; +import { DropdownItem } from "@web/core/dropdown/dropdown_item"; +import { SalespersonLine } from "../salesperson_line/salesperson_line"; + +export class SalespersonList extends Component { + static template = "point_of_sale.SalespersonList"; + static components = { Dialog, Dropdown, DropdownItem, SalespersonLine }; + static props = { + salesperson: { + optional: true, + type: [{ value: null }, Object], + }, + getPayload: { type: Function }, + close: { type: Function } + } + + setup() { + this.pos = usePos(); + this.ui = useService("ui"); + this.dialog = useService("dialog"); + this.state = useState({ + query: null, + previousQuery: "", + currentOffset: 0, + totalSalespersons: 0, + isLoading: false, + query: '' + }); + } + + getSalesPerson() { + const salesperson = this.pos.models['hr.employee'].getAll(); + this.state.totalSalespersons = salesperson.length + return salesperson; + } + + clickSalesPerson(salesperson) { + this.props.getPayload(salesperson); + this.props.close(); + } +} diff --git a/pos_order_salesperson/static/src/salesperson_list/salesperson_list.xml b/pos_order_salesperson/static/src/salesperson_list/salesperson_list.xml new file mode 100644 index 00000000000..574301e0aff --- /dev/null +++ b/pos_order_salesperson/static/src/salesperson_list/salesperson_list.xml @@ -0,0 +1,38 @@ + + + + + + Choose Salesperson + + +
+ + + + + + +
+
+
+ +

No Salesperson found.

+
+
+
+ +
+ +
+
+
+
+
diff --git a/pos_order_salesperson/views/pos_order_view.xml b/pos_order_salesperson/views/pos_order_view.xml new file mode 100644 index 00000000000..93b80800127 --- /dev/null +++ b/pos_order_salesperson/views/pos_order_view.xml @@ -0,0 +1,24 @@ + + + + pos.order.tree.inherit + pos.order + + + + + + + + + + pos.order.form.inherit + pos.order + + + + + + + +