Merge branch 'develop' of https://github.com/frappe/erpnext into accounting_dimension_filters
This commit is contained in:
		
						commit
						133ce286f9
					
				| @ -1885,8 +1885,8 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 			"item_code": "_Test Item", | ||||
| 			"uom": "Nos", | ||||
| 			"warehouse": "_Test Warehouse - _TC", | ||||
| 			"qty": 2, | ||||
| 			"rate": 100, | ||||
| 			"qty": 2000, | ||||
| 			"rate": 12, | ||||
| 			"income_account": "Sales - _TC", | ||||
| 			"expense_account": "Cost of Goods Sold - _TC", | ||||
| 			"cost_center": "_Test Cost Center - _TC", | ||||
| @ -1895,31 +1895,52 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 			"item_code": "_Test Item 2", | ||||
| 			"uom": "Nos", | ||||
| 			"warehouse": "_Test Warehouse - _TC", | ||||
| 			"qty": 4, | ||||
| 			"rate": 150, | ||||
| 			"qty": 420, | ||||
| 			"rate": 15, | ||||
| 			"income_account": "Sales - _TC", | ||||
| 			"expense_account": "Cost of Goods Sold - _TC", | ||||
| 			"cost_center": "_Test Cost Center - _TC", | ||||
| 		}) | ||||
| 		si.discount_amount = 100 | ||||
| 		si.save() | ||||
| 
 | ||||
| 		einvoice = make_einvoice(si) | ||||
| 
 | ||||
| 		total_item_ass_value = sum([d['AssAmt'] for d in einvoice['ItemList']]) | ||||
| 		total_item_cgst_value = sum([d['CgstAmt'] for d in einvoice['ItemList']]) | ||||
| 		total_item_sgst_value = sum([d['SgstAmt'] for d in einvoice['ItemList']]) | ||||
| 		total_item_igst_value = sum([d['IgstAmt'] for d in einvoice['ItemList']]) | ||||
| 		total_item_value = sum([d['TotItemVal'] for d in einvoice['ItemList']]) | ||||
| 		total_item_ass_value = 0 | ||||
| 		total_item_cgst_value = 0 | ||||
| 		total_item_sgst_value = 0 | ||||
| 		total_item_igst_value = 0 | ||||
| 		total_item_value = 0 | ||||
| 
 | ||||
| 		for item in einvoice['ItemList']: | ||||
| 			total_item_ass_value += item['AssAmt'] | ||||
| 			total_item_cgst_value += item['CgstAmt'] | ||||
| 			total_item_sgst_value += item['SgstAmt'] | ||||
| 			total_item_igst_value += item['IgstAmt'] | ||||
| 			total_item_value += item['TotItemVal'] | ||||
| 
 | ||||
| 			self.assertTrue(item['AssAmt'], item['TotAmt'] - item['Discount']) | ||||
| 			self.assertTrue(item['TotItemVal'], item['AssAmt'] + item['CgstAmt'] + item['SgstAmt'] + item['IgstAmt']) | ||||
| 
 | ||||
| 		value_details = einvoice['ValDtls'] | ||||
| 
 | ||||
| 		self.assertEqual(einvoice['Version'], '1.1') | ||||
| 		self.assertEqual(einvoice['ValDtls']['AssVal'], total_item_ass_value) | ||||
| 		self.assertEqual(einvoice['ValDtls']['CgstVal'], total_item_cgst_value) | ||||
| 		self.assertEqual(einvoice['ValDtls']['SgstVal'], total_item_sgst_value) | ||||
| 		self.assertEqual(einvoice['ValDtls']['IgstVal'], total_item_igst_value) | ||||
| 		self.assertEqual(einvoice['ValDtls']['TotInvVal'], total_item_value) | ||||
| 		self.assertEqual(value_details['AssVal'], total_item_ass_value) | ||||
| 		self.assertEqual(value_details['CgstVal'], total_item_cgst_value) | ||||
| 		self.assertEqual(value_details['SgstVal'], total_item_sgst_value) | ||||
| 		self.assertEqual(value_details['IgstVal'], total_item_igst_value) | ||||
| 
 | ||||
| 		self.assertEqual( | ||||
| 			value_details['TotInvVal'], | ||||
| 			value_details['AssVal'] + value_details['CgstVal'] | ||||
| 			+ value_details['SgstVal'] + value_details['IgstVal'] | ||||
| 			+ value_details['OthChrg'] - value_details['Discount'] | ||||
| 		) | ||||
| 
 | ||||
| 		self.assertEqual(value_details['TotInvVal'], si.base_grand_total) | ||||
| 		self.assertTrue(einvoice['EwbDtls']) | ||||
| 
 | ||||
| def make_sales_invoice_for_ewaybill(): | ||||
| def make_test_address_for_ewaybill(): | ||||
| 	if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): | ||||
| 		address = frappe.get_doc({ | ||||
| 			"address_line1": "_Test Address Line 1", | ||||
| @ -1967,7 +1988,8 @@ def make_sales_invoice_for_ewaybill(): | ||||
| 		}) | ||||
| 
 | ||||
| 		address.save() | ||||
| 	 | ||||
| 
 | ||||
| def make_test_transporter_for_ewaybill(): | ||||
| 	if not frappe.db.exists('Supplier', '_Test Transporter'): | ||||
| 		frappe.get_doc({ | ||||
| 			"doctype": "Supplier", | ||||
| @ -1978,12 +2000,17 @@ def make_sales_invoice_for_ewaybill(): | ||||
| 			"is_transporter": 1 | ||||
| 		}).insert() | ||||
| 
 | ||||
| def make_sales_invoice_for_ewaybill(): | ||||
| 	make_test_address_for_ewaybill() | ||||
| 	make_test_transporter_for_ewaybill() | ||||
| 
 | ||||
| 	gst_settings = frappe.get_doc("GST Settings") | ||||
| 
 | ||||
| 	gst_account = frappe.get_all( | ||||
| 		"GST Account", | ||||
| 		fields=["cgst_account", "sgst_account", "igst_account"], | ||||
| 		filters = {"company": "_Test Company"}) | ||||
| 		filters = {"company": "_Test Company"} | ||||
| 	) | ||||
| 
 | ||||
