Skip to content

Commit 288e2e0

Browse files
committed
[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
1 parent b68a192 commit 288e2e0

File tree

12 files changed

+270
-0
lines changed

12 files changed

+270
-0
lines changed

pos_order_salesperson/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import models
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "Point of Sale Salesperson",
3+
"description": """
4+
Point of Sale Salesperson Module to add Salesperson in pos order, form and billing in session.
5+
""",
6+
"version": "1.0",
7+
"depends": ["pos_hr"],
8+
"author": "danal",
9+
"category": "Category",
10+
"license": "LGPL-3",
11+
"data": [
12+
"views/pos_order_view.xml",
13+
],
14+
"assets": {
15+
"point_of_sale._assets_pos": [
16+
"pos_order_salesperson/static/src/**/*"
17+
]
18+
},
19+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from . import pos_order
4+
from . import pos_session
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from odoo import fields, models
2+
3+
4+
class PosOrder(models.Model):
5+
_inherit = 'pos.order'
6+
7+
salesperson_id = fields.Many2one("hr.employee", string="Salesperson")
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from odoo import api, models
2+
3+
4+
class PosSession(models.Model):
5+
_inherit = 'pos.session'
6+
7+
@api.model
8+
def _load_pos_data_models(self, config_id):
9+
data = super()._load_pos_data_models(config_id)
10+
data += ["hr.employee"]
11+
return data
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { SalespersonList } from '../salesperson_list/salesperson_list';
2+
import { ControlButtons } from '@point_of_sale/app/screens/product_screen/control_buttons/control_buttons';
3+
import { makeAwaitable } from "@point_of_sale/app/utils/make_awaitable_dialog";
4+
import { patch } from '@web/core/utils/patch';
5+
import { useState } from '@odoo/owl';
6+
7+
patch(ControlButtons.prototype, {
8+
setup() {
9+
super.setup();
10+
const order = this.pos.getOrder();
11+
this.state = useState({
12+
salesperson_id: order ? order.salesperson_id : null,
13+
});
14+
15+
},
16+
17+
async selectSalesperson() {
18+
const currentOrder = this.pos.getOrder();
19+
if (!currentOrder) {
20+
return;
21+
}
22+
23+
const currentSalesperson = currentOrder.salesperson_id || null;
24+
const payload = await makeAwaitable(this.dialog, SalespersonList, {
25+
salesperson: currentSalesperson,
26+
getPayload: (newSalesperson) => newSalesperson || null,
27+
});
28+
this.state.salesperson_id = payload || null;
29+
currentOrder.salesperson_id = payload || null;
30+
return currentOrder.salesperson_id;
31+
}
32+
})
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<templates id="template" xml:space="preserve">
3+
<t t-name="SelectSalespersonButton" t-inherit="point_of_sale.ControlButtons" t-inherit-mode="extension">
4+
<xpath expr="//InternalNoteButton" position="after">
5+
<t t-name="point_of_sale.SelectSalespersonButton" owl="1">
6+
<button class="set-partner btn btn-secondary btn-lg lh-lg text-truncate w-auto" t-on-click="selectSalesperson">
7+
<div t-if="state.salesperson_id" t-out="state.salesperson_id?.name" class="text-truncate text-action" />
8+
<span t-else="">Salesperson</span>
9+
</button>
10+
</t>
11+
</xpath>
12+
</t>
13+
</templates>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Component } from "@odoo/owl";
2+
import { useService } from "@web/core/utils/hooks";
3+
import { Dropdown } from "@web/core/dropdown/dropdown";
4+
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
5+
import { usePos } from "@point_of_sale/app/hooks/pos_hook";
6+
7+
export class SalespersonLine extends Component {
8+
static template = "point_of_sale.SalespersonLine";
9+
static components = { Dropdown, DropdownItem };
10+
static props = [
11+
"close",
12+
"salesperson",
13+
"isSelected",
14+
"onClickEdit",
15+
"onClickUnselect",
16+
"clickSalesPerson"
17+
];
18+
19+
setup() {
20+
this.pos = usePos();
21+
this.ui = useService("ui");
22+
}
23+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<templates id="template" xml:space="preserve">
3+
<t t-name="point_of_sale.SalespersonLine">
4+
<tr class="partner-line partner-info"
5+
t-att-class="{'selected': props.isSelected}"
6+
t-on-click="() => props.clickSalesPerson(props.salesperson)">
7+
8+
<td class="text-break w-25">
9+
<b t-out="props.salesperson.name or ''" />
10+
<div t-if="props.salesperson.parent_name" class="company-field text-bg-muted" t-out="props.salesperson.parent_name" />
11+
</td>
12+
13+
<td>
14+
<div class="partner-line-adress" t-if="props.salesperson.work_contact_id.pos_contact_address" t-out="props.salesperson.work_contact_id.pos_contact_address" />
15+
</td>
16+
17+
<td class="partner-line-email">
18+
<div t-if="props.salesperson.work_contact_id.phone">
19+
<i class="fa fa-fw fa-phone me-2"/><t t-out="props.salesperson.work_contact_id.phone"/>
20+
</div>
21+
<div t-if="props.salesperson.work_contact_id.email" class="email-field">
22+
<i class="fa fa-fw fa-paper-plane-o me-2"/><t t-out="props.salesperson.work_contact_id.email" />
23+
</div>
24+
</td>
25+
26+
<td class="edit-partner-button-cell align-middle pe-0">
27+
<button t-if="props.isSelected" t-on-click.stop="props.onClickUnselect" class="unselect-tag d-lg-inline-block d-none btn btn-link btn-lg mt-1 float-end">
28+
<i class="fa fa-check"/>
29+
</button>
30+
</td>
31+
32+
<td class="edit-partner-button-cell align-middle">
33+
<Dropdown>
34+
<button class="btn btn-light btn-lg lh-lg border float-end">
35+
<i class="fa fa-fw fa-bars"/>
36+
</button>
37+
<t t-set-slot="content">
38+
<t t-if="!props.isSelected">
39+
<DropdownItem onSelected="() => props.clickSalesPerson(props.salesperson)">
40+
Select
41+
</DropdownItem>
42+
</t>
43+
<t t-if="props.isSelected">
44+
<DropdownItem onSelected="props.onClickUnselect">
45+
unselect
46+
</DropdownItem>
47+
</t>
48+
</t>
49+
</Dropdown>
50+
</td>
51+
</tr>
52+
</t>
53+
</templates>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { useService } from "@web/core/utils/hooks";
2+
import { Dialog } from "@web/core/dialog/dialog";
3+
import { usePos } from "@point_of_sale/app/hooks/pos_hook";
4+
import { Component, useState } from "@odoo/owl";
5+
import { Dropdown } from "@web/core/dropdown/dropdown";
6+
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
7+
import { SalespersonLine } from "../salesperson_line/salesperson_line";
8+
9+
export class SalespersonList extends Component {
10+
static template = "point_of_sale.SalespersonList";
11+
static components = { Dialog, Dropdown, DropdownItem, SalespersonLine };
12+
static props = {
13+
salesperson: {
14+
optional: true,
15+
type: [{ value: null }, Object],
16+
},
17+
getPayload: { type: Function },
18+
close: { type: Function }
19+
}
20+
21+
setup() {
22+
this.pos = usePos();
23+
this.ui = useService("ui");
24+
this.dialog = useService("dialog");
25+
this.state = useState({
26+
query: null,
27+
previousQuery: "",
28+
currentOffset: 0,
29+
totalSalespersons: 0,
30+
isLoading: false,
31+
query: ''
32+
});
33+
}
34+
35+
getSalesPerson() {
36+
const salesperson = this.pos.models['hr.employee'].getAll();
37+
this.state.totalSalespersons = salesperson.length
38+
return salesperson;
39+
}
40+
41+
clickSalesPerson(salesperson) {
42+
this.props.getPayload(salesperson);
43+
this.props.close();
44+
}
45+
}

0 commit comments

Comments
 (0)