Merge branch 'develop' into issue-condition-fix
This commit is contained in:
		
						commit
						14b98c1737
					
				| @ -293,6 +293,11 @@ def validate_accounts(file_name): | ||||
| 	accounts_dict = {} | ||||
| 	for account in accounts: | ||||
| 		accounts_dict.setdefault(account["account_name"], account) | ||||
| 		if not hasattr(account, "parent_account"): | ||||
| 			msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.") | ||||
| 			msg += "<br><br>" | ||||
| 			msg += _("Alternatively, you can download the template and fill your data in.") | ||||
| 			frappe.throw(msg, title=_("Parent Account Missing")) | ||||
| 		if account["parent_account"] and accounts_dict.get(account["parent_account"]): | ||||
| 			accounts_dict[account["parent_account"]]["is_group"] = 1 | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										8
									
								
								erpnext/crm/doctype/lead_source/lead_source.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								erpnext/crm/doctype/lead_source/lead_source.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| // Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| 
 | ||||
| frappe.ui.form.on('Lead Source', { | ||||
| 	// refresh: function(frm) {
 | ||||
| 
 | ||||
| 	// }
 | ||||
| }); | ||||
							
								
								
									
										62
									
								
								erpnext/crm/doctype/lead_source/lead_source.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								erpnext/crm/doctype/lead_source/lead_source.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "allow_rename": 1, | ||||
|  "autoname": "field:source_name", | ||||
|  "creation": "2016-09-16 01:47:47.382372", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "source_name", | ||||
|   "details" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "source_name", | ||||
|    "fieldtype": "Data", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Source Name", | ||||
|    "reqd": 1, | ||||
|    "unique": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "details", | ||||
|    "fieldtype": "Text Editor", | ||||
|    "label": "Details" | ||||
|   } | ||||
|  ], | ||||
|  "links": [], | ||||
|  "modified": "2021-02-08 12:51:48.971517", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "CRM", | ||||
|  "name": "Lead Source", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Sales Manager", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "create": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Sales User", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   } | ||||
|  ], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC" | ||||
| } | ||||
| @ -1,9 +1,9 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| # import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class LeadSource(Document): | ||||
| @ -1,12 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| import frappe | ||||
| # import frappe | ||||
| import unittest | ||||
| 
 | ||||
| # test_records = frappe.get_test_records('Lead Source') | ||||
| 
 | ||||
