Merge branch 'develop' of https://github.com/frappe/erpnext into stock_entry_conversion_factor
This commit is contained in:
		
						commit
						01a7297f69
					
				
							
								
								
									
										48
									
								
								.github/helper/documentation.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								.github/helper/documentation.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | |||||||
|  | import sys | ||||||
|  | import requests | ||||||
|  | from urllib.parse import urlparse | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | docs_repos = [ | ||||||
|  | 	"frappe_docs", | ||||||
|  | 	"erpnext_documentation", | ||||||
|  | 	"erpnext_com", | ||||||
|  | 	"frappe_io", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def uri_validator(x): | ||||||
|  | 	result = urlparse(x) | ||||||
|  | 	return all([result.scheme, result.netloc, result.path]) | ||||||
|  | 
 | ||||||
|  | def docs_link_exists(body): | ||||||
|  | 	for line in body.splitlines(): | ||||||
|  | 		for word in line.split(): | ||||||
|  | 			if word.startswith('http') and uri_validator(word): | ||||||
|  | 				parsed_url = urlparse(word) | ||||||
|  | 				if parsed_url.netloc == "github.com": | ||||||
|  | 					_, org, repo, _type, ref = parsed_url.path.split('/') | ||||||
|  | 					if org == "frappe" and repo in docs_repos: | ||||||
|  | 						return True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  | 	pr = sys.argv[1] | ||||||
|  | 	response = requests.get("https://api.github.com/repos/frappe/erpnext/pulls/{}".format(pr)) | ||||||
|  | 
 | ||||||
|  | 	if response.ok: | ||||||
|  | 		payload = response.json() | ||||||
|  | 		title = payload.get("title", "").lower() | ||||||
|  | 		head_sha = payload.get("head", {}).get("sha") | ||||||
|  | 		body = payload.get("body", "").lower() | ||||||
|  | 
 | ||||||
|  | 		if title.startswith("feat") and head_sha and "no-docs" not in body: | ||||||
|  | 			if docs_link_exists(body): | ||||||
|  | 				print("Documentation Link Found. You're Awesome! 🎉") | ||||||
|  | 
 | ||||||
|  | 			else: | ||||||
|  | 				print("Documentation Link Not Found! ⚠️") | ||||||
|  | 				sys.exit(1) | ||||||
|  | 
 | ||||||
|  | 		else: | ||||||
|  | 			print("Skipping documentation checks... 🏃") | ||||||
							
								
								
									
										60
									
								
								.github/helper/translation.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								.github/helper/translation.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,60 @@ | |||||||
|  | import re | ||||||
|  | import sys | ||||||
|  | 
 | ||||||
|  | errors_encounter = 0 | ||||||
|  | pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,\s*(.)*?\s*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)") | ||||||
|  | words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]") | ||||||
|  | start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}") | ||||||
|  | f_string_pattern = re.compile(r"_\(f[\"']") | ||||||
|  | starts_with_f_pattern = re.compile(r"_\(f") | ||||||
|  | 
 | ||||||
|  | # skip first argument | ||||||
|  | files = sys.argv[1:] | ||||||
|  | files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))] | ||||||
|  | 
 | ||||||
|  | for _file in files_to_scan: | ||||||
|  | 	with open(_file, 'r') as f: | ||||||
|  | 		print(f'Checking: {_file}') | ||||||
|  | 		file_lines = f.readlines() | ||||||
|  | 		for line_number, line in enumerate(file_lines, 1): | ||||||
|  | 			if 'frappe-lint: disable-translate' in line: | ||||||
|  | 				continue | ||||||
|  | 
 | ||||||
|  | 			start_matches = start_pattern.search(line) | ||||||
|  | 			if start_matches: | ||||||
|  | 				starts_with_f = starts_with_f_pattern.search(line) | ||||||
|  | 
 | ||||||
|  | 				if starts_with_f: | ||||||
|  | 					has_f_string = f_string_pattern.search(line) | ||||||
|  | 					if has_f_string: | ||||||
|  | 						errors_encounter += 1 | ||||||
|  | 						print(f'\nF-strings are not supported for translations at line number {line_number + 1}\n{line.strip()[:100]}') | ||||||
|  | 						continue | ||||||
|  | 					else: | ||||||
|  | 						continue | ||||||
|  | 
 | ||||||
|  | 				match = pattern.search(line) | ||||||
|  | 				error_found = False | ||||||
|  | 
 | ||||||
|  | 				if not match and line.endswith(',\n'): | ||||||
|  | 					# concat remaining text to validate multiline pattern | ||||||
|  | 					line = "".join(file_lines[line_number - 1:]) | ||||||
|  | 					line = line[start_matches.start() + 1:] | ||||||
|  | 					match = pattern.match(line) | ||||||
|  | 
 | ||||||
|  | 				if not match: | ||||||
|  | 					error_found = True | ||||||
|  | 					print(f'\nTranslation syntax error at line number {line_number + 1}\n{line.strip()[:100]}') | ||||||
|  | 
 | ||||||
|  | 				if not error_found and not words_pattern.search(line): | ||||||
|  | 					error_found = True | ||||||
|  | 					print(f'\nTranslation is useless because it has no words at line number {line_number + 1}\n{line.strip()[:100]}') | ||||||
|  | 
 | ||||||
|  | 				if error_found: | ||||||
|  | 					errors_encounter += 1 | ||||||
|  | 
 | ||||||
|  | if errors_encounter > 0: | ||||||
|  | 	print('\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.') | ||||||
|  | 	sys.exit(1) | ||||||
|  | else: | ||||||
|  | 	print('\nGood To Go!') | ||||||
							
								
								
									
										24
									
								
								.github/workflows/docs-checker.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/docs-checker.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | name: 'Documentation Required' | ||||||
|  | on: | ||||||
|  |   pull_request: | ||||||
|  |     types: [ opened, synchronize, reopened, edited ] | ||||||
|  | 
 | ||||||
|  | jobs: | ||||||
|  |   build: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  | 
 | ||||||
|  |     steps: | ||||||
|  |       - name: 'Setup Environment' | ||||||
|  |         uses: actions/setup-python@v2 | ||||||
|  |         with: | ||||||
|  |           python-version: 3.6 | ||||||
|  | 
 | ||||||
|  |       - name: 'Clone repo' | ||||||
|  |         uses: actions/checkout@v2 | ||||||
|  | 
 | ||||||
|  |       - name: Validate Docs | ||||||
|  |         env: | ||||||
|  |           PR_NUMBER: ${{ github.event.number }} | ||||||
|  |         run: | | ||||||
|  |           pip install requests --quiet | ||||||
|  |           python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER | ||||||
							
								
								
									
										22
									
								
								.github/workflows/translation_linter.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/translation_linter.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | name: Frappe Linter | ||||||
|  | on: | ||||||
|  |   pull_request: | ||||||
|  |     branches: | ||||||
|  |       - develop | ||||||
|  |       - version-12-hotfix | ||||||
|  |       - version-11-hotfix | ||||||
|  | jobs: | ||||||
|  |   check_translation: | ||||||
|  |     name: Translation Syntax Check | ||||||
|  |     runs-on: ubuntu-18.04 | ||||||
|  |     steps: | ||||||
|  |     - uses: actions/checkout@v2 | ||||||
|  |     - name: Setup python3 | ||||||
|  |       uses: actions/setup-python@v1 | ||||||
|  |       with: | ||||||
|  |         python-version: 3.6 | ||||||
|  |     - name: Validating Translation Syntax | ||||||
|  |       run: | | ||||||
|  |         git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q | ||||||
|  |         files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) | ||||||
|  |         python $GITHUB_WORKSPACE/.github/helper/translation.py $files | ||||||
| @ -53,7 +53,7 @@ | |||||||
|   { |   { | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
|    "label": "Goods and Services Tax (GST India)", |    "label": "Goods and Services Tax (GST India)", | ||||||
|    "links": "[\n    {\n        \"label\": \"GST Settings\",\n        \"name\": \"GST Settings\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"GST HSN Code\",\n        \"name\": \"GST HSN Code\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"GSTR-1\",\n        \"name\": \"GSTR-1\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"GSTR-2\",\n        \"name\": \"GSTR-2\",\n        \"type\": \"report\"\n    },\n    {\n        \"label\": \"GSTR 3B Report\",\n        \"name\": \"GSTR 3B Report\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"GST Sales Register\",\n        \"name\": \"GST Sales Register\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"GST Purchase Register\",\n        \"name\": \"GST Purchase Register\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"GST Itemised Sales Register\",\n        \"name\": \"GST Itemised Sales Register\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"GST Itemised Purchase Register\",\n        \"name\": \"GST Itemised Purchase Register\",\n        \"type\": \"report\"\n    },\n    {\n        \"country\": \"India\",\n        \"description\": \"C-Form records\",\n        \"label\": \"C-Form\",\n        \"name\": \"C-Form\",\n        \"type\": \"doctype\"\n    }\n]" |    "links": "[\n    {\n        \"label\": \"GST Settings\",\n        \"name\": \"GST Settings\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"GST HSN Code\",\n        \"name\": \"GST HSN Code\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"GSTR-1\",\n        \"name\": \"GSTR-1\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"GSTR-2\",\n        \"name\": \"GSTR-2\",\n        \"type\": \"report\"\n    },\n    {\n        \"label\": \"GSTR 3B Report\",\n        \"name\": \"GSTR 3B Report\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"GST Sales Register\",\n        \"name\": \"GST Sales Register\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"GST Purchase Register\",\n        \"name\": \"GST Purchase Register\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"GST Itemised Sales Register\",\n        \"name\": \"GST Itemised Sales Register\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"GST Itemised Purchase Register\",\n        \"name\": \"GST Itemised Purchase Register\",\n        \"type\": \"report\"\n    },\n    {\n        \"country\": \"India\",\n        \"description\": \"C-Form records\",\n        \"label\": \"C-Form\",\n        \"name\": \"C-Form\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"country\": \"India\",\n        \"label\": \"Lower Deduction Certificate\",\n        \"name\": \"Lower Deduction Certificate\",\n        \"type\": \"doctype\"\n    }\n]" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
| @ -98,7 +98,7 @@ | |||||||
|  "idx": 0, |  "idx": 0, | ||||||
|  "is_standard": 1, |  "is_standard": 1, | ||||||
|  "label": "Accounting", |  "label": "Accounting", | ||||||
|  "modified": "2020-09-09 11:45:33.766400", |  "modified": "2020-10-08 20:31:46.022470", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Accounts", |  "module": "Accounts", | ||||||
|  "name": "Accounting", |  "name": "Accounting", | ||||||
| @ -108,7 +108,7 @@ | |||||||
|  "pin_to_top": 0, |  "pin_to_top": 0, | ||||||
|  "shortcuts": [ |  "shortcuts": [ | ||||||
|   { |   { | ||||||
|    "label": "Chart Of Accounts", |    "label": "Chart of Accounts", | ||||||
|    "link_to": "Account", |    "link_to": "Account", | ||||||
|    "type": "DocType" |    "type": "DocType" | ||||||
|   }, |   }, | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ frappe.provide("frappe.treeview_settings") | |||||||
| 
 | 
 | ||||||
| frappe.treeview_settings["Account"] = { | frappe.treeview_settings["Account"] = { | ||||||
| 	breadcrumb: "Accounts", | 	breadcrumb: "Accounts", | ||||||
| 	title: __("Chart Of Accounts"), | 	title: __("Chart of Accounts"), | ||||||
| 	get_tree_root: false, | 	get_tree_root: false, | ||||||
| 	filters: [ | 	filters: [ | ||||||
| 		{ | 		{ | ||||||
| @ -97,7 +97,7 @@ frappe.treeview_settings["Account"] = { | |||||||
| 		treeview.page.add_inner_button(__("Journal Entry"), function() { | 		treeview.page.add_inner_button(__("Journal Entry"), function() { | ||||||
| 			frappe.new_doc('Journal Entry', {company: get_company()}); | 			frappe.new_doc('Journal Entry', {company: get_company()}); | ||||||
| 		}, __('Create')); | 		}, __('Create')); | ||||||
| 		treeview.page.add_inner_button(__("New Company"), function() { | 		treeview.page.add_inner_button(__("Company"), function() { | ||||||
| 			frappe.new_doc('Company'); | 			frappe.new_doc('Company'); | ||||||
| 		}, __('Create')); | 		}, __('Create')); | ||||||
| 
 | 
 | ||||||
|  | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -195,7 +195,7 @@ def build_response_as_excel(writer): | |||||||
| 	reader = csv.reader(f) | 	reader = csv.reader(f) | ||||||
| 
 | 
 | ||||||
| 	from frappe.utils.xlsxutils import make_xlsx | 	from frappe.utils.xlsxutils import make_xlsx | ||||||
| 	xlsx_file = make_xlsx(reader, "Chart Of Accounts Importer Template") | 	xlsx_file = make_xlsx(reader, "Chart of Accounts Importer Template") | ||||||
| 
 | 
 | ||||||
| 	f.close() | 	f.close() | ||||||
| 	os.remove(filename) | 	os.remove(filename) | ||||||
|  | |||||||
| @ -22,8 +22,12 @@ class JournalEntry(AccountsController): | |||||||
| 		return self.voucher_type | 		return self.voucher_type | ||||||
| 
 | 
 | ||||||
| 	def validate(self): | 	def validate(self): | ||||||
|  | 		if self.voucher_type == 'Opening Entry': | ||||||
|  | 			self.is_opening = 'Yes' | ||||||
|  | 
 | ||||||
| 		if not self.is_opening: | 		if not self.is_opening: | ||||||
| 			self.is_opening='No' | 			self.is_opening='No' | ||||||
|  | 
 | ||||||
| 		self.clearance_date = None | 		self.clearance_date = None | ||||||
| 
 | 
 | ||||||
| 		self.validate_party() | 		self.validate_party() | ||||||
|  | |||||||
| @ -195,88 +195,91 @@ def create_sales_invoice_record(qty=1): | |||||||
| 
 | 
 | ||||||
| def create_records(): | def create_records(): | ||||||
| 	# create a new loyalty Account | 	# create a new loyalty Account | ||||||
| 	if frappe.db.exists("Account", "Loyalty - _TC"): | 	if not frappe.db.exists("Account", "Loyalty - _TC"): | ||||||
| 		return | 		frappe.get_doc({ | ||||||
| 
 | 			"doctype": "Account", | ||||||
| 	frappe.get_doc({ | 			"account_name": "Loyalty", | ||||||
| 		"doctype": "Account", | 			"parent_account": "Direct Expenses - _TC", | ||||||
| 		"account_name": "Loyalty", | 			"company": "_Test Company", | ||||||
| 		"parent_account": "Direct Expenses - _TC", | 			"is_group": 0, | ||||||
| 		"company": "_Test Company", | 			"account_type": "Expense Account", | ||||||
| 		"is_group": 0, | 		}).insert() | ||||||
| 		"account_type": "Expense Account", |  | ||||||
| 	}).insert() |  | ||||||
| 
 | 
 | ||||||
| 	# create a new loyalty program Single tier | 	# create a new loyalty program Single tier | ||||||
| 	frappe.get_doc({ | 	if not frappe.db.exists("Loyalty Program","Test Single Loyalty"): | ||||||
| 		"doctype": "Loyalty Program", | 		frappe.get_doc({ | ||||||
| 		"loyalty_program_name": "Test Single Loyalty", | 			"doctype": "Loyalty Program", | ||||||
| 		"auto_opt_in": 1, | 			"loyalty_program_name": "Test Single Loyalty", | ||||||
| 		"from_date": today(), | 			"auto_opt_in": 1, | ||||||
| 		"loyalty_program_type": "Single Tier Program", | 			"from_date": today(), | ||||||
| 		"conversion_factor": 1, | 			"loyalty_program_type": "Single Tier Program", | ||||||
| 		"expiry_duration": 10, | 			"conversion_factor": 1, | ||||||
| 		"company": "_Test Company", | 			"expiry_duration": 10, | ||||||
| 		"cost_center": "Main - _TC", | 			"company": "_Test Company", | ||||||
| 		"expense_account": "Loyalty - _TC", | 			"cost_center": "Main - _TC", | ||||||
| 		"collection_rules": [{ | 			"expense_account": "Loyalty - _TC", | ||||||
| 			'tier_name': 'Silver', | 			"collection_rules": [{ | ||||||
| 			'collection_factor': 1000, |  | ||||||
| 			'min_spent': 1000 |  | ||||||
| 		}] |  | ||||||
| 	}).insert() |  | ||||||
| 
 |  | ||||||
| 	# create a new customer |  | ||||||
| 	frappe.get_doc({ |  | ||||||
| 		"customer_group": "_Test Customer Group", |  | ||||||
| 		"customer_name": "Test Loyalty Customer", |  | ||||||
| 		"customer_type": "Individual", |  | ||||||
| 		"doctype": "Customer", |  | ||||||
| 		"territory": "_Test Territory" |  | ||||||
| 	}).insert() |  | ||||||
| 
 |  | ||||||
| 	# create a new loyalty program Multiple tier |  | ||||||
| 	frappe.get_doc({ |  | ||||||
| 		"doctype": "Loyalty Program", |  | ||||||
| 		"loyalty_program_name": "Test Multiple Loyalty", |  | ||||||
| 		"auto_opt_in": 1, |  | ||||||
| 		"from_date": today(), |  | ||||||
| 		"loyalty_program_type": "Multiple Tier Program", |  | ||||||
| 		"conversion_factor": 1, |  | ||||||
| 		"expiry_duration": 10, |  | ||||||
| 		"company": "_Test Company", |  | ||||||
| 		"cost_center": "Main - _TC", |  | ||||||
| 		"expense_account": "Loyalty - _TC", |  | ||||||
| 		"collection_rules": [ |  | ||||||
| 			{ |  | ||||||
| 				'tier_name': 'Silver', | 				'tier_name': 'Silver', | ||||||
| 				'collection_factor': 1000, | 				'collection_factor': 1000, | ||||||
| 				'min_spent': 10000 | 				'min_spent': 1000 | ||||||
| 			}, | 			}] | ||||||
| 			{ | 		}).insert() | ||||||
| 				'tier_name': 'Gold', | 
 | ||||||
| 				'collection_factor': 1000, | 	# create a new customer | ||||||
| 				'min_spent': 19000 | 	if not frappe.db.exists("Customer","Test Loyalty Customer"): | ||||||
| 			} | 		frappe.get_doc({ | ||||||
| 		] | 			"customer_group": "_Test Customer Group", | ||||||
| 	}).insert() | 			"customer_name": "Test Loyalty Customer", | ||||||
|  | 			"customer_type": "Individual", | ||||||
|  | 			"doctype": "Customer", | ||||||
|  | 			"territory": "_Test Territory" | ||||||
|  | 		}).insert() | ||||||
|  | 
 | ||||||
|  | 	# create a new loyalty program Multiple tier | ||||||
|  | 	if not frappe.db.exists("Loyalty Program","Test Multiple Loyalty"): | ||||||
|  | 		frappe.get_doc({ | ||||||
|  | 			"doctype": "Loyalty Program", | ||||||
|  | 			"loyalty_program_name": "Test Multiple Loyalty", | ||||||
|  | 			"auto_opt_in": 1, | ||||||
|  | 			"from_date": today(), | ||||||
|  | 			"loyalty_program_type": "Multiple Tier Program", | ||||||
|  | 			"conversion_factor": 1, | ||||||
|  | 			"expiry_duration": 10, | ||||||
|  | 			"company": "_Test Company", | ||||||
|  | 			"cost_center": "Main - _TC", | ||||||
|  | 			"expense_account": "Loyalty - _TC", | ||||||
|  | 			"collection_rules": [ | ||||||
|  | 				{ | ||||||
|  | 					'tier_name': 'Silver', | ||||||
|  | 					'collection_factor': 1000, | ||||||
|  | 					'min_spent': 10000 | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					'tier_name': 'Gold', | ||||||
|  | 					'collection_factor': 1000, | ||||||
|  | 					'min_spent': 19000 | ||||||
|  | 				} | ||||||
|  | 			] | ||||||
|  | 		}).insert() | ||||||
| 
 | 
 | ||||||
| 	# create an item | 	# create an item | ||||||
| 	item = frappe.get_doc({ | 	if not frappe.db.exists("Item", "Loyal Item"): | ||||||
| 		"doctype": "Item", | 		frappe.get_doc({ | ||||||
| 		"item_code": "Loyal Item", | 			"doctype": "Item", | ||||||
| 		"item_name": "Loyal Item", | 			"item_code": "Loyal Item", | ||||||
| 		"item_group": "All Item Groups", | 			"item_name": "Loyal Item", | ||||||
| 		"company": "_Test Company", | 			"item_group": "All Item Groups", | ||||||
| 		"is_stock_item": 1, | 			"company": "_Test Company", | ||||||
| 		"opening_stock": 100, | 			"is_stock_item": 1, | ||||||
| 		"valuation_rate": 10000, | 			"opening_stock": 100, | ||||||
| 	}).insert() | 			"valuation_rate": 10000, | ||||||
|  | 		}).insert() | ||||||
| 
 | 
 | ||||||
| 	# create item price | 	# create item price | ||||||
| 	frappe.get_doc({ | 	if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}): | ||||||
| 		"doctype": "Item Price", | 		frappe.get_doc({ | ||||||
| 		"price_list": "Standard Selling", | 			"doctype": "Item Price", | ||||||
| 		"item_code": item.item_code, | 			"price_list": "Standard Selling", | ||||||
| 		"price_list_rate": 10000 | 			"item_code": "Loyal Item", | ||||||
| 	}).insert() | 			"price_list_rate": 10000 | ||||||
|  | 		}).insert() | ||||||
|  | |||||||
| @ -1,12 +1,14 @@ | |||||||
| frappe.listview_settings['Payment Entry'] = { | frappe.listview_settings['Payment Entry'] = { | ||||||
| 
 | 
 | ||||||
| 	onload: function(listview) { | 	onload: function(listview) { | ||||||
| 		listview.page.fields_dict.party_type.get_query = function() { | 		if (listview.page.fields_dict.party_type) { | ||||||
| 			return { | 			listview.page.fields_dict.party_type.get_query = function() { | ||||||
| 				"filters": { | 				return { | ||||||
| 					"name": ["in", Object.keys(frappe.boot.party_account_types)], | 					"filters": { | ||||||
| 				} | 						"name": ["in", Object.keys(frappe.boot.party_account_types)], | ||||||
|  | 					} | ||||||
|  | 				}; | ||||||
| 			}; | 			}; | ||||||
| 		}; | 		} | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
| @ -99,6 +99,7 @@ class PaymentReconciliation(Document): | |||||||
| 				and `tabGL Entry`.against_voucher_type = %(voucher_type)s | 				and `tabGL Entry`.against_voucher_type = %(voucher_type)s | ||||||
| 				and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s | 				and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s | ||||||
| 				and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s | 				and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s | ||||||
|  | 				and `tabGL Entry`.is_cancelled = 0 | ||||||
| 			GROUP BY `tab{doc}`.name | 			GROUP BY `tab{doc}`.name | ||||||
| 			Having | 			Having | ||||||
| 				amount > 0 | 				amount > 0 | ||||||
|  | |||||||
| @ -45,6 +45,7 @@ | |||||||
|    "unique": 0 |    "unique": 0 | ||||||
|   },  |   },  | ||||||
|   { |   { | ||||||
|  |    "description": "Provide the invoice portion in percent", | ||||||
|    "allow_bulk_edit": 0,  |    "allow_bulk_edit": 0,  | ||||||
|    "allow_on_submit": 0,  |    "allow_on_submit": 0,  | ||||||
|    "bold": 1,  |    "bold": 1,  | ||||||
| @ -170,6 +171,7 @@ | |||||||
|    "unique": 0 |    "unique": 0 | ||||||
|   },  |   },  | ||||||
|   { |   { | ||||||
|  |    "description": "Give number of days according to prior selection", | ||||||
|    "allow_bulk_edit": 0,  |    "allow_bulk_edit": 0,  | ||||||
|    "allow_on_submit": 0,  |    "allow_on_submit": 0,  | ||||||
|    "bold": 1,  |    "bold": 1,  | ||||||
| @ -305,7 +307,7 @@ | |||||||
|  "issingle": 0,  |  "issingle": 0,  | ||||||
|  "istable": 0,  |  "istable": 0,  | ||||||
|  "max_attachments": 0,  |  "max_attachments": 0,  | ||||||
|  "modified": "2018-03-08 10:47:32.830478",  |  "modified": "2020-10-14 10:47:32.830478",  | ||||||
|  "modified_by": "Administrator",  |  "modified_by": "Administrator",  | ||||||
|  "module": "Accounts",  |  "module": "Accounts",  | ||||||
|  "name": "Payment Term",  |  "name": "Payment Term",  | ||||||
| @ -381,4 +383,4 @@ | |||||||
|  "sort_order": "DESC",  |  "sort_order": "DESC",  | ||||||
|  "track_changes": 1,  |  "track_changes": 1,  | ||||||
|  "track_seen": 0 |  "track_seen": 0 | ||||||
| } | } | ||||||
|  | |||||||
| @ -45,7 +45,7 @@ class TestPOSClosingEntry(unittest.TestCase): | |||||||
| 		frappe.set_user("Administrator") | 		frappe.set_user("Administrator") | ||||||
| 		frappe.db.sql("delete from `tabPOS Profile`") | 		frappe.db.sql("delete from `tabPOS Profile`") | ||||||
| 
 | 
 | ||||||
| def init_user_and_profile(): | def init_user_and_profile(**args): | ||||||
| 	user = 'test@example.com' | 	user = 'test@example.com' | ||||||
| 	test_user = frappe.get_doc('User', user) | 	test_user = frappe.get_doc('User', user) | ||||||
| 
 | 
 | ||||||
| @ -53,7 +53,7 @@ def init_user_and_profile(): | |||||||
| 	test_user.add_roles(*roles) | 	test_user.add_roles(*roles) | ||||||
| 	frappe.set_user(user) | 	frappe.set_user(user) | ||||||
| 
 | 
 | ||||||
| 	pos_profile = make_pos_profile() | 	pos_profile = make_pos_profile(**args) | ||||||
| 	pos_profile.append('applicable_for_users', { | 	pos_profile.append('applicable_for_users', { | ||||||
| 		'default': 1, | 		'default': 1, | ||||||
| 		'user': user | 		'user': user | ||||||
|  | |||||||
| @ -139,7 +139,8 @@ class POSInvoice(SalesInvoice): | |||||||
| 			frappe.throw(_("At least one mode of payment is required for POS invoice.")) | 			frappe.throw(_("At least one mode of payment is required for POS invoice.")) | ||||||
| 
 | 
 | ||||||
| 	def validate_change_account(self): | 	def validate_change_account(self): | ||||||
| 		if frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company: | 		if self.change_amount and self.account_for_change_amount and \ | ||||||
|  | 			frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company: | ||||||
| 			frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company)) | 			frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company)) | ||||||
| 
 | 
 | ||||||
| 	def validate_change_amount(self): | 	def validate_change_amount(self): | ||||||
|  | |||||||
| @ -7,6 +7,8 @@ import frappe | |||||||
| import unittest, copy, time | import unittest, copy, time | ||||||
| from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile | from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile | ||||||
| from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return | from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return | ||||||
|  | from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry | ||||||
|  | from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt | ||||||
| 
 | 
 | ||||||
| class TestPOSInvoice(unittest.TestCase): | class TestPOSInvoice(unittest.TestCase): | ||||||
| 	def test_timestamp_change(self): | 	def test_timestamp_change(self): | ||||||
| @ -221,29 +223,29 @@ class TestPOSInvoice(unittest.TestCase): | |||||||
| 		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item | 		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item | ||||||
| 		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos | 		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos | ||||||
| 
 | 
 | ||||||
| 		se = make_serialized_item(company='_Test Company with perpetual inventory', | 		se = make_serialized_item(company='_Test Company', | ||||||
| 			target_warehouse="Stores - TCP1", cost_center='Main - TCP1', expense_account='Cost of Goods Sold - TCP1') | 			target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC') | ||||||
| 
 | 
 | ||||||
| 		serial_nos = get_serial_nos(se.get("items")[0].serial_no) | 		serial_nos = get_serial_nos(se.get("items")[0].serial_no) | ||||||
| 
 | 
 | ||||||
| 		pos = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1', | 		pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC', | ||||||
| 			account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1', | 			account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC', | ||||||
| 			expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1', | 			expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC', | ||||||
| 			item=se.get("items")[0].item_code, rate=1000, do_not_save=1) | 			item=se.get("items")[0].item_code, rate=1000, do_not_save=1) | ||||||
| 
 | 
 | ||||||
| 		pos.get("items")[0].serial_no = serial_nos[0] | 		pos.get("items")[0].serial_no = serial_nos[0] | ||||||
| 		pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000}) | 		pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000}) | ||||||
| 
 | 
 | ||||||
| 		pos.insert() | 		pos.insert() | ||||||
| 		pos.submit() | 		pos.submit() | ||||||
| 
 | 
 | ||||||
| 		pos2 = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1', | 		pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC', | ||||||
| 			account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1', | 			account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC', | ||||||
| 			expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1', | 			expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC', | ||||||
| 			item=se.get("items")[0].item_code, rate=1000, do_not_save=1) | 			item=se.get("items")[0].item_code, rate=1000, do_not_save=1) | ||||||
| 
 | 
 | ||||||
| 		pos2.get("items")[0].serial_no = serial_nos[0] | 		pos2.get("items")[0].serial_no = serial_nos[0] | ||||||
| 		pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000}) | 		pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000}) | ||||||
| 
 | 
 | ||||||
| 		self.assertRaises(frappe.ValidationError, pos2.insert) | 		self.assertRaises(frappe.ValidationError, pos2.insert) | ||||||
| 
 | 
 | ||||||
| @ -285,7 +287,7 @@ class TestPOSInvoice(unittest.TestCase): | |||||||
| 
 | 
 | ||||||
| 		after_redeem_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program) | 		after_redeem_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program) | ||||||
| 		self.assertEqual(after_redeem_lp_details.loyalty_points, 9) | 		self.assertEqual(after_redeem_lp_details.loyalty_points, 9) | ||||||
| 	 | 
 | ||||||
| 	def test_merging_into_sales_invoice_with_discount(self): | 	def test_merging_into_sales_invoice_with_discount(self): | ||||||
| 		from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile | 		from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile | ||||||
| 		from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices | 		from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices | ||||||
| @ -294,7 +296,7 @@ class TestPOSInvoice(unittest.TestCase): | |||||||
| 		test_user, pos_profile = init_user_and_profile() | 		test_user, pos_profile = init_user_and_profile() | ||||||
| 		pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1) | 		pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1) | ||||||
| 		pos_inv.append('payments', { | 		pos_inv.append('payments', { | ||||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 | 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 270 | ||||||
| 		}) | 		}) | ||||||
| 		pos_inv.submit() | 		pos_inv.submit() | ||||||
| 
 | 
 | ||||||
| @ -307,9 +309,10 @@ class TestPOSInvoice(unittest.TestCase): | |||||||
| 		merge_pos_invoices() | 		merge_pos_invoices() | ||||||
| 
 | 
 | ||||||
| 		pos_inv.load_from_db() | 		pos_inv.load_from_db() | ||||||
| 		sales_invoice = frappe.get_doc("Sales Invoice", pos_inv.consolidated_invoice) | 		rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total") | ||||||
| 		self.assertEqual(sales_invoice.grand_total, 3500) | 		self.assertEqual(rounded_total, 3470) | ||||||
| 	 | 		frappe.set_user("Administrator") | ||||||
|  | 
 | ||||||
| 	def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self): | 	def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self): | ||||||
| 		from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile | 		from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile | ||||||
| 		from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices | 		from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices | ||||||
| @ -348,8 +351,55 @@ class TestPOSInvoice(unittest.TestCase): | |||||||
| 		merge_pos_invoices() | 		merge_pos_invoices() | ||||||
| 
 | 
 | ||||||
| 		pos_inv.load_from_db() | 		pos_inv.load_from_db() | ||||||
| 		sales_invoice = frappe.get_doc("Sales Invoice", pos_inv.consolidated_invoice) | 		rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total") | ||||||
| 		self.assertEqual(sales_invoice.rounded_total, 840) | 		self.assertEqual(rounded_total, 840) | ||||||
|  | 		frappe.set_user("Administrator") | ||||||
|  | 
 | ||||||
|  | 	def test_merging_with_validate_selling_price(self): | ||||||
|  | 		from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile | ||||||
|  | 		from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices | ||||||
|  | 
 | ||||||
|  | 		if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): | ||||||
|  | 			frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1) | ||||||
|  | 
 | ||||||
|  | 		make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=1, rate=300) | ||||||
|  | 		frappe.db.sql("delete from `tabPOS Invoice`") | ||||||
|  | 		test_user, pos_profile = init_user_and_profile() | ||||||
|  | 		pos_inv = create_pos_invoice(rate=300, do_not_submit=1) | ||||||
|  | 		pos_inv.append('payments', { | ||||||
|  | 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 | ||||||
|  | 		}) | ||||||
|  | 		pos_inv.append('taxes', { | ||||||
|  | 			"charge_type": "On Net Total", | ||||||
|  | 			"account_head": "_Test Account Service Tax - _TC", | ||||||
|  | 			"cost_center": "_Test Cost Center - _TC", | ||||||
|  | 			"description": "Service Tax", | ||||||
|  | 			"rate": 14, | ||||||
|  | 			'included_in_print_rate': 1 | ||||||
|  | 		}) | ||||||
|  | 		self.assertRaises(frappe.ValidationError, pos_inv.submit) | ||||||
|  | 
 | ||||||
|  | 		pos_inv2 = create_pos_invoice(rate=400, do_not_submit=1) | ||||||
|  | 		pos_inv2.append('payments', { | ||||||
|  | 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 400 | ||||||
|  | 		}) | ||||||
|  | 		pos_inv2.append('taxes', { | ||||||
|  | 			"charge_type": "On Net Total", | ||||||
|  | 			"account_head": "_Test Account Service Tax - _TC", | ||||||
|  | 			"cost_center": "_Test Cost Center - _TC", | ||||||
|  | 			"description": "Service Tax", | ||||||
|  | 			"rate": 14, | ||||||
|  | 			'included_in_print_rate': 1 | ||||||
|  | 		}) | ||||||
|  | 		pos_inv2.submit() | ||||||
|  | 
 | ||||||
