diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index a5303215d5b..e4692a3033e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -283,7 +283,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") elif self.party_type == "Donor": @@ -1355,7 +1355,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 @@ -1389,6 +1392,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre exchange_rate = 1 elif reference_doctype == "Gratuity": outstanding_amount = ref_doc.amount - flt(ref_doc.paid_amount) + elif reference_doctype == "Sales Commission": + outstanding_amount = 0 else: outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid) else: diff --git a/erpnext/payroll/doctype/contributions/__init__.py b/erpnext/payroll/doctype/contributions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/payroll/doctype/contributions/contributions.json b/erpnext/payroll/doctype/contributions/contributions.json new file mode 100644 index 00000000000..05fe414017a --- /dev/null +++ b/erpnext/payroll/doctype/contributions/contributions.json @@ -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 +} \ No newline at end of file diff --git a/erpnext/payroll/doctype/contributions/contributions.py b/erpnext/payroll/doctype/contributions/contributions.py new file mode 100644 index 00000000000..ec69202d612 --- /dev/null +++ b/erpnext/payroll/doctype/contributions/contributions.py @@ -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 diff --git a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json index 54377e94b30..fb307f30fc6 100644 --- a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json +++ b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json @@ -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", diff --git a/erpnext/payroll/doctype/process_sales_commission/__init__.py b/erpnext/payroll/doctype/process_sales_commission/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/payroll/doctype/process_sales_commission/process_sales_commission.js b/erpnext/payroll/doctype/process_sales_commission/process_sales_commission.js new file mode 100644 index 00000000000..6b7f239f376 --- /dev/null +++ b/erpnext/payroll/doctype/process_sales_commission/process_sales_commission.js @@ -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 + } + } + }); + }, +}); diff --git a/erpnext/payroll/doctype/process_sales_commission/process_sales_commission.json b/erpnext/payroll/doctype/process_sales_commission/process_sales_commission.json new file mode 100644 index 00000000000..37ee5085338 --- /dev/null +++ b/erpnext/payroll/doctype/process_sales_commission/process_sales_commission.json @@ -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 +} \ No newline at end of file diff --git a/erpnext/payroll/doctype/process_sales_commission/process_sales_commission.py b/erpnext/payroll/doctype/process_sales_commission/process_sales_commission.py new file mode 100644 index 00000000000..201ea97985c --- /dev/null +++ b/erpnext/payroll/doctype/process_sales_commission/process_sales_commission.py @@ -0,0 +1,98 @@ +# 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: + if 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, 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 len(sales_persons_details): + sales_persons = set(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_details_map, filter_date) + + def get_sales_persons_list(self, sales_persons): + sales_persons_list = sales_persons + if self.department or self.designation or self.branch: + 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}, fieldname=["company", "department", "designation", "branch"], 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.set("contributions", []) + self.add_contributions(doc, sales_persons_details_map[record], filter_date) + 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() + + def add_contributions(self, doc, records, filter_date): + for items in records: + sales_record_details = frappe.db.get_value(self.commission_based_on, filters={"name": items["parent"]}, fieldname=["customer", filter_date], as_dict=True) + contribution = { + "document_type": self.commission_based_on, + "order_or_invoice": items["parent"], + "customer": sales_record_details["customer"], + "posting_date": sales_record_details[filter_date], + "contribution_percent": items["allocated_percentage"], + "contribution_amount": items["allocated_amount"], + "commission_rate": items["commission_rate"], + "commission_amount": items["incentives"], + } + doc.append("contributions", contribution) \ No newline at end of file diff --git a/erpnext/payroll/doctype/process_sales_commission/test_process_sales_commission.py b/erpnext/payroll/doctype/process_sales_commission/test_process_sales_commission.py new file mode 100644 index 00000000000..8ef2dc9aa2d --- /dev/null +++ b/erpnext/payroll/doctype/process_sales_commission/test_process_sales_commission.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestProcessSalesCommission(unittest.TestCase): + pass diff --git a/erpnext/payroll/doctype/sales_commission/__init__.py b/erpnext/payroll/doctype/sales_commission/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/payroll/doctype/sales_commission/sales_commission.js b/erpnext/payroll/doctype/sales_commission/sales_commission.js new file mode 100644 index 00000000000..3cfa8d2a951 --- /dev/null +++ b/erpnext/payroll/doctype/sales_commission/sales_commission.js @@ -0,0 +1,118 @@ +// 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); + } + }, + + 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) { + if (!frm.doc.employee) { + frappe.throw(__("No employee is linked to Sales Person {0}. Please select an employee for {1} to process this Commission.").format(frappe.bold(frm.doc.sales_person), get_link_to_form("Sales Person", frm.doc.sales_person))) + } + 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); + } + } + ); +}; \ No newline at end of file diff --git a/erpnext/payroll/doctype/sales_commission/sales_commission.json b/erpnext/payroll/doctype/sales_commission/sales_commission.json new file mode 100644 index 00000000000..f2437320ff1 --- /dev/null +++ b/erpnext/payroll/doctype/sales_commission/sales_commission.json @@ -0,0 +1,263 @@ +{ + "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", + "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", + "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", + "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" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-09-20 15:46:26.805073", + "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 +} \ No newline at end of file diff --git a/erpnext/payroll/doctype/sales_commission/sales_commission.py b/erpnext/payroll/doctype/sales_commission/sales_commission.py new file mode 100644 index 00000000000..4187f816a4a --- /dev/null +++ b/erpnext/payroll/doctype/sales_commission/sales_commission.py @@ -0,0 +1,103 @@ +# 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_salary_component(self): + if self.pay_via_salary: + if 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 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 + + def on_submit(self): + if not self.employee: + frappe.throw(_("No employee is linked to Sales Person: {0}. Please select an employee for {1} to submit this document.").format(frappe.bold(self.sales_person), get_link_to_form("Sales Person", self.sales_person))) + if self.pay_via_salary: + self.make_additional_salary() + else: + self.make_payment_entry() + + @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): + doc = frappe.new_doc("Additional Salary") + doc.employee = self.employee + doc.company = self.company + 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) + + 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) + + def add_references(self, doc): + reference = {} + reference['reference_doctype'] = "Sales Commission" + reference['reference_name'] = self.name + reference['due_date'] = self.to_date + reference['total_amount'] = self.total_commission_amount + reference['outstanding_amount'] = self.total_commission_amount + reference['allocated_amount'] = self.total_commission_amount + doc.append("references", reference) \ No newline at end of file diff --git a/erpnext/payroll/doctype/sales_commission/test_sales_commission.py b/erpnext/payroll/doctype/sales_commission/test_sales_commission.py new file mode 100644 index 00000000000..8cc43529532 --- /dev/null +++ b/erpnext/payroll/doctype/sales_commission/test_sales_commission.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestSalesCommission(unittest.TestCase): + pass diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index c27f1ea81ad..f9895d8826b 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -32,7 +32,8 @@ "column_break_5", "allow_multiple_items", "allow_against_multiple_purchase_orders", - "hide_tax_id" + "hide_tax_id", + "approval_required_for_sales_commission_payout" ], "fields": [ { @@ -197,6 +198,12 @@ "fieldname": "editable_bundle_item_rates", "fieldtype": "Check", "label": "Calculate Product Bundle Price based on Child Items' Rates" + }, + { + "default": "0", + "fieldname": "approval_required_for_sales_commission_payout", + "fieldtype": "Check", + "label": "Approval Required for Sales Commission Payout" } ], "icon": "fa fa-cog", @@ -204,7 +211,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-09-08 19:38:10.175989", + "modified": "2021-09-20 15:43:29.319488", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings",