Compare commits

...

44 Commits

Author SHA1 Message Date
chillaranand
ca11e7716f Merge branch 'develop' into sales-commission-payout 2022-05-11 12:35:33 +05:30
Afshan
d67e07fe65 fix: minor fixes and refactors 2022-05-11 12:16:46 +05:30
Afshan
93a9d3b71f fix: linting issues 2022-05-11 12:16:46 +05:30
Afshan
7a80271e66 fix: remove commission rate from list view 2022-05-11 12:16:46 +05:30
Afshan
d4fe911e24 fix: show proper status in list view 2022-05-11 12:16:46 +05:30
Afshan
6e4b94f265 fix: clear contribution table on reselection of sales person 2022-05-11 12:16:46 +05:30
Afshan
90cafeedc7 style:show Sales Person, Total COmmission and Commission Rate in list view 2022-05-11 12:16:46 +05:30
Afshan
ab862842f9 fix: validate total commission amount to be greater than 0 2022-05-11 12:16:46 +05:30
Afshan
e0e520f165 fix: update status on submission 2022-05-11 12:16:46 +05:30
Afshan
741a418a31 fix: added to workspaces 2022-05-11 12:16:46 +05:30
Afshan
303af52e1c fix: linting issues 2022-05-11 12:16:25 +05:30
Afshan
ad168c0ee3 fix: added tests 2022-05-11 12:16:25 +05:30
Afshan
eb77a8c6a9 feat: sales commission payout 2022-05-11 12:16:25 +05:30
Afshan
67c18a9a5a fix: linting issues 2022-05-11 12:16:25 +05:30
Afshan
cde14eb640 feat: Sales Commissoion Payout 2022-05-11 12:16:25 +05:30
Afshan
488624c75b fix: minor fixes and refactors 2021-10-28 19:27:12 +05:30
Afshan
044ad02578 Merge branch 'develop' into sales-commission-payout 2021-10-28 13:20:10 +05:30
Afshan
e43cfdf460 fix: linting issues 2021-10-27 17:24:30 +05:30
Afshan
5d52fa966d fix: remove commission rate from list view 2021-10-27 16:33:19 +05:30
Afshan
8cc90b9def fix: show proper status in list view 2021-10-27 16:28:59 +05:30
Afshan
e1ec223ee5 fix: clear contribution table on reselection of sales person 2021-10-27 16:18:46 +05:30
Afshan
63e0a174c0 style:show Sales Person, Total COmmission and Commission Rate in list view 2021-10-27 16:09:49 +05:30
Afshan
f81f6edbab fix: validate total commission amount to be greater than 0 2021-10-27 16:02:31 +05:30
Afshan
6f4275d488 Merge branch 'develop' into sales-commission-payout 2021-10-27 15:38:48 +05:30
Afshan
f82163b7ae Merge branch 'develop' into sales-commission-payout 2021-10-27 14:42:21 +05:30
Afshan
83e4d65fcd Merge branch 'develop' into sales-commission-payout 2021-10-27 12:53:09 +05:30
Afshan
95912f05fb Merge branch 'develop' into sales-commission-payout 2021-10-26 19:52:24 +05:30
Afshan
bd9189fd7f Merge branch 'develop' into sales-commission-payout 2021-10-18 11:16:16 +05:30
Afshan
1569add4e0 Merge branch 'develop' into sales-commission-payout 2021-10-07 19:44:16 +05:30
Afshan
9e20f6222d Merge branch 'develop' into sales-commission-payout 2021-10-05 11:59:59 +05:30
Afshan
8ae6cd5dd1 Merge branch 'develop' into sales-commission-payout 2021-10-04 12:13:07 +05:30
Afshan
7a5b3a5af1 Merge branch 'develop' into sales-commission-payout 2021-10-01 13:59:58 +05:30
Afshan
aeea9b14c6 fix: update status on submission 2021-09-30 22:04:09 +05:30
Afshan
43bc0d571c Merge branch 'develop' into sales-commission-payout 2021-09-30 21:30:58 +05:30
Afshan
9fe22c75ef fix: added to workspaces 2021-09-30 20:22:58 +05:30
Afshan
a5de9a3909 fix: linting issues 2021-09-30 19:46:09 +05:30
Afshan
0196ab56e8 fix: added tests 2021-09-30 19:21:36 +05:30
Afshan
72d91b6195 feat: sales commission payout 2021-09-30 17:14:06 +05:30
Afshan
aeb68a46c2 Merge branch 'develop' into sales-commission-payout 2021-09-30 17:06:49 +05:30
Afshan
1e0de1769f Merge branch 'develop' into sales-commission-payout 2021-09-27 10:55:45 +05:30
Afshan
c9025aceec Merge branch 'sales-commission-payout' of https://github.com/AfshanKhan/erpnext into sales-commission-payout 2021-09-21 11:09:36 +05:30
Afshan
9da6d21e75 fix: linting issues 2021-09-21 11:09:07 +05:30
Afshan
43ddf845b1 Merge branch 'develop' into sales-commission-payout 2021-09-20 15:52:08 +05:30
Afshan
9688d745f8 feat: Sales Commissoion Payout 2021-09-20 15:50:12 +05:30
18 changed files with 1234 additions and 11 deletions