| class TestLeadSource(unittest.TestCase): | ||||
| 	pass | ||||
| @ -260,7 +260,10 @@ doc_events = { | ||||
| 			"erpnext.regional.italy.utils.sales_invoice_on_cancel", | ||||
| 			"erpnext.erpnext_integrations.taxjar_integration.delete_transaction" | ||||
| 		], | ||||
| 		"on_trash": "erpnext.regional.check_deletion_permission" | ||||
| 		"on_trash": "erpnext.regional.check_deletion_permission", | ||||
| 		"validate": [ | ||||
| 			"erpnext.regional.india.utils.validate_document_name" | ||||
| 		] | ||||
| 	}, | ||||
| 	"Purchase Invoice": { | ||||
| 		"validate": [ | ||||
| @ -282,9 +285,6 @@ doc_events = { | ||||
| 	('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): { | ||||
| 		'validate': ['erpnext.regional.india.utils.set_place_of_supply'] | ||||
| 	}, | ||||
| 	('Sales Invoice', 'Purchase Invoice'): { | ||||
| 		'validate': ['erpnext.regional.india.utils.validate_document_name'] | ||||
| 	}, | ||||
| 	"Contact": { | ||||
| 		"on_trash": "erpnext.support.doctype.issue.issue.update_issue", | ||||
| 		"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations", | ||||
|  | ||||
| @ -15,6 +15,7 @@ | ||||
|  "hide_custom": 0, | ||||
|  "icon": "hr", | ||||
|  "idx": 0, | ||||
|  "is_default": 0, | ||||
|  "is_standard": 1, | ||||
|  "label": "HR", | ||||
|  "links": [ | ||||
| @ -226,42 +227,12 @@ | ||||
|    "onboard": 0, | ||||
|    "type": "Card Break" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Leave Application", | ||||
|    "link_to": "Leave Application", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Leave Allocation", | ||||
|    "link_to": "Leave Allocation", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Leave Type", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Leave Policy", | ||||
|    "link_to": "Leave Policy", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Leave Period", | ||||
|    "link_to": "Leave Period", | ||||
|    "label": "Holiday List", | ||||
|    "link_to": "Holiday List", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
| @ -280,8 +251,28 @@ | ||||
|    "dependencies": "", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Holiday List", | ||||
|    "link_to": "Holiday List", | ||||
|    "label": "Leave Period", | ||||
|    "link_to": "Leave Period", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Leave Type", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Leave Policy", | ||||
|    "link_to": "Leave Policy", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Leave Policy", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Leave Policy Assignment", | ||||
|    "link_to": "Leave Policy Assignment", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
| @ -290,8 +281,18 @@ | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Compensatory Leave Request", | ||||
|    "link_to": "Compensatory Leave Request", | ||||
|    "label": "Leave Application", | ||||
|    "link_to": "Leave Application", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Leave Allocation", | ||||
|    "link_to": "Leave Allocation", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
| @ -317,12 +318,12 @@ | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Leave Application", | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 1, | ||||
|    "label": "Employee Leave Balance", | ||||
|    "link_to": "Employee Leave Balance", | ||||
|    "link_type": "Report", | ||||
|    "is_query_report": 0, | ||||
|    "label": "Compensatory Leave Request", | ||||
|    "link_to": "Compensatory Leave Request", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
| @ -383,16 +384,6 @@ | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Attendance", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 1, | ||||
|    "label": "Monthly Attendance Sheet", | ||||
|    "link_to": "Monthly Attendance Sheet", | ||||
|    "link_type": "Report", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
| @ -420,6 +411,15 @@ | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Travel Request", | ||||
|    "link_to": "Travel Request", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
| @ -464,6 +464,15 @@ | ||||
|    "onboard": 0, | ||||
|    "type": "Card Break" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Driver", | ||||
|    "link_to": "Driver", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "", | ||||
|    "hidden": 0, | ||||
| @ -541,6 +550,24 @@ | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Appointment Letter", | ||||
|    "link_to": "Appointment Letter", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Appointment Letter Template", | ||||
|    "link_to": "Appointment Letter Template", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
| @ -625,33 +652,6 @@ | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Reports", | ||||
|    "onboard": 0, | ||||
|    "type": "Card Break" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 1, | ||||
|    "label": "Employee Birthday", | ||||
|    "link_to": "Employee Birthday", | ||||
|    "link_type": "Report", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 1, | ||||
|    "label": "Employees working on a holiday", | ||||
|    "link_to": "Employees working on a holiday", | ||||
|    "link_type": "Report", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
| @ -702,7 +702,74 @@ | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Employee Tax and Benefits", | ||||
|    "label": "Key Reports", | ||||
|    "onboard": 0, | ||||
|    "type": "Card Break" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Attendance", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 1, | ||||
|    "label": "Monthly Attendance Sheet", | ||||
|    "link_to": "Monthly Attendance Sheet", | ||||
|    "link_type": "Report", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Staffing Plan", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 1, | ||||
|    "label": "Recruitment Analytics", | ||||
|    "link_to": "Recruitment Analytics", | ||||
|    "link_type": "Report", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 1, | ||||
|    "label": "Employee Analytics", | ||||
|    "link_to": "Employee Analytics", | ||||
|    "link_type": "Report", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 1, | ||||
|    "label": "Employee Leave Balance", | ||||
|    "link_to": "Employee Leave Balance", | ||||
|    "link_type": "Report", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 1, | ||||
|    "label": "Employee Leave Balance Summary", | ||||
|    "link_to": "Employee Leave Balance Summary", | ||||
|    "link_type": "Report", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee Advance", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 1, | ||||
|    "label": "Employee Advance Summary", | ||||
|    "link_to": "Employee Advance Summary", | ||||
|    "link_type": "Report", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Other Reports", | ||||
|    "onboard": 0, | ||||
|    "type": "Card Break" | ||||
|   }, | ||||
| @ -710,74 +777,44 @@ | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Employee Tax Exemption Declaration", | ||||
|    "link_to": "Employee Tax Exemption Declaration", | ||||
|    "link_type": "DocType", | ||||
|    "label": "Employee Information", | ||||
|    "link_to": "Employee Information", | ||||
|    "link_type": "Report", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Employee Tax Exemption Proof Submission", | ||||
|    "link_to": "Employee Tax Exemption Proof Submission", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee, Payroll Period", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Employee Other Income", | ||||
|    "link_to": "Employee Other Income", | ||||
|    "link_type": "DocType", | ||||
|    "is_query_report": 1, | ||||
|    "label": "Employee Birthday", | ||||
|    "link_to": "Employee Birthday", | ||||
|    "link_type": "Report", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Employee Benefit Application", | ||||
|    "link_to": "Employee Benefit Application", | ||||
|    "link_type": "DocType", | ||||
|    "is_query_report": 1, | ||||
|    "label": "Employees Working on a Holiday", | ||||
|    "link_to": "Employees working on a holiday", | ||||
|    "link_type": "Report", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee", | ||||
|    "dependencies": "Daily Work Summary", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Employee Benefit Claim", | ||||
|    "link_to": "Employee Benefit Claim", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Employee Tax Exemption Category", | ||||
|    "link_to": "Employee Tax Exemption Category", | ||||
|    "link_type": "DocType", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   }, | ||||
|   { | ||||
|    "dependencies": "Employee", | ||||
|    "hidden": 0, | ||||
|    "is_query_report": 0, | ||||
|    "label": "Employee Tax Exemption Sub Category", | ||||
|    "link_to": "Employee Tax Exemption Sub Category", | ||||
|    "link_type": "DocType", | ||||
|    "is_query_report": 1, | ||||
|    "label": "Daily Work Summary Replies", | ||||
|    "link_to": "Daily Work Summary Replies", | ||||
|    "link_type": "Report", | ||||
|    "onboard": 0, | ||||
|    "type": "Link" | ||||
|   } | ||||
|  ], | ||||
|  "modified": "2021-01-21 13:38:38.941001", | ||||
|  "modified": "2021-03-24 17:35:21.483297", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "HR", | ||||
|  "name": "HR", | ||||
|  | ||||
| @ -21,6 +21,7 @@ class LoanRepayment(AccountsController): | ||||
| 	def validate(self): | ||||
| 		amounts = calculate_amounts(self.against_loan, self.posting_date) | ||||
| 		self.set_missing_values(amounts) | ||||
| 		self.check_future_entries() | ||||
| 		self.validate_amount() | ||||
| 		self.allocate_amounts(amounts) | ||||
| 
 | ||||
| @ -69,6 +70,13 @@ class LoanRepayment(AccountsController): | ||||
| 		if amounts.get('due_date'): | ||||
| 			self.due_date = amounts.get('due_date') | ||||
| 
 | ||||
| 	def check_future_entries(self): | ||||
| 		future_repayment_date = frappe.db.get_value("Loan Repayment", {"posting_date": (">", self.posting_date), | ||||
| 			"docstatus": 1, "against_loan": self.against_loan}, 'posting_date') | ||||
| 
 | ||||
| 		if future_repayment_date: | ||||
| 			frappe.throw("Repayment already made till date {0}".format(getdate(future_repayment_date))) | ||||
| 
 | ||||
| 	def validate_amount(self): | ||||
| 		precision = cint(frappe.db.get_default("currency_precision")) or 2 | ||||
| 
 | ||||
| @ -307,7 +315,9 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type, | ||||
| 
 | ||||
| 	return lr | ||||
| 
 | ||||
| def get_accrued_interest_entries(against_loan): | ||||
| def get_accrued_interest_entries(against_loan, posting_date=None): | ||||
| 	if not posting_date: | ||||
| 		posting_date = getdate() | ||||
| 
 | ||||
| 	unpaid_accrued_entries = frappe.db.sql( | ||||
| 		""" | ||||
| @ -318,12 +328,13 @@ def get_accrued_interest_entries(against_loan): | ||||
| 				`tabLoan Interest Accrual` | ||||
| 			WHERE | ||||
| 				loan = %s | ||||
| 			AND posting_date <= %s | ||||
| 			AND (interest_amount - paid_interest_amount > 0 OR | ||||
| 				payable_principal_amount - paid_principal_amount > 0) | ||||
| 			AND | ||||
| 				docstatus = 1 | ||||
| 			ORDER BY posting_date | ||||
| 		""", (against_loan), as_dict=1) | ||||
| 		""", (against_loan, posting_date), as_dict=1) | ||||
| 
 | ||||
| 	return unpaid_accrued_entries | ||||
| 
 | ||||
| @ -335,7 +346,7 @@ def get_amounts(amounts, against_loan, posting_date): | ||||
| 
 | ||||
| 	against_loan_doc = frappe.get_doc("Loan", against_loan) | ||||
| 	loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type) | ||||
| 	accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name) | ||||
| 	accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name, posting_date) | ||||
| 
 | ||||
| 	pending_accrual_entries = {} | ||||
| 
 | ||||
|  | ||||
| @ -70,7 +70,9 @@ | ||||
|   { | ||||
|    "fieldname": "loan_repayment_entry", | ||||
|    "fieldtype": "Link", | ||||
|    "hidden": 1, | ||||
|    "label": "Loan Repayment Entry", | ||||
|    "no_copy": 1, | ||||
|    "options": "Loan Repayment", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
| @ -83,9 +85,10 @@ | ||||
|    "read_only": 1 | ||||
|   } | ||||
|  ], | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-04-16 13:17:04.798335", | ||||
|  "modified": "2021-03-14 20:47:11.725818", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Loan Management", | ||||
|  "name": "Salary Slip Loan", | ||||
|  | ||||
| @ -5,7 +5,7 @@ | ||||
| from __future__ import unicode_literals | ||||
| import unittest | ||||
| import frappe | ||||
| from frappe.utils import cstr | ||||
| from frappe.utils import cstr, flt | ||||
| from frappe.test_runner import make_test_records | ||||
| from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation | ||||
| from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost | ||||
| @ -81,15 +81,27 @@ class TestBOM(unittest.TestCase): | ||||
| 		bom = frappe.copy_doc(test_records[2]) | ||||
| 		bom.insert() | ||||
| 
 | ||||
| 		# test amounts in selected currency | ||||
| 		self.assertEqual(bom.operating_cost, 100) | ||||
| 		self.assertEqual(bom.raw_material_cost, 351.68) | ||||
| 		self.assertEqual(bom.total_cost, 451.68) | ||||
| 		raw_material_cost = 0.0 | ||||
| 		op_cost = 0.0 | ||||
| 
 | ||||
| 		for op_row in bom.operations: | ||||
| 			op_cost += op_row.operating_cost | ||||
| 
 | ||||
| 		for row in bom.items: | ||||
| 			raw_material_cost += row.amount | ||||
| 
 | ||||
| 		base_raw_material_cost = raw_material_cost * flt(bom.conversion_rate, bom.precision("conversion_rate")) | ||||
| 		base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate")) | ||||
| 
 | ||||
| 		# test amounts in selected currency | ||||
| 		self.assertEqual(bom.base_operating_cost, 6000) | ||||
| 		self.assertEqual(bom.base_raw_material_cost, 21100.80) | ||||
| 		self.assertEqual(bom.base_total_cost, 27100.80) | ||||
| 		self.assertEqual(bom.operating_cost, op_cost) | ||||
| 		self.assertEqual(bom.raw_material_cost, raw_material_cost) | ||||
| 		self.assertEqual(bom.total_cost, raw_material_cost + op_cost) | ||||
| 
 | ||||
| 		# test amounts in selected currency | ||||
| 		self.assertEqual(bom.base_operating_cost, base_op_cost) | ||||
| 		self.assertEqual(bom.base_raw_material_cost, base_raw_material_cost) | ||||
| 		self.assertEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost) | ||||
| 
 | ||||
| 	def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self): | ||||
| 		frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1) | ||||
|  | ||||
| @ -47,6 +47,8 @@ class JobCard(Document): | ||||
| 				if d.completed_qty: | ||||
| 					self.total_completed_qty += d.completed_qty | ||||
| 
 | ||||
| 			self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty")) | ||||
| 
 | ||||
| 	def get_overlap_for(self, args, check_next_available_slot=False): | ||||
| 		production_capacity = 1 | ||||
| 
 | ||||
|  | ||||
| @ -5,7 +5,7 @@ from frappe import _ | ||||
| def execute(): | ||||
| 	from erpnext.setup.setup_wizard.operations.install_fixtures import default_lead_sources | ||||
| 
 | ||||
| 	frappe.reload_doc('selling', 'doctype', 'lead_source') | ||||
| 	frappe.reload_doc('crm', 'doctype', 'lead_source') | ||||
| 
 | ||||
| 	frappe.local.lang = frappe.db.get_default("lang") or 'en' | ||||
| 
 | ||||
|  | ||||
| @ -133,45 +133,59 @@ frappe.ui.form.on('Payroll Entry', { | ||||
| 				} | ||||
| 			}; | ||||
| 		}); | ||||
| 
 | ||||
| 		frm.set_query('employee', 'employees', () => { | ||||
| 			if (!frm.doc.company) { | ||||
| 				frappe.msgprint(__("Please set a Company")); | ||||
| 				return []; | ||||
| 			} | ||||
| 			return { | ||||
| 				query: "erpnext.payroll.doctype.payroll_entry.payroll_entry.employee_query", | ||||
| 				filters: frm.events.get_employee_filters(frm) | ||||
| 			}; | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	get_employee_filters: function (frm) { | ||||
| 		let filters = {}; | ||||
| 		filters['company'] = frm.doc.company; | ||||
| 		filters['start_date'] = frm.doc.start_date; | ||||
| 		filters['end_date'] = frm.doc.end_date; | ||||
| 
 | ||||
| 		if (frm.doc.department) { | ||||
| 			filters['department'] = frm.doc.department; | ||||
| 		} | ||||
| 		if (frm.doc.branch) { | ||||
| 			filters['branch'] = frm.doc.branch; | ||||
| 		} | ||||
| 		if (frm.doc.designation) { | ||||
| 			filters['designation'] = frm.doc.designation; | ||||
| 		} | ||||
| 		if (frm.doc.employees) { | ||||
| 			filters['employees'] = frm.doc.employees.filter(d => d.employee).map(d => d.employee); | ||||
| 		} | ||||
| 		return filters; | ||||
| 	}, | ||||
| 
 | ||||
| 	payroll_frequency: function (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) { | ||||
| 		frm.events.clear_employee_table(frm); | ||||
| 		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); | ||||
| 		frm.trigger("set_payable_account_and_currency"); | ||||
| 	}, | ||||
| 
 | ||||
| 	set_payable_account_and_currency: function (frm) { | ||||
| 		frappe.db.get_value("Company", {"name": frm.doc.company}, "default_currency", (r) => { | ||||
| 			frm.set_value('currency', r.default_currency); | ||||
| 		}); | ||||
| 		frappe.db.get_value("Company", {"name": frm.doc.company}, "default_payroll_payable_account", (r) => { | ||||
| 			frm.set_value('payroll_payable_account', r.default_payroll_payable_account); | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	currency: function (frm) { | ||||
| @ -345,11 +359,3 @@ let render_employee_attendance = function (frm, data) { | ||||
| 		}) | ||||
| 	); | ||||
| }; | ||||
| 
 | ||||
| frappe.ui.form.on('Payroll Employee Detail', { | ||||
| 	employee: function(frm) { | ||||
| 		if (!frm.doc.payroll_frequency) { | ||||
| 			frappe.throw(__("Please set a Payroll Frequency")); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| @ -10,16 +10,17 @@ from frappe.utils import cint, flt, add_days, getdate, add_to_date, DATE_FORMAT, | ||||
| from frappe import _ | ||||
| from erpnext.accounts.utils import get_fiscal_year | ||||
| from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee | ||||
| from frappe.desk.reportview import get_match_cond, get_filters_cond | ||||
| 
 | ||||
| class PayrollEntry(Document): | ||||
| 	def onload(self): | ||||
| 		if not self.docstatus==1 or self.salary_slips_submitted: | ||||
|     			return | ||||
| 			return | ||||
| 
 | ||||
| 		# 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 validate(self): | ||||
| 		self.number_of_employees = len(self.employees) | ||||
| @ -59,16 +60,16 @@ class PayrollEntry(Document): | ||||
| 			condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency} | ||||
| 
 | ||||
| 		sal_struct = frappe.db.sql_list(""" | ||||
| 				select | ||||
| 					name from `tabSalary Structure` | ||||
| 				where | ||||
| 					docstatus = 1 and | ||||
| 					is_active = 'Yes' | ||||
| 					and company = %(company)s | ||||
| 					and currency = %(currency)s and | ||||
| 					ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s | ||||
| 					{condition}""".format(condition=condition), | ||||
| 				{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) | ||||
| 			select | ||||
| 				name from `tabSalary Structure` | ||||
| 			where | ||||
| 				docstatus = 1 and | ||||
| 				is_active = 'Yes' | ||||
| 				and company = %(company)s | ||||
| 				and currency = %(currency)s and | ||||
| 				ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s | ||||
| 				{condition}""".format(condition=condition), | ||||
| 			{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) | ||||
| 
 | ||||
| 		if sal_struct: | ||||
| 			cond += "and t2.salary_structure IN %(sal_struct)s " | ||||
| @ -176,15 +177,15 @@ class PayrollEntry(Document): | ||||
| 		""" | ||||
| 			Returns list of salary slips based on selected criteria | ||||
| 		""" | ||||
| 		cond = self.get_filter_condition() | ||||
| 
 | ||||
| 		ss_list = frappe.db.sql(""" | ||||
| 			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) | ||||
| 			where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s and t1.payroll_entry = %s | ||||
| 			and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s | ||||
| 		""", (ss_status, self.start_date, self.end_date, self.name, self.salary_slip_based_on_timesheet), as_dict=as_dict) | ||||
| 		return ss_list | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def submit_salary_slips(self): | ||||
| 		self.check_permission('write') | ||||
| 		ss_list = self.get_sal_slip_list(ss_status=0) | ||||
| @ -270,26 +271,26 @@ class PayrollEntry(Document): | ||||
| 				exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies) | ||||
| 				payable_amount += flt(amount, precision) | ||||
| 				accounts.append({ | ||||
| 						"account": acc_cc[0], | ||||
| 						"debit_in_account_currency": flt(amt, precision), | ||||
| 						"exchange_rate": flt(exchange_rate), | ||||
| 						"party_type": '', | ||||
| 						"cost_center": acc_cc[1] or self.cost_center, | ||||
| 						"project": self.project | ||||
| 					}) | ||||
| 					"account": acc_cc[0], | ||||
| 					"debit_in_account_currency": flt(amt, precision), | ||||
| 					"exchange_rate": flt(exchange_rate), | ||||
| 					"party_type": '', | ||||
| 					"cost_center": acc_cc[1] or self.cost_center, | ||||
| 					"project": self.project | ||||
| 				}) | ||||
| 
 | ||||
