feat: Payroll based on employee cost center (#21609)

diff --git a/erpnext/hr/doctype/department/department.json b/erpnext/hr/doctype/department/department.json
index 6469f4c..a54c1d1 100644
--- a/erpnext/hr/doctype/department/department.json
+++ b/erpnext/hr/doctype/department/department.json
@@ -14,6 +14,8 @@
   "is_group",
   "disabled",
   "section_break_4",
+  "payroll_cost_center",
+  "column_break_9",
   "leave_block_list",
   "leave_section",
   "leave_approvers",
@@ -125,13 +127,23 @@
   {
    "fieldname": "column_break_3",
    "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "payroll_cost_center",
+   "fieldtype": "Link",
+   "label": "Payroll Cost Center",
+   "options": "Cost Center"
+  },
+  {
+   "fieldname": "column_break_9",
+   "fieldtype": "Column Break"
   }
  ],
  "icon": "fa fa-sitemap",
  "idx": 1,
  "is_tree": 1,
  "links": [],
- "modified": "2020-03-18 18:03:27.784362",
+ "modified": "2020-05-05 18:49:28.503931",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Department",
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index 13c202c..f575765 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -60,6 +60,8 @@
   "default_shift",
   "salary_information",
   "salary_mode",
+  "payroll_cost_center",
+  "column_break_52",
   "bank_name",
   "bank_ac_no",
   "health_insurance_section",
@@ -783,13 +785,25 @@
   {
    "fieldname": "column_break_19",
    "fieldtype": "Column Break"
+  },
+  {
+   "fetch_from": "department.payroll_cost_center",
+   "fetch_if_empty": 1,
+   "fieldname": "payroll_cost_center",
+   "fieldtype": "Link",
+   "label": "Payroll Cost Center",
+   "options": "Cost Center"
+  },
+  {
+   "fieldname": "column_break_52",
+   "fieldtype": "Column Break"
   }
  ],
  "icon": "fa fa-user",
  "idx": 24,
  "image_field": "image",
  "links": [],
- "modified": "2020-04-08 12:25:34.306695",
+ "modified": "2020-05-05 18:51:03.152503",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee",
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index d3410de..f4b214a 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -45,7 +45,7 @@
 		employee1_doc.status = 'Left'
 		self.assertRaises(EmployeeLeftValidationError, employee1_doc.save)
 
-def make_employee(user, company=None):
+def make_employee(user, company=None, **kwargs):
 	if not frappe.db.get_value("User", user):
 		frappe.get_doc({
 			"doctype": "User",
@@ -55,7 +55,7 @@
 			"roles": [{"doctype": "Has Role", "role": "Employee"}]
 		}).insert()
 
-	if not frappe.db.get_value("Employee", { "user_id": user, "company": company or erpnext.get_default_company() }):
+	if not frappe.db.get_value("Employee", {"user_id": user}):
 		employee = frappe.get_doc({
 			"doctype": "Employee",
 			"naming_series": "EMP-",
@@ -71,7 +71,10 @@
 			"prefered_email": user,
 			"status": "Active",
 			"employment_type": "Intern"
-		}).insert()
+		})
+		if kwargs:
+			employee.update(kwargs)
+		employee.insert()
 		return employee.name
 	else:
 		return frappe.get_value("Employee", {"employee_name":user}, "name")
diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py
index 9ef3a99..656de01 100644
--- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py
@@ -55,6 +55,7 @@
 					ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
 					{condition}""".format(condition=condition),
 				{"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
+		
 		if sal_struct:
 			cond += "and t2.salary_structure IN %(sal_struct)s "
 			cond += "and %(from_date)s >= t2.from_date"
@@ -138,7 +139,7 @@
 		cond = self.get_filter_condition()
 
 		ss_list = frappe.db.sql("""
-			select t1.name, t1.salary_structure from `tabSalary Slip` t1
+			select t1.name, t1.salary_structure, t1.payroll_cost_center from `tabSalary Slip` t1
 			where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s
 			and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s %s
 		""" % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
@@ -169,10 +170,14 @@
 
 	def get_salary_components(self, component_type):
 		salary_slips = self.get_sal_slip_list(ss_status = 1, as_dict = True)
-		if salary_slips:
-			salary_components = frappe.db.sql("""select salary_component, amount, parentfield
-				from `tabSalary Detail` where parentfield = '%s' and parent in (%s)""" %
-				(component_type, ', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=True)
+		if salary_slips:			
+			salary_components = frappe.db.sql("""
+				select ssd.salary_component, ssd.amount, ssd.parentfield, ss.payroll_cost_center
+				from `tabSalary Slip` ss, `tabSalary Detail` ssd
+				where ss.name = ssd.parent and ssd.parentfield = '%s' and ss.name in (%s)
+			""" % (component_type, ', '.join(['%s']*len(salary_slips))),
+				tuple([d.name for d in salary_slips]), as_dict=True)
+
 			return salary_components
 
 	def get_salary_component_total(self, component_type = None):
@@ -186,15 +191,16 @@
 					if is_flexible_benefit == 1 and only_tax_impact ==1:
 						add_component_to_accrual_jv_entry = False
 				if add_component_to_accrual_jv_entry:
-					component_dict[item['salary_component']] = component_dict.get(item['salary_component'], 0) + item['amount']
+					component_dict[(item.salary_component, item.payroll_cost_center)] \
+						= component_dict.get((item.salary_component, item.payroll_cost_center), 0) + flt(item.amount)
 			account_details = self.get_account(component_dict = component_dict)
 			return account_details
 
 	def get_account(self, component_dict = None):
-		account_dict = {}
-		for s, a in component_dict.items():
-			account = self.get_salary_component_account(s)
-			account_dict[account] = account_dict.get(account, 0) + a
+		account_dict = {}		
+		for key, amount in component_dict.items():
+			account = self.get_salary_component_account(key[0])
+			account_dict[(account, key[1])] = account_dict.get((account, key[1]), 0) + amount
 		return account_dict
 
 	def get_default_payroll_payable_account(self):
@@ -227,23 +233,23 @@
 			payable_amount = 0
 
 			# Earnings
-			for acc, amount in earnings.items():
+			for acc_cc, amount in earnings.items():
 				payable_amount += flt(amount, precision)
 				accounts.append({
-						"account": acc,
+						"account": acc_cc[0],
 						"debit_in_account_currency": flt(amount, precision),
 						"party_type": '',
-						"cost_center": self.cost_center,
+						"cost_center": acc_cc[1] or self.cost_center,
 						"project": self.project
 					})
 
 			# Deductions
-			for acc, amount in deductions.items():
+			for acc_cc, amount in deductions.items():
 				payable_amount -= flt(amount, precision)
 				accounts.append({
-						"account": acc,
+						"account": acc_cc[0],
 						"credit_in_account_currency": flt(amount, precision),
-						"cost_center": self.cost_center,
+						"cost_center": acc_cc[1] or self.cost_center,
 						"party_type": '',
 						"project": self.project
 					})
@@ -253,6 +259,7 @@
 				"account": default_payroll_payable_account,
 				"credit_in_account_currency": flt(payable_amount, precision),
 				"party_type": '',
+				"cost_center": self.cost_center
 			})
 
 			journal_entry.set("accounts", accounts)
diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py
index e43f744..3c318e7 100644
--- a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py
@@ -10,15 +10,16 @@
 from erpnext.hr.doctype.payroll_entry.payroll_entry import get_start_end_dates, get_end_date
 from erpnext.hr.doctype.employee.test_employee import make_employee
 from erpnext.hr.doctype.salary_slip.test_salary_slip import get_salary_component_account, \
-		make_earning_salary_component, make_deduction_salary_component
+		make_earning_salary_component, make_deduction_salary_component, create_account
 from erpnext.hr.doctype.salary_structure.test_salary_structure import make_salary_structure
 from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry
 from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
 
 class TestPayrollEntry(unittest.TestCase):
 	def setUp(self):
-		for dt in ["Salary Slip", "Salary Component", "Salary Component Account", "Payroll Entry", "Salary Structure"]:
-			frappe.db.sql("delete from `tab%s`" % dt)
+		for dt in ["Salary Slip", "Salary Component", "Salary Component Account",
+			"Payroll Entry", "Salary Structure", "Salary Structure Assignment", "Payroll Employee Detail", "Additional Salary"]:
+				frappe.db.sql("delete from `tab%s`" % dt)
 
 		make_earning_salary_component(setup=True, company_list=["_Test Company"])
 		make_deduction_salary_component(setup=True, company_list=["_Test Company"])
@@ -33,11 +34,59 @@
 				get_salary_component_account(data.name)
 
 		employee = frappe.db.get_value("Employee", {'company': company})
-		make_salary_structure("_Test Salary Structure", "Monthly", employee)
+		make_salary_structure("_Test Salary Structure", "Monthly", employee, company=company)
 		dates = get_start_end_dates('Monthly', nowdate())
 		if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
 			make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date)
 
+	def test_payroll_entry_with_employee_cost_center(self): # pylint: disable=no-self-use
+		for data in frappe.get_all('Salary Component', fields = ["name"]):
+			if not frappe.db.get_value('Salary Component Account',
+				{'parent': data.name, 'company': "_Test Company"}, 'name'):
+				get_salary_component_account(data.name)
+
+		if not frappe.db.exists('Department', "cc - _TC"):
+			frappe.get_doc({
+				'doctype': 'Department',
+				'department_name': "cc",
+				"company": "_Test Company"
+			}).insert()
+
+		employee1 = make_employee("test_employee1@example.com", payroll_cost_center="_Test Cost Center - _TC",
+			department="cc - _TC", company="_Test Company")
+		employee2 = make_employee("test_employee2@example.com", payroll_cost_center="_Test Cost Center 2 - _TC",
+			department="cc - _TC", company="_Test Company")
+
+		make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company")
+		make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company")
+
+		if not frappe.db.exists("Account", "_Test Payroll Payable - _TC"):
+			create_account(account_name="_Test Payroll Payable",
+				company="_Test Company", parent_account="Current Liabilities - _TC")
+			frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account",
+				"_Test Payroll Payable - _TC")
+
+		dates = get_start_end_dates('Monthly', nowdate())
+		if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
+			pe = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
+				department="cc - _TC", company="_Test Company", payment_account="Cash - _TC", cost_center="Main - _TC")
+			je = frappe.db.get_value("Salary Slip", {"payroll_entry": pe.name}, "journal_entry")
+			je_entries = frappe.db.sql("""
+				select account, cost_center, debit, credit
+				from `tabJournal Entry Account`
+				where parent=%s
+				order by account, cost_center
+			""", je)
+			expected_je = (
+				('_Test Payroll Payable - _TC', 'Main - _TC', 0.0, 155600.0),
+				('Salary - _TC', '_Test Cost Center - _TC', 78000.0, 0.0),
+				('Salary - _TC', '_Test Cost Center 2 - _TC', 78000.0, 0.0),
+				('Salary Deductions - _TC', '_Test Cost Center - _TC', 0.0, 200.0),
+				('Salary Deductions - _TC', '_Test Cost Center 2 - _TC', 0.0, 200.0)
+			)
+
+			self.assertEqual(je_entries, expected_je)
+
 	def test_get_end_date(self):
 		self.assertEqual(get_end_date('2017-01-01', 'monthly'), {'end_date': '2017-01-31'})
 		self.assertEqual(get_end_date('2017-02-01', 'monthly'), {'end_date': '2017-02-28'})
@@ -49,7 +98,6 @@
 		self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'})
 
 	def test_loan(self):
-
 		branch = "Test Employee Branch"
 		applicant = make_employee("test_employee@loan.com", company="_Test Company")
 		company = "_Test Company"
@@ -116,6 +164,7 @@
 	payroll_entry.posting_date = nowdate()
 	payroll_entry.payroll_frequency = "Monthly"
 	payroll_entry.branch = args.branch or None
+	payroll_entry.department = args.department or None
 
 	if args.cost_center:
 		payroll_entry.cost_center = args.cost_center
@@ -123,6 +172,7 @@
 	if args.payment_account:
 		payroll_entry.payment_account = args.payment_account
 
+	payroll_entry.fill_employee_details()
 	payroll_entry.save()
 	payroll_entry.create_salary_slips()
 	payroll_entry.submit_salary_slips()
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.json b/erpnext/hr/doctype/salary_slip/salary_slip.json
index 54a8164..cfd4d89 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.json
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.json
@@ -12,6 +12,7 @@
   "department",
   "designation",
   "branch",
+  "payroll_cost_center",
   "column_break1",
   "status",
   "journal_entry",
@@ -459,13 +460,22 @@
    "options": "Salary Slip",
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "fetch_from": "employee.payroll_cost_center",
+   "fetch_if_empty": 1,
+   "fieldname": "payroll_cost_center",
+   "fieldtype": "Link",
+   "label": "Payroll Cost Center",
+   "options": "Cost Center",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 9,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-04-14 20:02:53.159827", 
+ "modified": "2020-05-05 18:55:26.173629",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Salary Slip",
diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
index a7dcb94..3eff738 100644
--- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
@@ -422,22 +422,32 @@
 	sal_comp = frappe.get_doc("Salary Component", sal_comp)
 	if not sal_comp.get("accounts"):
 		for d in company_list:
+			company_abbr = frappe.get_cached_value('Company', d, '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": d,
-				"default_account": create_account(d)
+				"default_account": create_account(account_name, d, parent_account)
 			})
 			sal_comp.save()
 
-def create_account(company):
-	salary_account = frappe.db.get_value("Account", "Salary - " + frappe.get_cached_value('Company',  company,  'abbr'))
-	if not salary_account:
+def create_account(account_name, company, 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": "Salary",
-			"parent_account": "Indirect Expenses - " + frappe.get_cached_value('Company',  company,  'abbr'),
+			"account_name": account_name,
+			"parent_account": parent_account,
 			"company": company
 		}).insert()
-	return salary_account
+	return account
 
 def make_earning_salary_component(setup=False, test_tax=False, company_list=None):
 	data = [
@@ -683,7 +693,7 @@
 	make_earning_salary_component(setup=True, company_list=["_Test Company"])
 	make_deduction_salary_component(setup=True, company_list=["_Test Company"])
 
-	for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Attendance"]:
+	for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Attendance", "Additional Salary"]:
 		frappe.db.sql("delete from `tab%s`" % dt)
 
 	make_holiday_list()
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.py b/erpnext/hr/doctype/salary_structure/salary_structure.py
index 5ba7f1c..ffc16d7 100644
--- a/erpnext/hr/doctype/salary_structure/salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.py
@@ -153,12 +153,16 @@
 	def postprocess(source, target):
 		if employee:
 			employee_details = frappe.db.get_value("Employee", employee,
-				["employee_name", "branch", "designation", "department"], as_dict=1)
+				["employee_name", "branch", "designation", "department", "payroll_cost_center"], as_dict=1)
 			target.employee = employee
 			target.employee_name = employee_details.employee_name
 			target.branch = employee_details.branch
 			target.designation = employee_details.designation
 			target.department = employee_details.department
+			target.payroll_cost_center = employee_details.payroll_cost_center
+			if not target.payroll_cost_center and target.department:
+				target.payroll_cost_center = frappe.db.get_value("Department", target.department, "payroll_cost_center")
+
 		target.run_method('process_salary_structure', for_preview=for_preview)
 
 	doc = get_mapped_doc("Salary Structure", source_name, {
diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
index c1869f0..eb5311e 100644
--- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
@@ -128,6 +128,7 @@
 		salary_structure_doc.insert()
 		if not dont_submit:
 			salary_structure_doc.submit()
+		
 	else:
 		salary_structure_doc = frappe.get_doc("Salary Structure", salary_structure)