|  | 		merge_pos_invoices() | ||||||
|  | 
 | ||||||
|  | 		pos_inv2.load_from_db() | ||||||
|  | 		rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total") | ||||||
|  | 		self.assertEqual(rounded_total, 400) | ||||||
|  | 		frappe.set_user("Administrator") | ||||||
|  | 		frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 0) | ||||||
| 
 | 
 | ||||||
| def create_pos_invoice(**args): | def create_pos_invoice(**args): | ||||||
| 	args = frappe._dict(args) | 	args = frappe._dict(args) | ||||||
| @ -364,8 +414,6 @@ def create_pos_invoice(**args): | |||||||
| 	pos_inv.is_pos = 1 | 	pos_inv.is_pos = 1 | ||||||
| 	pos_inv.pos_profile = args.pos_profile or pos_profile.name | 	pos_inv.pos_profile = args.pos_profile or pos_profile.name | ||||||
| 
 | 
 | ||||||
| 	pos_inv.set_missing_values() |  | ||||||
| 
 |  | ||||||
| 	if args.posting_date: | 	if args.posting_date: | ||||||
| 		pos_inv.set_posting_time = 1 | 		pos_inv.set_posting_time = 1 | ||||||
| 	pos_inv.posting_date = args.posting_date or frappe.utils.nowdate() | 	pos_inv.posting_date = args.posting_date or frappe.utils.nowdate() | ||||||
| @ -379,6 +427,8 @@ def create_pos_invoice(**args): | |||||||
| 	pos_inv.conversion_rate = args.conversion_rate or 1 | 	pos_inv.conversion_rate = args.conversion_rate or 1 | ||||||
| 	pos_inv.account_for_change_amount = args.account_for_change_amount or "Cash - _TC" | 	pos_inv.account_for_change_amount = args.account_for_change_amount or "Cash - _TC" | ||||||
| 
 | 
 | ||||||
|  | 	pos_inv.set_missing_values() | ||||||
|  | 
 | ||||||
| 	pos_inv.append("items", { | 	pos_inv.append("items", { | ||||||
| 		"item_code": args.item or args.item_code or "_Test Item", | 		"item_code": args.item or args.item_code or "_Test Item", | ||||||
| 		"warehouse": args.warehouse or "_Test Warehouse - _TC", | 		"warehouse": args.warehouse or "_Test Warehouse - _TC", | ||||||
|  | |||||||
| @ -15,15 +15,6 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) { | |||||||
| 	erpnext.queries.setup_queries(frm, "Warehouse", function() { | 	erpnext.queries.setup_queries(frm, "Warehouse", function() { | ||||||
| 		return erpnext.queries.warehouse(frm.doc); | 		return erpnext.queries.warehouse(frm.doc); | ||||||
| 	}); | 	}); | ||||||
| 
 |  | ||||||
| 	frm.call({ |  | ||||||
| 		method: "erpnext.accounts.doctype.pos_profile.pos_profile.get_series", |  | ||||||
| 		callback: function(r) { |  | ||||||
| 			if(!r.exc) { |  | ||||||
| 				set_field_options("naming_series", r.message); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	}); |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| frappe.ui.form.on('POS Profile', { | frappe.ui.form.on('POS Profile', { | ||||||
|  | |||||||
| @ -8,7 +8,6 @@ | |||||||
|  "field_order": [ |  "field_order": [ | ||||||
|   "disabled", |   "disabled", | ||||||
|   "section_break_2", |   "section_break_2", | ||||||
|   "naming_series", |  | ||||||
|   "customer", |   "customer", | ||||||
|   "company", |   "company", | ||||||
|   "country", |   "country", | ||||||
| @ -59,17 +58,6 @@ | |||||||
|    "fieldname": "section_break_2", |    "fieldname": "section_break_2", | ||||||
|    "fieldtype": "Section Break" |    "fieldtype": "Section Break" | ||||||
|   }, |   }, | ||||||
|   { |  | ||||||
|    "fieldname": "naming_series", |  | ||||||
|    "fieldtype": "Select", |  | ||||||
|    "in_list_view": 1, |  | ||||||
|    "label": "Series", |  | ||||||
|    "no_copy": 1, |  | ||||||
|    "oldfieldname": "naming_series", |  | ||||||
|    "oldfieldtype": "Select", |  | ||||||
|    "options": "[Select]", |  | ||||||
|    "reqd": 1 |  | ||||||
|   }, |  | ||||||
|   { |   { | ||||||
|    "fieldname": "customer", |    "fieldname": "customer", | ||||||
|    "fieldtype": "Link", |    "fieldtype": "Link", | ||||||
| @ -323,7 +311,7 @@ | |||||||
|  "icon": "icon-cog", |  "icon": "icon-cog", | ||||||
|  "idx": 1, |  "idx": 1, | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-06-29 12:20:30.977272", |  "modified": "2020-10-01 17:29:27.759088", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Accounts", |  "module": "Accounts", | ||||||
|  "name": "POS Profile", |  "name": "POS Profile", | ||||||
| @ -350,4 +338,4 @@ | |||||||
|  ], |  ], | ||||||
|  "sort_field": "modified", |  "sort_field": "modified", | ||||||
|  "sort_order": "DESC" |  "sort_order": "DESC" | ||||||
| } | } | ||||||
| @ -109,10 +109,6 @@ def get_child_nodes(group_type, root): | |||||||
| 	return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where | 	return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where | ||||||
| 			lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1) | 			lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1) | ||||||
| 
 | 
 | ||||||
| @frappe.whitelist() |  | ||||||
| def get_series(): |  | ||||||
| 	return frappe.get_meta("POS Invoice").get_field("naming_series").options or "s" |  | ||||||
| 
 |  | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
| @frappe.validate_and_sanitize_search_inputs | @frappe.validate_and_sanitize_search_inputs | ||||||
| def pos_profile_query(doctype, txt, searchfield, start, page_len, filters): | def pos_profile_query(doctype, txt, searchfield, start, page_len, filters): | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| { | { | ||||||
|  |  "actions": [], | ||||||
|  "allow_import": 1, |  "allow_import": 1, | ||||||
|  "allow_rename": 1, |  "allow_rename": 1, | ||||||
|  "autoname": "field:title", |  "autoname": "field:title", | ||||||
| @ -71,6 +72,7 @@ | |||||||
|   "section_break_13", |   "section_break_13", | ||||||
|   "threshold_percentage", |   "threshold_percentage", | ||||||
|   "priority", |   "priority", | ||||||
|  |   "condition", | ||||||
|   "column_break_66", |   "column_break_66", | ||||||
|   "apply_multiple_pricing_rules", |   "apply_multiple_pricing_rules", | ||||||
|   "apply_discount_on_rate", |   "apply_discount_on_rate", | ||||||
| @ -550,11 +552,18 @@ | |||||||
|    "fieldtype": "Link", |    "fieldtype": "Link", | ||||||
|    "label": "Promotional Scheme", |    "label": "Promotional Scheme", | ||||||
|    "options": "Promotional Scheme" |    "options": "Promotional Scheme" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "description": "Simple Python Expression, Example: territory != 'All Territories'", | ||||||
|  |    "fieldname": "condition", | ||||||
|  |    "fieldtype": "Code", | ||||||
|  |    "label": "Condition" | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "icon": "fa fa-gift", |  "icon": "fa fa-gift", | ||||||
|  "idx": 1, |  "idx": 1, | ||||||
|  "modified": "2019-12-18 17:29:22.957077", |  "links": [], | ||||||
|  |  "modified": "2020-08-26 12:24:44.740734", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Accounts", |  "module": "Accounts", | ||||||
|  "name": "Pricing Rule", |  "name": "Pricing Rule", | ||||||
|  | |||||||
| @ -6,9 +6,10 @@ from __future__ import unicode_literals | |||||||
| import frappe | import frappe | ||||||
| import json | import json | ||||||
| import copy | import copy | ||||||
|  | import re | ||||||
|  | 
 | ||||||
| from frappe import throw, _ | from frappe import throw, _ | ||||||
| from frappe.utils import flt, cint, getdate | from frappe.utils import flt, cint, getdate | ||||||
| 
 |  | ||||||
| from frappe.model.document import Document | from frappe.model.document import Document | ||||||
| 
 | 
 | ||||||
| from six import string_types | from six import string_types | ||||||
| @ -30,6 +31,7 @@ class PricingRule(Document): | |||||||
| 		self.validate_max_discount() | 		self.validate_max_discount() | ||||||
| 		self.validate_price_list_with_currency() | 		self.validate_price_list_with_currency() | ||||||
| 		self.validate_dates() | 		self.validate_dates() | ||||||
|  | 		self.validate_condition() | ||||||
| 
 | 
 | ||||||
| 		if not self.margin_type: self.margin_rate_or_amount = 0.0 | 		if not self.margin_type: self.margin_rate_or_amount = 0.0 | ||||||
| 
 | 
 | ||||||
| @ -140,6 +142,10 @@ class PricingRule(Document): | |||||||
| 		if self.valid_from and self.valid_upto and getdate(self.valid_from) > getdate(self.valid_upto): | 		if self.valid_from and self.valid_upto and getdate(self.valid_from) > getdate(self.valid_upto): | ||||||
| 			frappe.throw(_("Valid from date must be less than valid upto date")) | 			frappe.throw(_("Valid from date must be less than valid upto date")) | ||||||
| 
 | 
 | ||||||
|  | 	def validate_condition(self): | ||||||
|  | 		if self.condition and ("=" in self.condition) and re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", self.condition): | ||||||
|  | 			frappe.throw(_("Invalid condition expression")) | ||||||
|  | 
 | ||||||
| #-------------------------------------------------------------------------------- | #-------------------------------------------------------------------------------- | ||||||
| 
 | 
 | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
|  | |||||||
| @ -429,7 +429,34 @@ class TestPricingRule(unittest.TestCase): | |||||||
| 		details = get_item_details(args) | 		details = get_item_details(args) | ||||||
| 
 | 
 | ||||||
| 		self.assertTrue(details) | 		self.assertTrue(details) | ||||||
| 
 | 	 | ||||||
|  | 	def test_pricing_rule_for_condition(self): | ||||||
|  | 		frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule") | ||||||
|  | 		 | ||||||
|  | 		make_pricing_rule(selling=1, margin_type="Percentage", \ | ||||||
|  | 			condition="customer=='_Test Customer 1' and is_return==0", discount_percentage=10) | ||||||
|  | 		 | ||||||
|  | 		# Incorrect Customer and Correct is_return value | ||||||
|  | 		si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 2", is_return=0) | ||||||
|  | 		si.items[0].price_list_rate = 1000 | ||||||
|  | 		si.submit() | ||||||
|  | 		item = si.items[0] | ||||||
|  | 		self.assertEquals(item.rate, 100) | ||||||
|  | 		 | ||||||
|  | 		# Correct Customer and Incorrect is_return value | ||||||
|  | 		si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1) | ||||||
|  | 		si.items[0].price_list_rate = 1000 | ||||||
|  | 		si.submit() | ||||||
|  | 		item = si.items[0] | ||||||
|  | 		self.assertEquals(item.rate, 100) | ||||||
|  | 		 | ||||||
|  | 		# Correct Customer and correct is_return value | ||||||
|  | 		si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0) | ||||||
|  | 		si.items[0].price_list_rate = 1000 | ||||||
|  | 		si.submit() | ||||||
|  | 		item = si.items[0] | ||||||
|  | 		self.assertEquals(item.rate, 900) | ||||||
|  | 		 | ||||||
| def make_pricing_rule(**args): | def make_pricing_rule(**args): | ||||||
| 	args = frappe._dict(args) | 	args = frappe._dict(args) | ||||||
| 
 | 
 | ||||||
| @ -448,7 +475,8 @@ def make_pricing_rule(**args): | |||||||
| 		"discount_percentage": args.discount_percentage or 0.0, | 		"discount_percentage": args.discount_percentage or 0.0, | ||||||
| 		"rate": args.rate or 0.0, | 		"rate": args.rate or 0.0, | ||||||
| 		"margin_type": args.margin_type, | 		"margin_type": args.margin_type, | ||||||
| 		"margin_rate_or_amount": args.margin_rate_or_amount or 0.0 | 		"margin_rate_or_amount": args.margin_rate_or_amount or 0.0, | ||||||
|  | 		"condition": args.condition or '' | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	apply_on = doc.apply_on.replace(' ', '_').lower() | 	apply_on = doc.apply_on.replace(' ', '_').lower() | ||||||
|  | |||||||
| @ -37,6 +37,8 @@ def get_pricing_rules(args, doc=None): | |||||||
| 
 | 
 | ||||||
| 	rules = [] | 	rules = [] | ||||||
| 
 | 
 | ||||||
|  | 	pricing_rules = filter_pricing_rule_based_on_condition(pricing_rules, doc) | ||||||
|  | 
 | ||||||
| 	if not pricing_rules: return [] | 	if not pricing_rules: return [] | ||||||
| 
 | 
 | ||||||
| 	if apply_multiple_pricing_rules(pricing_rules): | 	if apply_multiple_pricing_rules(pricing_rules): | ||||||
| @ -51,6 +53,23 @@ def get_pricing_rules(args, doc=None): | |||||||
| 
 | 
 | ||||||
| 	return rules | 	return rules | ||||||
| 
 | 
 | ||||||
|  | def filter_pricing_rule_based_on_condition(pricing_rules, doc=None): | ||||||
|  | 	filtered_pricing_rules = [] | ||||||
|  | 	if doc: | ||||||
|  | 		for pricing_rule in pricing_rules: | ||||||
|  | 			if pricing_rule.condition: | ||||||
|  | 				try: | ||||||
|  | 					if frappe.safe_eval(pricing_rule.condition, None, doc.as_dict()): | ||||||
|  | 						filtered_pricing_rules.append(pricing_rule) | ||||||
|  | 				except: | ||||||
|  | 					pass | ||||||
|  | 			else: | ||||||
|  | 				filtered_pricing_rules.append(pricing_rule) | ||||||
|  | 	else: | ||||||
|  | 		filtered_pricing_rules = pricing_rules | ||||||
|  | 
 | ||||||
|  | 	return filtered_pricing_rules | ||||||
|  | 
 | ||||||
| def _get_pricing_rules(apply_on, args, values): | def _get_pricing_rules(apply_on, args, values): | ||||||
| 	apply_on_field = frappe.scrub(apply_on) | 	apply_on_field = frappe.scrub(apply_on) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -711,7 +711,8 @@ class PurchaseInvoice(BuyingController): | |||||||
| 									item.item_tax_amount / self.conversion_rate) | 									item.item_tax_amount / self.conversion_rate) | ||||||
| 						}, item=item)) | 						}, item=item)) | ||||||
| 				else: | 				else: | ||||||
| 					cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company) | 					cwip_account = get_asset_account("capital_work_in_progress_account", | ||||||
|  | 						asset_category=item.asset_category,company=self.company) | ||||||
| 
 | 
 | ||||||
| 					cwip_account_currency = get_account_currency(cwip_account) | 					cwip_account_currency = get_account_currency(cwip_account) | ||||||
| 					gl_entries.append(self.get_gl_dict({ | 					gl_entries.append(self.get_gl_dict({ | ||||||
|  | |||||||
| @ -1002,7 +1002,8 @@ def make_purchase_invoice(**args): | |||||||
| 		"cost_center": args.cost_center or "_Test Cost Center - _TC", | 		"cost_center": args.cost_center or "_Test Cost Center - _TC", | ||||||
| 		"project": args.project, | 		"project": args.project, | ||||||
| 		"rejected_warehouse": args.rejected_warehouse or "", | 		"rejected_warehouse": args.rejected_warehouse or "", | ||||||
| 		"rejected_serial_no": args.rejected_serial_no or "" | 		"rejected_serial_no": args.rejected_serial_no or "", | ||||||
|  | 		"asset_location": args.location or "" | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	if args.get_taxes_and_charges: | 	if args.get_taxes_and_charges: | ||||||
|  | |||||||
| @ -19,6 +19,7 @@ | |||||||
|   "is_return", |   "is_return", | ||||||
|   "column_break1", |   "column_break1", | ||||||
|   "company", |   "company", | ||||||
|  |   "company_tax_id", | ||||||
|   "posting_date", |   "posting_date", | ||||||
|   "posting_time", |   "posting_time", | ||||||
|   "set_posting_time", |   "set_posting_time", | ||||||
| @ -1825,7 +1826,7 @@ | |||||||
|    "fieldtype": "Table", |    "fieldtype": "Table", | ||||||
|    "hide_days": 1, |    "hide_days": 1, | ||||||
|    "hide_seconds": 1, |    "hide_seconds": 1, | ||||||
|    "label": "Sales Team1", |    "label": "Sales Contributions and Incentives", | ||||||
|    "oldfieldname": "sales_team", |    "oldfieldname": "sales_team", | ||||||
|    "oldfieldtype": "Table", |    "oldfieldtype": "Table", | ||||||
|    "options": "Sales Team", |    "options": "Sales Team", | ||||||
| @ -1926,6 +1927,7 @@ | |||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "default": "0", |    "default": "0", | ||||||
|  |    "depends_on": "eval:(doc.is_pos && doc.is_consolidated)", | ||||||
|    "fieldname": "is_consolidated", |    "fieldname": "is_consolidated", | ||||||
|    "fieldtype": "Check", |    "fieldtype": "Check", | ||||||
|    "label": "Is Consolidated", |    "label": "Is Consolidated", | ||||||
| @ -1940,13 +1942,20 @@ | |||||||
|    "hide_seconds": 1, |    "hide_seconds": 1, | ||||||
|    "label": "Is Internal Customer", |    "label": "Is Internal Customer", | ||||||
|    "read_only": 1 |    "read_only": 1 | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "fetch_from": "company.tax_id", | ||||||
|  |    "fieldname": "company_tax_id", | ||||||
|  |    "fieldtype": "Data", | ||||||
|  |    "label": "Company Tax ID", | ||||||
|  |    "read_only": 1 | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "icon": "fa fa-file-text", |  "icon": "fa fa-file-text", | ||||||
|  "idx": 181, |  "idx": 181, | ||||||
|  "is_submittable": 1, |  "is_submittable": 1, | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-08-27 01:56:28.532140", |  "modified": "2020-10-09 15:59:57.544736", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Accounts", |  "module": "Accounts", | ||||||
|  "name": "Sales Invoice", |  "name": "Sales Invoice", | ||||||
|  | |||||||
| @ -428,7 +428,7 @@ class SalesInvoice(SellingController): | |||||||
| 			if pos.get('account_for_change_amount'): | 			if pos.get('account_for_change_amount'): | ||||||
| 				self.account_for_change_amount = pos.get('account_for_change_amount') | 				self.account_for_change_amount = pos.get('account_for_change_amount') | ||||||
| 
 | 
 | ||||||
| 			for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name', | 			for fieldname in ('currency', 'letter_head', 'tc_name', | ||||||
| 				'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges', | 				'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges', | ||||||
| 				'write_off_cost_center', 'apply_discount_on', 'cost_center'): | 				'write_off_cost_center', 'apply_discount_on', 'cost_center'): | ||||||
| 					if (not for_validate) or (for_validate and not self.get(fieldname)): | 					if (not for_validate) or (for_validate and not self.get(fieldname)): | ||||||
| @ -572,7 +572,8 @@ class SalesInvoice(SellingController): | |||||||
| 
 | 
 | ||||||
| 	def validate_pos(self): | 	def validate_pos(self): | ||||||
| 		if self.is_return: | 		if self.is_return: | ||||||
| 			if flt(self.paid_amount) + flt(self.write_off_amount) - flt(self.grand_total) > \ | 			invoice_total = self.rounded_total or self.grand_total | ||||||
|  | 			if flt(self.paid_amount) + flt(self.write_off_amount) - flt(invoice_total) > \ | ||||||
| 				1.0/(10.0**(self.precision("grand_total") + 1.0)): | 				1.0/(10.0**(self.precision("grand_total") + 1.0)): | ||||||
| 					frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total")) | 					frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total")) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -13,8 +13,7 @@ def get_data(): | |||||||
| 			'Auto Repeat': 'reference_document', | 			'Auto Repeat': 'reference_document', | ||||||
| 		}, | 		}, | ||||||
| 		'internal_links': { | 		'internal_links': { | ||||||
| 			'Sales Order': ['items', 'sales_order'], | 			'Sales Order': ['items', 'sales_order'] | ||||||
| 			'Delivery Note': ['items', 'delivery_note'] |  | ||||||
| 		}, | 		}, | ||||||
| 		'transactions': [ | 		'transactions': [ | ||||||
| 			{ | 			{ | ||||||
|  | |||||||
| @ -345,13 +345,14 @@ class Subscription(Document): | |||||||
| 			invoice.set_taxes() | 			invoice.set_taxes() | ||||||
| 
 | 
 | ||||||
| 		# Due date | 		# Due date | ||||||
| 		invoice.append( | 		if self.days_until_due: | ||||||
| 			'payment_schedule', | 			invoice.append( | ||||||
| 			{ | 				'payment_schedule', | ||||||
| 				'due_date': add_days(invoice.posting_date, cint(self.days_until_due)), | 				{ | ||||||
| 				'invoice_portion': 100 | 					'due_date': add_days(invoice.posting_date, cint(self.days_until_due)), | ||||||
| 			} | 					'invoice_portion': 100 | ||||||
| 		) | 				} | ||||||
|  | 			) | ||||||
| 
 | 
 | ||||||
| 		# Discounts | 		# Discounts | ||||||
| 		if self.additional_discount_percentage: | 		if self.additional_discount_percentage: | ||||||
|  | |||||||
| @ -106,6 +106,7 @@ def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_detai | |||||||
| 			from `tabGL Entry` | 			from `tabGL Entry` | ||||||
| 			where company = %s and | 			where company = %s and | ||||||
| 			party in %s and fiscal_year=%s and credit > 0 | 			party in %s and fiscal_year=%s and credit > 0 | ||||||
|  | 			and is_opening = 'No' | ||||||
| 		""", (company, tuple(suppliers), fiscal_year), as_dict=1) | 		""", (company, tuple(suppliers), fiscal_year), as_dict=1) | ||||||
| 
 | 
 | ||||||
| 	vouchers = [d.voucher_no for d in entries] | 	vouchers = [d.voucher_no for d in entries] | ||||||
| @ -192,6 +193,7 @@ def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=No | |||||||
| 		select distinct voucher_no | 		select distinct voucher_no | ||||||
| 		from `tabGL Entry` | 		from `tabGL Entry` | ||||||
| 		where party in %s and %s and debit > 0 | 		where party in %s and %s and debit > 0 | ||||||
|  | 		and is_opening = 'No' | ||||||
| 	""", (tuple(suppliers), condition)) or [] | 	""", (tuple(suppliers), condition)) or [] | ||||||
| 
 | 
 | ||||||
| def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None): | def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None): | ||||||
|  | |||||||
| @ -171,7 +171,7 @@ def validate_account_for_perpetual_inventory(gl_map): | |||||||
| 					frappe.throw(_("Account: {0} can only be updated via Stock Transactions") | 					frappe.throw(_("Account: {0} can only be updated via Stock Transactions") | ||||||
| 						.format(account), StockAccountInvalidTransaction) | 						.format(account), StockAccountInvalidTransaction) | ||||||
| 
 | 
 | ||||||
| 			elif account_bal != stock_bal: | 			elif abs(account_bal - stock_bal) > 0.1: | ||||||
| 				precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), | 				precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), | ||||||
| 					currency=frappe.get_cached_value('Company',  gl_map[0].company,  "default_currency")) | 					currency=frappe.get_cached_value('Company',  gl_map[0].company,  "default_currency")) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ | |||||||
|  "owner": "Administrator", |  "owner": "Administrator", | ||||||
|  "steps": [ |  "steps": [ | ||||||
|   { |   { | ||||||
|    "step": "Chart Of Accounts" |    "step": "Chart of Accounts" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "step": "Setup Taxes" |    "step": "Setup Taxes" | ||||||
|  | |||||||
| @ -10,11 +10,11 @@ | |||||||
|  "is_skipped": 0, |  "is_skipped": 0, | ||||||
|  "modified": "2020-05-14 17:40:28.410447", |  "modified": "2020-05-14 17:40:28.410447", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "name": "Chart Of Accounts", |  "name": "Chart of Accounts", | ||||||
|  "owner": "Administrator", |  "owner": "Administrator", | ||||||
|  "path": "Tree/Account", |  "path": "Tree/Account", | ||||||
|  "reference_document": "Account", |  "reference_document": "Account", | ||||||
|  "show_full_form": 0, |  "show_full_form": 0, | ||||||
|  "title": "Review Chart Of Accounts", |  "title": "Review Chart of Accounts", | ||||||
|  "validate_action": 0 |  "validate_action": 0 | ||||||
| } | } | ||||||
| @ -3,6 +3,14 @@ | |||||||
| 
 | 
 | ||||||
| frappe.query_reports["Bank Reconciliation Statement"] = { | frappe.query_reports["Bank Reconciliation Statement"] = { | ||||||
| 	"filters": [ | 	"filters": [ | ||||||
|  | 		{ | ||||||
|  | 			"fieldname":"company", | ||||||
|  | 			"label": __("Company"), | ||||||
|  | 			"fieldtype": "Link", | ||||||
|  | 			"options": "Company", | ||||||
|  | 			"reqd": 1, | ||||||
|  | 			"default": frappe.defaults.get_user_default("Company") | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"fieldname":"account", | 			"fieldname":"account", | ||||||
| 			"label": __("Bank Account"), | 			"label": __("Bank Account"), | ||||||
| @ -12,11 +20,14 @@ frappe.query_reports["Bank Reconciliation Statement"] = { | |||||||
| 				locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "", | 				locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "", | ||||||
| 			"reqd": 1, | 			"reqd": 1, | ||||||
| 			"get_query": function() { | 			"get_query": function() { | ||||||
|  | 				var company = frappe.query_report.get_filter_value('company') | ||||||
| 				return { | 				return { | ||||||
| 					"query": "erpnext.controllers.queries.get_account_list", | 					"query": "erpnext.controllers.queries.get_account_list", | ||||||
| 					"filters": [ | 					"filters": [ | ||||||
| 						['Account', 'account_type', 'in', 'Bank, Cash'], | 						['Account', 'account_type', 'in', 'Bank, Cash'], | ||||||
| 						['Account', 'is_group', '=', 0], | 						['Account', 'is_group', '=', 0], | ||||||
|  | 						['Account', 'disabled', '=', 0], | ||||||
|  | 						['Account', 'company', '=', company], | ||||||
| 					] | 					] | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @ -34,4 +45,4 @@ frappe.query_reports["Bank Reconciliation Statement"] = { | |||||||
| 			"fieldtype": "Check" | 			"fieldtype": "Check" | ||||||
| 		}, | 		}, | ||||||
| 	] | 	] | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										76
									
								
								erpnext/accounts/report/pos_register/pos_register.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								erpnext/accounts/report/pos_register/pos_register.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | |||||||
|  | // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
 | ||||||
|  | // For license information, please see license.txt
 | ||||||
|  | /* eslint-disable */ | ||||||
|  | 
 | ||||||
|  | frappe.query_reports["POS Register"] = { | ||||||
|  | 	"filters": [ | ||||||
|  | 		{ | ||||||
|  | 			"fieldname":"company", | ||||||
|  | 			"label": __("Company"), | ||||||
|  | 			"fieldtype": "Link", | ||||||
|  | 			"options": "Company", | ||||||
|  | 			"default": frappe.defaults.get_user_default("Company"), | ||||||
|  | 			"reqd": 1 | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"fieldname":"from_date", | ||||||
|  | 			"label": __("From Date"), | ||||||
|  | 			"fieldtype": "Date", | ||||||
|  | 			"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), | ||||||
|  | 			"reqd": 1, | ||||||
|  | 			"width": "60px" | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"fieldname":"to_date", | ||||||
|  | 			"label": __("To Date"), | ||||||
|  | 			"fieldtype": "Date", | ||||||
|  | 			"default": frappe.datetime.get_today(), | ||||||
|  | 			"reqd": 1, | ||||||
|  | 			"width": "60px" | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"fieldname":"pos_profile", | ||||||
|  | 			"label": __("POS Profile"), | ||||||
|  | 			"fieldtype": "Link", | ||||||
|  | 			"options": "POS Profile" | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"fieldname":"cashier", | ||||||
|  | 			"label": __("Cashier"), | ||||||
|  | 			"fieldtype": "Link", | ||||||
|  | 			"options": "User" | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"fieldname":"customer", | ||||||
|  | 			"label": __("Customer"), | ||||||
|  | 			"fieldtype": "Link", | ||||||
|  | 			"options": "Customer" | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"fieldname":"mode_of_payment", | ||||||
|  | 			"label": __("Payment Method"), | ||||||
|  | 			"fieldtype": "Link", | ||||||
|  | 			"options": "Mode of Payment" | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"fieldname":"group_by", | ||||||
|  | 			"label": __("Group by"), | ||||||
|  | 			"fieldtype": "Select", | ||||||
|  | 			"options": ["", "POS Profile", "Cashier", "Payment Method", "Customer"], | ||||||
|  | 			"default": "POS Profile" | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"fieldname":"is_return", | ||||||
|  | 			"label": __("Is Return"), | ||||||
|  | 			"fieldtype": "Check" | ||||||
|  | 		}, | ||||||
|  | 	], | ||||||
|  | 	"formatter": function(value, row, column, data, default_formatter) { | ||||||
|  | 		value = default_formatter(value, row, column, data); | ||||||
|  | 		if (data && data.bold) { | ||||||
|  | 			value = value.bold(); | ||||||
|  | 
 | ||||||
|  | 		} | ||||||
|  | 		return value; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
							
								
								
									
										30
									
								
								erpnext/accounts/report/pos_register/pos_register.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								erpnext/accounts/report/pos_register/pos_register.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | { | ||||||
|  |  "add_total_row": 0, | ||||||
|  |  "columns": [], | ||||||
|  |  "creation": "2020-09-10 19:25:03.766871", | ||||||
|  |  "disable_prepared_report": 0, | ||||||
|  |  "disabled": 0, | ||||||
|  |  "docstatus": 0, | ||||||
|  |  "doctype": "Report", | ||||||
|  |  "filters": [], | ||||||
|  |  "idx": 0, | ||||||
|  |  "is_standard": "Yes", | ||||||
|  |  "json": "{}", | ||||||
|  |  "modified": "2020-09-10 19:25:15.851331", | ||||||
|  |  "modified_by": "Administrator", | ||||||
|  |  "module": "Accounts", | ||||||
|  |  "name": "POS Register", | ||||||
|  |  "owner": "Administrator", | ||||||
|  |  "prepared_report": 0, | ||||||
|  |  "ref_doctype": "POS Invoice", | ||||||
|  |  "report_name": "POS Register", | ||||||
|  |  "report_type": "Script Report", | ||||||
|  |  "roles": [ | ||||||
|  |   { | ||||||
|  |    "role": "Accounts Manager" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "role": "Accounts User" | ||||||
|  |   } | ||||||
|  |  ] | ||||||
|  | } | ||||||
							
								
								
									
										222
									
								
								erpnext/accounts/report/pos_register/pos_register.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								erpnext/accounts/report/pos_register/pos_register.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,222 @@ | |||||||
|  | # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors | ||||||
|  | # For license information, please see license.txt | ||||||
|  | 
 | ||||||
|  | from __future__ import unicode_literals | ||||||
|  | import frappe | ||||||
|  | from frappe import _, _dict | ||||||
|  | from erpnext import get_company_currency, get_default_company | ||||||
|  | from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments | ||||||
|  | 
 | ||||||
|  | def execute(filters=None): | ||||||
|  | 	if not filters: | ||||||
|  | 		return [], [] | ||||||
|  | 	 | ||||||
|  | 	validate_filters(filters) | ||||||
|  | 
 | ||||||
|  | 	columns = get_columns(filters) | ||||||
|  | 
 | ||||||
|  | 	group_by_field = get_group_by_field(filters.get("group_by")) | ||||||
|  | 
 | ||||||
|  | 	pos_entries = get_pos_entries(filters, group_by_field) | ||||||
|  | 	if group_by_field != "mode_of_payment": | ||||||
|  | 		concat_mode_of_payments(pos_entries) | ||||||
|  | 
 | ||||||
|  | 	# return only entries if group by is unselected | ||||||
|  | 	if not group_by_field: | ||||||
|  | 		return columns, pos_entries | ||||||
|  | 
 | ||||||
|  | 	# handle grouping | ||||||
|  | 	invoice_map, grouped_data = {}, [] | ||||||
|  | 	for d in pos_entries: | ||||||
|  | 		invoice_map.setdefault(d[group_by_field], []).append(d) | ||||||
|  | 	 | ||||||
|  | 	for key in invoice_map: | ||||||
|  | 		invoices = invoice_map[key] | ||||||
|  | 		grouped_data += invoices | ||||||
|  | 		add_subtotal_row(grouped_data, invoices, group_by_field, key) | ||||||
|  | 
 | ||||||
|  | 	# move group by column to first position | ||||||
|  | 	column_index = next((index for (index, d) in enumerate(columns) if d["fieldname"] == group_by_field), None) | ||||||
|  | 	columns.insert(0, columns.pop(column_index)) | ||||||
|  | 
 | ||||||
|  | 	return columns, grouped_data | ||||||
|  | 
 | ||||||
|  | def get_pos_entries(filters, group_by_field): | ||||||
|  | 	conditions = get_conditions(filters) | ||||||
|  | 	order_by = "p.posting_date" | ||||||
|  | 	select_mop_field, from_sales_invoice_payment, group_by_mop_condition = "", "", "" | ||||||
|  | 	if group_by_field == "mode_of_payment": | ||||||
|  | 		select_mop_field = ", sip.mode_of_payment" | ||||||
|  | 		from_sales_invoice_payment = ", `tabSales Invoice Payment` sip" | ||||||
|  | 		group_by_mop_condition = "sip.parent = p.name AND ifnull(sip.base_amount, 0) != 0 AND" | ||||||
|  | 		order_by += ", sip.mode_of_payment" | ||||||
|  | 
 | ||||||
|  | 	elif group_by_field: | ||||||
|  | 		order_by += ", p.{}".format(group_by_field) | ||||||
|  | 
 | ||||||
|  | 	return frappe.db.sql( | ||||||
|  | 		""" | ||||||
|  | 		SELECT  | ||||||
|  | 			p.posting_date, p.name as pos_invoice, p.pos_profile, | ||||||
|  | 			p.owner, p.base_grand_total as grand_total, p.base_paid_amount as paid_amount, | ||||||
|  | 			p.customer, p.is_return {select_mop_field} | ||||||
|  | 		FROM | ||||||
|  | 			`tabPOS Invoice` p {from_sales_invoice_payment} | ||||||
|  | 		WHERE | ||||||
|  | 			{group_by_mop_condition} | ||||||
|  | 			{conditions} | ||||||
|  | 		ORDER BY | ||||||
|  | 			{order_by} | ||||||
|  | 		""".format( | ||||||
|  | 			select_mop_field=select_mop_field, | ||||||
|  | 			from_sales_invoice_payment=from_sales_invoice_payment, | ||||||
|  | 			group_by_mop_condition=group_by_mop_condition, | ||||||
|  | 			conditions=conditions, | ||||||
|  | 			order_by=order_by | ||||||
|  | 		), filters, as_dict=1) | ||||||
|  | 
 | ||||||
|  | def concat_mode_of_payments(pos_entries): | ||||||
|  | 	mode_of_payments = get_mode_of_payments(set([d.pos_invoice for d in pos_entries])) | ||||||
|  | 	for entry in pos_entries: | ||||||
|  | 		if mode_of_payments.get(entry.pos_invoice): | ||||||
|  | 			entry.mode_of_payment = ", ".join(mode_of_payments.get(entry.pos_invoice, [])) | ||||||
|  | 
 | ||||||
|  | def add_subtotal_row(data, group_invoices, group_by_field, group_by_value): | ||||||
|  | 	grand_total = sum([d.grand_total for d in group_invoices]) | ||||||
|  | 	paid_amount = sum([d.paid_amount for d in group_invoices]) | ||||||
|  | 	data.append({ | ||||||
|  | 		group_by_field: group_by_value, | ||||||
|  | 		"grand_total": grand_total, | ||||||
|  | 		"paid_amount": paid_amount, | ||||||
|  | 		"bold": 1 | ||||||
|  | 	}) | ||||||
|  | 	data.append({}) | ||||||
|  | 
 | ||||||
|  | def validate_filters(filters): | ||||||
|  | 	if not filters.get("company"): | ||||||
|  | 		frappe.throw(_("{0} is mandatory").format(_("Company"))) | ||||||
|  | 	 | ||||||
|  | 	if not filters.get("from_date") and not filters.get("to_date"): | ||||||
|  | 		frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date")))) | ||||||
|  | 	 | ||||||
|  | 	if filters.from_date > filters.to_date: | ||||||
|  | 		frappe.throw(_("From Date must be before To Date")) | ||||||
|  | 
 | ||||||
|  | 	if (filters.get("pos_profile") and filters.get("group_by") == _('POS Profile')): | ||||||
|  | 		frappe.throw(_("Can not filter based on POS Profile, if grouped by POS Profile")) | ||||||
|  | 	 | ||||||
|  | 	if (filters.get("customer") and filters.get("group_by") == _('Customer')): | ||||||
|  | 		frappe.throw(_("Can not filter based on Customer, if grouped by Customer")) | ||||||
|  | 	 | ||||||
|  | 	if (filters.get("owner") and filters.get("group_by") == _('Cashier')): | ||||||
|  | 		frappe.throw(_("Can not filter based on Cashier, if grouped by Cashier")) | ||||||
|  | 	 | ||||||
|  | 	if (filters.get("mode_of_payment") and filters.get("group_by") == _('Payment Method')): | ||||||
|  | 		frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method")) | ||||||
|  | 
 | ||||||
|  | def get_conditions(filters): | ||||||
|  | 	conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s".format( | ||||||
|  | 		company=filters.get("company"), | ||||||
|  | 		from_date=filters.get("from_date"), | ||||||
|  | 		to_date=filters.get("to_date")) | ||||||
|  | 
 | ||||||
|  | 	if filters.get("pos_profile"): | ||||||
|  | 		conditions += " AND pos_profile = %(pos_profile)s".format(pos_profile=filters.get("pos_profile")) | ||||||
|  | 	 | ||||||
|  | 	if filters.get("owner"): | ||||||
|  | 		conditions += " AND owner = %(owner)s".format(owner=filters.get("owner")) | ||||||
|  | 	 | ||||||
|  | 	if filters.get("customer"): | ||||||
|  | 		conditions += " AND customer = %(customer)s".format(customer=filters.get("customer")) | ||||||
|  | 	 | ||||||
|  | 	if filters.get("is_return"): | ||||||
|  | 		conditions += " AND is_return = %(is_return)s".format(is_return=filters.get("is_return")) | ||||||
|  | 	 | ||||||
|  | 	if filters.get("mode_of_payment"): | ||||||
|  | 		conditions += """ | ||||||
|  | 			AND EXISTS( | ||||||
|  | 					SELECT name FROM `tabSales Invoice Payment` sip | ||||||
|  | 					WHERE parent=p.name AND ifnull(sip.mode_of_payment, '') = %(mode_of_payment)s | ||||||
|  | 				)""" | ||||||
|  | 	 | ||||||
|  | 	return conditions | ||||||
|  | 
 | ||||||
|  | def get_group_by_field(group_by): | ||||||
|  | 	group_by_field = "" | ||||||
|  | 
 | ||||||
|  | 	if group_by == "POS Profile": | ||||||
|  | 		group_by_field = "pos_profile" | ||||||
|  | 	elif group_by == "Cashier": | ||||||
|  | 		group_by_field = "owner" | ||||||
|  | 	elif group_by == "Customer": | ||||||
|  | 		group_by_field = "customer" | ||||||
|  | 	elif group_by == "Payment Method": | ||||||
|  | 		group_by_field = "mode_of_payment" | ||||||
|  | 	 | ||||||
|  | 	return group_by_field | ||||||
|  | 
 | ||||||
|  | def get_columns(filters): | ||||||
|  | 	columns = [ | ||||||
|  | 		{ | ||||||
|  | 			"label": _("Posting Date"), | ||||||
|  | 			"fieldname": "posting_date", | ||||||
|  | 			"fieldtype": "Date", | ||||||
|  | 			"width": 90 | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"label": _("POS Invoice"), | ||||||
|  | 			"fieldname": "pos_invoice", | ||||||
|  | 			"fieldtype": "Link", | ||||||
|  | 			"options": "POS Invoice", | ||||||
|  | 			"width": 120 | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"label": _("Customer"), | ||||||
|  | 			"fieldname": "customer", | ||||||
|  | 			"fieldtype": "Link", | ||||||
|  | 			"options": "Customer", | ||||||
|  | 			"width": 120 | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"label": _("POS Profile"), | ||||||
|  | 			"fieldname": "pos_profile", | ||||||
|  | 			"fieldtype": "Link", | ||||||
|  | 			"options": "POS Profile", | ||||||
|  | 			"width": 160 | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"label": _("Cashier"), | ||||||
|  | 			"fieldname": "owner", | ||||||
|  | 			"fieldtype": "Link", | ||||||
|  | 			"options": "User", | ||||||
|  | 			"width": 140 | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"label": _("Grand Total"), | ||||||
|  | 			"fieldname": "grand_total", | ||||||
|  | 			"fieldtype": "Currency", | ||||||
|  | 			"options": "company:currency", | ||||||
|  | 			"width": 120 | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"label": _("Paid Amount"), | ||||||
|  | 			"fieldname": "paid_amount", | ||||||
|  | 			"fieldtype": "Currency", | ||||||
|  | 			"options": "company:currency", | ||||||
|  | 			"width": 120 | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"label": _("Payment Method"), | ||||||
|  | 			"fieldname": "mode_of_payment", | ||||||
|  | 			"fieldtype": "Data", | ||||||
|  | 			"width": 150 | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"label": _("Is Return"), | ||||||
|  | 			"fieldname": "is_return", | ||||||
|  | 			"fieldtype": "Data", | ||||||
|  | 			"width": 80 | ||||||
|  | 		}, | ||||||
|  | 	] | ||||||
|  | 
 | ||||||
|  | 	return columns | ||||||
| @ -131,7 +131,7 @@ class Asset(AccountsController): | |||||||
| 
 | 
 | ||||||
| 	def validate_gross_and_purchase_amount(self): | 	def validate_gross_and_purchase_amount(self): | ||||||
| 		if self.is_existing_asset: return | 		if self.is_existing_asset: return | ||||||
| 		 | 
 | ||||||
| 		if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount: | 		if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount: | ||||||
| 			frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\ | 			frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\ | ||||||
| 				Please do not book expense of multiple assets against one single Asset.") | 				Please do not book expense of multiple assets against one single Asset.") | ||||||
| @ -466,50 +466,63 @@ class Asset(AccountsController): | |||||||
| 
 | 
 | ||||||
| 	def validate_make_gl_entry(self): | 	def validate_make_gl_entry(self): | ||||||
| 		purchase_document = self.get_purchase_document() | 		purchase_document = self.get_purchase_document() | ||||||
| 		asset_bought_with_invoice = purchase_document == self.purchase_invoice | 		if not purchase_document: | ||||||
| 		fixed_asset_account, cwip_account = self.get_asset_accounts() |  | ||||||
| 		cwip_enabled = is_cwip_accounting_enabled(self.asset_category) |  | ||||||
| 		# check if expense already has been booked in case of cwip was enabled after purchasing asset |  | ||||||
| 		expense_booked = False |  | ||||||
| 		cwip_booked = False |  | ||||||
| 
 |  | ||||||
| 		if asset_bought_with_invoice: |  | ||||||
| 			expense_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""", |  | ||||||
| 				(purchase_document, fixed_asset_account), as_dict=1) |  | ||||||
| 		else: |  | ||||||
| 			cwip_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""", |  | ||||||
| 				(purchase_document, cwip_account), as_dict=1) |  | ||||||
| 
 |  | ||||||
| 		if cwip_enabled and (expense_booked or not cwip_booked): |  | ||||||
| 			# if expense has already booked from invoice or cwip is booked from receipt |  | ||||||
| 			return False | 			return False | ||||||
| 		elif not cwip_enabled and (not expense_booked or cwip_booked): | 
 | ||||||
| 			# if cwip is disabled but expense hasn't been booked yet | 		asset_bought_with_invoice = (purchase_document == self.purchase_invoice) | ||||||
| 			return True | 		fixed_asset_account = self.get_fixed_asset_account() | ||||||
| 		elif cwip_enabled: | 		 | ||||||
| 			# default condition | 		cwip_enabled = is_cwip_accounting_enabled(self.asset_category) | ||||||
| 			return True | 		cwip_account = self.get_cwip_account(cwip_enabled=cwip_enabled) | ||||||
|  | 
 | ||||||
|  | 		query = """SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""" | ||||||
|  | 		if asset_bought_with_invoice: | ||||||
|  | 			# with invoice purchase either expense or cwip has been booked | ||||||
|  | 			expense_booked = frappe.db.sql(query, (purchase_document, fixed_asset_account), as_dict=1) | ||||||
|  | 			if expense_booked: | ||||||
|  | 				# if expense is already booked from invoice then do not make gl entries regardless of cwip enabled/disabled | ||||||
|  | 				return False | ||||||
|  | 
 | ||||||
|  | 			cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1) | ||||||
|  | 			if cwip_booked: | ||||||
|  | 				# if cwip is booked from invoice then make gl entries regardless of cwip enabled/disabled | ||||||
|  | 				return True | ||||||
|  | 		else: | ||||||
|  | 			# with receipt purchase either cwip has been booked or no entries have been made | ||||||
|  | 			if not cwip_account: | ||||||
|  | 				# if cwip account isn't available do not make gl entries | ||||||
|  | 				return False | ||||||
|  | 
 | ||||||
|  | 			cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1) | ||||||
|  | 			# if cwip is not booked from receipt then do not make gl entries | ||||||
|  | 			# if cwip is booked from receipt then make gl entries | ||||||
|  | 			return cwip_booked | ||||||
| 
 | 
 | ||||||
| 	def get_purchase_document(self): | 	def get_purchase_document(self): | ||||||
| 		asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock') | 		asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock') | ||||||
| 		purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt | 		purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt | ||||||
| 
 | 
 | ||||||
| 		return purchase_document | 		return purchase_document | ||||||
|  | 	 | ||||||
|  | 	def get_fixed_asset_account(self): | ||||||
|  | 		return get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company) | ||||||
|  | 	 | ||||||
|  | 	def get_cwip_account(self, cwip_enabled=False): | ||||||
|  | 		cwip_account = None | ||||||
|  | 		try: | ||||||
|  | 			cwip_account = get_asset_account("capital_work_in_progress_account", self.name, self.asset_category, self.company) | ||||||
|  | 		except: | ||||||
|  | 			# if no cwip account found in category or company and "cwip is enabled" then raise else silently pass | ||||||
|  | 			if cwip_enabled: | ||||||
|  | 				raise | ||||||
| 
 | 
 | ||||||
| 	def get_asset_accounts(self): | 		return cwip_account | ||||||
| 		fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name, |  | ||||||
| 					asset_category = self.asset_category, company = self.company) |  | ||||||
| 
 |  | ||||||
| 		cwip_account = get_asset_account("capital_work_in_progress_account", |  | ||||||
| 			self.name, self.asset_category, self.company) |  | ||||||
| 
 |  | ||||||
| 		return fixed_asset_account, cwip_account |  | ||||||
| 
 | 
 | ||||||
| 	def make_gl_entries(self): | 	def make_gl_entries(self): | ||||||
| 		gl_entries = [] | 		gl_entries = [] | ||||||
| 
 | 
 | ||||||
| 		purchase_document = self.get_purchase_document() | 		purchase_document = self.get_purchase_document() | ||||||
| 		fixed_asset_account, cwip_account = self.get_asset_accounts() | 		fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account() | ||||||
| 
 | 
 | ||||||
| 		if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()): | 		if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()): | ||||||
| 
 | 
 | ||||||
| @ -561,14 +574,18 @@ class Asset(AccountsController): | |||||||
| 			return 100 * (1 - flt(depreciation_rate, float_precision)) | 			return 100 * (1 - flt(depreciation_rate, float_precision)) | ||||||
| 
 | 
 | ||||||
| def update_maintenance_status(): | def update_maintenance_status(): | ||||||
| 	assets = frappe.get_all('Asset', filters = {'docstatus': 1, 'maintenance_required': 1}) | 	assets = frappe.get_all( | ||||||
|  | 		"Asset", filters={"docstatus": 1, "maintenance_required": 1} | ||||||
|  | 	) | ||||||
| 
 | 
 | ||||||
| 	for asset in assets: | 	for asset in assets: | ||||||
| 		asset = frappe.get_doc("Asset", asset.name) | 		asset = frappe.get_doc("Asset", asset.name) | ||||||
| 		if frappe.db.exists('Asset Maintenance Task', {'parent': asset.name, 'next_due_date': today()}): | 		if frappe.db.exists("Asset Repair", {"asset_name": asset.name, "repair_status": "Pending"}): | ||||||
| 			asset.set_status('In Maintenance') | 			asset.set_status("Out of Order") | ||||||
| 		if frappe.db.exists('Asset Repair', {'asset_name': asset.name, 'repair_status': 'Pending'}): | 		elif frappe.db.exists("Asset Maintenance Task", {"parent": asset.name, "next_due_date": today()}): | ||||||
| 			asset.set_status('Out of Order') | 			asset.set_status("In Maintenance") | ||||||
|  | 		else: | ||||||
|  | 			asset.set_status() | ||||||
| 
 | 
 | ||||||
| def make_post_gl_entry(): | def make_post_gl_entry(): | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ from frappe.utils import cstr, nowdate, getdate, flt, get_last_day, add_days, ad | |||||||
| from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset | from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset | ||||||
| from erpnext.assets.doctype.asset.asset import make_sales_invoice | from erpnext.assets.doctype.asset.asset import make_sales_invoice | ||||||
| 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.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice | ||||||
| from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice | from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice | ||||||
| 
 | 
 | ||||||
| class TestAsset(unittest.TestCase): | class TestAsset(unittest.TestCase): | ||||||
| @ -558,81 +559,6 @@ class TestAsset(unittest.TestCase): | |||||||
| 
 | 
 | ||||||
| 		self.assertEqual(gle, expected_gle) | 		self.assertEqual(gle, expected_gle) | ||||||
| 
 | 
 | ||||||
| 	def test_gle_with_cwip_toggling(self): |  | ||||||
| 		# TEST: purchase an asset with cwip enabled and then disable cwip and try submitting the asset |  | ||||||
| 		frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1) |  | ||||||
| 
 |  | ||||||
| 		pr = make_purchase_receipt(item_code="Macbook Pro", |  | ||||||
| 			qty=1, rate=5000, do_not_submit=True, location="Test Location") |  | ||||||
| 		pr.set('taxes', [{ |  | ||||||
| 			'category': 'Total', |  | ||||||
| 			'add_deduct_tax': 'Add', |  | ||||||
| 			'charge_type': 'On Net Total', |  | ||||||
| 			'account_head': '_Test Account Service Tax - _TC', |  | ||||||
| 			'description': '_Test Account Service Tax', |  | ||||||
| 			'cost_center': 'Main - _TC', |  | ||||||
| 			'rate': 5.0 |  | ||||||
| 		}, { |  | ||||||
| 			'category': 'Valuation and Total', |  | ||||||
| 			'add_deduct_tax': 'Add', |  | ||||||
| 			'charge_type': 'On Net Total', |  | ||||||
| 			'account_head': '_Test Account Shipping Charges - _TC', |  | ||||||
| 			'description': '_Test Account Shipping Charges', |  | ||||||
| 			'cost_center': 'Main - _TC', |  | ||||||
| 			'rate': 5.0 |  | ||||||
| 		}]) |  | ||||||
| 		pr.submit() |  | ||||||
| 		expected_gle = ( |  | ||||||
| 			("Asset Received But Not Billed - _TC", 0.0, 5250.0), |  | ||||||
| 			("CWIP Account - _TC", 5250.0, 0.0) |  | ||||||
| 		) |  | ||||||
| 		pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` |  | ||||||
| 			where voucher_type='Purchase Receipt' and voucher_no = %s |  | ||||||
| 			order by account""", pr.name) |  | ||||||
| 		self.assertEqual(pr_gle, expected_gle) |  | ||||||
| 
 |  | ||||||
| 		pi = make_invoice(pr.name) |  | ||||||
| 		pi.submit() |  | ||||||
| 		expected_gle = ( |  | ||||||
| 			("_Test Account Service Tax - _TC", 250.0, 0.0), |  | ||||||
| 			("_Test Account Shipping Charges - _TC", 250.0, 0.0), |  | ||||||
| 			("Asset Received But Not Billed - _TC", 5250.0, 0.0), |  | ||||||
| 			("Creditors - _TC", 0.0, 5500.0), |  | ||||||
| 			("Expenses Included In Asset Valuation - _TC", 0.0, 250.0), |  | ||||||
| 		) |  | ||||||
| 		pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` |  | ||||||
| 			where voucher_type='Purchase Invoice' and voucher_no = %s |  | ||||||
| 			order by account""", pi.name) |  | ||||||
| 		self.assertEqual(pi_gle, expected_gle) |  | ||||||
| 
 |  | ||||||
| 		asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') |  | ||||||
| 		asset_doc = frappe.get_doc('Asset', asset) |  | ||||||
| 		month_end_date = get_last_day(nowdate()) |  | ||||||
| 		asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) |  | ||||||
| 		self.assertEqual(asset_doc.gross_purchase_amount, 5250.0) |  | ||||||
| 		asset_doc.append("finance_books", { |  | ||||||
| 			"expected_value_after_useful_life": 200, |  | ||||||
| 			"depreciation_method": "Straight Line", |  | ||||||
| 			"total_number_of_depreciations": 3, |  | ||||||
| 			"frequency_of_depreciation": 10, |  | ||||||
| 			"depreciation_start_date": month_end_date |  | ||||||
| 		}) |  | ||||||
| 
 |  | ||||||
| 		# disable cwip and try submitting |  | ||||||
| 		frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0) |  | ||||||
| 		asset_doc.submit() |  | ||||||
| 		# asset should have gl entries even if cwip is disabled |  | ||||||
| 		expected_gle = ( |  | ||||||
| 			("_Test Fixed Asset - _TC", 5250.0, 0.0), |  | ||||||
| 			("CWIP Account - _TC", 0.0, 5250.0) |  | ||||||
| 		) |  | ||||||
| 		gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` |  | ||||||
| 			where voucher_type='Asset' and voucher_no = %s |  | ||||||
| 			order by account""", asset_doc.name) |  | ||||||
| 		self.assertEqual(gle, expected_gle) |  | ||||||
| 
 |  | ||||||
| 		frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1) |  | ||||||
| 
 |  | ||||||
| 	def test_expense_head(self): | 	def test_expense_head(self): | ||||||
| 		pr = make_purchase_receipt(item_code="Macbook Pro", | 		pr = make_purchase_receipt(item_code="Macbook Pro", | ||||||
| 			qty=2, rate=200000.0, location="Test Location") | 			qty=2, rate=200000.0, location="Test Location") | ||||||
| @ -640,6 +566,74 @@ class TestAsset(unittest.TestCase): | |||||||
| 		doc = make_invoice(pr.name) | 		doc = make_invoice(pr.name) | ||||||
| 
 | 
 | ||||||
| 		self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) | 		self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) | ||||||
|  | 	 | ||||||
|  | 	def test_asset_cwip_toggling_cases(self): | ||||||
|  | 		cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting") | ||||||
|  | 		name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"]) | ||||||
|  | 		cwip_acc = "CWIP Account - _TC" | ||||||
|  | 
 | ||||||
|  | 		frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0) | ||||||
|  | 		frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "") | ||||||
|  | 		frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", "") | ||||||
|  | 
 | ||||||
|  | 		# case 0 -- PI with cwip disable, Asset with cwip disabled, No cwip account set | ||||||
|  | 		pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1) | ||||||
|  | 		asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name') | ||||||
|  | 		asset_doc = frappe.get_doc('Asset', asset) | ||||||
|  | 		asset_doc.available_for_use_date = nowdate() | ||||||
|  | 		asset_doc.calculate_depreciation = 0 | ||||||
|  | 		asset_doc.submit() | ||||||
|  | 		gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name) | ||||||
|  | 		self.assertFalse(gle) | ||||||
|  | 
 | ||||||
|  | 		# case 1 -- PR with cwip disabled, Asset with cwip enabled | ||||||
|  | 		pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location") | ||||||
|  | 		frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1) | ||||||
|  | 		frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc) | ||||||
|  | 		asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') | ||||||
|  | 		asset_doc = frappe.get_doc('Asset', asset) | ||||||
|  | 		asset_doc.available_for_use_date = nowdate() | ||||||
|  | 		asset_doc.calculate_depreciation = 0 | ||||||
|  | 		asset_doc.submit() | ||||||
|  | 		gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name) | ||||||
|  | 		self.assertFalse(gle) | ||||||
|  | 
 | ||||||
|  | 		# case 2 -- PR with cwip enabled, Asset with cwip disabled | ||||||
|  | 		pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location") | ||||||
|  | 		frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0) | ||||||
|  | 		asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') | ||||||
|  | 		asset_doc = frappe.get_doc('Asset', asset) | ||||||
|  | 		asset_doc.available_for_use_date = nowdate() | ||||||
|  | 		asset_doc.calculate_depreciation = 0 | ||||||
|  | 		asset_doc.submit() | ||||||
|  | 		gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name) | ||||||
|  | 		self.assertTrue(gle) | ||||||
|  | 
 | ||||||
|  | 		# case 3 -- PI with cwip disabled, Asset with cwip enabled | ||||||
|  | 		pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1) | ||||||
|  | 		frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1) | ||||||
|  | 		asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name') | ||||||
|  | 		asset_doc = frappe.get_doc('Asset', asset) | ||||||
|  | 		asset_doc.available_for_use_date = nowdate() | ||||||
|  | 		asset_doc.calculate_depreciation = 0 | ||||||
|  | 		asset_doc.submit() | ||||||
|  | 		gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name) | ||||||
|  | 		self.assertFalse(gle) | ||||||
|  | 
 | ||||||
|  | 		# case 4 -- PI with cwip enabled, Asset with cwip disabled | ||||||
|  | 		pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1) | ||||||
|  | 		frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0) | ||||||
|  | 		asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name') | ||||||
|  | 		asset_doc = frappe.get_doc('Asset', asset) | ||||||
|  | 		asset_doc.available_for_use_date = nowdate() | ||||||
|  | 		asset_doc.calculate_depreciation = 0 | ||||||
|  | 		asset_doc.submit() | ||||||
|  | 		gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name) | ||||||
|  | 		self.assertTrue(gle) | ||||||
|  | 
 | ||||||
|  | 		frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", cwip) | ||||||
|  | 		frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc) | ||||||
|  | 		frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc) | ||||||
| 
 | 
 | ||||||
| def create_asset_data(): | def create_asset_data(): | ||||||
| 	if not frappe.db.exists("Asset Category", "Computers"): | 	if not frappe.db.exists("Asset Category", "Computers"): | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ | |||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| import frappe | import frappe | ||||||
| from frappe import _ | from frappe import _ | ||||||
| from frappe.utils import cint | from frappe.utils import cint, get_link_to_form | ||||||
| from frappe.model.document import Document | from frappe.model.document import Document | ||||||
| 
 | 
 | ||||||
| class AssetCategory(Document): | class AssetCategory(Document): | ||||||
| @ -13,6 +13,7 @@ class AssetCategory(Document): | |||||||
| 		self.validate_finance_books() | 		self.validate_finance_books() | ||||||
| 		self.validate_account_types() | 		self.validate_account_types() | ||||||
| 		self.validate_account_currency() | 		self.validate_account_currency() | ||||||
|  | 		self.valide_cwip_account() | ||||||
| 
 | 
 | ||||||
| 	def validate_finance_books(self): | 	def validate_finance_books(self): | ||||||
| 		for d in self.finance_books: | 		for d in self.finance_books: | ||||||
| @ -58,6 +59,21 @@ class AssetCategory(Document): | |||||||
| 						frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.") | 						frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.") | ||||||
| 							.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)), | 							.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)), | ||||||
| 							title=_("Invalid Account")) | 							title=_("Invalid Account")) | ||||||
|  | 	 | ||||||
|  | 	def valide_cwip_account(self): | ||||||
|  | 		if self.enable_cwip_accounting: | ||||||
|  | 			missing_cwip_accounts_for_company = [] | ||||||
|  | 			for d in self.accounts: | ||||||
|  | 				if (not d.capital_work_in_progress_account and  | ||||||
|  | 					not frappe.db.get_value("Company", d.company_name, "capital_work_in_progress_account")): | ||||||
|  | 					missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name)) | ||||||
|  | 
 | ||||||
|  | 			if missing_cwip_accounts_for_company: | ||||||
|  | 				msg = _("""To enable Capital Work in Progress Accounting, """) | ||||||
|  | 				msg += _("""you must select Capital Work in Progress Account in accounts table""") | ||||||
|  | 				msg += "<br><br>" | ||||||
|  | 				msg += _("You can also set default CWIP account in Company {}").format(", ".join(missing_cwip_accounts_for_company)) | ||||||
|  | 				frappe.throw(msg, title=_("Missing Account")) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
|  | |||||||
| @ -26,4 +26,22 @@ class TestAssetCategory(unittest.TestCase): | |||||||
| 			asset_category.insert() | 			asset_category.insert() | ||||||
| 		except frappe.DuplicateEntryError: | 		except frappe.DuplicateEntryError: | ||||||
| 			pass | 			pass | ||||||
| 			 | 
 | ||||||
|  | 	def test_cwip_accounting(self): | ||||||
|  | 		company_cwip_acc = frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account") | ||||||
|  | 		frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "") | ||||||
|  | 
 | ||||||
|  | 		asset_category = frappe.new_doc("Asset Category") | ||||||
|  | 		asset_category.asset_category_name = "Computers" | ||||||
|  | 		asset_category.enable_cwip_accounting = 1 | ||||||
|  | 
 | ||||||
|  | 		asset_category.total_number_of_depreciations = 3 | ||||||
|  | 		asset_category.frequency_of_depreciation = 3 | ||||||
|  | 		asset_category.append("accounts", { | ||||||
|  | 			"company_name": "_Test Company", | ||||||
|  | 			"fixed_asset_account": "_Test Fixed Asset - _TC", | ||||||
|  | 			"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC", | ||||||
|  | 			"depreciation_expense_account": "_Test Depreciations - _TC" | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		self.assertRaises(frappe.ValidationError, asset_category.insert) | ||||||
| @ -33,7 +33,7 @@ | |||||||
|   { |   { | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
|    "label": "Other Reports", |    "label": "Other Reports", | ||||||
|    "links": "[\n    {\n        \"is_query_report\": true,\n        \"label\": \"Items To Be Requested\",\n        \"name\": \"Items To Be Requested\",\n        \"onboard\": 1,\n        \"reference_doctype\": \"Item\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Item-wise Purchase History\",\n        \"name\": \"Item-wise Purchase History\",\n        \"onboard\": 1,\n        \"reference_doctype\": \"Item\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Purchase Receipt Trends\",\n        \"name\": \"Purchase Receipt Trends\",\n        \"reference_doctype\": \"Purchase Receipt\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Purchase Invoice Trends\",\n        \"name\": \"Purchase Invoice Trends\",\n        \"reference_doctype\": \"Purchase Invoice\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n        \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n        \"reference_doctype\": \"Purchase Order\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Subcontracted Item To Be Received\",\n        \"name\": \"Subcontracted Item To Be Received\",\n        \"reference_doctype\": \"Purchase Order\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Quoted Item Comparison\",\n        \"name\": \"Quoted Item Comparison\",\n         \"onboard\": 1,\n        \"reference_doctype\": \"Supplier Quotation\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Material Requests for which Supplier Quotations are not created\",\n        \"name\": \"Material Requests for which Supplier Quotations are not created\",\n        \"reference_doctype\": \"Material Request\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Supplier Addresses And Contacts\",\n        \"name\": \"Address And Contacts\",\n        \"reference_doctype\": \"Address\",\n        \"route_options\": {\n            \"party_type\": \"Supplier\"\n        },\n        \"type\": \"report\"\n    }\n]" |    "links": "[\n    {\n        \"is_query_report\": true,\n        \"label\": \"Items To Be Requested\",\n        \"name\": \"Items To Be Requested\",\n        \"onboard\": 1,\n        \"reference_doctype\": \"Item\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Item-wise Purchase History\",\n        \"name\": \"Item-wise Purchase History\",\n        \"onboard\": 1,\n        \"reference_doctype\": \"Item\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Purchase Receipt Trends\",\n        \"name\": \"Purchase Receipt Trends\",\n        \"reference_doctype\": \"Purchase Receipt\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Purchase Invoice Trends\",\n        \"name\": \"Purchase Invoice Trends\",\n        \"reference_doctype\": \"Purchase Invoice\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n        \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n        \"reference_doctype\": \"Purchase Order\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Subcontracted Item To Be Received\",\n        \"name\": \"Subcontracted Item To Be Received\",\n        \"reference_doctype\": \"Purchase Order\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Supplier Quotation Comparison\",\n        \"name\": \"Supplier Quotation Comparison\",\n        \"onboard\": 1,\n        \"reference_doctype\": \"Supplier Quotation\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Material Requests for which Supplier Quotations are not created\",\n        \"name\": \"Material Requests for which Supplier Quotations are not created\",\n        \"reference_doctype\": \"Material Request\",\n        \"type\": \"report\"\n    },\n    {\n        \"is_query_report\": true,\n        \"label\": \"Supplier Addresses And Contacts\",\n        \"name\": \"Address And Contacts\",\n        \"reference_doctype\": \"Address\",\n        \"route_options\": {\n            \"party_type\": \"Supplier\"\n        },\n        \"type\": \"report\"\n    }\n]" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
| @ -60,7 +60,7 @@ | |||||||
|  "idx": 0, |  "idx": 0, | ||||||
|  "is_standard": 1, |  "is_standard": 1, | ||||||
|  "label": "Buying", |  "label": "Buying", | ||||||
|  "modified": "2020-06-29 19:30:24.983050", |  "modified": "2020-09-30 14:40:55.638458", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Buying", |  "module": "Buying", | ||||||
|  "name": "Buying", |  "name": "Buying", | ||||||
|  | |||||||
| @ -651,12 +651,12 @@ class TestPurchaseOrder(unittest.TestCase): | |||||||
| 		make_subcontracted_item(item_code) | 		make_subcontracted_item(item_code) | ||||||
| 
 | 
 | ||||||
| 		po = create_purchase_order(item_code=item_code, qty=1, | 		po = create_purchase_order(item_code=item_code, qty=1, | ||||||
| 			is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") | 			is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=1) | ||||||
| 
 | 
 | ||||||
| 		name = frappe.db.get_value('BOM', {'item': item_code}, 'name') | 		name = frappe.db.get_value('BOM', {'item': item_code}, 'name') | ||||||
| 		bom = frappe.get_doc('BOM', name) | 		bom = frappe.get_doc('BOM', name) | ||||||
| 
 | 
 | ||||||
| 		exploded_items = sorted([d.item_code for d in bom.exploded_items]) | 		exploded_items = sorted([d.item_code for d in bom.exploded_items if not d.get('sourced_by_supplier')]) | ||||||
| 		supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) | 		supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) | ||||||
| 		self.assertEquals(exploded_items, supplied_items) | 		self.assertEquals(exploded_items, supplied_items) | ||||||
| 
 | 
 | ||||||
| @ -664,7 +664,7 @@ class TestPurchaseOrder(unittest.TestCase): | |||||||
| 			is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0) | 			is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0) | ||||||
| 
 | 
 | ||||||
| 		supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items]) | 		supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items]) | ||||||
| 		bom_items = sorted([d.item_code for d in bom.items]) | 		bom_items = sorted([d.item_code for d in bom.items if not d.get('sourced_by_supplier')]) | ||||||
| 
 | 
 | ||||||
| 		self.assertEquals(supplied_items1, bom_items) | 		self.assertEquals(supplied_items1, bom_items) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -22,8 +22,6 @@ frappe.ui.form.on("Request for Quotation",{ | |||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	onload: function(frm) { | 	onload: function(frm) { | ||||||
| 		frm.add_fetch('email_template', 'response', 'message_for_supplier'); |  | ||||||
| 
 |  | ||||||
| 		if(!frm.doc.message_for_supplier) { | 		if(!frm.doc.message_for_supplier) { | ||||||
| 			frm.set_value("message_for_supplier", __("Please supply the specified items at the best possible rates")) | 			frm.set_value("message_for_supplier", __("Please supply the specified items at the best possible rates")) | ||||||
| 		} | 		} | ||||||
| @ -179,7 +177,7 @@ frappe.ui.form.on("Request for Quotation",{ | |||||||
| 			dialog.hide(); | 			dialog.hide(); | ||||||
| 			return frappe.call({ | 			return frappe.call({ | ||||||
| 				type: "GET", | 				type: "GET", | ||||||
| 				method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation", | 				method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq", | ||||||
| 				args: { | 				args: { | ||||||
| 					"source_name": doc.name, | 					"source_name": doc.name, | ||||||
| 					"for_supplier": args.supplier | 					"for_supplier": args.supplier | ||||||
| @ -194,6 +192,66 @@ frappe.ui.form.on("Request for Quotation",{ | |||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
| 		dialog.show() | 		dialog.show() | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	preview: (frm) => { | ||||||
|  | 		let dialog = new frappe.ui.Dialog({ | ||||||
|  | 			title: __('Preview Email'), | ||||||
|  | 			fields: [ | ||||||
|  | 				{ | ||||||
|  | 					label: __('Supplier'), | ||||||
|  | 					fieldtype: 'Select', | ||||||
|  | 					fieldname: 'supplier', | ||||||
|  | 					options: frm.doc.suppliers.map(row => row.supplier), | ||||||
|  | 					reqd: 1 | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					fieldtype: 'Column Break', | ||||||
|  | 					fieldname: 'col_break_1', | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					label: __('Subject'), | ||||||
|  | 					fieldtype: 'Data', | ||||||
|  | 					fieldname: 'subject', | ||||||
|  | 					read_only: 1, | ||||||
|  | 					depends_on: 'subject' | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					fieldtype: 'Section Break', | ||||||
|  | 					fieldname: 'sec_break_1', | ||||||
|  | 					hide_border: 1 | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					label: __('Email'), | ||||||
|  | 					fieldtype: 'HTML', | ||||||
|  | 					fieldname: 'email_preview' | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					fieldtype: 'Section Break', | ||||||
|  | 					fieldname: 'sec_break_2' | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					label: __('Note'), | ||||||
|  | 					fieldtype: 'HTML', | ||||||
|  | 					fieldname: 'note' | ||||||
|  | 				} | ||||||
|  | 			] | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		dialog.fields_dict['supplier'].df.onchange = () => { | ||||||
|  | 			var supplier = dialog.get_value('supplier'); | ||||||
|  | 			frm.call('get_supplier_email_preview', {supplier: supplier}).then(result => { | ||||||
|  | 				dialog.fields_dict.email_preview.$wrapper.empty(); | ||||||
|  | 				dialog.fields_dict.email_preview.$wrapper.append(result.message); | ||||||
|  | 			}); | ||||||
|  | 
 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		dialog.fields_dict.note.$wrapper.append(`<p class="small text-muted">This is a preview of the email to be sent. A PDF of the document will
 | ||||||
|  | 			automatically be attached with the email.</p>`); | ||||||
|  | 
 | ||||||
|  | 		dialog.set_value("subject", frm.doc.subject); | ||||||
|  | 		dialog.show(); | ||||||
| 	} | 	} | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| @ -276,7 +334,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e | |||||||
| 					}) | 					}) | ||||||
| 				}, __("Get items from")); | 				}, __("Get items from")); | ||||||
| 			// Get items from Opportunity
 | 			// Get items from Opportunity
 | ||||||
|             this.frm.add_custom_button(__('Opportunity'), | 			this.frm.add_custom_button(__('Opportunity'), | ||||||
| 				function() { | 				function() { | ||||||
| 					erpnext.utils.map_current_doc({ | 					erpnext.utils.map_current_doc({ | ||||||
| 						method: "erpnext.crm.doctype.opportunity.opportunity.make_request_for_quotation", | 						method: "erpnext.crm.doctype.opportunity.opportunity.make_request_for_quotation", | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| { | { | ||||||
|  "actions": "", |  "actions": [], | ||||||
|  "allow_import": 1, |  "allow_import": 1, | ||||||
|  "autoname": "naming_series:", |  "autoname": "naming_series:", | ||||||
|  "creation": "2016-02-25 01:24:07.224790", |  "creation": "2016-02-25 01:24:07.224790", | ||||||
| @ -19,7 +19,12 @@ | |||||||
|   "items", |   "items", | ||||||
|   "link_to_mrs", |   "link_to_mrs", | ||||||
|   "supplier_response_section", |   "supplier_response_section", | ||||||
|  |   "salutation", | ||||||
|   "email_template", |   "email_template", | ||||||
|  |   "col_break_email_1", | ||||||
|  |   "subject", | ||||||
|  |   "preview", | ||||||
|  |   "sec_break_email_2", | ||||||
|   "message_for_supplier", |   "message_for_supplier", | ||||||
|   "terms_section_break", |   "terms_section_break", | ||||||
|   "tc_name", |   "tc_name", | ||||||
| @ -126,8 +131,10 @@ | |||||||
|    "label": "Link to Material Requests" |    "label": "Link to Material Requests" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|  |    "depends_on": "eval:!doc.__islocal", | ||||||
|    "fieldname": "supplier_response_section", |    "fieldname": "supplier_response_section", | ||||||
|    "fieldtype": "Section Break" |    "fieldtype": "Section Break", | ||||||
|  |    "label": "Email Details" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "fieldname": "email_template", |    "fieldname": "email_template", | ||||||
| @ -137,6 +144,8 @@ | |||||||
|    "print_hide": 1 |    "print_hide": 1 | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|  |    "fetch_from": "email_template.response", | ||||||
|  |    "fetch_if_empty": 1, | ||||||
|    "fieldname": "message_for_supplier", |    "fieldname": "message_for_supplier", | ||||||
|    "fieldtype": "Text Editor", |    "fieldtype": "Text Editor", | ||||||
|    "in_list_view": 1, |    "in_list_view": 1, | ||||||
| @ -230,12 +239,45 @@ | |||||||
|    "options": "Request for Quotation", |    "options": "Request for Quotation", | ||||||
|    "print_hide": 1, |    "print_hide": 1, | ||||||
|    "read_only": 1 |    "read_only": 1 | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "fetch_from": "email_template.subject", | ||||||
|  |    "fetch_if_empty": 1, | ||||||
|  |    "fieldname": "subject", | ||||||
|  |    "fieldtype": "Data", | ||||||
|  |    "label": "Subject", | ||||||
|  |    "print_hide": 1 | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "description": "Select a greeting for the receiver. E.g. Mr., Ms., etc.", | ||||||
|  |    "fieldname": "salutation", | ||||||
|  |    "fieldtype": "Link", | ||||||
|  |    "label": "Salutation", | ||||||
|  |    "no_copy": 1, | ||||||
|  |    "options": "Salutation", | ||||||
|  |    "print_hide": 1 | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "fieldname": "col_break_email_1", | ||||||
|  |    "fieldtype": "Column Break" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "depends_on": "eval:!doc.docstatus==1", | ||||||
|  |    "fieldname": "preview", | ||||||
|  |    "fieldtype": "Button", | ||||||
|  |    "label": "Preview Email" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "depends_on": "eval:!doc.__islocal", | ||||||
|  |    "fieldname": "sec_break_email_2", | ||||||
|  |    "fieldtype": "Section Break", | ||||||
|  |    "hide_border": 1 | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "icon": "fa fa-shopping-cart", |  "icon": "fa fa-shopping-cart", | ||||||
|  "is_submittable": 1, |  "is_submittable": 1, | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-06-25 14:37:21.140194", |  "modified": "2020-10-01 14:54:50.888729", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Buying", |  "module": "Buying", | ||||||
|  "name": "Request for Quotation", |  "name": "Request for Quotation", | ||||||
|  | |||||||
| @ -51,7 +51,7 @@ class RequestforQuotation(BuyingController): | |||||||
| 
 | 
 | ||||||
| 	def validate_email_id(self, args): | 	def validate_email_id(self, args): | ||||||
| 		if not args.email_id: | 		if not args.email_id: | ||||||
| 			frappe.throw(_("Row {0}: For Supplier {0}, Email Address is Required to Send Email").format(args.idx, args.supplier)) | 			frappe.throw(_("Row {0}: For Supplier {1}, Email Address is Required to send an email").format(args.idx, frappe.bold(args.supplier))) | ||||||
| 
 | 
 | ||||||
| 	def on_submit(self): | 	def on_submit(self): | ||||||
| 		frappe.db.set(self, 'status', 'Submitted') | 		frappe.db.set(self, 'status', 'Submitted') | ||||||
| @ -62,17 +62,31 @@ class RequestforQuotation(BuyingController): | |||||||
| 	def on_cancel(self): | 	def on_cancel(self): | ||||||
| 		frappe.db.set(self, 'status', 'Cancelled') | 		frappe.db.set(self, 'status', 'Cancelled') | ||||||
| 
 | 
 | ||||||
|  | 	def get_supplier_email_preview(self, supplier): | ||||||
|  | 		"""Returns formatted email preview as string.""" | ||||||
|  | 		rfq_suppliers = list(filter(lambda row: row.supplier == supplier, self.suppliers)) | ||||||
|  | 		rfq_supplier = rfq_suppliers[0] | ||||||
|  | 
 | ||||||
|  | 		self.validate_email_id(rfq_supplier) | ||||||
|  | 
 | ||||||
|  | 		message  = self.supplier_rfq_mail(rfq_supplier, '', self.get_link(), True) | ||||||
|  | 
 | ||||||
|  | 		return message | ||||||
|  | 
 | ||||||
| 	def send_to_supplier(self): | 	def send_to_supplier(self): | ||||||
|  | 		"""Sends RFQ mail to involved suppliers.""" | ||||||
| 		for rfq_supplier in self.suppliers: | 		for rfq_supplier in self.suppliers: | ||||||
| 			if rfq_supplier.send_email: | 			if rfq_supplier.send_email: | ||||||
| 				self.validate_email_id(rfq_supplier) | 				self.validate_email_id(rfq_supplier) | ||||||
| 
 | 
 | ||||||
| 				# make new user if required | 				# make new user if required | ||||||
| 				update_password_link = self.update_supplier_contact(rfq_supplier, self.get_link()) | 				update_password_link, contact = self.update_supplier_contact(rfq_supplier, self.get_link()) | ||||||
| 
 | 
 | ||||||
| 				self.update_supplier_part_no(rfq_supplier) | 				self.update_supplier_part_no(rfq_supplier) | ||||||
| 				self.supplier_rfq_mail(rfq_supplier, update_password_link, self.get_link()) | 				self.supplier_rfq_mail(rfq_supplier, update_password_link, self.get_link()) | ||||||
| 				rfq_supplier.email_sent = 1 | 				rfq_supplier.email_sent = 1 | ||||||
|  | 				if not rfq_supplier.contact: | ||||||
|  | 					rfq_supplier.contact = contact | ||||||
| 				rfq_supplier.save() | 				rfq_supplier.save() | ||||||
| 
 | 
 | ||||||
| 	def get_link(self): | 	def get_link(self): | ||||||
| @ -87,18 +101,19 @@ class RequestforQuotation(BuyingController): | |||||||
| 
 | 
 | ||||||
| 	def update_supplier_contact(self, rfq_supplier, link): | 	def update_supplier_contact(self, rfq_supplier, link): | ||||||
| 		'''Create a new user for the supplier if not set in contact''' | 		'''Create a new user for the supplier if not set in contact''' | ||||||
| 		update_password_link = '' | 		update_password_link, contact = '', '' | ||||||
| 
 | 
 | ||||||
| 		if frappe.db.exists("User", rfq_supplier.email_id): | 		if frappe.db.exists("User", rfq_supplier.email_id): | ||||||
| 			user = frappe.get_doc("User", rfq_supplier.email_id) | 			user = frappe.get_doc("User", rfq_supplier.email_id) | ||||||
| 		else: | 		else: | ||||||
| 			user, update_password_link = self.create_user(rfq_supplier, link) | 			user, update_password_link = self.create_user(rfq_supplier, link) | ||||||
| 
 | 
 | ||||||
| 		self.update_contact_of_supplier(rfq_supplier, user) | 		contact = self.link_supplier_contact(rfq_supplier, user) | ||||||
| 
 | 
 | ||||||
| 		return update_password_link | 		return update_password_link, contact | ||||||
| 
 | 
 | ||||||
| 	def update_contact_of_supplier(self, rfq_supplier, user): | 	def link_supplier_contact(self, rfq_supplier, user): | ||||||
|  | 		"""If no Contact, create a new contact against Supplier. If Contact exists, check if email and user id set.""" | ||||||
| 		if rfq_supplier.contact: | 		if rfq_supplier.contact: | ||||||
| 			contact = frappe.get_doc("Contact", rfq_supplier.contact) | 			contact = frappe.get_doc("Contact", rfq_supplier.contact) | ||||||
| 		else: | 		else: | ||||||
| @ -115,6 +130,10 @@ class RequestforQuotation(BuyingController): | |||||||
| 
 | 
 | ||||||
| 		contact.save(ignore_permissions=True) | 		contact.save(ignore_permissions=True) | ||||||
| 
 | 
 | ||||||
|  | 		if not rfq_supplier.contact: | ||||||
|  | 			# return contact to later update, RFQ supplier row's contact | ||||||
|  | 			return contact.name | ||||||
|  | 
 | ||||||
| 	def create_user(self, rfq_supplier, link): | 	def create_user(self, rfq_supplier, link): | ||||||
| 		user = frappe.get_doc({ | 		user = frappe.get_doc({ | ||||||
| 			'doctype': 'User', | 			'doctype': 'User', | ||||||
| @ -129,22 +148,36 @@ class RequestforQuotation(BuyingController): | |||||||
| 
 | 
 | ||||||
| 		return user, update_password_link | 		return user, update_password_link | ||||||
| 
 | 
 | ||||||
| 	def supplier_rfq_mail(self, data, update_password_link, rfq_link): | 	def supplier_rfq_mail(self, data, update_password_link, rfq_link, preview=False): | ||||||
| 		full_name = get_user_fullname(frappe.session['user']) | 		full_name = get_user_fullname(frappe.session['user']) | ||||||
| 		if full_name == "Guest": | 		if full_name == "Guest": | ||||||
| 			full_name = "Administrator" | 			full_name = "Administrator" | ||||||
| 
 | 
 | ||||||
|  | 		# send document dict and some important data from suppliers row | ||||||
|  | 		# to render message_for_supplier from any template | ||||||
|  | 		doc_args = self.as_dict() | ||||||
|  | 		doc_args.update({ | ||||||
|  | 			'supplier': data.get('supplier'), | ||||||
|  | 			'supplier_name': data.get('supplier_name') | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
| 		args = { | 		args = { | ||||||
| 			'update_password_link': update_password_link, | 			'update_password_link': update_password_link, | ||||||
| 			'message': frappe.render_template(self.message_for_supplier, data.as_dict()), | 			'message': frappe.render_template(self.message_for_supplier, doc_args), | ||||||
| 			'rfq_link': rfq_link, | 			'rfq_link': rfq_link, | ||||||
| 			'user_fullname': full_name | 			'user_fullname': full_name, | ||||||
|  | 			'supplier_name' : data.get('supplier_name'), | ||||||
|  | 			'supplier_salutation' : self.salutation or 'Dear Mx.', | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		subject = _("Request for Quotation") | 		subject = self.subject or _("Request for Quotation") | ||||||
| 		template = "templates/emails/request_for_quotation.html" | 		template = "templates/emails/request_for_quotation.html" | ||||||
| 		sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None | 		sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None | ||||||
| 		message = frappe.get_template(template).render(args) | 		message = frappe.get_template(template).render(args) | ||||||
|  | 
 | ||||||
|  | 		if preview: | ||||||
|  | 			return message | ||||||
|  | 
 | ||||||
| 		attachments = self.get_attachments() | 		attachments = self.get_attachments() | ||||||
| 
 | 
 | ||||||
| 		self.send_email(data, sender, subject, message, attachments) | 		self.send_email(data, sender, subject, message, attachments) | ||||||
| @ -214,14 +247,14 @@ def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters): | |||||||
| 		and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent | 		and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent | ||||||
| 		limit %(start)s, %(page_len)s""", {"start": start, "page_len":page_len, "txt": "%%%s%%" % txt, "name": filters.get('supplier')}) | 		limit %(start)s, %(page_len)s""", {"start": start, "page_len":page_len, "txt": "%%%s%%" % txt, "name": filters.get('supplier')}) | ||||||
| 
 | 
 | ||||||
| # This method is used to make supplier quotation from material request form. |  | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
| def make_supplier_quotation(source_name, for_supplier, target_doc=None): | def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None): | ||||||
| 	def postprocess(source, target_doc): | 	def postprocess(source, target_doc): | ||||||
| 		target_doc.supplier = for_supplier | 		if for_supplier: | ||||||
| 		args = get_party_details(for_supplier, party_type="Supplier", ignore_permissions=True) | 			target_doc.supplier = for_supplier | ||||||
| 		target_doc.currency = args.currency or get_party_account_currency('Supplier', for_supplier, source.company) | 			args = get_party_details(for_supplier, party_type="Supplier", ignore_permissions=True) | ||||||
| 		target_doc.buying_price_list = args.buying_price_list or frappe.db.get_value('Buying Settings', None, 'buying_price_list') | 			target_doc.currency = args.currency or get_party_account_currency('Supplier', for_supplier, source.company) | ||||||
|  | 			target_doc.buying_price_list = args.buying_price_list or frappe.db.get_value('Buying Settings', None, 'buying_price_list') | ||||||
| 		set_missing_values(source, target_doc) | 		set_missing_values(source, target_doc) | ||||||
| 
 | 
 | ||||||
| 	doclist = get_mapped_doc("Request for Quotation", source_name, { | 	doclist = get_mapped_doc("Request for Quotation", source_name, { | ||||||
| @ -354,3 +387,32 @@ def get_supplier_tag(): | |||||||
| 		frappe.cache().hset("Supplier", "Tags", tags) | 		frappe.cache().hset("Supplier", "Tags", tags) | ||||||
| 
 | 
 | ||||||
| 	return frappe.cache().hget("Supplier", "Tags") | 	return frappe.cache().hget("Supplier", "Tags") | ||||||
|  | 
 | ||||||
|  | @frappe.whitelist() | ||||||
|  | @frappe.validate_and_sanitize_search_inputs | ||||||
|  | def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filters): | ||||||
|  | 	conditions = "" | ||||||
|  | 	if txt: | ||||||
|  | 		conditions += "and rfq.name like '%%"+txt+"%%' " | ||||||
|  | 
 | ||||||
|  | 	if filters.get("transaction_date"): | ||||||
|  | 		conditions += "and rfq.transaction_date = '{0}'".format(filters.get("transaction_date")) | ||||||
|  | 
 | ||||||
|  | 	rfq_data = frappe.db.sql(""" | ||||||
|  | 		select | ||||||
|  | 			distinct rfq.name, rfq.transaction_date, | ||||||
|  | 			rfq.company | ||||||
|  | 		from | ||||||
|  | 			`tabRequest for Quotation` rfq, `tabRequest for Quotation Supplier` rfq_supplier | ||||||
|  | 		where | ||||||
|  | 			rfq.name = rfq_supplier.parent | ||||||
|  | 			and rfq_supplier.supplier = '{0}' | ||||||
|  | 			and rfq.docstatus = 1 | ||||||
|  | 			and rfq.company = '{1}' | ||||||
|  | 			{2} | ||||||
|  | 		order by rfq.transaction_date ASC | ||||||
|  | 		limit %(page_len)s offset %(start)s """ \ | ||||||
|  | 		.format(filters.get("supplier"), filters.get("company"), conditions), | ||||||
|  | 			{"page_len": page_len, "start": start}, as_dict=1) | ||||||
|  | 
 | ||||||
|  | 	return rfq_data | ||||||
| @ -9,7 +9,7 @@ import frappe | |||||||
| from frappe.utils import nowdate | from frappe.utils import nowdate | ||||||
| from erpnext.stock.doctype.item.test_item import make_item | from erpnext.stock.doctype.item.test_item import make_item | ||||||
| from erpnext.templates.pages.rfq import check_supplier_has_docname_access | from erpnext.templates.pages.rfq import check_supplier_has_docname_access | ||||||
| from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation | from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation_from_rfq | ||||||
| from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation | from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation | ||||||
| from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity | from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity | ||||||
| from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq | from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq | ||||||
| @ -22,7 +22,7 @@ class TestRequestforQuotation(unittest.TestCase): | |||||||
| 		self.assertEqual(rfq.get('suppliers')[1].quote_status, 'Pending') | 		self.assertEqual(rfq.get('suppliers')[1].quote_status, 'Pending') | ||||||
| 
 | 
 | ||||||
| 		# Submit the first supplier quotation | 		# Submit the first supplier quotation | ||||||
| 		sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier) | 		sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier) | ||||||
| 		sq.submit() | 		sq.submit() | ||||||
| 
 | 
 | ||||||
| 		# No Quote first supplier quotation | 		# No Quote first supplier quotation | ||||||
| @ -37,10 +37,10 @@ class TestRequestforQuotation(unittest.TestCase): | |||||||
| 	def test_make_supplier_quotation(self): | 	def test_make_supplier_quotation(self): | ||||||
| 		rfq = make_request_for_quotation() | 		rfq = make_request_for_quotation() | ||||||
| 
 | 
 | ||||||
| 		sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier) | 		sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier) | ||||||
| 		sq.submit() | 		sq.submit() | ||||||
| 
 | 
 | ||||||
| 		sq1 = make_supplier_quotation(rfq.name, rfq.get('suppliers')[1].supplier) | 		sq1 = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[1].supplier) | ||||||
| 		sq1.submit() | 		sq1.submit() | ||||||
| 
 | 
 | ||||||
| 		self.assertEqual(sq.supplier, rfq.get('suppliers')[0].supplier) | 		self.assertEqual(sq.supplier, rfq.get('suppliers')[0].supplier) | ||||||
| @ -62,7 +62,7 @@ class TestRequestforQuotation(unittest.TestCase): | |||||||
| 
 | 
 | ||||||
| 		rfq = make_request_for_quotation(supplier_data=supplier_wt_appos) | 		rfq = make_request_for_quotation(supplier_data=supplier_wt_appos) | ||||||
| 
 | 
 | ||||||
| 		sq = make_supplier_quotation(rfq.name, supplier_wt_appos[0].get("supplier")) | 		sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier_wt_appos[0].get("supplier")) | ||||||
| 		sq.submit() | 		sq.submit() | ||||||
| 
 | 
 | ||||||
| 		frappe.form_dict = frappe.local("form_dict") | 		frappe.form_dict = frappe.local("form_dict") | ||||||
|  | |||||||
| @ -1,362 +1,112 @@ | |||||||
| { | { | ||||||
|  "allow_copy": 0,  |  "actions": [], | ||||||
|  "allow_guest_to_view": 0,  |  "creation": "2016-03-29 05:59:11.896885", | ||||||
|  "allow_import": 0,  |  "doctype": "DocType", | ||||||
|  "allow_rename": 0,  |  "editable_grid": 1, | ||||||
|  "beta": 0,  |  "engine": "InnoDB", | ||||||
|  "creation": "2016-03-29 05:59:11.896885",  |  "field_order": [ | ||||||
|  "custom": 0,  |   "send_email", | ||||||
|  "docstatus": 0,  |   "email_sent", | ||||||
|  "doctype": "DocType",  |   "supplier", | ||||||
|  "document_type": "",  |   "contact", | ||||||
|  "editable_grid": 1,  |   "no_quote", | ||||||
|  "engine": "InnoDB",  |   "quote_status", | ||||||
|  |   "column_break_3", | ||||||
|  |   "supplier_name", | ||||||
|  |   "email_id", | ||||||
|  |   "download_pdf" | ||||||
|  |  ], | ||||||
|  "fields": [ |  "fields": [ | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "allow_on_submit": 1, | ||||||
|    "allow_on_submit": 1,  |    "default": "1", | ||||||
|    "bold": 0,  |    "fieldname": "send_email", | ||||||
|    "collapsible": 0,  |    "fieldtype": "Check", | ||||||
|    "columns": 0,  |    "label": "Send Email" | ||||||
|    "default": "1",  |   }, | ||||||
|    "fieldname": "send_email",  |  | ||||||
|    "fieldtype": "Check",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Send Email",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0, |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "allow_on_submit": 1, | ||||||
|    "allow_on_submit": 1,  |    "default": "0", | ||||||
|    "bold": 0,  |    "depends_on": "eval:doc.docstatus >= 1", | ||||||
|    "collapsible": 0,  |    "fieldname": "email_sent", | ||||||
|    "columns": 0,  |    "fieldtype": "Check", | ||||||
|    "default": "0",  |    "label": "Email Sent", | ||||||
|    "depends_on": "eval:doc.docstatus >= 1",  |    "no_copy": 1, | ||||||
|    "fieldname": "email_sent",  |    "read_only": 1 | ||||||
|    "fieldtype": "Check",  |   }, | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Email Sent",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 1,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0, |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "columns": 4, | ||||||
|    "allow_on_submit": 0,  |    "fieldname": "supplier", | ||||||
|    "bold": 0,  |    "fieldtype": "Link", | ||||||
|    "collapsible": 0,  |    "in_list_view": 1, | ||||||
|    "columns": 4,  |    "label": "Supplier", | ||||||
|    "fieldname": "supplier",  |    "options": "Supplier", | ||||||
|    "fieldtype": "Link",  |    "reqd": 1 | ||||||
|    "hidden": 0,  |   }, | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 1,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Supplier",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "Supplier",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 1,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0, |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "allow_on_submit": 1, | ||||||
|    "allow_on_submit": 0,  |    "columns": 3, | ||||||
|    "bold": 0,  |    "fieldname": "contact", | ||||||
|    "collapsible": 0,  |    "fieldtype": "Link", | ||||||
|    "columns": 3,  |    "in_list_view": 1, | ||||||
|    "fieldname": "contact",  |    "label": "Contact", | ||||||
|    "fieldtype": "Link",  |    "no_copy": 1, | ||||||
|    "hidden": 0,  |    "options": "Contact" | ||||||
|    "ignore_user_permissions": 0,  |   }, | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 1,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Contact",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 1,  |  | ||||||
|    "options": "Contact",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0, |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "allow_on_submit": 1, | ||||||
|    "allow_on_submit": 1,  |    "default": "0", | ||||||
|    "bold": 0,  |    "depends_on": "eval:doc.docstatus >= 1 && doc.quote_status != 'Received'", | ||||||
|    "collapsible": 0,  |    "fieldname": "no_quote", | ||||||
|    "columns": 0,  |    "fieldtype": "Check", | ||||||
|    "depends_on": "eval:doc.docstatus >= 1 && doc.quote_status != 'Received'",  |    "label": "No Quote" | ||||||
|    "fieldname": "no_quote",  |   }, | ||||||
|    "fieldtype": "Check",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "No Quote",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0, |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "allow_on_submit": 1, | ||||||
|    "allow_on_submit": 1,  |    "depends_on": "eval:doc.docstatus >= 1 && !doc.no_quote", | ||||||
|    "bold": 0,  |    "fieldname": "quote_status", | ||||||
|    "collapsible": 0,  |    "fieldtype": "Select", | ||||||
|    "columns": 0,  |    "label": "Quote Status", | ||||||
|    "depends_on": "eval:doc.docstatus >= 1 && !doc.no_quote",  |    "options": "Pending\nReceived\nNo Quote", | ||||||
|    "fieldname": "quote_status",  |    "read_only": 1 | ||||||
|    "fieldtype": "Select",  |   }, | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Quote Status",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "Pending\nReceived\nNo Quote",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0, |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "column_break_3", | ||||||
|    "allow_on_submit": 0,  |    "fieldtype": "Column Break" | ||||||
|    "bold": 0,  |   }, | ||||||
|    "collapsible": 0,  |  | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "column_break_3",  |  | ||||||
|    "fieldtype": "Column Break",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0, |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "bold": 1, | ||||||
|    "allow_on_submit": 0,  |  | ||||||
|    "bold": 1,  |  | ||||||
|    "collapsible": 0,  |  | ||||||
|    "columns": 0,  |  | ||||||
|    "fetch_from": "supplier.supplier_name", |    "fetch_from": "supplier.supplier_name", | ||||||
|    "fieldname": "supplier_name",  |    "fieldname": "supplier_name", | ||||||
|    "fieldtype": "Read Only",  |    "fieldtype": "Read Only", | ||||||
|    "hidden": 0,  |    "in_global_search": 1, | ||||||
|    "ignore_user_permissions": 0,  |    "label": "Supplier Name" | ||||||
|    "ignore_xss_filter": 0,  |   }, | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 1,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Supplier Name",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0, |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "columns": 3, | ||||||
|    "allow_on_submit": 0,  |  | ||||||
|    "bold": 0,  |  | ||||||
|    "collapsible": 0,  |  | ||||||
|    "columns": 3,  |  | ||||||
|    "fetch_from": "contact.email_id", |    "fetch_from": "contact.email_id", | ||||||
|    "fieldname": "email_id",  |    "fieldname": "email_id", | ||||||
|    "fieldtype": "Data",  |    "fieldtype": "Data", | ||||||
|    "hidden": 0,  |    "in_list_view": 1, | ||||||
|    "ignore_user_permissions": 0,  |    "label": "Email Id", | ||||||
|    "ignore_xss_filter": 0,  |    "no_copy": 1 | ||||||
|    "in_filter": 0,  |   }, | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 1,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Email Id",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 1,  |  | ||||||
|    "options": "",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0, |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "allow_on_submit": 1, | ||||||
|    "allow_on_submit": 1,  |    "fieldname": "download_pdf", | ||||||
|    "bold": 0,  |    "fieldtype": "Button", | ||||||
|    "collapsible": 0,  |    "label": "Download PDF" | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "download_pdf",  |  | ||||||
|    "fieldtype": "Button",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Download PDF",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0, |  | ||||||
|    "unique": 0 |  | ||||||
|   } |   } | ||||||
|  ],  |  ], | ||||||
|  "has_web_view": 0,  |  "istable": 1, | ||||||
|  "hide_heading": 0,  |  "links": [], | ||||||
|  "hide_toolbar": 0,  |  "modified": "2020-09-28 19:31:11.855588", | ||||||
|  "idx": 0,  |  "modified_by": "Administrator", | ||||||
|  "image_view": 0,  |  "module": "Buying", | ||||||
|  "in_create": 0,  |  "name": "Request for Quotation Supplier", | ||||||
|  "is_submittable": 0,  |  "owner": "Administrator", | ||||||
|  "issingle": 0,  |  "permissions": [], | ||||||
|  "istable": 1,  |  "sort_field": "modified", | ||||||
|  "max_attachments": 0,  |  "sort_order": "DESC", | ||||||
|  "modified": "2018-05-16 22:43:30.212408", |  "track_changes": 1 | ||||||
|  "modified_by": "Administrator",  |  | ||||||
|  "module": "Buying",  |  | ||||||
|  "name": "Request for Quotation Supplier",  |  | ||||||
|  "name_case": "",  |  | ||||||
|  "owner": "Administrator",  |  | ||||||
|  "permissions": [],  |  | ||||||
|  "quick_entry": 0,  |  | ||||||
|  "read_only": 0,  |  | ||||||
|  "read_only_onload": 0,  |  | ||||||
|  "show_name_in_global_search": 0,  |  | ||||||
|  "sort_field": "modified",  |  | ||||||
|  "sort_order": "DESC",  |  | ||||||
|  "track_changes": 1,  |  | ||||||
|  "track_seen": 0 |  | ||||||
| } | } | ||||||
| @ -8,8 +8,7 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext | |||||||
| 	setup: function() { | 	setup: function() { | ||||||
| 		this.frm.custom_make_buttons = { | 		this.frm.custom_make_buttons = { | ||||||
| 			'Purchase Order': 'Purchase Order', | 			'Purchase Order': 'Purchase Order', | ||||||
| 			'Quotation': 'Quotation', | 			'Quotation': 'Quotation' | ||||||
| 			'Subscription': 'Subscription' |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		this._super(); | 		this._super(); | ||||||
| @ -28,12 +27,6 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext | |||||||
| 			cur_frm.page.set_inner_btn_group_as_primary(__('Create')); | 			cur_frm.page.set_inner_btn_group_as_primary(__('Create')); | ||||||
| 			cur_frm.add_custom_button(__("Quotation"), this.make_quotation, | 			cur_frm.add_custom_button(__("Quotation"), this.make_quotation, | ||||||
| 				__('Create')); | 				__('Create')); | ||||||
| 
 |  | ||||||
| 			if(!this.frm.doc.auto_repeat) { |  | ||||||
| 				cur_frm.add_custom_button(__('Subscription'), function() { |  | ||||||
| 					erpnext.utils.make_subscription(me.frm.doc.doctype, me.frm.doc.name) |  | ||||||
| 				}, __('Create')) |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 		else if (this.frm.doc.docstatus===0) { | 		else if (this.frm.doc.docstatus===0) { | ||||||
| 
 | 
 | ||||||
| @ -54,6 +47,27 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext | |||||||
| 						} | 						} | ||||||
| 					}) | 					}) | ||||||
| 				}, __("Get items from")); | 				}, __("Get items from")); | ||||||
|  | 
 | ||||||
|  | 			this.frm.add_custom_button(__("Request for Quotation"), | ||||||
|  | 			function() { | ||||||
|  | 				if (!me.frm.doc.supplier) { | ||||||
|  | 					frappe.throw({message:__("Please select a Supplier"), title:__("Mandatory")}) | ||||||
|  | 				} | ||||||
|  | 				erpnext.utils.map_current_doc({ | ||||||
|  | 					method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq", | ||||||
|  | 					source_doctype: "Request for Quotation", | ||||||
|  | 					target: me.frm, | ||||||
|  | 					setters: { | ||||||
|  | 						company: me.frm.doc.company, | ||||||
|  | 						transaction_date: null | ||||||
|  | 					}, | ||||||
|  | 					get_query_filters: { | ||||||
|  | 						supplier: me.frm.doc.supplier | ||||||
|  | 					}, | ||||||
|  | 					get_query_method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_rfq_containing_supplier" | ||||||
|  | 
 | ||||||
|  | 				}) | ||||||
|  | 			}, __("Get items from")); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -159,6 +159,7 @@ | |||||||
|    "default": "Today", |    "default": "Today", | ||||||
|    "fieldname": "transaction_date", |    "fieldname": "transaction_date", | ||||||
|    "fieldtype": "Date", |    "fieldtype": "Date", | ||||||
|  |    "in_list_view": 1, | ||||||
|    "label": "Date", |    "label": "Date", | ||||||
|    "oldfieldname": "transaction_date", |    "oldfieldname": "transaction_date", | ||||||
|    "oldfieldtype": "Date", |    "oldfieldtype": "Date", | ||||||
| @ -798,6 +799,7 @@ | |||||||
|   { |   { | ||||||
|    "fieldname": "valid_till", |    "fieldname": "valid_till", | ||||||
|    "fieldtype": "Date", |    "fieldtype": "Date", | ||||||
|  |    "in_list_view": 1, | ||||||
|    "label": "Valid Till" |    "label": "Valid Till" | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
| @ -805,7 +807,7 @@ | |||||||
|  "idx": 29, |  "idx": 29, | ||||||
|  "is_submittable": 1, |  "is_submittable": 1, | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-07-18 05:10:45.556792", |  "modified": "2020-10-01 20:56:17.932007", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Buying", |  "module": "Buying", | ||||||
|  "name": "Supplier Quotation", |  "name": "Supplier Quotation", | ||||||
|  | |||||||
| @ -12,6 +12,8 @@ | |||||||
|   "item_name", |   "item_name", | ||||||
|   "column_break_3", |   "column_break_3", | ||||||
|   "lead_time_days", |   "lead_time_days", | ||||||
|  |   "expected_delivery_date", | ||||||
|  |   "is_free_item", | ||||||
|   "section_break_5", |   "section_break_5", | ||||||
|   "description", |   "description", | ||||||
|   "item_group", |   "item_group", | ||||||
| @ -19,20 +21,18 @@ | |||||||
|   "col_break1", |   "col_break1", | ||||||
|   "image", |   "image", | ||||||
|   "image_view", |   "image_view", | ||||||
|   "manufacture_details", |  | ||||||
|   "manufacturer", |  | ||||||
|   "column_break_15", |  | ||||||
|   "manufacturer_part_no", |  | ||||||
|   "quantity_and_rate", |   "quantity_and_rate", | ||||||
|   "qty", |   "qty", | ||||||
|   "stock_uom", |   "stock_uom", | ||||||
|   "price_list_rate", |  | ||||||
|   "discount_percentage", |  | ||||||
|   "discount_amount", |  | ||||||
|   "col_break2", |   "col_break2", | ||||||
|   "uom", |   "uom", | ||||||
|   "conversion_factor", |   "conversion_factor", | ||||||
|   "stock_qty", |   "stock_qty", | ||||||
|  |   "sec_break_price_list", | ||||||
|  |   "price_list_rate", | ||||||
|  |   "discount_percentage", | ||||||
|  |   "discount_amount", | ||||||
|  |   "col_break_price_list", | ||||||
|   "base_price_list_rate", |   "base_price_list_rate", | ||||||
|   "sec_break1", |   "sec_break1", | ||||||
|   "rate", |   "rate", | ||||||
| @ -42,7 +42,6 @@ | |||||||
|   "base_rate", |   "base_rate", | ||||||
|   "base_amount", |   "base_amount", | ||||||
|   "pricing_rules", |   "pricing_rules", | ||||||
|   "is_free_item", |  | ||||||
|   "section_break_24", |   "section_break_24", | ||||||
|   "net_rate", |   "net_rate", | ||||||
|   "net_amount", |   "net_amount", | ||||||
| @ -56,7 +55,6 @@ | |||||||
|   "weight_uom", |   "weight_uom", | ||||||
|   "warehouse_and_reference", |   "warehouse_and_reference", | ||||||
|   "warehouse", |   "warehouse", | ||||||
|   "project", |  | ||||||
|   "prevdoc_doctype", |   "prevdoc_doctype", | ||||||
|   "material_request", |   "material_request", | ||||||
|   "sales_order", |   "sales_order", | ||||||
| @ -65,13 +63,19 @@ | |||||||
|   "material_request_item", |   "material_request_item", | ||||||
|   "request_for_quotation_item", |   "request_for_quotation_item", | ||||||
|   "item_tax_rate", |   "item_tax_rate", | ||||||
|  |   "manufacture_details", | ||||||
|  |   "manufacturer", | ||||||
|  |   "column_break_15", | ||||||
|  |   "manufacturer_part_no", | ||||||
|  |   "ad_sec_break", | ||||||
|  |   "project", | ||||||
|   "section_break_44", |   "section_break_44", | ||||||
|   "page_break" |   "page_break" | ||||||
|  ], |  ], | ||||||
|  "fields": [ |  "fields": [ | ||||||
|   { |   { | ||||||
|    "bold": 1, |    "bold": 1, | ||||||
|    "columns": 4, |    "columns": 2, | ||||||
|    "fieldname": "item_code", |    "fieldname": "item_code", | ||||||
|    "fieldtype": "Link", |    "fieldtype": "Link", | ||||||
|    "in_list_view": 1, |    "in_list_view": 1, | ||||||
| @ -107,7 +111,7 @@ | |||||||
|   { |   { | ||||||
|    "fieldname": "lead_time_days", |    "fieldname": "lead_time_days", | ||||||
|    "fieldtype": "Int", |    "fieldtype": "Int", | ||||||
|    "label": "Lead Time in days" |    "label": "Supplier Lead Time (days)" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "collapsible": 1, |    "collapsible": 1, | ||||||
| @ -162,7 +166,6 @@ | |||||||
|   { |   { | ||||||
|    "fieldname": "stock_uom", |    "fieldname": "stock_uom", | ||||||
|    "fieldtype": "Link", |    "fieldtype": "Link", | ||||||
|    "in_list_view": 1, |  | ||||||
|    "label": "Stock UOM", |    "label": "Stock UOM", | ||||||
|    "options": "UOM", |    "options": "UOM", | ||||||
|    "print_hide": 1, |    "print_hide": 1, | ||||||
| @ -196,6 +199,7 @@ | |||||||
|   { |   { | ||||||
|    "fieldname": "uom", |    "fieldname": "uom", | ||||||
|    "fieldtype": "Link", |    "fieldtype": "Link", | ||||||
|  |    "in_list_view": 1, | ||||||
|    "label": "UOM", |    "label": "UOM", | ||||||
|    "options": "UOM", |    "options": "UOM", | ||||||
|    "print_hide": 1, |    "print_hide": 1, | ||||||
| @ -289,14 +293,6 @@ | |||||||
|    "print_hide": 1, |    "print_hide": 1, | ||||||
|    "read_only": 1 |    "read_only": 1 | ||||||
|   }, |   }, | ||||||
|   { |  | ||||||
|    "default": "0", |  | ||||||
|    "fieldname": "is_free_item", |  | ||||||
|    "fieldtype": "Check", |  | ||||||
|    "label": "Is Free Item", |  | ||||||
|    "print_hide": 1, |  | ||||||
|    "read_only": 1 |  | ||||||
|   }, |  | ||||||
|   { |   { | ||||||
|    "fieldname": "section_break_24", |    "fieldname": "section_break_24", | ||||||
|    "fieldtype": "Section Break" |    "fieldtype": "Section Break" | ||||||
| @ -528,12 +524,43 @@ | |||||||
|   { |   { | ||||||
|    "fieldname": "column_break_15", |    "fieldname": "column_break_15", | ||||||
|    "fieldtype": "Column Break" |    "fieldtype": "Column Break" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "fieldname": "sec_break_price_list", | ||||||
|  |    "fieldtype": "Section Break" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "fieldname": "col_break_price_list", | ||||||
|  |    "fieldtype": "Column Break" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "collapsible": 1, | ||||||
|  |    "fieldname": "ad_sec_break", | ||||||
|  |    "fieldtype": "Section Break", | ||||||
|  |    "label": "Accounting Dimensions" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "default": "0", | ||||||
|  |    "depends_on": "is_free_item", | ||||||
|  |    "fieldname": "is_free_item", | ||||||
|  |    "fieldtype": "Check", | ||||||
|  |    "label": "Is Free Item", | ||||||
|  |    "print_hide": 1, | ||||||
|  |    "read_only": 1 | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "allow_on_submit": 1, | ||||||
|  |    "bold": 1, | ||||||
|  |    "fieldname": "expected_delivery_date", | ||||||
|  |    "fieldtype": "Date", | ||||||
|  |    "label": "Expected Delivery Date" | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "idx": 1, |  "idx": 1, | ||||||
|  |  "index_web_pages_for_search": 1, | ||||||
|  "istable": 1, |  "istable": 1, | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-04-07 18:35:51.175947", |  "modified": "2020-10-01 16:34:39.703033", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Buying", |  "module": "Buying", | ||||||
|  "name": "Supplier Quotation Item", |  "name": "Supplier Quotation Item", | ||||||
|  | |||||||
| @ -143,7 +143,7 @@ def get_conditions(filters): | |||||||
| 	conditions = "" | 	conditions = "" | ||||||
| 
 | 
 | ||||||
| 	if filters.get("company"): | 	if filters.get("company"): | ||||||
| 		conditions += " AND par.company=%s" % frappe.db.escape(filters.get('company')) | 		conditions += " AND parent.company=%s" % frappe.db.escape(filters.get('company')) | ||||||
| 
 | 
 | ||||||
| 	if filters.get("cost_center") or filters.get("project"): | 	if filters.get("cost_center") or filters.get("project"): | ||||||
| 		conditions += """ | 		conditions += """ | ||||||
| @ -151,10 +151,10 @@ def get_conditions(filters): | |||||||
| 			""" % (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project'))) | 			""" % (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project'))) | ||||||
| 
 | 
 | ||||||
| 	if filters.get("from_date"): | 	if filters.get("from_date"): | ||||||
| 		conditions += " AND par.transaction_date>='%s'" % filters.get('from_date') | 		conditions += " AND parent.transaction_date>='%s'" % filters.get('from_date') | ||||||
| 
 | 
 | ||||||
| 	if filters.get("to_date"): | 	if filters.get("to_date"): | ||||||
| 		conditions += " AND par.transaction_date<='%s'" % filters.get('to_date') | 		conditions += " AND parent.transaction_date<='%s'" % filters.get('to_date') | ||||||
| 	return conditions | 	return conditions | ||||||
| 
 | 
 | ||||||
| def get_data(filters): | def get_data(filters): | ||||||
| @ -198,21 +198,23 @@ def get_mapped_mr_details(conditions): | |||||||
| 	mr_records = {} | 	mr_records = {} | ||||||
| 	mr_details = frappe.db.sql(""" | 	mr_details = frappe.db.sql(""" | ||||||
| 		SELECT | 		SELECT | ||||||
| 			par.transaction_date, | 			parent.transaction_date, | ||||||
| 			par.per_ordered, | 			parent.per_ordered, | ||||||
| 			par.owner, | 			parent.owner, | ||||||
| 			child.name, | 			child.name, | ||||||
| 			child.parent, | 			child.parent, | ||||||
| 			child.amount, | 			child.amount, | ||||||
| 			child.qty, | 			child.qty, | ||||||
| 			child.item_code, | 			child.item_code, | ||||||
| 			child.uom, | 			child.uom, | ||||||
| 			par.status | 			parent.status, | ||||||
| 		FROM `tabMaterial Request` par, `tabMaterial Request Item` child | 			child.project, | ||||||
|  | 			child.cost_center | ||||||
|  | 		FROM `tabMaterial Request` parent, `tabMaterial Request Item` child | ||||||
| 		WHERE | 		WHERE | ||||||
| 			par.per_ordered>=0 | 			parent.per_ordered>=0 | ||||||
| 			AND par.name=child.parent | 			AND parent.name=child.parent | ||||||
| 			AND par.docstatus=1 | 			AND parent.docstatus=1 | ||||||
| 			{conditions} | 			{conditions} | ||||||
| 		""".format(conditions=conditions), as_dict=1) #nosec | 		""".format(conditions=conditions), as_dict=1) #nosec | ||||||
| 
 | 
 | ||||||
| @ -232,7 +234,9 @@ def get_mapped_mr_details(conditions): | |||||||
| 				status=record.status, | 				status=record.status, | ||||||
| 				actual_cost=0, | 				actual_cost=0, | ||||||
| 				purchase_order_amt=0, | 				purchase_order_amt=0, | ||||||
| 				purchase_order_amt_in_company_currency=0 | 				purchase_order_amt_in_company_currency=0, | ||||||
|  | 				project = record.project, | ||||||
|  | 				cost_center = record.cost_center | ||||||
| 			) | 			) | ||||||
| 			procurement_record_against_mr.append(procurement_record_details) | 			procurement_record_against_mr.append(procurement_record_details) | ||||||
| 	return mr_records, procurement_record_against_mr | 	return mr_records, procurement_record_against_mr | ||||||
| @ -280,16 +284,16 @@ def get_po_entries(conditions): | |||||||
| 			child.amount, | 			child.amount, | ||||||
| 			child.base_amount, | 			child.base_amount, | ||||||
| 			child.schedule_date, | 			child.schedule_date, | ||||||
| 			par.transaction_date, | 			parent.transaction_date, | ||||||
| 			par.supplier, | 			parent.supplier, | ||||||
| 			par.status, | 			parent.status, | ||||||
| 			par.owner | 			parent.owner | ||||||
| 		FROM `tabPurchase Order` par, `tabPurchase Order Item` child | 		FROM `tabPurchase Order` parent, `tabPurchase Order Item` child | ||||||
| 		WHERE | 		WHERE | ||||||
| 			par.docstatus = 1 | 			parent.docstatus = 1 | ||||||
| 			AND par.name = child.parent | 			AND parent.name = child.parent | ||||||
| 			AND par.status not in  ("Closed","Completed","Cancelled") | 			AND parent.status not in  ("Closed","Completed","Cancelled") | ||||||
| 			{conditions} | 			{conditions} | ||||||
| 		GROUP BY | 		GROUP BY | ||||||
| 			par.name, child.item_code | 			parent.name, child.item_code | ||||||
| 		""".format(conditions=conditions), as_dict=1) #nosec | 		""".format(conditions=conditions), as_dict=1) #nosec | ||||||
| @ -1,32 +0,0 @@ | |||||||
| { |  | ||||||
|  "add_total_row": 0,  |  | ||||||
|  "apply_user_permissions": 1,  |  | ||||||
|  "creation": "2016-07-21 08:31:05.890362",  |  | ||||||
|  "disabled": 0,  |  | ||||||
|  "docstatus": 0,  |  | ||||||
|  "doctype": "Report",  |  | ||||||
|  "idx": 2,  |  | ||||||
|  "is_standard": "Yes",  |  | ||||||
|  "modified": "2017-02-24 20:04:58.784351",  |  | ||||||
|  "modified_by": "Administrator",  |  | ||||||
|  "module": "Buying",  |  | ||||||
|  "name": "Quoted Item Comparison",  |  | ||||||
|  "owner": "Administrator",  |  | ||||||
|  "ref_doctype": "Supplier Quotation",  |  | ||||||
|  "report_name": "Quoted Item Comparison",  |  | ||||||
|  "report_type": "Script Report",  |  | ||||||
|  "roles": [ |  | ||||||
|   { |  | ||||||
|    "role": "Manufacturing Manager" |  | ||||||
|   },  |  | ||||||
|   { |  | ||||||
|    "role": "Purchase Manager" |  | ||||||
|   },  |  | ||||||
|   { |  | ||||||
|    "role": "Purchase User" |  | ||||||
|   },  |  | ||||||
|   { |  | ||||||
|    "role": "Stock User" |  | ||||||
|   } |  | ||||||
|  ] |  | ||||||
| } |  | ||||||
| @ -1,7 +1,7 @@ | |||||||
| // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
 | // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
 | ||||||
| // For license information, please see license.txt
 | // For license information, please see license.txt
 | ||||||
| 
 | 
 | ||||||
| frappe.query_reports["Quoted Item Comparison"] = { | frappe.query_reports["Supplier Quotation Comparison"] = { | ||||||
| 	filters: [ | 	filters: [ | ||||||
| 		{ | 		{ | ||||||
| 			fieldtype: "Link", | 			fieldtype: "Link", | ||||||
| @ -78,6 +78,13 @@ frappe.query_reports["Quoted Item Comparison"] = { | |||||||
| 				return { filters: { "docstatus": ["<", 2] } } | 				return { filters: { "docstatus": ["<", 2] } } | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"fieldname":"group_by", | ||||||
|  | 			"label": __("Group by"), | ||||||
|  | 			"fieldtype": "Select", | ||||||
|  | 			"options": [__("Group by Supplier"), __("Group by Item")], | ||||||
|  | 			"default": __("Group by Supplier") | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			fieldtype: "Check", | 			fieldtype: "Check", | ||||||
| 			label: __("Include Expired"), | 			label: __("Include Expired"), | ||||||
| @ -98,6 +105,9 @@ frappe.query_reports["Quoted Item Comparison"] = { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if(column.fieldname === "price_per_unit" && data.price_per_unit && data.min && data.min === 1){ | ||||||
|  | 			value = `<div style="color:green">${value}</div>`; | ||||||
|  | 		} | ||||||
| 		return value; | 		return value; | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| @ -0,0 +1,32 @@ | |||||||
|  | { | ||||||
|  |  "add_total_row": 0, | ||||||
|  |  "apply_user_permissions": 1, | ||||||
|  |  "creation": "2016-07-21 08:31:05.890362", | ||||||
|  |  "disabled": 0, | ||||||
|  |  "docstatus": 0, | ||||||
|  |  "doctype": "Report", | ||||||
|  |  "idx": 2, | ||||||
|  |  "is_standard": "Yes", | ||||||
|  |  "modified": "2017-02-24 20:04:58.784351", | ||||||
|  |  "modified_by": "Administrator", | ||||||
|  |  "module": "Buying", | ||||||
|  |  "name": "Supplier Quotation Comparison", | ||||||
|  |  "owner": "Administrator", | ||||||
|  |  "ref_doctype": "Supplier Quotation", | ||||||
|  |  "report_name": "Supplier Quotation Comparison", | ||||||
|  |  "report_type": "Script Report", | ||||||
|  |  "roles": [ | ||||||
|  |   { | ||||||
|  |    "role": "Manufacturing Manager" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "role": "Purchase Manager" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "role": "Purchase User" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "role": "Stock User" | ||||||
|  |   } | ||||||
|  |  ] | ||||||
|  | } | ||||||
| @ -12,9 +12,9 @@ def execute(filters=None): | |||||||
| 	if not filters: | 	if not filters: | ||||||
| 		return [], [] | 		return [], [] | ||||||
| 
 | 
 | ||||||
|  | 	columns = get_columns(filters) | ||||||
| 	conditions = get_conditions(filters) | 	conditions = get_conditions(filters) | ||||||
| 	supplier_quotation_data = get_data(filters, conditions) | 	supplier_quotation_data = get_data(filters, conditions) | ||||||
| 	columns = get_columns() |  | ||||||
| 
 | 
 | ||||||
| 	data, chart_data = prepare_data(supplier_quotation_data, filters) | 	data, chart_data = prepare_data(supplier_quotation_data, filters) | ||||||
| 	message = get_message() | 	message = get_message() | ||||||
| @ -41,9 +41,13 @@ def get_conditions(filters): | |||||||
| 	return conditions | 	return conditions | ||||||
| 
 | 
 | ||||||
| def get_data(filters, conditions): | def get_data(filters, conditions): | ||||||
| 	supplier_quotation_data = frappe.db.sql("""SELECT | 	supplier_quotation_data = frappe.db.sql(""" | ||||||
| 		sqi.parent, sqi.item_code, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation, | 		SELECT | ||||||
| 		sqi.lead_time_days, sq.supplier, sq.valid_till | 			sqi.parent, sqi.item_code, | ||||||
|  | 			sqi.qty, sqi.stock_qty, sqi.amount, | ||||||
|  | 			sqi.uom, sqi.stock_uom, | ||||||
|  | 			sqi.request_for_quotation, | ||||||
|  | 			sqi.lead_time_days, sq.supplier as supplier_name, sq.valid_till | ||||||
| 		FROM | 		FROM | ||||||
| 			`tabSupplier Quotation Item` sqi, | 			`tabSupplier Quotation Item` sqi, | ||||||
| 			`tabSupplier Quotation` sq | 			`tabSupplier Quotation` sq | ||||||
| @ -58,16 +62,18 @@ def get_data(filters, conditions): | |||||||
| 	return supplier_quotation_data | 	return supplier_quotation_data | ||||||
| 
 | 
 | ||||||
| def prepare_data(supplier_quotation_data, filters): | def prepare_data(supplier_quotation_data, filters): | ||||||
| 	out, suppliers, qty_list, chart_data = [], [], [], [] | 	out, groups, qty_list, suppliers, chart_data = [], [], [], [], [] | ||||||
| 	supplier_wise_map = defaultdict(list) | 	group_wise_map = defaultdict(list) | ||||||
| 	supplier_qty_price_map = {} | 	supplier_qty_price_map = {} | ||||||
| 
 | 
 | ||||||
|  | 	group_by_field = "supplier_name" if filters.get("group_by") == "Group by Supplier" else "item_code" | ||||||
| 	company_currency = frappe.db.get_default("currency") | 	company_currency = frappe.db.get_default("currency") | ||||||
| 	float_precision = cint(frappe.db.get_default("float_precision")) or 2 | 	float_precision = cint(frappe.db.get_default("float_precision")) or 2 | ||||||
| 
 | 
 | ||||||
| 	for data in supplier_quotation_data: | 	for data in supplier_quotation_data: | ||||||
| 		supplier = data.get("supplier") | 		group = data.get(group_by_field) # get item or supplier value for this row | ||||||
| 		supplier_currency = frappe.db.get_value("Supplier", data.get("supplier"), "default_currency") | 
 | ||||||
|  | 		supplier_currency = frappe.db.get_value("Supplier", data.get("supplier_name"), "default_currency") | ||||||
| 
 | 
 | ||||||
| 		if supplier_currency: | 		if supplier_currency: | ||||||
| 			exchange_rate = get_exchange_rate(supplier_currency, company_currency) | 			exchange_rate = get_exchange_rate(supplier_currency, company_currency) | ||||||
| @ -75,38 +81,55 @@ def prepare_data(supplier_quotation_data, filters): | |||||||
| 			exchange_rate = 1 | 			exchange_rate = 1 | ||||||
| 
 | 
 | ||||||
| 		row = { | 		row = { | ||||||
| 			"item_code": data.get('item_code'), | 			"item_code":  "" if group_by_field=="item_code" else data.get("item_code"), # leave blank if group by field | ||||||
|  | 			"supplier_name": "" if group_by_field=="supplier_name" else data.get("supplier_name"), | ||||||
| 			"quotation": data.get("parent"), | 			"quotation": data.get("parent"), | ||||||
| 			"qty": data.get("qty"), | 			"qty": data.get("qty"), | ||||||
| 			"price": flt(data.get("rate") * exchange_rate, float_precision), | 			"price": flt(data.get("amount") * exchange_rate, float_precision), | ||||||
| 			"uom": data.get("uom"), | 			"uom": data.get("uom"), | ||||||
|  | 			"stock_uom": data.get('stock_uom'), | ||||||
| 			"request_for_quotation": data.get("request_for_quotation"), | 			"request_for_quotation": data.get("request_for_quotation"), | ||||||
| 			"valid_till": data.get('valid_till'), | 			"valid_till": data.get('valid_till'), | ||||||
| 			"lead_time_days": data.get('lead_time_days') | 			"lead_time_days": data.get('lead_time_days') | ||||||
| 		} | 		} | ||||||
|  | 		row["price_per_unit"] = flt(row["price"]) / (flt(data.get("stock_qty")) or 1) | ||||||
| 
 | 
 | ||||||
| 		# map for report view of form {'supplier1':[{},{},...]} | 		# map for report view of form {'supplier1'/'item1':[{},{},...]} | ||||||
| 		supplier_wise_map[supplier].append(row) | 		group_wise_map[group].append(row) | ||||||
| 
 | 
 | ||||||
| 		# map for chart preparation of the form {'supplier1': {'qty': 'price'}} | 		# map for chart preparation of the form {'supplier1': {'qty': 'price'}} | ||||||
|  | 		supplier = data.get("supplier_name") | ||||||
| 		if filters.get("item_code"): | 		if filters.get("item_code"): | ||||||
| 			if not supplier in supplier_qty_price_map: | 			if not supplier in supplier_qty_price_map: | ||||||
| 				supplier_qty_price_map[supplier] = {} | 				supplier_qty_price_map[supplier] = {} | ||||||
| 			supplier_qty_price_map[supplier][row["qty"]] = row["price"] | 			supplier_qty_price_map[supplier][row["qty"]] = row["price"] | ||||||
| 
 | 
 | ||||||
|  | 		groups.append(group) | ||||||
| 		suppliers.append(supplier) | 		suppliers.append(supplier) | ||||||
| 		qty_list.append(data.get("qty")) | 		qty_list.append(data.get("qty")) | ||||||
| 
 | 
 | ||||||
|  | 	groups = list(set(groups)) | ||||||
| 	suppliers = list(set(suppliers)) | 	suppliers = list(set(suppliers)) | ||||||
| 	qty_list = list(set(qty_list)) | 	qty_list = list(set(qty_list)) | ||||||
| 
 | 
 | ||||||
|  | 	highlight_min_price = group_by_field == "item_code" or filters.get("item_code") | ||||||
|  | 
 | ||||||
| 	# final data format for report view | 	# final data format for report view | ||||||
| 	for supplier in suppliers: | 	for group in groups: | ||||||
| 		supplier_wise_map[supplier][0].update({"supplier_name": supplier}) | 		group_entries = group_wise_map[group] # all entries pertaining to item/supplier | ||||||
| 		for entry in supplier_wise_map[supplier]: | 		group_entries[0].update({group_by_field : group}) # Add item/supplier name in first group row | ||||||
|  | 
 | ||||||
|  | 		if highlight_min_price: | ||||||
|  | 			prices = [group_entry["price_per_unit"] for group_entry in group_entries] | ||||||
|  | 			min_price = min(prices) | ||||||
|  | 
 | ||||||
|  | 		for entry in group_entries: | ||||||
|  | 			if highlight_min_price and entry["price_per_unit"] == min_price: | ||||||
|  | 				entry["min"] = 1 | ||||||
| 			out.append(entry) | 			out.append(entry) | ||||||
| 
 | 
 | ||||||
| 	if filters.get("item_code"): | 	if filters.get("item_code"): | ||||||
|  | 		# render chart only for one item comparison | ||||||
| 		chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map) | 		chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map) | ||||||
| 
 | 
 | ||||||
| 	return out, chart_data | 	return out, chart_data | ||||||
| @ -145,8 +168,9 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map): | |||||||
| 
 | 
 | ||||||
| 	return chart_data | 	return chart_data | ||||||
| 
 | 
 | ||||||
| def get_columns(): | def get_columns(filters): | ||||||
| 	columns = [{ | 	group_by_columns = [ | ||||||
|  | 	{ | ||||||
| 		"fieldname": "supplier_name", | 		"fieldname": "supplier_name", | ||||||
| 		"label": _("Supplier"), | 		"label": _("Supplier"), | ||||||
| 		"fieldtype": "Link", | 		"fieldtype": "Link", | ||||||
| @ -158,8 +182,10 @@ def get_columns(): | |||||||
| 		"label": _("Item"), | 		"label": _("Item"), | ||||||
| 		"fieldtype": "Link", | 		"fieldtype": "Link", | ||||||
| 		"options": "Item", | 		"options": "Item", | ||||||
| 		"width": 200 | 		"width": 150 | ||||||
| 	}, | 	}] | ||||||
|  | 
 | ||||||
|  | 	columns = [ | ||||||
| 	{ | 	{ | ||||||
| 		"fieldname": "uom", | 		"fieldname": "uom", | ||||||
| 		"label": _("UOM"), | 		"label": _("UOM"), | ||||||
| @ -180,6 +206,20 @@ def get_columns(): | |||||||
| 		"options": "Company:company:default_currency", | 		"options": "Company:company:default_currency", | ||||||
| 		"width": 110 | 		"width": 110 | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fieldname": "stock_uom", | ||||||
|  | 		"label": _("Stock UOM"), | ||||||
|  | 		"fieldtype": "Link", | ||||||
|  | 		"options": "UOM", | ||||||
|  | 		"width": 90 | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"fieldname": "price_per_unit", | ||||||
|  | 		"label": _("Price per Unit (Stock UOM)"), | ||||||
|  | 		"fieldtype": "Currency", | ||||||
|  | 		"options": "Company:company:default_currency", | ||||||
|  | 		"width": 120 | ||||||
|  | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"fieldname": "quotation", | 		"fieldname": "quotation", | ||||||
| 		"label": _("Supplier Quotation"), | 		"label": _("Supplier Quotation"), | ||||||
| @ -205,9 +245,12 @@ def get_columns(): | |||||||
| 		"fieldtype": "Link", | 		"fieldtype": "Link", | ||||||
| 		"options": "Request for Quotation", | 		"options": "Request for Quotation", | ||||||
| 		"width": 150 | 		"width": 150 | ||||||
| 	} | 	}] | ||||||
| 	] |  | ||||||
| 
 | 
 | ||||||
|  | 	if filters.get("group_by") == "Group by Item": | ||||||
|  | 		group_by_columns.reverse() | ||||||
|  | 
 | ||||||
|  | 	columns[0:0] = group_by_columns # add positioned group by columns to the report | ||||||
| 	return columns | 	return columns | ||||||
| 
 | 
 | ||||||
| def get_message(): | def get_message(): | ||||||
| @ -63,8 +63,8 @@ def get_data(): | |||||||
| 				{ | 				{ | ||||||
| 					"type": "doctype", | 					"type": "doctype", | ||||||
| 					"name": "Chart of Accounts Importer", | 					"name": "Chart of Accounts Importer", | ||||||
| 					"labe": _("Chart Of Accounts Importer"), | 					"label": _("Chart of Accounts Importer"), | ||||||
| 					"description": _("Import Chart Of Accounts from CSV / Excel files"), | 					"description": _("Import Chart of Accounts from CSV / Excel files"), | ||||||
| 					"onboard": 1 | 					"onboard": 1 | ||||||
| 				}, | 				}, | ||||||
| 				{ | 				{ | ||||||
|  | |||||||
| @ -1319,24 +1319,26 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil | |||||||
| 		validate_quantity(child_item, d) | 		validate_quantity(child_item, d) | ||||||
| 
 | 
 | ||||||
| 		child_item.qty = flt(d.get("qty")) | 		child_item.qty = flt(d.get("qty")) | ||||||
| 		precision = child_item.precision("rate") or 2 | 		rate_precision = child_item.precision("rate") or 2 | ||||||
|  | 		conv_fac_precision = child_item.precision("conversion_factor") or 2 | ||||||
|  | 		qty_precision = child_item.precision("qty") or 2 | ||||||
| 
 | 
 | ||||||
| 		if flt(child_item.billed_amt, precision) > flt(flt(d.get("rate")) * flt(d.get("qty")), precision): | 		if flt(child_item.billed_amt, rate_precision) > flt(flt(d.get("rate"), rate_precision) * flt(d.get("qty"), qty_precision), rate_precision): | ||||||
| 			frappe.throw(_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.") | 			frappe.throw(_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.") | ||||||
| 						 .format(child_item.idx, child_item.item_code)) | 						 .format(child_item.idx, child_item.item_code)) | ||||||
| 		else: | 		else: | ||||||
| 			child_item.rate = flt(d.get("rate")) | 			child_item.rate = flt(d.get("rate"), rate_precision) | ||||||
| 
 | 
 | ||||||
| 		if d.get("conversion_factor"): | 		if d.get("conversion_factor"): | ||||||
| 			if child_item.stock_uom == child_item.uom: | 			if child_item.stock_uom == child_item.uom: | ||||||
| 				child_item.conversion_factor = 1 | 				child_item.conversion_factor = 1 | ||||||
| 			else: | 			else: | ||||||
| 				child_item.conversion_factor = flt(d.get('conversion_factor')) | 				child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision) | ||||||
| 
 | 
 | ||||||
| 		if d.get("uom"): | 		if d.get("uom"): | ||||||
| 			child_item.uom = d.get("uom") | 			child_item.uom = d.get("uom") | ||||||
| 			conversion_factor = flt(get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor")) | 			conversion_factor = flt(get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor")) | ||||||
| 			child_item.conversion_factor = flt(d.get('conversion_factor')) or conversion_factor | 			child_item.conversion_factor = flt(d.get('conversion_factor'), conv_fac_precision) or conversion_factor | ||||||
| 
 | 
 | ||||||
| 		if d.get("delivery_date") and parent_doctype == 'Sales Order': | 		if d.get("delivery_date") and parent_doctype == 'Sales Order': | ||||||
| 			child_item.delivery_date = d.get('delivery_date') | 			child_item.delivery_date = d.get('delivery_date') | ||||||
|  | |||||||
| @ -847,6 +847,7 @@ def get_items_from_bom(item_code, bom, exploded_item=1): | |||||||
| 		where | 		where | ||||||
| 			t2.parent = t1.name and t1.item = %s | 			t2.parent = t1.name and t1.item = %s | ||||||
| 			and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s | 			and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s | ||||||
|  | 			and t2.sourced_by_supplier = 0 | ||||||
| 			and t2.item_code = t3.name""".format(doctype), | 			and t2.item_code = t3.name""".format(doctype), | ||||||
| 			(item_code, bom), as_dict=1) | 			(item_code, bom), as_dict=1) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| 
 | 
 | ||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| import frappe | import frappe | ||||||
| from frappe.utils import cint, flt, cstr, comma_or | from frappe.utils import cint, flt, cstr, comma_or, get_link_to_form | ||||||
| from frappe import _, throw | from frappe import _, throw | ||||||
| from erpnext.stock.get_item_details import get_bin_details | from erpnext.stock.get_item_details import get_bin_details | ||||||
| from erpnext.stock.utils import get_incoming_rate | from erpnext.stock.utils import get_incoming_rate | ||||||
| @ -173,22 +173,26 @@ class SellingController(StockController): | |||||||
| 
 | 
 | ||||||
| 	def validate_selling_price(self): | 	def validate_selling_price(self): | ||||||
| 		def throw_message(idx, item_name, rate, ref_rate_field): | 		def throw_message(idx, item_name, rate, ref_rate_field): | ||||||
| 			frappe.throw(_("""Row #{}: Selling rate for item {} is lower than its {}. Selling rate should be atleast {}""") | 			bold_net_rate = frappe.bold("net rate") | ||||||
| 				.format(idx, item_name, ref_rate_field, rate)) | 			msg = (_("""Row #{}: Selling rate for item {} is lower than its {}. Selling {} should be atleast {}""") | ||||||
|  | 						.format(idx, frappe.bold(item_name), frappe.bold(ref_rate_field), bold_net_rate, frappe.bold(rate))) | ||||||
|  | 			msg += "<br><br>" | ||||||
|  | 			msg += (_("""You can alternatively disable selling price validation in {} to bypass this validation.""") | ||||||
|  | 						.format(get_link_to_form("Selling Settings", "Selling Settings"))) | ||||||
|  | 			frappe.throw(msg, title=_("Invalid Selling Price")) | ||||||
| 
 | 
 | ||||||
| 		if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): | 		if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): | ||||||
| 			return | 			return | ||||||
| 
 |  | ||||||
| 		if hasattr(self, "is_return") and self.is_return: | 		if hasattr(self, "is_return") and self.is_return: | ||||||
| 			return | 			return | ||||||
| 
 | 
 | ||||||
| 		for it in self.get("items"): | 		for it in self.get("items"): | ||||||
| 			if not it.item_code: | 			if not it.item_code: | ||||||
| 				continue | 				continue | ||||||
| 
 | 			 | ||||||
| 			last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"]) | 			last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"]) | ||||||
| 			last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1) | 			last_purchase_rate_in_sales_uom = last_purchase_rate * (it.conversion_factor or 1) | ||||||
| 			if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom): | 			if flt(it.base_net_rate) < flt(last_purchase_rate_in_sales_uom): | ||||||
| 				throw_message(it.idx, frappe.bold(it.item_name), last_purchase_rate_in_sales_uom, "last purchase rate") | 				throw_message(it.idx, frappe.bold(it.item_name), last_purchase_rate_in_sales_uom, "last purchase rate") | ||||||
| 
 | 
 | ||||||
| 			last_valuation_rate = frappe.db.sql(""" | 			last_valuation_rate = frappe.db.sql(""" | ||||||
| @ -197,8 +201,8 @@ class SellingController(StockController): | |||||||
| 				ORDER BY posting_date DESC, posting_time DESC, creation DESC LIMIT 1 | 				ORDER BY posting_date DESC, posting_time DESC, creation DESC LIMIT 1 | ||||||
| 				""", (it.item_code, it.warehouse)) | 				""", (it.item_code, it.warehouse)) | ||||||
| 			if last_valuation_rate: | 			if last_valuation_rate: | ||||||
| 				last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1) | 				last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] * (it.conversion_factor or 1) | ||||||
| 				if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate_in_sales_uom) \ | 				if is_stock_item and flt(it.base_net_rate) < flt(last_valuation_rate_in_sales_uom) \ | ||||||
| 					and not self.get('is_internal_customer'): | 					and not self.get('is_internal_customer'): | ||||||
| 					throw_message(it.idx, frappe.bold(it.item_name), last_valuation_rate_in_sales_uom, "valuation rate") | 					throw_message(it.idx, frappe.bold(it.item_name), last_valuation_rate_in_sales_uom, "valuation rate") | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -241,6 +241,7 @@ | |||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "depends_on": "eval: doc.__islocal", |    "depends_on": "eval: doc.__islocal", | ||||||
|  |    "description": "Home, Work, etc.", | ||||||
|    "fieldname": "address_title", |    "fieldname": "address_title", | ||||||
|    "fieldtype": "Data", |    "fieldtype": "Data", | ||||||
|    "label": "Address Title" |    "label": "Address Title" | ||||||
| @ -249,7 +250,8 @@ | |||||||
|    "depends_on": "eval: doc.__islocal", |    "depends_on": "eval: doc.__islocal", | ||||||
|    "fieldname": "address_line1", |    "fieldname": "address_line1", | ||||||
|    "fieldtype": "Data", |    "fieldtype": "Data", | ||||||
|    "label": "Address Line 1" |    "label": "Address Line 1", | ||||||
|  |    "mandatory_depends_on": "eval: doc.address_title && doc.address_type" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "depends_on": "eval: doc.__islocal", |    "depends_on": "eval: doc.__islocal", | ||||||
| @ -261,7 +263,8 @@ | |||||||
|    "depends_on": "eval: doc.__islocal", |    "depends_on": "eval: doc.__islocal", | ||||||
|    "fieldname": "city", |    "fieldname": "city", | ||||||
|    "fieldtype": "Data", |    "fieldtype": "Data", | ||||||
|    "label": "City/Town" |    "label": "City/Town", | ||||||
|  |    "mandatory_depends_on": "eval: doc.address_title && doc.address_type" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "depends_on": "eval: doc.__islocal", |    "depends_on": "eval: doc.__islocal", | ||||||
| @ -280,6 +283,7 @@ | |||||||
|    "fieldname": "country", |    "fieldname": "country", | ||||||
|    "fieldtype": "Link", |    "fieldtype": "Link", | ||||||
|    "label": "Country", |    "label": "Country", | ||||||
|  |    "mandatory_depends_on": "eval: doc.address_title && doc.address_type", | ||||||
|    "options": "Country" |    "options": "Country" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
| @ -449,7 +453,7 @@ | |||||||
|  "idx": 5, |  "idx": 5, | ||||||
|  "image_field": "image", |  "image_field": "image", | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-06-18 14:39:41.835416", |  "modified": "2020-10-13 15:24:00.094811", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "CRM", |  "module": "CRM", | ||||||
|  "name": "Lead", |  "name": "Lead", | ||||||
|  | |||||||
| @ -22,7 +22,8 @@ class Lead(SellingController): | |||||||
| 		load_address_and_contact(self) | 		load_address_and_contact(self) | ||||||
| 
 | 
 | ||||||
| 	def before_insert(self): | 	def before_insert(self): | ||||||
| 		self.address_doc = self.create_address() | 		if self.address_title and self.address_type: | ||||||
|  | 			self.address_doc = self.create_address() | ||||||
| 		self.contact_doc = self.create_contact() | 		self.contact_doc = self.create_contact() | ||||||
| 
 | 
 | ||||||
| 	def after_insert(self): | 	def after_insert(self): | ||||||
| @ -133,15 +134,6 @@ class Lead(SellingController): | |||||||
| 		# skipping country since the system auto-sets it from system defaults | 		# skipping country since the system auto-sets it from system defaults | ||||||
| 		address = frappe.new_doc("Address") | 		address = frappe.new_doc("Address") | ||||||
| 
 | 
 | ||||||
| 		mandatory_fields = [ df.fieldname for df in address.meta.fields if df.reqd ] |  | ||||||
| 
 |  | ||||||
| 		if not all([self.get(field) for field in mandatory_fields]): |  | ||||||
| 			frappe.msgprint(_('Missing mandatory fields in address. \ |  | ||||||
| 				{0} to create address' ).format("<a href='desk#Form/Address/New Address 1' \ |  | ||||||
| 				> Click here </a>"), |  | ||||||
| 				alert=True, indicator='yellow') |  | ||||||
| 			return |  | ||||||
| 
 |  | ||||||
| 		address.update({addr_field: self.get(addr_field) for addr_field in address_fields}) | 		address.update({addr_field: self.get(addr_field) for addr_field in address_fields}) | ||||||
| 		address.update({info_field: self.get(info_field) for info_field in info_fields}) | 		address.update({info_field: self.get(info_field) for info_field in info_fields}) | ||||||
| 		address.insert() | 		address.insert() | ||||||
| @ -190,7 +182,7 @@ class Lead(SellingController): | |||||||
| 
 | 
 | ||||||
| 	def update_links(self): | 	def update_links(self): | ||||||
| 		# update address links | 		# update address links | ||||||
| 		if self.address_doc: | 		if hasattr(self, 'address_doc'): | ||||||
| 			self.address_doc.append("links", { | 			self.address_doc.append("links", { | ||||||
| 				"link_doctype": "Lead", | 				"link_doctype": "Lead", | ||||||
| 				"link_name": self.name, | 				"link_name": self.name, | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ from erpnext.accounts.party import get_party_account_currency | |||||||
| from erpnext.exceptions import InvalidCurrency | from erpnext.exceptions import InvalidCurrency | ||||||
| from erpnext.stock.doctype.material_request.material_request import make_request_for_quotation | from erpnext.stock.doctype.material_request.material_request import make_request_for_quotation | ||||||
| from erpnext.buying.doctype.request_for_quotation.request_for_quotation import \ | from erpnext.buying.doctype.request_for_quotation.request_for_quotation import \ | ||||||
| 			 make_supplier_quotation as make_quotation_from_rfq | 			 make_supplier_quotation_from_rfq | ||||||
| 
 | 
 | ||||||
| def work(): | def work(): | ||||||
| 	frappe.set_user(frappe.db.get_global('demo_purchase_user')) | 	frappe.set_user(frappe.db.get_global('demo_purchase_user')) | ||||||
| @ -44,7 +44,7 @@ def work(): | |||||||
| 				rfq = frappe.get_doc('Request for Quotation', rfq.name) | 				rfq = frappe.get_doc('Request for Quotation', rfq.name) | ||||||
| 
 | 
 | ||||||
| 				for supplier in rfq.suppliers: | 				for supplier in rfq.suppliers: | ||||||
| 					supplier_quotation = make_quotation_from_rfq(rfq.name, supplier.supplier) | 					supplier_quotation = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier.supplier) | ||||||
| 					supplier_quotation.save() | 					supplier_quotation.save() | ||||||
| 					supplier_quotation.submit() | 					supplier_quotation.submit() | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -68,7 +68,7 @@ def make_program_and_linked_courses(program_name, course_name_list): | |||||||
| 		program = frappe.get_doc("Program", program_name) | 		program = frappe.get_doc("Program", program_name) | ||||||
| 	course_list = [make_course(course_name) for course_name in course_name_list] | 	course_list = [make_course(course_name) for course_name in course_name_list] | ||||||
| 	for course in course_list: | 	for course in course_list: | ||||||
| 		program.append("courses", {"course": course}) | 		program.append("courses", {"course": course, "required": 1}) | ||||||
| 	program.save() | 	program.save() | ||||||
| 	return program | 	return program | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -17,9 +17,7 @@ | |||||||
|    "in_list_view": 1, |    "in_list_view": 1, | ||||||
|    "label": "Course", |    "label": "Course", | ||||||
|    "options": "Course", |    "options": "Course", | ||||||
|    "reqd": 1, |    "reqd": 1 | ||||||
|    "show_days": 1, |  | ||||||
|    "show_seconds": 1 |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "fetch_from": "course.course_name", |    "fetch_from": "course.course_name", | ||||||
| @ -27,23 +25,19 @@ | |||||||
|    "fieldtype": "Data", |    "fieldtype": "Data", | ||||||
|    "in_list_view": 1, |    "in_list_view": 1, | ||||||
|    "label": "Course Name", |    "label": "Course Name", | ||||||
|    "read_only": 1, |    "read_only": 1 | ||||||
|    "show_days": 1, |  | ||||||
|    "show_seconds": 1 |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "default": "0", |    "default": "1", | ||||||
|    "fieldname": "required", |    "fieldname": "required", | ||||||
|    "fieldtype": "Check", |    "fieldtype": "Check", | ||||||
|    "in_list_view": 1, |    "in_list_view": 1, | ||||||
|    "label": "Mandatory", |    "label": "Mandatory" | ||||||
|    "show_days": 1, |  | ||||||
|    "show_seconds": 1 |  | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "istable": 1, |  "istable": 1, | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-06-09 18:56:10.213241", |  "modified": "2020-09-15 18:14:22.816795", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Education", |  "module": "Education", | ||||||
|  "name": "Program Course", |  "name": "Program Course", | ||||||
|  | |||||||
| @ -2,16 +2,24 @@ | |||||||
| // For license information, please see license.txt
 | // For license information, please see license.txt
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| frappe.ui.form.on("Program Enrollment", { | frappe.ui.form.on('Program Enrollment', { | ||||||
| 	setup: function(frm) { | 	setup: function(frm) { | ||||||
| 		frm.add_fetch('fee_structure', 'total_amount', 'amount'); | 		frm.add_fetch('fee_structure', 'total_amount', 'amount'); | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	onload: function(frm, cdt, cdn){ | 	onload: function(frm) { | ||||||
| 		frm.set_query("academic_term", "fees", function(){ | 		frm.set_query('academic_term', function() { | ||||||
| 			return{ | 			return { | ||||||
| 				"filters":{ | 				'filters':{ | ||||||
| 					"academic_year": (frm.doc.academic_year) | 					'academic_year': frm.doc.academic_year | ||||||
|  | 				} | ||||||
|  | 			}; | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		frm.set_query('academic_term', 'fees', function() { | ||||||
|  | 			return { | ||||||
|  | 				'filters':{ | ||||||
|  | 					'academic_year': frm.doc.academic_year | ||||||
| 				} | 				} | ||||||
| 			}; | 			}; | ||||||
| 		}); | 		}); | ||||||
| @ -24,9 +32,9 @@ frappe.ui.form.on("Program Enrollment", { | |||||||
| 		}; | 		}; | ||||||
| 
 | 
 | ||||||
| 		if (frm.doc.program) { | 		if (frm.doc.program) { | ||||||
| 			frm.set_query("course", "courses", function(doc, cdt, cdn) { | 			frm.set_query('course', 'courses', function() { | ||||||
| 				return{ | 				return { | ||||||
| 					query: "erpnext.education.doctype.program_enrollment.program_enrollment.get_program_courses", | 					query: 'erpnext.education.doctype.program_enrollment.program_enrollment.get_program_courses', | ||||||
| 					filters: { | 					filters: { | ||||||
| 						'program': frm.doc.program | 						'program': frm.doc.program | ||||||
| 					} | 					} | ||||||
| @ -34,9 +42,9 @@ frappe.ui.form.on("Program Enrollment", { | |||||||
| 			}); | 			}); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		frm.set_query("student", function() { | 		frm.set_query('student', function() { | ||||||
| 			return{ | 			return{ | ||||||
| 				query: "erpnext.education.doctype.program_enrollment.program_enrollment.get_students", | 				query: 'erpnext.education.doctype.program_enrollment.program_enrollment.get_students', | ||||||
| 				filters: { | 				filters: { | ||||||
| 					'academic_year': frm.doc.academic_year, | 					'academic_year': frm.doc.academic_year, | ||||||
| 					'academic_term': frm.doc.academic_term | 					'academic_term': frm.doc.academic_term | ||||||
| @ -49,14 +57,14 @@ frappe.ui.form.on("Program Enrollment", { | |||||||
| 		frm.events.get_courses(frm); | 		frm.events.get_courses(frm); | ||||||
| 		if (frm.doc.program) { | 		if (frm.doc.program) { | ||||||
| 			frappe.call({ | 			frappe.call({ | ||||||
| 				method: "erpnext.education.api.get_fee_schedule", | 				method: 'erpnext.education.api.get_fee_schedule', | ||||||
| 				args: { | 				args: { | ||||||
| 					"program": frm.doc.program, | 					'program': frm.doc.program, | ||||||
| 					"student_category": frm.doc.student_category | 					'student_category': frm.doc.student_category | ||||||
| 				}, | 				}, | ||||||
| 				callback: function(r) { | 				callback: function(r) { | ||||||
| 					if(r.message) { | 					if (r.message) { | ||||||
| 						frm.set_value("fees" ,r.message); | 						frm.set_value('fees' ,r.message); | ||||||
| 						frm.events.get_courses(frm); | 						frm.events.get_courses(frm); | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| @ -65,17 +73,17 @@ frappe.ui.form.on("Program Enrollment", { | |||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	student_category: function() { | 	student_category: function() { | ||||||
| 		frappe.ui.form.trigger("Program Enrollment", "program"); | 		frappe.ui.form.trigger('Program Enrollment', 'program'); | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	get_courses: function(frm) { | 	get_courses: function(frm) { | ||||||
| 		frm.set_value("courses",[]); | 		frm.set_value('courses',[]); | ||||||
| 		frappe.call({ | 		frappe.call({ | ||||||
| 			method: "get_courses", | 			method: 'get_courses', | ||||||
| 			doc:frm.doc, | 			doc:frm.doc, | ||||||
| 			callback: function(r) { | 			callback: function(r) { | ||||||
| 				if(r.message) { | 				if (r.message) { | ||||||
| 					frm.set_value("courses", r.message); | 					frm.set_value('courses', r.message); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| @ -84,10 +92,10 @@ frappe.ui.form.on("Program Enrollment", { | |||||||
| 
 | 
 | ||||||
| frappe.ui.form.on('Program Enrollment Course', { | frappe.ui.form.on('Program Enrollment Course', { | ||||||
| 	courses_add: function(frm){ | 	courses_add: function(frm){ | ||||||
| 		frm.fields_dict['courses'].grid.get_field('course').get_query = function(doc){ | 		frm.fields_dict['courses'].grid.get_field('course').get_query = function(doc) { | ||||||
| 			var course_list = []; | 			var course_list = []; | ||||||
| 			if(!doc.__islocal) course_list.push(doc.name); | 			if(!doc.__islocal) course_list.push(doc.name); | ||||||
| 			$.each(doc.courses, function(idx, val){ | 			$.each(doc.courses, function(_idx, val) { | ||||||
| 				if (val.course) course_list.push(val.course); | 				if (val.course) course_list.push(val.course); | ||||||
| 			}); | 			}); | ||||||
| 			return { filters: [['Course', 'name', 'not in', course_list]] }; | 			return { filters: [['Course', 'name', 'not in', course_list]] }; | ||||||
|  | |||||||
| @ -1,775 +1,218 @@ | |||||||
| { | { | ||||||
|  "allow_copy": 0,  |  "actions": [], | ||||||
|  "allow_events_in_timeline": 0,  |  "allow_import": 1, | ||||||
|  "allow_guest_to_view": 0,  |  "autoname": "EDU-ENR-.YYYY.-.#####", | ||||||
|  "allow_import": 1,  |  "creation": "2015-12-02 12:58:32.916080", | ||||||
|  "allow_rename": 0,  |  "doctype": "DocType", | ||||||
|  "autoname": "EDU-ENR-.YYYY.-.#####",  |  "document_type": "Document", | ||||||
|  "beta": 0,  |  "engine": "InnoDB", | ||||||
|  "creation": "2015-12-02 12:58:32.916080",  |  "field_order": [ | ||||||
|  "custom": 0,  |   "student", | ||||||
|  "docstatus": 0,  |   "student_name", | ||||||
|  "doctype": "DocType",  |   "student_category", | ||||||
|  "document_type": "Document",  |   "student_batch_name", | ||||||
|  "editable_grid": 0,  |   "school_house", | ||||||
|  "engine": "InnoDB",  |   "column_break_4", | ||||||
|  |   "program", | ||||||
|  |   "academic_year", | ||||||
|  |   "academic_term", | ||||||
|  |   "enrollment_date", | ||||||
|  |   "boarding_student", | ||||||
|  |   "enrolled_courses", | ||||||
|  |   "courses", | ||||||
|  |   "transportation", | ||||||
|  |   "mode_of_transportation", | ||||||
|  |   "column_break_13", | ||||||
|  |   "vehicle_no", | ||||||
|  |   "section_break_7", | ||||||
|  |   "fees", | ||||||
|  |   "amended_from", | ||||||
|  |   "image" | ||||||
|  |  ], | ||||||
|  "fields": [ |  "fields": [ | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "student", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Link", | ||||||
|    "allow_on_submit": 0,  |    "in_global_search": 1, | ||||||
|    "bold": 0,  |    "label": "Student", | ||||||
|    "collapsible": 0,  |    "options": "Student", | ||||||
|    "columns": 0,  |    "reqd": 1 | ||||||
|    "fieldname": "student",  |   }, | ||||||
|    "fieldtype": "Link",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 1,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Student",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "Student",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 1,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fetch_from": "student.title", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldname": "student_name", | ||||||
|    "allow_on_submit": 0,  |    "fieldtype": "Read Only", | ||||||
|    "bold": 0,  |    "in_global_search": 1, | ||||||
|    "collapsible": 0,  |    "label": "Student Name", | ||||||
|    "columns": 0,  |    "read_only": 1 | ||||||
|    "fetch_from": "student.title",  |   }, | ||||||
|    "fieldname": "student_name",  |  | ||||||
|    "fieldtype": "Read Only",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 1,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Student Name",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "student_category", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Link", | ||||||
|    "allow_on_submit": 0,  |    "label": "Student Category", | ||||||
|    "bold": 0,  |    "options": "Student Category" | ||||||
|    "collapsible": 0,  |   }, | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "student_category",  |  | ||||||
|    "fieldtype": "Link",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Student Category",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "Student Category",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "allow_on_submit": 1, | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldname": "student_batch_name", | ||||||
|    "allow_on_submit": 1,  |    "fieldtype": "Link", | ||||||
|    "bold": 0,  |    "in_global_search": 1, | ||||||
|    "collapsible": 0,  |    "label": "Student Batch", | ||||||
|    "columns": 0,  |    "options": "Student Batch Name" | ||||||
|    "fieldname": "student_batch_name",  |   }, | ||||||
|    "fieldtype": "Link",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 1,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Student Batch",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "Student Batch Name",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "allow_on_submit": 1, | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldname": "school_house", | ||||||
|    "allow_on_submit": 1,  |    "fieldtype": "Link", | ||||||
|    "bold": 0,  |    "label": "School House", | ||||||
|    "collapsible": 0,  |    "options": "School House" | ||||||
|    "columns": 0,  |   }, | ||||||
|    "fieldname": "school_house",  |  | ||||||
|    "fieldtype": "Link",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "School House",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "School House",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "column_break_4", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Column Break" | ||||||
|    "allow_on_submit": 0,  |   }, | ||||||
|    "bold": 0,  |  | ||||||
|    "collapsible": 0,  |  | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "column_break_4",  |  | ||||||
|    "fieldtype": "Column Break",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "program", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Link", | ||||||
|    "allow_on_submit": 0,  |    "in_list_view": 1, | ||||||
|    "bold": 0,  |    "in_standard_filter": 1, | ||||||
|    "collapsible": 0,  |    "label": "Program", | ||||||
|    "columns": 0,  |    "options": "Program", | ||||||
|    "fieldname": "program",  |    "reqd": 1 | ||||||
|    "fieldtype": "Link",  |   }, | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 1,  |  | ||||||
|    "in_standard_filter": 1,  |  | ||||||
|    "label": "Program",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "Program",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 1,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "academic_year", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Link", | ||||||
|    "allow_on_submit": 0,  |    "in_list_view": 1, | ||||||
|    "bold": 0,  |    "in_standard_filter": 1, | ||||||
|    "collapsible": 0,  |    "label": "Academic Year", | ||||||
|    "columns": 0,  |    "options": "Academic Year", | ||||||
|    "fieldname": "academic_year",  |    "reqd": 1 | ||||||
|    "fieldtype": "Link",  |   }, | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 1,  |  | ||||||
|    "in_standard_filter": 1,  |  | ||||||
|    "label": "Academic Year",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "Academic Year",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 1,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "academic_term", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Link", | ||||||
|    "allow_on_submit": 0,  |    "label": "Academic Term", | ||||||
|    "bold": 0,  |    "options": "Academic Term" | ||||||
|    "collapsible": 0,  |   }, | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "academic_term",  |  | ||||||
|    "fieldtype": "Link",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Academic Term",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "Academic Term",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "default": "Today", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldname": "enrollment_date", | ||||||
|    "allow_on_submit": 0,  |    "fieldtype": "Date", | ||||||
|    "bold": 0,  |    "label": "Enrollment Date", | ||||||
|    "collapsible": 0,  |    "reqd": 1 | ||||||
|    "columns": 0,  |   }, | ||||||
|    "default": "Today",  |  | ||||||
|    "fieldname": "enrollment_date",  |  | ||||||
|    "fieldtype": "Date",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Enrollment Date",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 1,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "default": "0", | ||||||
|    "allow_in_quick_entry": 0,  |    "description": "Check this if the Student is residing at the Institute's Hostel.", | ||||||
|    "allow_on_submit": 0,  |    "fieldname": "boarding_student", | ||||||
|    "bold": 0,  |    "fieldtype": "Check", | ||||||
|    "collapsible": 0,  |    "label": "Boarding Student" | ||||||
|    "columns": 0,  |   }, | ||||||
|    "default": "0",  |  | ||||||
|    "description": "Check this if the Student is residing at the Institute's Hostel.",  |  | ||||||
|    "fieldname": "boarding_student",  |  | ||||||
|    "fieldtype": "Check",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Boarding Student",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "collapsible": 1, | ||||||
|    "allow_in_quick_entry": 0,  |    "collapsible_depends_on": "vehicle_no", | ||||||
|    "allow_on_submit": 0,  |    "fieldname": "transportation", | ||||||
|    "bold": 0,  |    "fieldtype": "Section Break", | ||||||
|    "collapsible": 1,  |    "label": "Transportation" | ||||||
|    "collapsible_depends_on": "vehicle_no",  |   }, | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "transportation",  |  | ||||||
|    "fieldtype": "Section Break",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Transportation",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "allow_on_submit": 1, | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldname": "mode_of_transportation", | ||||||
|    "allow_on_submit": 1,  |    "fieldtype": "Select", | ||||||
|    "bold": 0,  |    "label": "Mode of Transportation", | ||||||
|    "collapsible": 0,  |    "options": "\nWalking\nInstitute's Bus\nPublic Transport\nSelf-Driving Vehicle\nPick/Drop by Guardian" | ||||||
|    "columns": 0,  |   }, | ||||||
|    "fieldname": "mode_of_transportation",  |  | ||||||
|    "fieldtype": "Select",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Mode of Transportation",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "\nWalking\nInstitute's Bus\nPublic Transport\nSelf-Driving Vehicle\nPick/Drop by Guardian",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "column_break_13", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Column Break" | ||||||
|    "allow_on_submit": 0,  |   }, | ||||||
|    "bold": 0,  |  | ||||||
|    "collapsible": 0,  |  | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "column_break_13",  |  | ||||||
|    "fieldtype": "Column Break",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "allow_on_submit": 1, | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldname": "vehicle_no", | ||||||
|    "allow_on_submit": 1,  |    "fieldtype": "Data", | ||||||
|    "bold": 0,  |    "label": "Vehicle/Bus Number" | ||||||
|    "collapsible": 0,  |   }, | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "vehicle_no",  |  | ||||||
|    "fieldtype": "Data",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Vehicle/Bus Number",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "enrolled_courses", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Section Break", | ||||||
|    "allow_on_submit": 0,  |    "label": "Enrolled courses" | ||||||
|    "bold": 0,  |   }, | ||||||
|    "collapsible": 1,  |  | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "enrolled_courses",  |  | ||||||
|    "fieldtype": "Section Break",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Enrolled courses",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "allow_on_submit": 1, | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldname": "courses", | ||||||
|    "allow_on_submit": 1,  |    "fieldtype": "Table", | ||||||
|    "bold": 0,  |    "label": "Courses", | ||||||
|    "collapsible": 0,  |    "options": "Program Enrollment Course" | ||||||
|    "columns": 0,  |   }, | ||||||
|    "fieldname": "courses",  |  | ||||||
|    "fieldtype": "Table",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Courses",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "Program Enrollment Course",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "collapsible": 1, | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldname": "section_break_7", | ||||||
|    "allow_on_submit": 0,  |    "fieldtype": "Section Break", | ||||||
|    "bold": 0,  |    "label": "Fees" | ||||||
|    "collapsible": 1,  |   }, | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "section_break_7",  |  | ||||||
|    "fieldtype": "Section Break",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Fees",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "fees", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Table", | ||||||
|    "allow_on_submit": 0,  |    "label": "Fees", | ||||||
|    "bold": 0,  |    "options": "Program Fee" | ||||||
|    "collapsible": 0,  |   }, | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "fees",  |  | ||||||
|    "fieldtype": "Table",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Fees",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "Program Fee",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0,  |  | ||||||
|    "width": "" |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "amended_from", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Link", | ||||||
|    "allow_on_submit": 0,  |    "label": "Amended From", | ||||||
|    "bold": 0,  |    "no_copy": 1, | ||||||
|    "collapsible": 0,  |    "options": "Program Enrollment", | ||||||
|    "columns": 0,  |    "print_hide": 1, | ||||||
|    "fieldname": "amended_from",  |    "read_only": 1 | ||||||
|    "fieldtype": "Link",  |   }, | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Amended From",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 1,  |  | ||||||
|    "options": "Program Enrollment",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "print_hide": 1,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "image", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Attach Image", | ||||||
|    "allow_on_submit": 0,  |    "hidden": 1, | ||||||
|    "bold": 0,  |    "label": "Image" | ||||||
|    "collapsible": 0,  |  | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "image",  |  | ||||||
|    "fieldtype": "Attach Image",  |  | ||||||
|    "hidden": 1,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Image",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   } |   } | ||||||
|  ],  |  ], | ||||||
|  "has_web_view": 0,  |  "image_field": "image", | ||||||
|  "hide_heading": 0,  |  "is_submittable": 1, | ||||||
|  "hide_toolbar": 0,  |  "links": [], | ||||||
|  "idx": 0,  |  "modified": "2020-09-15 18:12:11.988565", | ||||||
|  "image_field": "image",  |  "modified_by": "Administrator", | ||||||
|  "image_view": 0,  |  "module": "Education", | ||||||
|  "in_create": 0,  |  "name": "Program Enrollment", | ||||||
|  "is_submittable": 1,  |  "owner": "Administrator", | ||||||
|  "issingle": 0,  |  | ||||||
|  "istable": 0,  |  | ||||||
|  "max_attachments": 0,  |  | ||||||
|  "menu_index": 0,  |  | ||||||
|  "modified": "2018-11-07 21:13:06.502279",  |  | ||||||
|  "modified_by": "Administrator",  |  | ||||||
|  "module": "Education",  |  | ||||||
|  "name": "Program Enrollment",  |  | ||||||
|  "name_case": "",  |  | ||||||
|  "owner": "Administrator",  |  | ||||||
|  "permissions": [ |  "permissions": [ | ||||||
|   { |   { | ||||||
|    "amend": 1,  |    "amend": 1, | ||||||
|    "cancel": 1,  |    "cancel": 1, | ||||||
|    "create": 1,  |    "create": 1, | ||||||
|    "delete": 1,  |    "delete": 1, | ||||||
|    "email": 1,  |    "email": 1, | ||||||
|    "export": 1,  |    "export": 1, | ||||||
|    "if_owner": 0,  |    "print": 1, | ||||||
|    "import": 0,  |    "read": 1, | ||||||
|    "permlevel": 0,  |    "report": 1, | ||||||
|    "print": 1,  |    "role": "Academics User", | ||||||
|    "read": 1,  |    "share": 1, | ||||||
|    "report": 1,  |    "submit": 1, | ||||||
|    "role": "Academics User",  |  | ||||||
|    "set_user_permissions": 0,  |  | ||||||
|    "share": 1,  |  | ||||||
|    "submit": 1,  |  | ||||||
|    "write": 1 |    "write": 1 | ||||||
|   },  |   }, | ||||||
|   { |   { | ||||||
|    "amend": 0,  |    "create": 1, | ||||||
|    "cancel": 0,  |    "email": 1, | ||||||
|    "create": 1,  |    "export": 1, | ||||||
|    "delete": 0,  |    "print": 1, | ||||||
|    "email": 1,  |    "read": 1, | ||||||
|    "export": 1,  |    "report": 1, | ||||||
|    "if_owner": 0,  |    "role": "LMS User", | ||||||
|    "import": 0,  |    "share": 1, | ||||||
|    "permlevel": 0,  |    "submit": 1, | ||||||
|    "print": 1,  |  | ||||||
|    "read": 1,  |  | ||||||
|    "report": 1,  |  | ||||||
|    "role": "LMS User",  |  | ||||||
|    "set_user_permissions": 0,  |  | ||||||
|    "share": 1,  |  | ||||||
|    "submit": 1,  |  | ||||||
|    "write": 1 |    "write": 1 | ||||||
|   } |   } | ||||||
|  ],  |  ], | ||||||
|  "quick_entry": 0,  |  "restrict_to_domain": "Education", | ||||||
|  "read_only": 0,  |  "show_name_in_global_search": 1, | ||||||
|  "read_only_onload": 0,  |  "sort_field": "modified", | ||||||
|  "restrict_to_domain": "Education",  |  "sort_order": "DESC", | ||||||
|  "show_name_in_global_search": 1,  |  "title_field": "student_name" | ||||||
|  "sort_field": "modified",  |  | ||||||
|  "sort_order": "DESC",  |  | ||||||
|  "title_field": "student_name",  |  | ||||||
|  "track_changes": 0,  |  | ||||||
|  "track_seen": 0,  |  | ||||||
|  "track_views": 0 |  | ||||||
| } | } | ||||||
| @ -7,12 +7,15 @@ import frappe | |||||||
| from frappe import msgprint, _ | from frappe import msgprint, _ | ||||||
| from frappe.model.document import Document | from frappe.model.document import Document | ||||||
| from frappe.desk.reportview import get_match_cond, get_filters_cond | from frappe.desk.reportview import get_match_cond, get_filters_cond | ||||||
| from frappe.utils import comma_and | from frappe.utils import comma_and, get_link_to_form, getdate | ||||||
| import erpnext.www.lms as lms | import erpnext.www.lms as lms | ||||||
| 
 | 
 | ||||||
| class ProgramEnrollment(Document): | class ProgramEnrollment(Document): | ||||||
| 	def validate(self): | 	def validate(self): | ||||||
| 		self.validate_duplication() | 		self.validate_duplication() | ||||||
|  | 		self.validate_academic_year() | ||||||
|  | 		if self.academic_term: | ||||||
|  | 			self.validate_academic_term() | ||||||
| 		if not self.student_name: | 		if not self.student_name: | ||||||
| 			self.student_name = frappe.db.get_value("Student", self.student, "title") | 			self.student_name = frappe.db.get_value("Student", self.student, "title") | ||||||
| 		if not self.courses: | 		if not self.courses: | ||||||
| @ -23,11 +26,34 @@ class ProgramEnrollment(Document): | |||||||
| 		self.make_fee_records() | 		self.make_fee_records() | ||||||
| 		self.create_course_enrollments() | 		self.create_course_enrollments() | ||||||
| 
 | 
 | ||||||
|  | 	def validate_academic_year(self): | ||||||
|  | 		start_date, end_date = frappe.db.get_value("Academic Year", self.academic_year, ["year_start_date", "year_end_date"]) | ||||||
|  | 		if self.enrollment_date: | ||||||
|  | 			if start_date and getdate(self.enrollment_date) < getdate(start_date): | ||||||
|  | 				frappe.throw(_("Enrollment Date cannot be before the Start Date of the Academic Year {0}").format( | ||||||
|  | 					get_link_to_form("Academic Year", self.academic_year))) | ||||||
|  | 
 | ||||||
|  | 			if end_date and getdate(self.enrollment_date) > getdate(end_date): | ||||||
|  | 				frappe.throw(_("Enrollment Date cannot be after the End Date of the Academic Term {0}").format( | ||||||
|  | 					get_link_to_form("Academic Year", self.academic_year))) | ||||||
|  | 
 | ||||||
|  | 	def validate_academic_term(self): | ||||||
|  | 		start_date, end_date = frappe.db.get_value("Academic Term", self.academic_term, ["term_start_date", "term_end_date"]) | ||||||
|  | 		if self.enrollment_date: | ||||||
|  | 			if start_date and getdate(self.enrollment_date) < getdate(start_date): | ||||||
|  | 				frappe.throw(_("Enrollment Date cannot be before the Start Date of the Academic Term {0}").format( | ||||||
|  | 					get_link_to_form("Academic Term", self.academic_term))) | ||||||
|  | 
 | ||||||
|  | 			if end_date and getdate(self.enrollment_date) > getdate(end_date): | ||||||
|  | 				frappe.throw(_("Enrollment Date cannot be after the End Date of the Academic Term {0}").format( | ||||||
|  | 					get_link_to_form("Academic Term", self.academic_term))) | ||||||
|  | 
 | ||||||
| 	def validate_duplication(self): | 	def validate_duplication(self): | ||||||
| 		enrollment = frappe.get_all("Program Enrollment", filters={ | 		enrollment = frappe.get_all("Program Enrollment", filters={ | ||||||
| 			"student": self.student, | 			"student": self.student, | ||||||
| 			"program": self.program, | 			"program": self.program, | ||||||
| 			"academic_year": self.academic_year, | 			"academic_year": self.academic_year, | ||||||
|  | 			"academic_term": self.academic_term, | ||||||
| 			"docstatus": ("<", 2), | 			"docstatus": ("<", 2), | ||||||
| 			"name": ("!=", self.name) | 			"name": ("!=", self.name) | ||||||
| 		}) | 		}) | ||||||
| @ -70,10 +96,9 @@ class ProgramEnrollment(Document): | |||||||
| 
 | 
 | ||||||
| 	def create_course_enrollments(self): | 	def create_course_enrollments(self): | ||||||
| 		student = frappe.get_doc("Student", self.student) | 		student = frappe.get_doc("Student", self.student) | ||||||
| 		program = frappe.get_doc("Program", self.program) | 		course_list = [course.course for course in self.courses] | ||||||
| 		course_list = [course.course for course in program.courses] |  | ||||||
| 		for course_name in course_list: | 		for course_name in course_list: | ||||||
| 			student.enroll_in_course(course_name=course_name, program_enrollment=self.name) | 			student.enroll_in_course(course_name=course_name, program_enrollment=self.name, enrollment_date=self.enrollment_date) | ||||||
| 
 | 
 | ||||||
| 	def get_all_course_enrollments(self): | 	def get_all_course_enrollments(self): | ||||||
| 		course_enrollment_names = frappe.get_list("Course Enrollment", filters={'program_enrollment': self.name}) | 		course_enrollment_names = frappe.get_list("Course Enrollment", filters={'program_enrollment': self.name}) | ||||||
|  | |||||||
| @ -392,6 +392,9 @@ regional_overrides = { | |||||||
| 	'Italy': { | 	'Italy': { | ||||||
| 		'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.italy.utils.update_itemised_tax_data', | 		'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.italy.utils.update_itemised_tax_data', | ||||||
| 		'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.italy.utils.sales_invoice_validate', | 		'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.italy.utils.sales_invoice_validate', | ||||||
|  | 	}, | ||||||
|  | 	'Germany': { | ||||||
|  | 		'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.germany.accounts_controller.validate_regional', | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| user_privacy_documents = [ | user_privacy_documents = [ | ||||||
|  | |||||||
| @ -109,7 +109,6 @@ | |||||||
|   "encashment_date", |   "encashment_date", | ||||||
|   "exit_interview_details", |   "exit_interview_details", | ||||||
|   "held_on", |   "held_on", | ||||||
|   "reason_for_resignation", |  | ||||||
|   "new_workplace", |   "new_workplace", | ||||||
|   "feedback", |   "feedback", | ||||||
|   "lft", |   "lft", | ||||||
| @ -682,7 +681,7 @@ | |||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "fieldname": "reason_for_leaving", |    "fieldname": "reason_for_leaving", | ||||||
|    "fieldtype": "Data", |    "fieldtype": "Small Text", | ||||||
|    "label": "Reason for Leaving", |    "label": "Reason for Leaving", | ||||||
|    "oldfieldname": "reason_for_leaving", |    "oldfieldname": "reason_for_leaving", | ||||||
|    "oldfieldtype": "Data" |    "oldfieldtype": "Data" | ||||||
| @ -696,6 +695,7 @@ | |||||||
|    "options": "\nYes\nNo" |    "options": "\nYes\nNo" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|  |    "depends_on": "eval:doc.leave_encashed ==\"Yes\"", | ||||||
|    "fieldname": "encashment_date", |    "fieldname": "encashment_date", | ||||||
|    "fieldtype": "Date", |    "fieldtype": "Date", | ||||||
|    "label": "Encashment Date", |    "label": "Encashment Date", | ||||||
| @ -705,7 +705,6 @@ | |||||||
|   { |   { | ||||||
|    "fieldname": "exit_interview_details", |    "fieldname": "exit_interview_details", | ||||||
|    "fieldtype": "Column Break", |    "fieldtype": "Column Break", | ||||||
|    "label": "Exit Interview Details", |  | ||||||
|    "oldfieldname": "col_brk6", |    "oldfieldname": "col_brk6", | ||||||
|    "oldfieldtype": "Column Break", |    "oldfieldtype": "Column Break", | ||||||
|    "width": "50%" |    "width": "50%" | ||||||
| @ -713,18 +712,10 @@ | |||||||
|   { |   { | ||||||
|    "fieldname": "held_on", |    "fieldname": "held_on", | ||||||
|    "fieldtype": "Date", |    "fieldtype": "Date", | ||||||
|    "label": "Held On", |    "label": "Exit Interview Held On", | ||||||
|    "oldfieldname": "held_on", |    "oldfieldname": "held_on", | ||||||
|    "oldfieldtype": "Date" |    "oldfieldtype": "Date" | ||||||
|   }, |   }, | ||||||
|   { |  | ||||||
|    "fieldname": "reason_for_resignation", |  | ||||||
|    "fieldtype": "Select", |  | ||||||
|    "label": "Reason for Resignation", |  | ||||||
|    "oldfieldname": "reason_for_resignation", |  | ||||||
|    "oldfieldtype": "Select", |  | ||||||
|    "options": "\nBetter Prospects\nHealth Concerns" |  | ||||||
|   }, |  | ||||||
|   { |   { | ||||||
|    "fieldname": "new_workplace", |    "fieldname": "new_workplace", | ||||||
|    "fieldtype": "Data", |    "fieldtype": "Data", | ||||||
| @ -809,37 +800,29 @@ | |||||||
|    "fieldname": "expense_approver", |    "fieldname": "expense_approver", | ||||||
|    "fieldtype": "Link", |    "fieldtype": "Link", | ||||||
|    "label": "Expense Approver", |    "label": "Expense Approver", | ||||||
|    "options": "User", |    "options": "User" | ||||||
|    "show_days": 1, |  | ||||||
|    "show_seconds": 1 |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "fieldname": "approvers_section", |    "fieldname": "approvers_section", | ||||||
|    "fieldtype": "Section Break", |    "fieldtype": "Section Break", | ||||||
|    "label": "Approvers", |    "label": "Approvers" | ||||||
|    "show_days": 1, |  | ||||||
|    "show_seconds": 1 |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "fieldname": "column_break_45", |    "fieldname": "column_break_45", | ||||||
|    "fieldtype": "Column Break", |    "fieldtype": "Column Break" | ||||||
|    "show_days": 1, |  | ||||||
|    "show_seconds": 1 |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "fieldname": "shift_request_approver", |    "fieldname": "shift_request_approver", | ||||||
|    "fieldtype": "Link", |    "fieldtype": "Link", | ||||||
|    "label": "Shift Request Approver", |    "label": "Shift Request Approver", | ||||||
|    "options": "User", |    "options": "User" | ||||||
|    "show_days": 1, |  | ||||||
|    "show_seconds": 1 |  | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "icon": "fa fa-user", |  "icon": "fa fa-user", | ||||||
|  "idx": 24, |  "idx": 24, | ||||||
|  "image_field": "image", |  "image_field": "image", | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-07-28 01:36:04.109189", |  "modified": "2020-10-06 15:58:23.805489", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "HR", |  "module": "HR", | ||||||
|  "name": "Employee", |  "name": "Employee", | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | 
 | ||||||
| # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||||
| # License: GNU General Public License v3. See license.txt | # License: GNU General Public License v3. See license.txt | ||||||
| 
 | 
 | ||||||
| @ -32,7 +33,7 @@ class HolidayList(Document): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 	def validate_days(self): | 	def validate_days(self): | ||||||
| 		if self.from_date > self.to_date: | 		if getdate(self.from_date) > getdate(self.to_date): | ||||||
| 			throw(_("To Date cannot be before From Date")) | 			throw(_("To Date cannot be before From Date")) | ||||||
| 
 | 
 | ||||||
| 		for day in self.get("holidays"): | 		for day in self.get("holidays"): | ||||||
|  | |||||||
| @ -434,7 +434,8 @@ var get_bom_material_detail= function(doc, cdt, cdn, scrap_items) { | |||||||
| 				"include_item_in_manufacturing": d.include_item_in_manufacturing, | 				"include_item_in_manufacturing": d.include_item_in_manufacturing, | ||||||
| 				"uom": d.uom, | 				"uom": d.uom, | ||||||
| 				"stock_uom": d.stock_uom, | 				"stock_uom": d.stock_uom, | ||||||
| 				"conversion_factor": d.conversion_factor | 				"conversion_factor": d.conversion_factor, | ||||||
|  | 				"sourced_by_supplier": d.sourced_by_supplier | ||||||
| 			}, | 			}, | ||||||
| 			callback: function(r) { | 			callback: function(r) { | ||||||
| 				d = locals[cdt][cdn]; | 				d = locals[cdt][cdn]; | ||||||
| @ -616,6 +617,22 @@ frappe.ui.form.on("BOM Item", "item_code", function(frm, cdt, cdn) { | |||||||
| 	refresh_field("allow_alternative_item", d.name, d.parentfield); | 	refresh_field("allow_alternative_item", d.name, d.parentfield); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | frappe.ui.form.on("BOM Item", "sourced_by_supplier", function(frm, cdt, cdn) { | ||||||
|  | 	var d = locals[cdt][cdn]; | ||||||
|  | 	if (d.sourced_by_supplier) { | ||||||
|  | 		d.rate = 0; | ||||||
|  | 		refresh_field("rate", d.name, d.parentfield); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | frappe.ui.form.on("BOM Item", "rate", function(frm, cdt, cdn) { | ||||||
|  | 	var d = locals[cdt][cdn]; | ||||||
|  | 	if (d.sourced_by_supplier) { | ||||||
|  | 		d.rate = 0; | ||||||
|  | 		refresh_field("rate", d.name, d.parentfield); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| frappe.ui.form.on("BOM Operation", "operations_remove", function(frm) { | frappe.ui.form.on("BOM Operation", "operations_remove", function(frm) { | ||||||
| 	erpnext.bom.calculate_op_cost(frm.doc); | 	erpnext.bom.calculate_op_cost(frm.doc); | ||||||
| 	erpnext.bom.calculate_total(frm.doc); | 	erpnext.bom.calculate_total(frm.doc); | ||||||
|  | |||||||
| @ -137,7 +137,8 @@ class BOM(WebsiteGenerator): | |||||||
| 				"qty": item.qty, | 				"qty": item.qty, | ||||||
| 				"uom": item.uom, | 				"uom": item.uom, | ||||||
| 				"stock_uom": item.stock_uom, | 				"stock_uom": item.stock_uom, | ||||||
| 				"conversion_factor": item.conversion_factor | 				"conversion_factor": item.conversion_factor, | ||||||
|  | 				"sourced_by_supplier": item.sourced_by_supplier | ||||||
| 			}) | 			}) | ||||||
| 			for r in ret: | 			for r in ret: | ||||||
| 				if not item.get(r): | 				if not item.get(r): | ||||||
| @ -172,7 +173,8 @@ class BOM(WebsiteGenerator): | |||||||
| 			 'qty'			: args.get("qty") or args.get("stock_qty") or 1, | 			 'qty'			: args.get("qty") or args.get("stock_qty") or 1, | ||||||
| 			 'stock_qty'	: args.get("qty") or args.get("stock_qty") or 1, | 			 'stock_qty'	: args.get("qty") or args.get("stock_qty") or 1, | ||||||
| 			 'base_rate'	: flt(rate) * (flt(self.conversion_rate) or 1), | 			 'base_rate'	: flt(rate) * (flt(self.conversion_rate) or 1), | ||||||
| 			 'include_item_in_manufacturing': cint(args['transfer_for_manufacture']) or 0 | 			 'include_item_in_manufacturing': cint(args['transfer_for_manufacture']) or 0, | ||||||
|  | 			 'sourced_by_supplier'		: args['sourced_by_supplier'] or 0 | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return ret_item | 		return ret_item | ||||||
| @ -191,8 +193,8 @@ class BOM(WebsiteGenerator): | |||||||
| 		if arg.get('scrap_items'): | 		if arg.get('scrap_items'): | ||||||
| 			rate = get_valuation_rate(arg) | 			rate = get_valuation_rate(arg) | ||||||
| 		elif arg: | 		elif arg: | ||||||
| 			#Customer Provided parts will have zero rate | 			#Customer Provided parts and Supplier sourced parts will have zero rate | ||||||
| 			if not frappe.db.get_value('Item', arg["item_code"], 'is_customer_provided_item'): | 			if not frappe.db.get_value('Item', arg["item_code"], 'is_customer_provided_item') and not arg.get('sourced_by_supplier'): | ||||||
| 				if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom: | 				if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom: | ||||||
| 					rate = flt(self.get_bom_unitcost(arg['bom_no'])) * (arg.get("conversion_factor") or 1) | 					rate = flt(self.get_bom_unitcost(arg['bom_no'])) * (arg.get("conversion_factor") or 1) | ||||||
| 				else: | 				else: | ||||||
| @ -205,7 +207,6 @@ class BOM(WebsiteGenerator): | |||||||
| 						else: | 						else: | ||||||
| 							frappe.msgprint(_("{0} not found for item {1}") | 							frappe.msgprint(_("{0} not found for item {1}") | ||||||
| 								.format(self.rm_cost_as_per, arg["item_code"]), alert=True) | 								.format(self.rm_cost_as_per, arg["item_code"]), alert=True) | ||||||
| 
 |  | ||||||
| 		return flt(rate) * flt(self.plc_conversion_rate or 1) / (self.conversion_rate or 1) | 		return flt(rate) * flt(self.plc_conversion_rate or 1) / (self.conversion_rate or 1) | ||||||
| 
 | 
 | ||||||
| 	def update_cost(self, update_parent=True, from_child_bom=False, save=True): | 	def update_cost(self, update_parent=True, from_child_bom=False, save=True): | ||||||
| @ -221,7 +222,8 @@ class BOM(WebsiteGenerator): | |||||||
| 				"qty": d.qty, | 				"qty": d.qty, | ||||||
| 				"uom": d.uom, | 				"uom": d.uom, | ||||||
| 				"stock_uom": d.stock_uom, | 				"stock_uom": d.stock_uom, | ||||||
| 				"conversion_factor": d.conversion_factor | 				"conversion_factor": d.conversion_factor, | ||||||
|  | 				"sourced_by_supplier": d.sourced_by_supplier | ||||||
| 			}) | 			}) | ||||||
| 
 | 
 | ||||||
| 			if rate: | 			if rate: | ||||||
| @ -495,7 +497,8 @@ class BOM(WebsiteGenerator): | |||||||
| 					'stock_uom'		: d.stock_uom, | 					'stock_uom'		: d.stock_uom, | ||||||
| 					'stock_qty'		: flt(d.stock_qty), | 					'stock_qty'		: flt(d.stock_qty), | ||||||
| 					'rate'			: flt(d.base_rate) / (flt(d.conversion_factor) or 1.0), | 					'rate'			: flt(d.base_rate) / (flt(d.conversion_factor) or 1.0), | ||||||
| 					'include_item_in_manufacturing': d.include_item_in_manufacturing | 					'include_item_in_manufacturing': d.include_item_in_manufacturing, | ||||||
|  | 					'sourced_by_supplier': d.sourced_by_supplier | ||||||
| 				})) | 				})) | ||||||
| 
 | 
 | ||||||
| 	def company_currency(self): | 	def company_currency(self): | ||||||
| @ -521,6 +524,7 @@ class BOM(WebsiteGenerator): | |||||||
| 				bom_item.stock_qty, | 				bom_item.stock_qty, | ||||||
| 				bom_item.rate, | 				bom_item.rate, | ||||||
| 				bom_item.include_item_in_manufacturing, | 				bom_item.include_item_in_manufacturing, | ||||||
|  | 				bom_item.sourced_by_supplier, | ||||||
| 				bom_item.stock_qty / ifnull(bom.quantity, 1) AS qty_consumed_per_unit | 				bom_item.stock_qty / ifnull(bom.quantity, 1) AS qty_consumed_per_unit | ||||||
| 			FROM `tabBOM Explosion Item` bom_item, tabBOM bom | 			FROM `tabBOM Explosion Item` bom_item, tabBOM bom | ||||||
| 			WHERE | 			WHERE | ||||||
| @ -539,7 +543,8 @@ class BOM(WebsiteGenerator): | |||||||
| 				'stock_uom'				: d['stock_uom'], | 				'stock_uom'				: d['stock_uom'], | ||||||
| 				'stock_qty'				: d['qty_consumed_per_unit'] * stock_qty, | 				'stock_qty'				: d['qty_consumed_per_unit'] * stock_qty, | ||||||
| 				'rate'					: flt(d['rate']), | 				'rate'					: flt(d['rate']), | ||||||
| 				'include_item_in_manufacturing': d.get('include_item_in_manufacturing', 0) | 				'include_item_in_manufacturing': d.get('include_item_in_manufacturing', 0), | ||||||
|  | 				'sourced_by_supplier': d.get('sourced_by_supplier', 0) | ||||||
| 			})) | 			})) | ||||||
| 
 | 
 | ||||||
| 	def add_exploded_items(self): | 	def add_exploded_items(self): | ||||||
| @ -679,7 +684,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite | |||||||
| 			is_stock_item=is_stock_item, | 			is_stock_item=is_stock_item, | ||||||
| 			qty_field="stock_qty", | 			qty_field="stock_qty", | ||||||
| 			select_columns = """, bom_item.source_warehouse, bom_item.operation, | 			select_columns = """, bom_item.source_warehouse, bom_item.operation, | ||||||
| 				bom_item.include_item_in_manufacturing, bom_item.description, bom_item.rate, | 				bom_item.include_item_in_manufacturing, bom_item.description, bom_item.rate, bom_item.sourced_by_supplier, | ||||||
| 				(Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""") | 				(Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""") | ||||||
| 
 | 
 | ||||||
| 		items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True) | 		items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True) | ||||||
| @ -692,7 +697,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite | |||||||
| 		query = query.format(table="BOM Item", where_conditions="", is_stock_item=is_stock_item, | 		query = query.format(table="BOM Item", where_conditions="", is_stock_item=is_stock_item, | ||||||
| 			qty_field="stock_qty" if fetch_qty_in_stock_uom else "qty", | 			qty_field="stock_qty" if fetch_qty_in_stock_uom else "qty", | ||||||
| 			select_columns = """, bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse, | 			select_columns = """, bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse, | ||||||
| 				bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing, | 				bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing, bom_item.sourced_by_supplier, | ||||||
| 				bom_item.description, bom_item.base_rate as rate """) | 				bom_item.description, bom_item.base_rate as rate """) | ||||||
| 		items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True) | 		items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -10,6 +10,8 @@ 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 | ||||||
| from six import string_types | from six import string_types | ||||||
|  | from erpnext.stock.doctype.item.test_item import make_item | ||||||
|  | from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order | ||||||
| 
 | 
 | ||||||
| test_records = frappe.get_test_records('BOM') | test_records = frappe.get_test_records('BOM') | ||||||
| 
 | 
 | ||||||
| @ -138,6 +140,73 @@ class TestBOM(unittest.TestCase): | |||||||
| 
 | 
 | ||||||
| 		self.assertEqual(bom.items[0].rate, 20) | 		self.assertEqual(bom.items[0].rate, 20) | ||||||
| 
 | 
 | ||||||
|  | 	def test_subcontractor_sourced_item(self): | ||||||
|  | 		item_code = "_Test Subcontracted FG Item 1" | ||||||
|  | 
 | ||||||
|  | 		if not frappe.db.exists('Item', item_code): | ||||||
|  | 			make_item(item_code, { | ||||||
|  | 				'is_stock_item': 1, | ||||||
|  | 				'is_sub_contracted_item': 1, | ||||||
|  | 				'stock_uom': 'Nos' | ||||||
|  | 			}) | ||||||
|  | 
 | ||||||
|  | 		if not frappe.db.exists('Item', "Test Extra Item 1"): | ||||||
|  | 			make_item("Test Extra Item 1", { | ||||||
|  | 				'is_stock_item': 1, | ||||||
|  | 				'stock_uom': 'Nos' | ||||||
|  | 			}) | ||||||
|  | 
 | ||||||
|  | 		if not frappe.db.exists('Item', "Test Extra Item 2"): | ||||||
|  | 			make_item("Test Extra Item 2", { | ||||||
|  | 				'is_stock_item': 1, | ||||||
|  | 				'stock_uom': 'Nos' | ||||||
|  | 			}) | ||||||
|  | 
 | ||||||
|  | 		if not frappe.db.exists('Item', "Test Extra Item 3"): | ||||||
|  | 			make_item("Test Extra Item 3", { | ||||||
|  | 				'is_stock_item': 1, | ||||||
|  | 				'stock_uom': 'Nos' | ||||||
|  | 			}) | ||||||
|  | 		bom = frappe.get_doc({ | ||||||
|  | 			'doctype': 'BOM', | ||||||
|  | 			'is_default': 1, | ||||||
|  | 			'item': item_code, | ||||||
|  | 			'currency': 'USD', | ||||||
|  | 			'quantity': 1, | ||||||
|  | 			'company': '_Test Company' | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		for item in ["Test Extra Item 1", "Test Extra Item 2"]: | ||||||
|  | 			item_doc = frappe.get_doc('Item', item) | ||||||
|  | 
 | ||||||
|  | 			bom.append('items', { | ||||||
|  | 				'item_code': item, | ||||||
|  | 				'qty': 1, | ||||||
|  | 				'uom': item_doc.stock_uom, | ||||||
|  | 				'stock_uom': item_doc.stock_uom, | ||||||
|  | 				'rate': item_doc.valuation_rate | ||||||
|  | 			}) | ||||||
|  | 
 | ||||||
|  | 		bom.append('items', { | ||||||
|  | 			'item_code': "Test Extra Item 3", | ||||||
|  | 			'qty': 1, | ||||||
|  | 			'uom': item_doc.stock_uom, | ||||||
|  | 			'stock_uom': item_doc.stock_uom, | ||||||
|  | 			'rate': 0, | ||||||
|  | 			'sourced_by_supplier': 1 | ||||||
|  | 		}) | ||||||
|  | 		bom.insert(ignore_permissions=True) | ||||||
|  | 		bom.update_cost() | ||||||
|  | 		bom.submit() | ||||||
|  | 		# test that sourced_by_supplier rate is zero even after updating cost | ||||||
|  | 		self.assertEqual(bom.items[2].rate, 0) | ||||||
|  | 		# test in Purchase Order sourced_by_supplier is not added to Supplied Item | ||||||
|  | 		po = create_purchase_order(item_code=item_code, qty=1, | ||||||
|  | 			is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") | ||||||
|  | 		bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1]) | ||||||
|  | 		supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) | ||||||
|  | 		self.assertEquals(bom_items, supplied_items) | ||||||
|  | 
 | ||||||
| def get_default_bom(item_code="_Test FG Item 2"): | def get_default_bom(item_code="_Test FG Item 2"): | ||||||
| 	return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) | 	return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,626 +1,181 @@ | |||||||
| { | { | ||||||
|  "allow_copy": 0,  |  "actions": [], | ||||||
|  "allow_events_in_timeline": 0,  |  "autoname": "hash", | ||||||
|  "allow_guest_to_view": 0,  |  "creation": "2013-03-07 11:42:57", | ||||||
|  "allow_import": 0,  |  "doctype": "DocType", | ||||||
|  "allow_rename": 0,  |  "document_type": "Setup", | ||||||
|  "autoname": "hash",  |  "editable_grid": 1, | ||||||
|  "beta": 0,  |  "engine": "InnoDB", | ||||||
|  "creation": "2013-03-07 11:42:57",  |  "field_order": [ | ||||||
|  "custom": 0,  |   "item_code", | ||||||
|  "docstatus": 0,  |   "item_name", | ||||||
|  "doctype": "DocType",  |   "cb", | ||||||
|  "document_type": "Setup",  |   "source_warehouse", | ||||||
|  "editable_grid": 1,  |   "operation", | ||||||
|  "engine": "InnoDB",  |   "section_break_3", | ||||||
|  |   "description", | ||||||
|  |   "column_break_2", | ||||||
|  |   "image", | ||||||
|  |   "image_view", | ||||||
|  |   "section_break_4", | ||||||
|  |   "stock_qty", | ||||||
|  |   "rate", | ||||||
|  |   "qty_consumed_per_unit", | ||||||
|  |   "column_break_8", | ||||||
|  |   "stock_uom", | ||||||
|  |   "amount", | ||||||
|  |   "include_item_in_manufacturing", | ||||||
|  |   "sourced_by_supplier" | ||||||
|  |  ], | ||||||
|  "fields": [ |  "fields": [ | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "item_code", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Link", | ||||||
|    "allow_on_submit": 0,  |    "in_global_search": 1, | ||||||
|    "bold": 0,  |    "in_list_view": 1, | ||||||
|    "collapsible": 0,  |    "label": "Item Code", | ||||||
|    "columns": 0,  |    "oldfieldname": "item_code", | ||||||
|    "fieldname": "item_code",  |    "oldfieldtype": "Link", | ||||||
|    "fieldtype": "Link",  |    "options": "Item", | ||||||
|    "hidden": 0,  |    "read_only": 1 | ||||||
|    "ignore_user_permissions": 0,  |   }, | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 1,  |  | ||||||
|    "in_list_view": 1,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Item Code",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "oldfieldname": "item_code",  |  | ||||||
|    "oldfieldtype": "Link",  |  | ||||||
|    "options": "Item",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "item_name", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Data", | ||||||
|    "allow_on_submit": 0,  |    "in_global_search": 1, | ||||||
|    "bold": 0,  |    "in_list_view": 1, | ||||||
|    "collapsible": 0,  |    "label": "Item Name", | ||||||
|    "columns": 0,  |    "read_only": 1 | ||||||
|    "fieldname": "item_name",  |   }, | ||||||
|    "fieldtype": "Data",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 1,  |  | ||||||
|    "in_list_view": 1,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Item Name",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "cb", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Column Break" | ||||||
|    "allow_on_submit": 0,  |   }, | ||||||
|    "bold": 0,  |  | ||||||
|    "collapsible": 0,  |  | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "cb",  |  | ||||||
|    "fieldtype": "Column Break",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "source_warehouse", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Link", | ||||||
|    "allow_on_submit": 0,  |    "label": "Source Warehouse", | ||||||
|    "bold": 0,  |    "options": "Warehouse", | ||||||
|    "collapsible": 0,  |    "read_only": 1 | ||||||
|    "columns": 0,  |   }, | ||||||
|    "fieldname": "source_warehouse",  |  | ||||||
|    "fieldtype": "Link",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Source Warehouse",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "Warehouse",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "operation", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Link", | ||||||
|    "allow_on_submit": 0,  |    "label": "Operation", | ||||||
|    "bold": 0,  |    "options": "Operation", | ||||||
|    "collapsible": 0,  |    "read_only": 1 | ||||||
|    "columns": 0,  |   }, | ||||||
|    "fieldname": "operation",  |  | ||||||
|    "fieldtype": "Link",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Operation",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "Operation",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "section_break_3", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Section Break" | ||||||
|    "allow_on_submit": 0,  |   }, | ||||||
|    "bold": 0,  |  | ||||||
|    "collapsible": 0,  |  | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "section_break_3",  |  | ||||||
|    "fieldtype": "Section Break",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "description", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Text Editor", | ||||||
|    "allow_on_submit": 0,  |    "in_list_view": 1, | ||||||
|    "bold": 0,  |    "label": "Description", | ||||||
|    "collapsible": 0,  |    "oldfieldname": "description", | ||||||
|    "columns": 0,  |    "oldfieldtype": "Text", | ||||||
|    "fieldname": "description",  |    "print_width": "300px", | ||||||
|    "fieldtype": "Text Editor",  |    "read_only": 1, | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 1,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Description",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "oldfieldname": "description",  |  | ||||||
|    "oldfieldtype": "Text",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "print_width": "300px",  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0,  |  | ||||||
|    "width": "300px" |    "width": "300px" | ||||||
|   },  |   }, | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "column_break_2", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Column Break" | ||||||
|    "allow_on_submit": 0,  |   }, | ||||||
|    "bold": 0,  |  | ||||||
|    "collapsible": 0,  |  | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "column_break_2",  |  | ||||||
|    "fieldtype": "Column Break",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "image", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Attach", | ||||||
|    "allow_on_submit": 0,  |    "hidden": 1, | ||||||
|    "bold": 0,  |    "label": "Image", | ||||||
|    "collapsible": 0,  |    "print_hide": 1 | ||||||
|    "columns": 0,  |   }, | ||||||
|    "fieldname": "image",  |  | ||||||
|    "fieldtype": "Attach",  |  | ||||||
|    "hidden": 1,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Image",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 1,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "image_view", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Image", | ||||||
|    "allow_on_submit": 0,  |    "label": "Image View", | ||||||
|    "bold": 0,  |    "options": "image" | ||||||
|    "collapsible": 0,  |   }, | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "image_view",  |  | ||||||
|    "fieldtype": "Image",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Image View",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "options": "image",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "section_break_4", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Section Break" | ||||||
|    "allow_on_submit": 0,  |   }, | ||||||
|    "bold": 0,  |  | ||||||
|    "collapsible": 0,  |  | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "section_break_4",  |  | ||||||
|    "fieldtype": "Section Break",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "stock_qty", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Float", | ||||||
|    "allow_on_submit": 0,  |    "in_list_view": 1, | ||||||
|    "bold": 0,  |    "label": "Stock Qty", | ||||||
|    "collapsible": 0,  |    "oldfieldname": "qty", | ||||||
|    "columns": 0,  |    "oldfieldtype": "Currency", | ||||||
|    "fieldname": "stock_qty",  |    "read_only": 1 | ||||||
|    "fieldtype": "Float",  |   }, | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 1,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Stock Qty",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "oldfieldname": "qty",  |  | ||||||
|    "oldfieldtype": "Currency",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "rate", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Currency", | ||||||
|    "allow_on_submit": 0,  |    "in_list_view": 1, | ||||||
|    "bold": 0,  |    "label": "Rate", | ||||||
|    "collapsible": 0,  |    "oldfieldname": "standard_rate", | ||||||
|    "columns": 0,  |    "oldfieldtype": "Currency", | ||||||
|    "fieldname": "rate",  |    "options": "Company:company:default_currency", | ||||||
|    "fieldtype": "Currency",  |    "read_only": 1 | ||||||
|    "hidden": 0,  |   }, | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 1,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Rate",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "oldfieldname": "standard_rate",  |  | ||||||
|    "oldfieldtype": "Currency",  |  | ||||||
|    "options": "Company:company:default_currency",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "qty_consumed_per_unit", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Float", | ||||||
|    "allow_on_submit": 0,  |    "in_list_view": 1, | ||||||
|    "bold": 0,  |    "label": "Qty Consumed Per Unit", | ||||||
|    "collapsible": 0,  |    "read_only": 1 | ||||||
|    "columns": 0,  |   }, | ||||||
|    "fieldname": "qty_consumed_per_unit",  |  | ||||||
|    "fieldtype": "Float",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 1,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Qty Consumed Per Unit",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "column_break_8", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Column Break" | ||||||
|    "allow_on_submit": 0,  |   }, | ||||||
|    "bold": 0,  |  | ||||||
|    "collapsible": 0,  |  | ||||||
|    "columns": 0,  |  | ||||||
|    "fieldname": "column_break_8",  |  | ||||||
|    "fieldtype": "Column Break",  |  | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 0,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "stock_uom", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Link", | ||||||
|    "allow_on_submit": 0,  |    "label": "Stock UOM", | ||||||
|    "bold": 0,  |    "oldfieldname": "stock_uom", | ||||||
|    "collapsible": 0,  |    "oldfieldtype": "Link", | ||||||
|    "columns": 0,  |    "options": "UOM", | ||||||
|    "fieldname": "stock_uom",  |    "read_only": 1 | ||||||
|    "fieldtype": "Link",  |   }, | ||||||
|    "hidden": 0,  |  | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Stock UOM",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "oldfieldname": "stock_uom",  |  | ||||||
|    "oldfieldtype": "Link",  |  | ||||||
|    "options": "UOM",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "fieldname": "amount", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldtype": "Currency", | ||||||
|    "allow_on_submit": 0,  |    "in_list_view": 1, | ||||||
|    "bold": 0,  |    "label": "Amount", | ||||||
|    "collapsible": 0,  |    "oldfieldname": "amount_as_per_sr", | ||||||
|    "columns": 0,  |    "oldfieldtype": "Currency", | ||||||
|    "fieldname": "amount",  |    "options": "Company:company:default_currency", | ||||||
|    "fieldtype": "Currency",  |    "read_only": 1 | ||||||
|    "hidden": 0,  |   }, | ||||||
|    "ignore_user_permissions": 0,  |  | ||||||
|    "ignore_xss_filter": 0,  |  | ||||||
|    "in_filter": 0,  |  | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 1,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Amount",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "oldfieldname": "amount_as_per_sr",  |  | ||||||
|    "oldfieldtype": "Currency",  |  | ||||||
|    "options": "Company:company:default_currency",  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "allow_bulk_edit": 0,  |    "default": "0", | ||||||
|    "allow_in_quick_entry": 0,  |    "fieldname": "include_item_in_manufacturing", | ||||||
|    "allow_on_submit": 0,  |    "fieldtype": "Check", | ||||||
|    "bold": 0,  |    "label": "Include Item In Manufacturing", | ||||||
|    "collapsible": 0,  |    "read_only": 1 | ||||||
|    "columns": 0,  |   }, | ||||||
|    "fieldname": "include_item_in_manufacturing",  |   { | ||||||
|    "fieldtype": "Check",  |    "default": "0", | ||||||
|    "hidden": 0,  |    "fieldname": "sourced_by_supplier", | ||||||
|    "ignore_user_permissions": 0,  |    "fieldtype": "Check", | ||||||
|    "ignore_xss_filter": 0,  |    "label": "Sourced by Supplier", | ||||||
|    "in_filter": 0,  |    "read_only": 1 | ||||||
|    "in_global_search": 0,  |  | ||||||
|    "in_list_view": 0,  |  | ||||||
|    "in_standard_filter": 0,  |  | ||||||
|    "label": "Include Item In Manufacturing",  |  | ||||||
|    "length": 0,  |  | ||||||
|    "no_copy": 0,  |  | ||||||
|    "permlevel": 0,  |  | ||||||
|    "precision": "",  |  | ||||||
|    "print_hide": 0,  |  | ||||||
|    "print_hide_if_no_value": 0,  |  | ||||||
|    "read_only": 1,  |  | ||||||
|    "remember_last_selected_value": 0,  |  | ||||||
|    "report_hide": 0,  |  | ||||||
|    "reqd": 0,  |  | ||||||
|    "search_index": 0,  |  | ||||||
|    "set_only_once": 0,  |  | ||||||
|    "translatable": 0,  |  | ||||||
|    "unique": 0 |  | ||||||
|   } |   } | ||||||
|  ],  |  ], | ||||||
|  "has_web_view": 0,  |  "idx": 1, | ||||||
|  "hide_heading": 0,  |  "index_web_pages_for_search": 1, | ||||||
|  "hide_toolbar": 0,  |  "istable": 1, | ||||||
|  "idx": 1,  |  "links": [], | ||||||
|  "image_view": 0,  |  "modified": "2020-10-08 16:21:29.386212", | ||||||
|  "in_create": 0,  |  "modified_by": "Administrator", | ||||||
|  "is_submittable": 0,  |  "module": "Manufacturing", | ||||||
|  "issingle": 0,  |  "name": "BOM Explosion Item", | ||||||
|  "istable": 1,  |  "owner": "Administrator", | ||||||
|  "max_attachments": 0,  |  "permissions": [], | ||||||
|  "modified": "2018-11-20 19:04:59.813773",  |  "sort_field": "modified", | ||||||
|  "modified_by": "Administrator",  |  "sort_order": "DESC", | ||||||
|  "module": "Manufacturing",  |  "track_changes": 1 | ||||||
|  "name": "BOM Explosion Item",  |  | ||||||
|  "owner": "Administrator",  |  | ||||||
|  "permissions": [],  |  | ||||||
|  "quick_entry": 0,  |  | ||||||
|  "read_only": 0,  |  | ||||||
|  "read_only_onload": 0,  |  | ||||||
|  "show_name_in_global_search": 0,  |  | ||||||
|  "track_changes": 1,  |  | ||||||
|  "track_seen": 0,  |  | ||||||
|  "track_views": 0 |  | ||||||
| } | } | ||||||
| @ -37,7 +37,9 @@ | |||||||
|   "section_break_27", |   "section_break_27", | ||||||
|   "has_variants", |   "has_variants", | ||||||
|   "include_item_in_manufacturing", |   "include_item_in_manufacturing", | ||||||
|   "original_item" |   "original_item", | ||||||
|  |   "column_break_33", | ||||||
|  |   "sourced_by_supplier" | ||||||
|  ], |  ], | ||||||
|  "fields": [ |  "fields": [ | ||||||
|   { |   { | ||||||
| @ -272,12 +274,23 @@ | |||||||
|    "no_copy": 1, |    "no_copy": 1, | ||||||
|    "print_hide": 1, |    "print_hide": 1, | ||||||
|    "read_only": 1 |    "read_only": 1 | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "fieldname": "column_break_33", | ||||||
|  |    "fieldtype": "Column Break" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "default": "0", | ||||||
|  |    "fieldname": "sourced_by_supplier", | ||||||
|  |    "fieldtype": "Check", | ||||||
|  |    "label": "Sourced by Supplier" | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "idx": 1, |  "idx": 1, | ||||||
|  |  "index_web_pages_for_search": 1, | ||||||
|  "istable": 1, |  "istable": 1, | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-04-09 14:30:26.535546", |  "modified": "2020-10-08 14:19:37.563300", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Manufacturing", |  "module": "Manufacturing", | ||||||
|  "name": "BOM Item", |  "name": "BOM Item", | ||||||
|  | |||||||
| @ -381,7 +381,6 @@ class ProductionPlan(Document): | |||||||
| 					"transaction_date": nowdate(), | 					"transaction_date": nowdate(), | ||||||
| 					"status": "Draft", | 					"status": "Draft", | ||||||
| 					"company": self.company, | 					"company": self.company, | ||||||
| 					"requested_by": frappe.session.user, |  | ||||||
| 					'material_request_type': material_request_type, | 					'material_request_type': material_request_type, | ||||||
| 					'customer': item_doc.customer or '' | 					'customer': item_doc.customer or '' | ||||||
| 				}) | 				}) | ||||||
|  | |||||||
| @ -434,7 +434,7 @@ class WorkOrder(Document): | |||||||
| 			elif flt(d.completed_qty) <= max_allowed_qty_for_wo: | 			elif flt(d.completed_qty) <= max_allowed_qty_for_wo: | ||||||
| 				d.status = "Completed" | 				d.status = "Completed" | ||||||
| 			else: | 			else: | ||||||
| 				frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'")) | 				frappe.throw(_("Completed Qty cannot be greater than 'Qty to Manufacture'")) | ||||||
| 
 | 
 | ||||||
| 	def set_actual_dates(self): | 	def set_actual_dates(self): | ||||||
| 		if self.get("operations"): | 		if self.get("operations"): | ||||||
|  | |||||||
| @ -729,3 +729,4 @@ erpnext.patches.v13_0.setting_custom_roles_for_some_regional_reports | |||||||
| erpnext.patches.v13_0.rename_issue_doctype_fields | erpnext.patches.v13_0.rename_issue_doctype_fields | ||||||
| erpnext.patches.v13_0.change_default_pos_print_format | erpnext.patches.v13_0.change_default_pos_print_format | ||||||
| erpnext.patches.v13_0.set_youtube_video_id | erpnext.patches.v13_0.set_youtube_video_id | ||||||
|  | erpnext.patches.v13_0.print_uom_after_quantity_patch | ||||||
							
								
								
									
										10
									
								
								erpnext/patches/v13_0/print_uom_after_quantity_patch.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								erpnext/patches/v13_0/print_uom_after_quantity_patch.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | # Copyright (c) 2019, Frappe and Contributors | ||||||
|  | # License: GNU General Public License v3. See license.txt | ||||||
|  | 
 | ||||||
|  | from __future__ import unicode_literals | ||||||
|  | 
 | ||||||
|  | import frappe | ||||||
|  | from erpnext.setup.install import create_print_uom_after_qty_custom_field | ||||||
|  | 
 | ||||||
|  | def execute(): | ||||||
|  |     create_print_uom_after_qty_custom_field() | ||||||
| @ -217,7 +217,7 @@ | |||||||
|    "fieldname": "help", |    "fieldname": "help", | ||||||
|    "fieldtype": "HTML", |    "fieldtype": "HTML", | ||||||
|    "label": "Help", |    "label": "Help", | ||||||
|    "options": "<h3>Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base < 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS > 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>" |    "options": "<h3>Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condition. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base < 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS > 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "default": "0", |    "default": "0", | ||||||
| @ -238,14 +238,13 @@ | |||||||
|    "depends_on": "eval:doc.type == \"Deduction\"", |    "depends_on": "eval:doc.type == \"Deduction\"", | ||||||
|    "fieldname": "is_income_tax_component", |    "fieldname": "is_income_tax_component", | ||||||
|    "fieldtype": "Check", |    "fieldtype": "Check", | ||||||
|    "label": "Is Income Tax Component", |    "label": "Is Income Tax Component" | ||||||
|    "show_days": 1, |  | ||||||
|    "show_seconds": 1 |  | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "icon": "fa fa-flag", |  "icon": "fa fa-flag", | ||||||
|  |  "index_web_pages_for_search": 1, | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-06-22 15:39:20.826565", |  "modified": "2020-10-07 20:38:33.795853", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Payroll", |  "module": "Payroll", | ||||||
|  "name": "Salary Component", |  "name": "Salary Component", | ||||||
|  | |||||||
| @ -117,7 +117,7 @@ | |||||||
|    "depends_on": "eval:doc.is_flexible_benefit != 1", |    "depends_on": "eval:doc.is_flexible_benefit != 1", | ||||||
|    "fieldname": "section_break_2", |    "fieldname": "section_break_2", | ||||||
|    "fieldtype": "Section Break", |    "fieldtype": "Section Break", | ||||||
|    "label": "Condtion and formula" |    "label": "Condition and formula" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "allow_on_submit": 1, |    "allow_on_submit": 1, | ||||||
| @ -206,38 +206,28 @@ | |||||||
|    "collapsible": 1, |    "collapsible": 1, | ||||||
|    "fieldname": "section_break_5", |    "fieldname": "section_break_5", | ||||||
|    "fieldtype": "Section Break", |    "fieldtype": "Section Break", | ||||||
|    "label": "Component properties and references ", |    "label": "Component properties and references " | ||||||
|    "show_days": 1, |  | ||||||
|    "show_seconds": 1 |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "fieldname": "column_break_11", |    "fieldname": "column_break_11", | ||||||
|    "fieldtype": "Column Break", |    "fieldtype": "Column Break" | ||||||
|    "show_days": 1, |  | ||||||
|    "show_seconds": 1 |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "fieldname": "section_break_19", |    "fieldname": "section_break_19", | ||||||
|    "fieldtype": "Section Break", |    "fieldtype": "Section Break" | ||||||
|    "show_days": 1, |  | ||||||
|    "show_seconds": 1 |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "fieldname": "column_break_18", |    "fieldname": "column_break_18", | ||||||
|    "fieldtype": "Column Break", |    "fieldtype": "Column Break" | ||||||
|    "show_days": 1, |  | ||||||
|    "show_seconds": 1 |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "fieldname": "column_break_24", |    "fieldname": "column_break_24", | ||||||
|    "fieldtype": "Column Break", |    "fieldtype": "Column Break" | ||||||
|    "show_days": 1, |  | ||||||
|    "show_seconds": 1 |  | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "istable": 1, |  "istable": 1, | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-07-01 12:13:41.956495", |  "modified": "2020-10-07 20:39:41.619283", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Payroll", |  "module": "Payroll", | ||||||
|  "name": "Salary Detail", |  "name": "Salary Detail", | ||||||
|  | |||||||
| @ -347,8 +347,7 @@ class TestSalarySlip(unittest.TestCase): | |||||||
| 
 | 
 | ||||||
| 		# create additional salary of 150000 | 		# create additional salary of 150000 | ||||||
| 		frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee)) | 		frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee)) | ||||||
| 		data["additional-1"] = create_additional_salary(employee, payroll_period, 50000) | 		data["additional-1"] = create_additional_salary(employee, payroll_period, 150000) | ||||||
| 		data["additional-2"] = create_additional_salary(employee, payroll_period, 100000) |  | ||||||
| 		data["deducted_dates"] = create_salary_slips_for_payroll_period(employee, | 		data["deducted_dates"] = create_salary_slips_for_payroll_period(employee, | ||||||
| 			salary_structure.name, payroll_period) | 			salary_structure.name, payroll_period) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -136,6 +136,7 @@ def get_timesheet_details(filters, timesheet_list): | |||||||
| 	return timesheet_details_map | 	return timesheet_details_map | ||||||
| 
 | 
 | ||||||
| def get_billable_and_total_duration(activity, start_time, end_time): | def get_billable_and_total_duration(activity, start_time, end_time): | ||||||
|  | 	precision = frappe.get_precision("Timesheet Detail", "hours") | ||||||
| 	activity_duration = time_diff_in_hours(end_time, start_time) | 	activity_duration = time_diff_in_hours(end_time, start_time) | ||||||
| 	billing_duration = 0.0 | 	billing_duration = 0.0 | ||||||
| 	if activity.billable: | 	if activity.billable: | ||||||
| @ -143,4 +144,4 @@ def get_billable_and_total_duration(activity, start_time, end_time): | |||||||
| 		if activity_duration != activity.billing_hours: | 		if activity_duration != activity.billing_hours: | ||||||
| 			billing_duration = activity_duration * activity.billing_hours / activity.hours | 			billing_duration = activity_duration * activity.billing_hours / activity.hours | ||||||
| 
 | 
 | ||||||
| 	return flt(activity_duration, 2), flt(billing_duration, 2) | 	return flt(activity_duration, precision), flt(billing_duration, precision) | ||||||
| @ -309,7 +309,6 @@ erpnext.setup.fiscal_years = { | |||||||
| 	"Hong Kong": ["04-01", "03-31"], | 	"Hong Kong": ["04-01", "03-31"], | ||||||
| 	"India": ["04-01", "03-31"], | 	"India": ["04-01", "03-31"], | ||||||
| 	"Iran": ["06-23", "06-22"], | 	"Iran": ["06-23", "06-22"], | ||||||
| 	"Italy": ["07-01", "06-30"], |  | ||||||
| 	"Myanmar": ["04-01", "03-31"], | 	"Myanmar": ["04-01", "03-31"], | ||||||
| 	"New Zealand": ["04-01", "03-31"], | 	"New Zealand": ["04-01", "03-31"], | ||||||
| 	"Pakistan": ["07-01", "06-30"], | 	"Pakistan": ["07-01", "06-30"], | ||||||
|  | |||||||
| @ -452,6 +452,9 @@ erpnext.utils.update_child_items = function(opts) { | |||||||
| 	const frm = opts.frm; | 	const frm = opts.frm; | ||||||
| 	const cannot_add_row = (typeof opts.cannot_add_row === 'undefined') ? true : opts.cannot_add_row; | 	const cannot_add_row = (typeof opts.cannot_add_row === 'undefined') ? true : opts.cannot_add_row; | ||||||
| 	const child_docname = (typeof opts.cannot_add_row === 'undefined') ? "items" : opts.child_docname; | 	const child_docname = (typeof opts.cannot_add_row === 'undefined') ? "items" : opts.child_docname; | ||||||
|  | 	const child_meta = frappe.get_meta(`${frm.doc.doctype} Item`); | ||||||
|  | 	const get_precision = (fieldname) => child_meta.fields.find(f => f.fieldname == fieldname).precision; | ||||||
|  | 
 | ||||||
| 	this.data = []; | 	this.data = []; | ||||||
| 	const fields = [{ | 	const fields = [{ | ||||||
| 		fieldtype:'Data', | 		fieldtype:'Data', | ||||||
| @ -499,14 +502,16 @@ erpnext.utils.update_child_items = function(opts) { | |||||||
| 		default: 0, | 		default: 0, | ||||||
| 		read_only: 0, | 		read_only: 0, | ||||||
| 		in_list_view: 1, | 		in_list_view: 1, | ||||||
| 		label: __('Qty') | 		label: __('Qty'), | ||||||
|  | 		precision: get_precision("qty") | ||||||
| 	}, { | 	}, { | ||||||
| 		fieldtype:'Currency', | 		fieldtype:'Currency', | ||||||
| 		fieldname:"rate", | 		fieldname:"rate", | ||||||
| 		default: 0, | 		default: 0, | ||||||
| 		read_only: 0, | 		read_only: 0, | ||||||
| 		in_list_view: 1, | 		in_list_view: 1, | ||||||
| 		label: __('Rate') | 		label: __('Rate'), | ||||||
|  | 		precision: get_precision("rate") | ||||||
| 	}]; | 	}]; | ||||||
| 
 | 
 | ||||||
| 	if (frm.doc.doctype == 'Sales Order' || frm.doc.doctype == 'Purchase Order' ) { | 	if (frm.doc.doctype == 'Sales Order' || frm.doc.doctype == 'Purchase Order' ) { | ||||||
| @ -521,7 +526,8 @@ erpnext.utils.update_child_items = function(opts) { | |||||||
| 			fieldtype: 'Float', | 			fieldtype: 'Float', | ||||||
| 			fieldname: "conversion_factor", | 			fieldname: "conversion_factor", | ||||||
| 			in_list_view: 1, | 			in_list_view: 1, | ||||||
| 			label: __("Conversion Factor") | 			label: __("Conversion Factor"), | ||||||
|  | 			precision: get_precision('conversion_factor') | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -697,9 +703,13 @@ erpnext.utils.map_current_doc = function(opts) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| frappe.form.link_formatters['Item'] = function(value, doc) { | frappe.form.link_formatters['Item'] = function(value, doc) { | ||||||
| 	if(doc && doc.item_name && doc.item_name !== value) { | 	if (doc && value && doc.item_name && doc.item_name !== value) { | ||||||
| 		return value? value + ': ' + doc.item_name: doc.item_name; | 		return value + ': ' + doc.item_name; | ||||||
|  | 	} else if (!value && doc.doctype && doc.item_name) { | ||||||
|  | 		// format blank value in child table
 | ||||||
|  | 		return doc.item_name; | ||||||
| 	} else { | 	} else { | ||||||
|  | 		// if value is blank in report view or item code and name are the same, return as is
 | ||||||
| 		return value; | 		return value; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -10,5 +10,13 @@ frappe.ui.form.on('Quality Procedure', { | |||||||
| 				} | 				} | ||||||
| 			}; | 			}; | ||||||
| 		}); | 		}); | ||||||
|  | 
 | ||||||
|  | 		frm.set_query('parent_quality_procedure', function(){ | ||||||
|  | 			return { | ||||||
|  | 				filters: { | ||||||
|  | 					is_group: 1 | ||||||
|  | 				} | ||||||
|  | 			}; | ||||||
|  | 		}); | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| @ -21,8 +21,7 @@ | |||||||
|    "fieldname": "parent_quality_procedure", |    "fieldname": "parent_quality_procedure", | ||||||
|    "fieldtype": "Link", |    "fieldtype": "Link", | ||||||
|    "label": "Parent Procedure", |    "label": "Parent Procedure", | ||||||
|    "options": "Quality Procedure", |    "options": "Quality Procedure" | ||||||
|    "read_only": 1 |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "default": "0", |    "default": "0", | ||||||
| @ -73,7 +72,7 @@ | |||||||
|  ], |  ], | ||||||
|  "is_tree": 1, |  "is_tree": 1, | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-06-17 17:25:03.434953", |  "modified": "2020-10-13 11:46:07.744194", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Quality Management", |  "module": "Quality Management", | ||||||
|  "name": "Quality Procedure", |  "name": "Quality Procedure", | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
| 
 | 
 | ||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| import frappe | import frappe | ||||||
| from frappe.utils.nestedset import NestedSet | from frappe.utils.nestedset import NestedSet, rebuild_tree | ||||||
| from frappe import _ | from frappe import _ | ||||||
| 
 | 
 | ||||||
| class QualityProcedure(NestedSet): | class QualityProcedure(NestedSet): | ||||||
| @ -42,6 +42,8 @@ class QualityProcedure(NestedSet): | |||||||
| 			doc.save(ignore_permissions=True) | 			doc.save(ignore_permissions=True) | ||||||
| 
 | 
 | ||||||
| 	def set_parent(self): | 	def set_parent(self): | ||||||
|  | 		rebuild_tree('Quality Procedure', 'parent_quality_procedure') | ||||||
|  | 
 | ||||||
| 		for process in self.processes: | 		for process in self.processes: | ||||||
| 			# Set parent for only those children who don't have a parent | 			# Set parent for only those children who don't have a parent | ||||||
| 			parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure") | 			parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure") | ||||||
|  | |||||||
| @ -0,0 +1,4 @@ | |||||||
|  | {% if address_line1 %}{{ address_line1 }}<br>{% endif -%} | ||||||
|  | {% if address_line2 %}{{ address_line2 }}<br>{% endif -%} | ||||||
|  | {% if pincode %}L-{{ pincode }}{% endif -%}{% if city %} {{ city }}{% endif %}<br> | ||||||
|  | {% if country %}{{ country | upper }}{% endif %} | ||||||
							
								
								
									
										57
									
								
								erpnext/regional/germany/accounts_controller.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								erpnext/regional/germany/accounts_controller.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | |||||||
|  | import frappe | ||||||
|  | from frappe import _ | ||||||
|  | from frappe import msgprint | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | REQUIRED_FIELDS = { | ||||||
|  | 	"Sales Invoice": [ | ||||||
|  | 		{ | ||||||
|  | 			"field_name": "company_address", | ||||||
|  | 			"regulation": "§ 14 Abs. 4 Nr. 1 UStG" | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"field_name": "company_tax_id", | ||||||
|  | 			"regulation": "§ 14 Abs. 4 Nr. 2 UStG" | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"field_name": "taxes", | ||||||
|  | 			"regulation": "§ 14 Abs. 4 Nr. 8 UStG", | ||||||
|  | 			"condition": "not exempt_from_sales_tax" | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"field_name": "customer_address", | ||||||
|  | 			"regulation": "§ 14 Abs. 4 Nr. 1 UStG", | ||||||
|  | 			"condition": "base_grand_total > 250" | ||||||
|  | 		} | ||||||
|  | 	] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def validate_regional(doc): | ||||||
|  | 	"""Check if required fields for this document are present.""" | ||||||
|  | 	required_fields = REQUIRED_FIELDS.get(doc.doctype) | ||||||
|  | 	if not required_fields: | ||||||
|  | 		return | ||||||
|  | 
 | ||||||
|  | 	meta = frappe.get_meta(doc.doctype) | ||||||
|  | 	field_map = {field.fieldname: field.label for field in meta.fields} | ||||||
|  | 
 | ||||||
|  | 	for field in required_fields: | ||||||
|  | 		condition = field.get("condition") | ||||||
|  | 		if condition and not frappe.safe_eval(condition, doc.as_dict()): | ||||||
|  | 			continue | ||||||
|  | 
 | ||||||
|  | 		field_name = field.get("field_name") | ||||||
|  | 		regulation = field.get("regulation") | ||||||
|  | 		if field_name and not doc.get(field_name): | ||||||
|  | 			missing(field_map.get(field_name), regulation) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def missing(field_label, regulation): | ||||||
|  | 	"""Notify the user that a required field is missing.""" | ||||||
|  | 	context = 'Specific for Germany. Example: Remember to set Company Tax ID. It is required by § 14 Abs. 4 Nr. 2 UStG.' | ||||||
|  | 	msgprint(_('Remember to set {field_label}. It is required by {regulation}.', context=context).format( | ||||||
|  | 			field_label=frappe.bold(_(field_label)), | ||||||
|  | 			regulation=regulation | ||||||
|  | 		) | ||||||
|  | 	) | ||||||
| @ -132,7 +132,7 @@ class Customer(TransactionBase): | |||||||
| 				address = frappe.get_doc('Address', address_name.get('name')) | 				address = frappe.get_doc('Address', address_name.get('name')) | ||||||
| 				if not address.has_link('Customer', self.name): | 				if not address.has_link('Customer', self.name): | ||||||
| 					address.append('links', dict(link_doctype='Customer', link_name=self.name)) | 					address.append('links', dict(link_doctype='Customer', link_name=self.name)) | ||||||
| 					address.save() | 					address.save(ignore_permissions=self.flags.ignore_permissions) | ||||||
| 
 | 
 | ||||||
| 			lead = frappe.db.get_value("Lead", self.lead_name, ["organization_lead", "lead_name", "email_id", "phone", "mobile_no", "gender", "salutation"], as_dict=True) | 			lead = frappe.db.get_value("Lead", self.lead_name, ["organization_lead", "lead_name", "email_id", "phone", "mobile_no", "gender", "salutation"], as_dict=True) | ||||||
| 
 | 
 | ||||||
| @ -150,7 +150,7 @@ class Customer(TransactionBase): | |||||||
| 					contact = frappe.get_doc('Contact', contact_name.get('name')) | 					contact = frappe.get_doc('Contact', contact_name.get('name')) | ||||||
| 					if not contact.has_link('Customer', self.name): | 					if not contact.has_link('Customer', self.name): | ||||||
| 						contact.append('links', dict(link_doctype='Customer', link_name=self.name)) | 						contact.append('links', dict(link_doctype='Customer', link_name=self.name)) | ||||||
| 						contact.save() | 						contact.save(ignore_permissions=self.flags.ignore_permissions) | ||||||
| 
 | 
 | ||||||
| 			else: | 			else: | ||||||
| 				lead.lead_name = lead.lead_name.lstrip().split(" ") | 				lead.lead_name = lead.lead_name.lstrip().split(" ") | ||||||
|  | |||||||
| @ -46,7 +46,7 @@ QUnit.test("test: quotation", function (assert) { | |||||||
| 			assert.ok(cur_frm.doc.items[0].rate == 200, "Price Changed Manually"); | 			assert.ok(cur_frm.doc.items[0].rate == 200, "Price Changed Manually"); | ||||||
| 			assert.equal(cur_frm.doc.total, 1000, "New Total Calculated"); | 			assert.equal(cur_frm.doc.total, 1000, "New Total Calculated"); | ||||||
| 
 | 
 | ||||||
| 			// Check Terms and Condtions
 | 			// Check Terms and Conditions
 | ||||||
| 			assert.ok(cur_frm.doc.tc_name == "Test Term 1", "Terms and Conditions Checked"); | 			assert.ok(cur_frm.doc.tc_name == "Test Term 1", "Terms and Conditions Checked"); | ||||||
| 
 | 
 | ||||||
| 			assert.ok(cur_frm.doc.payment_terms_template, "Payment Terms Template is correct"); | 			assert.ok(cur_frm.doc.payment_terms_template, "Payment Terms Template is correct"); | ||||||
|  | |||||||
| @ -989,7 +989,6 @@ def make_raw_material_request(items, company, sales_order, project=None): | |||||||
| 		doctype = 'Material Request', | 		doctype = 'Material Request', | ||||||
| 		transaction_date = nowdate(), | 		transaction_date = nowdate(), | ||||||
| 		company = company, | 		company = company, | ||||||
| 		requested_by = frappe.session.user, |  | ||||||
| 		material_request_type = 'Purchase' | 		material_request_type = 'Purchase' | ||||||
| 	)) | 	)) | ||||||
| 	for item in raw_materials: | 	for item in raw_materials: | ||||||
|  | |||||||
| @ -403,6 +403,22 @@ class TestSalesOrder(unittest.TestCase): | |||||||
| 
 | 
 | ||||||
| 		trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 2, 'docname': so.items[0].name}]) | 		trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 2, 'docname': so.items[0].name}]) | ||||||
| 		self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) | 		self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) | ||||||
|  | 	 | ||||||
|  | 	def test_update_child_with_precision(self): | ||||||
|  | 		from frappe.model.meta import get_field_precision | ||||||
|  | 		from frappe.custom.doctype.property_setter.property_setter import make_property_setter | ||||||
|  | 
 | ||||||
|  | 		precision = get_field_precision(frappe.get_meta("Sales Order Item").get_field("rate")) | ||||||
|  | 
 | ||||||
|  | 		make_property_setter("Sales Order Item", "rate", "precision", 7, "Currency") | ||||||
|  | 		so = make_sales_order(item_code= "_Test Item", qty=4, rate=200.34664) | ||||||
|  | 
 | ||||||
|  | 		trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200.34669, 'qty' : 4, 'docname': so.items[0].name}]) | ||||||
|  | 		update_child_qty_rate('Sales Order', trans_item, so.name) | ||||||
|  | 
 | ||||||
|  | 		so.reload() | ||||||
|  | 		self.assertEqual(so.items[0].rate, 200.34669) | ||||||
|  | 		make_property_setter("Sales Order Item", "rate", "precision", precision, "Currency") | ||||||
| 
 | 
 | ||||||
| 	def test_update_child_perm(self): | 	def test_update_child_perm(self): | ||||||
| 		so = make_sales_order(item_code= "_Test Item", qty=4) | 		so = make_sales_order(item_code= "_Test Item", qty=4) | ||||||
|  | |||||||
| @ -43,7 +43,7 @@ | |||||||
|   { |   { | ||||||
|    "hidden": 0, |    "hidden": 0, | ||||||
|    "label": "Data Import and Settings", |    "label": "Data Import and Settings", | ||||||
|    "links": "[\n    {\n        \"description\": \"Import Data from CSV / Excel files.\",\n        \"icon\": \"octicon octicon-cloud-upload\",\n        \"label\": \"Import Data\",\n        \"name\": \"Data Import\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Import Chart Of Accounts from CSV / Excel files\",\n        \"labe\": \"Chart Of Accounts Importer\",\n        \"label\": \"Chart of Accounts Importer\",\n        \"name\": \"Chart of Accounts Importer\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Letter Heads for print templates.\",\n        \"label\": \"Letter Head\",\n        \"name\": \"Letter Head\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Add / Manage Email Accounts.\",\n        \"label\": \"Email Account\",\n        \"name\": \"Email Account\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    }\n]" |    "links": "[\n    {\n        \"description\": \"Import Data from CSV / Excel files.\",\n        \"icon\": \"octicon octicon-cloud-upload\",\n        \"label\": \"Import Data\",\n        \"name\": \"Data Import\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Import Chart of Accounts from CSV / Excel files\",\n        \"label\": \"Chart of Accounts Importer\",\n        \"label\": \"Chart of Accounts Importer\",\n        \"name\": \"Chart of Accounts Importer\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Letter Heads for print templates.\",\n        \"label\": \"Letter Head\",\n        \"name\": \"Letter Head\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Add / Manage Email Accounts.\",\n        \"label\": \"Email Account\",\n        \"name\": \"Email Account\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    }\n]" | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "category": "Modules", |  "category": "Modules", | ||||||
|  | |||||||
| @ -261,14 +261,14 @@ | |||||||
|   { |   { | ||||||
|    "fieldname": "create_chart_of_accounts_based_on", |    "fieldname": "create_chart_of_accounts_based_on", | ||||||
|    "fieldtype": "Select", |    "fieldtype": "Select", | ||||||
|    "label": "Create Chart Of Accounts Based On", |    "label": "Create Chart of Accounts Based on", | ||||||
|    "options": "\nStandard Template\nExisting Company" |    "options": "\nStandard Template\nExisting Company" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"", |    "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"", | ||||||
|    "fieldname": "chart_of_accounts", |    "fieldname": "chart_of_accounts", | ||||||
|    "fieldtype": "Select", |    "fieldtype": "Select", | ||||||
|    "label": "Chart Of Accounts Template", |    "label": "Chart of Accounts Template", | ||||||
|    "no_copy": 1 |    "no_copy": 1 | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user