| 			# Deductions | ||||
| 			for acc_cc, amount in deductions.items(): | ||||
| 				exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies) | ||||
| 				payable_amount -= flt(amount, precision) | ||||
| 				accounts.append({ | ||||
| 						"account": acc_cc[0], | ||||
| 						"credit_in_account_currency": flt(amt, precision), | ||||
| 						"exchange_rate": flt(exchange_rate), | ||||
| 						"cost_center": acc_cc[1] or self.cost_center, | ||||
| 						"party_type": '', | ||||
| 						"project": self.project | ||||
| 					}) | ||||
| 					"account": acc_cc[0], | ||||
| 					"credit_in_account_currency": flt(amt, precision), | ||||
| 					"exchange_rate": flt(exchange_rate), | ||||
| 					"cost_center": acc_cc[1] or self.cost_center, | ||||
| 					"party_type": '', | ||||
| 					"project": self.project | ||||
| 				}) | ||||
| 
 | ||||
| 			# Payable amount | ||||
| 			exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies) | ||||
| @ -335,10 +336,9 @@ class PayrollEntry(Document): | ||||
| 	def make_payment_entry(self): | ||||
| 		self.check_permission('write') | ||||
| 
 | ||||
| 		cond = self.get_filter_condition() | ||||
| 		salary_slip_name_list = frappe.db.sql(""" select t1.name from `tabSalary Slip` t1 | ||||
| 			where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s | ||||
| 			""" % ('%s', '%s', cond), (self.start_date, self.end_date), as_list = True) | ||||
| 			where t1.docstatus = 1 and start_date >= %s and end_date <= %s and t1.payroll_entry = %s | ||||
| 			""", (self.start_date, self.end_date, self.name), as_list = True) | ||||
| 
 | ||||
| 		if salary_slip_name_list and len(salary_slip_name_list) > 0: | ||||
| 			salary_slip_total = 0 | ||||
| @ -370,20 +370,20 @@ class PayrollEntry(Document): | ||||
| 
 | ||||
| 		exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(self.payment_account, je_payment_amount, company_currency, currencies) | ||||
| 		accounts.append({ | ||||
| 				"account": self.payment_account, | ||||
| 				"bank_account": self.bank_account, | ||||
| 				"credit_in_account_currency": flt(amount, precision), | ||||
| 				"exchange_rate": flt(exchange_rate), | ||||
| 			}) | ||||
| 			"account": self.payment_account, | ||||
| 			"bank_account": self.bank_account, | ||||
| 			"credit_in_account_currency": flt(amount, precision), | ||||
| 			"exchange_rate": flt(exchange_rate), | ||||
| 		}) | ||||
| 
 | ||||
| 		exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, je_payment_amount, company_currency, currencies) | ||||
| 		accounts.append({ | ||||
| 				"account": payroll_payable_account, | ||||
| 				"debit_in_account_currency": flt(amount, precision), | ||||
| 				"exchange_rate": flt(exchange_rate), | ||||
| 				"reference_type": self.doctype, | ||||
| 				"reference_name": self.name | ||||
| 			}) | ||||
| 			"account": payroll_payable_account, | ||||
| 			"debit_in_account_currency": flt(amount, precision), | ||||
| 			"exchange_rate": flt(exchange_rate), | ||||
| 			"reference_type": self.doctype, | ||||
| 			"reference_name": self.name | ||||
| 		}) | ||||
| 
 | ||||
| 		if len(currencies) > 1: | ||||
| 				multi_currency = 1 | ||||
| @ -409,6 +409,7 @@ class PayrollEntry(Document): | ||||
| 		self.update(get_start_end_dates(self.payroll_frequency, | ||||
| 			self.start_date or self.posting_date, self.company)) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def validate_employee_attendance(self): | ||||
| 		employees_to_mark_attendance = [] | ||||
| 		days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0 | ||||
| @ -424,7 +425,7 @@ class PayrollEntry(Document): | ||||
| 				employees_to_mark_attendance.append({ | ||||
| 					"employee": employee_detail.employee, | ||||
| 					"employee_name": employee_detail.employee_name | ||||
| 					}) | ||||
| 				}) | ||||
| 		return employees_to_mark_attendance | ||||
| 
 | ||||
| 	def get_count_holidays_of_employee(self, employee, start_date): | ||||
| @ -441,11 +442,11 @@ class PayrollEntry(Document): | ||||
| 	def get_count_employee_attendance(self, employee, start_date): | ||||
| 		marked_days = 0 | ||||
| 		attendances = frappe.get_all("Attendance", | ||||
| 				fields = ["count(*)"], | ||||
| 				filters = { | ||||
| 					"employee": employee, | ||||
| 					"attendance_date": ('between', [start_date, self.end_date]) | ||||
| 				}, as_list=1) | ||||
| 			fields = ["count(*)"], | ||||
| 			filters = { | ||||
| 				"employee": employee, | ||||
| 				"attendance_date": ('between', [start_date, self.end_date]) | ||||
| 			}, as_list=1) | ||||
| 		if attendances and attendances[0][0]: | ||||
| 			marked_days = attendances[0][0] | ||||
| 		return marked_days | ||||
| @ -553,6 +554,7 @@ def payroll_entry_has_bank_entries(name): | ||||
| def create_salary_slips_for_employees(employees, args, publish_progress=True): | ||||
| 	salary_slips_exists_for = get_existing_salary_slips(employees, args) | ||||
| 	count=0 | ||||
| 	salary_slips_not_created = [] | ||||
| 	for emp in employees: | ||||
| 		if emp not in salary_slips_exists_for: | ||||
| 			args.update({ | ||||
| @ -566,33 +568,24 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True): | ||||
| 				frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)), | ||||
| 					title = _("Creating Salary Slips...")) | ||||
| 		else: | ||||
| 			salary_slip_name = frappe.db.sql( | ||||
| 				'''SELECT | ||||
| 						name | ||||
| 					FROM `tabSalary Slip` | ||||
| 					WHERE company=%s | ||||
| 					AND start_date >= %s | ||||
| 					AND end_date <= %s | ||||
| 					AND employee = %s | ||||
| 				''', (args.company, args.start_date, args.end_date, emp), as_dict=True) | ||||
| 
 | ||||
| 			salary_slip_doc = frappe.get_doc('Salary Slip', salary_slip_name[0].name) | ||||
| 			salary_slip_doc.exchange_rate = args.exchange_rate | ||||
| 			salary_slip_doc.set_totals() | ||||
| 			salary_slip_doc.db_update() | ||||
| 			salary_slips_not_created.append(emp) | ||||
| 
 | ||||
| 	payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry) | ||||
| 	payroll_entry.db_set("salary_slips_created", 1) | ||||
| 	payroll_entry.notify_update() | ||||
| 
 | ||||
| 	if salary_slips_not_created: | ||||
| 		frappe.msgprint(_("Salary Slips already exists for employees {}, and will not be processed by this payroll.") | ||||
| 			.format(frappe.bold(", ".join([emp for emp in salary_slips_not_created]))) , title=_("Message"), indicator="orange") | ||||
| 
 | ||||
| def get_existing_salary_slips(employees, args): | ||||
| 	return frappe.db.sql_list(""" | ||||
| 		select distinct employee from `tabSalary Slip` | ||||
| 		where docstatus!= 2 and company = %s | ||||
| 		where docstatus!= 2 and company = %s and payroll_entry = %s | ||||
| 			and start_date >= %s and end_date <= %s | ||||
| 			and employee in (%s) | ||||
| 	""" % ('%s', '%s', '%s', ', '.join(['%s']*len(employees))), | ||||
| 		[args.company, args.start_date, args.end_date] + employees) | ||||
| 	""" % ('%s', '%s', '%s', '%s', ', '.join(['%s']*len(employees))), | ||||
| 		[args.company, args.payroll_entry, args.start_date, args.end_date] + employees) | ||||
| 
 | ||||
| def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True): | ||||
| 	submitted_ss = [] | ||||
| @ -644,3 +637,61 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte | ||||
| 			'txt': "%%%s%%" % frappe.db.escape(txt), | ||||
| 			'start': start, 'page_len': page_len | ||||
| 		}) | ||||
| 
 | ||||
| def get_employee_with_existing_salary_slip(start_date, end_date, company): | ||||
| 	return frappe.db.sql_list(""" | ||||
| 		select employee from `tabSalary Slip` | ||||
| 		where | ||||
| 			(start_date between %(start_date)s and %(end_date)s | ||||
| 		or | ||||
| 			end_date between %(start_date)s and %(end_date)s | ||||
| 		or | ||||
| 			%(start_date)s between start_date and end_date) | ||||
| 		and company = %(company)s | ||||
| 		and docstatus = 1 | ||||
| 	""", {'start_date': start_date, 'end_date': end_date, 'company': company}) | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def employee_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	filters = frappe._dict(filters) | ||||
| 	conditions = [] | ||||
| 	exclude_employees = [] | ||||
| 	emp_cond = '' | ||||
| 	if filters.start_date and filters.end_date: | ||||
| 		employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date, filters.company) | ||||
| 		emp = filters.get('employees') | ||||
| 		filters.pop('start_date') | ||||
| 		filters.pop('end_date') | ||||
| 		if filters.employees is not None: | ||||
| 			filters.pop('employees') | ||||
| 		if employee_list: | ||||
| 			exclude_employees.extend(employee_list) | ||||
| 		if emp: | ||||
| 			exclude_employees.extend(emp) | ||||
| 		if exclude_employees: | ||||
| 			emp_cond += 'and employee not in %(exclude_employees)s' | ||||
| 
 | ||||
| 	return frappe.db.sql("""select name, employee_name from `tabEmployee` | ||||
| 		where status = 'Active' | ||||
| 			and docstatus < 2 | ||||
| 			and ({key} like %(txt)s | ||||
| 				or employee_name like %(txt)s) | ||||
| 			{emp_cond} | ||||
| 			{fcond} {mcond} | ||||
| 		order by | ||||
| 			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), | ||||
| 			if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999), | ||||
| 			idx desc, | ||||
| 			name, employee_name | ||||
| 		limit %(start)s, %(page_len)s""".format(**{ | ||||
| 			'key': searchfield, | ||||
| 			'fcond': get_filters_cond(doctype, filters, conditions), | ||||
| 			'mcond': get_match_cond(doctype), | ||||
| 			'emp_cond': emp_cond | ||||
| 		}), { | ||||
| 			'txt': "%%%s%%" % txt, | ||||
| 			'_txt': txt.replace("%", ""), | ||||
| 			'start': start, | ||||
| 			'page_len': page_len, | ||||
| 			'exclude_employees': exclude_employees}) | ||||
|  | ||||
| @ -51,21 +51,22 @@ class TestPayrollEntry(unittest.TestCase): | ||||
| 
 | ||||
| 		company_doc = frappe.get_doc('Company', company) | ||||
| 		salary_structure = make_salary_structure("_Test Multi Currency Salary Structure", "Monthly", company=company, currency='USD') | ||||
| 		create_salary_structure_assignment(employee, salary_structure.name, company=company) | ||||
| 		create_salary_structure_assignment(employee, salary_structure.name, company=company, currency='USD') | ||||
| 		frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""",(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"}))) | ||||
| 		salary_slip = get_salary_slip("test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure") | ||||
| 		dates = get_start_end_dates('Monthly', nowdate()) | ||||
| 		payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,  | ||||
| 		payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, | ||||
| 			payable_account=company_doc.default_payroll_payable_account, currency='USD', exchange_rate=70) | ||||
| 		payroll_entry.make_payment_entry() | ||||
| 
 | ||||
| 		salary_slip.load_from_db() | ||||
| 
 | ||||
| 		payroll_je = salary_slip.journal_entry | ||||
| 		payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je) | ||||
| 		if payroll_je: | ||||
| 			payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je) | ||||
| 
 | ||||
| 		self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit) | ||||
| 		self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit) | ||||
| 			self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit) | ||||
| 			self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit) | ||||
| 
 | ||||
| 		payment_entry = frappe.db.sql(''' | ||||
| 			Select ifnull(sum(je.total_debit),0) as total_debit, ifnull(sum(je.total_credit),0) as total_credit from `tabJournal Entry` je, `tabJournal Entry Account` jea | ||||
|  | ||||
| @ -39,7 +39,8 @@ frappe.ui.form.on("Salary Slip", { | ||||
| 
 | ||||
| 		frm.set_query("employee", function() { | ||||
| 			return { | ||||
| 				query: "erpnext.controllers.queries.employee_query" | ||||
| 				query: "erpnext.controllers.queries.employee_query", | ||||
| 				filters: frm.doc.company | ||||
| 			}; | ||||
| 		}); | ||||
| 	}, | ||||
| @ -93,28 +94,31 @@ frappe.ui.form.on("Salary Slip", { | ||||
| 	}, | ||||
| 
 | ||||
| 	set_exchange_rate: function(frm, company_currency) { | ||||
| 		if (frm.doc.currency) { | ||||
| 			var from_currency = frm.doc.currency; | ||||
| 			if (from_currency != company_currency) { | ||||
| 				frm.events.hide_loan_section(frm); | ||||
| 				frappe.call({ | ||||
| 					method: "erpnext.setup.utils.get_exchange_rate", | ||||
| 					args: { | ||||
| 						from_currency: from_currency, | ||||
| 						to_currency: company_currency, | ||||
| 					}, | ||||
| 					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); | ||||
| 					} | ||||
| 				}); | ||||
| 			} else { | ||||
| 				frm.set_value("exchange_rate", 1.0); | ||||
| 				frm.set_df_property("exchange_rate", "hidden", 1); | ||||
| 				frm.set_df_property("exchange_rate", "description", ""); | ||||
| 			} | ||||
| 		if (frm.doc.docstatus === 0) { | ||||
| 			if (frm.doc.currency) { | ||||
| 				var from_currency = frm.doc.currency; | ||||
| 				if (from_currency != company_currency) { | ||||
| 					frm.events.hide_loan_section(frm); | ||||
| 					frappe.call({ | ||||
| 						method: "erpnext.setup.utils.get_exchange_rate", | ||||
| 						args: { | ||||
| 							from_currency: from_currency, | ||||
| 							to_currency: company_currency, | ||||
| 						}, | ||||
| 						callback: function(r) { | ||||
| 							if (r.message) { | ||||
| 								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); | ||||
| 							} | ||||
| 						} | ||||
| 					}); | ||||
| 				} else { | ||||
| 					frm.set_value("exchange_rate", 1.0); | ||||
| 					frm.set_df_property('exchange_rate', 'hidden', 1); | ||||
| 					frm.set_df_property("exchange_rate", "description", "" ); | ||||
| 				} | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
|  | ||||
| @ -124,9 +124,12 @@ class SalarySlip(TransactionBase): | ||||
| 
 | ||||
| 	def check_existing(self): | ||||
| 		if not self.salary_slip_based_on_timesheet: | ||||
| 			cond = "" | ||||
| 			if self.payroll_entry: | ||||
| 				cond += "and payroll_entry = '{0}'".format(self.payroll_entry) | ||||
| 			ret_exist = frappe.db.sql("""select name from `tabSalary Slip` | ||||
| 						where start_date = %s and end_date = %s and docstatus != 2 | ||||
| 						and employee = %s and name != %s""", | ||||
| 						and employee = %s and name != %s {0}""".format(cond), | ||||
| 						(self.start_date, self.end_date, self.employee, self.name)) | ||||
| 			if ret_exist: | ||||
| 				self.employee = '' | ||||
| @ -1053,7 +1056,7 @@ class SalarySlip(TransactionBase): | ||||
| 			repayment_entry.save() | ||||
| 			repayment_entry.submit() | ||||
| 
 | ||||
| 			loan.loan_repayment_entry = repayment_entry.name | ||||
| 			frappe.db.set_value("Salary Slip Loan", loan.name, "loan_repayment_entry", repayment_entry.name) | ||||
| 
 | ||||
| 	def cancel_loan_repayment_entry(self): | ||||
| 		for loan in self.loans: | ||||
|  | ||||
| @ -33,12 +33,16 @@ class TestProject(unittest.TestCase): | ||||
| 
 | ||||
| 	def test_project_template_having_parent_child_tasks(self): | ||||
| 		project_name = "Test Project with Template - Tasks with Parent-Child Relation" | ||||
| 
 | ||||
| 		if frappe.db.get_value('Project', {'project_name': project_name}, 'name'): | ||||
| 			project_name = frappe.db.get_value('Project', {'project_name': project_name}, 'name') | ||||
| 
 | ||||
| 		frappe.db.sql(""" delete from tabTask where project = %s """, project_name) | ||||
| 		frappe.delete_doc('Project', project_name) | ||||
| 
 | ||||
| 		task1 = task_exists("Test Template Task Parent") | ||||
| 		if not task1: | ||||
| 			task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=4) | ||||
| 			task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=10) | ||||
| 
 | ||||
| 		task2 = task_exists("Test Template Task Child 1") | ||||
| 		if not task2: | ||||
| @ -53,7 +57,7 @@ class TestProject(unittest.TestCase): | ||||
| 		tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks', 'name', 'parent_task'], dict(project=project.name), order_by='creation asc') | ||||
| 
 | ||||
| 		self.assertEqual(tasks[0].subject, 'Test Template Task Parent') | ||||
| 		self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 4)) | ||||
| 		self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 10)) | ||||
| 
 | ||||
| 		self.assertEqual(tasks[1].subject, 'Test Template Task Child 1') | ||||
| 		self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 1, 3)) | ||||
|  | ||||
| @ -737,28 +737,34 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ | ||||
| 				this.frm.trigger("item_code", cdt, cdn); | ||||
| 			} | ||||
| 			else { | ||||
| 				var valid_serial_nos = []; | ||||
| 				var serialnos = []; | ||||
| 				// Replacing all occurences of comma with carriage return
 | ||||
| 				item.serial_no = item.serial_no.replace(/,/g, '\n'); | ||||
| 				serialnos = item.serial_no.split("\n"); | ||||
| 				for (var i = 0; i < serialnos.length; i++) { | ||||
| 					if (serialnos[i] != "") { | ||||
| 						valid_serial_nos.push(serialnos[i]); | ||||
| 					} | ||||
| 				} | ||||
| 				item.conversion_factor = item.conversion_factor || 1; | ||||
| 
 | ||||
| 				refresh_field("serial_no", item.name, item.parentfield); | ||||
| 				if(!doc.is_return && cint(user_defaults.set_qty_in_transactions_based_on_serial_no_input)) { | ||||
| 					frappe.model.set_value(item.doctype, item.name, | ||||
| 						"qty", valid_serial_nos.length / item.conversion_factor); | ||||
| 					frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length); | ||||
| 				if (!doc.is_return && cint(frappe.user_defaults.set_qty_in_transactions_based_on_serial_no_input)) { | ||||
| 					setTimeout(() => { | ||||
| 						me.update_qty(cdt, cdn); | ||||
| 					}, 10000); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	update_qty: function(cdt, cdn) { | ||||
| 		var valid_serial_nos = []; | ||||
| 		var serialnos = []; | ||||
| 		var item = frappe.get_doc(cdt, cdn); | ||||
| 		serialnos = item.serial_no.split("\n"); | ||||
| 		for (var i = 0; i < serialnos.length; i++) { | ||||
| 			if (serialnos[i] != "") { | ||||
| 				valid_serial_nos.push(serialnos[i]); | ||||
| 			} | ||||
| 		} | ||||
| 		frappe.model.set_value(item.doctype, item.name, | ||||
| 			"qty", valid_serial_nos.length / item.conversion_factor); | ||||
| 		frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length); | ||||
| 	}, | ||||
| 
 | ||||
| 	validate: function() { | ||||
| 		this.calculate_taxes_and_totals(false); | ||||
| 	}, | ||||
|  | ||||
| @ -787,6 +787,8 @@ class GSPConnector(): | ||||
| 
 | ||||
| 		self.invoice.irn = res.get('Irn') | ||||
| 		self.invoice.ewaybill = res.get('EwbNo') | ||||
| 		self.invoice.ack_no = res.get('AckNo') | ||||
| 		self.invoice.ack_date = res.get('AckDt') | ||||
| 		self.invoice.signed_einvoice = dec_signed_invoice | ||||
| 		self.invoice.signed_qr_code = res.get('SignedQRCode') | ||||
| 
 | ||||
|  | ||||
| @ -1,8 +0,0 @@ | ||||
| // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| 
 | ||||
| frappe.ui.form.on('Lead Source', { | ||||
| 	refresh: function(frm) { | ||||
| 
 | ||||
| 	} | ||||
| }); | ||||
| @ -1,131 +0,0 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 1,  | ||||
|  "autoname": "field:source_name",  | ||||
|  "beta": 0,  | ||||
|  "creation": "2016-09-16 01:47:47.382372",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "",  | ||||
|  "editable_grid": 1,  | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "source_name",  | ||||
|    "fieldtype": "Data",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "label": "Source Name",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "details",  | ||||
|    "fieldtype": "Text Editor",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "label": "Details",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   } | ||||
|  ],  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "idx": 0,  | ||||
|  "image_view": 0,  | ||||
|  "in_create": 0,  | ||||
| 
 | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 0,  | ||||
|  "max_attachments": 0,  | ||||
|  "modified": "2020-09-16 02:03:01.441622",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Selling",  | ||||
|  "name": "Lead Source",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "apply_user_permissions": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 1,  | ||||
|    "delete": 1,  | ||||
|    "email": 1,  | ||||
|    "export": 1,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 1,  | ||||
|    "read": 1,  | ||||
|    "report": 1,  | ||||
|    "role": "Sales Manager",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 1,  | ||||
|    "submit": 0,  | ||||
|    "write": 1 | ||||
|   },  | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "apply_user_permissions": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 1,  | ||||
|    "delete": 0,  | ||||
|    "email": 1,  | ||||
|    "export": 1,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 1,  | ||||
|    "read": 1,  | ||||
|    "report": 1,  | ||||
|    "role": "Sales User",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 1,  | ||||
|    "submit": 0,  | ||||
|    "write": 1 | ||||
|   } | ||||
|  ],  | ||||
|  "quick_entry": 1,  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC",  | ||||
|  "track_seen": 0 | ||||
| } | ||||
| @ -17,7 +17,7 @@ frappe.ui.form.on('Global Defaults', { | ||||
| 			method: "frappe.client.get_list", | ||||
| 			args: { | ||||
| 				doctype: "UOM Conversion Factor", | ||||
| 				filters: { "category": "Length" }, | ||||
| 				filters: { "category": __("Length") }, | ||||
| 				fields: ["to_uom"], | ||||
| 				limit_page_length: 500 | ||||
| 			}, | ||||
|  | ||||
| @ -1,14 +1,14 @@ | ||||
| frappe.provide('erpnext.stock'); | ||||
| 
 | ||||
| erpnext.stock.ItemDashboard = Class.extend({ | ||||
| 	init: function(opts) { | ||||
| 	init: function (opts) { | ||||
| 		$.extend(this, opts); | ||||
| 		this.make(); | ||||
| 	}, | ||||
| 	make: function() { | ||||
| 	make: function () { | ||||
| 		var me = this; | ||||
| 		this.start = 0; | ||||
| 		if(!this.sort_by) { | ||||
| 		if (!this.sort_by) { | ||||
| 			this.sort_by = 'projected_qty'; | ||||
| 			this.sort_order = 'asc'; | ||||
| 		} | ||||
| @ -16,22 +16,25 @@ erpnext.stock.ItemDashboard = Class.extend({ | ||||
| 		this.content = $(frappe.render_template('item_dashboard')).appendTo(this.parent); | ||||
| 		this.result = this.content.find('.result'); | ||||
| 
 | ||||
| 		this.content.on('click', '.btn-move', function() { | ||||
| 			handle_move_add($(this), "Move") | ||||
| 		this.content.on('click', '.btn-move', function () { | ||||
| 			handle_move_add($(this), "Move"); | ||||
| 		}); | ||||
| 
 | ||||
| 		this.content.on('click', '.btn-add', function() { | ||||
| 			handle_move_add($(this), "Add") | ||||
| 		this.content.on('click', '.btn-add', function () { | ||||
| 			handle_move_add($(this), "Add"); | ||||
| 		}); | ||||
| 
 | ||||
| 		this.content.on('click', '.btn-edit', function() { | ||||
| 		this.content.on('click', '.btn-edit', function () { | ||||
| 			let item = unescape($(this).attr('data-item')); | ||||
| 			let warehouse = unescape($(this).attr('data-warehouse')); | ||||
| 			let company = unescape($(this).attr('data-company')); | ||||
| 			frappe.db.get_value('Putaway Rule', | ||||
| 				{'item_code': item, 'warehouse': warehouse, 'company': company}, 'name', (r) => { | ||||
| 					frappe.set_route("Form", "Putaway Rule", r.name); | ||||
| 				}); | ||||
| 			frappe.db.get_value('Putaway Rule', { | ||||
| 				'item_code': item, | ||||
| 				'warehouse': warehouse, | ||||
| 				'company': company | ||||
| 			}, 'name', (r) => { | ||||
| 				frappe.set_route("Form", "Putaway Rule", r.name); | ||||
| 			}); | ||||
| 		}); | ||||
| 
 | ||||
| 		function handle_move_add(element, action) { | ||||
| @ -39,23 +42,26 @@ erpnext.stock.ItemDashboard = Class.extend({ | ||||
| 			let warehouse = unescape(element.attr('data-warehouse')); | ||||
| 			let actual_qty = unescape(element.attr('data-actual_qty')); | ||||
| 			let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry'))); | ||||
| 			let entry_type = action === "Move" ? "Material Transfer": null; | ||||
| 			let entry_type = action === "Move" ? "Material Transfer" : null; | ||||
| 
 | ||||
| 			if (disable_quick_entry) { | ||||
| 				open_stock_entry(item, warehouse, entry_type); | ||||
| 			} else { | ||||
| 				if (action === "Add") { | ||||
| 					let rate = unescape($(this).attr('data-rate')); | ||||
| 					erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function() { me.refresh(); }); | ||||
| 				} | ||||
| 				else { | ||||
| 					erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function() { me.refresh(); }); | ||||
| 					erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function () { | ||||
| 						me.refresh(); | ||||
| 					}); | ||||
| 				} else { | ||||
| 					erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function () { | ||||
| 						me.refresh(); | ||||
| 					}); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		function open_stock_entry(item, warehouse, entry_type) { | ||||
| 			frappe.model.with_doctype('Stock Entry', function() { | ||||
| 			frappe.model.with_doctype('Stock Entry', function () { | ||||
| 				var doc = frappe.model.get_new_doc('Stock Entry'); | ||||
| 				if (entry_type) doc.stock_entry_type = entry_type; | ||||
| 
 | ||||
| @ -64,18 +70,18 @@ erpnext.stock.ItemDashboard = Class.extend({ | ||||
| 				row.s_warehouse = warehouse; | ||||
| 
 | ||||
| 				frappe.set_route('Form', doc.doctype, doc.name); | ||||
| 			}) | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		// more
 | ||||
| 		this.content.find('.btn-more').on('click', function() { | ||||
| 		this.content.find('.btn-more').on('click', function () { | ||||
| 			me.start += me.page_length; | ||||
| 			me.refresh(); | ||||
| 		}); | ||||
| 
 | ||||
| 	}, | ||||
| 	refresh: function() { | ||||
| 		if(this.before_refresh) { | ||||
| 	refresh: function () { | ||||
| 		if (this.before_refresh) { | ||||
| 			this.before_refresh(); | ||||
| 		} | ||||
| 
 | ||||
| @ -94,13 +100,13 @@ erpnext.stock.ItemDashboard = Class.extend({ | ||||
| 		frappe.call({ | ||||
| 			method: this.method, | ||||
| 			args: args, | ||||
| 			callback: function(r) { | ||||
| 			callback: function (r) { | ||||
| 				me.render(r.message); | ||||
| 			} | ||||
| 		}); | ||||
| 	}, | ||||
| 	render: function(data) { | ||||
| 		if (this.start===0) { | ||||
| 	render: function (data) { | ||||
| 		if (this.start === 0) { | ||||
| 			this.max_count = 0; | ||||
| 			this.result.empty(); | ||||
| 		} | ||||
| @ -115,7 +121,7 @@ erpnext.stock.ItemDashboard = Class.extend({ | ||||
| 		this.max_count = this.max_count; | ||||
| 
 | ||||
| 		// show more button
 | ||||
| 		if (data && data.length===(this.page_length + 1)) { | ||||
| 		if (data && data.length === (this.page_length + 1)) { | ||||
| 			this.content.find('.more').removeClass('hidden'); | ||||
| 
 | ||||
| 			// remove the last element
 | ||||
| @ -137,15 +143,15 @@ erpnext.stock.ItemDashboard = Class.extend({ | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	get_item_dashboard_data: function(data, max_count, show_item) { | ||||
| 		if(!max_count) max_count = 0; | ||||
| 		if(!data) data = []; | ||||
| 	get_item_dashboard_data: function (data, max_count, show_item) { | ||||
| 		if (!max_count) max_count = 0; | ||||
| 		if (!data) data = []; | ||||
| 
 | ||||
| 		data.forEach(function(d) { | ||||
| 		data.forEach(function (d) { | ||||
| 			d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract; | ||||
| 			d.pending_qty = 0; | ||||
| 			d.total_reserved = d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract; | ||||
| 			if(d.actual_or_pending > d.actual_qty) { | ||||
| 			if (d.actual_or_pending > d.actual_qty) { | ||||
| 				d.pending_qty = d.actual_or_pending - d.actual_qty; | ||||
| 			} | ||||
| 
 | ||||
| @ -161,16 +167,16 @@ erpnext.stock.ItemDashboard = Class.extend({ | ||||
| 		return { | ||||
| 			data: data, | ||||
| 			max_count: max_count, | ||||
| 			can_write:can_write, | ||||
| 			can_write: can_write, | ||||
| 			show_item: show_item || false | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	get_capacity_dashboard_data: function(data) { | ||||
| 	get_capacity_dashboard_data: function (data) { | ||||
| 		if (!data) data = []; | ||||
| 
 | ||||
| 		data.forEach(function(d) { | ||||
| 			d.color =  d.percent_occupied >=80 ? "#f8814f" : "#2490ef"; | ||||
| 		data.forEach(function (d) { | ||||
| 			d.color = d.percent_occupied >= 80 ? "#f8814f" : "#2490ef"; | ||||
| 		}); | ||||
| 
 | ||||
| 		let can_write = 0; | ||||
| @ -185,53 +191,77 @@ erpnext.stock.ItemDashboard = Class.extend({ | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callback) { | ||||
| erpnext.stock.move_item = function (item, source, target, actual_qty, rate, callback) { | ||||
| 	var dialog = new frappe.ui.Dialog({ | ||||
| 		title: target ? __('Add Item') : __('Move Item'), | ||||
| 		fields: [ | ||||
| 			{fieldname: 'item_code', label: __('Item'), | ||||
| 				fieldtype: 'Link', options: 'Item', read_only: 1}, | ||||
| 			{fieldname: 'source', label: __('Source Warehouse'), | ||||
| 				fieldtype: 'Link', options: 'Warehouse', read_only: 1}, | ||||
| 			{fieldname: 'target', label: __('Target Warehouse'), | ||||
| 				fieldtype: 'Link', options: 'Warehouse', reqd: 1}, | ||||
| 			{fieldname: 'qty', label: __('Quantity'), reqd: 1, | ||||
| 				fieldtype: 'Float', description: __('Available {0}', [actual_qty]) }, | ||||
| 			{fieldname: 'rate', label: __('Rate'), fieldtype: 'Currency', hidden: 1 }, | ||||
| 		fields: [{ | ||||
| 			fieldname: 'item_code', | ||||
| 			label: __('Item'), | ||||
| 			fieldtype: 'Link', | ||||
| 			options: 'Item', | ||||
| 			read_only: 1 | ||||
| 		}, | ||||
| 		{ | ||||
| 			fieldname: 'source', | ||||
| 			label: __('Source Warehouse'), | ||||
| 			fieldtype: 'Link', | ||||
| 			options: 'Warehouse', | ||||
| 			read_only: 1 | ||||
| 		}, | ||||
| 		{ | ||||
| 			fieldname: 'target', | ||||
| 			label: __('Target Warehouse'), | ||||
| 			fieldtype: 'Link', | ||||
| 			options: 'Warehouse', | ||||
| 			reqd: 1 | ||||
| 		}, | ||||
| 		{ | ||||
| 			fieldname: 'qty', | ||||
| 			label: __('Quantity'), | ||||
| 			reqd: 1, | ||||
| 			fieldtype: 'Float', | ||||
| 			description: __('Available {0}', [actual_qty]) | ||||
| 		}, | ||||
| 		{ | ||||
| 			fieldname: 'rate', | ||||
| 			label: __('Rate'), | ||||
| 			fieldtype: 'Currency', | ||||
| 			hidden: 1 | ||||
| 		}, | ||||
| 		], | ||||
| 	}) | ||||
| 	}); | ||||
| 	dialog.show(); | ||||
| 	dialog.get_field('item_code').set_input(item); | ||||
| 
 | ||||
| 	if(source) { | ||||
| 	if (source) { | ||||
| 		dialog.get_field('source').set_input(source); | ||||
| 	} else { | ||||
| 		dialog.get_field('source').df.hidden = 1; | ||||
| 		dialog.get_field('source').refresh(); | ||||
| 	} | ||||
| 
 | ||||
| 	if(rate) { | ||||
| 	if (rate) { | ||||
| 		dialog.get_field('rate').set_value(rate); | ||||
| 		dialog.get_field('rate').df.hidden = 0; | ||||
| 		dialog.get_field('rate').refresh(); | ||||
| 	} | ||||
| 
 | ||||
| 	if(target) { | ||||
| 	if (target) { | ||||
| 		dialog.get_field('target').df.read_only = 1; | ||||
| 		dialog.get_field('target').value = target; | ||||
| 		dialog.get_field('target').refresh(); | ||||
| 	} | ||||
| 
 | ||||
| 	dialog.set_primary_action(__('Submit'), function() { | ||||
| 	dialog.set_primary_action(__('Submit'), function () { | ||||
| 		var values = dialog.get_values(); | ||||
| 		if(!values) { | ||||
| 		if (!values) { | ||||
| 			return; | ||||
| 		} | ||||
| 		if(source && values.qty > actual_qty) { | ||||
| 		if (source && values.qty > actual_qty) { | ||||
| 			frappe.msgprint(__('Quantity must be less than or equal to {0}', [actual_qty])); | ||||
| 			return; | ||||
| 		} | ||||
| 		if(values.source === values.target) { | ||||
| 		if (values.source === values.target) { | ||||
| 			frappe.msgprint(__('Source and target warehouse must be different')); | ||||
| 		} | ||||
| 
 | ||||
| @ -239,21 +269,21 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb | ||||
| 			method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', | ||||
| 			args: values, | ||||
| 			freeze: true, | ||||
| 			callback: function(r) { | ||||
| 			callback: function (r) { | ||||
| 				frappe.show_alert(__('Stock Entry {0} created', | ||||
| 					['<a href="/app/stock-entry/'+r.message.name+'">' + r.message.name+ '</a>'])); | ||||
| 					['<a href="/app/stock-entry/' + r.message.name + '">' + r.message.name + '</a>'])); | ||||
| 				dialog.hide(); | ||||
| 				callback(r); | ||||
| 			}, | ||||
| 		}); | ||||
| 	}); | ||||
| 
 | ||||
| 	$('<p style="margin-left: 10px;"><a class="link-open text-muted small">' | ||||
| 		+ __("Add more items or open full form") + '</a></p>') | ||||
| 	$('<p style="margin-left: 10px;"><a class="link-open text-muted small">' + | ||||
| 			__("Add more items or open full form") + '</a></p>') | ||||
| 		.appendTo(dialog.body) | ||||
| 		.find('.link-open') | ||||
| 		.on('click', function() { | ||||
| 			frappe.model.with_doctype('Stock Entry', function() { | ||||
| 		.on('click', function () { | ||||
| 			frappe.model.with_doctype('Stock Entry', function () { | ||||
| 				var doc = frappe.model.get_new_doc('Stock Entry'); | ||||
| 				doc.from_warehouse = dialog.get_value('source'); | ||||
| 				doc.to_warehouse = dialog.get_value('target'); | ||||
| @ -266,6 +296,6 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb | ||||
| 				row.transfer_qty = dialog.get_value('qty'); | ||||
| 				row.basic_rate = dialog.get_value('rate'); | ||||
| 				frappe.set_route('Form', doc.doctype, doc.name); | ||||
| 			}) | ||||
| 			}); | ||||
| 		}); | ||||
| } | ||||
| }; | ||||
|  | ||||
| @ -2,6 +2,7 @@ from __future__ import unicode_literals | ||||
| 
 | ||||
| import frappe | ||||
| from frappe.model.db_query import DatabaseQuery | ||||
| from frappe.utils import flt, cint | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_data(item_code=None, warehouse=None, item_group=None, | ||||
| @ -42,11 +43,20 @@ def get_data(item_code=None, warehouse=None, item_group=None, | ||||
| 		limit_start=start, | ||||
| 		limit_page_length='21') | ||||
| 
 | ||||
| 	precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) | ||||
| 
 | ||||
| 	for item in items: | ||||
| 		item.update({ | ||||
| 			'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'), | ||||
| 			'disable_quick_entry': frappe.get_cached_value("Item", item.item_code, 'has_batch_no') | ||||
| 				or frappe.get_cached_value("Item", item.item_code, 'has_serial_no'), | ||||
| 			'item_name': frappe.get_cached_value( | ||||
| 				"Item", item.item_code, 'item_name'), | ||||
| 			'disable_quick_entry': frappe.get_cached_value( | ||||
| 				"Item", item.item_code, 'has_batch_no') | ||||
| 			or frappe.get_cached_value( | ||||
| 				"Item", item.item_code, 'has_serial_no'), | ||||
| 			'projected_qty': flt(item.projected_qty, precision), | ||||
| 			'reserved_qty': flt(item.reserved_qty, precision), | ||||
| 			'reserved_qty_for_production': flt(item.reserved_qty_for_production, precision), | ||||
| 			'reserved_qty_for_sub_contract': flt(item.reserved_qty_for_sub_contract, precision), | ||||
| 			'actual_qty': flt(item.actual_qty, precision), | ||||
| 		}) | ||||
| 
 | ||||
| 	return items | ||||
|  | ||||
| @ -494,7 +494,8 @@ def make_item_variant(): | ||||
| 
 | ||||
| test_records = frappe.get_test_records('Item') | ||||
| 
 | ||||
| def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=None): | ||||
| def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None, | ||||
| 	customer=None, is_purchase_item=None, opening_stock=None, company=None): | ||||
| 	if not frappe.db.exists("Item", item_code): | ||||
| 		item = frappe.new_doc("Item") | ||||
| 		item.item_code = item_code | ||||
| @ -509,7 +510,7 @@ def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, | ||||
| 		item.customer = customer or '' | ||||
| 		item.append("item_defaults", { | ||||
| 			"default_warehouse": warehouse or '_Test Warehouse - _TC', | ||||
| 			"company": "_Test Company" | ||||
| 			"company": company or "_Test Company" | ||||
| 		}) | ||||
| 		item.save() | ||||
| 	else: | ||||
|  | ||||
| @ -346,7 +346,7 @@ def create_delivery_note(source_name, target_doc=None): | ||||
| 
 | ||||
| 		if dn_item: | ||||
| 			dn_item.warehouse = location.warehouse | ||||
| 			dn_item.qty = location.picked_qty | ||||
| 			dn_item.qty = flt(location.picked_qty) / (flt(location.conversion_factor) or 1) | ||||
| 			dn_item.batch_no = location.batch_no | ||||
| 			dn_item.serial_no = location.serial_no | ||||
| 
 | ||||
|  | ||||
| @ -9,6 +9,7 @@ test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch'] | ||||
| 
 | ||||
| from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt | ||||
| from erpnext.stock.doctype.item.test_item import create_item | ||||
| from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note | ||||
| from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \ | ||||
| 		import EmptyStockReconciliationItemsError | ||||
| 
 | ||||
| @ -291,6 +292,61 @@ class TestPickList(unittest.TestCase): | ||||
| 		self.assertEqual(pick_list.locations[1].qty, 5) | ||||
| 		self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name) | ||||
| 
 | ||||
| 	def test_pick_list_for_items_with_multiple_UOM(self): | ||||
| 		purchase_receipt = make_purchase_receipt(item_code="_Test Item", qty=10) | ||||
| 		purchase_receipt.submit() | ||||
| 
 | ||||
| 		sales_order = frappe.get_doc({ | ||||
| 				'doctype': 'Sales Order', | ||||
| 				'customer': '_Test Customer', | ||||
| 				'company': '_Test Company', | ||||
| 				'items': [{ | ||||
| 					'item_code': '_Test Item', | ||||
| 					'qty': 1, | ||||
| 					'conversion_factor': 5, | ||||
| 					'delivery_date': frappe.utils.today() | ||||
| 				}, { | ||||
| 					'item_code': '_Test Item', | ||||
| 					'qty': 1, | ||||
| 					'conversion_factor': 1, | ||||
| 					'delivery_date': frappe.utils.today() | ||||
| 				}], | ||||
| 			}).insert() | ||||
| 		sales_order.submit() | ||||
| 
 | ||||
| 		pick_list = frappe.get_doc({ | ||||
| 			'doctype': 'Pick List', | ||||
| 			'company': '_Test Company', | ||||
| 			'customer': '_Test Customer', | ||||
| 			'items_based_on': 'Sales Order', | ||||
| 			'locations': [{ | ||||
| 				'item_code': '_Test Item', | ||||
| 				'qty': 1, | ||||
| 				'stock_qty': 5, | ||||
| 				'conversion_factor': 5, | ||||
| 				'sales_order': sales_order.name, | ||||
| 				'sales_order_item': sales_order.items[0].name , | ||||
| 			}, { | ||||
| 				'item_code': '_Test Item', | ||||
| 				'qty': 1, | ||||
| 				'stock_qty': 1, | ||||
| 				'conversion_factor': 1, | ||||
| 				'sales_order': sales_order.name, | ||||
| 				'sales_order_item': sales_order.items[1].name , | ||||
| 			}] | ||||
| 		}) | ||||
| 		pick_list.set_item_locations() | ||||
| 		pick_list.submit() | ||||
| 
 | ||||
| 		delivery_note = create_delivery_note(pick_list.name) | ||||
| 
 | ||||
| 		self.assertEqual(pick_list.locations[0].qty, delivery_note.items[0].qty) | ||||
| 		self.assertEqual(pick_list.locations[1].qty, delivery_note.items[1].qty) | ||||
| 		self.assertEqual(sales_order.items[0].conversion_factor, delivery_note.items[0].conversion_factor) | ||||
| 
 | ||||
| 		pick_list.cancel() | ||||
| 		sales_order.cancel() | ||||
| 		purchase_receipt.cancel() | ||||
| 
 | ||||
| 	# def test_pick_list_skips_items_in_expired_batch(self): | ||||
| 	# 	pass | ||||
|  | ||||
| @ -179,11 +179,15 @@ class TestStockEntry(unittest.TestCase): | ||||
| 	def test_material_transfer_gl_entry(self): | ||||
| 		company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') | ||||
| 
 | ||||
| 		mtn = make_stock_entry(item_code="_Test Item", source="Stores - TCP1", | ||||
| 		item_code = 'Hand Sanitizer - 001' | ||||
| 		create_item(item_code =item_code, is_stock_item = 1, | ||||
| 			is_purchase_item=1, opening_stock=1000, valuation_rate=10, company=company, warehouse="Stores - TCP1") | ||||
| 
 | ||||
| 		mtn = make_stock_entry(item_code=item_code, source="Stores - TCP1", | ||||
| 			target="Finished Goods - TCP1", qty=45, company=company) | ||||
| 
 | ||||
| 		self.check_stock_ledger_entries("Stock Entry", mtn.name, | ||||
| 			[["_Test Item", "Stores - TCP1", -45.0], ["_Test Item", "Finished Goods - TCP1", 45.0]]) | ||||
| 			[[item_code, "Stores - TCP1", -45.0], [item_code, "Finished Goods - TCP1", 45.0]]) | ||||
| 
 | ||||
| 		source_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse) | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user