From f1790d2759340aa43fc87236fdc0db2bfa92f2f1 Mon Sep 17 00:00:00 2001 From: fproldan Date: Thu, 3 Oct 2024 14:46:48 +0000 Subject: [PATCH] feat: sales commission --- .../sales_commission/sales_commission.json | 15 +-- .../sales_commission/sales_commission.py | 111 ++++++++++++++---- 2 files changed, 93 insertions(+), 33 deletions(-) diff --git a/erpnext/payroll/doctype/sales_commission/sales_commission.json b/erpnext/payroll/doctype/sales_commission/sales_commission.json index 1239e53d5c1f..b0b271faa517 100644 --- a/erpnext/payroll/doctype/sales_commission/sales_commission.json +++ b/erpnext/payroll/doctype/sales_commission/sales_commission.json @@ -20,7 +20,7 @@ "commission_against", "omit_sales_person_transactions", "column_break_14", - "filtro", + "commission_against_filter", "section_break_10", "from_date", "to_date", @@ -245,24 +245,25 @@ "fieldname": "commission_against", "fieldtype": "Link", "label": "Comisionar Sobre", + "mandatory_depends_on": "eval:doc.omit_sales_person_transactions == 1;", "options": "DocType" }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, { "depends_on": "eval:doc.omit_sales_person_transactions == 1;", - "fieldname": "filtro", + "fieldname": "commission_against_filter", "fieldtype": "Dynamic Link", "label": "Filtro", "options": "commission_against" - }, - { - "fieldname": "column_break_14", - "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-09-30 13:48:21.649556", + "modified": "2024-09-30 14:33:15.176817", "modified_by": "Administrator", "module": "Payroll", "name": "Sales Commission", diff --git a/erpnext/payroll/doctype/sales_commission/sales_commission.py b/erpnext/payroll/doctype/sales_commission/sales_commission.py index eea0253c2fae..9bae6bd4fba5 100644 --- a/erpnext/payroll/doctype/sales_commission/sales_commission.py +++ b/erpnext/payroll/doctype/sales_commission/sales_commission.py @@ -4,7 +4,7 @@ import frappe import erpnext from frappe import _ -from frappe.utils import get_link_to_form +from frappe.utils import get_link_to_form, flt, nowdate from erpnext.accounts.general_ledger import make_gl_entries from erpnext.controllers.accounts_controller import ( AccountsController, @@ -13,7 +13,7 @@ class SalesCommission(AccountsController): def validate(self): self.validate_from_to_dates() - + self.validate_omit_transaction_duplicates() self.validate_salary_component() self.calculate_total_contribution_and_total_commission_amount() self.create_employee() @@ -21,6 +21,24 @@ def validate(self): def validate_from_to_dates(self): return super().validate_from_to_dates("from_date", "to_date") + def validate_omit_transaction_duplicates(self): + # TODO chequear por solapamiento de fechas + if not self.omit_sales_person_transactions: + return + + previous_contibutions = frappe.get_all( + "Sales Commission", + filters={ + "sales_person": self.sales_person, + "docstatus": 1, + "from_date": self.from_date, + "to_date": self.to_date, + }, + pluck="name" + ) + if previous_contibutions: + frappe.throw(_("Este vendedor ya tiene liquidado ventas en el período seleccionado")) + def validate_amount(self): if self.total_commission_amount <= 0: frappe.throw(_("Total Commission Amount should be greater than 0")) @@ -46,15 +64,15 @@ def make_gl_entries(self, cancel=False): def get_gl_entries(self): gl_entry = [] - sales_commission_expense_account = frappe.get_value("Company", self.company, "sales_commission_expense_account") # DEBIT - payment_account_sales_commission = frappe.get_value("Company", self.company, "payment_account_sales_commission") # CREDIT + sales_commission_expense_account = frappe.get_value("Company", self.company, "sales_commission_expense_account") + payment_account_sales_commission = frappe.get_value("Company", self.company, "payment_account_sales_commission") if not sales_commission_expense_account or not payment_account_sales_commission: frappe.throw(_("Debe configurar la Cuenta de Pago Comisión Vendedores y la Cuenta de Gastos Comisión Vendedores en la Compañia.")) gl_entry.append( self.get_gl_dict({ - "posting_date": self.creation.date(), + "posting_date": nowdate(), "account": payment_account_sales_commission, "credit": self.total_commission_amount, "credit_in_account_currency": self.total_commission_amount, @@ -69,7 +87,7 @@ def get_gl_entries(self): gl_entry.append( self.get_gl_dict({ - "posting_date": self.creation.date(), + "posting_date": nowdate(), "account": sales_commission_expense_account, "debit": self.total_commission_amount, "debit_in_account_currency": self.total_commission_amount, @@ -86,28 +104,69 @@ def add_contributions(self, process_sales_commission): self.set("contributions", []) filter_date = "transaction_date" if self.commission_based_on == "Sales Order" else "posting_date" customer_field = "customer" if self.commission_based_on != "Payment Entry" else "party" - 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_field, filter_date], as_dict=True) - contribution = { - "document_type": self.commission_based_on, - "order_or_invoice": record["parent"], - "customer": record_details.get(customer_field), - "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"], - "process_sales_commission": process_sales_commission, - } - self.append("contributions", contribution) + + if self.omit_sales_person_transactions: + filter_fieldname = self.get_filter_commission_against_field() + filter_value = self.commission_against_filter + commission_rate = frappe.get_value("Sales Person", self.sales_person, "commission_rate") + filter_value_sql = f"AND {filter_fieldname} = '{filter_value}'" if filter_fieldname and filter_value else "" + records = frappe.db.sql(f""" + SELECT {filter_fieldname} AS commission_filter, SUM(total) AS allocated_amount + FROM `tab{self.commission_based_on}` + WHERE docstatus = 1 + AND company = '{self.company}' + AND {filter_date} BETWEEN '{self.from_date}' AND '{self.to_date}' + {filter_value_sql} + GROUP BY {filter_fieldname} + """, as_dict=True) + + commission_rate = frappe.get_value("Sales Person", self.sales_person, "commission_rate") + + for record in records: + record['commission_rate'] = commission_rate + record['incentives'] = flt(record['allocated_amount'] * flt(commission_rate) / 100.0, self.precision("total_contribution")) + record['allocated_percentage'] = 100 + contribution = { + "commission_filter": record["commission_filter"], + "contribution_percent": record["allocated_percentage"], + "contribution_amount": record["allocated_amount"], + "commission_rate": record["commission_rate"], + "commission_amount": record["incentives"], + "process_sales_commission": process_sales_commission, + } + self.append("contributions", contribution) + else: + 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_field, filter_date], as_dict=True) + contribution = { + "document_type": self.commission_based_on, + "order_or_invoice": record["parent"], + "customer": record_details.get(customer_field), + "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"], + "process_sales_commission": process_sales_commission, + } + self.append("contributions", contribution) self.calculate_total_contribution_and_total_commission_amount() + def get_filter_commission_against_field(self): + commission_based_on_meta = frappe.get_meta(self.commission_based_on) + for field in commission_based_on_meta.fields: + if field.fieldtype == "Link" and field.options == self.commission_against: + return field.fieldname + frappe.throw(f"{self.commission_based_on} no tiene campo {self.commission_against}") + def calculate_total_contribution_and_total_commission_amount(self): total_contribution, total_commission_amount = 0, 0 for entry in self.contributions: