diff --git a/pre_commissions/__init__.py b/pre_commissions/__init__.py
new file mode 100644
index 00000000000..0650744f6bc
--- /dev/null
+++ b/pre_commissions/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/pre_commissions/__manifest__.py b/pre_commissions/__manifest__.py
new file mode 100644
index 00000000000..4ac676c1d76
--- /dev/null
+++ b/pre_commissions/__manifest__.py
@@ -0,0 +1,17 @@
+{
+ 'name': "Sale Commission",
+ 'author': "Kunj Koradiya",
+ 'description': "This is the description",
+ 'license': "LGPL-3",
+ 'depends': ['base', 'account', 'sale_management'],
+ 'auto_install': True,
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'views/commision_rule.xml',
+ 'views/sale_commission.xml',
+ 'views/menus.xml'
+ ],
+ 'demo': [
+ 'data/demo.xml'
+ ]
+}
diff --git a/pre_commissions/data/demo.xml b/pre_commissions/data/demo.xml
new file mode 100644
index 00000000000..8a9b620fa8c
--- /dev/null
+++ b/pre_commissions/data/demo.xml
@@ -0,0 +1,336 @@
+
+
+
+ Alice
+ alice
+
+
+ Bob
+ bob
+
+
+ Charlie
+ charlie
+
+
+ Diana
+ diana
+
+
+ Ellen
+ ellen
+
+
+
+
+ Alpha Team
+
+
+
+ Beta Team
+
+
+
+ Gamma Team
+
+
+
+
+
+ Electronics
+
+
+ Furniture
+
+
+
+ Laptop
+
+
+
+ Phone
+
+
+
+ Table
+
+
+
+ Chair
+
+
+
+ Monitor
+
+
+
+
+
+ 1
+ True
+ 0.05
+ salesperson
+
+
+
+
+ 2
+ True
+ 0.03
+ team
+
+
+
+
+ 3
+ True
+ 0.07
+ salesperson
+
+
+
+
+ 4
+ True
+ 0.04
+ team
+
+
+
+
+
+ 5
+ True
+ 0.06
+ salesperson
+ 10.0
+
+
+
+
+ ACME Corp
+
+
+ Globex Inc
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2
+ 1000.0
+ 5.0
+
+
+
+
+ 1
+ 300.0
+ 0.0
+
+
+
+
+ 4
+ 50.0
+ 0.0
+
+
+
+
+ out_invoice
+
+ 2026-02-17
+
+
+
+
+ out_invoice
+
+ 2026-02-17
+
+
+
+
+ out_invoice
+
+ 2026-02-17
+
+
+
+
+
+
+
+ 2026-02-17
+
+
+
+ 110.0
+
+
+
+
+
+ 2026-02-17
+
+
+
+ 60.0
+
+
+
+
+
+ 2026-02-17
+
+
+
+ 35.0
+
+
+
+
+
+ 2026-02-17
+
+
+
+ 15.0
+
+
+
+
+
+
+ 2026-02-17
+
+
+
+ 9.0
+
+
+
+
+
+
+ 2026-02-17
+
+
+
+ 12.0
+
+
+
+
+
+
+ 2026-02-17
+
+
+
+ 14.0
+
+
+
+
+
+
+ 2026-02-17
+
+
+
+ 21.0
+
+
+
+
+
+
+ 2026-02-17
+
+
+
+ 6.0
+
+
+
+
+
+ 2026-02-17
+
+
+
+ 10.5
+
+
+
+
+
+ 2026-02-17
+
+
+
+ 8.0
+
+
+
+
+
+ 2026-02-17
+
+
+
+ 5.0
+
+
+
+
+
+
+ 2025-02-17
+
+
+
+ 710.0
+
+
+
+
+
diff --git a/pre_commissions/models/__init__.py b/pre_commissions/models/__init__.py
new file mode 100644
index 00000000000..eb2b588db60
--- /dev/null
+++ b/pre_commissions/models/__init__.py
@@ -0,0 +1,3 @@
+from . import commission_rule
+from . import sale_commission
+from . import account_move
diff --git a/pre_commissions/models/account_move.py b/pre_commissions/models/account_move.py
new file mode 100644
index 00000000000..86d1d49ba80
--- /dev/null
+++ b/pre_commissions/models/account_move.py
@@ -0,0 +1,21 @@
+from odoo import models
+
+
+class AccountMove(models.Model):
+ _inherit = 'account.move'
+
+ def action_post(self):
+ res = super().action_post()
+ # breakpoint()
+ for move in self:
+ if move.move_type != 'out_invoice':
+ continue
+
+ sale_orders = move.invoice_line_ids.sale_line_ids.order_id
+ sale_orders = sale_orders.exists()
+
+ if not sale_orders:
+ continue
+
+ self.env['sale.commission'].sudo().check_commission_rules(sale_orders, move)
+ return res
diff --git a/pre_commissions/models/commission_rule.py b/pre_commissions/models/commission_rule.py
new file mode 100644
index 00000000000..2d7bba6c23d
--- /dev/null
+++ b/pre_commissions/models/commission_rule.py
@@ -0,0 +1,85 @@
+from odoo import fields, models, api
+
+
+class CommisionRule(models.Model):
+ _name = 'commission.rule'
+ _description = "Commission Rule"
+ _order = 'sequence'
+
+ sequence = fields.Integer()
+ active = fields.Boolean(default=True)
+ commission_rate = fields.Float(required=True)
+ due_at = fields.Selection([
+ ('invoice', "Invoice Posted"),
+ ('payment', "Payment Received")
+ ], default='invoice', required=True)
+ commission_for = fields.Selection([
+ ('salesperson', "Salesperson"),
+ ('team', "Sales Team")
+ ])
+ product_id = fields.Many2one(
+ 'product.product')
+ product_category_id = fields.Many2one('product.category')
+ product_expired = fields.Selection([
+ ('no_impact', 'No Impact'),
+ ('yes', "Expired Only"),
+ ('no', 'Not Expired')
+ ], default='no_impact')
+ max_discount = fields.Float(string="Max Discount %")
+ fast_payment = fields.Boolean()
+ fast_payment_days = fields.Integer()
+ salesperson_id = fields.Many2one('res.users')
+ team_id = fields.Many2one('crm.team', string="Sales Team")
+ condition_display = fields.Char(
+ compute='_compute_condition_display',
+ store=True
+ )
+
+ @api.onchange('commission_for')
+ def _onchange_commission_for(self):
+ for rec in self:
+ if rec.commission_for == 'salesperson':
+ rec.team_id = False
+ elif rec.commission_for == 'team':
+ rec.salesperson_id = False
+ else:
+ rec.salesperson_id = False
+ rec.team_id = False
+
+ @api.depends(
+ 'product_category_id',
+ 'product_id',
+ 'salesperson_id',
+ 'team_id',
+ 'max_discount',
+ 'product_expired'
+ )
+ def _compute_condition_display(self):
+ for rec in self:
+ parts = []
+
+ if rec.product_id:
+ parts.append(f"Product: {rec.product_id.name}")
+ rec.product_category_id = rec.product_category_id or rec.product_id.categ_id
+
+ if rec.product_category_id:
+ parts.append(f"Category: {rec.product_category_id.name}")
+
+ if rec.salesperson_id:
+ parts.append(f"Salesperson: {rec.salesperson_id.name}")
+
+ if rec.team_id:
+ parts.append(f"Team: {rec.team_id.name}")
+
+ if rec.max_discount:
+ parts.append(f"Max Discount: {rec.max_discount}%")
+
+ if rec.product_expired != 'no_impact':
+ selection = dict(
+ rec.fields_get(['product_expired'])[
+ 'product_expired']['selection']
+ )
+ label = selection.get(rec.product_expired)
+ parts.append(f"Product Expiry: {label}")
+
+ rec.condition_display = " AND ".join(parts) if parts else "All"
diff --git a/pre_commissions/models/sale_commission.py b/pre_commissions/models/sale_commission.py
new file mode 100644
index 00000000000..8f707f90dcf
--- /dev/null
+++ b/pre_commissions/models/sale_commission.py
@@ -0,0 +1,108 @@
+from odoo import models, fields
+
+
+class SaleCommission(models.Model):
+ _name = 'sale.commission'
+ _description = "Sale Commission"
+ _order = 'date desc'
+
+ date = fields.Date(required=True, default=fields.Date.context_today)
+
+ user_id = fields.Many2one(
+ 'res.users', string="Salesperson", required=True, index=True)
+ team_id = fields.Many2one('crm.team', string="Sales Team", index=True)
+ invoice_id = fields.Many2one('account.move', string="Invoice", index=True)
+ partner_id = fields.Many2one(
+ related='invoice_id.partner_id', store=True, string="Customer")
+ amount = fields.Monetary(required=True)
+ currency_id = fields.Many2one(
+ 'res.currency', default=lambda self: self.env.company.currency_id, required=True)
+ rule_id = fields.Many2one(
+ 'commission.rule', string="Commission Rule", required=True)
+ sale_order_id = fields.Many2one('sale.order', string="Sale Order")
+
+ def check_commission_rules(self, sale_orders, invoice):
+ if not sale_orders or not invoice:
+ return
+
+ rules = self.env['commission.rule'].search(
+ [('active', '=', True)],
+ order='sequence asc'
+ )
+
+ commission_vals = []
+
+ for order in sale_orders:
+ for rule in rules:
+ if not self._rule_applies(rule, order, invoice):
+ continue
+
+ users, team = self._get_commission_owners(rule, order)
+ if not users:
+ continue
+
+ total_amount = invoice.amount_total * rule.commission_rate
+ per_user_amount = (
+ total_amount / len(users)
+ if rule.commission_for == 'team'
+ else total_amount
+ )
+
+ for user in users:
+ commission_vals.append({
+ 'date': invoice.invoice_date,
+ 'invoice_id': invoice.id,
+ 'user_id': user.id,
+ 'team_id': team.id if team else user.sale_team_id.id,
+ 'amount': per_user_amount,
+ 'rule_id': rule.id,
+ 'sale_order_id': order.id,
+ })
+
+ break # first valid rule wins
+
+ if commission_vals:
+ self.create(commission_vals)
+
+ def _get_commission_owners(self, rule, order):
+ if rule.commission_for == 'team' and order.team_id:
+ users = order.team_id.member_ids.filtered(lambda u: u.active)
+ return users, order.team_id
+
+ if order.user_id:
+ return order.user_id, False
+
+ return self.env['res.users'], False
+
+ def _rule_applies(self, rule, order, invoice):
+ return all([
+ self._rule_salesperson(rule, order),
+ self._rule_team(rule, order),
+ self._rule_products(rule, order),
+ self._rule_discount(rule, order),
+ self._rule_due_at(rule, invoice),
+ ])
+
+ def _rule_salesperson(self, rule, order):
+ return not rule.salesperson_id or order.user_id == rule.salesperson_id
+
+ def _rule_team(self, rule, order):
+ return not rule.team_id or order.team_id == rule.team_id
+
+ def _rule_products(self, rule, order):
+ for line in order.order_line:
+ if rule.product_id and line.product_id != rule.product_id:
+ return False
+ if rule.product_category_id and line.product_id.categ_id != rule.product_category_id:
+ return False
+ return True
+
+ def _rule_discount(self, rule, order):
+ if not rule.max_discount:
+ return True
+ return all(line.discount <= rule.max_discount for line in order.order_line)
+
+ def _rule_due_at(self, rule, invoice):
+ if rule.due_at == 'payment':
+ return invoice.payment_state == 'paid'
+ return True
diff --git a/pre_commissions/security/ir.model.access.csv b/pre_commissions/security/ir.model.access.csv
new file mode 100644
index 00000000000..835acb20bf8
--- /dev/null
+++ b/pre_commissions/security/ir.model.access.csv
@@ -0,0 +1,3 @@
+id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
+access_commission_rules,access_commission_rules,model_commission_rule,base.group_user,1,1,1,1
+access_sale_commissions,access_sale_commissions,model_sale_commission,base.group_user,1,1,1,1
diff --git a/pre_commissions/views/commision_rule.xml b/pre_commissions/views/commision_rule.xml
new file mode 100644
index 00000000000..fa11715be68
--- /dev/null
+++ b/pre_commissions/views/commision_rule.xml
@@ -0,0 +1,65 @@
+
+
+
+
+ Commission Rules
+ commission.rule
+ list,form
+
+
+
+
+ commission.rule.view.form
+ commission.rule
+
+
+
+
+
+
+
+ commission.rule.view.list
+ commission.rule
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pre_commissions/views/menus.xml b/pre_commissions/views/menus.xml
new file mode 100644
index 00000000000..10b9e753f28
--- /dev/null
+++ b/pre_commissions/views/menus.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pre_commissions/views/sale_commission.xml b/pre_commissions/views/sale_commission.xml
new file mode 100644
index 00000000000..fa17e09953d
--- /dev/null
+++ b/pre_commissions/views/sale_commission.xml
@@ -0,0 +1,80 @@
+
+
+
+ Sale Commission
+ sale.commission
+ list,form,pivot,graph
+ {'group_by': ['date','team_id','user_id']}
+
+
+
+
+
+ sale.commission.view.list
+ sale.commission
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sale.commission.view.pivot
+ sale.commission
+
+
+
+
+
+
+
+
+
+
+
+
+ sale.commission.view.graph
+ sale.commission
+
+
+
+
+
+
+
+
+
+
+
+ sale.commission.view.search
+ sale.commission
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file