Merge branch 'develop' into youtube-analytics
This commit is contained in:
		
						commit
						54ab426c1c
					
				| @ -43,7 +43,7 @@ | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "label": "Bank Statement", | ||||
|    "links": "[\n    {\n        \"label\": \"Bank\",\n        \"name\": \"Bank\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Account\",\n        \"name\": \"Bank Account\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Statement Transaction Entry\",\n        \"name\": \"Bank Statement Transaction Entry\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Statement Settings\",\n        \"name\": \"Bank Statement Settings\",\n        \"type\": \"doctype\"\n    }\n]" | ||||
|    "links": "[\n    {\n        \"label\": \"Bank\",\n        \"name\": \"Bank\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Account\",\n        \"name\": \"Bank Account\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Reconciliation\",\n        \"name\": \"bank-reconciliation\",\n        \"type\": \"page\"\n    },\n    {\n        \"label\": \"Bank Clearance\",\n        \"name\": \"Bank Clearance\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Statement Transaction Entry\",\n        \"name\": \"Bank Statement Transaction Entry\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Bank Statement Settings\",\n        \"name\": \"Bank Statement Settings\",\n        \"type\": \"doctype\"\n    }\n]" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
| @ -98,7 +98,7 @@ | ||||
|  "idx": 0, | ||||
|  "is_standard": 1, | ||||
|  "label": "Accounting", | ||||
|  "modified": "2020-06-19 12:42:44.054598", | ||||
|  "modified": "2020-09-03 10:37:07.865801", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Accounting", | ||||
| @ -158,4 +158,4 @@ | ||||
|    "type": "Dashboard" | ||||
|   } | ||||
|  ] | ||||
| } | ||||
| } | ||||
| @ -244,6 +244,8 @@ class Account(NestedSet): | ||||
| 
 | ||||
| 		super(Account, self).on_trash(True) | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_parent_account(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	return frappe.db.sql("""select name from tabAccount | ||||
| 		where is_group = 1 and docstatus != 2 and company = %s | ||||
|  | ||||
| @ -225,7 +225,7 @@ def build_tree_from_json(chart_template, chart_data=None): | ||||
| 
 | ||||
| 			account['parent_account'] = parent | ||||
| 			account['expandable'] = True if identify_is_group(child) else False | ||||
| 			account['value'] = (child.get('account_number') + ' - ' + account_name) \ | ||||
| 			account['value'] = (cstr(child.get('account_number')).strip() + ' - ' + account_name) \ | ||||
| 				if child.get('account_number') else account_name | ||||
| 			accounts.append(account) | ||||
| 			_import_accounts(child, account['value']) | ||||
|  | ||||
| @ -225,7 +225,7 @@ | ||||
|  "idx": 1, | ||||
|  "issingle": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-06-22 20:13:26.043092", | ||||
|  "modified": "2020-08-03 20:13:26.043092", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Accounts Settings", | ||||
|  | ||||
| @ -60,12 +60,12 @@ class BankClearance(Document): | ||||
| 		""".format(condition=condition), {"account": self.account, "from":self.from_date, | ||||
| 				"to": self.to_date, "bank_account": self.bank_account}, as_dict=1) | ||||
| 
 | ||||
| 		pos_entries = [] | ||||
| 		pos_sales_invoices, pos_purchase_invoices = [], [] | ||||
| 		if self.include_pos_transactions: | ||||
| 			pos_entries = frappe.db.sql(""" | ||||
| 			pos_sales_invoices = frappe.db.sql(""" | ||||
| 				select | ||||
| 					"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit, | ||||
| 					si.posting_date, si.debit_to as against_account, sip.clearance_date, | ||||
| 					si.posting_date, si.customer as against_account, sip.clearance_date, | ||||
| 					account.account_currency, 0 as credit | ||||
| 				from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account | ||||
| 				where | ||||
| @ -75,7 +75,20 @@ class BankClearance(Document): | ||||
| 					si.posting_date ASC, si.name DESC | ||||
| 			""", {"account":self.account, "from":self.from_date, "to":self.to_date}, as_dict=1) | ||||
| 
 | ||||
| 		entries = sorted(list(payment_entries)+list(journal_entries+list(pos_entries)), | ||||
| 			pos_purchase_invoices = frappe.db.sql(""" | ||||
| 				select | ||||
| 					"Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit, | ||||
| 					pi.posting_date, pi.supplier as against_account, pi.clearance_date, | ||||
| 					account.account_currency, 0 as debit | ||||
| 				from `tabPurchase Invoice` pi, `tabAccount` account | ||||
| 				where | ||||
| 					pi.cash_bank_account=%(account)s and pi.docstatus=1 and account.name = pi.cash_bank_account | ||||
| 					and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s | ||||
| 				order by | ||||
| 					pi.posting_date ASC, pi.name DESC | ||||
| 			""", {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1) | ||||
| 
 | ||||
| 		entries = sorted(list(payment_entries) + list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)), | ||||
| 			key=lambda k: k['posting_date'] or getdate(nowdate())) | ||||
| 
 | ||||
| 		self.set('payment_entries', []) | ||||
|  | ||||
| @ -27,4 +27,4 @@ def get_vouchar_detials(column_list, doctype, docname): | ||||
| 	for col in column_list: | ||||
| 		sanitize_searchfield(col)  | ||||
| 	return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s''' | ||||
| 		.format(columns=", ".join(json.loads(column_list)), doctype=doctype), docname, as_dict=1)[0] | ||||
| 		.format(columns=", ".join(column_list), doctype=doctype), docname, as_dict=1)[0] | ||||
|  | ||||
| @ -135,7 +135,7 @@ var create_import_button = function(frm) { | ||||
| 			callback: function(r) { | ||||
| 				if(!r.exc) { | ||||
| 					clearInterval(frm.page["interval"]); | ||||
| 					frm.page.set_indicator(__('Import Successfull'), 'blue'); | ||||
| 					frm.page.set_indicator(__('Import Successful'), 'blue'); | ||||
| 					create_reset_button(frm); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| @ -9,6 +9,8 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde | ||||
| from erpnext.stock.get_item_details import get_item_details | ||||
| from frappe.test_runner import make_test_objects | ||||
| 
 | ||||
| test_dependencies = ['Item'] | ||||
| 
 | ||||
| def test_create_test_data(): | ||||
| 	frappe.set_user("Administrator") | ||||
| 	# create test item | ||||
| @ -95,7 +97,6 @@ def test_create_test_data(): | ||||
| 		}) | ||||
| 		coupon_code.insert() | ||||
| 
 | ||||
| 
 | ||||
| class TestCouponCode(unittest.TestCase): | ||||
| 	def setUp(self): | ||||
| 		test_create_test_data() | ||||
|  | ||||
| @ -44,6 +44,19 @@ frappe.ui.form.on("Dunning", { | ||||
| 			); | ||||
| 			frm.page.set_inner_btn_group_as_primary(__("Create")); | ||||
| 		} | ||||
| 
 | ||||
| 		if(frm.doc.docstatus > 0) { | ||||
| 			frm.add_custom_button(__('Ledger'), function() { | ||||
| 				frappe.route_options = { | ||||
| 					"voucher_no": frm.doc.name, | ||||
| 					"from_date": frm.doc.posting_date, | ||||
| 					"to_date": frm.doc.posting_date, | ||||
| 					"company": frm.doc.company, | ||||
| 					"show_cancelled_entries": frm.doc.docstatus === 2 | ||||
| 				}; | ||||
| 				frappe.set_route("query-report", "General Ledger"); | ||||
| 			}, __('View')); | ||||
| 		} | ||||
| 	}, | ||||
| 	overdue_days: function (frm) { | ||||
| 		frappe.db.get_value( | ||||
| @ -125,9 +138,9 @@ frappe.ui.form.on("Dunning", { | ||||
| 	}, | ||||
| 	calculate_interest_and_amount: function (frm) { | ||||
| 		const interest_per_year = frm.doc.outstanding_amount * frm.doc.rate_of_interest / 100; | ||||
| 		const interest_amount = interest_per_year / 365 * frm.doc.overdue_days || 0; | ||||
| 		const dunning_amount = interest_amount + frm.doc.dunning_fee; | ||||
| 		const grand_total = frm.doc.outstanding_amount + dunning_amount; | ||||
| 		const interest_amount = flt((interest_per_year * cint(frm.doc.overdue_days)) / 365 || 0, precision('interest_amount')); | ||||
| 		const dunning_amount = flt(interest_amount + frm.doc.dunning_fee, precision('dunning_amount')); | ||||
| 		const grand_total = flt(frm.doc.outstanding_amount + dunning_amount, precision('grand_total')); | ||||
| 		frm.set_value("interest_amount", interest_amount); | ||||
| 		frm.set_value("dunning_amount", dunning_amount); | ||||
| 		frm.set_value("grand_total", grand_total); | ||||
|  | ||||
| @ -29,10 +29,10 @@ | ||||
|   "company_address_display", | ||||
|   "section_break_6", | ||||
|   "dunning_type", | ||||
|   "interest_amount", | ||||
|   "dunning_fee", | ||||
|   "column_break_8", | ||||
|   "rate_of_interest", | ||||
|   "dunning_fee", | ||||
|   "interest_amount", | ||||
|   "section_break_12", | ||||
|   "dunning_amount", | ||||
|   "grand_total", | ||||
| @ -215,7 +215,7 @@ | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fetch_from": "dunning_type.interest_rate", | ||||
|    "fetch_from": "dunning_type.rate_of_interest", | ||||
|    "fetch_if_empty": 1, | ||||
|    "fieldname": "rate_of_interest", | ||||
|    "fieldtype": "Float", | ||||
| @ -315,7 +315,7 @@ | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-07-21 18:20:23.512151", | ||||
|  "modified": "2020-08-03 18:55:43.683053", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Dunning", | ||||
|  | ||||
| @ -6,7 +6,7 @@ from __future__ import unicode_literals | ||||
| import frappe | ||||
| import json | ||||
| from six import string_types | ||||
| from frappe.utils import getdate, get_datetime, rounded, flt | ||||
| from frappe.utils import getdate, get_datetime, rounded, flt, cint | ||||
| from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year | ||||
| from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries | ||||
| from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions | ||||
| @ -27,11 +27,11 @@ class Dunning(AccountsController): | ||||
| 		amounts = calculate_interest_and_amount( | ||||
| 			self.posting_date, self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days) | ||||
| 		if self.interest_amount != amounts.get('interest_amount'): | ||||
| 			self.interest_amount = amounts.get('interest_amount') | ||||
| 			self.interest_amount = flt(amounts.get('interest_amount'), self.precision('interest_amount')) | ||||
| 		if self.dunning_amount != amounts.get('dunning_amount'): | ||||
| 			self.dunning_amount = amounts.get('dunning_amount') | ||||
| 			self.dunning_amount = flt(amounts.get('dunning_amount'), self.precision('dunning_amount')) | ||||
| 		if self.grand_total != amounts.get('grand_total'): | ||||
| 			self.grand_total = amounts.get('grand_total') | ||||
| 			self.grand_total = flt(amounts.get('grand_total'), self.precision('grand_total')) | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		self.make_gl_entries() | ||||
| @ -47,10 +47,13 @@ class Dunning(AccountsController): | ||||
| 		gl_entries = [] | ||||
| 		invoice_fields = ["project", "cost_center", "debit_to", "party_account_currency", "conversion_rate", "cost_center"] | ||||
| 		inv = frappe.db.get_value("Sales Invoice", self.sales_invoice, invoice_fields, as_dict=1) | ||||
| 
 | ||||
| 		accounting_dimensions = get_accounting_dimensions() | ||||
| 		invoice_fields.extend(accounting_dimensions) | ||||
| 
 | ||||
| 		dunning_in_company_currency = flt(self.dunning_amount * inv.conversion_rate) | ||||
| 		default_cost_center = frappe.get_cached_value('Company',  self.company,  'cost_center') | ||||
| 
 | ||||
| 		gl_entries.append( | ||||
| 			self.get_gl_dict({ | ||||
| 				"account": inv.debit_to, | ||||
| @ -90,10 +93,10 @@ def resolve_dunning(doc, state): | ||||
| 
 | ||||
| def calculate_interest_and_amount(posting_date, outstanding_amount, rate_of_interest, dunning_fee, overdue_days): | ||||
| 	interest_amount = 0 | ||||
| 	grand_total = 0 | ||||
| 	if rate_of_interest: | ||||
| 		interest_per_year = rounded(flt(outstanding_amount) * flt(rate_of_interest))/100 | ||||
| 		interest_amount = ( | ||||
|             interest_per_year / days_in_year(get_datetime(posting_date).year)) * int(overdue_days) | ||||
| 		interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100 | ||||
| 		interest_amount = (interest_per_year * cint(overdue_days)) / 365  | ||||
| 		grand_total = flt(outstanding_amount) + flt(interest_amount) + flt(dunning_fee) | ||||
| 	dunning_amount = flt(interest_amount) + flt(dunning_fee) | ||||
| 	return { | ||||
|  | ||||
							
								
								
									
										17
									
								
								erpnext/accounts/doctype/dunning/dunning_dashboard.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								erpnext/accounts/doctype/dunning/dunning_dashboard.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| from __future__ import unicode_literals | ||||
| from frappe import _ | ||||
| 
 | ||||
| def get_data(): | ||||
| 	return { | ||||
| 		'fieldname': 'dunning', | ||||
| 		'non_standard_fieldnames': { | ||||
| 			'Journal Entry': 'reference_name', | ||||
| 			'Payment Entry': 'reference_name' | ||||
| 		}, | ||||
| 		'transactions': [ | ||||
| 			{ | ||||
| 				'label': _('Payment'), | ||||
| 				'items': ['Payment Entry', 'Journal Entry'] | ||||
| 			} | ||||
| 		] | ||||
| 	} | ||||
| @ -13,7 +13,7 @@ def get_data(): | ||||
| 			}, | ||||
| 			{ | ||||
| 				'label': _('References'), | ||||
| 				'items': ['Period Closing Voucher', 'Request for Quotation', 'Tax Withholding Category'] | ||||
| 				'items': ['Period Closing Voucher', 'Tax Withholding Category'] | ||||
| 			}, | ||||
| 			{ | ||||
| 				'label': _('Target Details'), | ||||
|  | ||||
| @ -638,20 +638,12 @@ $.extend(erpnext.journal_entry, { | ||||
| 		return { filters: filters }; | ||||
| 	}, | ||||
| 
 | ||||
| 	reverse_journal_entry: function(frm) { | ||||
| 		var me = frm.doc; | ||||
| 		for(var i=0; i<me.accounts.length; i++) { | ||||
| 			me.accounts[i].credit += me.accounts[i].debit; | ||||
| 			me.accounts[i].debit = me.accounts[i].credit - me.accounts[i].debit; | ||||
| 			me.accounts[i].credit -= me.accounts[i].debit; | ||||
| 			me.accounts[i].credit_in_account_currency = me.accounts[i].credit; | ||||
| 			me.accounts[i].debit_in_account_currency = me.accounts[i].debit; | ||||
| 			me.accounts[i].reference_type = "Journal Entry"; | ||||
| 			me.accounts[i].reference_name = me.name | ||||
| 		} | ||||
| 		frm.copy_doc(); | ||||
| 		cur_frm.reload_doc(); | ||||
| 	} | ||||
| 	reverse_journal_entry: function() { | ||||
| 		frappe.model.open_mapped_doc({ | ||||
| 			method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry", | ||||
| 			frm: cur_frm | ||||
| 		}) | ||||
| 	}, | ||||
| }); | ||||
| 
 | ||||
| $.extend(erpnext.journal_entry, { | ||||
|  | ||||
| @ -841,13 +841,33 @@ def get_opening_accounts(company): | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_against_jv(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark | ||||
| 		from `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail | ||||
| 		where jv_detail.parent = jv.name and jv_detail.account = %s and ifnull(jv_detail.party, '') = %s | ||||
| 		and (jv_detail.reference_type is null or jv_detail.reference_type = '') | ||||
| 		and jv.docstatus = 1 and jv.`{0}` like %s order by jv.name desc limit %s, %s""".format(searchfield), | ||||
| 		(filters.get("account"), cstr(filters.get("party")), "%{0}%".format(txt), start, page_len)) | ||||
| 	if not frappe.db.has_column('Journal Entry', searchfield): | ||||
| 		return [] | ||||
| 
 | ||||
| 	return frappe.db.sql(""" | ||||
| 		SELECT jv.name, jv.posting_date, jv.user_remark | ||||
| 		FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail | ||||
| 		WHERE jv_detail.parent = jv.name | ||||
| 			AND jv_detail.account = %(account)s | ||||
| 			AND IFNULL(jv_detail.party, '') = %(party)s | ||||
| 			AND ( | ||||
| 				jv_detail.reference_type IS NULL | ||||
| 				OR jv_detail.reference_type = '' | ||||
| 			) | ||||
| 			AND jv.docstatus = 1 | ||||
| 			AND jv.`{0}` LIKE %(txt)s | ||||
| 		ORDER BY jv.name DESC | ||||
| 		LIMIT %(offset)s, %(limit)s | ||||
| 		""".format(searchfield), dict( | ||||
| 				account=filters.get("account"), | ||||
| 				party=cstr(filters.get("party")), | ||||
| 				txt="%{0}%".format(txt), | ||||
| 				offset=start, | ||||
| 				limit=page_len | ||||
| 			) | ||||
| 		) | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @ -1001,3 +1021,34 @@ def make_inter_company_journal_entry(name, voucher_type, company): | ||||
| 	journal_entry.posting_date = nowdate() | ||||
| 	journal_entry.inter_company_journal_entry_reference = name | ||||
| 	return journal_entry.as_dict() | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def make_reverse_journal_entry(source_name, target_doc=None, ignore_permissions=False): | ||||
| 	from frappe.model.mapper import get_mapped_doc | ||||
| 
 | ||||
| 	def update_accounts(source, target, source_parent): | ||||
| 		target.reference_type = "Journal Entry" | ||||
| 		target.reference_name = source_parent.name | ||||
| 
 | ||||
| 	doclist = get_mapped_doc("Journal Entry", source_name, { | ||||
| 		"Journal Entry": { | ||||
| 			"doctype": "Journal Entry", | ||||
| 			"validation": { | ||||
| 				"docstatus": ["=", 1] | ||||
| 			} | ||||
| 		}, | ||||
| 		"Journal Entry Account": { | ||||
| 			"doctype": "Journal Entry Account", | ||||
| 			"field_map": { | ||||
| 				"account_currency": "account_currency", | ||||
| 				"exchange_rate": "exchange_rate", | ||||
| 				"debit_in_account_currency": "credit_in_account_currency", | ||||
| 				"debit": "credit", | ||||
| 				"credit_in_account_currency": "debit_in_account_currency", | ||||
| 				"credit": "debit", | ||||
| 			}, | ||||
| 			"postprocess": update_accounts, | ||||
| 		}, | ||||
| 	}, target_doc, ignore_permissions=ignore_permissions) | ||||
| 
 | ||||
| 	return doclist | ||||
| @ -167,6 +167,49 @@ class TestJournalEntry(unittest.TestCase): | ||||
| 
 | ||||
| 		self.assertFalse(gle) | ||||
| 
 | ||||
| 	def test_reverse_journal_entry(self): | ||||
| 		from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry  | ||||
| 		jv = make_journal_entry("_Test Bank USD - _TC", | ||||
| 			"Sales - _TC", 100, exchange_rate=50, save=False) | ||||
| 
 | ||||
| 		jv.get("accounts")[1].credit_in_account_currency = 5000 | ||||
| 		jv.get("accounts")[1].exchange_rate = 1 | ||||
| 		jv.submit() | ||||
| 
 | ||||
| 		rjv = make_reverse_journal_entry(jv.name) | ||||
| 		rjv.posting_date = nowdate() | ||||
| 		rjv.submit() | ||||
| 
 | ||||
| 
 | ||||
| 		gl_entries = frappe.db.sql("""select account, account_currency, debit, credit, | ||||
| 			debit_in_account_currency, credit_in_account_currency | ||||
| 			from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s | ||||
| 			order by account asc""", rjv.name, as_dict=1) | ||||
| 
 | ||||
| 		self.assertTrue(gl_entries) | ||||
| 
 | ||||
| 
 | ||||
| 		expected_values = { | ||||
| 			"_Test Bank USD - _TC": { | ||||
| 				"account_currency": "USD", | ||||
| 				"debit": 0, | ||||
| 				"debit_in_account_currency": 0, | ||||
| 				"credit": 5000, | ||||
| 				"credit_in_account_currency": 100, | ||||
| 			}, | ||||
| 			"Sales - _TC": { | ||||
| 				"account_currency": "INR", | ||||
| 				"debit": 5000, | ||||
| 				"debit_in_account_currency": 5000, | ||||
| 				"credit": 0, | ||||
| 				"credit_in_account_currency": 0, | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"): | ||||
| 			for i, gle in enumerate(gl_entries): | ||||
| 				self.assertEqual(expected_values[gle.account][field], gle[field]) | ||||
| 
 | ||||
| 	def test_disallow_change_in_account_currency_for_a_party(self): | ||||
| 		# create jv in USD | ||||
| 		jv = make_journal_entry("_Test Bank USD - _TC", | ||||
|  | ||||
| @ -42,7 +42,8 @@ frappe.ui.form.on('Payment Entry', { | ||||
| 		frm.set_query("bank_account", function() { | ||||
| 			return { | ||||
| 				filters: { | ||||
| 					is_company_account: 1 | ||||
| 					is_company_account: 1, | ||||
| 					company: frm.doc.company | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| @ -1049,4 +1050,4 @@ frappe.ui.form.on('Payment Entry', { | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
| }) | ||||
| }) | ||||
|  | ||||
| @ -897,7 +897,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre | ||||
| 		total_amount = ref_doc.get("grand_total") | ||||
| 		exchange_rate = 1 | ||||
| 		outstanding_amount = ref_doc.get("outstanding_amount") | ||||
| 	if reference_doctype == "Dunning": | ||||
| 	elif reference_doctype == "Dunning": | ||||
| 		total_amount = ref_doc.get("dunning_amount") | ||||
| 		exchange_rate = 1 | ||||
| 		outstanding_amount = ref_doc.get("dunning_amount") | ||||
| @ -1101,7 +1101,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= | ||||
| 					'outstanding_amount': doc.get('dunning_amount'), | ||||
| 					'allocated_amount': doc.get('dunning_amount') | ||||
| 				}) | ||||
| 			else:	 | ||||
| 			else: | ||||
| 				pe.append("references", { | ||||
| 					'reference_doctype': dt, | ||||
| 					'reference_name': dn, | ||||
|  | ||||
| @ -27,6 +27,7 @@ class PaymentOrder(Document): | ||||
| 			frappe.db.set_value(self.payment_order_type, d.get(frappe.scrub(self.payment_order_type)), ref_field, status) | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_mop_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	return frappe.db.sql(""" select mode_of_payment from `tabPayment Order Reference` | ||||
| 		where parent = %(parent)s and mode_of_payment like %(txt)s | ||||
| @ -38,6 +39,7 @@ def get_mop_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 		}) | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_supplier_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	return frappe.db.sql(""" select supplier from `tabPayment Order Reference` | ||||
| 		where parent = %(parent)s and supplier like %(txt)s and | ||||
|  | ||||
| @ -24,7 +24,7 @@ class POSClosingEntry(Document): | ||||
| 		if user: | ||||
| 			frappe.throw(_("POS Closing Entry {} against {} between selected period" | ||||
| 				.format(frappe.bold("already exists"), frappe.bold(self.user))), title=_("Invalid Period")) | ||||
| 		 | ||||
| 
 | ||||
| 		if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open": | ||||
| 			frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry")) | ||||
| 
 | ||||
| @ -41,6 +41,7 @@ class POSClosingEntry(Document): | ||||
| 			{"data": self, "currency": currency}) | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_cashiers(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user']) | ||||
| 	return [c['user'] for c in cashiers_list] | ||||
| @ -48,12 +49,12 @@ def get_cashiers(doctype, txt, searchfield, start, page_len, filters): | ||||
| @frappe.whitelist() | ||||
| def get_pos_invoices(start, end, user): | ||||
| 	data = frappe.db.sql(""" | ||||
| 	select  | ||||
| 	select | ||||
| 		name, timestamp(posting_date, posting_time) as "timestamp" | ||||
| 	from  | ||||
| 	from | ||||
| 		`tabPOS Invoice` | ||||
| 	where  | ||||
| 		owner = %s and docstatus = 1 and  | ||||
| 	where | ||||
| 		owner = %s and docstatus = 1 and | ||||
| 		(consolidated_invoice is NULL or consolidated_invoice = '') | ||||
| 	""", (user), as_dict=1) | ||||
| 
 | ||||
| @ -101,7 +102,7 @@ def make_closing_entry_from_opening(opening_entry): | ||||
| 		for t in d.taxes: | ||||
| 			existing_tax = [tx for tx in taxes if tx.account_head == t.account_head and tx.rate == t.rate] | ||||
| 			if existing_tax: | ||||
| 				existing_tax[0].amount += flt(t.tax_amount);  | ||||
| 				existing_tax[0].amount += flt(t.tax_amount); | ||||
| 			else: | ||||
| 				taxes.append(frappe._dict({ | ||||
| 					'account_head': t.account_head, | ||||
|  | ||||
| @ -21,7 +21,7 @@ from six import iteritems | ||||
| class POSInvoice(SalesInvoice): | ||||
| 	def __init__(self, *args, **kwargs): | ||||
| 		super(POSInvoice, self).__init__(*args, **kwargs) | ||||
| 	 | ||||
| 
 | ||||
| 	def validate(self): | ||||
| 		if not cint(self.is_pos): | ||||
| 			frappe.throw(_("POS Invoice should have {} field checked.").format(frappe.bold("Include Payment"))) | ||||
| @ -58,7 +58,7 @@ class POSInvoice(SalesInvoice): | ||||
| 		if self.redeem_loyalty_points and self.loyalty_points: | ||||
| 			self.apply_loyalty_points() | ||||
| 		self.set_status(update=True) | ||||
| 	 | ||||
| 
 | ||||
| 	def on_cancel(self): | ||||
| 		# run on cancel method of selling controller | ||||
| 		super(SalesInvoice, self).on_cancel() | ||||
| @ -68,10 +68,10 @@ class POSInvoice(SalesInvoice): | ||||
| 			against_psi_doc = frappe.get_doc("POS Invoice", self.return_against) | ||||
| 			against_psi_doc.delete_loyalty_point_entry() | ||||
| 			against_psi_doc.make_loyalty_point_entry() | ||||
| 		 | ||||
| 
 | ||||
| 	def validate_stock_availablility(self): | ||||
| 		allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock') | ||||
| 		 | ||||
| 
 | ||||
| 		for d in self.get('items'): | ||||
| 			if d.serial_no: | ||||
| 				filters = { | ||||
| @ -89,11 +89,11 @@ class POSInvoice(SalesInvoice): | ||||
| 				for s in serial_nos: | ||||
| 					if s in reserved_serial_nos: | ||||
| 						invalid_serial_nos.append(s) | ||||
| 				 | ||||
| 
 | ||||
| 				if len(invalid_serial_nos): | ||||
| 					multiple_nos = 's' if len(invalid_serial_nos) > 1 else '' | ||||
| 					frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. \ | ||||
| 						Please select valid serial no.".format(d.idx, multiple_nos,  | ||||
| 						Please select valid serial no.".format(d.idx, multiple_nos, | ||||
| 						frappe.bold(', '.join(invalid_serial_nos)))), title=_("Not Available")) | ||||
| 			else: | ||||
| 				if allow_negative_stock: | ||||
| @ -105,9 +105,9 @@ class POSInvoice(SalesInvoice): | ||||
| 						.format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse))), title=_("Not Available")) | ||||
| 				elif flt(available_stock) < flt(d.qty): | ||||
| 					frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. \ | ||||
| 						Available quantity {}.'.format(d.idx, frappe.bold(d.item_code),  | ||||
| 						Available quantity {}.'.format(d.idx, frappe.bold(d.item_code), | ||||
| 						frappe.bold(d.warehouse), frappe.bold(d.qty))), title=_("Not Available")) | ||||
| 	 | ||||
| 
 | ||||
| 	def validate_serialised_or_batched_item(self): | ||||
| 		for d in self.get("items"): | ||||
| 			serialized = d.get("has_serial_no") | ||||
| @ -125,7 +125,7 @@ class POSInvoice(SalesInvoice): | ||||
| 			if batched and no_batch_selected: | ||||
| 				frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.' | ||||
| 						.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item")) | ||||
| 	 | ||||
| 
 | ||||
| 	def validate_return_items(self): | ||||
| 		if not self.get("is_return"): return | ||||
| 
 | ||||
| @ -158,7 +158,7 @@ class POSInvoice(SalesInvoice): | ||||
| 				frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx)) | ||||
| 			if self.is_return and entry.amount > 0: | ||||
| 				frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx)) | ||||
| 	 | ||||
| 
 | ||||
