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 = {} | 	accounts_dict = {} | ||||||
| 	for account in accounts: | 	for account in accounts: | ||||||
| 		accounts_dict.setdefault(account["account_name"], account) | 		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"]): | 		if account["parent_account"] and accounts_dict.get(account["parent_account"]): | ||||||
| 			accounts_dict[account["parent_account"]]["is_group"] = 1 | 			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 -*- | # -*- 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 | # For license information, please see license.txt | ||||||
| 
 | 
 | ||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| import frappe | # import frappe | ||||||
| from frappe.model.document import Document | from frappe.model.document import Document | ||||||
| 
 | 
 | ||||||
| class LeadSource(Document): | class LeadSource(Document): | ||||||
| @ -1,12 +1,10 @@ | |||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors | ||||||
| # See license.txt | # See license.txt | ||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| 
 | 
 | ||||||
| import frappe | # import frappe | ||||||
| import unittest | import unittest | ||||||
| 
 | 
 | ||||||
| # test_records = frappe.get_test_records('Lead Source') |  | ||||||
| 
 |  | ||||||
| class TestLeadSource(unittest.TestCase): | class TestLeadSource(unittest.TestCase): | ||||||
| 	pass | 	pass | ||||||
| @ -260,7 +260,10 @@ doc_events = { | |||||||
| 			"erpnext.regional.italy.utils.sales_invoice_on_cancel", | 			"erpnext.regional.italy.utils.sales_invoice_on_cancel", | ||||||
| 			"erpnext.erpnext_integrations.taxjar_integration.delete_transaction" | 			"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": { | 	"Purchase Invoice": { | ||||||
| 		"validate": [ | 		"validate": [ | ||||||
| @ -282,9 +285,6 @@ doc_events = { | |||||||
| 	('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): { | 	('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): { | ||||||
| 		'validate': ['erpnext.regional.india.utils.set_place_of_supply'] | 		'validate': ['erpnext.regional.india.utils.set_place_of_supply'] | ||||||
| 	}, | 	}, | ||||||
| 	('Sales Invoice', 'Purchase Invoice'): { |  | ||||||
| 		'validate': ['erpnext.regional.india.utils.validate_document_name'] |  | ||||||
| 	}, |  | ||||||
| 	"Contact": { | 	"Contact": { | ||||||
| 		"on_trash": "erpnext.support.doctype.issue.issue.update_issue", | 		"on_trash": "erpnext.support.doctype.issue.issue.update_issue", | ||||||
| 		"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations", | 		"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations", | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ | |||||||
|  "hide_custom": 0, |  "hide_custom": 0, | ||||||
|  "icon": "hr", |  "icon": "hr", | ||||||
|  "idx": 0, |  "idx": 0, | ||||||
|  |  "is_default": 0, | ||||||
|  "is_standard": 1, |  "is_standard": 1, | ||||||
|  "label": "HR", |  "label": "HR", | ||||||
|  "links": [ |  "links": [ | ||||||
| @ -226,42 +227,12 @@ | |||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Card Break" |    "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": "", |    "dependencies": "", | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
|    "is_query_report": 0, |    "is_query_report": 0, | ||||||
|    "label": "Leave Period", |    "label": "Holiday List", | ||||||
|    "link_to": "Leave Period", |    "link_to": "Holiday List", | ||||||
|    "link_type": "DocType", |    "link_type": "DocType", | ||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Link" |    "type": "Link" | ||||||
| @ -280,8 +251,28 @@ | |||||||
|    "dependencies": "", |    "dependencies": "", | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
|    "is_query_report": 0, |    "is_query_report": 0, | ||||||
|    "label": "Holiday List", |    "label": "Leave Period", | ||||||
|    "link_to": "Holiday List", |    "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", |    "link_type": "DocType", | ||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Link" |    "type": "Link" | ||||||
| @ -290,8 +281,18 @@ | |||||||
|    "dependencies": "Employee", |    "dependencies": "Employee", | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
|    "is_query_report": 0, |    "is_query_report": 0, | ||||||
|    "label": "Compensatory Leave Request", |    "label": "Leave Application", | ||||||
|    "link_to": "Compensatory Leave Request", |    "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", |    "link_type": "DocType", | ||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Link" |    "type": "Link" | ||||||
| @ -317,12 +318,12 @@ | |||||||
|    "type": "Link" |    "type": "Link" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "dependencies": "Leave Application", |    "dependencies": "Employee", | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
|    "is_query_report": 1, |    "is_query_report": 0, | ||||||
|    "label": "Employee Leave Balance", |    "label": "Compensatory Leave Request", | ||||||
|    "link_to": "Employee Leave Balance", |    "link_to": "Compensatory Leave Request", | ||||||
|    "link_type": "Report", |    "link_type": "DocType", | ||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Link" |    "type": "Link" | ||||||
|   }, |   }, | ||||||
| @ -383,16 +384,6 @@ | |||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Link" |    "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, |    "hidden": 0, | ||||||
|    "is_query_report": 0, |    "is_query_report": 0, | ||||||
| @ -420,6 +411,15 @@ | |||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Link" |    "type": "Link" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |    "hidden": 0, | ||||||
|  |    "is_query_report": 0, | ||||||
|  |    "label": "Travel Request", | ||||||
|  |    "link_to": "Travel Request", | ||||||
|  |    "link_type": "DocType", | ||||||
|  |    "onboard": 0, | ||||||
|  |    "type": "Link" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
|    "is_query_report": 0, |    "is_query_report": 0, | ||||||
| @ -464,6 +464,15 @@ | |||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Card Break" |    "type": "Card Break" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |    "hidden": 0, | ||||||
|  |    "is_query_report": 0, | ||||||
|  |    "label": "Driver", | ||||||
|  |    "link_to": "Driver", | ||||||
|  |    "link_type": "DocType", | ||||||
|  |    "onboard": 0, | ||||||
|  |    "type": "Link" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|    "dependencies": "", |    "dependencies": "", | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
| @ -541,6 +550,24 @@ | |||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Link" |    "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, |    "hidden": 0, | ||||||
|    "is_query_report": 0, |    "is_query_report": 0, | ||||||
| @ -625,33 +652,6 @@ | |||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Link" |    "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, |    "hidden": 0, | ||||||
|    "is_query_report": 0, |    "is_query_report": 0, | ||||||
| @ -702,7 +702,74 @@ | |||||||
|   { |   { | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
|    "is_query_report": 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, |    "onboard": 0, | ||||||
|    "type": "Card Break" |    "type": "Card Break" | ||||||
|   }, |   }, | ||||||
| @ -710,74 +777,44 @@ | |||||||
|    "dependencies": "Employee", |    "dependencies": "Employee", | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
|    "is_query_report": 0, |    "is_query_report": 0, | ||||||
|    "label": "Employee Tax Exemption Declaration", |    "label": "Employee Information", | ||||||
|    "link_to": "Employee Tax Exemption Declaration", |    "link_to": "Employee Information", | ||||||
|    "link_type": "DocType", |    "link_type": "Report", | ||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Link" |    "type": "Link" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "dependencies": "Employee", |    "dependencies": "Employee", | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
|    "is_query_report": 0, |    "is_query_report": 1, | ||||||
|    "label": "Employee Tax Exemption Proof Submission", |    "label": "Employee Birthday", | ||||||
|    "link_to": "Employee Tax Exemption Proof Submission", |    "link_to": "Employee Birthday", | ||||||
|    "link_type": "DocType", |    "link_type": "Report", | ||||||
|    "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", |  | ||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Link" |    "type": "Link" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "dependencies": "Employee", |    "dependencies": "Employee", | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
|    "is_query_report": 0, |    "is_query_report": 1, | ||||||
|    "label": "Employee Benefit Application", |    "label": "Employees Working on a Holiday", | ||||||
|    "link_to": "Employee Benefit Application", |    "link_to": "Employees working on a holiday", | ||||||
|    "link_type": "DocType", |    "link_type": "Report", | ||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Link" |    "type": "Link" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "dependencies": "Employee", |    "dependencies": "Daily Work Summary", | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
|    "is_query_report": 0, |    "is_query_report": 1, | ||||||
|    "label": "Employee Benefit Claim", |    "label": "Daily Work Summary Replies", | ||||||
|    "link_to": "Employee Benefit Claim", |    "link_to": "Daily Work Summary Replies", | ||||||
|    "link_type": "DocType", |    "link_type": "Report", | ||||||
|    "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", |  | ||||||
|    "onboard": 0, |    "onboard": 0, | ||||||
|    "type": "Link" |    "type": "Link" | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "modified": "2021-01-21 13:38:38.941001", |  "modified": "2021-03-24 17:35:21.483297", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "HR", |  "module": "HR", | ||||||
|  "name": "HR", |  "name": "HR", | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ class LoanRepayment(AccountsController): | |||||||
| 	def validate(self): | 	def validate(self): | ||||||
| 		amounts = calculate_amounts(self.against_loan, self.posting_date) | 		amounts = calculate_amounts(self.against_loan, self.posting_date) | ||||||
| 		self.set_missing_values(amounts) | 		self.set_missing_values(amounts) | ||||||
|  | 		self.check_future_entries() | ||||||
| 		self.validate_amount() | 		self.validate_amount() | ||||||
| 		self.allocate_amounts(amounts) | 		self.allocate_amounts(amounts) | ||||||
| 
 | 
 | ||||||
| @ -69,6 +70,13 @@ class LoanRepayment(AccountsController): | |||||||
| 		if amounts.get('due_date'): | 		if amounts.get('due_date'): | ||||||
| 			self.due_date = 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): | 	def validate_amount(self): | ||||||
| 		precision = cint(frappe.db.get_default("currency_precision")) or 2 | 		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 | 	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( | 	unpaid_accrued_entries = frappe.db.sql( | ||||||
| 		""" | 		""" | ||||||
| @ -318,12 +328,13 @@ def get_accrued_interest_entries(against_loan): | |||||||
| 				`tabLoan Interest Accrual` | 				`tabLoan Interest Accrual` | ||||||
| 			WHERE | 			WHERE | ||||||
| 				loan = %s | 				loan = %s | ||||||
|  | 			AND posting_date <= %s | ||||||
| 			AND (interest_amount - paid_interest_amount > 0 OR | 			AND (interest_amount - paid_interest_amount > 0 OR | ||||||
| 				payable_principal_amount - paid_principal_amount > 0) | 				payable_principal_amount - paid_principal_amount > 0) | ||||||
| 			AND | 			AND | ||||||
| 				docstatus = 1 | 				docstatus = 1 | ||||||
| 			ORDER BY posting_date | 			ORDER BY posting_date | ||||||
| 		""", (against_loan), as_dict=1) | 		""", (against_loan, posting_date), as_dict=1) | ||||||
| 
 | 
 | ||||||
| 	return unpaid_accrued_entries | 	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) | 	against_loan_doc = frappe.get_doc("Loan", against_loan) | ||||||
| 	loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type) | 	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 = {} | 	pending_accrual_entries = {} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -70,7 +70,9 @@ | |||||||
|   { |   { | ||||||
|    "fieldname": "loan_repayment_entry", |    "fieldname": "loan_repayment_entry", | ||||||
|    "fieldtype": "Link", |    "fieldtype": "Link", | ||||||
|  |    "hidden": 1, | ||||||
|    "label": "Loan Repayment Entry", |    "label": "Loan Repayment Entry", | ||||||
|  |    "no_copy": 1, | ||||||
|    "options": "Loan Repayment", |    "options": "Loan Repayment", | ||||||
|    "read_only": 1 |    "read_only": 1 | ||||||
|   }, |   }, | ||||||
| @ -83,9 +85,10 @@ | |||||||
|    "read_only": 1 |    "read_only": 1 | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  |  "index_web_pages_for_search": 1, | ||||||
|  "istable": 1, |  "istable": 1, | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-04-16 13:17:04.798335", |  "modified": "2021-03-14 20:47:11.725818", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Loan Management", |  "module": "Loan Management", | ||||||
|  "name": "Salary Slip Loan", |  "name": "Salary Slip Loan", | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ | |||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| import unittest | import unittest | ||||||
| import frappe | import frappe | ||||||
| from frappe.utils import cstr | from frappe.utils import cstr, flt | ||||||
| from frappe.test_runner import make_test_records | from frappe.test_runner import make_test_records | ||||||
| from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation | 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 | 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 = frappe.copy_doc(test_records[2]) | ||||||
| 		bom.insert() | 		bom.insert() | ||||||
| 
 | 
 | ||||||
| 		# test amounts in selected currency | 		raw_material_cost = 0.0 | ||||||
| 		self.assertEqual(bom.operating_cost, 100) | 		op_cost = 0.0 | ||||||
| 		self.assertEqual(bom.raw_material_cost, 351.68) | 
 | ||||||
| 		self.assertEqual(bom.total_cost, 451.68) | 		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 | 		# test amounts in selected currency | ||||||
| 		self.assertEqual(bom.base_operating_cost, 6000) | 		self.assertEqual(bom.operating_cost, op_cost) | ||||||
| 		self.assertEqual(bom.base_raw_material_cost, 21100.80) | 		self.assertEqual(bom.raw_material_cost, raw_material_cost) | ||||||
| 		self.assertEqual(bom.base_total_cost, 27100.80) | 		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): | 	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) | 		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: | 				if d.completed_qty: | ||||||
| 					self.total_completed_qty += 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): | 	def get_overlap_for(self, args, check_next_available_slot=False): | ||||||
| 		production_capacity = 1 | 		production_capacity = 1 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ from frappe import _ | |||||||
| def execute(): | def execute(): | ||||||
| 	from erpnext.setup.setup_wizard.operations.install_fixtures import default_lead_sources | 	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' | 	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) { | 	payroll_frequency: function (frm) { | ||||||
| 		frm.trigger("set_start_end_dates").then( ()=> { | 		frm.trigger("set_start_end_dates").then( ()=> { | ||||||
| 			frm.events.clear_employee_table(frm); | 			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) { | 	company: function (frm) { | ||||||
| 		frm.events.clear_employee_table(frm); | 		frm.events.clear_employee_table(frm); | ||||||
| 		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); | 		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) { | 	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 frappe import _ | ||||||
| from erpnext.accounts.utils import get_fiscal_year | from erpnext.accounts.utils import get_fiscal_year | ||||||
| from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee | 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): | class PayrollEntry(Document): | ||||||
| 	def onload(self): | 	def onload(self): | ||||||
| 		if not self.docstatus==1 or self.salary_slips_submitted: | 		if not self.docstatus==1 or self.salary_slips_submitted: | ||||||
|     			return | 			return | ||||||
| 
 | 
 | ||||||
| 		# check if salary slips were manually submitted | 		# check if salary slips were manually submitted | ||||||
| 		entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name']) | 		entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name']) | ||||||
| 		if cint(entries) == len(self.employees): | 		if cint(entries) == len(self.employees): | ||||||
| 				self.set_onload("submitted_ss", True) | 			self.set_onload("submitted_ss", True) | ||||||
| 
 | 
 | ||||||
| 	def validate(self): | 	def validate(self): | ||||||
| 		self.number_of_employees = len(self.employees) | 		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} | 			condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency} | ||||||
| 
 | 
 | ||||||
| 		sal_struct = frappe.db.sql_list(""" | 		sal_struct = frappe.db.sql_list(""" | ||||||
| 				select | 			select | ||||||
| 					name from `tabSalary Structure` | 				name from `tabSalary Structure` | ||||||
| 				where | 			where | ||||||
| 					docstatus = 1 and | 				docstatus = 1 and | ||||||
| 					is_active = 'Yes' | 				is_active = 'Yes' | ||||||
| 					and company = %(company)s | 				and company = %(company)s | ||||||
| 					and currency = %(currency)s and | 				and currency = %(currency)s and | ||||||
| 					ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s | 				ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s | ||||||
| 					{condition}""".format(condition=condition), | 				{condition}""".format(condition=condition), | ||||||
| 				{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) | 			{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) | ||||||
| 
 | 
 | ||||||
| 		if sal_struct: | 		if sal_struct: | ||||||
| 			cond += "and t2.salary_structure IN %(sal_struct)s " | 			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 | 			Returns list of salary slips based on selected criteria | ||||||
| 		""" | 		""" | ||||||
| 		cond = self.get_filter_condition() |  | ||||||
| 
 | 
 | ||||||
| 		ss_list = frappe.db.sql(""" | 		ss_list = frappe.db.sql(""" | ||||||
| 			select t1.name, t1.salary_structure, t1.payroll_cost_center from `tabSalary Slip` t1 | 			select t1.name, t1.salary_structure, t1.payroll_cost_center from `tabSalary Slip` t1 | ||||||
| 			where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s | 			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 %s | 			and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s | ||||||
| 		""" % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict) | 		""", (ss_status, self.start_date, self.end_date, self.name, self.salary_slip_based_on_timesheet), as_dict=as_dict) | ||||||
| 		return ss_list | 		return ss_list | ||||||
| 
 | 
 | ||||||
|  | 	@frappe.whitelist() | ||||||
| 	def submit_salary_slips(self): | 	def submit_salary_slips(self): | ||||||
| 		self.check_permission('write') | 		self.check_permission('write') | ||||||
| 		ss_list = self.get_sal_slip_list(ss_status=0) | 		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) | 				exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies) | ||||||
| 				payable_amount += flt(amount, precision) | 				payable_amount += flt(amount, precision) | ||||||
| 				accounts.append({ | 				accounts.append({ | ||||||
| 						"account": acc_cc[0], | 					"account": acc_cc[0], | ||||||
| 						"debit_in_account_currency": flt(amt, precision), | 					"debit_in_account_currency": flt(amt, precision), | ||||||
| 						"exchange_rate": flt(exchange_rate), | 					"exchange_rate": flt(exchange_rate), | ||||||
| 						"party_type": '', | 					"party_type": '', | ||||||
| 						"cost_center": acc_cc[1] or self.cost_center, | 					"cost_center": acc_cc[1] or self.cost_center, | ||||||
| 						"project": self.project | 					"project": self.project | ||||||
| 					}) | 				}) | ||||||
| 
 | 
 | ||||||
| 			# Deductions | 			# Deductions | ||||||
| 			for acc_cc, amount in deductions.items(): | 			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) | 				exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies) | ||||||
| 				payable_amount -= flt(amount, precision) | 				payable_amount -= flt(amount, precision) | ||||||
| 				accounts.append({ | 				accounts.append({ | ||||||
| 						"account": acc_cc[0], | 					"account": acc_cc[0], | ||||||
| 						"credit_in_account_currency": flt(amt, precision), | 					"credit_in_account_currency": flt(amt, precision), | ||||||
| 						"exchange_rate": flt(exchange_rate), | 					"exchange_rate": flt(exchange_rate), | ||||||
| 						"cost_center": acc_cc[1] or self.cost_center, | 					"cost_center": acc_cc[1] or self.cost_center, | ||||||
| 						"party_type": '', | 					"party_type": '', | ||||||
| 						"project": self.project | 					"project": self.project | ||||||
| 					}) | 				}) | ||||||
| 
 | 
 | ||||||
| 			# Payable amount | 			# Payable amount | ||||||
| 			exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies) | 			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): | 	def make_payment_entry(self): | ||||||
| 		self.check_permission('write') | 		self.check_permission('write') | ||||||
| 
 | 
 | ||||||
| 		cond = self.get_filter_condition() |  | ||||||
| 		salary_slip_name_list = frappe.db.sql(""" select t1.name from `tabSalary Slip` t1 | 		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 | 			where t1.docstatus = 1 and start_date >= %s and end_date <= %s and t1.payroll_entry = %s | ||||||
| 			""" % ('%s', '%s', cond), (self.start_date, self.end_date), as_list = True) | 			""", (self.start_date, self.end_date, self.name), as_list = True) | ||||||
| 
 | 
 | ||||||
| 		if salary_slip_name_list and len(salary_slip_name_list) > 0: | 		if salary_slip_name_list and len(salary_slip_name_list) > 0: | ||||||
| 			salary_slip_total = 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) | 		exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(self.payment_account, je_payment_amount, company_currency, currencies) | ||||||
| 		accounts.append({ | 		accounts.append({ | ||||||
| 				"account": self.payment_account, | 			"account": self.payment_account, | ||||||
| 				"bank_account": self.bank_account, | 			"bank_account": self.bank_account, | ||||||
| 				"credit_in_account_currency": flt(amount, precision), | 			"credit_in_account_currency": flt(amount, precision), | ||||||
| 				"exchange_rate": flt(exchange_rate), | 			"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) | 		exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, je_payment_amount, company_currency, currencies) | ||||||
| 		accounts.append({ | 		accounts.append({ | ||||||
| 				"account": payroll_payable_account, | 			"account": payroll_payable_account, | ||||||
| 				"debit_in_account_currency": flt(amount, precision), | 			"debit_in_account_currency": flt(amount, precision), | ||||||
| 				"exchange_rate": flt(exchange_rate), | 			"exchange_rate": flt(exchange_rate), | ||||||
| 				"reference_type": self.doctype, | 			"reference_type": self.doctype, | ||||||
| 				"reference_name": self.name | 			"reference_name": self.name | ||||||
| 			}) | 		}) | ||||||
| 
 | 
 | ||||||
| 		if len(currencies) > 1: | 		if len(currencies) > 1: | ||||||
| 				multi_currency = 1 | 				multi_currency = 1 | ||||||
| @ -409,6 +409,7 @@ class PayrollEntry(Document): | |||||||
| 		self.update(get_start_end_dates(self.payroll_frequency, | 		self.update(get_start_end_dates(self.payroll_frequency, | ||||||
| 			self.start_date or self.posting_date, self.company)) | 			self.start_date or self.posting_date, self.company)) | ||||||
| 
 | 
 | ||||||
|  | 	@frappe.whitelist() | ||||||
| 	def validate_employee_attendance(self): | 	def validate_employee_attendance(self): | ||||||
| 		employees_to_mark_attendance = [] | 		employees_to_mark_attendance = [] | ||||||
| 		days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0 | 		days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0 | ||||||
| @ -424,7 +425,7 @@ class PayrollEntry(Document): | |||||||
| 				employees_to_mark_attendance.append({ | 				employees_to_mark_attendance.append({ | ||||||
| 					"employee": employee_detail.employee, | 					"employee": employee_detail.employee, | ||||||
| 					"employee_name": employee_detail.employee_name | 					"employee_name": employee_detail.employee_name | ||||||
| 					}) | 				}) | ||||||
| 		return employees_to_mark_attendance | 		return employees_to_mark_attendance | ||||||
| 
 | 
 | ||||||
| 	def get_count_holidays_of_employee(self, employee, start_date): | 	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): | 	def get_count_employee_attendance(self, employee, start_date): | ||||||
| 		marked_days = 0 | 		marked_days = 0 | ||||||
| 		attendances = frappe.get_all("Attendance", | 		attendances = frappe.get_all("Attendance", | ||||||
| 				fields = ["count(*)"], | 			fields = ["count(*)"], | ||||||
| 				filters = { | 			filters = { | ||||||
| 					"employee": employee, | 				"employee": employee, | ||||||
| 					"attendance_date": ('between', [start_date, self.end_date]) | 				"attendance_date": ('between', [start_date, self.end_date]) | ||||||
| 				}, as_list=1) | 			}, as_list=1) | ||||||
| 		if attendances and attendances[0][0]: | 		if attendances and attendances[0][0]: | ||||||
| 			marked_days = attendances[0][0] | 			marked_days = attendances[0][0] | ||||||
| 		return marked_days | 		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): | def create_salary_slips_for_employees(employees, args, publish_progress=True): | ||||||
| 	salary_slips_exists_for = get_existing_salary_slips(employees, args) | 	salary_slips_exists_for = get_existing_salary_slips(employees, args) | ||||||
| 	count=0 | 	count=0 | ||||||
|  | 	salary_slips_not_created = [] | ||||||
| 	for emp in employees: | 	for emp in employees: | ||||||
| 		if emp not in salary_slips_exists_for: | 		if emp not in salary_slips_exists_for: | ||||||
| 			args.update({ | 			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)), | 				frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)), | ||||||
| 					title = _("Creating Salary Slips...")) | 					title = _("Creating Salary Slips...")) | ||||||
| 		else: | 		else: | ||||||
| 			salary_slip_name = frappe.db.sql( | 			salary_slips_not_created.append(emp) | ||||||
| 				'''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() |  | ||||||
| 
 | 
 | ||||||
| 	payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry) | 	payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry) | ||||||
| 	payroll_entry.db_set("salary_slips_created", 1) | 	payroll_entry.db_set("salary_slips_created", 1) | ||||||
| 	payroll_entry.notify_update() | 	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): | def get_existing_salary_slips(employees, args): | ||||||
| 	return frappe.db.sql_list(""" | 	return frappe.db.sql_list(""" | ||||||
| 		select distinct employee from `tabSalary Slip` | 		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 start_date >= %s and end_date <= %s | ||||||
| 			and employee in (%s) | 			and employee in (%s) | ||||||
| 	""" % ('%s', '%s', '%s', ', '.join(['%s']*len(employees))), | 	""" % ('%s', '%s', '%s', '%s', ', '.join(['%s']*len(employees))), | ||||||
| 		[args.company, args.start_date, args.end_date] + 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): | def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True): | ||||||
| 	submitted_ss = [] | 	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), | 			'txt': "%%%s%%" % frappe.db.escape(txt), | ||||||
| 			'start': start, 'page_len': page_len | 			'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) | 		company_doc = frappe.get_doc('Company', company) | ||||||
| 		salary_structure = make_salary_structure("_Test Multi Currency Salary Structure", "Monthly", company=company, currency='USD') | 		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"}))) | 		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") | 		salary_slip = get_salary_slip("test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure") | ||||||
| 		dates = get_start_end_dates('Monthly', nowdate()) | 		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) | 			payable_account=company_doc.default_payroll_payable_account, currency='USD', exchange_rate=70) | ||||||
| 		payroll_entry.make_payment_entry() | 		payroll_entry.make_payment_entry() | ||||||
| 
 | 
 | ||||||
| 		salary_slip.load_from_db() | 		salary_slip.load_from_db() | ||||||
| 
 | 
 | ||||||
| 		payroll_je = salary_slip.journal_entry | 		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_debit) | ||||||
| 		self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit) | 			self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit) | ||||||
| 
 | 
 | ||||||
| 		payment_entry = frappe.db.sql(''' | 		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 | 			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() { | 		frm.set_query("employee", function() { | ||||||
| 			return { | 			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) { | 	set_exchange_rate: function(frm, company_currency) { | ||||||
| 		if (frm.doc.currency) { | 		if (frm.doc.docstatus === 0) { | ||||||
| 			var from_currency = frm.doc.currency; | 			if (frm.doc.currency) { | ||||||
| 			if (from_currency != company_currency) { | 				var from_currency = frm.doc.currency; | ||||||
| 				frm.events.hide_loan_section(frm); | 				if (from_currency != company_currency) { | ||||||
| 				frappe.call({ | 					frm.events.hide_loan_section(frm); | ||||||
| 					method: "erpnext.setup.utils.get_exchange_rate", | 					frappe.call({ | ||||||
| 					args: { | 						method: "erpnext.setup.utils.get_exchange_rate", | ||||||
| 						from_currency: from_currency, | 						args: { | ||||||
| 						to_currency: company_currency, | 							from_currency: from_currency, | ||||||
| 					}, | 							to_currency: company_currency, | ||||||
| 					callback: function(r) { | 						}, | ||||||
| 						frm.set_value("exchange_rate", flt(r.message)); | 						callback: function(r) { | ||||||
| 						frm.set_df_property("exchange_rate", "hidden", 0); | 							if (r.message) { | ||||||
| 						frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency | 								frm.set_value("exchange_rate", flt(r.message)); | ||||||
| 							+ " = [?] " + company_currency); | 								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", ""); | 				} 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): | 	def check_existing(self): | ||||||
| 		if not self.salary_slip_based_on_timesheet: | 		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` | 			ret_exist = frappe.db.sql("""select name from `tabSalary Slip` | ||||||
| 						where start_date = %s and end_date = %s and docstatus != 2 | 						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)) | 						(self.start_date, self.end_date, self.employee, self.name)) | ||||||
| 			if ret_exist: | 			if ret_exist: | ||||||
| 				self.employee = '' | 				self.employee = '' | ||||||
| @ -1053,7 +1056,7 @@ class SalarySlip(TransactionBase): | |||||||
| 			repayment_entry.save() | 			repayment_entry.save() | ||||||
| 			repayment_entry.submit() | 			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): | 	def cancel_loan_repayment_entry(self): | ||||||
| 		for loan in self.loans: | 		for loan in self.loans: | ||||||
|  | |||||||
| @ -33,12 +33,16 @@ class TestProject(unittest.TestCase): | |||||||
| 
 | 
 | ||||||
| 	def test_project_template_having_parent_child_tasks(self): | 	def test_project_template_having_parent_child_tasks(self): | ||||||
| 		project_name = "Test Project with Template - Tasks with Parent-Child Relation" | 		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.db.sql(""" delete from tabTask where project = %s """, project_name) | ||||||
| 		frappe.delete_doc('Project', project_name) | 		frappe.delete_doc('Project', project_name) | ||||||
| 
 | 
 | ||||||
| 		task1 = task_exists("Test Template Task Parent") | 		task1 = task_exists("Test Template Task Parent") | ||||||
| 		if not task1: | 		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") | 		task2 = task_exists("Test Template Task Child 1") | ||||||
| 		if not task2: | 		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') | 		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(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(tasks[1].subject, 'Test Template Task Child 1') | ||||||
| 		self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 1, 3)) | 		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); | 				this.frm.trigger("item_code", cdt, cdn); | ||||||
| 			} | 			} | ||||||
| 			else { | 			else { | ||||||
| 				var valid_serial_nos = []; |  | ||||||
| 				var serialnos = []; |  | ||||||
| 				// Replacing all occurences of comma with carriage return
 | 				// Replacing all occurences of comma with carriage return
 | ||||||
| 				item.serial_no = item.serial_no.replace(/,/g, '\n'); | 				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; | 				item.conversion_factor = item.conversion_factor || 1; | ||||||
| 
 |  | ||||||
| 				refresh_field("serial_no", item.name, item.parentfield); | 				refresh_field("serial_no", item.name, item.parentfield); | ||||||
| 				if(!doc.is_return && cint(user_defaults.set_qty_in_transactions_based_on_serial_no_input)) { | 				if (!doc.is_return && cint(frappe.user_defaults.set_qty_in_transactions_based_on_serial_no_input)) { | ||||||
| 					frappe.model.set_value(item.doctype, item.name, | 					setTimeout(() => { | ||||||
| 						"qty", valid_serial_nos.length / item.conversion_factor); | 						me.update_qty(cdt, cdn); | ||||||
| 					frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length); | 					}, 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() { | 	validate: function() { | ||||||
| 		this.calculate_taxes_and_totals(false); | 		this.calculate_taxes_and_totals(false); | ||||||
| 	}, | 	}, | ||||||
|  | |||||||
| @ -787,6 +787,8 @@ class GSPConnector(): | |||||||
| 
 | 
 | ||||||
| 		self.invoice.irn = res.get('Irn') | 		self.invoice.irn = res.get('Irn') | ||||||
| 		self.invoice.ewaybill = res.get('EwbNo') | 		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_einvoice = dec_signed_invoice | ||||||
| 		self.invoice.signed_qr_code = res.get('SignedQRCode') | 		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", | 			method: "frappe.client.get_list", | ||||||
| 			args: { | 			args: { | ||||||
| 				doctype: "UOM Conversion Factor", | 				doctype: "UOM Conversion Factor", | ||||||
| 				filters: { "category": "Length" }, | 				filters: { "category": __("Length") }, | ||||||
| 				fields: ["to_uom"], | 				fields: ["to_uom"], | ||||||
| 				limit_page_length: 500 | 				limit_page_length: 500 | ||||||
| 			}, | 			}, | ||||||
|  | |||||||
| @ -1,14 +1,14 @@ | |||||||
| frappe.provide('erpnext.stock'); | frappe.provide('erpnext.stock'); | ||||||
| 
 | 
 | ||||||
| erpnext.stock.ItemDashboard = Class.extend({ | erpnext.stock.ItemDashboard = Class.extend({ | ||||||
| 	init: function(opts) { | 	init: function (opts) { | ||||||
| 		$.extend(this, opts); | 		$.extend(this, opts); | ||||||
| 		this.make(); | 		this.make(); | ||||||
| 	}, | 	}, | ||||||
| 	make: function() { | 	make: function () { | ||||||
| 		var me = this; | 		var me = this; | ||||||
| 		this.start = 0; | 		this.start = 0; | ||||||
| 		if(!this.sort_by) { | 		if (!this.sort_by) { | ||||||
| 			this.sort_by = 'projected_qty'; | 			this.sort_by = 'projected_qty'; | ||||||
| 			this.sort_order = 'asc'; | 			this.sort_order = 'asc'; | ||||||
| 		} | 		} | ||||||
| @ -16,22 +16,25 @@ erpnext.stock.ItemDashboard = Class.extend({ | |||||||
| 		this.content = $(frappe.render_template('item_dashboard')).appendTo(this.parent); | 		this.content = $(frappe.render_template('item_dashboard')).appendTo(this.parent); | ||||||
| 		this.result = this.content.find('.result'); | 		this.result = this.content.find('.result'); | ||||||
| 
 | 
 | ||||||
| 		this.content.on('click', '.btn-move', function() { | 		this.content.on('click', '.btn-move', function () { | ||||||
| 			handle_move_add($(this), "Move") | 			handle_move_add($(this), "Move"); | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		this.content.on('click', '.btn-add', function() { | 		this.content.on('click', '.btn-add', function () { | ||||||
| 			handle_move_add($(this), "Add") | 			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 item = unescape($(this).attr('data-item')); | ||||||
| 			let warehouse = unescape($(this).attr('data-warehouse')); | 			let warehouse = unescape($(this).attr('data-warehouse')); | ||||||
| 			let company = unescape($(this).attr('data-company')); | 			let company = unescape($(this).attr('data-company')); | ||||||
| 			frappe.db.get_value('Putaway Rule', | 			frappe.db.get_value('Putaway Rule', { | ||||||
| 				{'item_code': item, 'warehouse': warehouse, 'company': company}, 'name', (r) => { | 				'item_code': item, | ||||||
| 					frappe.set_route("Form", "Putaway Rule", r.name); | 				'warehouse': warehouse, | ||||||
| 				}); | 				'company': company | ||||||
|  | 			}, 'name', (r) => { | ||||||
|  | 				frappe.set_route("Form", "Putaway Rule", r.name); | ||||||
|  | 			}); | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		function handle_move_add(element, action) { | 		function handle_move_add(element, action) { | ||||||
| @ -39,23 +42,26 @@ erpnext.stock.ItemDashboard = Class.extend({ | |||||||
| 			let warehouse = unescape(element.attr('data-warehouse')); | 			let warehouse = unescape(element.attr('data-warehouse')); | ||||||
| 			let actual_qty = unescape(element.attr('data-actual_qty')); | 			let actual_qty = unescape(element.attr('data-actual_qty')); | ||||||
| 			let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry'))); | 			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) { | 			if (disable_quick_entry) { | ||||||
| 				open_stock_entry(item, warehouse, entry_type); | 				open_stock_entry(item, warehouse, entry_type); | ||||||
| 			} else { | 			} else { | ||||||
| 				if (action === "Add") { | 				if (action === "Add") { | ||||||
| 					let rate = unescape($(this).attr('data-rate')); | 					let rate = unescape($(this).attr('data-rate')); | ||||||
| 					erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, 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(); }); | 				} else { | ||||||
|  | 					erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function () { | ||||||
|  | 						me.refresh(); | ||||||
|  | 					}); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		function open_stock_entry(item, warehouse, entry_type) { | 		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'); | 				var doc = frappe.model.get_new_doc('Stock Entry'); | ||||||
| 				if (entry_type) doc.stock_entry_type = entry_type; | 				if (entry_type) doc.stock_entry_type = entry_type; | ||||||
| 
 | 
 | ||||||
| @ -64,18 +70,18 @@ erpnext.stock.ItemDashboard = Class.extend({ | |||||||
| 				row.s_warehouse = warehouse; | 				row.s_warehouse = warehouse; | ||||||
| 
 | 
 | ||||||
| 				frappe.set_route('Form', doc.doctype, doc.name); | 				frappe.set_route('Form', doc.doctype, doc.name); | ||||||
| 			}) | 			}); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// more
 | 		// more
 | ||||||
| 		this.content.find('.btn-more').on('click', function() { | 		this.content.find('.btn-more').on('click', function () { | ||||||
| 			me.start += me.page_length; | 			me.start += me.page_length; | ||||||
| 			me.refresh(); | 			me.refresh(); | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 	}, | 	}, | ||||||
| 	refresh: function() { | 	refresh: function () { | ||||||
| 		if(this.before_refresh) { | 		if (this.before_refresh) { | ||||||
| 			this.before_refresh(); | 			this.before_refresh(); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| @ -94,13 +100,13 @@ erpnext.stock.ItemDashboard = Class.extend({ | |||||||
| 		frappe.call({ | 		frappe.call({ | ||||||
| 			method: this.method, | 			method: this.method, | ||||||
| 			args: args, | 			args: args, | ||||||
| 			callback: function(r) { | 			callback: function (r) { | ||||||
| 				me.render(r.message); | 				me.render(r.message); | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
| 	}, | 	}, | ||||||
| 	render: function(data) { | 	render: function (data) { | ||||||
| 		if (this.start===0) { | 		if (this.start === 0) { | ||||||
| 			this.max_count = 0; | 			this.max_count = 0; | ||||||
| 			this.result.empty(); | 			this.result.empty(); | ||||||
| 		} | 		} | ||||||
| @ -115,7 +121,7 @@ erpnext.stock.ItemDashboard = Class.extend({ | |||||||
| 		this.max_count = this.max_count; | 		this.max_count = this.max_count; | ||||||
| 
 | 
 | ||||||
| 		// show more button
 | 		// 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'); | 			this.content.find('.more').removeClass('hidden'); | ||||||
| 
 | 
 | ||||||
| 			// remove the last element
 | 			// remove the last element
 | ||||||
| @ -137,15 +143,15 @@ erpnext.stock.ItemDashboard = Class.extend({ | |||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	get_item_dashboard_data: function(data, max_count, show_item) { | 	get_item_dashboard_data: function (data, max_count, show_item) { | ||||||
| 		if(!max_count) max_count = 0; | 		if (!max_count) max_count = 0; | ||||||
| 		if(!data) data = []; | 		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.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.pending_qty = 0; | ||||||
| 			d.total_reserved = d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract; | 			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; | 				d.pending_qty = d.actual_or_pending - d.actual_qty; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| @ -161,16 +167,16 @@ erpnext.stock.ItemDashboard = Class.extend({ | |||||||
| 		return { | 		return { | ||||||
| 			data: data, | 			data: data, | ||||||
| 			max_count: max_count, | 			max_count: max_count, | ||||||
| 			can_write:can_write, | 			can_write: can_write, | ||||||
| 			show_item: show_item || false | 			show_item: show_item || false | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	get_capacity_dashboard_data: function(data) { | 	get_capacity_dashboard_data: function (data) { | ||||||
| 		if (!data) data = []; | 		if (!data) data = []; | ||||||
| 
 | 
 | ||||||
| 		data.forEach(function(d) { | 		data.forEach(function (d) { | ||||||
| 			d.color =  d.percent_occupied >=80 ? "#f8814f" : "#2490ef"; | 			d.color = d.percent_occupied >= 80 ? "#f8814f" : "#2490ef"; | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		let can_write = 0; | 		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({ | 	var dialog = new frappe.ui.Dialog({ | ||||||
| 		title: target ? __('Add Item') : __('Move Item'), | 		title: target ? __('Add Item') : __('Move Item'), | ||||||
| 		fields: [ | 		fields: [{ | ||||||
| 			{fieldname: 'item_code', label: __('Item'), | 			fieldname: 'item_code', | ||||||
| 				fieldtype: 'Link', options: 'Item', read_only: 1}, | 			label: __('Item'), | ||||||
| 			{fieldname: 'source', label: __('Source Warehouse'), | 			fieldtype: 'Link', | ||||||
| 				fieldtype: 'Link', options: 'Warehouse', read_only: 1}, | 			options: 'Item', | ||||||
| 			{fieldname: 'target', label: __('Target Warehouse'), | 			read_only: 1 | ||||||
| 				fieldtype: 'Link', options: 'Warehouse', reqd: 1}, | 		}, | ||||||
| 			{fieldname: 'qty', label: __('Quantity'), reqd: 1, | 		{ | ||||||
| 				fieldtype: 'Float', description: __('Available {0}', [actual_qty]) }, | 			fieldname: 'source', | ||||||
| 			{fieldname: 'rate', label: __('Rate'), fieldtype: 'Currency', hidden: 1 }, | 			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.show(); | ||||||
| 	dialog.get_field('item_code').set_input(item); | 	dialog.get_field('item_code').set_input(item); | ||||||
| 
 | 
 | ||||||
| 	if(source) { | 	if (source) { | ||||||
| 		dialog.get_field('source').set_input(source); | 		dialog.get_field('source').set_input(source); | ||||||
| 	} else { | 	} else { | ||||||
| 		dialog.get_field('source').df.hidden = 1; | 		dialog.get_field('source').df.hidden = 1; | ||||||
| 		dialog.get_field('source').refresh(); | 		dialog.get_field('source').refresh(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(rate) { | 	if (rate) { | ||||||
| 		dialog.get_field('rate').set_value(rate); | 		dialog.get_field('rate').set_value(rate); | ||||||
| 		dialog.get_field('rate').df.hidden = 0; | 		dialog.get_field('rate').df.hidden = 0; | ||||||
| 		dialog.get_field('rate').refresh(); | 		dialog.get_field('rate').refresh(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if(target) { | 	if (target) { | ||||||
| 		dialog.get_field('target').df.read_only = 1; | 		dialog.get_field('target').df.read_only = 1; | ||||||
| 		dialog.get_field('target').value = target; | 		dialog.get_field('target').value = target; | ||||||
| 		dialog.get_field('target').refresh(); | 		dialog.get_field('target').refresh(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	dialog.set_primary_action(__('Submit'), function() { | 	dialog.set_primary_action(__('Submit'), function () { | ||||||
| 		var values = dialog.get_values(); | 		var values = dialog.get_values(); | ||||||
| 		if(!values) { | 		if (!values) { | ||||||
| 			return; | 			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])); | 			frappe.msgprint(__('Quantity must be less than or equal to {0}', [actual_qty])); | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 		if(values.source === values.target) { | 		if (values.source === values.target) { | ||||||
| 			frappe.msgprint(__('Source and target warehouse must be different')); | 			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', | 			method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', | ||||||
| 			args: values, | 			args: values, | ||||||
| 			freeze: true, | 			freeze: true, | ||||||
| 			callback: function(r) { | 			callback: function (r) { | ||||||
| 				frappe.show_alert(__('Stock Entry {0} created', | 				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(); | 				dialog.hide(); | ||||||
| 				callback(r); | 				callback(r); | ||||||
| 			}, | 			}, | ||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	$('<p style="margin-left: 10px;"><a class="link-open text-muted small">' | 	$('<p style="margin-left: 10px;"><a class="link-open text-muted small">' + | ||||||
| 		+ __("Add more items or open full form") + '</a></p>') | 			__("Add more items or open full form") + '</a></p>') | ||||||
| 		.appendTo(dialog.body) | 		.appendTo(dialog.body) | ||||||
| 		.find('.link-open') | 		.find('.link-open') | ||||||
| 		.on('click', function() { | 		.on('click', function () { | ||||||
| 			frappe.model.with_doctype('Stock Entry', function() { | 			frappe.model.with_doctype('Stock Entry', function () { | ||||||
| 				var doc = frappe.model.get_new_doc('Stock Entry'); | 				var doc = frappe.model.get_new_doc('Stock Entry'); | ||||||
| 				doc.from_warehouse = dialog.get_value('source'); | 				doc.from_warehouse = dialog.get_value('source'); | ||||||
| 				doc.to_warehouse = dialog.get_value('target'); | 				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.transfer_qty = dialog.get_value('qty'); | ||||||
| 				row.basic_rate = dialog.get_value('rate'); | 				row.basic_rate = dialog.get_value('rate'); | ||||||
| 				frappe.set_route('Form', doc.doctype, doc.name); | 				frappe.set_route('Form', doc.doctype, doc.name); | ||||||
| 			}) | 			}); | ||||||
| 		}); | 		}); | ||||||
| } | }; | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ from __future__ import unicode_literals | |||||||
| 
 | 
 | ||||||
| import frappe | import frappe | ||||||
| from frappe.model.db_query import DatabaseQuery | from frappe.model.db_query import DatabaseQuery | ||||||
|  | from frappe.utils import flt, cint | ||||||
| 
 | 
 | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
| def get_data(item_code=None, warehouse=None, item_group=None, | 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_start=start, | ||||||
| 		limit_page_length='21') | 		limit_page_length='21') | ||||||
| 
 | 
 | ||||||
|  | 	precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) | ||||||
|  | 
 | ||||||
| 	for item in items: | 	for item in items: | ||||||
| 		item.update({ | 		item.update({ | ||||||
| 			'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'), | 			'item_name': frappe.get_cached_value( | ||||||
| 			'disable_quick_entry': frappe.get_cached_value("Item", item.item_code, 'has_batch_no') | 				"Item", item.item_code, 'item_name'), | ||||||
| 				or frappe.get_cached_value("Item", item.item_code, 'has_serial_no'), | 			'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 | 	return items | ||||||
|  | |||||||
| @ -494,7 +494,8 @@ def make_item_variant(): | |||||||
| 
 | 
 | ||||||
| test_records = frappe.get_test_records('Item') | 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): | 	if not frappe.db.exists("Item", item_code): | ||||||
| 		item = frappe.new_doc("Item") | 		item = frappe.new_doc("Item") | ||||||
| 		item.item_code = item_code | 		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.customer = customer or '' | ||||||
| 		item.append("item_defaults", { | 		item.append("item_defaults", { | ||||||
| 			"default_warehouse": warehouse or '_Test Warehouse - _TC', | 			"default_warehouse": warehouse or '_Test Warehouse - _TC', | ||||||
| 			"company": "_Test Company" | 			"company": company or "_Test Company" | ||||||
| 		}) | 		}) | ||||||
| 		item.save() | 		item.save() | ||||||
| 	else: | 	else: | ||||||
|  | |||||||
| @ -346,7 +346,7 @@ def create_delivery_note(source_name, target_doc=None): | |||||||
| 
 | 
 | ||||||
| 		if dn_item: | 		if dn_item: | ||||||
| 			dn_item.warehouse = location.warehouse | 			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.batch_no = location.batch_no | ||||||
| 			dn_item.serial_no = location.serial_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.purchase_receipt.test_purchase_receipt import make_purchase_receipt | ||||||
| from erpnext.stock.doctype.item.test_item import create_item | 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 \ | from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \ | ||||||
| 		import EmptyStockReconciliationItemsError | 		import EmptyStockReconciliationItemsError | ||||||
| 
 | 
 | ||||||
| @ -291,6 +292,61 @@ class TestPickList(unittest.TestCase): | |||||||
| 		self.assertEqual(pick_list.locations[1].qty, 5) | 		self.assertEqual(pick_list.locations[1].qty, 5) | ||||||
| 		self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name) | 		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): | 	# def test_pick_list_skips_items_in_expired_batch(self): | ||||||
| 	# 	pass | 	# 	pass | ||||||
|  | |||||||
| @ -179,11 +179,15 @@ class TestStockEntry(unittest.TestCase): | |||||||
| 	def test_material_transfer_gl_entry(self): | 	def test_material_transfer_gl_entry(self): | ||||||
| 		company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') | 		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) | 			target="Finished Goods - TCP1", qty=45, company=company) | ||||||
| 
 | 
 | ||||||
| 		self.check_stock_ledger_entries("Stock Entry", mtn.name, | 		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) | 		source_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse) | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user