View File

@@ -301,7 +301,7 @@ class PaymentEntry(AccountsController):
elif self.party_type == "Supplier":
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
elif self.party_type == "Employee":
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity")
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity", "Sales Commission")
elif self.party_type == "Shareholder":
valid_reference_doctypes = "Journal Entry"
@@ -1593,7 +1593,10 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
elif reference_doctype != "Journal Entry":
if ref_doc.doctype == "Expense Claim":
if ref_doc.doctype == "Sales Commission":
total_amount = ref_doc.total_commission_amount
exchange_rate = 1
elif ref_doc.doctype == "Expense Claim":
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
elif ref_doc.doctype == "Employee Advance":
total_amount = ref_doc.advance_amount

View File

@@ -0,0 +1,101 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2021-09-07 12:49:18.526652",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"document_type",
"order_or_invoice",
"customer",
"customer_name",
"posting_date",
"column_break_5",
"contribution_percent",
"contribution_amount",
"commission_rate",
"commission_amount"
],
"fields": [
{
"fieldname": "order_or_invoice",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Order / Invoice",
"options": "document_type",
"read_only": 1
},
{
"fieldname": "customer",
"fieldtype": "Link",
"label": "Customer",
"options": "Customer",
"read_only": 1
},
{
"fetch_from": "customer.customer_name",
"fieldname": "customer_name",
"fieldtype": "Data",
"label": "Customer Name",
"read_only": 1
},
{
"fieldname": "posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Posting Date",
"read_only": 1
},
{
"fieldname": "commission_rate",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Commission Rate",
"read_only": 1
},
{
"fieldname": "commission_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Commission Amount",
"read_only": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "document_type",
"fieldtype": "Link",
"hidden": 1,
"label": "Document Type",
"options": "DocType"
},
{
"fieldname": "contribution_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Contribution Amount",
"read_only": 1
},
{
"fieldname": "contribution_percent",
"fieldtype": "Data",
"label": "Contribution %",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-09-13 19:11:43.548342",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Contributions",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class Contributions(Document):
pass

View File

@@ -9,6 +9,7 @@
"payroll_based_on",
"consider_unmarked_attendance_as",
"max_working_hours_against_timesheet",
"salary_component_for_sales_commission",
"include_holidays_in_total_working_days",
"disable_rounded_total",
"column_break_11",
@@ -91,13 +92,19 @@
"fieldname": "show_leave_balances_in_salary_slip",
"fieldtype": "Check",
"label": "Show Leave Balances in Salary Slip"
},
{
"fieldname": "salary_component_for_sales_commission",
"fieldtype": "Link",
"label": "Salary Component for Sales Commission",
"options": "Salary Component"
}
],
"icon": "fa fa-cog",
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-03-03 17:49:59.579723",
"modified": "2021-09-07 12:21:16.640474",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll Settings",

View File

@@ -0,0 +1,17 @@
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Process Sales Commission', {
setup: function(frm) {
frm.set_query("department", function() {
if (!frm.doc.company) {
frappe.throw(__("Please select company first"));
}
return {
filters: {
company: frm.doc.company
}
};
});
},
});

View File

@@ -0,0 +1,145 @@
{
"actions": [],
"autoname": "format:PRO-SAL-COM-{#####}",
"creation": "2021-09-08 13:28:11.658071",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"department",
"column_break_3",
"designation",
"branch",
"section_break_6",
"from_date",
"to_date",
"column_break_9",
"commission_based_on",
"pay_via_salary",
"amended_from"
],
"fields": [
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "department",
"fieldtype": "Link",
"label": "Department",
"options": "Department"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "designation",
"fieldtype": "Link",
"label": "Designation",
"options": "Designation"
},
{
"fieldname": "branch",
"fieldtype": "Link",
"label": "Branch",
"options": "Branch"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
{
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"reqd": 1
},
{
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"reqd": 1
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "commission_based_on",
"fieldtype": "Select",
"label": "Commission Based on",
"options": "Sales Order\nSales Invoice"
},
{
"default": "0",
"fieldname": "pay_via_salary",
"fieldtype": "Check",
"label": "Pay Via Salary"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Process Sales Commission",
"print_hide": 1,
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-09-20 15:45:39.240487",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Process Sales Commission",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,99 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_link_to_form
class ProcessSalesCommission(Document):
def validate(self):
self.validate_from_to_dates()
self.validate_salary_component()
def validate_from_to_dates(self):
return super().validate_from_to_dates("from_date", "to_date")
def validate_salary_component(self):
if self.pay_via_salary and not frappe.db.get_single_value(
"Payroll Settings", "salary_component_for_sales_commission"):
frappe.throw(_("Please set {0} in {1}").format(
frappe.bold("Salary Component for Sales Commission"), get_link_to_form("Payroll Settings", "Payroll Settings")))
def on_submit(self):
self.process_sales_commission()
def process_sales_commission(self):
filter_date = "transaction_date" if self.commission_based_on=="Sales Order" else "posting_date"
records = [entry.name for entry in frappe.db.get_all(
self.commission_based_on,
filters={"company": self.company, "docstatus":1, filter_date: ('between', [self.from_date, self.to_date])})]
sales_persons_details = frappe.get_all(
"Sales Team", filters={"parent": ['in', records]},
fields=["sales_person", "commission_rate", "incentives", "allocated_percentage", "allocated_amount", "parent"])
if sales_persons_details:
sales_persons = {e['sales_person'] for e in sales_persons_details}
sales_persons_list = self.get_sales_persons_list(sales_persons)
# sales_persons_details_map = self.map_sales_persons_details(sales_persons_list, sales_persons_details)
self.make_sales_commission_document(sales_persons_list, filter_date)
def get_sales_persons_list(self, sales_persons):
sales_persons_list = sales_persons
if self.department or self.designation or self.branch:
sales_persons_emp = frappe.get_all("Sales Person", filters= {"name": ["in", sales_persons]}, fields=["employee"], as_dict=True)['employee']
emp_filters = {"name": ["in", sales_persons_emp], "company": self.company}
# for field in ["department", "designation", "branch"]:
if self.department:
emp_filters["department"] = self.department
if self.designation:
emp_filters["designation"] = self.designation
if self.branch:
emp_filters["branch"] = self.branch
sales_persons_list = frappe.get_all("Employee", filters=emp_filters, as_dict=True)
# for person in sales_persons:
# emp = frappe.db.get_value("Sales Person", filters={"name": person}, fieldname="employee", as_dict=True)['employee']
# if emp:
# employee_details = frappe.db.get_value("Employee", filters={"name": emp}, as_dict=True)
# if self.company != employee_details["company"]:
# sales_persons_list.remove(person)
# continue
# if self.department and self.department != employee_details["department"]:
# sales_persons_list.remove(person)
# continue
# if self.designation and self.designation != employee_details["designation"]:
# sales_persons_list.remove(person)
# continue
# if self.branch and self.branch != employee_details["branch"]:
# sales_persons_list.remove(person)
# continue
return sales_persons_list
# def map_sales_persons_details(self, sales_persons, sales_persons_details):
# sales_persons_details_map = {}
# for person in sales_persons:
# sales_persons_details_map[person] = []
# for details in sales_persons_details:
# if details['sales_person'] == person:
# sales_persons_details_map[person].append(details)
# return sales_persons_details_map
def make_sales_commission_document(self, sales_persons_details_map, filter_date):
for record in sales_persons_details_map:
doc = doc = frappe.new_doc("Sales Commission")
doc.sales_person = record
doc.from_date = self.from_date
doc.to_date = self.to_date
doc.pay_via_salary = self.pay_via_salary
doc.process_sales_commission_reference = self.name
doc.add_contributions()
doc.insert()
if not frappe.db.get_single_value("Selling Settings", "approval_required_for_sales_commission_payout"):
doc.reload()
if self.pay_via_salary and doc.employee:
if frappe.db.exists('Salary Structure Assignment', {'employee': doc.employee}):
doc.submit()
doc.payout_entry()

View File

@@ -0,0 +1,60 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.utils import add_to_date, getdate
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
create_salary_structure_assignment,
)
from erpnext.payroll.doctype.sales_commission.test_sales_commission import (
company,
emp_name,
make_salary_structure,
make_sales_person,
make_so,
sales_commission_component,
setup_test,
)
salary_structure = "Salary Structure for Sales Commission2"
emp_name2= "test_sales_commission2@payout.com"
class TestProcessSalesCommission(unittest.TestCase):
def setUp(self):
setup_test()
emp_id = make_employee(emp_name2, company=company)
make_sales_person(emp_id, emp_name2)
make_salary_structure(salary_structure)
create_salary_structure_assignment(
emp_id,
salary_structure,
company=company,
currency="INR"
)
frappe.db.set_value("Payroll Settings", "Payroll Settings", "salary_component_for_sales_commission", sales_commission_component[0]["salary_component"])
frappe.db.set_value("Selling Settings", "Selling Settings", "approval_required_for_sales_commission_payout", 0)
frappe.db.sql("delete from `tabAdditional Salary`")
frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 0)
def tearDown(self):
for dt in ["Additional Salary", "Salary Structure Assignment", "Salary Structure"]:
frappe.db.sql("delete from `tab%s`" % dt)
def test_process_sales_commission(self):
make_so(emp_name)
make_so(emp_name2)
psc = frappe.new_doc("Process Sales Commission")
psc.company = company
psc.commission_based_on = "Sales Order"
psc.from_date = add_to_date(getdate(), days=-2)
psc.to_date = getdate()
psc.insert()
psc.submit()
sales_commissions = frappe.get_all("Sales Commission", filters={"process_sales_commission_reference": psc.name})
self.assertEqual(len(sales_commissions), 2)

View File

@@ -0,0 +1,133 @@
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Sales Commission', {
setup: function(frm) {
frm.set_query("commission_based_on", function() {
return {
filters: [
['name', 'in', ["Sales Order", "Sales Invoice"]]
]
};
});
},
refresh: function(frm) {
if (frm.doc.docstatus == 1) {
if (frm.custom_buttons) frm.clear_custom_buttons();
frm.events.add_context_buttons(frm);
}
},
sales_person: function (frm) {
frm.clear_table('contributions');
frm.refresh();
},
get_contributions: function (frm) {
frm.clear_table("contributions");
return frappe.call({
doc: frm.doc,
method: 'add_contributions',
callback: function () {
frm.dirty();
frm.save();
frm.refresh();
},
});
},
add_context_buttons: function (frm) {
if (!frm.doc.reference_name) {
if (frm.doc.pay_via_salary) {
frm.add_custom_button(__("Create Additional Salary"), function () {
create_additional_salary(frm);
}).addClass("btn-primary");
} else {
frm.add_custom_button(__("Create Payment Entry"), function () {
create_payment_entry(frm);
}).addClass("btn-primary");
}
}
},
});
const create_payment_entry = function (frm) {
var d = new frappe.ui.Dialog({
title: __("Select Mode of Payment"),
fields: [
{
'fieldname': 'mode_of_payment',
'fieldtype': 'Link',
'label': __('Mode of Payment'),
'options': 'Mode of Payment',
"get_query": function () {
return {
filters: {
type: ["in", ["Bank", "Cash"]]
}
};
},
'reqd': 1
}
],
});
d.set_primary_action(__('Create'), function() {
d.hide();
var arg = d.get_values();
frappe.confirm(__("Creating Payment Entry. Do you want to proceed?"),
function () {
frappe.call({
method: 'payout_entry',
args: {
"mode_of_payment": arg.mode_of_payment
},
callback: function () {
frappe.set_route(
'Form', "Payment Entry", {
"Payment Entry Reference.reference_name": frm.doc.name
}
);
},
doc: frm.doc,
freeze: true,
freeze_message: __('Creating Payment Entry')
});
},
function () {
if (frappe.dom.freeze_count) {
frappe.dom.unfreeze();
frm.events.refresh(frm);
}
}
);
});
d.show();
};
const create_additional_salary = function (frm) {
frappe.confirm(__("Creating Additional Salary. Do you want to proceed?"),
function () {
frappe.call({
method: 'payout_entry',
args: {},
callback: function () {
frappe.set_route(
"Form", "Additional Salary", {
"Additional Salary.ref_docname": frm.doc.name
}
);
},
doc: frm.doc,
freeze: true,
freeze_message: __('Creating Additional Salary')
});
},
function () {
if (frappe.dom.freeze_count) {
frappe.dom.unfreeze();
frm.events.refresh(frm);
}
}
);
};