| 	def validate_pos_return(self): | ||||
| 		if self.is_pos and self.is_return: | ||||
| 			total_amount_in_payments = 0 | ||||
| @ -167,12 +167,12 @@ class POSInvoice(SalesInvoice): | ||||
| 			invoice_total = self.rounded_total or self.grand_total | ||||
| 			if total_amount_in_payments < invoice_total: | ||||
| 				frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total))) | ||||
| 	 | ||||
| 
 | ||||
| 	def validate_loyalty_transaction(self): | ||||
| 		if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center): | ||||
| 			expense_account, cost_center = frappe.db.get_value('Loyalty Program', self.loyalty_program, ["expense_account", "cost_center"]) | ||||
| 			if not self.loyalty_redemption_account: | ||||
| 				self.loyalty_redemption_account = expense_account  | ||||
| 				self.loyalty_redemption_account = expense_account | ||||
| 			if not self.loyalty_redemption_cost_center: | ||||
| 				self.loyalty_redemption_cost_center = cost_center | ||||
| 
 | ||||
| @ -212,7 +212,7 @@ class POSInvoice(SalesInvoice): | ||||
| 
 | ||||
| 		if update: | ||||
| 			self.db_set('status', self.status, update_modified = update_modified) | ||||
| 	 | ||||
| 
 | ||||
| 	def set_pos_fields(self, for_validate=False): | ||||
| 		"""Set retail related fields from POS Profiles""" | ||||
| 		from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile | ||||
| @ -315,25 +315,25 @@ class POSInvoice(SalesInvoice): | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_stock_availability(item_code, warehouse): | ||||
| 	latest_sle = frappe.db.sql("""select qty_after_transaction  | ||||
| 		from `tabStock Ledger Entry`  | ||||
| 	latest_sle = frappe.db.sql("""select qty_after_transaction | ||||
| 		from `tabStock Ledger Entry` | ||||
| 		where item_code = %s and warehouse = %s | ||||
| 		order by posting_date desc, posting_time desc | ||||
| 		limit 1""", (item_code, warehouse), as_dict=1) | ||||
| 	 | ||||
| 
 | ||||
| 	pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty | ||||
| 		from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item | ||||
| 		where p.name = p_item.parent  | ||||
| 		and p.consolidated_invoice is NULL  | ||||
| 		where p.name = p_item.parent | ||||
| 		and p.consolidated_invoice is NULL | ||||
| 		and p.docstatus = 1 | ||||
| 		and p_item.docstatus = 1 | ||||
| 		and p_item.item_code = %s | ||||
| 		and p_item.warehouse = %s | ||||
| 		""", (item_code, warehouse), as_dict=1) | ||||
| 	 | ||||
| 
 | ||||
| 	sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0 | ||||
| 	pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0 | ||||
| 	 | ||||
| 
 | ||||
| 	if sle_qty and pos_sales_qty and sle_qty > pos_sales_qty: | ||||
| 		return sle_qty - pos_sales_qty | ||||
| 	else: | ||||
| @ -360,14 +360,14 @@ def make_merge_log(invoices): | ||||
| 	merge_log = frappe.new_doc("POS Invoice Merge Log") | ||||
| 	merge_log.posting_date = getdate(nowdate()) | ||||
| 	for inv in invoices: | ||||
| 		inv_data = frappe.db.get_values("POS Invoice", inv.get('name'),  | ||||
| 		inv_data = frappe.db.get_values("POS Invoice", inv.get('name'), | ||||
| 			["customer", "posting_date", "grand_total"], as_dict=1)[0] | ||||
| 		merge_log.customer = inv_data.customer | ||||
| 		merge_log.append("pos_invoices", { | ||||
| 			'pos_invoice': inv.get('name'), | ||||
| 			'customer': inv_data.customer, | ||||
| 			'posting_date': inv_data.posting_date, | ||||
| 			'grand_total': inv_data.grand_total  | ||||
| 			'grand_total': inv_data.grand_total | ||||
| 		}) | ||||
| 
 | ||||
| 	if merge_log.get('pos_invoices'): | ||||
|  | ||||
| @ -31,8 +31,7 @@ frappe.ui.form.on('POS Profile', { | ||||
| 		frm.set_query("print_format", function() { | ||||
| 			return { | ||||
| 				filters: [ | ||||
| 					['Print Format', 'doc_type', '=', 'Sales Invoice'], | ||||
| 					['Print Format', 'print_format_type', '=', 'Jinja'], | ||||
| 					['Print Format', 'doc_type', '=', 'POS Invoice'] | ||||
| 				] | ||||
| 			}; | ||||
| 		}); | ||||
| @ -45,10 +44,6 @@ frappe.ui.form.on('POS Profile', { | ||||
| 			}; | ||||
| 		}); | ||||
| 
 | ||||
| 		frm.set_query("print_format", function() { | ||||
| 			return { filters: { doc_type: "Sales Invoice", print_format_type: "JS"} }; | ||||
| 		}); | ||||
| 
 | ||||
| 		frm.set_query('company_address', function(doc) { | ||||
| 			if(!doc.company) { | ||||
| 				frappe.throw(__('Please set Company')); | ||||
|  | ||||
| @ -302,10 +302,10 @@ | ||||
|    "fieldname": "warehouse", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Warehouse", | ||||
|    "mandatory_depends_on": "update_stock", | ||||
|    "oldfieldname": "warehouse", | ||||
|    "oldfieldtype": "Link", | ||||
|    "options": "Warehouse", | ||||
|    "reqd": 1 | ||||
|    "options": "Warehouse" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
| @ -350,4 +350,4 @@ | ||||
|  ], | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC" | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -105,6 +105,7 @@ def get_series(): | ||||
| 	return frappe.get_meta("POS Invoice").get_field("naming_series").options or "s" | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def pos_profile_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	user = frappe.session['user'] | ||||
| 	company = filters.get('company') or frappe.defaults.get_user_default('company') | ||||
|  | ||||
| @ -8,6 +8,8 @@ import unittest | ||||
| from erpnext.stock.get_item_details import get_pos_profile | ||||
| from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes | ||||
| 
 | ||||
| test_dependencies = ['Item'] | ||||
| 
 | ||||
| class TestPOSProfile(unittest.TestCase): | ||||
| 	def test_pos_profile(self): | ||||
| 		make_pos_profile() | ||||
| @ -88,7 +90,7 @@ def make_pos_profile(**args): | ||||
| 		"write_off_account":  args.write_off_account or "_Test Write Off - _TC", | ||||
| 		"write_off_cost_center":  args.write_off_cost_center or "_Test Write Off Cost Center - _TC" | ||||
| 	}) | ||||
| 	 | ||||
| 
 | ||||
| 	payments = [{ | ||||
| 		'mode_of_payment': 'Cash', | ||||
| 		'default': 1 | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # MIT License. See license.txt | ||||
| 
 | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| @ -208,7 +207,7 @@ def get_serial_no_for_item(args): | ||||
| 
 | ||||
| def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False): | ||||
| 	from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules, | ||||
| 		get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule) | ||||
| 			get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule) | ||||
| 
 | ||||
| 	if isinstance(doc, string_types): | ||||
| 		doc = json.loads(doc) | ||||
| @ -237,7 +236,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa | ||||
| 
 | ||||
| 	update_args_for_pricing_rule(args) | ||||
| 
 | ||||
| 	pricing_rules = (get_applied_pricing_rules(args) | ||||
| 	pricing_rules = (get_applied_pricing_rules(args.get('pricing_rules')) | ||||
| 		if for_validate and args.get("pricing_rules") else get_pricing_rules(args, doc)) | ||||
| 
 | ||||
| 	if pricing_rules: | ||||
| @ -365,8 +364,9 @@ def set_discount_amount(rate, item_details): | ||||
| 			item_details.rate = rate | ||||
| 
 | ||||
| def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): | ||||
| 	from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rule_items | ||||
| 	for d in json.loads(pricing_rules): | ||||
| 	from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules, | ||||
| 		get_pricing_rule_items) | ||||
| 	for d in get_applied_pricing_rules(pricing_rules): | ||||
| 		if not d or not frappe.db.exists("Pricing Rule", d): continue | ||||
| 		pricing_rule = frappe.get_cached_doc('Pricing Rule', d) | ||||
| 
 | ||||