| 	if not gst_account: | ||||
| 		gst_settings.append("gst_accounts", { | ||||
| @ -1995,7 +2022,7 @@ def make_sales_invoice_for_ewaybill(): | ||||
| 
 | ||||
| 	gst_settings.save() | ||||
| 
 | ||||
| 	si = create_sales_invoice(do_not_save =1, rate = '60000') | ||||
| 	si = create_sales_invoice(do_not_save=1, rate='60000') | ||||
| 
 | ||||
| 	si.distance = 2000 | ||||
| 	si.company_address = "_Test Address for Eway bill-Billing" | ||||
|  | ||||
| @ -47,21 +47,22 @@ def get_data(filters): | ||||
| 
 | ||||
| 	for d in gl_entries: | ||||
| 		asset_data = assets_details.get(d.against_voucher) | ||||
| 		if not asset_data.get("accumulated_depreciation_amount"): | ||||
| 			asset_data.accumulated_depreciation_amount = d.debit | ||||
| 		else: | ||||
| 			asset_data.accumulated_depreciation_amount += d.debit | ||||
| 		if asset_data: | ||||
| 			if not asset_data.get("accumulated_depreciation_amount"): | ||||
| 				asset_data.accumulated_depreciation_amount = d.debit | ||||
| 			else: | ||||
| 				asset_data.accumulated_depreciation_amount += d.debit | ||||
| 
 | ||||
| 		row = frappe._dict(asset_data) | ||||
| 		row.update({ | ||||
| 			"depreciation_amount": d.debit, | ||||
| 			"depreciation_date": d.posting_date, | ||||
| 			"amount_after_depreciation": (flt(row.gross_purchase_amount) - | ||||
| 				flt(row.accumulated_depreciation_amount)), | ||||
| 			"depreciation_entry": d.voucher_no | ||||
| 		}) | ||||
| 			row = frappe._dict(asset_data) | ||||
| 			row.update({ | ||||
| 				"depreciation_amount": d.debit, | ||||
| 				"depreciation_date": d.posting_date, | ||||
| 				"amount_after_depreciation": (flt(row.gross_purchase_amount) - | ||||
| 					flt(row.accumulated_depreciation_amount)), | ||||
| 				"depreciation_entry": d.voucher_no | ||||
| 			}) | ||||
| 
 | ||||
| 		data.append(row) | ||||
| 			data.append(row) | ||||
| 
 | ||||
| 	return data | ||||
| 
 | ||||
|  | ||||
| @ -6,6 +6,7 @@ from __future__ import unicode_literals | ||||
| import frappe, math, json | ||||
| import erpnext | ||||
| from frappe import _ | ||||
| from six import string_types | ||||
| from frappe.utils import flt, rounded, add_months, nowdate, getdate, now_datetime | ||||
| from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty | ||||
| from erpnext.controllers.accounts_controller import AccountsController | ||||
| @ -280,10 +281,13 @@ def make_loan_write_off(loan, company=None, posting_date=None, amount=0, as_dict | ||||
| 		return write_off | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def unpledge_security(loan=None, loan_security_pledge=None, as_dict=0, save=0, submit=0, approve=0): | ||||
| 	# if loan is passed it will be considered as full unpledge | ||||
| def unpledge_security(loan=None, loan_security_pledge=None, security_map=None, as_dict=0, save=0, submit=0, approve=0): | ||||
| 	# if no security_map is passed it will be considered as full unpledge | ||||
| 	if security_map and isinstance(security_map, string_types): | ||||
| 		security_map = json.loads(security_map) | ||||
| 
 | ||||
| 	if loan: | ||||
| 		pledge_qty_map = get_pledged_security_qty(loan) | ||||
| 		pledge_qty_map = security_map or get_pledged_security_qty(loan) | ||||
| 		loan_doc = frappe.get_doc('Loan', loan) | ||||
| 		unpledge_request = create_loan_security_unpledge(pledge_qty_map, loan_doc.name, loan_doc.company, | ||||
| 			loan_doc.applicant_type, loan_doc.applicant) | ||||
|  | ||||
| @ -45,7 +45,7 @@ class TestLoan(unittest.TestCase): | ||||
| 		create_loan_security_price("Test Security 2", 250, "Nos", get_datetime() , get_datetime(add_to_date(nowdate(), hours=24))) | ||||
| 
 | ||||
| 		self.applicant1 = make_employee("robert_loan@loan.com") | ||||
| 		make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant1, currency='INR') | ||||
| 		make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant1, currency='INR', company="_Test Company") | ||||
| 		if not frappe.db.exists("Customer", "_Test Loan Customer"): | ||||
| 			frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True) | ||||
| 
 | ||||
| @ -325,6 +325,43 @@ class TestLoan(unittest.TestCase): | ||||
| 		self.assertEquals(amounts['payable_principal_amount'], 0.0) | ||||
| 		self.assertEqual(amounts['interest_amount'], 0) | ||||
| 
 | ||||
| 	def test_partial_loan_security_unpledge(self): | ||||
| 		pledge = [{ | ||||
| 			"loan_security": "Test Security 1", | ||||
| 			"qty": 2000.00 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"loan_security": "Test Security 2", | ||||
| 			"qty": 4000.00 | ||||
| 		}] | ||||
| 
 | ||||
| 		loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) | ||||
| 		create_pledge(loan_application) | ||||
| 
 | ||||
| 		loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') | ||||
| 		loan.submit() | ||||
| 
 | ||||
| 		self.assertEquals(loan.loan_amount, 1000000) | ||||
| 
 | ||||
| 		first_date = '2019-10-01' | ||||
| 		last_date = '2019-10-30' | ||||
| 
 | ||||
| 		make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) | ||||
| 		process_loan_interest_accrual_for_demand_loans(posting_date = last_date) | ||||
| 
 | ||||
| 		repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), 600000) | ||||
| 		repayment_entry.submit() | ||||
| 
 | ||||
| 		unpledge_map = {'Test Security 2': 2000} | ||||
| 
 | ||||
| 		unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1) | ||||
| 		unpledge_request.submit() | ||||
| 		unpledge_request.status = 'Approved' | ||||
| 		unpledge_request.save() | ||||
| 		unpledge_request.submit() | ||||
| 		unpledge_request.load_from_db() | ||||
| 		self.assertEqual(unpledge_request.docstatus, 1) | ||||
| 
 | ||||
| 	def test_disbursal_check_with_shortfall(self): | ||||
| 		pledges = [{ | ||||
| 			"loan_security": "Test Security 2", | ||||
|  | ||||
| @ -81,7 +81,6 @@ def check_for_ltv_shortfall(process_loan_security_shortfall): | ||||
| 				process_loan_security_shortfall) | ||||
| 
 | ||||
| def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall): | ||||
| 
 | ||||
| 	existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name") | ||||
| 
 | ||||
| 	if existing_shortfall: | ||||
|  | ||||
| @ -30,6 +30,8 @@ class LoanSecurityUnpledge(Document): | ||||
| 					d.idx, frappe.bold(d.loan_security))) | ||||
| 
 | ||||
| 	def validate_unpledge_qty(self): | ||||
| 		from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import get_ltv_ratio | ||||
| 
 | ||||
| 		pledge_qty_map = get_pledged_security_qty(self.loan) | ||||
| 
 | ||||
| 		ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type", | ||||
| @ -47,6 +49,8 @@ class LoanSecurityUnpledge(Document): | ||||
| 
 | ||||
| 		pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount) | ||||
| 		security_value = 0 | ||||
| 		unpledge_qty_map = {} | ||||
| 		ltv_ratio = 0 | ||||
| 
 | ||||
| 		for security in self.securities: | ||||
| 			pledged_qty = pledge_qty_map.get(security.loan_security, 0) | ||||
| @ -57,13 +61,15 @@ class LoanSecurityUnpledge(Document): | ||||
| 				msg += _("You are trying to unpledge more.") | ||||
| 				frappe.throw(msg, title=_("Loan Security Unpledge Error")) | ||||
| 
 | ||||
| 			qty_after_unpledge = pledged_qty - security.qty | ||||
| 			ltv_ratio = ltv_ratio_map.get(security.loan_security_type) | ||||
| 			unpledge_qty_map.setdefault(security.loan_security, 0) | ||||
| 			unpledge_qty_map[security.loan_security] += security.qty | ||||
| 
 | ||||
| 			current_price = loan_security_price_map.get(security.loan_security) | ||||
| 			if not current_price: | ||||
| 				frappe.throw(_("No valid Loan Security Price found for {0}").format(frappe.bold(security.loan_security))) | ||||
| 		for security in pledge_qty_map: | ||||
| 			if not ltv_ratio: | ||||
| 				ltv_ratio = get_ltv_ratio(security) | ||||
| 
 | ||||