View File

@@ -0,0 +1,272 @@
{
"actions": [],
"autoname": "format:SAL-COM-{#####}",
"creation": "2021-09-07 12:43:03.200379",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"sales_person",
"employee",
"employee_name",
"designation",
"department",
"branch",
"column_break_6",
"status",
"company",
"pay_via_salary",
"section_break_10",
"from_date",
"to_date",
"column_break_13",
"commission_based_on",
"process_sales_commission_reference",
"section_break_15",
"get_contributions",
"contributions",
"section_break_17",
"total_contribution",
"total_commission_amount",
"remarks",
"column_break_21",
"commission_rate",
"calculate_commission_manually",
"amended_from",
"reference_doctype",
"reference_name"
],
"fields": [
{
"fetch_from": "sales_person.employee",
"fieldname": "employee",
"fieldtype": "Link",
"label": "Employee",
"options": "Employee",
"read_only": 1
},
{
"depends_on": "employee",
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"label": "Employee Name",
"read_only": 1
},
{
"fetch_from": "employee.designation",
"fieldname": "designation",
"fieldtype": "Link",
"label": "Designation",
"options": "Designation",
"read_only": 1
},
{
"fetch_from": "employee.department",
"fieldname": "department",
"fieldtype": "Link",
"label": "Department",
"options": "Department",
"read_only": 1
},
{
"fetch_from": "employee.branch",
"fieldname": "branch",
"fieldtype": "Link",
"label": "Branch",
"options": "Branch",
"read_only": 1
},
{
"fetch_from": "employee.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"read_only": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"options": "Draft\nUnpaid\nPaid",
"read_only": 1
},
{
"default": "0",
"fieldname": "pay_via_salary",
"fieldtype": "Check",
"label": "Pay Via Salary"
},
{
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date"
},
{
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date"
},
{
"fieldname": "commission_based_on",
"fieldtype": "Select",
"label": "Commission Based on",
"options": "Sales Order\nSales Invoice"
},
{
"fieldname": "total_contribution",
"fieldtype": "Currency",
"label": "Total Contribution",
"read_only": 1
},
{
"default": "0",
"fieldname": "calculate_commission_manually",
"fieldtype": "Check",
"label": "Calculate Commission Manually"
},
{
"depends_on": "calculate_commission_manually",
"fieldname": "commission_rate",
"fieldtype": "Float",
"label": "Commission Rate"
},
{
"fieldname": "total_commission_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Total Commission Amount",
"read_only": 1
},
{
"fieldname": "remarks",
"fieldtype": "Data",
"hidden": 1,
"label": "Remarks"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Sales Commission",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "contributions",
"fieldtype": "Table",
"label": "Contributions",
"options": "Contributions"
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_10",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_15",
"fieldtype": "Section Break"
},
{
"fieldname": "section_break_17",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_21",
"fieldtype": "Column Break"
},
{
"fieldname": "process_sales_commission_reference",
"fieldtype": "Link",
"label": "Process Sales Commission Reference",
"options": "Process Sales Commission",
"read_only": 1
},
{
"fieldname": "sales_person",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Sales Person",
"options": "Sales Person"
},
{
"fieldname": "reference_doctype",
"fieldtype": "Link",
"hidden": 1,
"label": "Reference Doctype",
"options": "DocType"
},
{
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"hidden": 1,
"label": "Reference Name",
"options": "reference_doctype"
},
{
"depends_on": "eval:doc.docstatus==0",
"fieldname": "get_contributions",
"fieldtype": "Button",
"label": "Get Contributions"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-10-27 16:32:19.283507",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Sales Commission",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,147 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_link_to_form
class SalesCommission(Document):
def validate(self):
self.validate_from_to_dates()
self.validate_salary_component()
self.calculate_total_contribution_and_total_commission_amount()
def validate_from_to_dates(self):
return super().validate_from_to_dates("from_date", "to_date")
def validate_amount(self):
if self.total_commission_amount <= 0:
frappe.throw(_("Total Commission Amount should be greater than 0"))
def validate_salary_component(self):
if self.pay_via_salary and not frappe.db.get_single_value("Payroll Settings", "salary_component_for_sales_commission"):
frappe.throw(_("Please set {0} in {1}").format(
frappe.bold("Salary Component for Sales Commission"), get_link_to_form("Payroll Settings", "Payroll Settings")))
def on_submit(self):
self.validate_amount()
self.db_set("status", "Unpaid")
@frappe.whitelist()
def add_contributions(self):
self.set("contributions", [])
filter_date = "transaction_date" if self.commission_based_on=="Sales Order" else "posting_date"
records = [entry.name for entry in frappe.db.get_all(
self.commission_based_on,
filters={"company": self.company, "docstatus":1, filter_date: ('between', [self.from_date, self.to_date])})]
sales_persons_details = frappe.get_all(
"Sales Team", filters={"parent": ['in', records], "sales_person": self.sales_person},
fields=["sales_person", "commission_rate", "incentives", "allocated_percentage", "allocated_amount", "parent"])
if sales_persons_details:
for record in sales_persons_details:
if add_record(record, self.sales_person):
record_details = frappe.db.get_value(
self.commission_based_on, filters={"name": record["parent"]},
fieldname=["customer", filter_date], as_dict=True)
contribution = {
"document_type": self.commission_based_on,
"order_or_invoice": record["parent"],
"customer": record_details["customer"],
"posting_date": record_details[filter_date],
"contribution_percent": record["allocated_percentage"],
"contribution_amount": record["allocated_amount"],
"commission_rate": record["commission_rate"],
"commission_amount": record["incentives"],
}
self.append("contributions", contribution)
self.calculate_total_contribution_and_total_commission_amount()
def calculate_total_contribution_and_total_commission_amount(self):
total_contribution, total_commission_amount = 0,0
for entry in self.contributions:
total_contribution += entry.contribution_amount
total_commission_amount += entry.commission_amount
if self.calculate_commission_manually:
rate = self.commission_rate
total_commission_amount = total_contribution * (rate / 100)
self.total_contribution = total_contribution
self.total_commission_amount = total_commission_amount
@frappe.whitelist()
def payout_entry(self, mode_of_payment=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
if mode_of_payment:
paid_from = get_bank_cash_account(mode_of_payment, self.company).get("account")
paid_to = frappe.db.get_value(
"Company", filters={"name":self.company},
fieldname=['default_payable_account'], as_dict=True)['default_payable_account']
if not paid_to:
frappe.throw(_("Please set Default Payable Account in {}").format(get_link_to_form("Company", self.company)))
if self.pay_via_salary:
self.make_additional_salary()
else:
self.make_payment_entry(mode_of_payment, paid_from, paid_to)
def make_additional_salary(self):
currency = frappe.get_value("Company", self.company, "default_currency")
doc = frappe.new_doc("Additional Salary")
doc.employee = self.employee
doc.company = self.company
doc.currency = currency
doc.salary_component = frappe.db.get_single_value("Payroll Settings", "salary_component_for_sales_commission")
doc.payroll_date = self.to_date
doc.amount = self.total_commission_amount
doc.ref_doctype = self.doctype
doc.ref_docname = self.name
doc.submit()
self.db_set("reference_doctype", "Additional Salary")
self.db_set("reference_name", doc.name)
self.db_set("status", "Paid")
def make_payment_entry(self, mode_of_payment, paid_from, paid_to):
doc = frappe.new_doc("Payment Entry")
doc.company = self.company
doc.payment_type = "Pay"
doc.mode_of_payment = mode_of_payment
doc.party_type = "Employee"
doc.party = self.employee
doc.paid_from = paid_from
doc.paid_to = paid_to
doc.paid_amount = self.total_commission_amount
doc.received_amount = self.total_commission_amount
doc.source_exchange_rate = 1
doc.target_exchange_rate = 1
doc.set("references", [])
self.add_references(doc)
doc.submit()
self.db_set("reference_doctype", "Payment Entry")
self.db_set("reference_name", doc.name)
self.db_set("status", "Paid")
def add_references(self, doc):
reference = {
'reference_doctype': 'Sales Commission',
'reference_name': self.name,
'due_date': self.to_date,
'total_amount': self.total_commission_amount,
'outstanding_amount': self.total_commission_amount,
'allocated_amount': self.total_commission_amount,
}
doc.append("references", reference)
def add_record(record, sales_person):
previous_contibutions = frappe.get_all("Contributions", filters={"order_or_invoice":record["parent"], "docstatus": 1}, fields=["parent"])
if previous_contibutions:
for contributions in previous_contibutions:
if frappe.db.get_value("Sales Commission", {"name":contributions["parent"]}, fieldname=["sales_person"]) == sales_person:
return False
return True

View File

@@ -0,0 +1,10 @@
frappe.listview_settings['Sales Commission'] = {
add_fields: ["total_commission_amount", "status"],
get_indicator: function (doc) {
if (doc.status == "Paid") {
return [__(doc.status), "green", "status,=," + doc.status];
} else {
return [__(doc.status), "red", "status,=," + doc.status];
}
}
};

View File

@@ -0,0 +1,201 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.utils import add_to_date, getdate
from frappe.utils.make_random import get_random
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
create_salary_structure_assignment,
)
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
company = "_Test Company"
emp_name= "test_sales_commission@payout.com"
earning_component_data = [
{
"salary_component": 'Basic Salary',
"abbr":'BS',
"type": "Earning"
},
{
"salary_component": 'HRA',
"abbr":'H',
"type": "Earning"
},
]
sales_commission_component = [
{
"salary_component": 'Sales Commission',
"abbr":'SC',
"type": "Earning"
},
]
deduction_component_data = [
{
"salary_component": 'Professional Tax',
"abbr":'PT',
"type": "Deduction",
"exempted_from_income_tax": 1
}
]
salary_structure = "Salary Structure for Sales Commission"
class TestSalesCommission(unittest.TestCase):
def setUp(self):
setup_test()
def tearDown(self):
for dt in ["Additional Salary", "Salary Structure Assignment", "Salary Structure"]:
frappe.db.sql("delete from `tab%s`" % dt)
def test_sales_commision_via_additional_salary(self):
make_so(emp_name)
sc = self.make_sc()
sc.submit()
sc.payout_entry()
add_sal = frappe.get_all("Additional Salary", filters={"ref_docname": sc.name})
for entry in add_sal:
if frappe.get_value("Additional Salary", filters={"name": entry["name"]}, fieldname=["employee"]) == sc.employee:
add_sal_doc = frappe.get_doc("Additional Salary", entry['name'])
self.assertEqual(add_sal_doc.amount, sc.total_commission_amount)
def test_sales_commision_via_payment_entry(self):
make_so(emp_name)
sc = self.make_sc(via_salary=0)
sc.submit()
sc.payout_entry(mode_of_payment="Cash")
pe = frappe.get_all("Payment Entry Reference", filters={"reference_name": sc.name}, fields=["parent"])
for entry in pe:
if frappe.get_value("Payment Entry", filters={"name":entry["parent"]}, fieldname=["party"]) == sc.employee:
pe_doc = frappe.get_doc("Payment Entry", entry["parent"])
self.assertEqual(pe_doc.paid_amount, sc.total_commission_amount)
def make_sc(self, via_salary=1):
sc = frappe.new_doc("Sales Commission")
sc.sales_person = "test_sales_commission@payout.com"
sc.company = company
sc.pay_via_salary = via_salary
sc.commission_based_on = "Sales Order"
sc.from_date = add_to_date(getdate(), days=-2)
sc.to_date = getdate()
sc.add_contributions()
sc.insert()
return sc
def setup_test():
emp_id = make_employee(emp_name, company=company)
make_sales_person(emp_id, emp_name)
make_salary_component(earning_component_data)
make_salary_component(sales_commission_component)
make_salary_component(deduction_component_data)
make_salary_structure(salary_structure)
create_salary_structure_assignment(
emp_id,
salary_structure,
company=company,
currency="INR"
)
frappe.db.set_value("Payroll Settings", "Payroll Settings", "salary_component_for_sales_commission", sales_commission_component[0]["salary_component"])
frappe.db.set_value("Selling Settings", "Selling Settings", "approval_required_for_sales_commission_payout", 0)
frappe.db.sql("delete from `tabAdditional Salary`")
frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 0)
def add_sales_person(so, emp):
sales_person = {
"sales_person": emp,
"allocated_percentage": 100,
"commission_rate":10
}
so.append("sales_team", sales_person)
def make_salary_structure(salary_structure):
if frappe.db.exists("Salary Structure", salary_structure):
frappe.db.delete("Salary Structure", salary_structure)
details = {
"doctype": "Salary Structure",
"name": salary_structure,
"company": company,
"payroll_frequency": "Monthly",
"payment_account": get_random("Account", filters={"account_currency": "INR"}),
"currency": "INR"
}
salary_structure_doc = frappe.get_doc(details)
for entry in earning_component_data:
salary_structure_doc.append("earnings", entry)
for entry in deduction_component_data:
salary_structure_doc.append("deductions", entry)
salary_structure_doc.insert()
salary_structure_doc.submit()
def make_salary_component(salary_components):
for salary_component in salary_components:
if not frappe.db.exists('Salary Component', salary_component["salary_component"]):
salary_component["doctype"] = "Salary Component"
salary_component["salary_component_abbr"] = salary_component["abbr"]
frappe.get_doc(salary_component).insert()
get_salary_component_account(salary_component["salary_component"])
def get_salary_component_account(sal_comp):
sal_comp = frappe.get_doc("Salary Component", sal_comp)
if not sal_comp.get("accounts"):
company_abbr = frappe.get_cached_value('Company', company, 'abbr')
if sal_comp.type == "Earning":
account_name = "Salary"
parent_account = "Indirect Expenses - " + company_abbr
else:
account_name = "Salary Deductions"
parent_account = "Current Liabilities - " + company_abbr
sal_comp.append("accounts", {
"company": company,
"account": create_account(account_name, parent_account)
})
sal_comp.save()
def create_account(account_name, parent_account):
company_abbr = frappe.get_cached_value('Company', company, 'abbr')
account = frappe.db.get_value("Account", account_name + " - " + company_abbr)
if not account:
frappe.get_doc({
"doctype": "Account",
"account_name": account_name,
"parent_account": parent_account,
"company": company
}).insert()
return account
def make_sales_person(emp, emp_name):
if frappe.db.exists("Sales Person", {"employee":emp}):
frappe.db.set_value("Sales Person", "sales_person_name", emp_name)
else:
sales_person = frappe.new_doc("Sales Person")
sales_person.sales_person_name = emp_name
sales_person.employee = emp
sales_person.insert()
def make_so(emp_name):
so = make_sales_order(do_not_save=1)
add_sales_person(so, emp_name)
so.transaction_date = add_to_date(getdate(), days=-1)
so.submit()
return so

View File

@@ -226,6 +226,26 @@
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Sales Commission",
"link_count": 0,
"link_to": "Sales Commission",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Process Sales Commission",
"link_count": 0,
"link_to": "Process Sales Commission",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@@ -323,7 +343,7 @@
"type": "Link"
}
],
"modified": "2022-02-23 17:41:19.098813",
"modified": "2022-02-24 17:41:19.098813",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll",
@@ -369,4 +389,4 @@
}
],
"title": "Payroll"
}
}

View File

@@ -28,7 +28,7 @@
"allow_multiple_items",
"allow_against_multiple_purchase_orders",
"hide_tax_id",
"enable_discount_accounting"
"approval_required_for_sales_commission_payout"
],
"fields": [
{
@@ -168,10 +168,9 @@
},
{
"default": "0",
"description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
"fieldname": "enable_discount_accounting",
"fieldname": "approval_required_for_sales_commission_payout",
"fieldtype": "Check",
"label": "Enable Discount Accounting for Selling"
"label": "Approval Required for Sales Commission Payout"
}
],
"icon": "fa fa-cog",
@@ -179,7 +178,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-04-14 16:01:29.405642",
"modified": "2022-05-14 16:01:29.405642",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling Settings",
@@ -199,4 +198,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}