| @ -433,14 +433,14 @@ def make_pricing_rule(doctype, docname): | ||||
| 	return doc | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_item_uoms(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	items = [filters.get('value')] | ||||
| 	if filters.get('apply_on') != 'Item Code': | ||||
| 		field = frappe.scrub(filters.get('apply_on')) | ||||
| 		items = [d.name for d in frappe.db.get_all("Item", filters={field: filters.get('value')})] | ||||
| 
 | ||||
| 		items = frappe.db.sql_list("""select name | ||||
| 			from `tabItem` where {0} = %s""".format(field), filters.get('value')) | ||||
| 
 | ||||
| 	return frappe.get_all('UOM Conversion Detail', | ||||
| 		filters = {'parent': ('in', items), 'uom': ("like", "{0}%".format(txt))}, | ||||
| 		fields = ["distinct uom"], as_list=1) | ||||
| 	return frappe.get_all('UOM Conversion Detail', filters={ | ||||
| 			'parent': ('in', items), | ||||
| 			'uom': ("like", "{0}%".format(txt)) | ||||
| 		}, fields = ["distinct uom"], as_list=1) | ||||
|  | ||||
| @ -447,9 +447,14 @@ def apply_pricing_rule_on_transaction(doc): | ||||
| 				apply_pricing_rule_for_free_items(doc, item_details.free_item_data) | ||||
| 				doc.set_missing_values() | ||||
| 
 | ||||
| def get_applied_pricing_rules(item_row): | ||||
| 	return (json.loads(item_row.get("pricing_rules")) | ||||
| 		if item_row.get("pricing_rules") else []) | ||||
| def get_applied_pricing_rules(pricing_rules): | ||||
| 	if pricing_rules: | ||||
| 		if pricing_rules.startswith('['): | ||||
| 			return json.loads(pricing_rules) | ||||
| 		else: | ||||
| 			return pricing_rules.split(',') | ||||
| 
 | ||||
| 	return [] | ||||
| 
 | ||||
| def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): | ||||
| 	free_item = pricing_rule.free_item | ||||
|  | ||||
| @ -10,13 +10,15 @@ frappe.ui.form.on('Process Deferred Accounting', { | ||||
| 				} | ||||
| 			}; | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 		if (frm.doc.company) { | ||||
| 	type: function(frm) { | ||||
| 		if (frm.doc.company && frm.doc.type) { | ||||
| 			frm.set_query("account", function() { | ||||
| 				return { | ||||
| 					filters: { | ||||
| 						'company': frm.doc.company, | ||||
| 						'root_type': 'Liability', | ||||
| 						'root_type': frm.doc.type === 'Income' ? 'Liability' : 'Asset', | ||||
| 						'is_group': 0 | ||||
| 					} | ||||
| 				}; | ||||
|  | ||||
| @ -60,6 +60,7 @@ | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval: doc.type", | ||||
|    "fieldname": "account", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Account", | ||||
| @ -73,9 +74,10 @@ | ||||
|    "reqd": 1 | ||||
|   } | ||||
|  ], | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-02-06 18:18:09.852844", | ||||
|  "modified": "2020-09-03 18:07:02.463754", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Process Deferred Accounting", | ||||
|  | ||||
| @ -0,0 +1,89 @@ | ||||
| <h1 class="text-center" style="page-break-before:always">{{ filters.party[0] }}</h1> | ||||
| <h3 class="text-center">{{ _("Statement of Accounts") }}</h3> | ||||
| 
 | ||||
| <h5 class="text-center"> | ||||
|     {{ frappe.format(filters.from_date, 'Date')}} | ||||
| 	{{ _("to") }} | ||||
| 	{{ frappe.format(filters.to_date, 'Date')}} | ||||
| </h5> | ||||
| 
 | ||||
| <table class="table table-bordered"> | ||||
| 	<thead> | ||||
| 		<tr> | ||||
| 			<th style="width: 12%">{{ _("Date") }}</th> | ||||
| 			<th style="width: 15%">{{ _("Ref") }}</th> | ||||
| 			<th style="width: 25%">{{ _("Party") }}</th> | ||||
| 			<th style="width: 15%">{{ _("Debit") }}</th> | ||||
| 			<th style="width: 15%">{{ _("Credit") }}</th> | ||||
| 			<th style="width: 18%">{{ _("Balance (Dr - Cr)") }}</th> | ||||
| 		</tr> | ||||
| 	</thead> | ||||
| 	<tbody> | ||||
|         {% for row in data %} | ||||
| 			<tr> | ||||
| 			{% if(row.posting_date) %} | ||||
| 				<td>{{ frappe.format(row.posting_date, 'Date') }}</td> | ||||
| 				<td>{{ row.voucher_type }} | ||||
| 					<br>{{ row.voucher_no }}</td> | ||||
| 				<td> | ||||
| 					{% if not (filters.party or filters.account)  %} | ||||
| 						{{ row.party or row.account }} | ||||
| 						<br> | ||||
| 					{% endif %} | ||||
| 
 | ||||
| 					{{ _("Against") }}: {{ row.against }} | ||||
| 					<br>{{ _("Remarks") }}: {{ row.remarks }} | ||||
| 					{% if row.bill_no %} | ||||
| 						<br>{{ _("Supplier Invoice No") }}: {{ row.bill_no }} | ||||
| 					{% endif %} | ||||
| 					</td> | ||||
| 					<td style="text-align: right"> | ||||
| 						{{ frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}</td> | ||||
| 					<td style="text-align: right"> | ||||
| 						{{ frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}</td> | ||||
| 			{% else %} | ||||
| 				<td></td> | ||||
| 				<td></td> | ||||
| 				<td><b>{{ frappe.format(row.account, {fieldtype: "Link"}) or " " }}</b></td> | ||||
| 				<td style="text-align: right"> | ||||
| 					{{ row.account and frappe.utils.fmt_money(row.debit, filters.presentation_currency) }} | ||||
| 				</td> | ||||
| 				<td style="text-align: right"> | ||||
| 					{{ row.account and frappe.utils.fmt_money(row.credit, filters.presentation_currency) }} | ||||
| 				</td> | ||||
| 			{% endif %} | ||||
| 				<td style="text-align: right"> | ||||
| 					{{ frappe.utils.fmt_money(row.balance, filters.presentation_currency) }} | ||||
| 				</td> | ||||
| 			</tr> | ||||
| 		{% endfor %} | ||||
| 		</tbody> | ||||
| </table> | ||||
| <br><br> | ||||
| {% if aging %} | ||||
| <h3 class="text-center">{{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}</h3> | ||||
| <h5 class="text-center"> | ||||
| 	{{ _("Up to " ) }}  {{ frappe.format(filters.to_date, 'Date')}} | ||||
| </h5> | ||||
| <br> | ||||
| 
 | ||||
| <table class="table table-bordered"> | ||||
| 	<thead> | ||||
| 		<tr> | ||||
| 			<th style="width: 12%">30 Days</th> | ||||
| 			<th style="width: 15%">60 Days</th> | ||||
| 			<th style="width: 25%">90 Days</th> | ||||
| 			<th style="width: 15%">120 Days</th> | ||||
| 		</tr> | ||||
| 	</thead> | ||||
| 	<tbody> | ||||
| 		<tr> | ||||
| 			<td>{{ aging.range1 }}</td> | ||||
| 			<td>{{ aging.range2 }}</td> | ||||
| 			<td>{{ aging.range3 }}</td> | ||||
| 			<td>{{ aging.range4 }}</td> | ||||
| 		</tr> | ||||
| 	</tbody> | ||||
| </table> | ||||
| {% endif %} | ||||
| <p class="text-right text-muted">Printed On {{ frappe.format(frappe.utils.get_datetime(), 'Datetime') }}</p> | ||||
| @ -0,0 +1,132 @@ | ||||
| // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| 
 | ||||
| frappe.ui.form.on('Process Statement Of Accounts', { | ||||
| 	view_properties: function(frm) { | ||||
| 		frappe.route_options = {doc_type: 'Customer'}; | ||||
| 		frappe.set_route("Form", "Customize Form"); | ||||
| 	}, | ||||
| 	refresh: function(frm){ | ||||
| 		if(!frm.doc.__islocal) { | ||||
| 			frm.add_custom_button('Send Emails',function(){ | ||||
| 				frappe.call({ | ||||
| 					method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails", | ||||
| 					args: { | ||||
| 						"document_name": frm.doc.name, | ||||
| 					}, | ||||
| 					callback: function(r) { | ||||
| 						if(r && r.message) { | ||||
| 							frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'}); | ||||
| 						} | ||||
| 						else{ | ||||
| 							frappe.msgprint('No Records for these settings.') | ||||
| 						} | ||||
| 					} | ||||
| 				}); | ||||
| 			}); | ||||
| 			frm.add_custom_button('Download',function(){ | ||||
| 				var url = frappe.urllib.get_full_url( | ||||
| 					'/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?' | ||||
| 					+ 'document_name='+encodeURIComponent(frm.doc.name)) | ||||
| 				$.ajax({ | ||||
| 					url: url, | ||||
| 					type: 'GET', | ||||
| 					success: function(result) { | ||||
| 						if(jQuery.isEmptyObject(result)){ | ||||
| 							frappe.msgprint('No Records for these settings.'); | ||||
| 						} | ||||
| 						else{ | ||||
| 							window.location = url; | ||||
| 						} | ||||
| 					} | ||||
| 				}); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
| 	onload: function(frm) { | ||||
| 		frm.set_query('currency', function(){ | ||||
| 			return { | ||||
| 				filters: { | ||||
| 					'enabled': 1 | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 		if(frm.doc.__islocal){ | ||||
| 			frm.set_value('from_date', frappe.datetime.add_months(frappe.datetime.get_today(), -1)); | ||||
| 			frm.set_value('to_date', frappe.datetime.get_today()); | ||||
| 		} | ||||
| 	}, | ||||
| 	customer_collection: function(frm){ | ||||
| 		frm.set_value('collection_name', ''); | ||||
| 		if(frm.doc.customer_collection){ | ||||
| 			frm.get_field('collection_name').set_label(frm.doc.customer_collection); | ||||
| 		} | ||||
| 	}, | ||||
| 	frequency: function(frm){ | ||||
| 		if(frm.doc.frequency != ''){ | ||||
| 			frm.set_value('start_date', frappe.datetime.get_today()); | ||||
| 		} | ||||
| 		else{ | ||||
| 			frm.set_value('start_date', ''); | ||||
| 		} | ||||
| 	}, | ||||
| 	fetch_customers: function(frm){ | ||||
| 		if(frm.doc.collection_name){ | ||||
| 			frappe.call({ | ||||
| 				method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.fetch_customers", | ||||
| 				args: { | ||||
| 					'customer_collection': frm.doc.customer_collection, | ||||
| 					'collection_name': frm.doc.collection_name, | ||||
| 					'primary_mandatory': frm.doc.primary_mandatory | ||||
| 				}, | ||||
| 				callback: function(r) { | ||||
| 					if(!r.exc) { | ||||
| 						if(r.message.length){ | ||||
| 							frm.clear_table('customers'); | ||||
| 							for (const customer of r.message){ | ||||
| 								var row = frm.add_child('customers'); | ||||
| 								row.customer = customer.name; | ||||
| 								row.primary_email = customer.primary_email; | ||||
| 								row.billing_email = customer.billing_email; | ||||
| 							} | ||||
| 							frm.refresh_field('customers'); | ||||
| 						} | ||||
| 						else{ | ||||
| 							frappe.msgprint('No Customers found with selected options.'); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			}); | ||||
| 		} | ||||
| 		else { | ||||
| 			frappe.throw('Enter ' + frm.doc.customer_collection + ' name.'); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| frappe.ui.form.on('Process Statement Of Accounts Customer', { | ||||
| 	customer: function(frm, cdt, cdn){ | ||||
| 		var row = locals[cdt][cdn]; | ||||
| 		if (!row.customer){ | ||||
| 			return; | ||||
| 		} | ||||
| 		frappe.call({ | ||||
| 			method: 'erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.get_customer_emails', | ||||
| 			args: { | ||||
| 				'customer_name': row.customer, | ||||
| 				'primary_mandatory': frm.doc.primary_mandatory | ||||
| 			}, | ||||
| 			callback: function(r){ | ||||
| 				if(!r.exe){ | ||||
| 					if(r.message.length){ | ||||
| 						frappe.model.set_value(cdt, cdn, "primary_email", r.message[0]) | ||||
| 						frappe.model.set_value(cdt, cdn, "billing_email", r.message[1]) | ||||
| 					} | ||||
| 					else { | ||||
| 						return | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| }); | ||||
| @ -0,0 +1,310 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "allow_workflow": 1, | ||||
|  "autoname": "Prompt", | ||||
|  "creation": "2020-05-22 16:46:18.712954", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "section_break_11", | ||||
|   "from_date", | ||||
|   "company", | ||||
|   "account", | ||||
|   "group_by", | ||||
|   "cost_center", | ||||
|   "column_break_14", | ||||
|   "to_date", | ||||
|   "finance_book", | ||||
|   "currency", | ||||
|   "project", | ||||
|   "section_break_3", | ||||
|   "customer_collection", | ||||
|   "collection_name", | ||||
|   "fetch_customers", | ||||
|   "column_break_6", | ||||
|   "primary_mandatory", | ||||
|   "column_break_17", | ||||
|   "customers", | ||||
|   "preferences", | ||||
|   "orientation", | ||||
|   "section_break_14", | ||||
|   "include_ageing", | ||||
|   "ageing_based_on", | ||||
|   "section_break_1", | ||||
|   "enable_auto_email", | ||||
|   "section_break_18", | ||||
|   "frequency", | ||||
|   "filter_duration", | ||||
|   "column_break_21", | ||||
|   "start_date", | ||||
|   "section_break_33", | ||||
|   "subject", | ||||
|   "column_break_28", | ||||
|   "cc_to", | ||||
|   "section_break_30", | ||||
|   "body", | ||||
|   "help_text" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "frequency", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Frequency", | ||||
|    "options": "Weekly\nMonthly\nQuarterly" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "company", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Company", | ||||
|    "options": "Company", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:doc.enable_auto_email == 0;", | ||||
|    "fieldname": "from_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "From Date", | ||||
|    "mandatory_depends_on": "eval:doc.frequency == '';" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:doc.enable_auto_email == 0;", | ||||
|    "fieldname": "to_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "To Date", | ||||
|    "mandatory_depends_on": "eval:doc.frequency == '';" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "cost_center", | ||||
|    "fieldtype": "Table MultiSelect", | ||||
|    "label": "Cost Center", | ||||
|    "options": "PSOA Cost Center" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "project", | ||||
|    "fieldtype": "Table MultiSelect", | ||||
|    "label": "Project", | ||||
|    "options": "PSOA Project" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_3", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Customers" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_6", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_11", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "General Ledger Filters" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_14", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_17", | ||||
|    "fieldtype": "Section Break", | ||||
|    "hide_border": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "customer_collection", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Select Customers By", | ||||
|    "options": "\nCustomer Group\nTerritory\nSales Partner\nSales Person" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval: doc.customer_collection !== ''", | ||||
|    "fieldname": "collection_name", | ||||
|    "fieldtype": "Dynamic Link", | ||||
|    "label": "Recipient", | ||||
|    "options": "customer_collection" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_1", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Email Settings" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "account", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Account", | ||||
|    "options": "Account" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "finance_book", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Finance Book", | ||||
|    "options": "Finance Book" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "preferences", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Print Preferences" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "orientation", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Orientation", | ||||
|    "options": "Landscape\nPortrait" | ||||
|   }, | ||||
|   { | ||||
|    "default": "Today", | ||||
|    "fieldname": "start_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Start Date" | ||||
|   }, | ||||
|   { | ||||
|    "default": "Group by Voucher (Consolidated)", | ||||
|    "fieldname": "group_by", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Group By", | ||||
|    "options": "\nGroup by Voucher\nGroup by Voucher (Consolidated)" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "currency", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Currency", | ||||
|    "options": "Currency" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "include_ageing", | ||||
|    "fieldtype": "Check", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Include Ageing Summary" | ||||
|   }, | ||||
|   { | ||||
|    "default": "Due Date", | ||||
|    "depends_on": "eval:doc.include_ageing === 1", | ||||
|    "fieldname": "ageing_based_on", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Ageing Based On", | ||||
|    "options": "Due Date\nPosting Date" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "enable_auto_email", | ||||
|    "fieldtype": "Check", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Enable Auto Email" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_14", | ||||
|    "fieldtype": "Column Break", | ||||
|    "hide_border": 1 | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval: doc.enable_auto_email ==1", | ||||
|    "fieldname": "section_break_18", | ||||
|    "fieldtype": "Section Break", | ||||
|    "hide_border": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_21", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval: doc.customer_collection !== ''", | ||||
|    "fieldname": "fetch_customers", | ||||
|    "fieldtype": "Button", | ||||
|    "label": "Fetch Customers", | ||||
|    "options": "fetch_customers", | ||||
|    "print_hide": 1, | ||||
|    "report_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "1", | ||||
|    "fieldname": "primary_mandatory", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Send To Primary Contact" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "cc_to", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "CC To", | ||||
|    "options": "User" | ||||
|   }, | ||||
|   { | ||||
|    "default": "1", | ||||
|    "fieldname": "filter_duration", | ||||
|    "fieldtype": "Int", | ||||
|    "label": "Filter Duration (Months)" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "customers", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "Customers", | ||||
|    "options": "Process Statement Of Accounts Customer", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_28", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_30", | ||||
|    "fieldtype": "Section Break", | ||||
|    "hide_border": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_33", | ||||
|    "fieldtype": "Section Break", | ||||
|    "hide_border": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "help_text", | ||||
|    "fieldtype": "HTML", | ||||
|    "label": "Help Text", | ||||
|    "options": "<br>\n<h4>Note</h4>\n<ul>\n<li>\nYou can use <a href=\"https://jinja.palletsprojects.com/en/2.11.x/\" target=\"_blank\">Jinja tags</a> in <b>Subject</b> and <b>Body</b> fields for dynamic values.\n</li><li>\n    All fields in this doctype are available under the <b>doc</b> object and all fields for the customer to whom the mail will go to is available under the  <b>customer</b> object.\n</li></ul>\n<h4> Examples</h4>\n<!-- {% raw %} -->\n<ul>\n    <li><b>Subject</b>:<br><br><pre><code>Statement Of Accounts for {{ customer.name }}</code></pre><br></li>\n    <li><b>Body</b>: <br><br>\n<pre><code>Hello {{ customer.name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.</code> </pre></li>\n</ul>\n<!-- {% endraw %} -->" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "subject", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "Subject" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "body", | ||||
|    "fieldtype": "Text Editor", | ||||
|    "label": "Body" | ||||
|   } | ||||
|  ], | ||||
|  "links": [], | ||||
|  "modified": "2020-08-08 08:47:09.185728", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Process Statement Of Accounts", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Accounts User", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Accounts Manager", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   } | ||||
|  ], | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -0,0 +1,271 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe.model.document import Document | ||||
| from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa | ||||
| from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing | ||||
| from frappe.core.doctype.communication.email import make | ||||
| 
 | ||||
| from frappe.utils.print_format import report_to_pdf | ||||
| from frappe.utils.pdf import get_pdf | ||||
| from frappe.utils import today, add_days, add_months, getdate, format_date | ||||
| from frappe.utils.jinja import validate_template | ||||
| 
 | ||||
| import copy | ||||
| from datetime import timedelta | ||||
| from frappe.www.printview import get_print_style | ||||
| 
 | ||||
| class ProcessStatementOfAccounts(Document): | ||||
| 	def validate(self): | ||||
| 		if not self.subject: | ||||
| 			self.subject = 'Statement Of Accounts for {{ customer.name }}' | ||||
| 		if not self.body: | ||||
| 			self.body = 'Hello {{ customer.name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.' | ||||
| 
 | ||||
| 		validate_template(self.subject) | ||||
| 		validate_template(self.body) | ||||
| 
 | ||||
| 		if not self.customers: | ||||
| 			frappe.throw(frappe._('Customers not selected.')) | ||||
| 
 | ||||
| 		if self.enable_auto_email: | ||||
| 			self.to_date = self.start_date | ||||
| 			self.from_date = add_months(self.to_date, -1 * self.filter_duration) | ||||
| 
 | ||||
| 
 | ||||
| def get_report_pdf(doc, consolidated=True): | ||||
| 	statement_dict = {} | ||||
| 	aging = '' | ||||
| 	base_template_path = "frappe/www/printview.html" | ||||
| 	template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html" | ||||
| 
 | ||||
| 	for entry in doc.customers: | ||||
| 		if doc.include_ageing: | ||||
| 			ageing_filters = frappe._dict({ | ||||
| 				'company': doc.company, | ||||
| 				'report_date': doc.to_date, | ||||
| 				'ageing_based_on': doc.ageing_based_on, | ||||
| 				'range1': 30, | ||||
| 				'range2': 60, | ||||
| 				'range3': 90, | ||||
| 				'range4': 120, | ||||
| 				'customer': entry.customer | ||||
| 			}) | ||||
| 			col1, aging = get_ageing(ageing_filters) | ||||
| 			aging[0]['ageing_based_on'] = doc.ageing_based_on | ||||
| 
 | ||||
| 		tax_id = frappe.get_doc('Customer', entry.customer).tax_id | ||||
| 
 | ||||
| 		filters= frappe._dict({ | ||||
| 			'from_date': doc.from_date, | ||||
| 			'to_date': doc.to_date, | ||||
| 			'company': doc.company, | ||||
| 			'finance_book': doc.finance_book if doc.finance_book else None, | ||||
| 			"account": doc.account if doc.account else None, | ||||
| 			'party_type': 'Customer', | ||||
| 			'party': [entry.customer], | ||||
| 			'group_by': doc.group_by, | ||||
| 			'currency': doc.currency, | ||||
| 			'cost_center': [cc.cost_center_name for cc in doc.cost_center], | ||||
| 			'project': [p.project_name for p in doc.project], | ||||
| 			'show_opening_entries': 0, | ||||
| 			'include_default_book_entries': 0, | ||||
| 			'show_cancelled_entries': 1, | ||||
| 			'tax_id': tax_id if tax_id else None | ||||
| 		}) | ||||
| 		col, res = get_soa(filters) | ||||
| 
 | ||||
| 		for x in [0, -2, -1]: | ||||
| 			res[x]['account'] = res[x]['account'].replace("'","") | ||||
| 
 | ||||
| 		if len(res) == 3: | ||||
| 			continue | ||||
| 		html = frappe.render_template(template_path, \ | ||||
| 			{"filters": filters, "data": res, "aging": aging[0] if doc.include_ageing else None}) | ||||
| 		html = frappe.render_template(base_template_path, {"body": html, \ | ||||
| 			"css": get_print_style(), "title": "Statement For " + entry.customer}) | ||||
| 		statement_dict[entry.customer] = html | ||||
| 	if not bool(statement_dict): | ||||
| 		return False | ||||
| 	elif consolidated: | ||||
| 		result = ''.join(list(statement_dict.values())) | ||||
| 		return get_pdf(result, {'orientation': doc.orientation}) | ||||
| 	else: | ||||
| 		for customer, statement_html in statement_dict.items(): | ||||
| 			statement_dict[customer]=get_pdf(statement_html, {'orientation': doc.orientation}) | ||||
| 		return statement_dict | ||||
| 
 | ||||
| def get_customers_based_on_territory_or_customer_group(customer_collection, collection_name): | ||||
| 	fields_dict = { | ||||
| 		'Customer Group': 'customer_group', | ||||
| 		'Territory': 'territory', | ||||
| 	} | ||||
| 	collection = frappe.get_doc(customer_collection, collection_name) | ||||
| 	selected = [customer.name for customer in frappe.get_list(customer_collection, filters=[ | ||||
| 			['lft', '>=', collection.lft], | ||||
| 			['rgt', '<=', collection.rgt] | ||||
| 		], | ||||
| 		fields=['name'], | ||||
| 		order_by='lft asc, rgt desc' | ||||
| 	)] | ||||
| 	return frappe.get_list('Customer', fields=['name', 'email_id'], \ | ||||
| 		filters=[[fields_dict[customer_collection], 'IN', selected]]) | ||||
| 
 | ||||
| def get_customers_based_on_sales_person(sales_person): | ||||
| 	lft, rgt = frappe.db.get_value("Sales Person", | ||||
| 		sales_person, ["lft", "rgt"]) | ||||
| 	records = frappe.db.sql(""" | ||||
| 		select distinct parent, parenttype | ||||
| 		from `tabSales Team` steam | ||||
| 		where parenttype = 'Customer' | ||||
| 			and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person) | ||||
| 	""", (lft, rgt), as_dict=1) | ||||
| 	sales_person_records = frappe._dict() | ||||
| 	for d in records: | ||||
| 		sales_person_records.setdefault(d.parenttype, set()).add(d.parent) | ||||
| 	customers = frappe.get_list('Customer', fields=['name', 'email_id'], \ | ||||
| 			filters=[['name', 'in', list(sales_person_records['Customer'])]]) | ||||
| 	return customers | ||||
| 
 | ||||
| def get_recipients_and_cc(customer, doc): | ||||
| 	recipients = [] | ||||
| 	for clist in doc.customers: | ||||
| 		if clist.customer == customer: | ||||
| 			recipients.append(clist.billing_email) | ||||
| 			if doc.primary_mandatory and clist.primary_email: | ||||
| 				recipients.append(clist.primary_email) | ||||
| 	cc = [] | ||||
| 	if doc.cc_to != '': | ||||
| 		try: | ||||
| 			cc=[frappe.get_value('User', doc.cc_to, 'email')] | ||||
| 		except: | ||||
| 			pass | ||||
| 
 | ||||
| 	return recipients, cc | ||||
| 
 | ||||
| def get_context(customer, doc): | ||||
| 	template_doc = copy.deepcopy(doc) | ||||
| 	del template_doc.customers | ||||
| 	template_doc.from_date = format_date(template_doc.from_date) | ||||
| 	template_doc.to_date = format_date(template_doc.to_date) | ||||
| 	return { | ||||
| 		'doc': template_doc, | ||||
| 		'customer': frappe.get_doc('Customer', customer), | ||||
| 		'frappe': frappe.utils | ||||
| 	} | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def fetch_customers(customer_collection, collection_name, primary_mandatory): | ||||
| 	customer_list = [] | ||||
| 	customers = [] | ||||
| 
 | ||||
| 	if customer_collection == 'Sales Person': | ||||
| 		customers = get_customers_based_on_sales_person(collection_name) | ||||
| 		if not bool(customers): | ||||
| 			frappe.throw('No Customers found with selected options.') | ||||
| 	else: | ||||
| 		if customer_collection == 'Sales Partner': | ||||
| 			customers = frappe.get_list('Customer', fields=['name', 'email_id'], \ | ||||
| 				filters=[['default_sales_partner', '=', collection_name]]) | ||||
| 		else: | ||||
| 			customers = get_customers_based_on_territory_or_customer_group(customer_collection, collection_name) | ||||
| 
 | ||||
| 	for customer in customers: | ||||
| 		primary_email = customer.get('email_id') or '' | ||||
| 		billing_email = get_customer_emails(customer.name, 1, billing_and_primary=False) | ||||
| 
 | ||||
| 		if billing_email == '' or (primary_email == '' and int(primary_mandatory)): | ||||
| 			continue | ||||
| 
 | ||||
| 		customer_list.append({ | ||||
| 			'name': customer.name, | ||||
| 			'primary_email': primary_email, | ||||
| 			'billing_email': billing_email | ||||
| 		}) | ||||
| 	return customer_list | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True): | ||||
| 	billing_email = frappe.db.sql(""" | ||||
| 		SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent \ | ||||
| 		WHERE l.link_doctype='Customer' and l.link_name='""" + customer_name + """' and \ | ||||
| 		c.is_billing_contact=1 \ | ||||
| 		order by c.creation desc""") | ||||
| 
 | ||||
| 	if len(billing_email) == 0 or (billing_email[0][0] is None): | ||||
| 		if billing_and_primary: | ||||
| 			frappe.throw('No billing email found for customer: '+ customer_name) | ||||
| 		else: | ||||
| 			return '' | ||||
| 
 | ||||
| 	if billing_and_primary: | ||||
| 		primary_email =  frappe.get_value('Customer', customer_name, 'email_id') | ||||
| 		if primary_email is None and int(primary_mandatory): | ||||
| 			frappe.throw('No primary email found for customer: '+ customer_name) | ||||
| 		return [primary_email or '', billing_email[0][0]] | ||||
| 	else: | ||||
| 		return billing_email[0][0] or '' | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def download_statements(document_name): | ||||
| 	doc = frappe.get_doc('Process Statement Of Accounts', document_name) | ||||
| 	report = get_report_pdf(doc) | ||||
| 	if report: | ||||
| 		frappe.local.response.filename = doc.name + '.pdf' | ||||
| 		frappe.local.response.filecontent = report | ||||
| 		frappe.local.response.type = "download" | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def send_emails(document_name, from_scheduler=False): | ||||
| 	doc = frappe.get_doc('Process Statement Of Accounts', document_name) | ||||
| 	report = get_report_pdf(doc, consolidated=False) | ||||
| 
 | ||||
| 	if report: | ||||
| 		for customer, report_pdf in report.items(): | ||||
| 			attachments = [{ | ||||
| 				'fname': customer + '.pdf', | ||||
| 				'fcontent': report_pdf | ||||
| 			}] | ||||
| 
 | ||||
| 			recipients, cc = get_recipients_and_cc(customer, doc) | ||||
| 			context = get_context(customer, doc) | ||||
| 			subject = frappe.render_template(doc.subject, context) | ||||
| 			message = frappe.render_template(doc.body, context) | ||||
| 
 | ||||
| 			frappe.enqueue( | ||||
| 				queue='short', | ||||
| 				method=frappe.sendmail, | ||||
| 				recipients=recipients, | ||||
| 				sender=frappe.session.user, | ||||
| 				cc=cc, | ||||
| 				subject=subject, | ||||
| 				message=message, | ||||
| 				now=True, | ||||
| 				reference_doctype='Process Statement Of Accounts', | ||||
| 				reference_name=document_name, | ||||
| 				attachments=attachments | ||||
| 			) | ||||
| 
 | ||||
| 		if doc.enable_auto_email and from_scheduler: | ||||
| 			new_to_date = getdate(today()) | ||||
| 			if doc.frequency == 'Weekly': | ||||
| 				new_to_date = add_days(new_to_date, 7) | ||||
| 			else: | ||||
| 				new_to_date = add_months(new_to_date, 1 if doc.frequency == 'Monthly' else 3) | ||||
| 			new_from_date = add_months(new_to_date, -1 * doc.filter_duration) | ||||
| 			doc.add_comment('Comment', 'Emails sent on: ' + frappe.utils.format_datetime(frappe.utils.now())) | ||||
| 			doc.db_set('to_date', new_to_date, commit=True) | ||||
| 			doc.db_set('from_date', new_from_date, commit=True) | ||||
| 		return True | ||||
| 	else: | ||||
| 		return False | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def send_auto_email(): | ||||
| 	selected = frappe.get_list('Process Statement Of Accounts', filters={'to_date': format_date(today()), 'enable_auto_email': 1}) | ||||
| 	for entry in selected: | ||||
| 		send_emails(entry.name, from_scheduler=True) | ||||
| 	return True | ||||
| @ -0,0 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| # import frappe | ||||
| import unittest | ||||
| 
 | ||||
| class TestProcessStatementOfAccounts(unittest.TestCase): | ||||
| 	pass | ||||
| @ -0,0 +1,47 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "allow_workflow": 1, | ||||
|  "creation": "2020-08-03 16:35:21.852178", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "customer", | ||||
|   "billing_email", | ||||
|   "primary_email" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "customer", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Customer", | ||||
|    "options": "Customer", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "primary_email", | ||||
|    "fieldtype": "Read Only", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Primary Contact Email" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "billing_email", | ||||
|    "fieldtype": "Read Only", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Billing Email" | ||||
|   } | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-08-03 22:55:38.875601", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Process Statement Of Accounts Customer", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| # import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class ProcessStatementOfAccountsCustomer(Document): | ||||
| 	pass | ||||
| @ -0,0 +1,30 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "creation": "2020-08-03 16:56:45.744905", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "cost_center_name" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "cost_center_name", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Cost Center", | ||||
|    "options": "Cost Center" | ||||
|   } | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-08-03 16:56:45.744905", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "PSOA Cost Center", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| # import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class PSOACostCenter(Document): | ||||
| 	pass | ||||
							
								
								
									
										0
									
								
								erpnext/accounts/doctype/psoa_project/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								erpnext/accounts/doctype/psoa_project/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										30
									
								
								erpnext/accounts/doctype/psoa_project/psoa_project.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								erpnext/accounts/doctype/psoa_project/psoa_project.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "creation": "2020-08-03 16:52:14.731978", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "project_name" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "project_name", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Project", | ||||
|    "options": "Project" | ||||
|   } | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-08-03 16:53:39.219736", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "PSOA Project", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
							
								
								
									
										10
									
								
								erpnext/accounts/doctype/psoa_project/psoa_project.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								erpnext/accounts/doctype/psoa_project/psoa_project.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| # import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class PSOAProject(Document): | ||||
| 	pass | ||||
| @ -180,7 +180,7 @@ | ||||
|    "no_copy": 1, | ||||
|    "oldfieldname": "naming_series", | ||||
|    "oldfieldtype": "Select", | ||||
|    "options": "ACC-PINV-.YYYY.-", | ||||
|    "options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-", | ||||
|    "print_hide": 1, | ||||
|    "reqd": 1, | ||||
|    "set_only_once": 1 | ||||
| @ -969,8 +969,10 @@ | ||||
|   { | ||||
|    "fieldname": "clearance_date", | ||||
|    "fieldtype": "Date", | ||||
|    "hidden": 1, | ||||
|    "label": "Clearance Date" | ||||
|    "label": "Clearance Date", | ||||
|    "no_copy": 1, | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "col_br_payments", | ||||
| @ -1332,7 +1334,7 @@ | ||||
|  "idx": 204, | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-07-24 09:46:40.405463", | ||||
|  "modified": "2020-08-03 23:20:04.466153", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Purchase Invoice", | ||||
| @ -1394,4 +1396,4 @@ | ||||
|  "timeline_field": "supplier", | ||||
|  "title_field": "title", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "hash", | ||||
|  "creation": "2013-05-22 12:43:10", | ||||
|  "doctype": "DocType", | ||||
| @ -82,6 +81,7 @@ | ||||
|   "item_tax_rate", | ||||
|   "bom", | ||||
|   "include_exploded_items", | ||||
|   "purchase_invoice_item", | ||||
|   "col_break6", | ||||
|   "purchase_order", | ||||
|   "po_detail", | ||||
| @ -769,12 +769,21 @@ | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "col_break7", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:parent.update_stock == 1", | ||||
|    "fieldname": "purchase_invoice_item", | ||||
|    "fieldtype": "Data", | ||||
|    "ignore_user_permissions": 1, | ||||
|    "label": "Purchase Invoice Item", | ||||
|    "no_copy": 1, | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   } | ||||
|  ], | ||||
|  "idx": 1, | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-04-22 10:37:35.103176", | ||||
|  "modified": "2020-08-20 11:48:01.398356", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Purchase Invoice Item", | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "allow_import": 1, | ||||
|  "allow_workflow": 1, | ||||
|  "autoname": "naming_series:", | ||||
|  "creation": "2013-05-24 19:29:05", | ||||
|  "doctype": "DocType", | ||||
| @ -217,7 +216,7 @@ | ||||
|    "no_copy": 1, | ||||
|    "oldfieldname": "naming_series", | ||||
|    "oldfieldtype": "Select", | ||||
|    "options": "ACC-SINV-.YYYY.-", | ||||
|    "options": "ACC-SINV-.YYYY.-\nACC-SINV-RET-.YYYY.-", | ||||
|    "print_hide": 1, | ||||
|    "reqd": 1, | ||||
|    "set_only_once": 1 | ||||
| @ -448,7 +447,7 @@ | ||||
|   { | ||||
|    "allow_on_submit": 1, | ||||
|    "fieldname": "po_no", | ||||
|    "fieldtype": "Small Text", | ||||
|    "fieldtype": "Data", | ||||
|    "hide_days": 1, | ||||
|    "hide_seconds": 1, | ||||
|    "label": "Customer's Purchase Order", | ||||
| @ -1947,7 +1946,7 @@ | ||||
|  "idx": 181, | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-07-18 05:07:16.725974", | ||||
|  "modified": "2020-08-27 01:56:28.532140", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Sales Invoice", | ||||
|  | ||||
| @ -1619,22 +1619,23 @@ def update_multi_mode_option(doc, pos_profile): | ||||
| 
 | ||||
| 	for pos_payment_method in pos_profile.get('payments'): | ||||
| 		pos_payment_method = pos_payment_method.as_dict() | ||||
| 		 | ||||
| 
 | ||||
| 		payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company) | ||||
| 		payment_mode[0].default = pos_payment_method.default | ||||
| 		append_payment(payment_mode[0]) | ||||
| 		if payment_mode: | ||||
| 			payment_mode[0].default = pos_payment_method.default | ||||
| 			append_payment(payment_mode[0]) | ||||
| 
 | ||||
| def get_all_mode_of_payments(doc): | ||||
| 	return frappe.db.sql(""" | ||||
| 		select mpa.default_account, mpa.parent, mp.type as type  | ||||
| 		from `tabMode of Payment Account` mpa,`tabMode of Payment` mp  | ||||
| 		select mpa.default_account, mpa.parent, mp.type as type | ||||
| 		from `tabMode of Payment Account` mpa,`tabMode of Payment` mp | ||||
| 		where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""", | ||||
| 	{'company': doc.company}, as_dict=1) | ||||
| 
 | ||||
| def get_mode_of_payment_info(mode_of_payment, company): | ||||
| 	return frappe.db.sql(""" | ||||
| 		select mpa.default_account, mpa.parent, mp.type as type  | ||||
| 		from `tabMode of Payment Account` mpa,`tabMode of Payment` mp  | ||||
| 		select mpa.default_account, mpa.parent, mp.type as type | ||||
| 		from `tabMode of Payment Account` mpa,`tabMode of Payment` mp | ||||
| 		where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""", | ||||
| 	(company, mode_of_payment), as_dict=1) | ||||
| 
 | ||||
|  | ||||
| @ -13,12 +13,13 @@ def get_data(): | ||||
| 			'Auto Repeat': 'reference_document', | ||||
| 		}, | ||||
| 		'internal_links': { | ||||
| 			'Sales Order': ['items', 'sales_order'] | ||||
| 			'Sales Order': ['items', 'sales_order'], | ||||
| 			'Delivery Note': ['items', 'delivery_note'] | ||||
| 		}, | ||||
| 		'transactions': [ | ||||
| 			{ | ||||
| 				'label': _('Payment'), | ||||
| 				'items': ['Payment Entry', 'Payment Request', 'Journal Entry', 'Invoice Discounting'] | ||||
| 				'items': ['Payment Entry', 'Payment Request', 'Journal Entry', 'Invoice Discounting', 'Dunning'] | ||||
| 			}, | ||||
| 			{ | ||||
| 				'label': _('Reference'), | ||||
|  | ||||
| @ -206,10 +206,19 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 			"rate": 14, | ||||
| 			'included_in_print_rate': 1 | ||||
| 		}) | ||||
| 		si.append("taxes", { | ||||
| 			"charge_type": "On Item Quantity", | ||||
| 			"account_head": "_Test Account Education Cess - _TC", | ||||
| 			"cost_center": "_Test Cost Center - _TC", | ||||
| 			"description": "CESS", | ||||
| 			"rate": 5, | ||||
| 			'included_in_print_rate': 1 | ||||
| 		}) | ||||
| 		si.insert() | ||||
| 
 | ||||
| 		# with inclusive tax | ||||
| 		self.assertEqual(si.net_total, 4385.96) | ||||
| 		self.assertEqual(si.items[0].net_amount, 3947.368421052631) | ||||
| 		self.assertEqual(si.net_total, 3947.37) | ||||
| 		self.assertEqual(si.grand_total, 5000) | ||||
| 
 | ||||
| 		si.reload() | ||||
| @ -222,8 +231,8 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 		si.save() | ||||
| 
 | ||||
| 		# with inclusive tax and additional discount | ||||
| 		self.assertEqual(si.net_total, 4285.96) | ||||
| 		self.assertEqual(si.grand_total, 4885.99) | ||||
| 		self.assertEqual(si.net_total, 3847.37) | ||||
| 		self.assertEqual(si.grand_total, 4886) | ||||
| 
 | ||||
| 		si.reload() | ||||
| 
 | ||||
| @ -235,7 +244,7 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 		si.save() | ||||
| 
 | ||||
| 		# with inclusive tax and additional discount | ||||
| 		self.assertEqual(si.net_total, 4298.25) | ||||
| 		self.assertEqual(si.net_total, 3859.65) | ||||
| 		self.assertEqual(si.grand_total, 4900.00) | ||||
| 
 | ||||
| 	def test_sales_invoice_discount_amount(self): | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "hash", | ||||
|  "creation": "2013-06-04 11:02:19", | ||||
|  "doctype": "DocType", | ||||
| @ -87,6 +86,7 @@ | ||||
|   "edit_references", | ||||
|   "sales_order", | ||||
|   "so_detail", | ||||
|   "sales_invoice_item", | ||||
|   "column_break_74", | ||||
|   "delivery_note", | ||||
|   "dn_detail", | ||||
| @ -790,12 +790,22 @@ | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Project", | ||||
|    "options": "Project" | ||||
|   } | ||||
|   }, | ||||
|   { | ||||
|     "depends_on": "eval:parent.update_stock == 1", | ||||
|     "fieldname": "sales_invoice_item", | ||||
|     "fieldtype": "Data", | ||||
|     "ignore_user_permissions": 1, | ||||
|     "label": "Sales Invoice Item", | ||||
|     "no_copy": 1, | ||||
|     "print_hide": 1, | ||||
|     "read_only": 1 | ||||
|    } | ||||
|  ], | ||||
|  "idx": 1, | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-07-18 12:24:41.749986", | ||||
|  "modified": "2020-08-20 11:24:41.749986", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Sales Invoice Item", | ||||
|  | ||||
| @ -64,6 +64,7 @@ | ||||
|    "fieldname": "clearance_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Clearance Date", | ||||
|    "no_copy": 1, | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
| @ -78,7 +79,7 @@ | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-05 16:51:20.091441", | ||||
|  "modified": "2020-08-03 12:45:39.986598", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Sales Invoice Payment", | ||||
|  | ||||
| @ -8,7 +8,7 @@ def get_data(): | ||||
| 		'fieldname': 'taxes_and_charges', | ||||
| 		'non_standard_fieldnames': { | ||||
| 			'Tax Rule': 'sales_tax_template', | ||||
| 			'Subscription': 'tax_template', | ||||
| 			'Subscription': 'sales_tax_template', | ||||
| 			'Restaurant': 'default_tax_template' | ||||
| 		}, | ||||
| 		'transactions': [ | ||||
|  | ||||
| @ -3,6 +3,22 @@ | ||||
| 
 | ||||
| frappe.ui.form.on('Shipping Rule', { | ||||
| 	refresh: function(frm) { | ||||
| 		frm.set_query("cost_center", function() { | ||||
| 			return { | ||||
| 				filters: { | ||||
| 					company: frm.doc.company | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 
 | ||||
| 		frm.set_query("account", function() { | ||||
| 			return { | ||||
| 				filters: { | ||||
| 					company: frm.doc.company | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 
 | ||||
| 		frm.trigger('toggle_reqd'); | ||||
| 	}, | ||||
| 	calculate_based_on: function(frm) { | ||||
| @ -12,4 +28,4 @@ frappe.ui.form.on('Shipping Rule', { | ||||
| 		frm.toggle_reqd("shipping_amount", frm.doc.calculate_based_on === 'Fixed'); | ||||
| 		frm.toggle_reqd("conditions", frm.doc.calculate_based_on !== 'Fixed'); | ||||
| 	} | ||||
| }); | ||||
| }); | ||||
|  | ||||
| @ -7,8 +7,8 @@ import unittest | ||||
| 
 | ||||
| import frappe | ||||
| from erpnext.accounts.doctype.subscription.subscription import get_prorata_factor | ||||
| from frappe.utils.data import nowdate, add_days, add_to_date, add_months, date_diff, flt, get_date_str | ||||
| 
 | ||||
| from frappe.utils.data import (nowdate, add_days, add_to_date, add_months, date_diff, flt, get_date_str, | ||||
| 	get_first_day, get_last_day) | ||||
| 
 | ||||
| def create_plan(): | ||||
| 	if not frappe.db.exists('Subscription Plan', '_Test Plan Name'): | ||||
| @ -68,14 +68,14 @@ class TestSubscription(unittest.TestCase): | ||||
| 		subscription.party_type = 'Customer' | ||||
| 		subscription.party = '_Test Customer' | ||||
| 		subscription.trial_period_start = nowdate() | ||||
| 		subscription.trial_period_end = add_days(nowdate(), 30) | ||||
| 		subscription.trial_period_end = add_months(nowdate(), 1) | ||||
| 		subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) | ||||
| 		subscription.save() | ||||
| 
 | ||||
| 		self.assertEqual(subscription.trial_period_start, nowdate()) | ||||
| 		self.assertEqual(subscription.trial_period_end, add_days(nowdate(), 30)) | ||||
| 		self.assertEqual(subscription.trial_period_end, add_months(nowdate(), 1)) | ||||
| 		self.assertEqual(add_days(subscription.trial_period_end, 1), get_date_str(subscription.current_invoice_start)) | ||||
| 		self.assertEqual(add_days(subscription.current_invoice_start, 30), get_date_str(subscription.current_invoice_end)) | ||||
| 		self.assertEqual(add_to_date(subscription.current_invoice_start, months=1, days=-1), get_date_str(subscription.current_invoice_end)) | ||||
| 		self.assertEqual(subscription.invoices, []) | ||||
| 		self.assertEqual(subscription.status, 'Trialling') | ||||
| 
 | ||||
|  | ||||
| @ -1,134 +1,66 @@ | ||||
| { | ||||
|  "allow_copy": 0, | ||||
|  "allow_events_in_timeline": 0, | ||||
|  "allow_guest_to_view": 0, | ||||
|  "allow_import": 0, | ||||
|  "allow_rename": 0, | ||||
|  "actions": [], | ||||
|  "allow_rename": 1, | ||||
|  "autoname": "field:title", | ||||
|  "beta": 0, | ||||
|  "creation": "2018-11-22 23:38:39.668804", | ||||
|  "custom": 0, | ||||
|  "docstatus": 0, | ||||
|  "doctype": "DocType", | ||||
|  "document_type": "", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "title" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "fieldname": "title", | ||||
|    "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": "Title", | ||||
|    "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": 1 | ||||
|   } | ||||
|  ], | ||||
|  "has_web_view": 0, | ||||
|  "hide_heading": 0, | ||||
|  "hide_toolbar": 0, | ||||
|  "idx": 0, | ||||
|  "image_view": 0, | ||||
|  "in_create": 0, | ||||
|  "is_submittable": 0, | ||||
|  "issingle": 0, | ||||
|  "istable": 0, | ||||
|  "max_attachments": 0, | ||||
|  "modified": "2020-01-15 17:14:28.951793",  | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-08-30 19:41:25.783852", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Tax Category", | ||||
|  "name_case": "", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "amend": 0, | ||||
|    "cancel": 0, | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "if_owner": 0, | ||||
|    "import": 0, | ||||
|    "permlevel": 0, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "System Manager", | ||||
|    "set_user_permissions": 0, | ||||
|    "share": 1, | ||||
|    "submit": 0, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "amend": 0, | ||||
|    "cancel": 0, | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "if_owner": 0, | ||||
|    "import": 0, | ||||
|    "permlevel": 0, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Accounts Manager", | ||||
|    "set_user_permissions": 0, | ||||
|    "share": 1, | ||||
|    "submit": 0, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "amend": 0, | ||||
|    "cancel": 0, | ||||
|    "create": 0, | ||||
|    "delete": 0, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "if_owner": 0, | ||||
|    "import": 0, | ||||
|    "permlevel": 0, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Accounts User", | ||||
|    "set_user_permissions": 0, | ||||
|    "share": 1, | ||||
|    "submit": 0, | ||||
|    "write": 0 | ||||
|    "share": 1 | ||||
|   } | ||||
|  ], | ||||
|  "quick_entry": 1, | ||||
|  "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, | ||||
|  "track_views": 0 | ||||
| } | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -45,8 +45,8 @@ def validate_accounting_period(gl_map): | ||||
| 			}, as_dict=1) | ||||
| 
 | ||||
| 	if accounting_periods: | ||||
| 		frappe.throw(_("You can't create accounting entries in the closed accounting period {0}") | ||||
| 			.format(accounting_periods[0].name), ClosedAccountingPeriod) | ||||
| 		frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}") | ||||
| 			.format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod) | ||||
| 
 | ||||
| def process_gl_map(gl_map, merge_entries=True): | ||||
| 	if merge_entries: | ||||
| @ -301,8 +301,9 @@ def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, | ||||
| 			}) | ||||
| 
 | ||||
| 	if gl_entries: | ||||
| 		set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no']) | ||||
| 		validate_accounting_period(gl_entries) | ||||
| 		check_freezing_date(gl_entries[0]["posting_date"], adv_adj) | ||||
| 		set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no']) | ||||
| 
 | ||||
| 		for entry in gl_entries: | ||||
| 			entry['name'] = None | ||||
| @ -342,7 +343,7 @@ def set_as_cancel(voucher_type, voucher_no): | ||||
| 	""" | ||||
| 		Set is_cancelled=1 in all original gl entries for the voucher | ||||
| 	""" | ||||
| 	frappe.db.sql("""update `tabGL Entry` set is_cancelled = 1, | ||||
| 	frappe.db.sql("""UPDATE `tabGL Entry` SET is_cancelled = 1, | ||||
| 		modified=%s, modified_by=%s | ||||
| 		where voucher_type=%s and voucher_no=%s and is_cancelled = 0""", | ||||
| 		(now(), frappe.session.user, voucher_type, voucher_no)) | ||||
|  | ||||
| @ -290,6 +290,7 @@ def get_matching_transactions_payments(description_matching): | ||||
| 		return [] | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def payment_entry_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account") | ||||
| 	if not account: | ||||
| @ -319,6 +320,7 @@ def payment_entry_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	) | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account") | ||||
| 
 | ||||
| @ -355,6 +357,7 @@ def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	) | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	return frappe.db.sql(""" | ||||
| 		SELECT | ||||
|  | ||||
| @ -611,7 +611,7 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None, futur | ||||
| 			cond = "posting_date <= '{0}'".format(posting_date) | ||||
| 
 | ||||
| 	if company: | ||||
| 		cond += "and company = '{0}'".format(company) | ||||
| 		cond += "and company = {0}".format(frappe.db.escape(company)) | ||||
| 
 | ||||
| 	data = frappe.db.sql(""" SELECT party, sum({0}) as amount | ||||
| 		FROM `tabGL Entry` | ||||
|  | ||||
| @ -643,8 +643,10 @@ class ReceivablePayableReport(object): | ||||
| 		account_type = "Receivable" if self.party_type == "Customer" else "Payable" | ||||
| 		accounts = [d.name for d in frappe.get_all("Account", | ||||
| 			filters={"account_type": account_type, "company": self.filters.company})] | ||||
| 		conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts))) | ||||
| 		values += accounts | ||||
| 
 | ||||
| 		if accounts: | ||||
| 			conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts))) | ||||
| 			values += accounts | ||||
| 
 | ||||
| 	def add_customer_filters(self, conditions, values): | ||||
| 		if self.filters.get("customer_group"): | ||||
|  | ||||
| @ -71,7 +71,22 @@ frappe.query_reports["Budget Variance Report"] = { | ||||
| 			fieldtype: "Check", | ||||
| 			default: 0, | ||||
| 		}, | ||||
| 	] | ||||
| 	], | ||||
| 	"formatter": function (value, row, column, data, default_formatter) { | ||||
| 		value = default_formatter(value, row, column, data); | ||||
| 
 | ||||
| 		if (column.fieldname.includes('variance')) { | ||||
| 
 | ||||
| 			if (data[column.fieldname] < 0) { | ||||
| 				value = "<span style='color:red'>" + value + "</span>"; | ||||
| 			} | ||||
| 			else if (data[column.fieldname] > 0) { | ||||
| 				value = "<span style='color:green'>" + value + "</span>"; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return value; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| erpnext.dimension_filters.forEach((dimension) => { | ||||
|  | ||||
| @ -378,7 +378,7 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g | ||||
| 		if filters and filters.get('presentation_currency') != d.default_currency: | ||||
| 			currency_info['company'] = d.name | ||||
| 			currency_info['company_currency'] = d.default_currency | ||||
| 			convert_to_presentation_currency(gl_entries, currency_info) | ||||
| 			convert_to_presentation_currency(gl_entries, currency_info, filters.get('company')) | ||||
| 
 | ||||
| 		for entry in gl_entries: | ||||
| 			key = entry.account_number or entry.account_name | ||||
|  | ||||
| @ -173,7 +173,7 @@ class PartyLedgerSummaryReport(object): | ||||
| 			from `tabGL Entry` gle | ||||
| 			{join} | ||||
| 			where | ||||
| 				gle.docstatus < 2 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != '' | ||||
| 				gle.docstatus < 2 and gle.is_cancelled = 0 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != '' | ||||
| 				and gle.posting_date <= %(to_date)s {conditions} | ||||
| 			order by gle.posting_date | ||||
| 		""".format(join=join, join_field=join_field, conditions=conditions), self.filters, as_dict=True) | ||||
| @ -248,7 +248,7 @@ class PartyLedgerSummaryReport(object): | ||||
| 			from | ||||
| 				`tabGL Entry` | ||||
| 			where | ||||
| 				docstatus < 2 | ||||
| 				docstatus < 2 and is_cancelled = 0 | ||||
| 				and (voucher_type, voucher_no) in ( | ||||
| 					select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc | ||||
| 					where acc.name = gle.account and acc.account_type = '{income_or_expense}' | ||||
|  | ||||
| @ -14,7 +14,7 @@ import frappe, erpnext | ||||
| from erpnext.accounts.report.utils import get_currency, convert_to_presentation_currency | ||||
| from erpnext.accounts.utils import get_fiscal_year | ||||
| from frappe import _ | ||||
| from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr) | ||||
| from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr, cint) | ||||
| 
 | ||||
| from six import itervalues | ||||
| from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions, get_dimension_with_children | ||||
| @ -46,7 +46,7 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_ | ||||
| 	start_date = year_start_date | ||||
| 	months = get_months(year_start_date, year_end_date) | ||||
| 
 | ||||
| 	for i in range(math.ceil(months / months_to_add)): | ||||
| 	for i in range(cint(math.ceil(months / months_to_add))): | ||||
| 		period = frappe._dict({ | ||||
| 			"from_date": start_date | ||||
| 		}) | ||||
| @ -423,7 +423,7 @@ def set_gl_entries_by_account( | ||||
| 				distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec | ||||
| 
 | ||||
| 		if filters and filters.get('presentation_currency'): | ||||
| 			convert_to_presentation_currency(gl_entries, get_currency(filters)) | ||||
| 			convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get('company')) | ||||
| 
 | ||||
| 		for entry in gl_entries: | ||||
| 			gl_entries_by_account.setdefault(entry.account, []).append(entry) | ||||
|  | ||||
| @ -146,6 +146,12 @@ frappe.query_reports["General Ledger"] = { | ||||
| 				return frappe.db.get_link_options('Project', txt); | ||||
| 			} | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname": "include_dimensions", | ||||
| 			"label": __("Consider Accounting Dimensions"), | ||||
| 			"fieldtype": "Check", | ||||
| 			"default": 0 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname": "show_opening_entries", | ||||
| 			"label": __("Show Opening Entries"), | ||||
|  | ||||
| @ -43,8 +43,11 @@ def execute(filters=None): | ||||
| 
 | ||||
| 
 | ||||
| def validate_filters(filters, account_details): | ||||
| 	if not filters.get('company'): | ||||
| 		frappe.throw(_('{0} is mandatory').format(_('Company'))) | ||||
| 	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.get("account") and not account_details.get(filters.account): | ||||
| 		frappe.throw(_("Account {0} does not exists").format(filters.account)) | ||||
| @ -106,15 +109,20 @@ def set_account_currency(filters): | ||||
| 	return filters | ||||
| 
 | ||||
| def get_result(filters, account_details): | ||||
| 	gl_entries = get_gl_entries(filters) | ||||
| 	accounting_dimensions = [] | ||||
| 	if filters.get("include_dimensions"): | ||||
| 		accounting_dimensions = get_accounting_dimensions() | ||||
| 
 | ||||
| 	data = get_data_with_opening_closing(filters, account_details, gl_entries) | ||||
| 	gl_entries = get_gl_entries(filters, accounting_dimensions) | ||||
| 
 | ||||
| 	data = get_data_with_opening_closing(filters, account_details, | ||||
| 		accounting_dimensions, gl_entries) | ||||
| 
 | ||||
| 	result = get_result_as_list(data, filters) | ||||
| 
 | ||||
| 	return result | ||||
| 
 | ||||
| def get_gl_entries(filters): | ||||
| def get_gl_entries(filters, accounting_dimensions): | ||||
| 	currency_map = get_currency(filters) | ||||
| 	select_fields = """, debit, credit, debit_in_account_currency, | ||||
| 		credit_in_account_currency """ | ||||
| @ -128,11 +136,15 @@ def get_gl_entries(filters): | ||||
| 		filters['company_fb'] = frappe.db.get_value("Company", | ||||
| 			filters.get("company"), 'default_finance_book') | ||||
| 
 | ||||
| 	dimension_fields = "" | ||||
| 	if accounting_dimensions: | ||||
| 		dimension_fields = ', '.join(accounting_dimensions) + ',' | ||||
| 
 | ||||
| 	distributed_cost_center_query = "" | ||||
| 	if filters and filters.get('cost_center'): | ||||
| 		select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency, | ||||
| 		credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """ | ||||
| 		 | ||||
| 
 | ||||
| 		distributed_cost_center_query = """ | ||||
| 		UNION ALL | ||||
| 		SELECT name as gl_entry, | ||||
| @ -141,12 +153,12 @@ def get_gl_entries(filters): | ||||
| 			party_type, | ||||
| 			party, | ||||
| 			voucher_type, | ||||
| 			voucher_no, | ||||
| 			voucher_no, {dimension_fields} | ||||
| 			cost_center, project, | ||||
| 			against_voucher_type, | ||||
| 			against_voucher, | ||||
| 			account_currency, | ||||
| 			remarks, against,  | ||||
| 			remarks, against, | ||||
| 			is_opening, `tabGL Entry`.creation {select_fields_with_percentage} | ||||
| 		FROM `tabGL Entry`, | ||||
| 		( | ||||
| @ -160,13 +172,14 @@ def get_gl_entries(filters): | ||||
| 		{conditions} | ||||
| 		AND posting_date <= %(to_date)s | ||||
| 		AND cost_center = DCC_allocation.parent | ||||
| 		""".format(select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", '')) | ||||
| 		""".format(dimension_fields=dimension_fields,select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", '')) | ||||
| 
 | ||||
| 	gl_entries = frappe.db.sql( | ||||
| 		""" | ||||
| 		select | ||||
| 			name as gl_entry, posting_date, account, party_type, party, | ||||
| 			voucher_type, voucher_no, cost_center, project, | ||||
| 			voucher_type, voucher_no, {dimension_fields} | ||||
| 			cost_center, project, | ||||
| 			against_voucher_type, against_voucher, account_currency, | ||||
| 			remarks, against, is_opening, creation {select_fields} | ||||
| 		from `tabGL Entry` | ||||
| @ -174,13 +187,13 @@ def get_gl_entries(filters): | ||||
| 		{distributed_cost_center_query} | ||||
| 		{order_by_statement} | ||||
| 		""".format( | ||||
| 			select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query, | ||||
| 			dimension_fields=dimension_fields, select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query, | ||||
| 			order_by_statement=order_by_statement | ||||
| 		), | ||||
| 		filters, as_dict=1) | ||||
| 
 | ||||
| 	if filters.get('presentation_currency'): | ||||
| 		return convert_to_presentation_currency(gl_entries, currency_map) | ||||
| 		return convert_to_presentation_currency(gl_entries, currency_map, filters.get('company')) | ||||
| 	else: | ||||
| 		return gl_entries | ||||
| 
 | ||||
| @ -247,12 +260,12 @@ def get_conditions(filters): | ||||
| 	return "and {}".format(" and ".join(conditions)) if conditions else "" | ||||
| 
 | ||||
| 
 | ||||
| def get_data_with_opening_closing(filters, account_details, gl_entries): | ||||
| def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries): | ||||
| 	data = [] | ||||
| 
 | ||||
| 	gle_map = initialize_gle_map(gl_entries, filters) | ||||
| 
 | ||||
| 	totals, entries = get_accountwise_gle(filters, gl_entries, gle_map) | ||||
| 	totals, entries = get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map) | ||||
| 
 | ||||
| 	# Opening for filtered account | ||||
| 	data.append(totals.opening) | ||||
| @ -318,7 +331,7 @@ def initialize_gle_map(gl_entries, filters): | ||||
| 	return gle_map | ||||
| 
 | ||||
| 
 | ||||
| def get_accountwise_gle(filters, gl_entries, gle_map): | ||||
| def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): | ||||
| 	totals = get_totals_dict() | ||||
| 	entries = [] | ||||
| 	consolidated_gle = OrderedDict() | ||||
| @ -350,8 +363,11 @@ def get_accountwise_gle(filters, gl_entries, gle_map): | ||||
| 			if filters.get("group_by") != _('Group by Voucher (Consolidated)'): | ||||
| 				gle_map[gle.get(group_by)].entries.append(gle) | ||||
| 			elif filters.get("group_by") == _('Group by Voucher (Consolidated)'): | ||||
| 				key = (gle.get("voucher_type"), gle.get("voucher_no"), | ||||
| 					gle.get("account"), gle.get("cost_center")) | ||||
| 				keylist = [gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account")] | ||||
| 				for dim in accounting_dimensions: | ||||
| 					keylist.append(gle.get(dim)) | ||||
| 				keylist.append(gle.get("cost_center")) | ||||
| 				key = tuple(keylist) | ||||
| 				if key not in consolidated_gle: | ||||
| 					consolidated_gle.setdefault(key, gle) | ||||
| 				else: | ||||
| @ -478,7 +494,19 @@ def get_columns(filters): | ||||
| 			"options": "Project", | ||||
| 			"fieldname": "project", | ||||
| 			"width": 100 | ||||
| 		}, | ||||
| 		} | ||||
| 	]) | ||||
| 
 | ||||
| 	if filters.get("include_dimensions"): | ||||
| 		for dim in get_accounting_dimensions(as_list = False): | ||||
| 			columns.append({ | ||||
| 				"label": _(dim.label), | ||||
| 				"options": dim.label, | ||||
| 				"fieldname": dim.fieldname, | ||||
| 				"width": 100 | ||||
| 			}) | ||||
| 
 | ||||
| 	columns.extend([ | ||||
| 		{ | ||||
| 			"label": _("Cost Center"), | ||||
| 			"options": "Cost Center", | ||||
|  | ||||
| @ -1,24 +1,23 @@ | ||||
| { | ||||
|  "add_total_row": 0,  | ||||
|  "apply_user_permissions": 1,  | ||||
|  "creation": "2013-02-25 17:03:34",  | ||||
|  "disabled": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "Report",  | ||||
|  "idx": 3,  | ||||
|  "is_standard": "Yes",  | ||||
|  "modified": "2017-02-24 20:12:22.464240",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Accounts",  | ||||
|  "name": "Gross Profit",  | ||||
|  "owner": "Administrator",  | ||||
|  "ref_doctype": "Sales Invoice",  | ||||
|  "report_name": "Gross Profit",  | ||||
|  "report_type": "Script Report",  | ||||
|  "add_total_row": 1, | ||||
|  "creation": "2013-02-25 17:03:34", | ||||
|  "disabled": 0, | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Report", | ||||
|  "idx": 3, | ||||
|  "is_standard": "Yes", | ||||
|  "modified": "2020-08-13 11:26:39.112352", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Gross Profit", | ||||
|  "owner": "Administrator", | ||||
|  "ref_doctype": "Sales Invoice", | ||||
|  "report_name": "Gross Profit", | ||||
|  "report_type": "Script Report", | ||||
|  "roles": [ | ||||
|   { | ||||
|    "role": "Accounts Manager" | ||||
|   },  | ||||
|   }, | ||||
|   { | ||||
|    "role": "Accounts User" | ||||
|   } | ||||
|  | ||||
| @ -6,10 +6,6 @@ from erpnext.accounts.doctype.fiscal_year.fiscal_year import get_from_and_to_dat | ||||
| from frappe.utils import cint, get_datetime_str, formatdate, flt | ||||
| 
 | ||||
| __exchange_rates = {} | ||||
| P_OR_L_ACCOUNTS = list( | ||||
| 	sum(frappe.get_list('Account', fields=['name'], or_filters=[{'root_type': 'Income'}, {'root_type': 'Expense'}], as_list=True), ()) | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| def get_currency(filters): | ||||
| 	""" | ||||
| @ -73,18 +69,7 @@ def get_rate_as_at(date, from_currency, to_currency): | ||||
| 
 | ||||
| 	return rate | ||||
| 
 | ||||
| 
 | ||||
| def is_p_or_l_account(account_name): | ||||
| 	""" | ||||
| 	Check if the given `account name` is an `Account` with `root_type` of either 'Income' | ||||
| 	or 'Expense'. | ||||
| 	:param account_name: | ||||
| 	:return: Boolean | ||||
| 	""" | ||||
| 	return account_name in P_OR_L_ACCOUNTS | ||||
| 
 | ||||
| 
 | ||||
| def convert_to_presentation_currency(gl_entries, currency_info): | ||||
| def convert_to_presentation_currency(gl_entries, currency_info, company): | ||||
| 	""" | ||||
| 	Take a list of GL Entries and change the 'debit' and 'credit' values to currencies | ||||
| 	in `currency_info`. | ||||
| @ -96,6 +81,9 @@ def convert_to_presentation_currency(gl_entries, currency_info): | ||||
| 	presentation_currency = currency_info['presentation_currency'] | ||||
| 	company_currency = currency_info['company_currency'] | ||||
| 
 | ||||
| 	pl_accounts = [d.name for d in frappe.get_list('Account', | ||||
| 		filters={'report_type': 'Profit and Loss', 'company': company})] | ||||
| 
 | ||||
| 	for entry in gl_entries: | ||||
| 		account = entry['account'] | ||||
| 		debit = flt(entry['debit']) | ||||
| @ -107,7 +95,7 @@ def convert_to_presentation_currency(gl_entries, currency_info): | ||||
| 		if account_currency != presentation_currency: | ||||
| 			value = debit or credit | ||||
| 
 | ||||
| 			date = currency_info['report_date'] if not is_p_or_l_account(account) else entry['posting_date'] | ||||
| 			date = entry['posting_date'] if account in pl_accounts else currency_info['report_date'] | ||||
| 			converted_value = convert(value, presentation_currency, company_currency, date) | ||||
| 
 | ||||
| 			if entry.get('debit'): | ||||
|  | ||||
| @ -106,6 +106,7 @@ def update_maintenance_log(asset_maintenance, item_code, item_name, task): | ||||
| 		maintenance_log.save() | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_team_members(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	return frappe.db.get_values('Maintenance Team Member', { 'parent': filters.get("maintenance_team") }) | ||||
| 
 | ||||
|  | ||||
| @ -11,7 +11,7 @@ from erpnext.assets.doctype.asset_maintenance.asset_maintenance import calculate | ||||
| 
 | ||||
| class AssetMaintenanceLog(Document): | ||||
| 	def validate(self): | ||||
| 		if getdate(self.due_date) < getdate(nowdate()): | ||||
| 		if getdate(self.due_date) < getdate(nowdate()) and self.maintenance_status not in ["Completed", "Cancelled"]: | ||||
| 			self.maintenance_status = "Overdue" | ||||
| 
 | ||||
| 		if self.maintenance_status == "Completed" and not self.completion_date: | ||||
| @ -41,6 +41,7 @@ class AssetMaintenanceLog(Document): | ||||
| 		asset_maintenance_doc.save() | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_maintenance_tasks(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	asset_maintenance_tasks = frappe.db.get_values('Asset Maintenance Task', {'parent':filters.get("asset_maintenance")}, 'maintenance_task') | ||||
| 	return asset_maintenance_tasks | ||||
|  | ||||
| @ -1,14 +1,15 @@ | ||||
| frappe.listview_settings['Asset Maintenance Log'] = { | ||||
| 	add_fields: ["maintenance_status"], | ||||
| 	has_indicator_for_draft: 1, | ||||
| 	get_indicator: function(doc) { | ||||
| 		if(doc.maintenance_status=="Pending") { | ||||
| 			return [__("Pending"), "orange"]; | ||||
| 		} else if(doc.maintenance_status=="Completed") { | ||||
| 			return [__("Completed"), "green"]; | ||||
| 		} else if(doc.maintenance_status=="Cancelled") { | ||||
| 			return [__("Cancelled"), "red"]; | ||||
| 		} else if(doc.maintenance_status=="Overdue") { | ||||
| 			return [__("Overdue"), "red"]; | ||||
| 		if (doc.maintenance_status=="Planned") { | ||||
| 			return [__(doc.maintenance_status), "orange", "status,=," + doc.maintenance_status]; | ||||
| 		} else if (doc.maintenance_status=="Completed") { | ||||
| 			return [__(doc.maintenance_status), "green", "status,=," + doc.maintenance_status]; | ||||
| 		} else if (doc.maintenance_status=="Cancelled") { | ||||
| 			return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status]; | ||||
| 		} else if (doc.maintenance_status=="Overdue") { | ||||
| 			return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status]; | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| @ -8,6 +8,7 @@ from frappe import _ | ||||
| from frappe.utils import flt, getdate, cint, date_diff, formatdate | ||||
| from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts | ||||
| from frappe.model.document import Document | ||||
| from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts | ||||
| 
 | ||||
| class AssetValueAdjustment(Document): | ||||
| 	def validate(self): | ||||
| @ -53,17 +54,33 @@ class AssetValueAdjustment(Document): | ||||
| 		je.company = self.company | ||||
| 		je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount) | ||||
| 
 | ||||
| 		je.append("accounts", { | ||||
| 		credit_entry = { | ||||
| 			"account": accumulated_depreciation_account, | ||||
| 			"credit_in_account_currency": self.difference_amount, | ||||
| 			"cost_center": depreciation_cost_center or self.cost_center | ||||
| 		}) | ||||
| 		} | ||||
| 
 | ||||
| 		je.append("accounts", { | ||||
| 		debit_entry = { | ||||
| 			"account": depreciation_expense_account, | ||||
| 			"debit_in_account_currency": self.difference_amount, | ||||
| 			"cost_center": depreciation_cost_center or self.cost_center | ||||
| 		}) | ||||
| 		} | ||||
| 
 | ||||
| 		accounting_dimensions = get_checks_for_pl_and_bs_accounts() | ||||
| 
 | ||||
| 		for dimension in accounting_dimensions: | ||||
| 			if dimension.get('mandatory_for_bs'): | ||||
| 				credit_entry.update({ | ||||
| 					dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension') | ||||
| 				}) | ||||
| 
 | ||||
| 			if dimension.get('mandatory_for_pl'): | ||||
| 				debit_entry.update({ | ||||
| 					dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension') | ||||
| 				}) | ||||
| 		 | ||||
| 		je.append("accounts", credit_entry) | ||||
| 		je.append("accounts", debit_entry) | ||||
| 
 | ||||
| 		je.flags.ignore_permissions = True | ||||
| 		je.submit() | ||||
|  | ||||
| @ -94,7 +94,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( | ||||
| 				if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) { | ||||
| 					this.frm.add_custom_button(__('Update Items'), () => { | ||||
| 						erpnext.utils.update_child_items({ | ||||
| 							frm: frm, | ||||
| 							frm: this.frm, | ||||
| 							child_docname: "items", | ||||
| 							child_doctype: "Purchase Order Detail", | ||||
| 							cannot_add_row: false, | ||||
|  | ||||
| @ -207,6 +207,7 @@ def get_list_context(context=None): | ||||
| 	return list_context | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	return frappe.db.sql("""select `tabContact`.name from `tabContact`, `tabDynamic Link` | ||||
| 		where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s | ||||
|  | ||||
| @ -11,6 +11,8 @@ from erpnext.stock.doctype.item.test_item import make_item | ||||
| 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 create_supplier_quotation | ||||
| from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity | ||||
| from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq | ||||
| 
 | ||||
| class TestRequestforQuotation(unittest.TestCase): | ||||
| 	def test_quote_status(self): | ||||
| @ -110,6 +112,23 @@ class TestRequestforQuotation(unittest.TestCase): | ||||
| 		self.assertEqual(supplier_quotation.items[0].qty, 5) | ||||
| 		self.assertEqual(supplier_quotation.items[0].stock_qty, 10) | ||||
| 
 | ||||
| 	def test_make_rfq_from_opportunity(self): | ||||
| 		opportunity = make_opportunity(with_items=1) | ||||
| 		supplier_data = get_supplier_data() | ||||
| 		rfq = make_rfq(opportunity.name) | ||||
| 
 | ||||
| 		self.assertEqual(len(rfq.get("items")), len(opportunity.get("items"))) | ||||
| 		rfq.message_for_supplier = 'Please supply the specified items at the best possible rates.' | ||||
| 
 | ||||
| 		for item in rfq.items: | ||||
| 			item.warehouse = "_Test Warehouse - _TC" | ||||
| 
 | ||||
| 		for data in supplier_data: | ||||
| 			rfq.append('suppliers', data) | ||||
| 
 | ||||
| 		rfq.status = 'Draft' | ||||
| 		rfq.submit() | ||||
| 
 | ||||
| def make_request_for_quotation(**args): | ||||
| 	""" | ||||
| 	:param supplier_data: List containing supplier data | ||||
|  | ||||
| @ -12,7 +12,22 @@ frappe.query_reports["Quoted Item Comparison"] = { | ||||
| 			"reqd": 1 | ||||
| 		}, | ||||
| 		{ | ||||
| 			reqd: 1, | ||||
| 			"fieldname":"from_date", | ||||
| 			"label": __("From Date"), | ||||
| 			"fieldtype": "Date", | ||||
| 			"width": "80", | ||||
| 			"reqd": 1, | ||||
| 			"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"to_date", | ||||
| 			"label": __("To Date"), | ||||
| 			"fieldtype": "Date", | ||||
| 			"width": "80", | ||||
| 			"reqd": 1, | ||||
| 			"default": frappe.datetime.get_today() | ||||
| 		}, | ||||
| 		{ | ||||
| 			default: "", | ||||
| 			options: "Item", | ||||
| 			label: __("Item"), | ||||
| @ -45,13 +60,12 @@ frappe.query_reports["Quoted Item Comparison"] = { | ||||
| 			} | ||||
| 		}, | ||||
| 		{ | ||||
| 			fieldtype: "Link", | ||||
| 			fieldtype: "MultiSelectList", | ||||
| 			label: __("Supplier Quotation"), | ||||
| 			options: "Supplier Quotation", | ||||
| 			fieldname: "supplier_quotation", | ||||
| 			default: "", | ||||
| 			get_query: () => { | ||||
| 				return { filters: { "docstatus": ["<", 2] } } | ||||
| 			get_data: function(txt) { | ||||
| 				return frappe.db.get_link_options('Supplier Quotation', txt, {'docstatus': ["<", 2]}); | ||||
| 			} | ||||
| 		}, | ||||
| 		{ | ||||
| @ -63,9 +77,30 @@ frappe.query_reports["Quoted Item Comparison"] = { | ||||
| 			get_query: () => { | ||||
| 				return { filters: { "docstatus": ["<", 2] } } | ||||
| 			} | ||||
| 		}, | ||||
| 		{ | ||||
| 			fieldtype: "Check", | ||||
| 			label: __("Include Expired"), | ||||
| 			fieldname: "include_expired", | ||||
| 			default: 0 | ||||
| 		} | ||||
| 	], | ||||
| 
 | ||||
| 	formatter: (value, row, column, data, default_formatter) => { | ||||
| 		value = default_formatter(value, row, column, data); | ||||
| 
 | ||||
| 		if(column.fieldname === "valid_till" && data.valid_till){ | ||||
| 			if(frappe.datetime.get_diff(data.valid_till, frappe.datetime.nowdate()) <= 1){ | ||||
| 				value = `<div style="color:red">${value}</div>`; | ||||
| 			} | ||||
| 			else if (frappe.datetime.get_diff(data.valid_till, frappe.datetime.nowdate()) <= 7){ | ||||
| 				value = `<div style="color:darkorange">${value}</div>`; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return value; | ||||
| 	}, | ||||
| 
 | ||||
| 	onload: (report) => { | ||||
| 		// Create a button for setting the default supplier
 | ||||
| 		report.page.add_inner_button(__("Select Default Supplier"), () => { | ||||
|  | ||||
| @ -16,44 +16,49 @@ def execute(filters=None): | ||||
| 	supplier_quotation_data = get_data(filters, conditions) | ||||
| 	columns = get_columns() | ||||
| 
 | ||||
| 	data, chart_data = prepare_data(supplier_quotation_data) | ||||
| 	data, chart_data = prepare_data(supplier_quotation_data, filters) | ||||
| 	message = get_message() | ||||
| 
 | ||||
| 	return columns, data, None, chart_data | ||||
| 	return columns, data, message, chart_data | ||||
| 
 | ||||
| def get_conditions(filters): | ||||
| 	conditions = "" | ||||
| 	if filters.get("item_code"): | ||||
| 		conditions += " AND sqi.item_code = %(item_code)s" | ||||
| 
 | ||||
| 	if filters.get("supplier_quotation"): | ||||
| 		conditions += " AND sqi.parent = %(supplier_quotation)s" | ||||
| 		conditions += " AND sqi.parent in %(supplier_quotation)s" | ||||
| 
 | ||||
| 	if filters.get("request_for_quotation"): | ||||
| 		conditions += " AND sqi.request_for_quotation = %(request_for_quotation)s" | ||||
| 
 | ||||
| 	if filters.get("supplier"): | ||||
| 		conditions += " AND sq.supplier in %(supplier)s" | ||||
| 
 | ||||
| 	if not filters.get("include_expired"): | ||||
| 		conditions += " AND sq.status != 'Expired'" | ||||
| 
 | ||||
| 	return conditions | ||||
| 
 | ||||
| def get_data(filters, conditions): | ||||
| 	if not filters.get("item_code"): | ||||
| 		return [] | ||||
| 
 | ||||
| 	supplier_quotation_data = frappe.db.sql("""SELECT | ||||
| 		sqi.parent, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation, | ||||
| 		sq.supplier | ||||
| 		sqi.parent, sqi.item_code, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation, | ||||
| 		sqi.lead_time_days, sq.supplier, sq.valid_till | ||||
| 		FROM | ||||
| 			`tabSupplier Quotation Item` sqi, | ||||
| 			`tabSupplier Quotation` sq | ||||
| 		WHERE | ||||
| 			sqi.item_code = %(item_code)s | ||||
| 			AND sqi.parent = sq.name | ||||
| 			sqi.parent = sq.name | ||||
| 			AND sqi.docstatus < 2 | ||||
| 			AND sq.company = %(company)s | ||||
| 			AND sq.status != 'Expired' | ||||
| 			{0}""".format(conditions), filters, as_dict=1) | ||||
| 			AND sq.transaction_date between %(from_date)s and %(to_date)s | ||||
| 			{0} | ||||
| 			order by sq.transaction_date, sqi.item_code""".format(conditions), filters, as_dict=1) | ||||
| 
 | ||||
| 	return supplier_quotation_data | ||||
| 
 | ||||
| def prepare_data(supplier_quotation_data): | ||||
| 	out, suppliers, qty_list = [], [], [] | ||||
| def prepare_data(supplier_quotation_data, filters): | ||||
| 	out, suppliers, qty_list, chart_data = [], [], [], [] | ||||
| 	supplier_wise_map = defaultdict(list) | ||||
| 	supplier_qty_price_map = {} | ||||
| 
 | ||||
| @ -70,20 +75,24 @@ def prepare_data(supplier_quotation_data): | ||||
| 			exchange_rate = 1 | ||||
| 
 | ||||
| 		row = { | ||||
| 			"item_code": data.get('item_code'), | ||||
| 			"quotation": data.get("parent"), | ||||
| 			"qty": data.get("qty"), | ||||
| 			"price": flt(data.get("rate") * exchange_rate, float_precision), | ||||
| 			"uom": data.get("uom"), | ||||
| 			"request_for_quotation": data.get("request_for_quotation"), | ||||
| 			"valid_till": data.get('valid_till'), | ||||
| 			"lead_time_days": data.get('lead_time_days') | ||||
| 		} | ||||
| 
 | ||||
| 		# map for report view of form {'supplier1':[{},{},...]} | ||||
| 		supplier_wise_map[supplier].append(row) | ||||
| 
 | ||||
| 		# map for chart preparation of the form {'supplier1': {'qty': 'price'}} | ||||
| 		if not supplier in supplier_qty_price_map: | ||||
| 			supplier_qty_price_map[supplier] = {} | ||||
| 		supplier_qty_price_map[supplier][row["qty"]] = row["price"] | ||||
| 		if filters.get("item_code"): | ||||
| 			if not supplier in supplier_qty_price_map: | ||||
| 				supplier_qty_price_map[supplier] = {} | ||||
| 			supplier_qty_price_map[supplier][row["qty"]] = row["price"] | ||||
| 
 | ||||
| 		suppliers.append(supplier) | ||||
| 		qty_list.append(data.get("qty")) | ||||
| @ -97,7 +106,8 @@ def prepare_data(supplier_quotation_data): | ||||
| 		for entry in supplier_wise_map[supplier]: | ||||
| 			out.append(entry) | ||||
| 
 | ||||
| 	chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map) | ||||
| 	if filters.get("item_code"): | ||||
| 		chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map) | ||||
| 
 | ||||
| 	return out, chart_data | ||||
| 
 | ||||
| @ -117,9 +127,10 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map): | ||||
| 				data_points_map[qty].append(None) | ||||
| 
 | ||||
| 	dataset = [] | ||||
| 	currency_symbol = frappe.db.get_value("Currency", frappe.db.get_default("currency"), "symbol") | ||||
| 	for qty in qty_list: | ||||
| 		datapoints = { | ||||
| 			"name": _("Price for Qty ") + str(qty), | ||||
| 			"name": currency_symbol + " (Qty " + str(qty) + " )", | ||||
| 			"values": data_points_map[qty] | ||||
| 		} | ||||
| 		dataset.append(datapoints) | ||||
| @ -140,14 +151,21 @@ def get_columns(): | ||||
| 		"label": _("Supplier"), | ||||
| 		"fieldtype": "Link", | ||||
| 		"options": "Supplier", | ||||
| 		"width": 150 | ||||
| 	}, | ||||
| 	{ | ||||
| 		"fieldname": "item_code", | ||||
| 		"label": _("Item"), | ||||
| 		"fieldtype": "Link", | ||||
| 		"options": "Item", | ||||
| 		"width": 200 | ||||
| 	}, | ||||
| 	{ | ||||
| 		"fieldname": "quotation", | ||||
| 		"label": _("Supplier Quotation"), | ||||
| 		"fieldname": "uom", | ||||
| 		"label": _("UOM"), | ||||
| 		"fieldtype": "Link", | ||||
| 		"options": "Supplier Quotation", | ||||
| 		"width": 200 | ||||
| 		"options": "UOM", | ||||
| 		"width": 90 | ||||
| 	}, | ||||
| 	{ | ||||
| 		"fieldname": "qty", | ||||
| @ -163,19 +181,43 @@ def get_columns(): | ||||
| 		"width": 110 | ||||
| 	}, | ||||
| 	{ | ||||
| 		"fieldname": "uom", | ||||
| 		"label": _("UOM"), | ||||
| 		"fieldname": "quotation", | ||||
| 		"label": _("Supplier Quotation"), | ||||
| 		"fieldtype": "Link", | ||||
| 		"options": "UOM", | ||||
| 		"width": 90 | ||||
| 		"options": "Supplier Quotation", | ||||
| 		"width": 200 | ||||
| 	}, | ||||
| 	{ | ||||
| 		"fieldname": "valid_till", | ||||
| 		"label": _("Valid Till"), | ||||
| 		"fieldtype": "Date", | ||||
| 		"width": 100 | ||||
| 	}, | ||||
| 	{ | ||||
| 		"fieldname": "lead_time_days", | ||||
| 		"label": _("Lead Time (Days)"), | ||||
| 		"fieldtype": "Int", | ||||
| 		"width": 100 | ||||
| 	}, | ||||
| 	{ | ||||
| 		"fieldname": "request_for_quotation", | ||||
| 		"label": _("Request for Quotation"), | ||||
| 		"fieldtype": "Link", | ||||
| 		"options": "Request for Quotation", | ||||
| 		"width": 200 | ||||
| 		"width": 150 | ||||
| 	} | ||||
| 	] | ||||
| 
 | ||||
| 	return columns | ||||
| 	return columns | ||||
| 
 | ||||
| def get_message(): | ||||
| 	return  """<span class="indicator"> | ||||
| 		Valid till :    | ||||
| 		</span> | ||||
| 		<span class="indicator orange"> | ||||
| 		Expires in a week or less | ||||
| 		</span> | ||||
| 		   | ||||
| 		<span class="indicator red"> | ||||
| 		Expires today / Already Expired | ||||
| 		</span>""" | ||||
| @ -1,4 +1,5 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "field:id", | ||||
|  "creation": "2019-06-05 12:07:02.634534", | ||||
|  "doctype": "DocType", | ||||
| @ -14,6 +15,7 @@ | ||||
|   "contact", | ||||
|   "contact_name", | ||||
|   "column_break_10", | ||||
|   "customer", | ||||
|   "lead", | ||||
|   "lead_name", | ||||
|   "section_break_5", | ||||
| @ -28,7 +30,8 @@ | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_5", | ||||
|    "fieldtype": "Section Break" | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Call Details" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "id", | ||||
| @ -125,10 +128,19 @@ | ||||
|    "in_list_view": 1, | ||||
|    "label": "Lead Name", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "customer", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Customer", | ||||
|    "options": "Customer", | ||||
|    "read_only": 1 | ||||
|   } | ||||
|  ], | ||||
|  "in_create": 1, | ||||
|  "modified": "2019-08-06 05:46:53.144683", | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-08-25 17:08:34.085731", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Communication", | ||||
|  "name": "Call Log", | ||||
|  | ||||
| @ -16,6 +16,9 @@ class CallLog(Document): | ||||
| 		self.contact = get_contact_with_phone_number(number) | ||||
| 		self.lead = get_lead_with_phone_number(number) | ||||
| 
 | ||||
| 		contact = frappe.get_doc("Contact", self.contact) | ||||
| 		self.customer = contact.get_link_for("Customer") | ||||
| 
 | ||||
| 	def after_insert(self): | ||||
| 		self.trigger_call_popup() | ||||
| 
 | ||||
|  | ||||
| @ -325,7 +325,7 @@ class AccountsController(TransactionBase): | ||||
| 				apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data')) | ||||
| 
 | ||||
| 		elif pricing_rule_args.get("validate_applied_rule"): | ||||
| 			for pricing_rule in get_applied_pricing_rules(item): | ||||
| 			for pricing_rule in get_applied_pricing_rules(item.get('pricing_rules')): | ||||
| 				pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule) | ||||
| 				for field in ['discount_percentage', 'discount_amount', 'rate']: | ||||
| 					if item.get(field) < pricing_rule_doc.get(field): | ||||
| @ -479,7 +479,11 @@ class AccountsController(TransactionBase): | ||||
| 			if d.against_order: | ||||
| 				allocated_amount = flt(d.amount) | ||||
| 			else: | ||||
| 				amount = self.rounded_total or self.grand_total | ||||
| 				if self.get('party_account_currency') == self.company_currency: | ||||
| 					amount = self.get('base_rounded_total') or self.base_grand_total | ||||
| 				else: | ||||
| 					amount = self.get('rounded_total') or self.grand_total | ||||
| 
 | ||||
| 				allocated_amount = min(amount - advance_allocated, d.amount) | ||||
| 			advance_allocated += flt(allocated_amount) | ||||
| 
 | ||||
| @ -802,10 +806,22 @@ class AccountsController(TransactionBase): | ||||
| 			self.payment_terms_template = '' | ||||
| 			return | ||||
| 
 | ||||
| 		party_account_currency = self.get('party_account_currency') | ||||
| 		if not party_account_currency: | ||||
| 			party_type, party = self.get_party() | ||||
| 
 | ||||
| 			if party_type and party: | ||||
| 				party_account_currency = get_party_account_currency(party_type, party, self.company) | ||||
| 
 | ||||
| 		posting_date = self.get("bill_date") or self.get("posting_date") or self.get("transaction_date") | ||||
| 		date = self.get("due_date") | ||||
| 		due_date = date or posting_date | ||||
| 		grand_total = self.get("rounded_total") or self.grand_total | ||||
| 
 | ||||
| 		if party_account_currency == self.company_currency: | ||||
| 			grand_total = self.get("base_rounded_total") or self.base_grand_total | ||||
| 		else: | ||||
| 			grand_total = self.get("rounded_total") or self.grand_total | ||||
| 
 | ||||
| 		if self.doctype in ("Sales Invoice", "Purchase Invoice"): | ||||
| 			grand_total = grand_total - flt(self.write_off_amount) | ||||
| 
 | ||||
| @ -850,13 +866,25 @@ class AccountsController(TransactionBase): | ||||
| 	def validate_payment_schedule_amount(self): | ||||
| 		if self.doctype == 'Sales Invoice' and self.is_pos: return | ||||
| 
 | ||||
| 		party_account_currency = self.get('party_account_currency') | ||||
| 		if not party_account_currency: | ||||
| 			party_type, party = self.get_party() | ||||
| 
 | ||||
| 			if party_type and party: | ||||
| 				party_account_currency = get_party_account_currency(party_type, party, self.company) | ||||
| 
 | ||||
| 		if self.get("payment_schedule"): | ||||
| 			total = 0 | ||||
| 			for d in self.get("payment_schedule"): | ||||
| 				total += flt(d.payment_amount) | ||||
| 			total = flt(total, self.precision("grand_total")) | ||||
| 
 | ||||
| 			grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total')) | ||||
| 			if party_account_currency == self.company_currency: | ||||
| 				total = flt(total, self.precision("base_grand_total")) | ||||
| 				grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total')) | ||||
| 			else: | ||||
| 				total = flt(total, self.precision("grand_total")) | ||||
| 				grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total')) | ||||
| 
 | ||||
| 			if self.get("total_advance"): | ||||
| 				grand_total -= self.get("total_advance") | ||||
| 
 | ||||
| @ -957,7 +985,7 @@ def validate_inclusive_tax(tax, doc): | ||||
| 			# all rows about the reffered tax should be inclusive | ||||
| 			_on_previous_row_error("1 - %d" % (tax.row_id,)) | ||||
| 		elif tax.get("category") == "Valuation": | ||||
| 			frappe.throw(_("Valuation type charges can not marked as Inclusive")) | ||||
| 			frappe.throw(_("Valuation type charges can not be marked as Inclusive")) | ||||
| 
 | ||||
| 
 | ||||
| def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None): | ||||
|  | ||||
| @ -276,6 +276,9 @@ class BuyingController(StockController): | ||||
| 		qty_to_be_received_map = get_qty_to_be_received(purchase_orders) | ||||
| 
 | ||||
| 		for item in self.get('items'): | ||||
| 			if not item.purchase_order: | ||||
| 				continue | ||||
| 
 | ||||
| 			# reset raw_material cost | ||||
| 			item.rm_supp_cost = 0 | ||||
| 
 | ||||
| @ -288,6 +291,12 @@ class BuyingController(StockController): | ||||
| 
 | ||||
| 			fg_yet_to_be_received = qty_to_be_received_map.get(item_key) | ||||
| 
 | ||||
| 			if not fg_yet_to_be_received: | ||||
| 				frappe.throw(_("Row #{0}: Item {1} is already fully received in Purchase Order {2}") | ||||
| 					.format(item.idx, frappe.bold(item.item_code), | ||||
| 						frappe.utils.get_link_to_form("Purchase Order", item.purchase_order)), | ||||
| 					title=_("Limit Crossed")) | ||||
| 
 | ||||
| 			transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code) | ||||
| 			backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code) | ||||
| 
 | ||||
| @ -559,9 +568,19 @@ class BuyingController(StockController): | ||||
| 						"serial_no": cstr(d.serial_no).strip() | ||||
| 					}) | ||||
| 					if self.is_return: | ||||
| 						original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", | ||||
| 							{"voucher_type": "Purchase Receipt", "voucher_no": self.return_against, | ||||
| 							"item_code": d.item_code}, "incoming_rate") | ||||
| 						filters = { | ||||
| 							"voucher_type": self.doctype, | ||||
| 							"voucher_no": self.return_against, | ||||
| 							"item_code": d.item_code | ||||
| 						} | ||||
| 
 | ||||
| 						if (self.doctype == "Purchase Invoice" and self.update_stock | ||||
| 							and d.get("purchase_invoice_item")): | ||||
| 							filters["voucher_detail_no"] = d.purchase_invoice_item | ||||
| 						elif self.doctype == "Purchase Receipt" and d.get("purchase_receipt_item"): | ||||
| 							filters["voucher_detail_no"] = d.purchase_receipt_item | ||||
| 
 | ||||
| 						original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", filters, "incoming_rate") | ||||
| 
 | ||||
| 						sle.update({ | ||||
| 							"outgoing_rate": original_incoming_rate | ||||
|  | ||||
| @ -12,6 +12,7 @@ from frappe.utils import unique | ||||
| 
 | ||||
| # searches for active employees | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def employee_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	conditions = [] | ||||
| 	fields = get_fields("Employee", ["name", "employee_name"]) | ||||
| @ -42,6 +43,7 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
| # searches for leads which are not converted | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def lead_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	fields = get_fields("Lead", ["name", "lead_name", "company_name"]) | ||||
| 
 | ||||
| @ -72,6 +74,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
|  # searches for customer | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def customer_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	conditions = [] | ||||
| 	cust_master_name = frappe.defaults.get_user_default("cust_master_name") | ||||
| @ -110,8 +113,10 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
| # searches for supplier | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def supplier_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	supp_master_name = frappe.defaults.get_user_default("supp_master_name") | ||||
| 
 | ||||
| 	if supp_master_name == "Supplier Name": | ||||
| 		fields = ["name", "supplier_group"] | ||||
| 	else: | ||||
| @ -142,32 +147,49 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def tax_account_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	company_currency = erpnext.get_company_currency(filters.get('company')) | ||||
| 
 | ||||
| 	tax_accounts = frappe.db.sql("""select name, parent_account	from tabAccount | ||||
| 		where tabAccount.docstatus!=2 | ||||
| 			and account_type in (%s) | ||||
| 			and is_group = 0 | ||||
| 			and company = %s | ||||
| 			and account_currency = %s | ||||
| 			and `%s` LIKE %s | ||||
| 		order by idx desc, name | ||||
| 		limit %s, %s""" % | ||||
| 		(", ".join(['%s']*len(filters.get("account_type"))), "%s", "%s", searchfield, "%s", "%s", "%s"), | ||||
| 		tuple(filters.get("account_type") + [filters.get("company"), company_currency, "%%%s%%" % txt, | ||||
| 			start, page_len])) | ||||
| 	def get_accounts(with_account_type_filter): | ||||
| 		account_type_condition = '' | ||||
| 		if with_account_type_filter: | ||||
| 			account_type_condition = "AND account_type in %(account_types)s" | ||||
| 
 | ||||
| 		accounts = frappe.db.sql(""" | ||||
| 			SELECT name, parent_account | ||||
| 			FROM `tabAccount` | ||||
| 			WHERE `tabAccount`.docstatus!=2 | ||||
| 				{account_type_condition} | ||||
| 				AND is_group = 0 | ||||
| 				AND company = %(company)s | ||||
| 				AND account_currency = %(currency)s | ||||
| 				AND `{searchfield}` LIKE %(txt)s | ||||
| 			ORDER BY idx DESC, name | ||||
| 			LIMIT %(offset)s, %(limit)s | ||||
| 		""".format(account_type_condition=account_type_condition, searchfield=searchfield), | ||||
| 			dict( | ||||
| 				account_types=filters.get("account_type"), | ||||
| 				company=filters.get("company"), | ||||
| 				currency=company_currency, | ||||
| 				txt="%{}%".format(txt), | ||||
| 				offset=start, | ||||
| 				limit=page_len | ||||
| 			) | ||||
| 		) | ||||
| 
 | ||||
| 		return accounts | ||||
| 
 | ||||
| 	tax_accounts = get_accounts(True) | ||||
| 
 | ||||
| 	if not tax_accounts: | ||||
| 		tax_accounts = frappe.db.sql("""select name, parent_account	from tabAccount | ||||
| 			where tabAccount.docstatus!=2 and is_group = 0 | ||||
| 				and company = %s and account_currency = %s and `%s` LIKE %s limit %s, %s""" #nosec | ||||
| 			% ("%s", "%s", searchfield, "%s", "%s", "%s"), | ||||
| 			(filters.get("company"), company_currency, "%%%s%%" % txt, start, page_len)) | ||||
| 		tax_accounts = get_accounts(False) | ||||
| 
 | ||||
| 	return tax_accounts | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): | ||||
| 	conditions = [] | ||||
| 
 | ||||
| @ -215,7 +237,6 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals | ||||
| 			idx desc, | ||||
| 			name, item_name | ||||
| 		limit %(start)s, %(page_len)s """.format( | ||||
| 			key=searchfield, | ||||
| 			columns=columns, | ||||
| 			scond=searchfields, | ||||
| 			fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'), | ||||
| @ -231,6 +252,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def bom(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	conditions = [] | ||||
| 	fields = get_fields("BOM", ["name", "item"]) | ||||
| @ -258,6 +280,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_project_name(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	cond = '' | ||||
| 	if filters.get('customer'): | ||||
| @ -285,6 +308,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict): | ||||
| 	fields = get_fields("Delivery Note", ["name", "customer", "posting_date"]) | ||||
| 
 | ||||
| @ -315,6 +339,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_batch_no(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	cond = "" | ||||
| 	if filters.get("posting_date"): | ||||
| @ -373,6 +398,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_account_list(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	filter_list = [] | ||||
| 
 | ||||
| @ -395,8 +421,8 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters): | ||||
| 		fields = ["name", "parent_account"], | ||||
| 		limit_start=start, limit_page_length=page_len, as_list=True) | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date | ||||
| 		from `tabBlanket Order` bo, `tabBlanket Order Item` boi | ||||
| @ -413,6 +439,7 @@ def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_income_account(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	from erpnext.controllers.queries import get_match_cond | ||||
| 
 | ||||
| @ -439,6 +466,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_expense_account(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	from erpnext.controllers.queries import get_match_cond | ||||
| 
 | ||||
| @ -463,29 +491,24 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def warehouse_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	# Should be used when item code is passed in filters. | ||||
| 	conditions, bin_conditions = [], [] | ||||
| 	filter_dict = get_doctype_wise_filters(filters) | ||||
| 
 | ||||
| 	sub_query = """ select round(`tabBin`.actual_qty, 2) from `tabBin` | ||||
| 		where `tabBin`.warehouse = `tabWarehouse`.name | ||||
| 		{bin_conditions} """.format( | ||||
| 		bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"), | ||||
| 			bin_conditions, ignore_permissions=True)) | ||||
| 
 | ||||
| 	query = """select `tabWarehouse`.name, | ||||
| 		CONCAT_WS(" : ", "Actual Qty", ifnull( ({sub_query}), 0) ) as actual_qty | ||||
| 		from `tabWarehouse` | ||||
| 		CONCAT_WS(" : ", "Actual Qty", ifnull(round(`tabBin`.actual_qty, 2), 0 )) actual_qty | ||||
| 		from `tabWarehouse` left join `tabBin` | ||||
| 		on `tabBin`.warehouse = `tabWarehouse`.name {bin_conditions} | ||||
| 		where | ||||
| 		   `tabWarehouse`.`{key}` like {txt} | ||||
| 			`tabWarehouse`.`{key}` like {txt} | ||||
| 			{fcond} {mcond} | ||||
| 		order by | ||||
| 			`tabWarehouse`.name desc | ||||
| 		order by ifnull(`tabBin`.actual_qty, 0) desc | ||||
| 		limit | ||||
| 			{start}, {page_len} | ||||
| 		""".format( | ||||
| 			sub_query=sub_query, | ||||
| 			bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"),bin_conditions, ignore_permissions=True), | ||||
| 			key=searchfield, | ||||
| 			fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions), | ||||
| 			mcond=get_match_cond(doctype), | ||||
| @ -506,6 +529,7 @@ def get_doctype_wise_filters(filters): | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	query = """select batch_id from `tabBatch` | ||||
| 			where disabled = 0 | ||||
| @ -519,6 +543,7 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	item_filters = [ | ||||
| 		['manufacturer', 'like', '%' + txt + '%'], | ||||
| @ -537,6 +562,7 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters) | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	query = """ | ||||
| 		select pr.name | ||||
| @ -551,6 +577,7 @@ def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	query = """ | ||||
| 		select pi.name | ||||
| @ -565,6 +592,7 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @frappe.validate_and_sanitize_search_inputs | ||||
| def get_tax_template(doctype, txt, searchfield, start, page_len, filters): | ||||
| 
 | ||||
| 	item_doc = frappe.get_cached_doc('Item', filters.get('item_code')) | ||||
| @ -579,9 +607,12 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	if not taxes: | ||||
| 		return frappe.db.sql(""" SELECT name FROM `tabItem Tax Template` """) | ||||
| 	else: | ||||
| 		valid_from = filters.get('valid_from') | ||||
| 		valid_from = valid_from[1] if isinstance(valid_from, list) else valid_from | ||||
| 
 | ||||
| 		args = { | ||||
| 			'item_code': filters.get('item_code'), | ||||
| 			'posting_date': filters.get('valid_from'), | ||||
| 			'posting_date': valid_from, | ||||
| 			'tax_category': filters.get('tax_category'), | ||||
| 			'company': filters.get('company') | ||||
| 		} | ||||
|  | ||||
| @ -281,6 +281,8 @@ def make_return_doc(doctype, source_name, target_doc=None): | ||||
| 			target_doc.rejected_warehouse = source_doc.rejected_warehouse | ||||
| 			target_doc.po_detail = source_doc.po_detail | ||||
| 			target_doc.pr_detail = source_doc.pr_detail | ||||
| 			target_doc.purchase_invoice_item = source_doc.name | ||||
| 
 | ||||
| 		elif doctype == "Delivery Note": | ||||
| 			target_doc.against_sales_order = source_doc.against_sales_order | ||||
| 			target_doc.against_sales_invoice = source_doc.against_sales_invoice | ||||
| @ -296,6 +298,7 @@ def make_return_doc(doctype, source_name, target_doc=None): | ||||
| 			target_doc.so_detail = source_doc.so_detail | ||||
| 			target_doc.dn_detail = source_doc.dn_detail | ||||
| 			target_doc.expense_account = source_doc.expense_account | ||||
| 			target_doc.sales_invoice_item = source_doc.name | ||||
| 			if default_warehouse_for_sales_return: | ||||
| 				target_doc.warehouse = default_warehouse_for_sales_return | ||||
| 
 | ||||
|  | ||||
| @ -217,7 +217,9 @@ class SellingController(StockController): | ||||
| 							'target_warehouse': p.target_warehouse, | ||||
| 							'company': self.company, | ||||
| 							'voucher_type': self.doctype, | ||||
| 							'allow_zero_valuation': d.allow_zero_valuation_rate | ||||
| 							'allow_zero_valuation': d.allow_zero_valuation_rate, | ||||
| 							'sales_invoice_item': d.get("sales_invoice_item"), | ||||
| 							'delivery_note_item': d.get("dn_detail") | ||||
| 						})) | ||||
| 			else: | ||||
| 				il.append(frappe._dict({ | ||||
| @ -233,7 +235,9 @@ class SellingController(StockController): | ||||
| 					'target_warehouse': d.target_warehouse, | ||||
| 					'company': self.company, | ||||
| 					'voucher_type': self.doctype, | ||||
| 					'allow_zero_valuation': d.allow_zero_valuation_rate | ||||
| 					'allow_zero_valuation': d.allow_zero_valuation_rate, | ||||
| 					'sales_invoice_item': d.get("sales_invoice_item"), | ||||
| 					'delivery_note_item': d.get("dn_detail") | ||||
| 				})) | ||||
| 		return il | ||||
| 
 | ||||
| @ -302,7 +306,11 @@ class SellingController(StockController): | ||||
| 					d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0 | ||||
| 				return_rate = 0 | ||||
| 				if cint(self.is_return) and self.return_against and self.docstatus==1: | ||||
| 					return_rate = self.get_incoming_rate_for_return(d.item_code, self.return_against) | ||||
| 					against_document_no = (d.get("sales_invoice_item") | ||||
| 						if self.doctype == "Sales Invoice" else d.get("delivery_note_item")) | ||||
| 
 | ||||
| 					return_rate = self.get_incoming_rate_for_return(d.item_code, | ||||
| 						self.return_against, against_document_no) | ||||
| 
 | ||||
| 				# On cancellation or if return entry submission, make stock ledger entry for | ||||
| 				# target warehouse first, to update serial no values properly | ||||
|  | ||||
| @ -301,14 +301,19 @@ class StockController(AccountsController): | ||||
| 
 | ||||
| 		return serialized_items | ||||
| 
 | ||||
| 	def get_incoming_rate_for_return(self, item_code, against_document): | ||||
| 	def get_incoming_rate_for_return(self, item_code, against_document, against_document_no=None): | ||||
| 		incoming_rate = 0.0 | ||||
| 		cond = '' | ||||
| 		if against_document and item_code: | ||||
| 			if against_document_no: | ||||
| 				cond = " and voucher_detail_no = %s" %(frappe.db.escape(against_document_no)) | ||||
| 
 | ||||
| 			incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty) | ||||
| 				from `tabStock Ledger Entry` | ||||
| 				where voucher_type = %s and voucher_no = %s | ||||
| 					and item_code = %s limit 1""", | ||||
| 					and item_code = %s {0} limit 1""".format(cond), | ||||
| 				(self.doctype, against_document, item_code)) | ||||
| 
 | ||||
| 			incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0 | ||||
| 
 | ||||
| 		return incoming_rate | ||||
|  | ||||
| @ -9,6 +9,7 @@ from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction | ||||
| from erpnext.controllers.accounts_controller import validate_conversion_rate, \ | ||||
| 	validate_taxes_and_charges, validate_inclusive_tax | ||||
| from erpnext.stock.get_item_details import _get_item_tax_template | ||||
| from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules | ||||
| 
 | ||||
| class calculate_taxes_and_totals(object): | ||||
| 	def __init__(self, doc): | ||||
| @ -161,8 +162,9 @@ class calculate_taxes_and_totals(object): | ||||
| 		for item in self.doc.get("items"): | ||||
| 			item_tax_map = self._load_item_tax_rate(item.item_tax_rate) | ||||
| 			cumulated_tax_fraction = 0 | ||||
| 			total_inclusive_tax_amount_per_qty = 0 | ||||
| 			for i, tax in enumerate(self.doc.get("taxes")): | ||||
| 				tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax, item_tax_map) | ||||
| 				tax.tax_fraction_for_current_item, inclusive_tax_amount_per_qty = self.get_current_tax_fraction(tax, item_tax_map) | ||||
| 
 | ||||
| 				if i==0: | ||||
| 					tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item | ||||
| @ -172,9 +174,12 @@ class calculate_taxes_and_totals(object): | ||||
| 						+ tax.tax_fraction_for_current_item | ||||
| 
 | ||||
| 				cumulated_tax_fraction += tax.tax_fraction_for_current_item | ||||
| 				total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty) | ||||
| 
 | ||||
| 			if cumulated_tax_fraction and not self.discount_amount_applied and item.qty: | ||||
| 				item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction)) | ||||
| 			if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty): | ||||
| 				amount = flt(item.amount) - total_inclusive_tax_amount_per_qty | ||||
| 
 | ||||
| 				item.net_amount = flt(amount / (1 + cumulated_tax_fraction)) | ||||
| 				item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) | ||||
| 				item.discount_percentage = flt(item.discount_percentage, | ||||
| 					item.precision("discount_percentage")) | ||||
| @ -190,6 +195,7 @@ class calculate_taxes_and_totals(object): | ||||
| 			from tax inclusive amount | ||||
| 		""" | ||||
| 		current_tax_fraction = 0 | ||||
| 		inclusive_tax_amount_per_qty = 0 | ||||
| 
 | ||||
| 		if cint(tax.included_in_print_rate): | ||||
| 			tax_rate = self._get_tax_rate(tax, item_tax_map) | ||||
| @ -205,9 +211,14 @@ class calculate_taxes_and_totals(object): | ||||
| 				current_tax_fraction = (tax_rate / 100.0) * \ | ||||
| 					self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item | ||||
| 
 | ||||
| 		if getattr(tax, "add_deduct_tax", None): | ||||
| 			current_tax_fraction *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0 | ||||
| 		return current_tax_fraction | ||||
| 			elif tax.charge_type == "On Item Quantity": | ||||
| 				inclusive_tax_amount_per_qty = flt(tax_rate) | ||||
| 
 | ||||
| 		if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct": | ||||
| 			current_tax_fraction *= -1.0 | ||||
| 			inclusive_tax_amount_per_qty *= -1.0 | ||||
| 
 | ||||
| 		return current_tax_fraction, inclusive_tax_amount_per_qty | ||||
| 
 | ||||
| 	def _get_tax_rate(self, tax, item_tax_map): | ||||
| 		if tax.account_head in item_tax_map: | ||||
| @ -321,7 +332,7 @@ class calculate_taxes_and_totals(object): | ||||
| 			current_tax_amount = (tax_rate / 100.0) * \ | ||||
| 				self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item | ||||
| 		elif tax.charge_type == "On Item Quantity": | ||||
| 			current_tax_amount = tax_rate * item.stock_qty | ||||
| 			current_tax_amount = tax_rate * item.qty | ||||
| 
 | ||||
| 		self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) | ||||
| 
 | ||||
| @ -472,7 +483,7 @@ class calculate_taxes_and_totals(object): | ||||
| 			actual_taxes_dict = {} | ||||
| 
 | ||||
| 			for tax in self.doc.get("taxes"): | ||||
| 				if tax.charge_type == "Actual": | ||||
| 				if tax.charge_type in ["Actual", "On Item Quantity"]: | ||||
| 					tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax) | ||||
| 					actual_taxes_dict.setdefault(tax.idx, tax_amount) | ||||
| 				elif tax.row_id in actual_taxes_dict: | ||||
| @ -597,7 +608,7 @@ class calculate_taxes_and_totals(object): | ||||
| 		base_rate_with_margin = 0.0 | ||||
| 		if item.price_list_rate: | ||||
| 			if item.pricing_rules and not self.doc.ignore_pricing_rule: | ||||
| 				for d in json.loads(item.pricing_rules): | ||||
| 				for d in get_applied_pricing_rules(item.pricing_rules): | ||||
| 					pricing_rule = frappe.get_cached_doc('Pricing Rule', d) | ||||
| 
 | ||||
| 					if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\ | ||||
|  | ||||
| @ -16,6 +16,7 @@ | ||||
|   "opportunity_from", | ||||
|   "party_name", | ||||
|   "customer_name", | ||||
|   "source", | ||||
|   "column_break0", | ||||
|   "title", | ||||
|   "opportunity_type", | ||||
| @ -49,10 +50,9 @@ | ||||
|   "contact_email", | ||||
|   "contact_mobile", | ||||
|   "more_info", | ||||
|   "source", | ||||
|   "company", | ||||
|   "campaign", | ||||
|   "column_break1", | ||||
|   "company", | ||||
|   "transaction_date", | ||||
|   "amended_from", | ||||
|   "lost_reasons" | ||||
| @ -344,7 +344,7 @@ | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "more_info", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Source", | ||||
|    "label": "More Information", | ||||
|    "oldfieldtype": "Section Break", | ||||
|    "options": "fa fa-file-text" | ||||
|   }, | ||||
| @ -411,7 +411,7 @@ | ||||
|    "fieldname": "lost_reasons", | ||||
|    "fieldtype": "Table MultiSelect", | ||||
|    "label": "Lost Reasons", | ||||
|    "options": "Lost Reason Detail", | ||||
|    "options": "Opportunity Lost Reason Detail", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
| @ -424,7 +424,7 @@ | ||||
|  "icon": "fa fa-info-sign", | ||||
|  "idx": 195, | ||||
|  "links": [], | ||||
|  "modified": "2020-07-14 16:49:15.888503", | ||||
|  "modified": "2020-08-11 17:34:35.066961", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "CRM", | ||||
|  "name": "Opportunity", | ||||
|  | ||||
| @ -119,11 +119,19 @@ class Opportunity(TransactionBase): | ||||
| 				and q.status not in ('Lost', 'Closed')""", self.name) | ||||
| 
 | ||||
| 	def has_ordered_quotation(self): | ||||
| 		return frappe.db.sql(""" | ||||
| 			select q.name | ||||
| 			from `tabQuotation` q, `tabQuotation Item` qi | ||||
| 			where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s | ||||
| 			and q.status = 'Ordered'""", self.name) | ||||
| 		if not self.with_items: | ||||
| 			return frappe.get_all('Quotation', | ||||
| 				{ | ||||
| 					'opportunity': self.name, | ||||
| 					'status': 'Ordered', | ||||
| 					'docstatus': 1 | ||||
| 				}, 'name') | ||||
| 		else: | ||||
| 			return frappe.db.sql(""" | ||||
| 				select q.name | ||||
| 				from `tabQuotation` q, `tabQuotation Item` qi | ||||
| 				where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s | ||||
| 				and q.status = 'Ordered'""", self.name) | ||||
| 
 | ||||
| 	def has_lost_quotation(self): | ||||
| 		lost_quotation = frappe.db.sql(""" | ||||
| @ -259,6 +267,9 @@ def make_quotation(source_name, target_doc=None): | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def make_request_for_quotation(source_name, target_doc=None): | ||||
| 	def update_item(obj, target, source_parent): | ||||
| 		target.conversion_factor = 1.0 | ||||
| 
 | ||||
| 	doclist = get_mapped_doc("Opportunity", source_name, { | ||||
| 		"Opportunity": { | ||||
| 			"doctype": "Request for Quotation" | ||||
| @ -269,7 +280,8 @@ def make_request_for_quotation(source_name, target_doc=None): | ||||
| 				["name", "opportunity_item"], | ||||
| 				["parent", "opportunity"], | ||||
| 				["uom", "uom"] | ||||
| 			] | ||||
| 			], | ||||
| 			"postprocess": update_item | ||||
| 		} | ||||
| 	}, target_doc) | ||||
| 
 | ||||
| @ -317,7 +329,7 @@ def auto_close_opportunity(): | ||||
| 		doc.save() | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def make_opportunity_from_communication(communication, ignore_communication_links=False): | ||||
| def make_opportunity_from_communication(communication, company, ignore_communication_links=False): | ||||
| 	from erpnext.crm.doctype.lead.lead import make_lead_from_communication | ||||
| 	doc = frappe.get_doc("Communication", communication) | ||||
| 
 | ||||
| @ -329,8 +341,9 @@ def make_opportunity_from_communication(communication, ignore_communication_link | ||||
| 
 | ||||
| 	opportunity = frappe.get_doc({ | ||||
| 		"doctype": "Opportunity", | ||||
| 		"company": company, | ||||
| 		"opportunity_from": opportunity_from, | ||||
| 		"lead": lead | ||||
| 		"party_name": lead | ||||
| 	}).insert(ignore_permissions=True) | ||||
| 
 | ||||
| 	link_communication_to_document(doc, "Opportunity", opportunity.name, ignore_communication_links) | ||||
|  | ||||
| @ -82,7 +82,8 @@ def make_opportunity(**args): | ||||
| 	if args.with_items: | ||||
| 		opp_doc.append('items', { | ||||
| 			"item_code": args.item_code or "_Test Item", | ||||
| 			"qty": args.qty or 1 | ||||
| 			"qty": args.qty or 1, | ||||
| 			"uom": "_Test UOM" | ||||
| 		}) | ||||
| 
 | ||||
| 	opp_doc.insert() | ||||
|  | ||||
| @ -0,0 +1,31 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "creation": "2020-07-16 16:11:39.830389", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "lost_reason" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "lost_reason", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Opportunity Lost Reason", | ||||
|    "options": "Opportunity Lost Reason" | ||||
|   } | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-07-26 17:58:26.313242", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "CRM", | ||||
|  "name": "Opportunity Lost Reason Detail", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| # import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class OpportunityLostReasonDetail(Document): | ||||
| 	pass | ||||
| @ -30,14 +30,14 @@ frappe.ui.form.on('Social Media Post', { | ||||
|                 let color = frm.doc.twitter_post_id ? "green" : "red"; | ||||
|                 let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted"; | ||||
|                 html += `<div class="col-xs-6">
 | ||||
|                             <span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">Twitter : ${status} </span></span> | ||||
|                             <span class="indicator whitespace-nowrap ${color}"><span>Twitter : ${status} </span></span> | ||||
|                         </div>` ; | ||||
|             } | ||||
|             if (frm.doc.linkedin){ | ||||
|                 let color = frm.doc.linkedin_post_id ? "green" : "red"; | ||||
|                 let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted"; | ||||
|                 html += `<div class="col-xs-6">
 | ||||
|                             <span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">LinkedIn : ${status} </span></span> | ||||
|                             <span class="indicator whitespace-nowrap ${color}"><span>LinkedIn : ${status} </span></span> | ||||
|                         </div>` ; | ||||
|             } | ||||
|             html = `<div class="row">${html}</div>`; | ||||
|  | ||||
							
								
								
									
										52
									
								
								erpnext/crm/report/lead_details/lead_details.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								erpnext/crm/report/lead_details/lead_details.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| /* eslint-disable */ | ||||
| 
 | ||||
| frappe.query_reports["Lead Details"] = { | ||||
| 	"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(), -12), | ||||
| 			"reqd": 1 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"to_date", | ||||
| 			"label": __("To Date"), | ||||
| 			"fieldtype": "Date", | ||||
| 			"default": frappe.datetime.get_today(), | ||||
| 			"reqd": 1 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"status", | ||||
| 			"label": __("Status"), | ||||
| 			"fieldtype": "Select", | ||||
| 			options: [ | ||||
| 				{ "value": "Lead", "label": __("Lead") }, | ||||
| 				{ "value": "Open", "label": __("Open") }, | ||||
| 				{ "value": "Replied", "label": __("Replied") }, | ||||
| 				{ "value": "Opportunity", "label": __("Opportunity") }, | ||||
| 				{ "value": "Quotation", "label": __("Quotation") }, | ||||
| 				{ "value": "Lost Quotation", "label": __("Lost Quotation") }, | ||||
| 				{ "value": "Interested", "label": __("Interested") }, | ||||
| 				{ "value": "Converted", "label": __("Converted") }, | ||||
| 				{ "value": "Do Not Contact", "label": __("Do Not Contact") }, | ||||
| 			], | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"territory", | ||||
| 			"label": __("Territory"), | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "Territory", | ||||
| 		} | ||||
| 	] | ||||
| }; | ||||
| @ -7,16 +7,15 @@ | ||||
|  "doctype": "Report", | ||||
|  "idx": 3, | ||||
|  "is_standard": "Yes", | ||||
|  "modified": "2020-01-22 16:51:56.591110", | ||||
|  "modified": "2020-07-26 23:59:49.897577", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "CRM", | ||||
|  "name": "Lead Details", | ||||
|  "owner": "Administrator", | ||||
|  "prepared_report": 0, | ||||
|  "query": "SELECT\n    `tabLead`.name as \"Lead Id:Link/Lead:120\",\n    `tabLead`.lead_name as \"Lead Name::120\",\n\t`tabLead`.company_name as \"Company Name::120\",\n\t`tabLead`.status as \"Status::120\",\n\tconcat_ws(', ', \n\t\ttrim(',' from `tabAddress`.address_line1), \n\t\ttrim(',' from tabAddress.address_line2)\n\t) as 'Address::180',\n\t`tabAddress`.state as \"State::100\",\n\t`tabAddress`.pincode as \"Pincode::70\",\n\t`tabAddress`.country as \"Country::100\",\n\t`tabLead`.phone as \"Phone::100\",\n\t`tabLead`.mobile_no as \"Mobile No::100\",\n\t`tabLead`.email_id as \"Email Id::120\",\n\t`tabLead`.lead_owner as \"Lead Owner::120\",\n\t`tabLead`.source as \"Source::120\",\n\t`tabLead`.territory as \"Territory::120\",\n\t`tabLead`.notes as \"Notes::360\",\n    `tabLead`.owner as \"Owner:Link/User:120\"\nFROM\n\t`tabLead`\n\tleft join `tabDynamic Link` on (\n\t\t`tabDynamic Link`.link_name=`tabLead`.name \n\t\tand `tabDynamic Link`.parenttype = 'Address'\n\t)\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.name=`tabDynamic Link`.parent\n\t)\nWHERE\n\t`tabLead`.docstatus<2\nORDER BY\n\t`tabLead`.name asc", | ||||
|  "ref_doctype": "Lead", | ||||
|  "report_name": "Lead Details", | ||||
|  "report_type": "Query Report", | ||||
|  "report_type": "Script Report", | ||||
|  "roles": [ | ||||
|   { | ||||
|    "role": "Sales User" | ||||
|  | ||||
							
								
								
									
										158
									
								
								erpnext/crm/report/lead_details/lead_details.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								erpnext/crm/report/lead_details/lead_details.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,158 @@ | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| from frappe import _ | ||||
| import frappe | ||||
| 
 | ||||
| def execute(filters=None): | ||||
| 	columns, data = get_columns(), get_data(filters) | ||||
| 	return columns, data | ||||
| 
 | ||||
| def get_columns(): | ||||
| 	columns = [ | ||||
| 		{ | ||||
| 			"label": _("Lead"), | ||||
| 			"fieldname": "name", | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "Lead", | ||||
| 			"width": 150, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Lead Name"), | ||||
| 			"fieldname": "lead_name", | ||||
| 			"fieldtype": "Data", | ||||
| 			"width": 120 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"status", | ||||
| 			"label": _("Status"), | ||||
| 			"fieldtype": "Data", | ||||
| 			"width": 100 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"lead_owner", | ||||
| 			"label": _("Lead Owner"), | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "User", | ||||
| 			"width": 100 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Territory"), | ||||
| 			"fieldname": "territory", | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "Territory", | ||||
| 			"width": 100 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Source"), | ||||
| 			"fieldname": "source", | ||||
| 			"fieldtype": "Data", | ||||
| 			"width": 120 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Email"), | ||||
| 			"fieldname": "email_id", | ||||
| 			"fieldtype": "Data", | ||||
| 			"width": 120 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Mobile"), | ||||
| 			"fieldname": "mobile_no", | ||||
| 			"fieldtype": "Data", | ||||
| 			"width": 120 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Phone"), | ||||
| 			"fieldname": "phone", | ||||
| 			"fieldtype": "Data", | ||||
| 			"width": 120 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Owner"), | ||||
| 			"fieldname": "owner", | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "user", | ||||
| 			"width": 120 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Company"), | ||||
| 			"fieldname": "company", | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "Company", | ||||
| 			"width": 120 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"address", | ||||
| 			"label": _("Address"), | ||||
| 			"fieldtype": "Data", | ||||
| 			"width": 130 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"state", | ||||
| 			"label": _("State"), | ||||
| 			"fieldtype": "Data", | ||||
| 			"width": 100 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"pincode", | ||||
| 			"label": _("Postal Code"), | ||||
| 			"fieldtype": "Data", | ||||
| 			"width": 90 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"country", | ||||
| 			"label": _("Country"), | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "Country", | ||||
| 			"width": 100 | ||||
| 		}, | ||||
| 		 | ||||
| 	] | ||||
| 	return columns | ||||
| 
 | ||||
| def get_data(filters): | ||||
| 	return frappe.db.sql(""" | ||||
| 		SELECT | ||||
| 			`tabLead`.name, | ||||
| 			`tabLead`.lead_name, | ||||
| 			`tabLead`.status, | ||||
| 			`tabLead`.lead_owner, | ||||
| 			`tabLead`.territory, | ||||
| 			`tabLead`.source, | ||||
| 			`tabLead`.email_id, | ||||
| 			`tabLead`.mobile_no, | ||||
| 			`tabLead`.phone, | ||||
| 			`tabLead`.owner, | ||||
| 			`tabLead`.company, | ||||
| 			concat_ws(', ', | ||||
| 				trim(',' from `tabAddress`.address_line1), | ||||
| 				trim(',' from tabAddress.address_line2) | ||||
| 			) AS address, | ||||
| 			`tabAddress`.state, | ||||
| 			`tabAddress`.pincode, | ||||
| 			`tabAddress`.country | ||||
| 		FROM | ||||
| 			`tabLead` left join `tabDynamic Link` on ( | ||||
| 			`tabLead`.name = `tabDynamic Link`.link_name and | ||||
| 			`tabDynamic Link`.parenttype = 'Address') | ||||
| 			left join `tabAddress` on ( | ||||
| 			`tabAddress`.name=`tabDynamic Link`.parent) | ||||
| 		WHERE | ||||
| 			company = %(company)s | ||||
| 			AND `tabLead`.creation BETWEEN %(from_date)s AND %(to_date)s | ||||
| 			{conditions} | ||||
| 		ORDER BY  | ||||
| 			`tabLead`.creation asc """.format(conditions=get_conditions(filters)), filters, as_dict=1) | ||||
| 
 | ||||
| def get_conditions(filters) : | ||||
| 	conditions = [] | ||||
| 
 | ||||
| 	if filters.get("territory"): | ||||
| 		conditions.append(" and `tabLead`.territory=%(territory)s") | ||||
| 
 | ||||
| 	if filters.get("status"): | ||||
| 		conditions.append(" and `tabLead`.status=%(status)s") | ||||
| 	 | ||||
| 	return " ".join(conditions) if conditions else "" | ||||
| 
 | ||||
							
								
								
									
										67
									
								
								erpnext/crm/report/lost_opportunity/lost_opportunity.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								erpnext/crm/report/lost_opportunity/lost_opportunity.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| /* eslint-disable */ | ||||
| 
 | ||||
| frappe.query_reports["Lost Opportunity"] = { | ||||
| 	"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(), -12), | ||||
| 			"reqd": 1 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"to_date", | ||||
| 			"label": __("To Date"), | ||||
| 			"fieldtype": "Date", | ||||
| 			"default": frappe.datetime.get_today(), | ||||
| 			"reqd": 1 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"lost_reason", | ||||
| 			"label": __("Lost Reason"), | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "Opportunity Lost Reason" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"territory", | ||||
| 			"label": __("Territory"), | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "Territory" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"opportunity_from", | ||||
| 			"label": __("Opportunity From"), | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "DocType", | ||||
| 			"get_query": function() { | ||||
| 				return { | ||||
| 					"filters": { | ||||
| 						"name": ["in", ["Customer", "Lead"]], | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"party_name", | ||||
| 			"label": __("Party"), | ||||
| 			"fieldtype": "Dynamic Link", | ||||
| 			"options": "opportunity_from" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname":"contact_by", | ||||
| 			"label": __("Next Contact By"), | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "User" | ||||
| 		}, | ||||
| 	] | ||||
| }; | ||||
| @ -1,13 +1,14 @@ | ||||
| { | ||||
|  "add_total_row": 0, | ||||
|  "creation": "2018-12-31 16:30:57.188837", | ||||
|  "disable_prepared_report": 0, | ||||
|  "disabled": 0, | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Report", | ||||
|  "idx": 0, | ||||
|  "is_standard": "Yes", | ||||
|  "json": "{\"order_by\": \"`tabOpportunity`.`modified` desc\", \"filters\": [[\"Opportunity\", \"status\", \"=\", \"Lost\"]], \"fields\": [[\"name\", \"Opportunity\"], [\"opportunity_from\", \"Opportunity\"], [\"party_name\", \"Opportunity\"], [\"customer_name\", \"Opportunity\"], [\"opportunity_type\", \"Opportunity\"], [\"status\", \"Opportunity\"], [\"contact_by\", \"Opportunity\"], [\"docstatus\", \"Opportunity\"], [\"lost_reason\", \"Lost Reason Detail\"]], \"add_totals_row\": 0, \"add_total_row\": 0, \"page_length\": 20}", | ||||
|  "modified": "2019-06-26 16:33:08.083618", | ||||
|  "modified": "2020-07-29 15:49:02.848845", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "CRM", | ||||
|  "name": "Lost Opportunity", | ||||
| @ -15,7 +16,7 @@ | ||||
|  "prepared_report": 0, | ||||
|  "ref_doctype": "Opportunity", | ||||
|  "report_name": "Lost Opportunity", | ||||
|  "report_type": "Report Builder", | ||||
|  "report_type": "Script Report", | ||||
|  "roles": [ | ||||
|   { | ||||
|    "role": "Sales User" | ||||
|  | ||||
							
								
								
									
										131
									
								
								erpnext/crm/report/lost_opportunity/lost_opportunity.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								erpnext/crm/report/lost_opportunity/lost_opportunity.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| from frappe import _ | ||||
| import frappe | ||||
| 
 | ||||
| def execute(filters=None): | ||||
| 	columns, data = get_columns(), get_data(filters) | ||||
| 	return columns, data | ||||
| 
 | ||||
| def get_columns(): | ||||
| 	columns = [ | ||||
| 		{ | ||||
| 			"label": _("Opportunity"), | ||||
| 			"fieldname": "name", | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "Opportunity", | ||||
| 			"width": 170, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Opportunity From"), | ||||
| 			"fieldname": "opportunity_from", | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "DocType", | ||||
| 			"width": 130 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Party"), | ||||
| 			"fieldname":"party_name", | ||||
| 			"fieldtype": "Dynamic Link", | ||||
| 			"options": "opportunity_from", | ||||
| 			"width": 160 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Customer/Lead Name"), | ||||
| 			"fieldname":"customer_name", | ||||
| 			"fieldtype": "Data", | ||||
| 			"width": 150 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Opportunity Type"), | ||||
| 			"fieldname": "opportunity_type", | ||||
| 			"fieldtype": "Data", | ||||
| 			"width": 130 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Lost Reasons"), | ||||
| 			"fieldname": "lost_reason", | ||||
| 			"fieldtype": "Data", | ||||
| 			"width": 220 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Sales Stage"), | ||||
| 			"fieldname": "sales_stage", | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "Sales Stage", | ||||
| 			"width": 150 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Territory"), | ||||
| 			"fieldname": "territory", | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "Territory", | ||||
| 			"width": 150 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"label": _("Next Contact By"), | ||||
| 			"fieldname": "contact_by", | ||||
| 			"fieldtype": "Link", | ||||
| 			"options": "User", | ||||
| 			"width": 150 | ||||
| 		} | ||||
| 	] | ||||
| 	return columns | ||||
| 
 | ||||
| def get_data(filters): | ||||
| 	return frappe.db.sql(""" | ||||
| 		SELECT | ||||
| 			`tabOpportunity`.name, | ||||
| 			`tabOpportunity`.opportunity_from, | ||||
| 			`tabOpportunity`.party_name, | ||||
| 			`tabOpportunity`.customer_name, | ||||
| 			`tabOpportunity`.opportunity_type, | ||||
| 			`tabOpportunity`.contact_by, | ||||
| 			GROUP_CONCAT(`tabOpportunity Lost Reason Detail`.lost_reason separator ', ') lost_reason, | ||||
| 			`tabOpportunity`.sales_stage, | ||||
| 			`tabOpportunity`.territory | ||||
| 		FROM | ||||
| 			`tabOpportunity`  | ||||
| 			{join} | ||||
| 		WHERE | ||||
| 			`tabOpportunity`.status = 'Lost' and `tabOpportunity`.company = %(company)s | ||||
| 			AND `tabOpportunity`.modified BETWEEN %(from_date)s AND %(to_date)s  | ||||
| 			{conditions}  | ||||
| 		GROUP BY  | ||||
| 			`tabOpportunity`.name  | ||||
| 		ORDER BY  | ||||
| 			`tabOpportunity`.creation asc  """.format(conditions=get_conditions(filters), join=get_join(filters)), filters, as_dict=1) | ||||
| 		 | ||||
| 
 | ||||
| def get_conditions(filters): | ||||
| 	conditions = [] | ||||
| 
 | ||||
| 	if filters.get("territory"): | ||||
| 		conditions.append(" and `tabOpportunity`.territory=%(territory)s") | ||||
| 
 | ||||
| 	if filters.get("opportunity_from"): | ||||
| 		conditions.append(" and `tabOpportunity`.opportunity_from=%(opportunity_from)s") | ||||
| 
 | ||||
| 	if filters.get("party_name"): | ||||
| 		conditions.append(" and `tabOpportunity`.party_name=%(party_name)s") | ||||
| 
 | ||||
| 	if filters.get("contact_by"): | ||||
| 		conditions.append(" and `tabOpportunity`.contact_by=%(contact_by)s") | ||||
| 
 | ||||
| 	return " ".join(conditions) if conditions else "" | ||||
| 
 | ||||
| def get_join(filters): | ||||
| 	join = """LEFT JOIN `tabOpportunity Lost Reason Detail`  | ||||
| 			ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and  | ||||
| 			`tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name""" | ||||
| 
 | ||||
| 	if filters.get("lost_reason"): | ||||
| 		join = """JOIN `tabOpportunity Lost Reason Detail`  | ||||
| 			ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and  | ||||
| 			`tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name and | ||||
| 			`tabOpportunity Lost Reason Detail`.lost_reason = '{0}' | ||||
| 			""".format(filters.get("lost_reason")) | ||||
| 	 | ||||
| 	return join | ||||
| @ -0,0 +1,31 @@ | ||||
| { | ||||
|  "based_on": "", | ||||
|  "chart_name": "Course wise Enrollment", | ||||
|  "chart_type": "Group By", | ||||
|  "creation": "2020-07-23 18:24:38.214220", | ||||
|  "docstatus": 0, | ||||
|  "doctype": "Dashboard Chart", | ||||
|  "document_type": "Course Enrollment", | ||||
|  "dynamic_filters_json": "[]", | ||||
|  "filters_json": "[[\"Course Enrollment\",\"enrollment_date\",\"Timespan\",\"this year\",false]]", | ||||
|  "group_by_based_on": "course", | ||||
|  "group_by_type": "Count", | ||||
|  "idx": 0, | ||||
|  "is_public": 1, | ||||
|  "is_standard": 1, | ||||
|  "last_synced_on": "2020-07-27 17:50:32.490587", | ||||
|  "modified": "2020-07-27 17:54:09.829206", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Education", | ||||
|  "name": "Course wise Enrollment", | ||||
|  "number_of_groups": 0, | ||||
|  "owner": "Administrator", | ||||
|  "source": "", | ||||
|  "time_interval": "Yearly", | ||||
|  "timeseries": 0, | ||||
|  "timespan": "Last Year", | ||||
|  "type": "Percentage", | ||||
|  "use_report_chart": 0, | ||||
|  "value_based_on": "", | ||||
|  "y_axis": [] | ||||
| } | ||||
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