| 			qty_after_unpledge = pledge_qty_map.get(security, 0) - unpledge_qty_map.get(security, 0) | ||||
| 			current_price = loan_security_price_map.get(security) | ||||
| 			security_value += qty_after_unpledge * current_price | ||||
| 
 | ||||
| 		if not security_value and flt(pending_principal_amount, 2) > 0: | ||||
|  | ||||
| @ -711,6 +711,7 @@ erpnext.patches.v13_0.delete_old_sales_reports | ||||
| execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation") | ||||
| erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020 | ||||
| erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020 | ||||
| execute:frappe.reload_doc("regional", "doctype", "e_invoice_settings") | ||||
| erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020 | ||||
| erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020 | ||||
| erpnext.patches.v12_0.add_taxjar_integration_field | ||||
|  | ||||
| @ -8,6 +8,7 @@ def execute(): | ||||
| 	if not company: | ||||
| 		return | ||||
| 
 | ||||
| 	frappe.reload_doc("custom", "doctype", "custom_field") | ||||
| 	frappe.reload_doc("regional", "doctype", "e_invoice_settings") | ||||
| 	custom_fields = { | ||||
| 		'Sales Invoice': [ | ||||
|  | ||||
| @ -86,19 +86,21 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): | ||||
| 
 | ||||
| 		self.assertEqual(declaration.total_exemption_amount, 100000) | ||||
| 
 | ||||
| def create_payroll_period(): | ||||
| 	if not frappe.db.exists("Payroll Period", "_Test Payroll Period"): | ||||
| def create_payroll_period(**args): | ||||
| 	args = frappe._dict(args) | ||||
| 	name = args.name or "_Test Payroll Period" | ||||
| 	if not frappe.db.exists("Payroll Period", name): | ||||
| 		from datetime import date | ||||
| 		payroll_period = frappe.get_doc(dict( | ||||
| 			doctype = 'Payroll Period', | ||||
| 			name = "_Test Payroll Period", | ||||
| 			company =  erpnext.get_default_company(), | ||||
| 			start_date = date(date.today().year, 1, 1), | ||||
| 			end_date = date(date.today().year, 12, 31) | ||||
| 			name = name, | ||||
| 			company =  args.company or erpnext.get_default_company(), | ||||
| 			start_date = args.start_date or date(date.today().year, 1, 1), | ||||
| 			end_date = args.end_date or date(date.today().year, 12, 31) | ||||
| 		)).insert() | ||||
| 		return payroll_period | ||||
| 	else: | ||||
| 		return frappe.get_doc("Payroll Period", "_Test Payroll Period") | ||||
| 		return frappe.get_doc("Payroll Period", name) | ||||
| 
 | ||||
| def create_exemption_category(): | ||||
| 	if not frappe.db.exists("Employee Tax Exemption Category", "_Test Category"): | ||||
|  | ||||
| @ -3,8 +3,11 @@ | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| # import frappe | ||||
| #import frappe | ||||
| import erpnext | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class IncomeTaxSlab(Document): | ||||
| 	pass | ||||
| 	def validate(self): | ||||
| 		if self.company: | ||||
| 			self.currency = erpnext.get_company_currency(self.company) | ||||
|  | ||||
| @ -17,8 +17,7 @@ | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Employee", | ||||
|    "options": "Employee", | ||||
|    "read_only": 1 | ||||
|    "options": "Employee" | ||||
|   }, | ||||
|   { | ||||
|    "fetch_from": "employee.employee_name", | ||||
| @ -52,7 +51,7 @@ | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-09-30 12:40:07.999878", | ||||
|  "modified": "2020-12-17 15:43:29.542977", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Payroll", | ||||
|  "name": "Payroll Employee Detail", | ||||
|  | ||||
| @ -12,17 +12,23 @@ frappe.ui.form.on('Payroll Entry', { | ||||
| 		} | ||||
| 		frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet); | ||||
| 
 | ||||
| 		frm.set_query("department", function() { | ||||
| 		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); | ||||
| 		frm.events.department_filters(frm); | ||||
| 		frm.events.payroll_payable_account_filters(frm); | ||||
| 	}, | ||||
| 
 | ||||
| 	department_filters: function (frm) { | ||||
| 		frm.set_query("department", function () { | ||||
| 			return { | ||||
| 				"filters": { | ||||
| 					"company": frm.doc.company, | ||||
| 				} | ||||
| 			}; | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); | ||||
| 
 | ||||
| 		frm.set_query("payroll_payable_account", function() { | ||||
| 	payroll_payable_account_filters: function (frm) { | ||||
| 		frm.set_query("payroll_payable_account", function () { | ||||
| 			return { | ||||
| 				filters: { | ||||
| 					"company": frm.doc.company, | ||||
| @ -33,12 +39,12 @@ frappe.ui.form.on('Payroll Entry', { | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	refresh: function(frm) { | ||||
| 	refresh: function (frm) { | ||||
| 		if (frm.doc.docstatus == 0) { | ||||
| 			if(!frm.is_new()) { | ||||
| 			if (!frm.is_new()) { | ||||
| 				frm.page.clear_primary_action(); | ||||
| 				frm.add_custom_button(__("Get Employees"), | ||||
| 					function() { | ||||
| 					function () { | ||||
| 						frm.events.get_employee_details(frm); | ||||
| 					} | ||||
| 				).toggleClass('btn-primary', !(frm.doc.employees || []).length); | ||||
| @ -46,7 +52,7 @@ frappe.ui.form.on('Payroll Entry', { | ||||
| 			if ((frm.doc.employees || []).length) { | ||||
| 				frm.page.clear_primary_action(); | ||||
| 				frm.page.set_primary_action(__('Create Salary Slips'), () => { | ||||
| 					frm.save('Submit').then(()=>{ | ||||
| 					frm.save('Submit').then(() => { | ||||
| 						frm.page.clear_primary_action(); | ||||
| 						frm.refresh(); | ||||
| 						frm.events.refresh(frm); | ||||
| @ -65,48 +71,48 @@ frappe.ui.form.on('Payroll Entry', { | ||||
| 			doc: frm.doc, | ||||
| 			method: 'fill_employee_details', | ||||
| 		}).then(r => { | ||||
| 			if (r.docs && r.docs[0].employees){ | ||||
| 			if (r.docs && r.docs[0].employees) { | ||||
| 				frm.employees = r.docs[0].employees; | ||||
| 				frm.dirty(); | ||||
| 				frm.save(); | ||||
| 				frm.refresh(); | ||||
| 				if(r.docs[0].validate_attendance){ | ||||
| 				if (r.docs[0].validate_attendance) { | ||||
| 					render_employee_attendance(frm, r.message); | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	create_salary_slips: function(frm) { | ||||
| 	create_salary_slips: function (frm) { | ||||
| 		frm.call({ | ||||
| 			doc: frm.doc, | ||||
| 			method: "create_salary_slips", | ||||
| 			callback: function(r) { | ||||
| 			callback: function () { | ||||
| 				frm.refresh(); | ||||
| 				frm.toolbar.refresh(); | ||||
| 			} | ||||
| 		}) | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	add_context_buttons: function(frm) { | ||||
| 		if(frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) { | ||||
| 	add_context_buttons: function (frm) { | ||||
| 		if (frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) { | ||||
| 			frm.events.add_bank_entry_button(frm); | ||||
| 		} else if(frm.doc.salary_slips_created) { | ||||
| 			frm.add_custom_button(__("Submit Salary Slip"), function() { | ||||
| 		} else if (frm.doc.salary_slips_created) { | ||||
| 			frm.add_custom_button(__("Submit Salary Slip"), function () { | ||||
| 				submit_salary_slip(frm); | ||||
| 			}).addClass("btn-primary"); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	add_bank_entry_button: function(frm) { | ||||
| 	add_bank_entry_button: function (frm) { | ||||
| 		frappe.call({ | ||||
| 			method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.payroll_entry_has_bank_entries', | ||||
| 			args: { | ||||
| 				'name': frm.doc.name | ||||
| 			}, | ||||
| 			callback: function(r) { | ||||
| 			callback: function (r) { | ||||
| 				if (r.message && !r.message.submitted) { | ||||
| 					frm.add_custom_button("Make Bank Entry", function() { | ||||
| 					frm.add_custom_button("Make Bank Entry", function () { | ||||
| 						make_bank_entry(frm); | ||||
| 					}).addClass("btn-primary"); | ||||
| 				} | ||||
| @ -130,8 +136,37 @@ frappe.ui.form.on('Payroll Entry', { | ||||
| 	}, | ||||
| 
 | ||||
| 	payroll_frequency: function (frm) { | ||||
| 		frm.trigger("set_start_end_dates"); | ||||
| 		frm.events.clear_employee_table(frm); | ||||
| 		frm.trigger("set_start_end_dates").then( ()=> { | ||||
| 			frm.events.clear_employee_table(frm); | ||||
| 			frm.events.get_employee_with_salary_slip_and_set_query(frm); | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	employee_filters: function (frm, emp_list) { | ||||
| 		frm.set_query('employee', 'employees', () => { | ||||
| 			return { | ||||
| 				filters: { | ||||
| 					name: ["not in", emp_list] | ||||
| 				} | ||||
| 			}; | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	get_employee_with_salary_slip_and_set_query: function (frm) { | ||||
| 		frappe.db.get_list('Salary Slip', { | ||||
| 			filters: { | ||||
| 				start_date: frm.doc.start_date, | ||||
| 				end_date: frm.doc.end_date, | ||||
| 				docstatus: 1, | ||||
| 			}, | ||||
| 			fields: ['employee'] | ||||
| 		}).then((emp) => { | ||||
| 			var emp_list = []; | ||||
| 			emp.forEach((employee_data) => { | ||||
| 				emp_list.push(Object.values(employee_data)[0]); | ||||
| 			}); | ||||
| 			frm.events.employee_filters(frm, emp_list); | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	company: function (frm) { | ||||
| @ -154,17 +189,17 @@ frappe.ui.form.on('Payroll Entry', { | ||||
| 						from_currency: frm.doc.currency, | ||||
| 						to_currency: company_currency, | ||||
| 					}, | ||||
| 					callback: function(r) { | ||||
| 					callback: function (r) { | ||||
| 						frm.set_value("exchange_rate", flt(r.message)); | ||||
| 						frm.set_df_property('exchange_rate', 'hidden', 0); | ||||
| 						frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency | ||||
| 							+ " = [?] " + company_currency); | ||||
| 						frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency + | ||||
| 							" = [?] " + company_currency); | ||||
| 					} | ||||
| 				}); | ||||
| 			} else { | ||||
| 				frm.set_value("exchange_rate", 1.0); | ||||
| 				frm.set_df_property('exchange_rate', 'hidden', 1); | ||||
| 				frm.set_df_property("exchange_rate", "description", "" ); | ||||
| 				frm.set_df_property("exchange_rate", "description", ""); | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| @ -182,9 +217,9 @@ frappe.ui.form.on('Payroll Entry', { | ||||
| 	}, | ||||
| 
 | ||||
| 	start_date: function (frm) { | ||||
| 		if(!in_progress && frm.doc.start_date){ | ||||
| 		if (!in_progress && frm.doc.start_date) { | ||||
| 			frm.trigger("set_end_date"); | ||||
| 		}else{ | ||||
| 		} else { | ||||
| 			// reset flag
 | ||||
| 			in_progress = false; | ||||
| 		} | ||||
| @ -218,7 +253,7 @@ frappe.ui.form.on('Payroll Entry', { | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	set_end_date: function(frm){ | ||||
| 	set_end_date: function (frm) { | ||||
| 		frappe.call({ | ||||
| 			method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.get_end_date', | ||||
| 			args: { | ||||
| @ -233,19 +268,19 @@ frappe.ui.form.on('Payroll Entry', { | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	validate_attendance: function(frm){ | ||||
| 		if(frm.doc.validate_attendance && frm.doc.employees){ | ||||
| 	validate_attendance: function (frm) { | ||||
| 		if (frm.doc.validate_attendance && frm.doc.employees) { | ||||
| 			frappe.call({ | ||||
| 				method: 'validate_employee_attendance', | ||||
| 				args: {}, | ||||
| 				callback: function(r) { | ||||
| 				callback: function (r) { | ||||
| 					render_employee_attendance(frm, r.message); | ||||
| 				}, | ||||
| 				doc: frm.doc, | ||||
| 				freeze: true, | ||||
| 				freeze_message: __('Validating Employee Attendance...') | ||||
| 			}); | ||||
| 		}else{ | ||||
| 		} else { | ||||
| 			frm.fields_dict.attendance_detail_html.html(""); | ||||
| 		} | ||||
| 	}, | ||||
| @ -260,18 +295,20 @@ frappe.ui.form.on('Payroll Entry', { | ||||
| 
 | ||||
| const submit_salary_slip = function (frm) { | ||||
| 	frappe.confirm(__('This will submit Salary Slips and create accrual Journal Entry. Do you want to proceed?'), | ||||
| 		function() { | ||||
| 		function () { | ||||
| 			frappe.call({ | ||||
| 				method: 'submit_salary_slips', | ||||
| 				args: {}, | ||||
| 				callback: function() {frm.events.refresh(frm);}, | ||||
| 				callback: function () { | ||||
| 					frm.events.refresh(frm); | ||||
| 				}, | ||||
| 				doc: frm.doc, | ||||
| 				freeze: true, | ||||
| 				freeze_message: __('Submitting Salary Slips and creating Journal Entry...') | ||||
| 			}); | ||||
| 		}, | ||||
| 		function() { | ||||
| 			if(frappe.dom.freeze_count) { | ||||
| 		function () { | ||||
| 			if (frappe.dom.freeze_count) { | ||||
| 				frappe.dom.unfreeze(); | ||||
| 				frm.events.refresh(frm); | ||||
| 			} | ||||
| @ -285,9 +322,11 @@ let make_bank_entry = function (frm) { | ||||
| 		return frappe.call({ | ||||
| 			doc: cur_frm.doc, | ||||
| 			method: "make_payment_entry", | ||||
| 			callback: function() { | ||||
| 			callback: function () { | ||||
| 				frappe.set_route( | ||||
| 					'List', 'Journal Entry', {"Journal Entry Account.reference_name": frm.doc.name} | ||||
| 					'List', 'Journal Entry', { | ||||
| 						"Journal Entry Account.reference_name": frm.doc.name | ||||
| 					} | ||||
| 				); | ||||
| 			}, | ||||
| 			freeze: true, | ||||
| @ -299,11 +338,19 @@ let make_bank_entry = function (frm) { | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| let render_employee_attendance = function(frm, data) { | ||||
| let render_employee_attendance = function (frm, data) { | ||||
| 	frm.fields_dict.attendance_detail_html.html( | ||||
| 		frappe.render_template('employees_to_mark_attendance', { | ||||
| 			data: data | ||||
| 		}) | ||||
| 	); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| frappe.ui.form.on('Payroll Employee Detail', { | ||||
| 	employee: function(frm) { | ||||
| 		frm.events.clear_employee_table(frm); | ||||
| 		if (!frm.doc.payroll_frequency) { | ||||
| 			frappe.throw(__("Please set a Payroll Frequency")); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| @ -129,8 +129,7 @@ | ||||
|    "fieldname": "employees", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "Employee Details", | ||||
|    "options": "Payroll Employee Detail", | ||||
|    "read_only": 1 | ||||
|    "options": "Payroll Employee Detail" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_13", | ||||
| @ -290,7 +289,7 @@ | ||||
|  "icon": "fa fa-cog", | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-10-23 13:00:33.753228", | ||||
|  "modified": "2020-12-17 15:13:17.766210", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Payroll", | ||||
|  "name": "Payroll Entry", | ||||
|  | ||||
| @ -6,7 +6,7 @@ from __future__ import unicode_literals | ||||
| import frappe, erpnext | ||||
| from frappe.model.document import Document | ||||
| from dateutil.relativedelta import relativedelta | ||||
| from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT, date_diff | ||||
| from frappe.utils import cint, flt, add_days, getdate, add_to_date, DATE_FORMAT, date_diff, comma_and | ||||
| from frappe import _ | ||||
| from erpnext.accounts.utils import get_fiscal_year | ||||
| from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee | ||||
| @ -19,16 +19,26 @@ class PayrollEntry(Document): | ||||
| 		# check if salary slips were manually submitted | ||||
| 		entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name']) | ||||
| 		if cint(entries) == len(self.employees): | ||||
|     			self.set_onload("submitted_ss", True) | ||||
| 				self.set_onload("submitted_ss", True) | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		self.create_salary_slips() | ||||
| 
 | ||||
| 	def before_submit(self): | ||||
| 		self.validate_employee_details() | ||||
| 		if self.validate_attendance: | ||||
| 			if self.validate_employee_attendance(): | ||||
| 				frappe.throw(_("Cannot Submit, Employees left to mark attendance")) | ||||
| 
 | ||||
| 	def validate_employee_details(self): | ||||
| 		emp_with_sal_slip = [] | ||||
| 		for employee_details in self.employees: | ||||
| 			if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}): | ||||
| 				emp_with_sal_slip.append(employee_details.employee) | ||||
| 
 | ||||
| 		if len(emp_with_sal_slip): | ||||
| 			frappe.throw(_("Salary Slip already exists for {0} ").format(comma_and(emp_with_sal_slip))) | ||||
| 
 | ||||
| 	def on_cancel(self): | ||||
| 		frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip` | ||||
| 			where payroll_entry=%s """, (self.name))) | ||||
| @ -71,8 +81,17 @@ class PayrollEntry(Document): | ||||
| 					and t2.docstatus = 1 | ||||
| 			%s order by t2.from_date desc | ||||
| 			""" % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True) | ||||
| 
 | ||||
| 			emp_list = self.remove_payrolled_employees(emp_list) | ||||
| 			return emp_list | ||||
| 
 | ||||
| 	def remove_payrolled_employees(self, emp_list): | ||||
| 		for employee_details in emp_list: | ||||
| 			if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}): | ||||
| 				emp_list.remove(employee_details) | ||||
| 
 | ||||
| 		return emp_list | ||||
| 
 | ||||
| 	def fill_employee_details(self): | ||||
| 		self.set('employees', []) | ||||
| 		employees = self.get_emp_list() | ||||
| @ -542,7 +561,7 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True): | ||||
| 					title = _("Creating Salary Slips...")) | ||||
| 		else: | ||||
| 			salary_slip_name = frappe.db.sql( | ||||
| 				'''SELECT  | ||||
| 				'''SELECT | ||||
| 						name | ||||
| 					FROM `tabSalary Slip` | ||||
| 					WHERE company=%s | ||||
|  | ||||
| @ -125,15 +125,15 @@ frappe.ui.form.on("Salary Slip", { | ||||
| 
 | ||||
| 	change_form_labels: function(frm, company_currency) { | ||||
| 		frm.set_currency_labels(["base_hour_rate", "base_gross_pay", "base_total_deduction", | ||||
| 			"base_net_pay", "base_rounded_total", "base_total_in_words"], | ||||
| 			"base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date"], | ||||
| 		company_currency); | ||||
| 
 | ||||
| 		frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words"], | ||||
| 		frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words", "year_to_date", "month_to_date"], | ||||
| 			frm.doc.currency); | ||||
| 
 | ||||
| 		// toggle fields
 | ||||
| 		frm.toggle_display(["exchange_rate", "base_hour_rate", "base_gross_pay", "base_total_deduction", | ||||
| 			"base_net_pay", "base_rounded_total", "base_total_in_words"], | ||||
| 			"base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date"], | ||||
| 		frm.doc.currency != company_currency); | ||||
| 	}, | ||||
| 
 | ||||
|  | ||||
| @ -69,9 +69,13 @@ | ||||
|   "net_pay_info", | ||||
|   "net_pay", | ||||
|   "base_net_pay", | ||||
|   "year_to_date", | ||||
|   "base_year_to_date", | ||||
|   "column_break_53", | ||||
|   "rounded_total", | ||||
|   "base_rounded_total", | ||||
|   "month_to_date", | ||||
|   "base_month_to_date", | ||||
|   "section_break_55", | ||||
|   "total_in_words", | ||||
|   "column_break_69", | ||||
| @ -578,13 +582,41 @@ | ||||
|   { | ||||
|    "fieldname": "column_break_69", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "year_to_date", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Year To Date", | ||||
|    "options": "currency", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "month_to_date", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Month To Date", | ||||
|    "options": "currency", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "base_year_to_date", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Year To Date(Company Currency)", | ||||
|    "options": "Company:company:default_currency", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "base_month_to_date", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Month To Date(Company Currency)", | ||||
|    "options": "Company:company:default_currency", | ||||
|    "read_only": 1 | ||||
|   } | ||||
|  ], | ||||
|  "icon": "fa fa-file-text", | ||||
|  "idx": 9, | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-10-21 23:02:59.400249", | ||||
|  "modified": "2020-12-21 23:43:44.959840", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Payroll", | ||||
|  "name": "Salary Slip", | ||||
|  | ||||
| @ -5,7 +5,7 @@ from __future__ import unicode_literals | ||||
| import frappe, erpnext | ||||
| import datetime, math | ||||
| 
 | ||||
| from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate | ||||
| from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate, get_first_day | ||||
| from frappe.model.naming import make_autoname | ||||
| 
 | ||||
| from frappe import msgprint, _ | ||||
| @ -18,6 +18,7 @@ from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_fac | ||||
| from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount | ||||
| from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits | ||||
| from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry | ||||
| from erpnext.accounts.utils import get_fiscal_year | ||||
| 
 | ||||
| class SalarySlip(TransactionBase): | ||||
| 	def __init__(self, *args, **kwargs): | ||||
| @ -49,6 +50,8 @@ class SalarySlip(TransactionBase): | ||||
| 			self.get_working_days_details(lwp = self.leave_without_pay) | ||||
| 
 | ||||
| 		self.calculate_net_pay() | ||||
| 		self.compute_year_to_date() | ||||
| 		self.compute_month_to_date() | ||||
| 
 | ||||
| 		if frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet"): | ||||
| 			max_working_hours = frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet") | ||||
| @ -1125,6 +1128,46 @@ class SalarySlip(TransactionBase): | ||||
| 				self.gross_pay += self.earnings[i].amount | ||||
| 		self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) | ||||
| 
 | ||||
| 	def compute_year_to_date(self): | ||||
| 		year_to_date = 0 | ||||
| 		payroll_period = get_payroll_period(self.start_date, self.end_date, self.company) | ||||
| 
 | ||||
| 		if payroll_period: | ||||
| 			period_start_date = payroll_period.start_date | ||||
| 			period_end_date = payroll_period.end_date | ||||
| 		else: | ||||
| 			# get dates based on fiscal year if no payroll period exists | ||||
| 			fiscal_year = get_fiscal_year(date=self.start_date, company=self.company, as_dict=1) | ||||
| 			period_start_date = fiscal_year.year_start_date | ||||
| 			period_end_date = fiscal_year.year_end_date | ||||
| 
 | ||||
| 		salary_slip_sum = frappe.get_list('Salary Slip', | ||||
| 			fields = ['sum(net_pay) as sum'], | ||||
| 			filters = {'employee_name' : self.employee_name, | ||||
| 				'start_date' : ['>=', period_start_date], | ||||
| 				'end_date' : ['<', period_end_date]}) | ||||
| 
 | ||||
| 
 | ||||
| 		year_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0 | ||||
| 
 | ||||
| 		year_to_date += self.net_pay | ||||
| 		self.year_to_date = year_to_date | ||||
| 
 | ||||
| 	def compute_month_to_date(self): | ||||
| 		month_to_date = 0 | ||||
| 		first_day_of_the_month = get_first_day(self.start_date) | ||||
| 		salary_slip_sum = frappe.get_list('Salary Slip', | ||||
| 			fields = ['sum(net_pay) as sum'], | ||||
| 			filters = {'employee_name' : self.employee_name, | ||||
| 				'start_date' : ['>=', first_day_of_the_month], | ||||
| 				'end_date' : ['<', self.start_date] | ||||
| 			}) | ||||
| 
 | ||||
| 		month_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0 | ||||
| 
 | ||||
| 		month_to_date += self.net_pay | ||||
| 		self.month_to_date = month_to_date | ||||
| 
 | ||||
| def unlink_ref_doc_from_salary_slip(ref_no): | ||||
| 	linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip` | ||||
| 	where journal_entry=%s and docstatus < 2""", (ref_no)) | ||||
| @ -1135,4 +1178,4 @@ def unlink_ref_doc_from_salary_slip(ref_no): | ||||
| 
 | ||||
| def generate_password_for_pdf(policy_template, employee): | ||||
| 	employee = frappe.get_doc("Employee", employee) | ||||
| 	return policy_template.format(**employee.as_dict()) | ||||
| 	return policy_template.format(**employee.as_dict()) | ||||
| @ -9,7 +9,7 @@ import calendar | ||||
| import random | ||||
| from erpnext.accounts.utils import get_fiscal_year | ||||
| from frappe.utils.make_random import get_random | ||||
| from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day | ||||
| from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day, cstr | ||||
| from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip | ||||
| from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details | ||||
| from erpnext.hr.doctype.employee.test_employee import make_employee | ||||
| @ -240,7 +240,11 @@ class TestSalarySlip(unittest.TestCase): | ||||
| 			interest_income_account='Interest Income Account - _TC', | ||||
| 			penalty_income_account='Penalty Income Account - _TC') | ||||
| 
 | ||||
| 		make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR') | ||||
| 		payroll_period = create_payroll_period(name="_Test Payroll Period 1", company="_Test Company") | ||||
| 
 | ||||
| 		make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR', | ||||
| 			payroll_period=payroll_period) | ||||
| 
 | ||||
| 		frappe.db.sql("""delete from `tabLoan""") | ||||
| 		loan = create_loan(applicant, "Car Loan", 11000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1)) | ||||
| 		loan.repay_from_salary = 1 | ||||
| @ -290,6 +294,33 @@ class TestSalarySlip(unittest.TestCase): | ||||
| 		self.assertEqual(salary_slip.gross_pay, 78000) | ||||
| 		self.assertEqual(salary_slip.base_gross_pay, 78000*70) | ||||
| 
 | ||||
| 	def test_year_to_date_computation(self): | ||||
| 		from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure | ||||
| 
 | ||||
| 		applicant = make_employee("test_ytd@salary.com", company="_Test Company") | ||||
| 
 | ||||
| 		payroll_period = create_payroll_period(name="_Test Payroll Period 1", company="_Test Company") | ||||
| 
 | ||||
| 		create_tax_slab(payroll_period, allow_tax_exemption=True, currency="INR", effective_date=getdate("2019-04-01"), | ||||
| 			company="_Test Company") | ||||
| 
 | ||||
| 		salary_structure = make_salary_structure("Monthly Salary Structure Test for Salary Slip YTD", | ||||
| 			"Monthly", employee=applicant, company="_Test Company", currency="INR", payroll_period=payroll_period) | ||||
| 
 | ||||
| 		# clear salary slip for this employee | ||||
| 		frappe.db.sql("DELETE FROM `tabSalary Slip` where employee_name = 'test_ytd@salary.com'") | ||||
| 
 | ||||
| 		create_salary_slips_for_payroll_period(applicant, salary_structure.name, | ||||
| 			payroll_period, deduct_random=False) | ||||
| 
 | ||||
| 		salary_slips = frappe.get_all('Salary Slip', fields=['year_to_date', 'net_pay'], filters={'employee_name': | ||||
| 			'test_ytd@salary.com'}, order_by = 'posting_date') | ||||
| 
 | ||||
| 		year_to_date = 0 | ||||
| 		for slip in salary_slips: | ||||
| 			year_to_date += slip.net_pay | ||||
| 			self.assertEqual(slip.year_to_date, year_to_date) | ||||
| 
 | ||||
| 	def test_tax_for_payroll_period(self): | ||||
| 		data = {} | ||||
| 		# test the impact of tax exemption declaration, tax exemption proof submission | ||||
| @ -410,10 +441,7 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None): | ||||
| 		salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip" | ||||
| 
 | ||||
| 	employee = frappe.db.get_value("Employee", {"user_id": user}) | ||||
| 	if not frappe.db.exists('Salary Structure', salary_structure): | ||||
| 		salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee) | ||||
| 	else: | ||||
| 		salary_structure_doc = frappe.get_doc('Salary Structure', salary_structure) | ||||
| 	salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee) | ||||
| 	salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})}) | ||||
| 
 | ||||
| 	if not salary_slip_name: | ||||
| @ -631,8 +659,13 @@ def create_benefit_claim(employee, payroll_period, amount, component): | ||||
| 	}).submit() | ||||
| 	return claim_date | ||||
| 
 | ||||
| def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False, currency=erpnext.get_default_currency()): | ||||
| 	frappe.db.sql("""delete from `tabIncome Tax Slab`""") | ||||
| def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False, currency=None, | ||||
| 	company=None): | ||||
| 	if not currency: | ||||
| 		currency = erpnext.get_default_currency() | ||||
| 
 | ||||
| 	if company: | ||||
| 		currency = erpnext.get_company_currency(company) | ||||
| 
 | ||||
| 	slabs = [ | ||||
| 		{ | ||||
| @ -652,26 +685,33 @@ def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = | ||||
| 		} | ||||
| 	] | ||||
| 
 | ||||
| 	income_tax_slab = frappe.new_doc("Income Tax Slab") | ||||
| 	income_tax_slab.name = "Tax Slab: " + payroll_period.name | ||||
| 	income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2) | ||||
| 	income_tax_slab.currency = currency | ||||
| 	income_tax_slab_name = frappe.db.get_value("Income Tax Slab", {"currency": currency}) | ||||
| 	if not income_tax_slab_name: | ||||
| 		income_tax_slab = frappe.new_doc("Income Tax Slab") | ||||
| 		income_tax_slab.name = "Tax Slab: " + payroll_period.name + " " + cstr(currency) | ||||
| 		income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2) | ||||
| 		income_tax_slab.company = company or '' | ||||
| 		income_tax_slab.currency = currency | ||||
| 
 | ||||
| 	if allow_tax_exemption: | ||||
| 		income_tax_slab.allow_tax_exemption = 1 | ||||
| 		income_tax_slab.standard_tax_exemption_amount = 50000 | ||||
| 		if allow_tax_exemption: | ||||
| 			income_tax_slab.allow_tax_exemption = 1 | ||||
| 			income_tax_slab.standard_tax_exemption_amount = 50000 | ||||
| 
 | ||||
| 	for item in slabs: | ||||
| 		income_tax_slab.append("slabs", item) | ||||
| 		for item in slabs: | ||||
| 			income_tax_slab.append("slabs", item) | ||||
| 
 | ||||
| 	income_tax_slab.append("other_taxes_and_charges", { | ||||
| 		"description": "cess", | ||||
| 		"percent": 4 | ||||
| 	}) | ||||
| 		income_tax_slab.append("other_taxes_and_charges", { | ||||
| 			"description": "cess", | ||||
| 			"percent": 4 | ||||
| 		}) | ||||
| 
 | ||||
| 	income_tax_slab.save() | ||||
| 	if not dont_submit: | ||||
| 		income_tax_slab.submit() | ||||
| 		income_tax_slab.save() | ||||
| 		if not dont_submit: | ||||
| 			income_tax_slab.submit() | ||||
| 
 | ||||
| 		return income_tax_slab.name | ||||
| 	else: | ||||
| 		return income_tax_slab_name | ||||
| 
 | ||||
| def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True): | ||||
| 	deducted_dates = [] | ||||
|  | ||||
| @ -114,7 +114,7 @@ class TestSalaryStructure(unittest.TestCase): | ||||
| 		self.assertEqual(sal_struct.currency, 'USD') | ||||
| 
 | ||||
| def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None, | ||||
| 	test_tax=False, company=None, currency=erpnext.get_default_currency()): | ||||
| 	test_tax=False, company=None, currency=erpnext.get_default_currency(), payroll_period=None): | ||||
| 	if test_tax: | ||||
| 		frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure)) | ||||
| 
 | ||||
| @ -141,16 +141,24 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do | ||||
| 
 | ||||
| 	if employee and not frappe.db.get_value("Salary Structure Assignment", | ||||
| 		{'employee':employee, 'docstatus': 1}) and salary_structure_doc.docstatus==1: | ||||
| 			create_salary_structure_assignment(employee, salary_structure, company=company, currency=currency) | ||||
| 			create_salary_structure_assignment(employee, salary_structure, company=company, currency=currency, | ||||
| 			payroll_period=payroll_period) | ||||
| 
 | ||||
| 	return salary_structure_doc | ||||
| 
 | ||||
| def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None, currency=erpnext.get_default_currency()): | ||||
| def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None, currency=erpnext.get_default_currency(), | ||||
| 	payroll_period=None): | ||||
| 
 | ||||
| 	if frappe.db.exists("Salary Structure Assignment", {"employee": employee}): | ||||
| 		frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""",(employee)) | ||||
| 
 | ||||
| 	payroll_period = create_payroll_period() | ||||
| 	create_tax_slab(payroll_period, allow_tax_exemption=True, currency=currency) | ||||
| 	if not payroll_period: | ||||
| 		payroll_period = create_payroll_period() | ||||
| 
 | ||||
| 	income_tax_slab = frappe.db.get_value("Income Tax Slab", {"currency": currency}) | ||||
| 
 | ||||
| 	if not income_tax_slab: | ||||
| 		income_tax_slab = create_tax_slab(payroll_period, allow_tax_exemption=True, currency=currency) | ||||
| 
 | ||||
| 	salary_structure_assignment = frappe.new_doc("Salary Structure Assignment") | ||||
| 	salary_structure_assignment.employee = employee | ||||
| @ -162,7 +170,7 @@ def create_salary_structure_assignment(employee, salary_structure, from_date=Non | ||||
| 	salary_structure_assignment.payroll_payable_account = get_payable_account(company) | ||||
| 	salary_structure_assignment.company = company or erpnext.get_default_company() | ||||
| 	salary_structure_assignment.save(ignore_permissions=True) | ||||
| 	salary_structure_assignment.income_tax_slab = "Tax Slab: _Test Payroll Period" | ||||
| 	salary_structure_assignment.income_tax_slab = income_tax_slab | ||||
| 	salary_structure_assignment.submit() | ||||
| 	return salary_structure_assignment | ||||
| 
 | ||||
|  | ||||
| @ -59,7 +59,7 @@ | ||||
|         {item_list} | ||||
|     ], | ||||
|     "ValDtls": {{ | ||||
|         "AssVal": "{invoice_value_details.base_net_total}", | ||||
|         "AssVal": "{invoice_value_details.base_total}", | ||||
|         "CgstVal": "{invoice_value_details.total_cgst_amt}", | ||||
|         "SgstVal": "{invoice_value_details.total_sgst_amt}", | ||||
|         "IgstVal": "{invoice_value_details.total_igst_amt}", | ||||
|  | ||||
| @ -146,12 +146,12 @@ def get_item_list(invoice): | ||||
| 		item.update(d.as_dict()) | ||||
| 
 | ||||
| 		item.sr_no = d.idx | ||||
| 		item.qty = abs(item.qty) | ||||
| 		item.description = d.item_name | ||||
| 		item.taxable_value = abs(item.base_net_amount) | ||||
| 		item.discount_amount = abs(item.discount_amount * item.qty) | ||||
| 		item.unit_rate = abs(item.base_price_list_rate) if item.discount_amount else abs(item.base_net_rate) | ||||
| 		item.gross_amount = abs(item.unit_rate * item.qty) | ||||
| 		item.description = d.item_name | ||||
| 		item.qty = abs(item.qty) | ||||
| 		item.unit_rate = abs(item.base_amount / item.qty) | ||||
| 		item.gross_amount = abs(item.base_amount) | ||||
| 		item.taxable_value = abs(item.base_amount) | ||||
| 
 | ||||
| 		item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None | ||||
| 		item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None | ||||
| @ -180,35 +180,35 @@ def update_item_taxes(invoice, item): | ||||
| 		item[attr] = 0 | ||||
| 
 | ||||
| 	for t in invoice.taxes: | ||||
| 		# this contains item wise tax rate & tax amount (incl. discount) | ||||
| 		item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) | ||||
| 		if t.account_head in gst_accounts_list: | ||||
| 			item_tax_rate = item_tax_detail[0] | ||||
| 			# item tax amount excluding discount amount | ||||
| 			item_tax_amount = (item_tax_rate / 100) * item.base_amount | ||||
| 
 | ||||
| 			if t.account_head in gst_accounts.cess_account: | ||||
| 				item_tax_amount_after_discount = item_tax_detail[1] | ||||
| 				if t.charge_type == 'On Item Quantity': | ||||
| 					item.cess_nadv_amount += abs(item_tax_detail[1]) | ||||
| 					item.cess_nadv_amount += abs(item_tax_amount_after_discount) | ||||
| 				else: | ||||
| 					item.cess_rate += item_tax_detail[0] | ||||
| 					item.cess_amount += abs(item_tax_detail[1]) | ||||
| 			elif t.account_head in gst_accounts.igst_account: | ||||
| 				item.tax_rate += item_tax_detail[0] | ||||
| 				item.igst_amount += abs(item_tax_detail[1]) | ||||
| 			elif t.account_head in gst_accounts.sgst_account: | ||||
| 				item.tax_rate += item_tax_detail[0] | ||||
| 				item.sgst_amount += abs(item_tax_detail[1]) | ||||
| 			elif t.account_head in gst_accounts.cgst_account: | ||||
| 				item.tax_rate += item_tax_detail[0] | ||||
| 				item.cgst_amount += abs(item_tax_detail[1]) | ||||
| 	 | ||||
| 					item.cess_rate += item_tax_rate | ||||
| 					item.cess_amount += abs(item_tax_amount_after_discount) | ||||
| 
 | ||||
| 			for tax_type in ['igst', 'cgst', 'sgst']: | ||||
| 				if t.account_head in gst_accounts[f'{tax_type}_account']: | ||||
| 					item.tax_rate += item_tax_rate | ||||
| 					item[f'{tax_type}_amount'] += abs(item_tax_amount) | ||||
| 
 | ||||
| 	return item | ||||
| 
 | ||||
| def get_invoice_value_details(invoice): | ||||
| 	invoice_value_details = frappe._dict(dict()) | ||||
| 	invoice_value_details.base_net_total = abs(invoice.base_net_total) | ||||
| 	invoice_value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount and invoice.discount_amount > 0 else 0 | ||||
| 	# discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off | ||||
| 	invoice_value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0) | ||||
| 	disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') | ||||
| 	invoice_value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else abs(invoice.base_rounded_total) | ||||
| 	invoice_value_details.grand_total = abs(invoice.grand_total) if disable_rounded else abs(invoice.rounded_total) | ||||
| 	invoice_value_details.base_total = abs(invoice.base_total) | ||||
| 	invoice_value_details.invoice_discount_amt = invoice.discount_amount | ||||
| 	invoice_value_details.round_off = invoice.rounding_adjustment | ||||
| 	invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total) | ||||
| 	invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total) | ||||
| 	 | ||||
| 	invoice_value_details = update_invoice_taxes(invoice, invoice_value_details) | ||||
| 	 | ||||
| @ -226,15 +226,14 @@ def update_invoice_taxes(invoice, invoice_value_details): | ||||
| 	for t in invoice.taxes: | ||||
| 		if t.account_head in gst_accounts_list: | ||||
| 			if t.account_head in gst_accounts.cess_account: | ||||
| 				# using after discount amt since item also uses after discount amt for cess calc | ||||
| 				invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount) | ||||
| 			elif t.account_head in gst_accounts.igst_account: | ||||
| 				invoice_value_details.total_igst_amt += abs(t.base_tax_amount_after_discount_amount) | ||||
| 			elif t.account_head in gst_accounts.sgst_account: | ||||
| 				invoice_value_details.total_sgst_amt += abs(t.base_tax_amount_after_discount_amount) | ||||
| 			elif t.account_head in gst_accounts.cgst_account: | ||||
| 				invoice_value_details.total_cgst_amt += abs(t.base_tax_amount_after_discount_amount) | ||||
| 			 | ||||
| 			for tax_type in ['igst', 'cgst', 'sgst']: | ||||
| 				if t.account_head in gst_accounts[f'{tax_type}_account']: | ||||
| 					invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount) | ||||
| 		else: | ||||
| 			invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount) | ||||
| 			invoice_value_details.total_other_charges += abs(t.base_tax_amount) | ||||
| 	 | ||||
| 	return invoice_value_details | ||||
| 
 | ||||
| @ -358,7 +357,8 @@ def validate_einvoice(validations, einvoice, errors=[]): | ||||
| 			einvoice[fieldname] = str(value) | ||||
| 		elif value_type == 'number': | ||||
| 			is_integer = '.' not in str(field_validation.get('maximum')) | ||||
| 			einvoice[fieldname] = flt(value, 2) if not is_integer else cint(value) | ||||
| 			precision = 3 if '.999' in str(field_validation.get('maximum')) else 2 | ||||
| 			einvoice[fieldname] = flt(value, precision) if not is_integer else cint(value) | ||||
| 			value = einvoice[fieldname] | ||||
| 
 | ||||
| 		max_length = field_validation.get('maxLength') | ||||
| @ -386,15 +386,15 @@ class GSPConnector(): | ||||
| 		self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None | ||||
| 		self.credentials = self.get_credentials() | ||||
| 
 | ||||
| 		self.base_url = 'https://gsp.adaequare.com/' | ||||
| 		self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token' | ||||
| 		self.gstin_details_url = self.base_url + 'test/enriched/ei/api/master/gstin' | ||||
| 		self.generate_irn_url = self.base_url + 'test/enriched/ei/api/invoice' | ||||
| 		self.irn_details_url = self.base_url + 'test/enriched/ei/api/invoice/irn' | ||||
| 		self.cancel_irn_url = self.base_url + 'test/enriched/ei/api/invoice/cancel' | ||||
| 		self.cancel_ewaybill_url = self.base_url + '/test/enriched/ei/api/ewayapi' | ||||
| 		self.generate_ewaybill_url = self.base_url + 'test/enriched/ei/api/ewaybill' | ||||
| 	 | ||||
| 		self.base_url = 'https://gsp.adaequare.com' | ||||
| 		self.authenticate_url = self.base_url + '/gsp/authenticate?grant_type=token' | ||||
| 		self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin' | ||||
| 		self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice' | ||||
| 		self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn' | ||||
| 		self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel' | ||||
| 		self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi' | ||||
| 		self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill' | ||||
| 
 | ||||
| 	def get_credentials(self): | ||||
| 		if self.invoice: | ||||
| 			gstin = self.get_seller_gstin() | ||||
|  | ||||
| @ -217,7 +217,7 @@ | ||||
|    "fieldname": "role_allowed_to_create_edit_back_dated_transactions", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Role Allowed to Create/Edit Back-dated Transactions", | ||||
|    "options": "User" | ||||
|    "options": "Role" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_26", | ||||
| @ -234,7 +234,7 @@ | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "issingle": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-11-23 22:26:54.225608", | ||||
|  "modified": "2020-12-29 12:53:31.162247", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Stock", | ||||
|  "name": "Stock Settings", | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user