Merge branch 'develop' into se-add-to-transit
This commit is contained in:
		
						commit
						8edf9d23c0
					
				| @ -214,6 +214,7 @@ class Account(NestedSet): | ||||
| 				if parent_value_changed: | ||||
| 					doc.save() | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def convert_group_to_ledger(self): | ||||
| 		if self.check_if_child_exists(): | ||||
| 			throw(_("Account with child nodes cannot be converted to ledger")) | ||||
| @ -224,6 +225,7 @@ class Account(NestedSet): | ||||
| 			self.save() | ||||
| 			return 1 | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def convert_ledger_to_group(self): | ||||
| 		if self.check_gle_exists(): | ||||
| 			throw(_("Account with existing transaction can not be converted to group.")) | ||||
|  | ||||
| @ -39,6 +39,7 @@ class AccountingPeriod(Document): | ||||
| 			frappe.throw(_("Accounting Period overlaps with {0}") | ||||
| 				.format(existing_accounting_period[0].get("name")), OverlapError) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_doctypes_for_closing(self): | ||||
| 		docs_for_closing = [] | ||||
| 		doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \ | ||||
|  | ||||
| @ -12,6 +12,7 @@ form_grid_templates = { | ||||
| } | ||||
| 
 | ||||
| class BankClearance(Document): | ||||
| 	@frappe.whitelist() | ||||
| 	def get_payment_entries(self): | ||||
| 		if not (self.from_date and self.to_date): | ||||
| 			frappe.throw(_("From Date and To Date are Mandatory")) | ||||
| @ -108,6 +109,7 @@ class BankClearance(Document): | ||||
| 			row.update(d) | ||||
| 			self.total_amount += flt(amount) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def update_clearance_date(self): | ||||
| 		clearance_date_updated = False | ||||
| 		for d in self.get('payment_entries'): | ||||
|  | ||||
| @ -532,43 +532,4 @@ frappe.ui.form.on("Bank Statement Import", { | ||||
| 			</table> | ||||
| 		`);
 | ||||
| 	}, | ||||
| 
 | ||||
| 	show_missing_link_values(frm, missing_link_values) { | ||||
| 		let can_be_created_automatically = missing_link_values.every( | ||||
| 			(d) => d.has_one_mandatory_field | ||||
| 		); | ||||
| 
 | ||||
| 		let html = missing_link_values | ||||
| 			.map((d) => { | ||||
| 				let doctype = d.doctype; | ||||
| 				let values = d.missing_values; | ||||
| 				return ` | ||||
| 					<h5>${doctype}</h5> | ||||
| 					<ul>${values.map((v) => `<li>${v}</li>`).join("")}</ul> | ||||
| 				`;
 | ||||
| 			}) | ||||
| 			.join(""); | ||||
| 
 | ||||
| 		if (can_be_created_automatically) { | ||||
| 			// prettier-ignore
 | ||||
| 			let message = __('There are some linked records which needs to be created before we can import your file. Do you want to create the following missing records automatically?'); | ||||
| 			frappe.confirm(message + html, () => { | ||||
| 				frm.call("create_missing_link_values", { | ||||
| 					missing_link_values, | ||||
| 				}).then((r) => { | ||||
| 					let records = r.message; | ||||
| 					frappe.msgprint(__( | ||||
| 						"Created {0} records successfully.", [ | ||||
| 							records.length, | ||||
| 						] | ||||
| 					)); | ||||
| 				}); | ||||
| 			}); | ||||
| 		} else { | ||||
| 			frappe.msgprint( | ||||
| 				// prettier-ignore
 | ||||
| 				__('The following records needs to be created before we can import your file.') + html | ||||
| 			); | ||||
| 		} | ||||
| 	}, | ||||
| }); | ||||
|  | ||||
| @ -15,12 +15,14 @@ from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profi | ||||
| test_dependencies = ["Item", "Cost Center"] | ||||
| 
 | ||||
| class TestBankTransaction(unittest.TestCase): | ||||
| 	def setUp(self): | ||||
| 	@classmethod | ||||
| 	def setUpClass(cls): | ||||
| 		make_pos_profile() | ||||
| 		add_transactions() | ||||
| 		add_vouchers() | ||||
| 
 | ||||
| 	def tearDown(self): | ||||
| 	@classmethod | ||||
| 	def tearDownClass(cls): | ||||
| 		for bt in frappe.get_all("Bank Transaction"): | ||||
| 			doc = frappe.get_doc("Bank Transaction", bt.name) | ||||
| 			doc.cancel() | ||||
| @ -33,9 +35,6 @@ class TestBankTransaction(unittest.TestCase): | ||||
| 		# Delete POS Profile | ||||
| 		frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 
 | ||||
| 		frappe.flags.test_bank_transactions_created = False | ||||
| 		frappe.flags.test_payments_created = False | ||||
| 
 | ||||
| 	# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction. | ||||
| 	def test_linked_payments(self): | ||||
| 		bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic")) | ||||
| @ -44,8 +43,8 @@ class TestBankTransaction(unittest.TestCase): | ||||
| 
 | ||||
| 	# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment | ||||
| 	def test_reconcile(self): | ||||
| 		bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G")) | ||||
| 		payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) | ||||
| 		bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G")) | ||||
| 		payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700)) | ||||
| 		vouchers = json.dumps([{ | ||||
| 		"payment_doctype":"Payment Entry", | ||||
| 		"payment_name":payment.name, | ||||
| @ -62,7 +61,6 @@ class TestBankTransaction(unittest.TestCase): | ||||
| 	def test_debit_credit_output(self): | ||||
| 		bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07")) | ||||
| 		linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match']) | ||||
| 		print(linked_payments) | ||||
| 		self.assertTrue(linked_payments[0][3]) | ||||
| 
 | ||||
| 	# Check error if already reconciled | ||||
| @ -116,10 +114,6 @@ def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"): | ||||
| 		pass | ||||
| 
 | ||||
| def add_transactions(): | ||||
| 	if frappe.flags.test_bank_transactions_created: | ||||
| 		return | ||||
| 
 | ||||
| 	frappe.set_user("Administrator") | ||||
| 	create_bank_account() | ||||
| 
 | ||||
| 	doc = frappe.get_doc({ | ||||
| @ -172,14 +166,8 @@ def add_transactions(): | ||||
| 	}).insert() | ||||
| 	doc.submit() | ||||
| 
 | ||||
| 	frappe.flags.test_bank_transactions_created = True | ||||
| 
 | ||||
| def add_vouchers(): | ||||
| 	if frappe.flags.test_payments_created: | ||||
| 		return | ||||
| 
 | ||||
| 	frappe.set_user("Administrator") | ||||
| 
 | ||||
| 	try: | ||||
| 		frappe.get_doc({ | ||||
| 			"doctype": "Supplier", | ||||
| @ -272,13 +260,6 @@ def add_vouchers(): | ||||
| 	except frappe.DuplicateEntryError: | ||||
| 		pass | ||||
| 
 | ||||
| 	si = create_sales_invoice(customer="Fayva", qty=1, rate=109080) | ||||
| 	pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") | ||||
| 	pe.reference_no = "Fayva Oct 18" | ||||
| 	pe.reference_date = "2018-10-29" | ||||
| 	pe.insert() | ||||
| 	pe.submit() | ||||
| 
 | ||||
| 	mode_of_payment = frappe.get_doc({ | ||||
| 		"doctype": "Mode of Payment", | ||||
| 		"name": "Cash" | ||||
| @ -291,14 +272,12 @@ def add_vouchers(): | ||||
| 		}) | ||||
| 		mode_of_payment.save() | ||||
| 
 | ||||
| 	si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_submit=1) | ||||
| 	si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1) | ||||
| 	si.is_pos = 1 | ||||
| 	si.append("payments", { | ||||
| 		"mode_of_payment": "Cash", | ||||
| 		"account": "_Test Bank - _TC", | ||||
| 		"amount": 109080 | ||||
| 	}) | ||||
| 	si.save() | ||||
| 	si.insert() | ||||
| 	si.submit() | ||||
| 
 | ||||
| 	frappe.flags.test_payments_created = True | ||||
|  | ||||
| @ -57,6 +57,7 @@ class CForm(Document): | ||||
| 		total = sum([flt(d.grand_total) for d in self.get('invoices')]) | ||||
| 		frappe.db.set(self, 'total_invoiced_amount', total) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_invoice_details(self, invoice_no): | ||||
| 		"""	Pull details from invoices for referrence """ | ||||
| 		if invoice_no: | ||||
|  | ||||
| @ -50,6 +50,7 @@ class CostCenter(NestedSet): | ||||
| 				frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format( | ||||
| 					frappe.bold(self.parent_cost_center))) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def convert_group_to_ledger(self): | ||||
| 		if self.check_if_child_exists(): | ||||
| 			frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes")) | ||||
| @ -60,6 +61,7 @@ class CostCenter(NestedSet): | ||||
| 			self.save() | ||||
| 			return 1 | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def convert_ledger_to_group(self): | ||||
| 		if cint(self.enable_distributed_cost_center): | ||||
| 			frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group")) | ||||
|  | ||||
| @ -27,6 +27,7 @@ class ExchangeRateRevaluation(Document): | ||||
| 		if not (self.company and self.posting_date): | ||||
| 			frappe.throw(_("Please select Company and Posting Date to getting entries")) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_accounts_data(self, account=None): | ||||
| 		accounts = [] | ||||
| 		self.validate_mandatory() | ||||
| @ -95,6 +96,7 @@ class ExchangeRateRevaluation(Document): | ||||
| 			message = _("No outstanding invoices found") | ||||
| 		frappe.msgprint(message) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def make_jv_entry(self): | ||||
| 		if self.total_gain_loss == 0: | ||||
| 			return | ||||
|  | ||||
| @ -12,6 +12,7 @@ from frappe.model.document import Document | ||||
| class FiscalYearIncorrectDate(frappe.ValidationError): pass | ||||
| 
 | ||||
| class FiscalYear(Document): | ||||
| 	@frappe.whitelist() | ||||
| 	def set_as_default(self): | ||||
| 		frappe.db.set_value("Global Defaults", None, "current_fiscal_year", self.name) | ||||
| 		global_defaults = frappe.get_doc("Global Defaults") | ||||
| @ -54,7 +55,7 @@ class FiscalYear(Document): | ||||
| 	def on_update(self): | ||||
| 		check_duplicate_fiscal_year(self) | ||||
| 		frappe.cache().delete_value("fiscal_years") | ||||
| 	 | ||||
| 
 | ||||
| 	def on_trash(self): | ||||
| 		global_defaults = frappe.get_doc("Global Defaults") | ||||
| 		if global_defaults.current_fiscal_year == self.name: | ||||
|  | ||||
| @ -290,4 +290,8 @@ def rename_temporarily_named_docs(doctype): | ||||
| 		oldname = doc.name | ||||
| 		set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc) | ||||
| 		newname = doc.name | ||||
| 		frappe.db.sql("""UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s""".format(doctype), (newname, oldname)) | ||||
| 		frappe.db.sql( | ||||
| 			"UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s".format(doctype), | ||||
| 			(newname, oldname), | ||||
| 			auto_commit=True | ||||
| 		) | ||||
|  | ||||
| @ -125,6 +125,7 @@ class InvoiceDiscounting(AccountsController): | ||||
| 
 | ||||
| 		make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No') | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def create_disbursement_entry(self): | ||||
| 		je = frappe.new_doc("Journal Entry") | ||||
| 		je.voucher_type = 'Journal Entry' | ||||
| @ -174,6 +175,7 @@ class InvoiceDiscounting(AccountsController): | ||||
| 
 | ||||
| 		return je | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def close_loan(self): | ||||
| 		je = frappe.new_doc("Journal Entry") | ||||
| 		je.voucher_type = 'Journal Entry' | ||||
|  | ||||
| @ -564,6 +564,7 @@ class JournalEntry(AccountsController): | ||||
| 		if gl_map: | ||||
| 			make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj, update_outstanding=update_outstanding) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_balance(self): | ||||
| 		if not self.get('accounts'): | ||||
| 			msgprint(_("'Entries' cannot be empty"), raise_exception=True) | ||||
|  | ||||
| @ -8,6 +8,7 @@ from frappe.utils import (flt, add_months) | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class MonthlyDistribution(Document): | ||||
| 	@frappe.whitelist() | ||||
| 	def get_months(self): | ||||
| 		month_list = ['January','February','March','April','May','June','July','August','September', | ||||
| 		'October','November','December'] | ||||
|  | ||||
| @ -167,6 +167,7 @@ class OpeningInvoiceCreationTool(Document): | ||||
| 
 | ||||
| 		return invoice | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def make_invoices(self): | ||||
| 		self.validate_company() | ||||
| 		invoices = self.get_invoices() | ||||
|  | ||||
| @ -637,13 +637,13 @@ frappe.ui.form.on('Payment Entry', { | ||||
| 			let to_field = fields[key][1]; | ||||
| 
 | ||||
| 			if (filters[from_field] && !filters[to_field]) { | ||||
| 				frappe.throw(__("Error: {0} is mandatory field", | ||||
| 					[to_field.replace(/_/g, " ")] | ||||
| 				)); | ||||
| 				frappe.throw( | ||||
| 					__("Error: {0} is mandatory field", [to_field.replace(/_/g, " ")]) | ||||
| 				); | ||||
| 			} else if (filters[from_field] && filters[from_field] > filters[to_field]) { | ||||
| 				frappe.throw(__("{0}: {1} must be less than {2}", | ||||
| 					[key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")] | ||||
| 				)); | ||||
| 				frappe.throw( | ||||
| 					__("{0}: {1} must be less than {2}", [key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")]) | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| @ -692,6 +692,8 @@ frappe.ui.form.on('Payment Entry', { | ||||
| 						c.total_amount = d.invoice_amount; | ||||
| 						c.outstanding_amount = d.outstanding_amount; | ||||
| 						c.bill_no = d.bill_no; | ||||
| 						c.payment_term = d.payment_term; | ||||
| 						c.allocated_amount = d.allocated_amount; | ||||
| 
 | ||||
| 						if(!in_list(["Sales Order", "Purchase Order", "Expense Claim", "Fees"], d.voucher_type)) { | ||||
| 							if(flt(d.outstanding_amount) > 0) | ||||
| @ -774,12 +776,15 @@ frappe.ui.form.on('Payment Entry', { | ||||
| 		} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) { | ||||
| 			if(paid_amount > total_negative_outstanding) { | ||||
| 				if(total_negative_outstanding == 0) { | ||||
| 					frappe.msgprint(__("Cannot {0} {1} {2} without any negative outstanding invoice", | ||||
| 						[frm.doc.payment_type, | ||||
| 							(frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type])); | ||||
| 					frappe.msgprint( | ||||
| 						__("Cannot {0} {1} {2} without any negative outstanding invoice", [frm.doc.payment_type, | ||||
| 							(frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type]) | ||||
| 					); | ||||
| 					return false | ||||
| 				} else { | ||||
| 					frappe.msgprint(__("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding])); | ||||
| 					frappe.msgprint( | ||||
| 						__("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding]) | ||||
| 					); | ||||
| 					return false; | ||||
| 				} | ||||
| 			} else { | ||||
| @ -791,10 +796,13 @@ frappe.ui.form.on('Payment Entry', { | ||||
| 		} | ||||
| 
 | ||||
| 		$.each(frm.doc.references || [], function(i, row) { | ||||
| 			row.allocated_amount = 0 //If allocate payment amount checkbox is unchecked, set zero to allocate amount
 | ||||
| 			if(frappe.flags.allocate_payment_amount != 0){ | ||||
| 				if(row.outstanding_amount > 0 && allocated_positive_outstanding > 0) { | ||||
| 					if(row.outstanding_amount >= allocated_positive_outstanding) { | ||||
| 			if (frappe.flags.allocate_payment_amount == 0) { | ||||
| 				//If allocate payment amount checkbox is unchecked, set zero to allocate amount
 | ||||
| 				row.allocated_amount = 0; | ||||
| 
 | ||||
| 			} else if (frappe.flags.allocate_payment_amount != 0 && !row.allocated_amount) { | ||||
| 				if (row.outstanding_amount > 0 && allocated_positive_outstanding > 0) { | ||||
| 					if (row.outstanding_amount >= allocated_positive_outstanding) { | ||||
| 						row.allocated_amount = allocated_positive_outstanding; | ||||
| 					} else { | ||||
| 						row.allocated_amount = row.outstanding_amount; | ||||
| @ -802,9 +810,11 @@ frappe.ui.form.on('Payment Entry', { | ||||
| 
 | ||||
| 					allocated_positive_outstanding -= flt(row.allocated_amount); | ||||
| 				} else if (row.outstanding_amount < 0 && allocated_negative_outstanding) { | ||||
| 					if(Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) | ||||
| 					if (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) { | ||||
| 						row.allocated_amount = -1*allocated_negative_outstanding; | ||||
| 					else row.allocated_amount = row.outstanding_amount; | ||||
| 					} else { | ||||
| 						row.allocated_amount = row.outstanding_amount; | ||||
| 					}; | ||||
| 
 | ||||
| 					allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount)); | ||||
| 				} | ||||
|  | ||||
| @ -333,33 +333,50 @@ class PaymentEntry(AccountsController): | ||||
| 		invoice_payment_amount_map = {} | ||||
| 		invoice_paid_amount_map = {} | ||||
| 
 | ||||
| 		for reference in self.get('references'): | ||||
| 			if reference.payment_term and reference.reference_name: | ||||
| 				key = (reference.payment_term, reference.reference_name) | ||||
| 		for ref in self.get('references'): | ||||
| 			if ref.payment_term and ref.reference_name: | ||||
| 				key = (ref.payment_term, ref.reference_name) | ||||
| 				invoice_payment_amount_map.setdefault(key, 0.0) | ||||
| 				invoice_payment_amount_map[key] += reference.allocated_amount | ||||
| 				invoice_payment_amount_map[key] += ref.allocated_amount | ||||
| 
 | ||||
| 				if not invoice_paid_amount_map.get(key): | ||||
| 					payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name}, | ||||
| 						fields=['paid_amount', 'payment_amount', 'payment_term']) | ||||
| 					payment_schedule = frappe.get_all( | ||||
| 						'Payment Schedule', | ||||
| 						filters={'parent': ref.reference_name}, | ||||
| 						fields=['paid_amount', 'payment_amount', 'payment_term', 'discount', 'outstanding'] | ||||
| 					) | ||||
| 					for term in payment_schedule: | ||||
| 						invoice_key = (term.payment_term, reference.reference_name) | ||||
| 						invoice_key = (term.payment_term, ref.reference_name) | ||||
| 						invoice_paid_amount_map.setdefault(invoice_key, {}) | ||||
| 						invoice_paid_amount_map[invoice_key]['outstanding'] = term.payment_amount - term.paid_amount | ||||
| 						invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding | ||||
| 						invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100) | ||||
| 
 | ||||
| 		for key, allocated_amount in iteritems(invoice_payment_amount_map): | ||||
| 			outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding')) | ||||
| 			discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get('discounted_amt')) | ||||
| 
 | ||||
| 		for key, amount in iteritems(invoice_payment_amount_map): | ||||
| 			if cancel: | ||||
| 				frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s | ||||
| 					WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0])) | ||||
| 				frappe.db.sql(""" | ||||
| 					UPDATE `tabPayment Schedule` | ||||
| 					SET | ||||
| 						paid_amount = `paid_amount` - %s, | ||||
| 						discounted_amount = `discounted_amount` - %s, | ||||
| 						outstanding = `outstanding` + %s | ||||
| 					WHERE parent = %s and payment_term = %s""", | ||||
| 					(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0])) | ||||
| 			else: | ||||
| 				outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding')) | ||||
| 
 | ||||
| 				if amount > outstanding: | ||||
| 				if allocated_amount > outstanding: | ||||
| 					frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0])) | ||||
| 
 | ||||
| 				if amount and outstanding: | ||||
| 					frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s | ||||
| 							WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0])) | ||||
| 				if allocated_amount and outstanding: | ||||
| 					frappe.db.sql(""" | ||||
| 						UPDATE `tabPayment Schedule` | ||||
| 						SET | ||||
| 							paid_amount = `paid_amount` + %s, | ||||
| 							discounted_amount = `discounted_amount` + %s, | ||||
| 							outstanding = `outstanding` - %s | ||||
| 						WHERE parent = %s and payment_term = %s""", | ||||
| 					(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0])) | ||||
| 
 | ||||
| 	def set_status(self): | ||||
| 		if self.docstatus == 2: | ||||
| @ -708,6 +725,8 @@ def get_outstanding_reference_documents(args): | ||||
| 	outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"), | ||||
| 		args.get("party_account"), filters=args, condition=condition) | ||||
| 
 | ||||
| 	outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices) | ||||
| 
 | ||||
| 	for d in outstanding_invoices: | ||||
| 		d["exchange_rate"] = 1 | ||||
| 		if party_account_currency != company_currency: | ||||
| @ -735,6 +754,46 @@ def get_outstanding_reference_documents(args): | ||||
| 	return data | ||||
| 
 | ||||
| 
 | ||||
| def split_invoices_based_on_payment_terms(outstanding_invoices): | ||||
| 	invoice_ref_based_on_payment_terms = {} | ||||
| 	for idx, d in enumerate(outstanding_invoices): | ||||
| 		if d.voucher_type in ['Sales Invoice', 'Purchase Invoice']: | ||||
| 			payment_term_template = frappe.db.get_value(d.voucher_type, d.voucher_no, 'payment_terms_template') | ||||
| 			if payment_term_template: | ||||
| 				allocate_payment_based_on_payment_terms = frappe.db.get_value( | ||||
| 					'Payment Terms Template', payment_term_template, 'allocate_payment_based_on_payment_terms') | ||||
| 				if allocate_payment_based_on_payment_terms: | ||||
| 					payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': d.voucher_no}, fields=["*"]) | ||||
| 
 | ||||
| 					for payment_term in payment_schedule: | ||||
| 						if payment_term.outstanding > 0.1: | ||||
| 							invoice_ref_based_on_payment_terms.setdefault(idx, []) | ||||
| 							invoice_ref_based_on_payment_terms[idx].append(frappe._dict({ | ||||
| 								'due_date': d.due_date, | ||||
| 								'currency': d.currency, | ||||
| 								'voucher_no': d.voucher_no, | ||||
| 								'voucher_type': d.voucher_type, | ||||
| 								'posting_date': d.posting_date, | ||||
| 								'invoice_amount': flt(d.invoice_amount), | ||||
| 								'outstanding_amount': flt(d.outstanding_amount), | ||||
| 								'payment_amount': payment_term.payment_amount, | ||||
| 								'payment_term': payment_term.payment_term, | ||||
| 								'allocated_amount': payment_term.outstanding | ||||
| 							})) | ||||
| 
 | ||||
| 	if invoice_ref_based_on_payment_terms: | ||||
| 		for idx, ref in invoice_ref_based_on_payment_terms.items(): | ||||
| 			voucher_no = outstanding_invoices[idx]['voucher_no'] | ||||
| 			voucher_type = outstanding_invoices[idx]['voucher_type'] | ||||
| 
 | ||||
| 			frappe.msgprint(_("Spliting {} {} into {} rows as per payment terms").format( | ||||
| 				voucher_type, voucher_no, len(ref)), alert=True) | ||||
| 
 | ||||
| 			outstanding_invoices.pop(idx - 1) | ||||
| 			outstanding_invoices += invoice_ref_based_on_payment_terms[idx] | ||||
| 	 | ||||
| 	return outstanding_invoices | ||||
| 
 | ||||
| def get_orders_to_be_billed(posting_date, party_type, party, | ||||
| 	company, party_account_currency, company_currency, cost_center=None, filters=None): | ||||
| 	if party_type == "Customer": | ||||
| @ -1091,6 +1150,8 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= | ||||
| 	paid_amount, received_amount = set_paid_amount_and_received_amount( | ||||
| 		dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc) | ||||
| 
 | ||||
| 	paid_amount, received_amount, discount_amount = apply_early_payment_discount(paid_amount, received_amount, doc) | ||||
| 
 | ||||
| 	pe = frappe.new_doc("Payment Entry") | ||||
| 	pe.payment_type = payment_type | ||||
| 	pe.company = doc.company | ||||
| @ -1160,11 +1221,20 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= | ||||
| 
 | ||||
| 	pe.setup_party_account_field() | ||||
| 	pe.set_missing_values() | ||||
| 
 | ||||
| 	if party_account and bank: | ||||
| 		if dt == "Employee Advance": | ||||
| 			reference_doc = doc | ||||
| 		pe.set_exchange_rate(ref_doc=reference_doc) | ||||
| 		pe.set_amounts() | ||||
| 		if discount_amount: | ||||
| 			pe.set_gain_or_loss(account_details={ | ||||
| 				'account': frappe.get_cached_value('Company', pe.company, "default_discount_account"), | ||||
| 				'cost_center': pe.cost_center or frappe.get_cached_value('Company', pe.company, "cost_center"), | ||||
| 				'amount': discount_amount * (-1 if payment_type == "Pay" else 1) | ||||
| 			}) | ||||
| 			pe.set_difference_amount() | ||||
| 
 | ||||
| 	return pe | ||||
| 
 | ||||
| def get_bank_cash_account(doc, bank_account): | ||||
| @ -1285,6 +1355,33 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta | ||||
| 				paid_amount = received_amount * doc.get('exchange_rate', 1) | ||||
| 	return paid_amount, received_amount | ||||
| 
 | ||||
| def apply_early_payment_discount(paid_amount, received_amount, doc): | ||||
| 	total_discount = 0 | ||||
| 	if doc.doctype in ['Sales Invoice', 'Purchase Invoice'] and doc.payment_schedule: | ||||
| 		for term in doc.payment_schedule: | ||||
| 			if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date: | ||||
| 				if term.discount_type == 'Percentage': | ||||
| 					discount_amount = flt(doc.get('grand_total')) * (term.discount / 100) | ||||
| 				else: | ||||
| 					discount_amount = term.discount | ||||
| 
 | ||||
| 				discount_amount_in_foreign_currency = discount_amount * doc.get('conversion_rate', 1) | ||||
| 
 | ||||
| 				if doc.doctype == 'Sales Invoice': | ||||
| 					paid_amount -= discount_amount | ||||
| 					received_amount -= discount_amount_in_foreign_currency | ||||
| 				else: | ||||
| 					received_amount -= discount_amount | ||||
| 					paid_amount -= discount_amount_in_foreign_currency | ||||
| 
 | ||||
| 				total_discount += discount_amount | ||||
| 
 | ||||
| 		if total_discount: | ||||
| 			money = frappe.utils.fmt_money(total_discount, currency=doc.get('currency')) | ||||
| 			frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1) | ||||
| 
 | ||||
| 	return paid_amount, received_amount, total_discount | ||||
| 
 | ||||
| def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount): | ||||
| 	references = [] | ||||
| 	for payment_term in payment_schedule: | ||||
|  | ||||
| @ -193,6 +193,34 @@ class TestPaymentEntry(unittest.TestCase): | ||||
| 		self.assertEqual(si.payment_schedule[0].paid_amount, 200.0) | ||||
| 		self.assertEqual(si.payment_schedule[1].paid_amount, 36.0) | ||||
| 
 | ||||
| 	def test_payment_entry_against_payment_terms_with_discount(self): | ||||
| 		si = create_sales_invoice(do_not_save=1, qty=1, rate=200) | ||||
| 		create_payment_terms_template_with_discount() | ||||
| 		si.payment_terms_template = 'Test Discount Template' | ||||
| 
 | ||||
| 		frappe.db.set_value('Company', si.company, 'default_discount_account', 'Write Off - _TC') | ||||
| 
 | ||||
| 		si.append('taxes', { | ||||
| 			"charge_type": "On Net Total", | ||||
| 			"account_head": "_Test Account Service Tax - _TC", | ||||
| 			"cost_center": "_Test Cost Center - _TC", | ||||
| 			"description": "Service Tax", | ||||
| 			"rate": 18 | ||||
| 		}) | ||||
| 		si.save() | ||||
| 
 | ||||
| 		si.submit() | ||||
| 
 | ||||
| 		pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC") | ||||
| 		pe.submit() | ||||
| 		si.load_from_db() | ||||
| 
 | ||||
| 		self.assertEqual(pe.references[0].payment_term, '30 Credit Days with 10% Discount') | ||||
| 		self.assertEqual(si.payment_schedule[0].payment_amount, 236.0) | ||||
| 		self.assertEqual(si.payment_schedule[0].paid_amount, 212.40) | ||||
| 		self.assertEqual(si.payment_schedule[0].outstanding, 0) | ||||
| 		self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6) | ||||
| 
 | ||||
| 
 | ||||
| 	def test_payment_against_purchase_invoice_to_check_status(self): | ||||
| 		pi = make_purchase_invoice(supplier="_Test Supplier USD", debit_to="_Test Payable USD - _TC", | ||||
| @ -591,6 +619,26 @@ def create_payment_terms_template(): | ||||
| 			}] | ||||
| 		}).insert() | ||||
| 
 | ||||
| def create_payment_terms_template_with_discount(): | ||||
| 
 | ||||
| 	create_payment_term('30 Credit Days with 10% Discount') | ||||
| 
 | ||||
| 	if not frappe.db.exists('Payment Terms Template', 'Test Discount Template'): | ||||
| 		payment_term_template = frappe.get_doc({ | ||||
| 			'doctype': 'Payment Terms Template', | ||||
| 			'template_name': 'Test Discount Template', | ||||
| 			'allocate_payment_based_on_payment_terms': 1, | ||||
| 			'terms': [{ | ||||
| 				'doctype': 'Payment Terms Template Detail', | ||||
| 				'payment_term': '30 Credit Days with 10% Discount', | ||||
| 				'invoice_portion': 100, | ||||
| 				'credit_days_based_on': 'Day(s) after invoice date', | ||||
| 				'credit_days': 2, | ||||
| 				'discount': 10, | ||||
| 				'discount_validity_based_on': 'Day(s) after invoice date', | ||||
| 				'discount_validity': 1 | ||||
| 			}] | ||||
| 		}).insert() | ||||
| 
 | ||||
| def create_payment_term(name): | ||||
| 	if not frappe.db.exists('Payment Term', name): | ||||
|  | ||||
| @ -58,7 +58,7 @@ | ||||
|    "fieldname": "total_amount", | ||||
|    "fieldtype": "Float", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Total Amount", | ||||
|    "label": "Grand Total", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
| @ -92,9 +92,10 @@ | ||||
|    "options": "Payment Term" | ||||
|   } | ||||
|  ], | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-03-13 12:07:19.362539", | ||||
|  "modified": "2021-02-10 11:25:47.144392", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Payment Entry Reference", | ||||
|  | ||||
| @ -11,6 +11,7 @@ from erpnext.accounts.utils import (get_outstanding_invoices, | ||||
| from erpnext.controllers.accounts_controller import get_advance_payment_entries | ||||
| 
 | ||||
| class PaymentReconciliation(Document): | ||||
| 	@frappe.whitelist() | ||||
| 	def get_unreconciled_entries(self): | ||||
| 		self.get_nonreconciled_payment_entries() | ||||
| 		self.get_invoice_entries() | ||||
| @ -147,6 +148,7 @@ class PaymentReconciliation(Document): | ||||
| 			ent.currency = e.get('currency') | ||||
| 			ent.outstanding_amount = e.get('outstanding_amount') | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def reconcile(self, args): | ||||
| 		for e in self.get('payments'): | ||||
| 			e.invoice_type = None | ||||
| @ -197,6 +199,7 @@ class PaymentReconciliation(Document): | ||||
| 			'difference_account': row.difference_account | ||||
| 		}) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_difference_amount(self, child_row): | ||||
| 		if child_row.get("reference_type") != 'Payment Entry': return | ||||
| 
 | ||||
|  | ||||
| @ -6,11 +6,23 @@ | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "payment_term", | ||||
|   "section_break_15", | ||||
|   "description", | ||||
|   "section_break_4", | ||||
|   "due_date", | ||||
|   "invoice_portion", | ||||
|   "payment_amount", | ||||
|   "mode_of_payment", | ||||
|   "column_break_5", | ||||
|   "invoice_portion", | ||||
|   "section_break_6", | ||||
|   "discount_type", | ||||
|   "discount_date", | ||||
|   "column_break_9", | ||||
|   "discount", | ||||
|   "section_break_9", | ||||
|   "payment_amount", | ||||
|   "discounted_amount", | ||||
|   "column_break_3", | ||||
|   "outstanding", | ||||
|   "paid_amount" | ||||
|  ], | ||||
|  "fields": [ | ||||
| @ -25,6 +37,7 @@ | ||||
|   }, | ||||
|   { | ||||
|    "columns": 2, | ||||
|    "fetch_from": "payment_term.description", | ||||
|    "fieldname": "description", | ||||
|    "fieldtype": "Small Text", | ||||
|    "in_list_view": 1, | ||||
| @ -62,14 +75,82 @@ | ||||
|    "options": "Mode of Payment" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "paid_amount", | ||||
|    "fieldname": "paid_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Paid Amount" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_3", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "depends_on": "discounted_amount", | ||||
|    "fieldname": "discounted_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Discounted Amount", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fetch_from": "payment_amount", | ||||
|    "fieldname": "outstanding", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Outstanding", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_5", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "discount", | ||||
|    "fieldname": "discount_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Discount Date", | ||||
|    "mandatory_depends_on": "discount" | ||||
|   }, | ||||
|   { | ||||
|    "default": "Percentage", | ||||
|    "fetch_from": "payment_term.discount_type", | ||||
|    "fieldname": "discount_type", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Discount Type", | ||||
|    "options": "Percentage\nAmount" | ||||
|   }, | ||||
|   { | ||||
|    "fetch_from": "payment_term.discount", | ||||
|    "fieldname": "discount", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Discount" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_9", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "section_break_15", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Description" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_6", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_9", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_4", | ||||
|    "fieldtype": "Section Break" | ||||
|   } | ||||
|  ], | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-03-13 17:58:24.729526", | ||||
|  "modified": "2021-02-15 21:03:12.540546", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Payment Schedule", | ||||
|  | ||||
| @ -1,2 +1,22 @@ | ||||
| // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| frappe.ui.form.on('Payment Term', { | ||||
| 	onload(frm) { | ||||
| 		frm.trigger('set_dynamic_description'); | ||||
| 	}, | ||||
| 	discount(frm) { | ||||
| 		frm.trigger('set_dynamic_description'); | ||||
| 	}, | ||||
| 	discount_type(frm) { | ||||
| 		frm.trigger('set_dynamic_description'); | ||||
| 	}, | ||||
| 	set_dynamic_description(frm) { | ||||
| 		if (frm.doc.discount) { | ||||
| 			let description = __("{0}% of total invoice value will be given as discount.", [frm.doc.discount]); | ||||
| 			if (frm.doc.discount_type == 'Amount') { | ||||
| 				description = __("{0} will be given as discount.", [fmt_money(frm.doc.discount)]); | ||||
| 			} | ||||
| 			frm.set_df_property("discount", "description", description); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| @ -1,386 +1,166 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_guest_to_view": 0,  | ||||
|  "allow_import": 1,  | ||||
|  "allow_rename": 1,  | ||||
|  "autoname": "field:payment_term_name",  | ||||
|  "beta": 0,  | ||||
|  "creation": "2017-08-10 15:24:54.876365",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "",  | ||||
|  "editable_grid": 1,  | ||||
|  "engine": "InnoDB",  | ||||
|  "actions": [], | ||||
|  "allow_import": 1, | ||||
|  "allow_rename": 1, | ||||
|  "autoname": "field:payment_term_name", | ||||
|  "creation": "2017-08-10 15:24:54.876365", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "payment_term_name", | ||||
|   "invoice_portion", | ||||
|   "mode_of_payment", | ||||
|   "column_break_3", | ||||
|   "due_date_based_on", | ||||
|   "credit_days", | ||||
|   "credit_months", | ||||
|   "section_break_8", | ||||
|   "discount_type", | ||||
|   "discount", | ||||
|   "column_break_11", | ||||
|   "discount_validity_based_on", | ||||
|   "discount_validity", | ||||
|   "section_break_6", | ||||
|   "description" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 1,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "payment_term_name",  | ||||
|    "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": "Payment Term Name",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "bold": 1, | ||||
|    "fieldname": "payment_term_name", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "Payment Term Name", | ||||
|    "unique": 1 | ||||
|   }, | ||||
|   { | ||||
|    "description": "Provide the invoice portion in percent", | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 1,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "invoice_portion",  | ||||
|    "fieldtype": "Float",  | ||||
|    "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": "Invoice Portion",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "bold": 1, | ||||
|    "fieldname": "invoice_portion", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Invoice Portion (%)" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "mode_of_payment",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Mode of Payment",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Mode of Payment",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "mode_of_payment", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Mode of Payment", | ||||
|    "options": "Mode of Payment" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "column_break_3",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "column_break_3", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 1,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "due_date_based_on",  | ||||
|    "fieldtype": "Select",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Due Date Based On",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "bold": 1, | ||||
|    "fieldname": "due_date_based_on", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Due Date Based On", | ||||
|    "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month" | ||||
|   }, | ||||
|   { | ||||
|    "description": "Give number of days according to prior selection", | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 1,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",  | ||||
|    "fieldname": "credit_days",  | ||||
|    "fieldtype": "Int",  | ||||
|    "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": "Credit Days",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "bold": 1, | ||||
|    "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)", | ||||
|    "fieldname": "credit_days", | ||||
|    "fieldtype": "Int", | ||||
|    "label": "Credit Days" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",  | ||||
|    "fieldname": "credit_months",  | ||||
|    "fieldtype": "Int",  | ||||
|    "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": "Credit Months",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'", | ||||
|    "fieldname": "credit_months", | ||||
|    "fieldtype": "Int", | ||||
|    "label": "Credit Months" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "section_break_6",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "fieldname": "section_break_6", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 1,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "description",  | ||||
|    "fieldtype": "Small Text",  | ||||
|    "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": "Description",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|    "bold": 1, | ||||
|    "fieldname": "description", | ||||
|    "fieldtype": "Small Text", | ||||
|    "label": "Description" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_8", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Discount Settings" | ||||
|   }, | ||||
|   { | ||||
|    "default": "Percentage", | ||||
|    "fieldname": "discount_type", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Discount Type", | ||||
|    "options": "Percentage\nAmount" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "discount", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Discount" | ||||
|   }, | ||||
|   { | ||||
|    "default": "Day(s) after invoice date", | ||||
|    "depends_on": "discount", | ||||
|    "fieldname": "discount_validity_based_on", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Discount Validity Based On", | ||||
|    "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "discount", | ||||
|    "fieldname": "discount_validity", | ||||
|    "fieldtype": "Int", | ||||
|    "label": "Discount Validity", | ||||
|    "mandatory_depends_on": "discount" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_11", | ||||
|    "fieldtype": "Column Break" | ||||
|   } | ||||
|  ],  | ||||
|  "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-10-14 10:47:32.830478",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Accounts",  | ||||
|  "name": "Payment Term",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  ], | ||||
|  "links": [], | ||||
|  "modified": "2021-02-15 20:30:56.256403", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Payment Term", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "apply_user_permissions": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 1,  | ||||
|    "delete": 1,  | ||||
|    "email": 1,  | ||||
|    "export": 1,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 1,  | ||||
|    "read": 1,  | ||||
|    "report": 1,  | ||||
|    "role": "System Manager",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 1,  | ||||
|    "submit": 0,  | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "System Manager", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   },  | ||||
|   }, | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "apply_user_permissions": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 1,  | ||||
|    "delete": 1,  | ||||
|    "email": 1,  | ||||
|    "export": 1,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 1,  | ||||
|    "read": 1,  | ||||
|    "report": 1,  | ||||
|    "role": "Accounts Manager",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 1,  | ||||
|    "submit": 0,  | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Accounts Manager", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   },  | ||||
|   }, | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "apply_user_permissions": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 1,  | ||||
|    "delete": 1,  | ||||
|    "email": 1,  | ||||
|    "export": 1,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 1,  | ||||
|    "read": 1,  | ||||
|    "report": 1,  | ||||
|    "role": "Accounts User",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 1,  | ||||
|    "submit": 0,  | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Accounts User", | ||||
|    "share": 1, | ||||
|    "write": 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 | ||||
| } | ||||
|  ], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -3,11 +3,6 @@ | ||||
| 
 | ||||
| frappe.ui.form.on('Payment Terms Template', { | ||||
| 	setup: function(frm) { | ||||
| 		frm.add_fetch("payment_term", "description", "description"); | ||||
| 		frm.add_fetch("payment_term", "invoice_portion", "invoice_portion"); | ||||
| 		frm.add_fetch("payment_term", "due_date_based_on", "due_date_based_on"); | ||||
| 		frm.add_fetch("payment_term", "credit_days", "credit_days"); | ||||
| 		frm.add_fetch("payment_term", "credit_months", "credit_months"); | ||||
| 		frm.add_fetch("payment_term", "mode_of_payment", "mode_of_payment"); | ||||
| 		 | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| @ -13,7 +13,6 @@ from frappe import _ | ||||
| class PaymentTermsTemplate(Document): | ||||
| 	def validate(self): | ||||
| 		self.validate_invoice_portion() | ||||
| 		self.validate_credit_days() | ||||
| 		self.check_duplicate_terms() | ||||
| 
 | ||||
| 	def validate_invoice_portion(self): | ||||
| @ -24,11 +23,6 @@ class PaymentTermsTemplate(Document): | ||||
| 		if flt(total_portion, 2) != 100.00: | ||||
| 			frappe.msgprint(_('Combined invoice portion must equal 100%'), raise_exception=1, indicator='red') | ||||
| 
 | ||||
| 	def validate_credit_days(self): | ||||
| 		for term in self.terms: | ||||
| 			if cint(term.credit_days) < 0: | ||||
| 				frappe.msgprint(_('Credit Days cannot be a negative number'), raise_exception=1, indicator='red') | ||||
| 
 | ||||
| 	def check_duplicate_terms(self): | ||||
| 		terms = [] | ||||
| 		for term in self.terms: | ||||
|  | ||||
| @ -1,278 +1,164 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_guest_to_view": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "autoname": "",  | ||||
|  "beta": 0,  | ||||
|  "creation": "2017-08-10 15:34:09.409562",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "",  | ||||
|  "editable_grid": 1,  | ||||
|  "engine": "InnoDB",  | ||||
|  "actions": [], | ||||
|  "creation": "2017-08-10 15:34:09.409562", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "payment_term", | ||||
|   "section_break_13", | ||||
|   "description", | ||||
|   "section_break_4", | ||||
|   "invoice_portion", | ||||
|   "mode_of_payment", | ||||
|   "column_break_3", | ||||
|   "due_date_based_on", | ||||
|   "credit_days", | ||||
|   "credit_months", | ||||
|   "section_break_8", | ||||
|   "discount_type", | ||||
|   "discount", | ||||
|   "column_break_11", | ||||
|   "discount_validity_based_on", | ||||
|   "discount_validity" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 2,  | ||||
|    "fieldname": "payment_term",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Payment Term",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Payment Term",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "columns": 2, | ||||
|    "fieldname": "payment_term", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Payment Term", | ||||
|    "options": "Payment Term" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 2,  | ||||
|    "fieldname": "description",  | ||||
|    "fieldtype": "Small Text",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Description",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "columns": 2, | ||||
|    "fetch_from": "payment_term.description", | ||||
|    "fieldname": "description", | ||||
|    "fieldtype": "Small Text", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Description" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 2,  | ||||
|    "default": "0",  | ||||
|    "fieldname": "invoice_portion",  | ||||
|    "fieldtype": "Percent",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Invoice Portion",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "columns": 2, | ||||
|    "fetch_from": "payment_term.invoice_portion", | ||||
|    "fetch_if_empty": 1, | ||||
|    "fieldname": "invoice_portion", | ||||
|    "fieldtype": "Float", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Invoice Portion (%)", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 2,  | ||||
|    "fieldname": "due_date_based_on",  | ||||
|    "fieldtype": "Select",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Due Date Based On",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "columns": 2, | ||||
|    "fetch_from": "payment_term.due_date_based_on", | ||||
|    "fetch_if_empty": 1, | ||||
|    "fieldname": "due_date_based_on", | ||||
|    "fieldtype": "Select", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Due Date Based On", | ||||
|    "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 2,  | ||||
|    "default": "0",  | ||||
|    "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",  | ||||
|    "fieldname": "credit_days",  | ||||
|    "fieldtype": "Int",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Credit Days",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "columns": 2, | ||||
|    "default": "0", | ||||
|    "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)", | ||||
|    "fetch_from": "payment_term.credit_days", | ||||
|    "fetch_if_empty": 1, | ||||
|    "fieldname": "credit_days", | ||||
|    "fieldtype": "Int", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Credit Days", | ||||
|    "non_negative": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "default": "0",  | ||||
|    "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",  | ||||
|    "fieldname": "credit_months",  | ||||
|    "fieldtype": "Int",  | ||||
|    "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": "Credit Months",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "0", | ||||
|    "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'", | ||||
|    "fetch_from": "payment_term.credit_months", | ||||
|    "fetch_if_empty": 1, | ||||
|    "fieldname": "credit_months", | ||||
|    "fieldtype": "Int", | ||||
|    "label": "Credit Months", | ||||
|    "non_negative": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "mode_of_payment",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Mode of Payment",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Mode of Payment",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|    "fetch_from": "payment_term.mode_of_payment", | ||||
|    "fieldname": "mode_of_payment", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Mode of Payment", | ||||
|    "options": "Mode of Payment" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_3", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_8", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Discount Settings" | ||||
|   }, | ||||
|   { | ||||
|    "default": "Percentage", | ||||
|    "fetch_from": "payment_term.discount_type", | ||||
|    "fetch_if_empty": 1, | ||||
|    "fieldname": "discount_type", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Discount Type", | ||||
|    "options": "Percentage\nAmount" | ||||
|   }, | ||||
|   { | ||||
|    "fetch_from": "payment_term.discount", | ||||
|    "fetch_if_empty": 1, | ||||
|    "fieldname": "discount", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Discount" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_11", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "default": "Day(s) after invoice date", | ||||
|    "depends_on": "discount", | ||||
|    "fetch_from": "payment_term.discount_validity_based_on", | ||||
|    "fetch_if_empty": 1, | ||||
|    "fieldname": "discount_validity_based_on", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Discount Validity Based On", | ||||
|    "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month" | ||||
|   }, | ||||
|   { | ||||
|    "collapsible": 1, | ||||
|    "fieldname": "section_break_13", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Description" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "discount", | ||||
|    "fetch_from": "payment_term.discount_validity", | ||||
|    "fetch_if_empty": 1, | ||||
|    "fieldname": "discount_validity", | ||||
|    "fieldtype": "Int", | ||||
|    "label": "Discount Validity", | ||||
|    "mandatory_depends_on": "discount" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_4", | ||||
|    "fieldtype": "Section Break" | ||||
|   } | ||||
|  ],  | ||||
|  "has_web_view": 0,  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "idx": 0,  | ||||
|  "image_view": 0,  | ||||
|  "in_create": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 1,  | ||||
|  "max_attachments": 0,  | ||||
|  "modified": "2018-08-21 16:15:55.143025",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Accounts",  | ||||
|  "name": "Payment Terms Template Detail",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [],  | ||||
|  "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 | ||||
|  ], | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2021-02-24 11:56:12.410807", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Payment Terms Template Detail", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -18,7 +18,7 @@ class POSClosingEntry(StatusUpdater): | ||||
| 
 | ||||
| 		self.validate_pos_closing() | ||||
| 		self.validate_pos_invoices() | ||||
| 	 | ||||
| 
 | ||||
| 	def validate_pos_closing(self): | ||||
| 		user = frappe.db.sql(""" | ||||
| 			SELECT name FROM `tabPOS Closing Entry` | ||||
| @ -37,12 +37,12 @@ class POSClosingEntry(StatusUpdater): | ||||
| 			bold_user = frappe.bold(self.user) | ||||
| 			frappe.throw(_("POS Closing Entry {} against {} between selected period") | ||||
| 				.format(bold_already_exists, bold_user), title=_("Invalid Period")) | ||||
| 	 | ||||
| 
 | ||||
| 	def validate_pos_invoices(self): | ||||
| 		invalid_rows = [] | ||||
| 		for d in self.pos_transactions: | ||||
| 			invalid_row = {'idx': d.idx} | ||||
| 			pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,  | ||||
| 			pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice, | ||||
| 				["consolidated_invoice", "pos_profile", "docstatus", "owner"], as_dict=1)[0] | ||||
| 			if pos_invoice.consolidated_invoice: | ||||
| 				invalid_row.setdefault('msg', []).append(_('POS Invoice is {}').format(frappe.bold("already consolidated"))) | ||||
| @ -68,14 +68,15 @@ class POSClosingEntry(StatusUpdater): | ||||
| 
 | ||||
| 		frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_payment_reconciliation_details(self): | ||||
| 		currency = frappe.get_cached_value('Company', self.company,  "default_currency") | ||||
| 		return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html", | ||||
| 			{"data": self, "currency": currency}) | ||||
| 	 | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		consolidate_pos_invoices(closing_entry=self) | ||||
| 	 | ||||
| 
 | ||||
| 	def on_cancel(self): | ||||
| 		unconsolidate_pos_invoices(closing_entry=self) | ||||
| 
 | ||||
|  | ||||
| @ -5,12 +5,21 @@ from __future__ import unicode_literals | ||||
| import frappe | ||||
| import unittest | ||||
| from frappe.utils import nowdate | ||||
| from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry | ||||
| from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice | ||||
| from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening | ||||
| from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry | ||||
| from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile | ||||
| 
 | ||||
| class TestPOSClosingEntry(unittest.TestCase): | ||||
| 	def setUp(self): | ||||
| 		# Make stock available for POS Sales | ||||
| 		make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100) | ||||
| 
 | ||||
| 	def tearDown(self): | ||||
| 		frappe.set_user("Administrator") | ||||
| 		frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 
 | ||||
| 	def test_pos_closing_entry(self): | ||||
| 		test_user, pos_profile = init_user_and_profile() | ||||
| 		opening_entry = create_opening_entry(pos_profile, test_user.name) | ||||
| @ -41,9 +50,6 @@ class TestPOSClosingEntry(unittest.TestCase): | ||||
| 		self.assertEqual(pcv_doc.total_quantity, 2) | ||||
| 		self.assertEqual(pcv_doc.net_total, 6700) | ||||
| 
 | ||||
| 		frappe.set_user("Administrator") | ||||
| 		frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 
 | ||||
| 	def test_cancelling_of_pos_closing_entry(self): | ||||
| 		test_user, pos_profile = init_user_and_profile() | ||||
| 		opening_entry = create_opening_entry(pos_profile, test_user.name) | ||||
| @ -84,8 +90,6 @@ class TestPOSClosingEntry(unittest.TestCase): | ||||
| 		self.assertEqual(si_doc.docstatus, 2) | ||||
| 		self.assertEqual(pos_inv1.status, 'Paid') | ||||
| 
 | ||||
| 		frappe.set_user("Administrator") | ||||
| 		frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 
 | ||||
| def init_user_and_profile(**args): | ||||
| 	user = 'test@example.com' | ||||
| @ -103,4 +107,4 @@ def init_user_and_profile(**args): | ||||
| 
 | ||||
| 	pos_profile.save() | ||||
| 
 | ||||
| 	return test_user, pos_profile | ||||
| 	return test_user, pos_profile | ||||
|  | ||||
| @ -57,7 +57,7 @@ class POSInvoice(SalesInvoice): | ||||
| 			self.apply_loyalty_points() | ||||
| 		self.check_phone_payments() | ||||
| 		self.set_status(update=True) | ||||
| 	 | ||||
| 
 | ||||
| 	def before_cancel(self): | ||||
| 		if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1: | ||||
| 			pos_closing_entry = frappe.get_all( | ||||
| @ -221,7 +221,7 @@ class POSInvoice(SalesInvoice): | ||||
| 		base_grand_total = flt(self.base_rounded_total) or flt(self.base_grand_total) | ||||
| 		if not flt(self.change_amount) and grand_total < flt(self.paid_amount): | ||||
| 			self.change_amount = flt(self.paid_amount - grand_total + flt(self.write_off_amount)) | ||||
| 			self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount)) | ||||
| 			self.base_change_amount = flt(self.base_paid_amount) - base_grand_total + flt(self.base_write_off_amount) | ||||
| 
 | ||||
| 		if flt(self.change_amount) and not self.account_for_change_amount: | ||||
| 			frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1) | ||||
| @ -355,6 +355,7 @@ class POSInvoice(SalesInvoice): | ||||
| 
 | ||||
| 		return profile | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def set_missing_values(self, for_validate=False): | ||||
| 		profile = self.set_pos_fields(for_validate) | ||||
| 
 | ||||
| @ -377,12 +378,20 @@ class POSInvoice(SalesInvoice): | ||||
| 				"allow_print_before_pay": profile.get("allow_print_before_pay") | ||||
| 			} | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def reset_mode_of_payments(self): | ||||
| 		if self.pos_profile: | ||||
| 			pos_profile = frappe.get_cached_doc('POS Profile', self.pos_profile) | ||||
| 			update_multi_mode_option(self, pos_profile) | ||||
| 			self.paid_amount = 0 | ||||
| 
 | ||||
| 	def set_account_for_mode_of_payment(self): | ||||
| 		self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default] | ||||
| 		for pay in self.payments: | ||||
| 			if not pay.account: | ||||
| 				pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account") | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def create_payment_request(self): | ||||
| 		for pay in self.payments: | ||||
| 			if pay.type == "Phone": | ||||
| @ -400,7 +409,7 @@ class POSInvoice(SalesInvoice): | ||||
| 					pay_req.request_phone_payment() | ||||
| 
 | ||||
| 				return pay_req | ||||
| 	 | ||||
| 
 | ||||
| 	def get_new_payment_request(self, mop): | ||||
| 		payment_gateway_account = frappe.db.get_value("Payment Gateway Account", { | ||||
| 			"payment_account": mop.account, | ||||
|  | ||||
| @ -12,6 +12,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu | ||||
| from erpnext.stock.doctype.item.test_item import make_item | ||||
| 
 | ||||
| class TestPOSInvoice(unittest.TestCase): | ||||
| 	@classmethod | ||||
| 	def setUpClass(cls): | ||||
| 		frappe.db.sql("delete from `tabTax Rule`") | ||||
| 
 | ||||
| 	def tearDown(self): | ||||
| 		if frappe.session.user != "Administrator": | ||||
| 			frappe.set_user("Administrator") | ||||
|  | ||||
| @ -14,85 +14,89 @@ class TestPOSInvoiceMergeLog(unittest.TestCase): | ||||
| 	def test_consolidated_invoice_creation(self): | ||||
| 		frappe.db.sql("delete from `tabPOS Invoice`") | ||||
| 
 | ||||
| 		test_user, pos_profile = init_user_and_profile() | ||||
| 		try: | ||||
| 			test_user, pos_profile = init_user_and_profile() | ||||
| 
 | ||||
| 		pos_inv = create_pos_invoice(rate=300, do_not_submit=1) | ||||
| 		pos_inv.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 | ||||
| 		}) | ||||
| 		pos_inv.submit() | ||||
| 			pos_inv = create_pos_invoice(rate=300, do_not_submit=1) | ||||
| 			pos_inv.append('payments', { | ||||
| 				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 | ||||
| 			}) | ||||
| 			pos_inv.submit() | ||||
| 
 | ||||
| 		pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) | ||||
| 		pos_inv2.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 | ||||
| 		}) | ||||
| 		pos_inv2.submit() | ||||
| 			pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) | ||||
| 			pos_inv2.append('payments', { | ||||
| 				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 | ||||
| 			}) | ||||
| 			pos_inv2.submit() | ||||
| 
 | ||||
| 		pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) | ||||
| 		pos_inv3.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300 | ||||
| 		}) | ||||
| 		pos_inv3.submit() | ||||
| 			pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) | ||||
| 			pos_inv3.append('payments', { | ||||
| 				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300 | ||||
| 			}) | ||||
| 			pos_inv3.submit() | ||||
| 
 | ||||
| 		consolidate_pos_invoices() | ||||
| 			consolidate_pos_invoices() | ||||
| 
 | ||||
| 		pos_inv.load_from_db() | ||||
| 		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) | ||||
| 			pos_inv.load_from_db() | ||||
| 			self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) | ||||
| 
 | ||||
| 		pos_inv3.load_from_db() | ||||
| 		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) | ||||
| 			pos_inv3.load_from_db() | ||||
| 			self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) | ||||
| 
 | ||||
| 		self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice) | ||||
| 			self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice) | ||||
| 
 | ||||
| 		finally: | ||||
| 			frappe.set_user("Administrator") | ||||
| 			frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 			frappe.db.sql("delete from `tabPOS Invoice`") | ||||
| 
 | ||||
| 		frappe.set_user("Administrator") | ||||
| 		frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 		frappe.db.sql("delete from `tabPOS Invoice`") | ||||
| 	 | ||||
| 	def test_consolidated_credit_note_creation(self): | ||||
| 		frappe.db.sql("delete from `tabPOS Invoice`") | ||||
| 
 | ||||
| 		test_user, pos_profile = init_user_and_profile() | ||||
| 		try: | ||||
| 			test_user, pos_profile = init_user_and_profile() | ||||
| 
 | ||||
| 		pos_inv = create_pos_invoice(rate=300, do_not_submit=1) | ||||
| 		pos_inv.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 | ||||
| 		}) | ||||
| 		pos_inv.submit() | ||||
| 			pos_inv = create_pos_invoice(rate=300, do_not_submit=1) | ||||
| 			pos_inv.append('payments', { | ||||
| 				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 | ||||
| 			}) | ||||
| 			pos_inv.submit() | ||||
| 
 | ||||
| 		pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) | ||||
| 		pos_inv2.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 | ||||
| 		}) | ||||
| 		pos_inv2.submit() | ||||
| 			pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) | ||||
| 			pos_inv2.append('payments', { | ||||
| 				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 | ||||
| 			}) | ||||
| 			pos_inv2.submit() | ||||
| 
 | ||||
| 		pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) | ||||
| 		pos_inv3.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300 | ||||
| 		}) | ||||
| 		pos_inv3.submit() | ||||
| 			pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) | ||||
| 			pos_inv3.append('payments', { | ||||
| 				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300 | ||||
| 			}) | ||||
| 			pos_inv3.submit() | ||||
| 
 | ||||
| 		pos_inv_cn = make_sales_return(pos_inv.name) | ||||
| 		pos_inv_cn.set("payments", []) | ||||
| 		pos_inv_cn.append('payments', { | ||||
| 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300 | ||||
| 		}) | ||||
| 		pos_inv_cn.paid_amount = -300 | ||||
| 		pos_inv_cn.submit() | ||||
| 			pos_inv_cn = make_sales_return(pos_inv.name) | ||||
| 			pos_inv_cn.set("payments", []) | ||||
| 			pos_inv_cn.append('payments', { | ||||
| 				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300 | ||||
| 			}) | ||||
| 			pos_inv_cn.paid_amount = -300 | ||||
| 			pos_inv_cn.submit() | ||||
| 
 | ||||
| 		consolidate_pos_invoices() | ||||
| 			consolidate_pos_invoices() | ||||
| 
 | ||||
| 		pos_inv.load_from_db() | ||||
| 		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) | ||||
| 			pos_inv.load_from_db() | ||||
| 			self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) | ||||
| 
 | ||||
| 		pos_inv3.load_from_db() | ||||
| 		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) | ||||
| 			pos_inv3.load_from_db() | ||||
| 			self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) | ||||
| 
 | ||||
| 		pos_inv_cn.load_from_db() | ||||
| 		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice)) | ||||
| 		self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return")) | ||||
| 			pos_inv_cn.load_from_db() | ||||
| 			self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice)) | ||||
| 			self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return")) | ||||
| 
 | ||||
| 		frappe.set_user("Administrator") | ||||
| 		frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 		frappe.db.sql("delete from `tabPOS Invoice`") | ||||
| 		finally: | ||||
| 			frappe.set_user("Administrator") | ||||
| 			frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 			frappe.db.sql("delete from `tabPOS Invoice`") | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -44,6 +44,14 @@ | ||||
|   "column_break_21", | ||||
|   "min_amt", | ||||
|   "max_amt", | ||||
|   "product_discount_scheme_section", | ||||
|   "same_item", | ||||
|   "free_item", | ||||
|   "free_qty", | ||||
|   "free_item_rate", | ||||
|   "column_break_42", | ||||
|   "free_item_uom", | ||||
|   "is_recursive", | ||||
|   "section_break_23", | ||||
|   "valid_from", | ||||
|   "valid_upto", | ||||
| @ -62,13 +70,6 @@ | ||||
|   "discount_amount", | ||||
|   "discount_percentage", | ||||
|   "for_price_list", | ||||
|   "product_discount_scheme_section", | ||||
|   "same_item", | ||||
|   "free_item", | ||||
|   "free_qty", | ||||
|   "column_break_51", | ||||
|   "free_item_uom", | ||||
|   "free_item_rate", | ||||
|   "section_break_13", | ||||
|   "threshold_percentage", | ||||
|   "priority", | ||||
| @ -458,10 +459,6 @@ | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Qty" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_51", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "free_item_uom", | ||||
|    "fieldtype": "Link", | ||||
| @ -552,19 +549,33 @@ | ||||
|    "fieldname": "promotional_scheme", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Promotional Scheme", | ||||
|    "options": "Promotional Scheme" | ||||
|    "no_copy": 1, | ||||
|    "options": "Promotional Scheme", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "description": "Simple Python Expression, Example: territory != 'All Territories'", | ||||
|    "fieldname": "condition", | ||||
|    "fieldtype": "Code", | ||||
|    "label": "Condition" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_42", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on", | ||||
|    "fieldname": "is_recursive", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Is Recursive" | ||||
|   } | ||||
|  ], | ||||
|  "icon": "fa fa-gift", | ||||
|  "idx": 1, | ||||
|  "links": [], | ||||
|  "modified": "2021-03-01 23:18:38.717613", | ||||
|  "modified": "2021-03-06 22:01:24.840422", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Pricing Rule", | ||||
|  | ||||
| @ -237,6 +237,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa | ||||
| 		"doctype": args.doctype, | ||||
| 		"has_margin": False, | ||||
| 		"name": args.name, | ||||
| 		"free_item_data": [], | ||||
| 		"parent": args.parent, | ||||
| 		"parenttype": args.parenttype, | ||||
| 		"child_docname": args.get('child_docname') | ||||
|  | ||||
| @ -367,7 +367,7 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args): | ||||
| 
 | ||||
| 	if items and doc.get("items"): | ||||
| 		for row in doc.get('items'): | ||||
| 			if row.get(apply_on) not in items: continue | ||||
| 			if (row.get(apply_on) or args.get(apply_on)) not in items: continue | ||||
| 
 | ||||
| 			if pr_doc.mixed_conditions: | ||||
| 				amt = args.get('qty') * args.get("price_list_rate") | ||||
| @ -479,7 +479,7 @@ def apply_pricing_rule_on_transaction(doc): | ||||
| 
 | ||||
| 				doc.calculate_taxes_and_totals() | ||||
| 			elif d.price_or_product_discount == 'Product': | ||||
| 				item_details = frappe._dict({'parenttype': doc.doctype}) | ||||
| 				item_details = frappe._dict({'parenttype': doc.doctype, 'free_item_data': []}) | ||||
| 				get_product_discount_rule(d, item_details, doc=doc) | ||||
| 				apply_pricing_rule_for_free_items(doc, item_details.free_item_data) | ||||
| 				doc.set_missing_values() | ||||
| @ -508,9 +508,16 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): | ||||
| 		frappe.throw(_("Free item not set in the pricing rule {0}") | ||||
| 			.format(get_link_to_form("Pricing Rule", pricing_rule.name))) | ||||
| 
 | ||||
| 	item_details.free_item_data = { | ||||
| 	qty = pricing_rule.free_qty or 1 | ||||
| 	if pricing_rule.is_recursive: | ||||
| 		transaction_qty = args.get('qty') if args else doc.total_qty | ||||
| 		if transaction_qty: | ||||
| 			qty = flt(transaction_qty) * qty | ||||
| 
 | ||||
| 	free_item_data_args = { | ||||
| 		'item_code': free_item, | ||||
| 		'qty': pricing_rule.free_qty or 1, | ||||
| 		'qty': qty, | ||||
| 		'pricing_rules': pricing_rule.name, | ||||
| 		'rate': pricing_rule.free_item_rate or 0, | ||||
| 		'price_list_rate': pricing_rule.free_item_rate or 0, | ||||
| 		'is_free_item': 1 | ||||
| @ -519,24 +526,26 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): | ||||
| 	item_data = frappe.get_cached_value('Item', free_item, ['item_name', | ||||
| 		'description', 'stock_uom'], as_dict=1) | ||||
| 
 | ||||
| 	item_details.free_item_data.update(item_data) | ||||
| 	item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom | ||||
| 	item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item, | ||||
| 		item_details.free_item_data['uom']).get("conversion_factor", 1) | ||||
| 	free_item_data_args.update(item_data) | ||||
| 	free_item_data_args['uom'] = pricing_rule.free_item_uom or item_data.stock_uom | ||||
| 	free_item_data_args['conversion_factor'] = get_conversion_factor(free_item, | ||||
| 		free_item_data_args['uom']).get("conversion_factor", 1) | ||||
| 
 | ||||
| 	if item_details.get("parenttype") == 'Purchase Order': | ||||
| 		item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today() | ||||
| 		free_item_data_args['schedule_date'] = doc.schedule_date if doc else today() | ||||
| 
 | ||||
| 	if item_details.get("parenttype") == 'Sales Order': | ||||
| 		item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today() | ||||
| 		free_item_data_args['delivery_date'] = doc.delivery_date if doc else today() | ||||
| 
 | ||||
| 	item_details.free_item_data.append(free_item_data_args) | ||||
| 
 | ||||
| def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): | ||||
| 	if pricing_rule_args.get('item_code'): | ||||
| 		items = [d.item_code for d in doc.items | ||||
| 			if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item] | ||||
| 	if pricing_rule_args: | ||||
| 		items = tuple([(d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item]) | ||||
| 
 | ||||
| 		if not items: | ||||
| 			doc.append('items', pricing_rule_args) | ||||
| 		for args in pricing_rule_args: | ||||
| 			if not items or (args.get('item_code'), args.get('pricing_rules')) not in items: | ||||
| 				doc.append('items', args) | ||||
| 
 | ||||
| def get_pricing_rule_items(pr_doc): | ||||
| 	apply_on_data = [] | ||||
|  | ||||
| @ -12,16 +12,16 @@ from frappe.model.document import Document | ||||
| pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group' | ||||
| 	'apply_rule_on_other', 'other_brand', 'selling', 'buying', 'applicable_for', 'valid_from', | ||||
| 	'valid_upto', 'customer', 'customer_group', 'territory', 'sales_partner', 'campaign', 'supplier', | ||||
| 	'supplier_group', 'company', 'currency'] | ||||
| 	'supplier_group', 'company', 'currency', 'apply_multiple_pricing_rules'] | ||||
| 
 | ||||
| other_fields = ['min_qty', 'max_qty', 'min_amt', | ||||
| 	'max_amt', 'priority','warehouse', 'threshold_percentage', 'rule_description'] | ||||
| 
 | ||||
| price_discount_fields = ['rate_or_discount', 'apply_discount_on', 'apply_discount_on_rate', | ||||
| 	'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule'] | ||||
| 	'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule', 'apply_multiple_pricing_rules'] | ||||
| 
 | ||||
| product_discount_fields = ['free_item', 'free_qty', 'free_item_uom', | ||||
| 	'free_item_rate', 'same_item'] | ||||
| 	'free_item_rate', 'same_item', 'is_recursive', 'apply_multiple_pricing_rules'] | ||||
| 
 | ||||
| class PromotionalScheme(Document): | ||||
| 	def validate(self): | ||||
|  | ||||
| @ -1,792 +1,181 @@ | ||||
| { | ||||
|  "allow_copy": 0, | ||||
|  "allow_events_in_timeline": 0, | ||||
|  "allow_guest_to_view": 0, | ||||
|  "allow_import": 0, | ||||
|  "allow_rename": 0, | ||||
|  "beta": 0, | ||||
|  "actions": [], | ||||
|  "creation": "2019-03-24 14:48:59.649168", | ||||
|  "custom": 0, | ||||
|  "docstatus": 0, | ||||
|  "doctype": "DocType", | ||||
|  "document_type": "", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "disable", | ||||
|   "apply_multiple_pricing_rules", | ||||
|   "column_break_2", | ||||
|   "rule_description", | ||||
|   "section_break_2", | ||||
|   "min_qty", | ||||
|   "max_qty", | ||||
|   "column_break_3", | ||||
|   "min_amount", | ||||
|   "max_amount", | ||||
|   "section_break_6", | ||||
|   "rate_or_discount", | ||||
|   "column_break_10", | ||||
|   "rate", | ||||
|   "discount_amount", | ||||
|   "discount_percentage", | ||||
|   "section_break_11", | ||||
|   "warehouse", | ||||
|   "threshold_percentage", | ||||
|   "validate_applied_rule", | ||||
|   "column_break_14", | ||||
|   "priority", | ||||
|   "apply_discount_on_rate" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "default": "0", | ||||
|    "fieldname": "disable", | ||||
|    "fieldtype": "Check", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 0, | ||||
|    "in_standard_filter": 0, | ||||
|    "label": "Disable", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "label": "Disable" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "fieldname": "column_break_2", | ||||
|    "fieldtype": "Column Break", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 0, | ||||
|    "in_standard_filter": 0, | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "fieldname": "rule_description", | ||||
|    "fieldtype": "Small Text", | ||||
|    "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": "Rule Description", | ||||
|    "length": 0, | ||||
|    "no_copy": 1, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 1, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "fieldname": "section_break_2", | ||||
|    "fieldtype": "Section Break", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 0, | ||||
|    "in_standard_filter": 0, | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 1, | ||||
|    "default": "0", | ||||
|    "fieldname": "min_qty", | ||||
|    "fieldtype": "Float", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 1, | ||||
|    "in_standard_filter": 0, | ||||
|    "label": "Min Qty", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "label": "Min Qty" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 1, | ||||
|    "default": "0", | ||||
|    "fieldname": "max_qty", | ||||
|    "fieldtype": "Float", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 1, | ||||
|    "in_standard_filter": 0, | ||||
|    "label": "Max Qty", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "label": "Max Qty" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "fieldname": "column_break_3", | ||||
|    "fieldtype": "Column Break", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 0, | ||||
|    "in_standard_filter": 0, | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "default": "0", | ||||
|    "fieldname": "min_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 1, | ||||
|    "in_standard_filter": 0, | ||||
|    "label": "Min Amount", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "label": "Min Amount" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "default": "0", | ||||
|    "depends_on": "", | ||||
|    "fieldname": "max_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 1, | ||||
|    "in_standard_filter": 0, | ||||
|    "label": "Max Amount", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "label": "Max Amount" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "depends_on": "", | ||||
|    "fieldname": "section_break_6", | ||||
|    "fieldtype": "Section Break", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 0, | ||||
|    "in_standard_filter": 0, | ||||
|    "label": "", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "default": "Discount Percentage", | ||||
|    "depends_on": "", | ||||
|    "fieldname": "rate_or_discount", | ||||
|    "fieldtype": "Select", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 1, | ||||
|    "in_standard_filter": 0, | ||||
|    "label": "Discount Type", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "options": "\nRate\nDiscount Percentage\nDiscount Amount", | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "options": "\nRate\nDiscount Percentage\nDiscount Amount" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "depends_on": "", | ||||
|    "fieldname": "column_break_10", | ||||
|    "fieldtype": "Column Break", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 0, | ||||
|    "in_standard_filter": 0, | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 2, | ||||
|    "depends_on": "eval:doc.rate_or_discount==\"Rate\"", | ||||
|    "fieldname": "rate", | ||||
|    "fieldtype": "Currency", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 1, | ||||
|    "in_standard_filter": 0, | ||||
|    "label": "Rate", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "label": "Rate" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "depends_on": "eval:doc.rate_or_discount==\"Discount Amount\"", | ||||
|    "fieldname": "discount_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "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": "Discount Amount", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "label": "Discount Amount" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "depends_on": "eval:doc.rate_or_discount==\"Discount Percentage\"", | ||||
|    "fieldname": "discount_percentage", | ||||
|    "fieldtype": "Float", | ||||
|    "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": "Discount Percentage", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "label": "Discount Percentage" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "fieldname": "section_break_11", | ||||
|    "fieldtype": "Section Break", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 0, | ||||
|    "in_standard_filter": 0, | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "fieldname": "warehouse", | ||||
|    "fieldtype": "Link", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 0, | ||||
|    "in_standard_filter": 0, | ||||
|    "label": "Warehouse", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "options": "Warehouse", | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "options": "Warehouse" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "fieldname": "threshold_percentage", | ||||
|    "fieldtype": "Percent", | ||||
|    "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": "Threshold for Suggestion", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "label": "Threshold for Suggestion" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "default": "1", | ||||
|    "fieldname": "validate_applied_rule", | ||||
|    "fieldtype": "Check", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 0, | ||||
|    "in_standard_filter": 0, | ||||
|    "label": "Validate Applied Rule", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "label": "Validate Applied Rule" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "fieldname": "column_break_14", | ||||
|    "fieldtype": "Column Break", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 0, | ||||
|    "in_standard_filter": 0, | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "fieldname": "priority", | ||||
|    "fieldtype": "Select", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 0, | ||||
|    "in_standard_filter": 0, | ||||
|    "label": "Priority", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20", | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "default": "0", | ||||
|    "depends_on": "priority", | ||||
|    "fieldname": "apply_multiple_pricing_rules", | ||||
|    "fieldtype": "Check", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 0, | ||||
|    "in_standard_filter": 0, | ||||
|    "label": "Apply Multiple Pricing Rules", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "label": "Apply Multiple Pricing Rules" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0, | ||||
|    "allow_in_quick_entry": 0, | ||||
|    "allow_on_submit": 0, | ||||
|    "bold": 0, | ||||
|    "collapsible": 0, | ||||
|    "columns": 0, | ||||
|    "default": "0", | ||||
|    "depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules", | ||||
|    "fieldname": "apply_discount_on_rate", | ||||
|    "fieldtype": "Check", | ||||
|    "hidden": 0, | ||||
|    "ignore_user_permissions": 0, | ||||
|    "ignore_xss_filter": 0, | ||||
|    "in_filter": 0, | ||||
|    "in_global_search": 0, | ||||
|    "in_list_view": 0, | ||||
|    "in_standard_filter": 0, | ||||
|    "label": "Apply Discount on Rate", | ||||
|    "length": 0, | ||||
|    "no_copy": 0, | ||||
|    "permlevel": 0, | ||||
|    "precision": "", | ||||
|    "print_hide": 0, | ||||
|    "print_hide_if_no_value": 0, | ||||
|    "read_only": 0, | ||||
|    "remember_last_selected_value": 0, | ||||
|    "report_hide": 0, | ||||
|    "reqd": 0, | ||||
|    "search_index": 0, | ||||
|    "set_only_once": 0, | ||||
|    "translatable": 0, | ||||
|    "unique": 0 | ||||
|    "label": "Apply Discount on Rate" | ||||
|   } | ||||
|  ], | ||||
|  "has_web_view": 0, | ||||
|  "hide_heading": 0, | ||||
|  "hide_toolbar": 0, | ||||
|  "idx": 0, | ||||
|  "image_view": 0, | ||||
|  "in_create": 0, | ||||
|  "is_submittable": 0, | ||||
|  "issingle": 0, | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "istable": 1, | ||||
|  "max_attachments": 0, | ||||
|  "modified": "2019-03-24 14:48:59.649168", | ||||
|  "links": [], | ||||
|  "modified": "2021-03-07 11:56:23.424137", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Promotional Scheme Price Discount", | ||||
|  "name_case": "", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 0, | ||||
|  "read_only": 0, | ||||
|  "read_only_onload": 0, | ||||
|  "show_name_in_global_search": 0, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 0, | ||||
|  "track_seen": 0, | ||||
|  "track_views": 0 | ||||
|  "sort_order": "DESC" | ||||
| } | ||||
| @ -1,10 +1,12 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "creation": "2019-03-24 14:48:59.649168", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "disable", | ||||
|   "apply_multiple_pricing_rules", | ||||
|   "column_break_2", | ||||
|   "rule_description", | ||||
|   "section_break_1", | ||||
| @ -25,7 +27,7 @@ | ||||
|   "threshold_percentage", | ||||
|   "column_break_15", | ||||
|   "priority", | ||||
|   "apply_multiple_pricing_rules" | ||||
|   "is_recursive" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
| @ -152,10 +154,19 @@ | ||||
|    "fieldname": "apply_multiple_pricing_rules", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Apply Multiple Pricing Rules" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on", | ||||
|    "fieldname": "is_recursive", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Is Recursive" | ||||
|   } | ||||
|  ], | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "istable": 1, | ||||
|  "modified": "2019-07-21 00:00:56.674284", | ||||
|  "links": [], | ||||
|  "modified": "2021-03-06 21:58:18.162346", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Promotional Scheme Product Discount", | ||||
|  | ||||
| @ -898,7 +898,7 @@ class TestPurchaseInvoice(unittest.TestCase): | ||||
| 		acc_settings.submit_journal_entries = 1 | ||||
| 		acc_settings.save() | ||||
| 
 | ||||
| 		item = create_item("_Test Item for Deferred Accounting") | ||||
| 		item = create_item("_Test Item for Deferred Accounting", is_purchase_item=True) | ||||
| 		item.enable_deferred_expense = 1 | ||||
| 		item.deferred_expense_account = deferred_account | ||||
| 		item.save() | ||||
|  | ||||
| @ -43,7 +43,7 @@ | ||||
|    } | ||||
|   ], | ||||
|   "grand_total": 0, | ||||
|   "naming_series": "_T-BILL", | ||||
|   "naming_series": "T-PINV-", | ||||
|   "taxes": [ | ||||
|    { | ||||
|     "account_head": "_Test Account Shipping Charges - _TC", | ||||
| @ -167,7 +167,7 @@ | ||||
|    } | ||||
|   ], | ||||
|   "grand_total": 0, | ||||
|   "naming_series": "_T-Purchase Invoice-", | ||||
|   "naming_series": "T-PINV-", | ||||
|   "taxes": [ | ||||
|    { | ||||
|     "account_head": "_Test Account Shipping Charges - _TC", | ||||
|  | ||||
| @ -1952,13 +1952,12 @@ | ||||
|  "is_submittable": 1, | ||||
|  "links": [ | ||||
|   { | ||||
|    "custom": 1, | ||||
|    "group": "Reference", | ||||
|    "link_doctype": "POS Invoice", | ||||
|    "link_fieldname": "consolidated_invoice" | ||||
|   } | ||||
|  ], | ||||
|  "modified": "2021-02-01 15:42:26.261540", | ||||
|  "modified": "2021-03-31 15:42:26.261540", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Sales Invoice", | ||||
|  | ||||
| @ -390,6 +390,7 @@ class SalesInvoice(SellingController): | ||||
| 		if validate_against_credit_limit: | ||||
| 			check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def set_missing_values(self, for_validate=False): | ||||
| 		pos = self.set_pos_fields(for_validate) | ||||
| 
 | ||||
| @ -729,6 +730,7 @@ class SalesInvoice(SellingController): | ||||
| 		else: | ||||
| 			self.calculate_billing_amount_for_timesheet() | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def add_timesheet_data(self): | ||||
| 		self.set('timesheets', []) | ||||
| 		if self.project: | ||||
| @ -1286,6 +1288,7 @@ class SalesInvoice(SellingController): | ||||
| 				break | ||||
| 
 | ||||
| 	# Healthcare | ||||
| 	@frappe.whitelist() | ||||
| 	def set_healthcare_services(self, checked_values): | ||||
| 		self.set("items", []) | ||||
| 		from erpnext.stock.get_item_details import get_item_details | ||||
|  | ||||
| @ -31,7 +31,7 @@ | ||||
|   "base_grand_total": 561.8, | ||||
|   "grand_total": 561.8, | ||||
|   "is_pos": 0, | ||||
|   "naming_series": "_T-Sales Invoice-", | ||||
|   "naming_series": "T-SINV-", | ||||
|   "base_net_total": 500.0, | ||||
|   "taxes": [ | ||||
|    { | ||||
| @ -104,7 +104,7 @@ | ||||
|   "base_grand_total": 630.0, | ||||
|   "grand_total": 630.0, | ||||
|   "is_pos": 0, | ||||
|   "naming_series": "_T-Sales Invoice-", | ||||
|   "naming_series": "T-SINV-", | ||||
|   "base_net_total": 500.0, | ||||
|   "taxes": [ | ||||
|    { | ||||
| @ -175,7 +175,7 @@ | ||||
|   ], | ||||
|   "grand_total": 0, | ||||
|   "is_pos": 0, | ||||
|   "naming_series": "_T-Sales Invoice-", | ||||
|   "naming_series": "T-SINV-", | ||||
|   "taxes": [ | ||||
|    { | ||||
|     "account_head": "_Test Account Shipping Charges - _TC", | ||||
| @ -301,7 +301,7 @@ | ||||
|   ], | ||||
|   "grand_total": 0, | ||||
|   "is_pos": 0, | ||||
|   "naming_series": "_T-Sales Invoice-", | ||||
|   "naming_series": "T-SINV-", | ||||
|   "taxes": [ | ||||
|    { | ||||
|     "account_head": "_Test Account Excise Duty - _TC", | ||||
|  | ||||
| @ -2115,6 +2115,7 @@ def create_sales_invoice(**args): | ||||
| 	si.return_against = args.return_against | ||||
| 	si.currency=args.currency or "INR" | ||||
| 	si.conversion_rate = args.conversion_rate or 1 | ||||
| 	si.naming_series = args.naming_series or "T-SINV-" | ||||
| 
 | ||||
| 	si.append("items", { | ||||
| 		"item_code": args.item or args.item_code or "_Test Item", | ||||
|  | ||||
| @ -14,10 +14,15 @@ test_records = frappe.get_test_records('Tax Rule') | ||||
| from six import iteritems | ||||
| 
 | ||||
| class TestTaxRule(unittest.TestCase): | ||||
| 	def setUp(self): | ||||
| 	@classmethod | ||||
| 	def setUpClass(cls): | ||||
| 		frappe.db.set_value("Shopping Cart Settings", None, "enabled", 0) | ||||
| 
 | ||||
| 	@classmethod | ||||
| 	def tearDownClass(cls): | ||||
| 		frappe.db.sql("delete from `tabTax Rule`") | ||||
| 
 | ||||
| 	def tearDown(self): | ||||
| 	def setUp(self): | ||||
| 		frappe.db.sql("delete from `tabTax Rule`") | ||||
| 
 | ||||
| 	def test_conflict(self): | ||||
|  | ||||
| @ -364,7 +364,7 @@ class ReceivablePayableReport(object): | ||||
| 		payment_terms_details = frappe.db.sql(""" | ||||
| 			select | ||||
| 				si.name, si.party_account_currency, si.currency, si.conversion_rate, | ||||
| 				ps.due_date, ps.payment_amount, ps.description, ps.paid_amount | ||||
| 				ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount | ||||
| 			from `tab{0}` si, `tabPayment Schedule` ps | ||||
| 			where | ||||
| 				si.name = ps.parent and | ||||
| @ -395,13 +395,13 @@ class ReceivablePayableReport(object): | ||||
| 			"invoiced": invoiced, | ||||
| 			"invoice_grand_total": row.invoiced, | ||||
| 			"payment_term": d.description, | ||||
| 			"paid": d.paid_amount, | ||||
| 			"paid": d.paid_amount + d.discounted_amount, | ||||
| 			"credit_note": 0.0, | ||||
| 			"outstanding": invoiced - d.paid_amount | ||||
| 			"outstanding": invoiced - d.paid_amount - d.discounted_amount | ||||
| 		})) | ||||
| 
 | ||||
| 		if d.paid_amount: | ||||
| 			row['paid'] -= d.paid_amount | ||||
| 			row['paid'] -= d.paid_amount + d.discounted_amount | ||||
| 
 | ||||
| 	def allocate_closing_to_term(self, row, term, key): | ||||
| 		if row[key]: | ||||
|  | ||||
| @ -51,7 +51,11 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_ | ||||
| 			"from_date": start_date | ||||
| 		}) | ||||
| 
 | ||||
| 		to_date = add_months(start_date, months_to_add) | ||||
| 		if i==0 and filter_based_on == 'Date Range': | ||||
| 			to_date = add_months(get_first_day(start_date), months_to_add) | ||||
| 		else: | ||||
| 			to_date = add_months(start_date, months_to_add) | ||||
| 
 | ||||
| 		start_date = to_date | ||||
| 
 | ||||
| 		# Subtract one day from to_date, as it may be first day in next fiscal year or month | ||||
|  | ||||
| @ -71,6 +71,7 @@ class CropCycle(Document): | ||||
| 				"exp_end_date": add_days(start_date, crop_task.get("end_day") - 1) | ||||
| 			}).insert() | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def reload_linked_analysis(self): | ||||
| 		linked_doctypes = ['Soil Texture', 'Soil Analysis', 'Plant Analysis'] | ||||
| 		required_fields = ['location', 'name', 'collection_datetime'] | ||||
| @ -87,6 +88,7 @@ class CropCycle(Document): | ||||
| 		frappe.publish_realtime("List of Linked Docs", | ||||
| 								output, user=frappe.session.user) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def append_to_child(self, obj_to_append): | ||||
| 		for doctype in obj_to_append: | ||||
| 			for doc_name in set(obj_to_append[doctype]): | ||||
|  | ||||
| @ -7,6 +7,7 @@ import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class Fertilizer(Document): | ||||
| 	@frappe.whitelist() | ||||
| 	def load_contents(self): | ||||
| 		docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Fertilizer'}) | ||||
| 		for doc in docs: | ||||
|  | ||||
| @ -8,6 +8,7 @@ from frappe.model.naming import make_autoname | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class PlantAnalysis(Document): | ||||
| 	@frappe.whitelist() | ||||
| 	def load_contents(self): | ||||
| 		docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Plant Analysis'}) | ||||
| 		for doc in docs: | ||||
|  | ||||
| @ -7,6 +7,7 @@ import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class SoilAnalysis(Document): | ||||
| 	@frappe.whitelist() | ||||
| 	def load_contents(self): | ||||
| 		docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Analysis'}) | ||||
| 		for doc in docs: | ||||
|  | ||||
| @ -13,6 +13,7 @@ class SoilTexture(Document): | ||||
| 	soil_edit_order = [2, 1, 0] | ||||
| 	soil_types = ['clay_composition', 'sand_composition', 'silt_composition'] | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def load_contents(self): | ||||
| 		docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Texture'}) | ||||
| 		for doc in docs: | ||||
| @ -26,6 +27,7 @@ class SoilTexture(Document): | ||||
| 		if sum(self.get(soil_type) for soil_type in self.soil_types) != 100: | ||||
| 			frappe.throw(_('Soil compositions do not add up to 100')) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def update_soil_edit(self, soil_type): | ||||
| 		self.soil_edit_order[self.soil_types.index(soil_type)] = max(self.soil_edit_order)+1 | ||||
| 		self.soil_type = self.get_soil_type() | ||||
| @ -35,8 +37,8 @@ class SoilTexture(Document): | ||||
| 		if sum(self.soil_edit_order) < 5: return | ||||
| 		last_edit_index = self.soil_edit_order.index(min(self.soil_edit_order)) | ||||
| 
 | ||||
| 		# set composition of the last edited soil  | ||||
| 		self.set( self.soil_types[last_edit_index],  | ||||
| 		# set composition of the last edited soil | ||||
| 		self.set(self.soil_types[last_edit_index], | ||||
| 			100 - sum(cint(self.get(soil_type)) for soil_type in self.soil_types) + cint(self.get(self.soil_types[last_edit_index]))) | ||||
| 
 | ||||
| 		# calculate soil type | ||||
| @ -67,4 +69,4 @@ class SoilTexture(Document): | ||||
| 		elif (c >= 40 and sa <= 45 and si < 40): | ||||
| 			return 'Clay' | ||||
| 		else: | ||||
| 			return 'Select' | ||||
| 			return 'Select' | ||||
|  | ||||
| @ -9,11 +9,13 @@ from frappe.model.document import Document | ||||
| from frappe import _ | ||||
| 
 | ||||
| class WaterAnalysis(Document): | ||||
| 	@frappe.whitelist() | ||||
| 	def load_contents(self): | ||||
| 		docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Water Analysis'}) | ||||
| 		for doc in docs: | ||||
| 			self.append('water_analysis_criteria', {'title': str(doc.name)}) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def update_lab_result_date(self): | ||||
| 		if not self.result_datetime: | ||||
| 			self.result_datetime = self.laboratory_testing_datetime | ||||
|  | ||||
| @ -7,6 +7,7 @@ import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class Weather(Document): | ||||
| 	@frappe.whitelist() | ||||
| 	def load_contents(self): | ||||
| 		docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Weather'}) | ||||
| 		for doc in docs: | ||||
|  | ||||
| @ -553,6 +553,7 @@ class Asset(AccountsController): | ||||
| 			make_gl_entries(gl_entries) | ||||
| 			self.db_set('booked_fixed_asset', 1) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_depreciation_rate(self, args, on_validate=False): | ||||
| 		if isinstance(args, string_types): | ||||
| 			args = json.loads(args) | ||||
|  | ||||
| @ -133,6 +133,7 @@ class PurchaseOrder(BuyingController): | ||||
| 						d.material_request_item, "schedule_date") | ||||
| 
 | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_last_purchase_rate(self): | ||||
| 		"""get last purchase rates for all items""" | ||||
| 
 | ||||
| @ -252,6 +253,7 @@ class PurchaseOrder(BuyingController): | ||||
| 		self.update_prevdoc_status() | ||||
| 
 | ||||
| 		# Must be called after updating ordered qty in Material Request | ||||
| 		# bin uses Material Request Items to recalculate & update | ||||
| 		self.update_requested_qty() | ||||
| 		self.update_ordered_qty() | ||||
| 
 | ||||
| @ -366,7 +368,6 @@ def make_purchase_receipt(source_name, target_doc=None): | ||||
| 		"Purchase Order": { | ||||
| 			"doctype": "Purchase Receipt", | ||||
| 			"field_map": { | ||||
| 				"per_billed": "per_billed", | ||||
| 				"supplier_warehouse":"supplier_warehouse" | ||||
| 			}, | ||||
| 			"validation": { | ||||
|  | ||||
| @ -90,6 +90,50 @@ class TestPurchaseOrder(unittest.TestCase): | ||||
| 		frappe.db.set_value('Item', '_Test Item', 'over_billing_allowance', 0) | ||||
| 		frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0) | ||||
| 
 | ||||
| 	def test_update_remove_child_linked_to_mr(self): | ||||
| 		"""Test impact on linked PO and MR on deleting/updating row.""" | ||||
| 		mr = make_material_request(qty=10) | ||||
| 		po = make_purchase_order(mr.name) | ||||
| 		po.supplier = "_Test Supplier" | ||||
| 		po.save() | ||||
| 		po.submit() | ||||
| 
 | ||||
| 		first_item_of_po = po.get("items")[0] | ||||
| 		existing_ordered_qty = get_ordered_qty() # 10 | ||||
| 		existing_requested_qty = get_requested_qty() # 0 | ||||
| 
 | ||||
| 		# decrease ordered qty by 3 (10 -> 7) and add item | ||||
| 		trans_item = json.dumps([ | ||||
| 			{ | ||||
| 				'item_code': first_item_of_po.item_code, | ||||
| 				'rate': first_item_of_po.rate, | ||||
| 				'qty': 7, | ||||
| 				'docname': first_item_of_po.name | ||||
| 			}, | ||||
| 			{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2} | ||||
| 		]) | ||||
| 		update_child_qty_rate('Purchase Order', trans_item, po.name) | ||||
| 		mr.reload() | ||||
| 
 | ||||
| 		# requested qty increases as ordered qty decreases | ||||
| 		self.assertEqual(get_requested_qty(), existing_requested_qty + 3) # 3 | ||||
| 		self.assertEqual(mr.items[0].ordered_qty, 7) | ||||
| 
 | ||||
| 		self.assertEqual(get_ordered_qty(), existing_ordered_qty - 3) # 7 | ||||
| 
 | ||||
| 		# delete first item linked to Material Request | ||||
| 		trans_item = json.dumps([ | ||||
| 			{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2} | ||||
| 		]) | ||||
| 		update_child_qty_rate('Purchase Order', trans_item, po.name) | ||||
| 		mr.reload() | ||||
| 
 | ||||
| 		# requested qty increases as ordered qty is 0 (deleted row) | ||||
| 		self.assertEqual(get_requested_qty(), existing_requested_qty + 10) # 10 | ||||
| 		self.assertEqual(mr.items[0].ordered_qty, 0) | ||||
| 
 | ||||
| 		# ordered qty decreases as ordered qty is 0 (deleted row) | ||||
| 		self.assertEqual(get_ordered_qty(), existing_ordered_qty - 10) # 0 | ||||
| 
 | ||||
| 	def test_update_child(self): | ||||
| 		mr = make_material_request(qty=10) | ||||
| @ -120,7 +164,6 @@ class TestPurchaseOrder(unittest.TestCase): | ||||
| 		self.assertEqual(po.get("items")[0].amount, 1400) | ||||
| 		self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3) | ||||
| 
 | ||||
| 
 | ||||
| 	def test_update_child_adding_new_item(self): | ||||
| 		po = create_purchase_order(do_not_save=1) | ||||
| 		po.items[0].qty = 4 | ||||
| @ -129,6 +172,7 @@ class TestPurchaseOrder(unittest.TestCase): | ||||
| 		pr = make_pr_against_po(po.name, 2) | ||||
| 
 | ||||
| 		po.load_from_db() | ||||
| 		existing_ordered_qty = get_ordered_qty() | ||||
| 		first_item_of_po = po.get("items")[0] | ||||
| 
 | ||||
| 		trans_item = json.dumps([ | ||||
| @ -145,7 +189,8 @@ class TestPurchaseOrder(unittest.TestCase): | ||||
| 		po.reload() | ||||
| 		self.assertEquals(len(po.get('items')), 2) | ||||
| 		self.assertEqual(po.status, 'To Receive and Bill') | ||||
| 
 | ||||
| 		# ordered qty should increase on row addition | ||||
| 		self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7) | ||||
| 
 | ||||
| 	def test_update_child_removing_item(self): | ||||
| 		po = create_purchase_order(do_not_save=1) | ||||
| @ -156,6 +201,7 @@ class TestPurchaseOrder(unittest.TestCase): | ||||
| 
 | ||||
| 		po.reload() | ||||
| 		first_item_of_po = po.get("items")[0] | ||||
| 		existing_ordered_qty = get_ordered_qty() | ||||
| 		# add an item | ||||
| 		trans_item = json.dumps([ | ||||
| 			{ | ||||
| @ -168,6 +214,10 @@ class TestPurchaseOrder(unittest.TestCase): | ||||
| 		update_child_qty_rate('Purchase Order', trans_item, po.name) | ||||
| 
 | ||||
| 		po.reload() | ||||
| 
 | ||||
| 		# ordered qty should increase on row addition | ||||
| 		self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7) | ||||
| 
 | ||||
| 		# check if can remove received item | ||||
| 		trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.get("items")[1].name}]) | ||||
| 		self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Purchase Order', trans_item, po.name) | ||||
| @ -187,6 +237,9 @@ class TestPurchaseOrder(unittest.TestCase): | ||||
| 		self.assertEquals(len(po.get('items')), 1) | ||||
| 		self.assertEqual(po.status, 'To Receive and Bill') | ||||
| 
 | ||||
| 		# ordered qty should decrease (back to initial) on row deletion | ||||
| 		self.assertEqual(get_ordered_qty(), existing_ordered_qty) | ||||
| 
 | ||||
| 	def test_update_child_perm(self): | ||||
| 		po = create_purchase_order(item_code= "_Test Item", qty=4) | ||||
| 
 | ||||
| @ -230,11 +283,13 @@ class TestPurchaseOrder(unittest.TestCase): | ||||
| 
 | ||||
| 		new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax") | ||||
| 
 | ||||
| 		new_item_with_tax.append("taxes", { | ||||
| 			"item_tax_template": "Test Update Items Template - _TC", | ||||
| 			"valid_from": nowdate() | ||||
| 		}) | ||||
| 		new_item_with_tax.save() | ||||
| 		if not frappe.db.exists("Item Tax", | ||||
| 			{"item_tax_template": "Test Update Items Template - _TC", "parent": "Test Item with Tax"}): | ||||
| 			new_item_with_tax.append("taxes", { | ||||
| 				"item_tax_template": "Test Update Items Template - _TC", | ||||
| 				"valid_from": nowdate() | ||||
| 			}) | ||||
| 			new_item_with_tax.save() | ||||
| 
 | ||||
| 		tax_template = "_Test Account Excise Duty @ 10 - _TC" | ||||
| 		item =  "_Test Item Home Desktop 100" | ||||
|  | ||||
| @ -66,6 +66,7 @@ class RequestforQuotation(BuyingController): | ||||
| 	def on_cancel(self): | ||||
| 		frappe.db.set(self, 'status', 'Cancelled') | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_supplier_email_preview(self, supplier): | ||||
| 		"""Returns formatted email preview as string.""" | ||||
| 		rfq_suppliers = list(filter(lambda row: row.supplier == supplier, self.suppliers)) | ||||
|  | ||||
| @ -9,9 +9,7 @@ import unittest | ||||
| class TestSupplierScorecard(unittest.TestCase): | ||||
| 
 | ||||
| 	def test_create_scorecard(self): | ||||
| 		delete_test_scorecards() | ||||
| 		my_doc = make_supplier_scorecard() | ||||
| 		doc = my_doc.insert() | ||||
| 		doc = make_supplier_scorecard().insert() | ||||
| 		self.assertEqual(doc.name, valid_scorecard[0].get("supplier")) | ||||
| 
 | ||||
| 	def test_criteria_weight(self): | ||||
| @ -121,7 +119,8 @@ valid_scorecard = [ | ||||
| 			{ | ||||
| 				"weight":100.0, | ||||
| 				"doctype":"Supplier Scorecard Scoring Criteria", | ||||
| 				"criteria_name":"Delivery" | ||||
| 				"criteria_name":"Delivery", | ||||
| 				"formula": "100" | ||||
| 			} | ||||
| 		], | ||||
| 		"supplier":"_Test Supplier", | ||||
|  | ||||
| @ -26,7 +26,8 @@ from erpnext.controllers.print_settings import set_print_templates_for_item_tabl | ||||
| 
 | ||||
| class AccountMissingError(frappe.ValidationError): pass | ||||
| 
 | ||||
| force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules") | ||||
| force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", | ||||
| 	"pricing_rules", "weight_per_unit", "weight_uom", "total_weight") | ||||
| 
 | ||||
| class AccountsController(TransactionBase): | ||||
| 	def __init__(self, *args, **kwargs): | ||||
| @ -516,6 +517,7 @@ class AccountsController(TransactionBase): | ||||
| 		frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s | ||||
| 			and allocated_amount = 0""" % (childtype, '%s', '%s'), (parentfield, self.name)) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def apply_shipping_rule(self): | ||||
| 		if self.shipping_rule: | ||||
| 			shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule) | ||||
| @ -536,6 +538,7 @@ class AccountsController(TransactionBase): | ||||
| 
 | ||||
| 		return {} | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def set_advances(self): | ||||
| 		"""Returns list of advances against Account, Party, Reference""" | ||||
| 
 | ||||
| @ -920,7 +923,8 @@ class AccountsController(TransactionBase): | ||||
| 		else: | ||||
| 			for d in self.get("payment_schedule"): | ||||
| 				if d.invoice_portion: | ||||
| 					d.payment_amount = flt(grand_total * flt(d.invoice_portion) / 100, d.precision('payment_amount')) | ||||
| 					d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) | ||||
| 					d.outstanding = d.payment_amount | ||||
| 
 | ||||
| 	def set_due_date(self): | ||||
| 		due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] | ||||
| @ -1235,18 +1239,24 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, bill_dat | ||||
| 	term_details.description = term.description | ||||
| 	term_details.invoice_portion = term.invoice_portion | ||||
| 	term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100 | ||||
| 	term_details.discount_type = term.discount_type | ||||
| 	term_details.discount = term.discount | ||||
| 	# term_details.discounted_amount = flt(grand_total) * (term.discount / 100) if term.discount_type == 'Percentage' else discount | ||||
| 	term_details.outstanding = term_details.payment_amount | ||||
| 	term_details.mode_of_payment = term.mode_of_payment | ||||
| 
 | ||||
| 	if bill_date: | ||||
| 		term_details.due_date = get_due_date(term, bill_date) | ||||
| 		term_details.discount_date = get_discount_date(term, bill_date) | ||||
| 	elif posting_date: | ||||
| 		term_details.due_date = get_due_date(term, posting_date) | ||||
| 		term_details.discount_date = get_discount_date(term, posting_date) | ||||
| 
 | ||||
| 	if getdate(term_details.due_date) < getdate(posting_date): | ||||
| 		term_details.due_date = posting_date | ||||
| 	term_details.mode_of_payment = term.mode_of_payment | ||||
| 
 | ||||
| 	return term_details | ||||
| 
 | ||||
| 
 | ||||
| def get_due_date(term, posting_date=None, bill_date=None): | ||||
| 	due_date = None | ||||
| 	date = bill_date or posting_date | ||||
| @ -1258,6 +1268,16 @@ def get_due_date(term, posting_date=None, bill_date=None): | ||||
| 		due_date = add_months(get_last_day(date), term.credit_months) | ||||
| 	return due_date | ||||
| 
 | ||||
| def get_discount_date(term, posting_date=None, bill_date=None): | ||||
| 	discount_validity = None | ||||
| 	date = bill_date or posting_date | ||||
| 	if term.discount_validity_based_on == "Day(s) after invoice date": | ||||
| 		discount_validity = add_days(date, term.discount_validity) | ||||
| 	elif term.discount_validity_based_on == "Day(s) after the end of the invoice month": | ||||
| 		discount_validity = add_days(get_last_day(date), term.discount_validity) | ||||
| 	elif term.discount_validity_based_on == "Month(s) after the end of the invoice month": | ||||
| 		discount_validity = add_months(get_last_day(date), term.discount_validity) | ||||
| 	return discount_validity | ||||
| 
 | ||||
| def get_supplier_block_status(party_name): | ||||
| 	""" | ||||
| @ -1316,25 +1336,63 @@ def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child | ||||
| 	p_doc = frappe.get_doc(parent_doctype, parent_doctype_name) | ||||
| 	child_item = frappe.new_doc(child_doctype, p_doc, child_docname) | ||||
| 	item = frappe.get_doc("Item", trans_item.get('item_code')) | ||||
| 
 | ||||
| 	for field in ("item_code", "item_name", "description", "item_group"): | ||||
| 	    child_item.update({field: item.get(field)}) | ||||
| 		child_item.update({field: item.get(field)}) | ||||
| 
 | ||||
| 	date_fieldname = "delivery_date" if child_doctype == "Sales Order Item" else "schedule_date" | ||||
| 	child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)}) | ||||
| 	child_item.stock_uom = item.stock_uom | ||||
| 	child_item.uom = trans_item.get("uom") or item.stock_uom | ||||
| 	child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) | ||||
| 	conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor")) | ||||
| 	child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor | ||||
| 
 | ||||
| 	if child_doctype == "Purchase Order Item": | ||||
| 		child_item.base_rate = 1 # Initiallize value will update in parent validation | ||||
| 		child_item.base_amount = 1 # Initiallize value will update in parent validation | ||||
| 		# Initialized value will update in parent validation | ||||
| 		child_item.base_rate = 1 | ||||
| 		child_item.base_amount = 1 | ||||
| 	if child_doctype == "Sales Order Item": | ||||
| 		child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) | ||||
| 		if not child_item.warehouse: | ||||
| 			frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.") | ||||
| 				.format(frappe.bold("default warehouse"), frappe.bold(item.item_code))) | ||||
| 
 | ||||
| 	set_child_tax_template_and_map(item, child_item, p_doc) | ||||
| 	add_taxes_from_tax_template(child_item, p_doc) | ||||
| 	return child_item | ||||
| 
 | ||||
| def validate_child_on_delete(row, parent): | ||||
| 	"""Check if partially transacted item (row) is being deleted.""" | ||||
| 	if parent.doctype == "Sales Order": | ||||
| 		if flt(row.delivered_qty): | ||||
| 			frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(row.idx, row.item_code)) | ||||
| 		if flt(row.work_order_qty): | ||||
| 			frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(row.idx, row.item_code)) | ||||
| 		if flt(row.ordered_qty): | ||||
| 			frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(row.idx, row.item_code)) | ||||
| 
 | ||||
| 	if parent.doctype == "Purchase Order" and flt(row.received_qty): | ||||
| 		frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(row.idx, row.item_code)) | ||||
| 
 | ||||
| 	if flt(row.billed_amt): | ||||
| 		frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(row.idx, row.item_code)) | ||||
| 
 | ||||
| def update_bin_on_delete(row, doctype): | ||||
| 	"""Update bin for deleted item (row).""" | ||||
| 	from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty, get_ordered_qty, get_indented_qty | ||||
| 	qty_dict = {} | ||||
| 
 | ||||
| 	if doctype == "Sales Order": | ||||
| 		qty_dict["reserved_qty"] = get_reserved_qty(row.item_code, row.warehouse) | ||||
| 	else: | ||||
| 		if row.material_request_item: | ||||
| 			qty_dict["indented_qty"] = get_indented_qty(row.item_code, row.warehouse) | ||||
| 
 | ||||
| 		qty_dict["ordered_qty"] = get_ordered_qty(row.item_code, row.warehouse) | ||||
| 
 | ||||
| 	update_bin_qty(row.item_code, row.warehouse, qty_dict) | ||||
| 
 | ||||
| def validate_and_delete_children(parent, data): | ||||
| 	deleted_children = [] | ||||
| 	updated_item_names = [d.get("docname") for d in data] | ||||
| @ -1343,23 +1401,17 @@ def validate_and_delete_children(parent, data): | ||||
| 			deleted_children.append(item) | ||||
| 
 | ||||
| 	for d in deleted_children: | ||||
| 		if parent.doctype == "Sales Order": | ||||
| 			if flt(d.delivered_qty): | ||||
| 				frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(d.idx, d.item_code)) | ||||
| 			if flt(d.work_order_qty): | ||||
| 				frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(d.idx, d.item_code)) | ||||
| 			if flt(d.ordered_qty): | ||||
| 				frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(d.idx, d.item_code)) | ||||
| 
 | ||||
| 		if parent.doctype == "Purchase Order" and flt(d.received_qty): | ||||
| 			frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(d.idx, d.item_code)) | ||||
| 
 | ||||
| 		if flt(d.billed_amt): | ||||
| 			frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(d.idx, d.item_code)) | ||||
| 
 | ||||
| 		validate_child_on_delete(d, parent) | ||||
| 		d.cancel() | ||||
| 		d.delete() | ||||
| 
 | ||||
| 	# need to update ordered qty in Material Request first | ||||
| 	# bin uses Material Request Items to recalculate & update | ||||
| 	parent.update_prevdoc_status() | ||||
| 
 | ||||
| 	for d in deleted_children: | ||||
| 		update_bin_on_delete(d, parent.doctype) | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): | ||||
| 	def check_doc_permissions(doc, perm_type='create'): | ||||
| @ -1394,7 +1446,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil | ||||
| 			) | ||||
| 
 | ||||
| 	def get_new_child_item(item_row): | ||||
| 		child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"  | ||||
| 		child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item" | ||||
| 		return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row) | ||||
| 
 | ||||
| 	def validate_quantity(child_item, d): | ||||
|  | ||||
| @ -495,7 +495,7 @@ class StockController(AccountsController): | ||||
| 			"voucher_no": self.name, | ||||
| 			"company": self.company | ||||
| 		}) | ||||
| 		if check_if_future_sle_exists(args): | ||||
| 		if future_sle_exists(args): | ||||
| 			create_repost_item_valuation_entry(args) | ||||
| 		elif not is_reposting_pending(): | ||||
| 			check_if_stock_and_account_balance_synced(self.posting_date, | ||||
| @ -506,37 +506,42 @@ def is_reposting_pending(): | ||||
| 		{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) | ||||
| 
 | ||||
| 
 | ||||
| def check_if_future_sle_exists(args): | ||||
| 	sl_entries = frappe.db.get_all("Stock Ledger Entry", | ||||
| def future_sle_exists(args): | ||||
| 	sl_entries = frappe.get_all("Stock Ledger Entry", | ||||
| 		filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no}, | ||||
| 		fields=["item_code", "warehouse"], | ||||
| 		order_by="creation asc") | ||||
| 
 | ||||
| 	distinct_item_warehouses = list(set([(d.item_code, d.warehouse) for d in sl_entries])) | ||||
| 	if not sl_entries: | ||||
| 		return | ||||
| 
 | ||||
| 	sle_exists = False | ||||
| 	for item_code, warehouse in distinct_item_warehouses: | ||||
| 		args.update({ | ||||
| 			"item_code": item_code, | ||||
| 			"warehouse": warehouse | ||||
| 		}) | ||||
| 		if get_sle(args): | ||||
| 			sle_exists = True | ||||
| 			break | ||||
| 	return sle_exists | ||||
| 	warehouse_items_map = {} | ||||
| 	for entry in sl_entries: | ||||
| 		if entry.warehouse not in warehouse_items_map: | ||||
| 			warehouse_items_map[entry.warehouse] = set() | ||||
| 
 | ||||
| 		warehouse_items_map[entry.warehouse].add(entry.item_code) | ||||
| 
 | ||||
| 	or_conditions = [] | ||||
| 	for warehouse, items in warehouse_items_map.items(): | ||||
| 		or_conditions.append( | ||||
| 			"warehouse = '{}' and item_code in ({})".format( | ||||
| 				warehouse, | ||||
| 				", ".join(frappe.db.escape(item) for item in items) | ||||
| 			) | ||||
| 		) | ||||
| 
 | ||||
| def get_sle(args): | ||||
| 	return frappe.db.sql(""" | ||||
| 		select name | ||||
| 		from `tabStock Ledger Entry` | ||||
| 		where | ||||
| 			item_code=%(item_code)s | ||||
| 			and warehouse=%(warehouse)s | ||||
| 			and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s) | ||||
| 			({}) | ||||
| 			and timestamp(posting_date, posting_time) | ||||
| 				>= timestamp(%(posting_date)s, %(posting_time)s) | ||||
| 			and voucher_no != %(voucher_no)s | ||||
| 			and is_cancelled = 0 | ||||
| 		limit 1 | ||||
| 	""", args) | ||||
| 		""".format(" or ".join(or_conditions)), args) | ||||
| 
 | ||||
| def create_repost_item_valuation_entry(args): | ||||
| 	args = frappe._dict(args) | ||||
| @ -554,4 +559,4 @@ def create_repost_item_valuation_entry(args): | ||||
| 	repost_entry.allow_zero_rate = args.allow_zero_rate | ||||
| 	repost_entry.flags.ignore_links = True | ||||
| 	repost_entry.save() | ||||
| 	repost_entry.submit() | ||||
| 	repost_entry.submit() | ||||
|  | ||||
| @ -113,7 +113,10 @@ class calculate_taxes_and_totals(object): | ||||
| 					item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item) | ||||
| 					if flt(item.rate_with_margin) > 0: | ||||
| 						item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) | ||||
| 						item.discount_amount = item.rate_with_margin - item.rate | ||||
| 						if item.discount_amount and not item.discount_percentage: | ||||
| 							item.rate -= item.discount_amount | ||||
| 						else: | ||||
| 							item.discount_amount = item.rate_with_margin - item.rate | ||||
| 					elif flt(item.price_list_rate) > 0: | ||||
| 						item.discount_amount = item.price_list_rate - item.rate | ||||
| 				elif flt(item.price_list_rate) > 0 and not item.discount_amount: | ||||
| @ -805,4 +808,4 @@ class init_landed_taxes_and_totals(object): | ||||
| 	def set_amounts_in_company_currency(self): | ||||
| 		for d in self.doc.get(self.tax_field): | ||||
| 			d.amount = flt(d.amount, d.precision("amount")) | ||||
| 			d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount")) | ||||
| 			d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount")) | ||||
|  | ||||
| @ -11,7 +11,8 @@ from frappe.utils.file_manager import get_file, get_file_path | ||||
| from six.moves.urllib.parse import urlencode | ||||
| 
 | ||||
| class LinkedInSettings(Document): | ||||
| 	def get_authorization_url(self):	 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_authorization_url(self): | ||||
| 		params = urlencode({ | ||||
| 			"response_type":"code", | ||||
| 			"client_id": self.consumer_key, | ||||
| @ -35,7 +36,7 @@ class LinkedInSettings(Document): | ||||
| 		headers = { | ||||
| 			"Content-Type": "application/x-www-form-urlencoded" | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 		response = self.http_post(url=url, data=body, headers=headers) | ||||
| 		response = frappe.parse_json(response.content.decode()) | ||||
| 		self.db_set("access_token", response["access_token"]) | ||||
|  | ||||
| @ -85,6 +85,7 @@ class Opportunity(TransactionBase): | ||||
| 			self.opportunity_from = "Lead" | ||||
| 			self.party_name = lead_name | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None): | ||||
| 		if not self.has_active_quotation(): | ||||
| 			frappe.db.set(self, 'status', 'Lost') | ||||
| @ -248,7 +249,6 @@ def make_quotation(source_name, target_doc=None): | ||||
| 			"doctype": "Quotation", | ||||
| 			"field_map": { | ||||
| 				"opportunity_from": "quotation_to", | ||||
| 				"opportunity_type": "order_type", | ||||
| 				"name": "enq_no", | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| @ -11,6 +11,7 @@ from frappe.utils import get_url_to_form, get_link_to_form | ||||
| from tweepy.error import TweepError | ||||
| 
 | ||||
| class TwitterSettings(Document): | ||||
| 	@frappe.whitelist() | ||||
| 	def get_authorize_url(self): | ||||
| 		callback_url = "{0}/api/method/erpnext.crm.doctype.twitter_settings.twitter_settings.callback?".format(frappe.utils.get_url()) | ||||
| 		auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"), callback_url) | ||||
| @ -21,12 +22,12 @@ class TwitterSettings(Document): | ||||
| 			frappe.msgprint(_("Error! Failed to get request token.")) | ||||
| 			frappe.throw(_('Invalid {0} or {1}').format(frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key"))) | ||||
| 
 | ||||
| 	 | ||||
| 
 | ||||
| 	def get_access_token(self, oauth_token, oauth_verifier): | ||||
| 		auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret")) | ||||
| 		auth.request_token = {  | ||||
| 		auth.request_token = { | ||||
| 			'oauth_token' : oauth_token, | ||||
| 			'oauth_token_secret' : oauth_verifier  | ||||
| 			'oauth_token_secret' : oauth_verifier | ||||
| 		} | ||||
| 
 | ||||
| 		try: | ||||
| @ -50,10 +51,10 @@ class TwitterSettings(Document): | ||||
| 			frappe.throw(_('Invalid Consumer Key or Consumer Secret Key')) | ||||
| 
 | ||||
| 	def get_api(self, access_token, access_token_secret): | ||||
| 		# authentication of consumer key and secret  | ||||
| 		auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))  | ||||
| 		# authentication of access token and secret  | ||||
| 		auth.set_access_token(access_token, access_token_secret)  | ||||
| 		# authentication of consumer key and secret | ||||
| 		auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret")) | ||||
| 		# authentication of access token and secret | ||||
| 		auth.set_access_token(access_token, access_token_secret) | ||||
| 
 | ||||
| 		return tweepy.API(auth) | ||||
| 
 | ||||
| @ -64,7 +65,7 @@ class TwitterSettings(Document): | ||||
| 		if media: | ||||
| 			media_id = self.upload_image(media) | ||||
| 			return self.send_tweet(text, media_id) | ||||
| 	 | ||||
| 
 | ||||
| 	def upload_image(self, media): | ||||
| 		media = get_file_path(media) | ||||
| 		api = self.get_api(self.access_token, self.access_token_secret) | ||||
|  | ||||
| @ -13,6 +13,7 @@ from erpnext.education.utils import OverlapError | ||||
| 
 | ||||
| class CourseSchedulingTool(Document): | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def schedule_course(self): | ||||
| 		"""Creates course schedules as per specified parameters""" | ||||
| 
 | ||||
|  | ||||
| @ -52,6 +52,7 @@ class FeeSchedule(Document): | ||||
| 		self.grand_total = no_of_students*self.total_amount | ||||
| 		self.grand_total_in_words = money_in_words(self.grand_total) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def create_fees(self): | ||||
| 		self.db_set("fee_creation_status", "In Process") | ||||
| 		frappe.publish_realtime("fee_schedule_progress", | ||||
|  | ||||
| @ -91,6 +91,8 @@ class ProgramEnrollment(Document): | ||||
| 				(fee, fee) for fee in fee_list] | ||||
| 			msgprint(_("Fee Records Created - {0}").format(comma_and(fee_list))) | ||||
| 
 | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_courses(self): | ||||
| 		return frappe.db.sql('''select course from `tabProgram Course` where parent = %s and required = 1''', (self.program), as_dict=1) | ||||
| 
 | ||||
|  | ||||
| @ -14,6 +14,7 @@ class ProgramEnrollmentTool(Document): | ||||
| 		academic_term_reqd = cint(frappe.db.get_single_value('Education Settings', 'academic_term_reqd')) | ||||
| 		self.set_onload("academic_term_reqd", academic_term_reqd) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_students(self): | ||||
| 		students = [] | ||||
| 		if not self.get_students_from: | ||||
| @ -49,6 +50,7 @@ class ProgramEnrollmentTool(Document): | ||||
| 		else: | ||||
| 			frappe.throw(_("No students Found")) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def enroll_students(self): | ||||
| 		total = len(self.students) | ||||
| 		for i, stud in enumerate(self.students): | ||||
|  | ||||
| @ -10,6 +10,7 @@ | ||||
|   "naming_series", | ||||
|   "student", | ||||
|   "student_name", | ||||
|   "student_mobile_number", | ||||
|   "course_schedule", | ||||
|   "student_group", | ||||
|   "column_break_3", | ||||
| @ -93,11 +94,19 @@ | ||||
|    "options": "Student Attendance", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fetch_from": "student.student_mobile_number", | ||||
|    "fieldname": "student_mobile_number", | ||||
|    "fieldtype": "Read Only", | ||||
|    "label": "Student Mobile Number", | ||||
|    "options": "Phone" | ||||
|   } | ||||
|  ], | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-07-08 13:55:42.580181", | ||||
|  "modified": "2021-03-24 00:02:11.005895", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Education", | ||||
|  "name": "Student Attendance", | ||||
|  | ||||
| @ -9,6 +9,7 @@ from frappe.model.document import Document | ||||
| from erpnext.education.doctype.student_group.student_group import get_students | ||||
| 
 | ||||
| class StudentGroupCreationTool(Document): | ||||
| 	@frappe.whitelist() | ||||
| 	def get_courses(self): | ||||
| 		group_list = [] | ||||
| 
 | ||||
| @ -42,6 +43,7 @@ class StudentGroupCreationTool(Document): | ||||
| 
 | ||||
| 		return group_list | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def create_student_groups(self): | ||||
| 		if not self.courses: | ||||
| 			frappe.throw(_("""No Student Groups created.""")) | ||||
|  | ||||
| @ -59,9 +59,10 @@ class MpesaSettings(Document): | ||||
| 				request_amounts.append(amount) | ||||
| 		else: | ||||
| 			request_amounts = [request_amount] | ||||
| 		 | ||||
| 
 | ||||
| 		return request_amounts | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_account_balance_info(self): | ||||
| 		payload = dict( | ||||
| 			reference_doctype="Mpesa Settings", | ||||
| @ -198,7 +199,7 @@ def get_completed_integration_requests_info(reference_doctype, reference_docname | ||||
| 		completed_mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name") | ||||
| 		completed_payments.append(completed_amount) | ||||
| 		mpesa_receipts.append(completed_mpesa_receipt) | ||||
| 	 | ||||
| 
 | ||||
| 	return mpesa_receipts, completed_payments | ||||
| 
 | ||||
| def get_account_balance(request_payload): | ||||
|  | ||||
| @ -15,6 +15,7 @@ from frappe.utils import add_months, formatdate, getdate, today | ||||
| 
 | ||||
| class PlaidSettings(Document): | ||||
| 	@staticmethod | ||||
| 	@frappe.whitelist() | ||||
| 	def get_link_token(): | ||||
| 		plaid = PlaidConnector() | ||||
| 		return plaid.get_link_token() | ||||
|  | ||||
| @ -23,14 +23,9 @@ class TestPlaidSettings(unittest.TestCase): | ||||
| 			doc.cancel() | ||||
| 			doc.delete() | ||||
| 
 | ||||
| 		for ba in frappe.get_all("Bank Account"): | ||||
| 			frappe.get_doc("Bank Account", ba.name).delete() | ||||
| 
 | ||||
| 		for at in frappe.get_all("Bank Account Type"): | ||||
| 			frappe.get_doc("Bank Account Type", at.name).delete() | ||||
| 
 | ||||
| 		for ast in frappe.get_all("Bank Account Subtype"): | ||||
| 			frappe.get_doc("Bank Account Subtype", ast.name).delete() | ||||
| 		for doctype in ("Bank Account", "Bank Account Type", "Bank Account Subtype"): | ||||
| 			for d in frappe.get_all(doctype): | ||||
| 				frappe.delete_doc(doctype, d.name, force=True) | ||||
| 
 | ||||
| 	def test_plaid_disabled(self): | ||||
| 		frappe.db.set_value("Plaid Settings", None, "enabled", 0) | ||||
|  | ||||
| @ -54,6 +54,7 @@ class QuickBooksMigrator(Document): | ||||
| 			self.authorization_url = self.oauth.authorization_url(self.authorization_endpoint)[0] | ||||
| 
 | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def migrate(self): | ||||
| 		frappe.enqueue_doc("QuickBooks Migrator", "QuickBooks Migrator", "_migrate", queue="long") | ||||
| 
 | ||||
|  | ||||
| @ -594,18 +594,22 @@ class TallyMigration(Document): | ||||
| 			frappe.db.set_value("Price List", "Tally Price List", "enabled", 0) | ||||
| 		frappe.flags.in_migrate = False | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def process_master_data(self): | ||||
| 		self.set_status("Processing Master Data") | ||||
| 		frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def import_master_data(self): | ||||
| 		self.set_status("Importing Master Data") | ||||
| 		frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def process_day_book_data(self): | ||||
| 		self.set_status("Processing Day Book Data") | ||||
| 		frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def import_day_book_data(self): | ||||
| 		self.set_status("Importing Day Book Data") | ||||
| 		frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600) | ||||
|  | ||||
| @ -54,6 +54,7 @@ class ClinicalProcedure(Document): | ||||
| 	def set_title(self): | ||||
| 		self.title = _('{0} - {1}').format(self.patient_name or self.patient, self.procedure_template)[:100] | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def complete_procedure(self): | ||||
| 		if self.consume_stock and self.items: | ||||
| 			stock_entry = make_stock_entry(self) | ||||
| @ -96,6 +97,7 @@ class ClinicalProcedure(Document): | ||||
| 		if self.consume_stock and self.items: | ||||
| 			return stock_entry | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def start_procedure(self): | ||||
| 		allow_start = self.set_actual_qty() | ||||
| 		if allow_start: | ||||
| @ -116,6 +118,7 @@ class ClinicalProcedure(Document): | ||||
| 
 | ||||
| 		return allow_start | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def make_material_receipt(self, submit=False): | ||||
| 		stock_entry = frappe.new_doc('Stock Entry') | ||||
| 
 | ||||
|  | ||||
| @ -14,6 +14,7 @@ class InpatientMedicationEntry(Document): | ||||
| 	def validate(self): | ||||
| 		self.validate_medication_orders() | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_medication_orders(self): | ||||
| 		# pull inpatient medication orders based on selected filters | ||||
| 		orders = get_pending_medication_orders(self) | ||||
|  | ||||
| @ -57,6 +57,7 @@ class InpatientMedicationOrder(Document): | ||||
| 
 | ||||
| 		self.db_set('status', status) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def add_order_entries(self, order): | ||||
| 		if order.get('drug_code'): | ||||
| 			dosage = frappe.get_doc('Prescription Dosage', order.get('dosage')) | ||||
|  | ||||
| @ -81,15 +81,8 @@ class TestInpatientMedicationOrder(unittest.TestCase): | ||||
| 			self.ip_record.reload() | ||||
| 			discharge_patient(self.ip_record) | ||||
| 
 | ||||
| 		for entry in frappe.get_all('Inpatient Medication Entry'): | ||||
| 			doc = frappe.get_doc('Inpatient Medication Entry', entry.name) | ||||
| 			doc.cancel() | ||||
| 			doc.delete() | ||||
| 
 | ||||
| 		for entry in frappe.get_all('Inpatient Medication Order'): | ||||
| 			doc = frappe.get_doc('Inpatient Medication Order', entry.name) | ||||
| 			doc.cancel() | ||||
| 			doc.delete() | ||||
| 		for doctype in ["Inpatient Medication Entry", "Inpatient Medication Order"]: | ||||
| 			frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) | ||||
| 
 | ||||
| def create_dosage_form(): | ||||
| 	if not frappe.db.exists('Dosage Form', 'Tablet'): | ||||
|  | ||||
| @ -53,7 +53,7 @@ | ||||
|   "discharge_ordered_date", | ||||
|   "discharge_practitioner", | ||||
|   "discharge_encounter", | ||||
|   "discharge_date", | ||||
|   "discharge_datetime", | ||||
|   "cb_discharge", | ||||
|   "discharge_instructions", | ||||
|   "followup_date", | ||||
| @ -404,14 +404,15 @@ | ||||
|    "permlevel": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "discharge_date", | ||||
|    "fieldtype": "Date", | ||||
|    "fieldname": "discharge_datetime", | ||||
|    "fieldtype": "Datetime", | ||||
|    "label": "Discharge Date", | ||||
|    "read_only": 1 | ||||
|   } | ||||
|  ], | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-21 02:26:22.144575", | ||||
|  "modified": "2021-03-18 14:44:11.689956", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Healthcare", | ||||
|  "name": "Inpatient Record", | ||||
|  | ||||
| @ -53,12 +53,15 @@ class InpatientRecord(Document): | ||||
| 				+ """ <b><a href="/app/Form/Inpatient Record/{0}">{0}</a></b>""".format(ip_record[0].name)) | ||||
| 			frappe.throw(msg) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def admit(self, service_unit, check_in, expected_discharge=None): | ||||
| 		admit_patient(self, service_unit, check_in, expected_discharge) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def discharge(self): | ||||
| 		discharge_patient(self) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def transfer(self, service_unit, check_in, leave_from): | ||||
| 		if leave_from: | ||||
| 			patient_leave_service_unit(self, check_in, leave_from) | ||||
| @ -151,7 +154,7 @@ def check_out_inpatient(inpatient_record): | ||||
| 
 | ||||
| def discharge_patient(inpatient_record): | ||||
| 	validate_inpatient_invoicing(inpatient_record) | ||||
| 	inpatient_record.discharge_date = today() | ||||
| 	inpatient_record.discharge_datetime = now_datetime() | ||||
| 	inpatient_record.status = "Discharged" | ||||
| 
 | ||||
| 	inpatient_record.save(ignore_permissions = True) | ||||
|  | ||||
| @ -111,6 +111,7 @@ class Patient(Document): | ||||
| 			age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)") | ||||
| 		return age_str | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def invoice_patient_registration(self): | ||||
| 		if frappe.db.get_single_value('Healthcare Settings', 'registration_fee'): | ||||
| 			company = frappe.defaults.get_user_default('company') | ||||
|  | ||||
| @ -113,6 +113,7 @@ class PatientAppointment(Document): | ||||
| 		if fee_validity: | ||||
| 			frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till)) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_therapy_types(self): | ||||
| 		if not self.therapy_plan: | ||||
| 			return | ||||
|  | ||||
| @ -34,6 +34,7 @@ class PatientHistorySettings(Document): | ||||
| 				frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format( | ||||
| 					entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type))) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_doctype_fields(self, document_type, fields): | ||||
| 		multicheck_fields = [] | ||||
| 		doc_fields = frappe.get_meta(document_type).fields | ||||
| @ -49,6 +50,7 @@ class PatientHistorySettings(Document): | ||||
| 
 | ||||
| 		return multicheck_fields | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_date_field_for_dt(self, document_type): | ||||
| 		meta = frappe.get_meta(document_type) | ||||
| 		date_fields = meta.get('fields', { | ||||
|  | ||||
| @ -33,6 +33,7 @@ class TherapyPlan(Document): | ||||
| 		self.db_set('total_sessions', total_sessions) | ||||
| 		self.db_set('total_sessions_completed', total_sessions_completed) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def set_therapy_details_from_template(self): | ||||
| 		# Add therapy types in the child table | ||||
| 		self.set('therapy_plan_details', []) | ||||
|  | ||||
| @ -195,6 +195,10 @@ sounds = [ | ||||
| 	{"name": "call-disconnect", "src": "/assets/erpnext/sounds/call-disconnect.mp3", "volume": 0.2}, | ||||
| ] | ||||
| 
 | ||||
| has_upload_permission = { | ||||
| 	"Employee": "erpnext.hr.doctype.employee.employee.has_upload_permission" | ||||
| } | ||||
| 
 | ||||
| has_website_permission = { | ||||
| 	"Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission", | ||||
| 	"Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission", | ||||
| @ -324,6 +328,7 @@ scheduler_events = { | ||||
| 		"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts", | ||||
| 		"erpnext.support.doctype.issue.issue.set_service_level_agreement_variance", | ||||
| 		"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders", | ||||
| 		"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries" | ||||
| 	], | ||||
| 	"daily": [ | ||||
| 		"erpnext.stock.reorder_item.reorder_item", | ||||
|  | ||||
| @ -5,7 +5,7 @@ | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe import _ | ||||
| from frappe.utils import date_diff, add_days, getdate, cint | ||||
| from frappe.utils import date_diff, add_days, getdate, cint, format_date | ||||
| from frappe.model.document import Document | ||||
| from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \ | ||||
| 	get_holidays_for_employee, create_additional_leave_ledger_entry | ||||
| @ -40,7 +40,12 @@ class CompensatoryLeaveRequest(Document): | ||||
| 	def validate_holidays(self): | ||||
| 		holidays = get_holidays_for_employee(self.employee, self.work_from_date, self.work_end_date) | ||||
| 		if len(holidays) < date_diff(self.work_end_date, self.work_from_date) + 1: | ||||
| 			frappe.throw(_("Compensatory leave request days not in valid holidays")) | ||||
| 			if date_diff(self.work_end_date, self.work_from_date): | ||||
| 				msg = _("The days between {0} to {1} are not valid holidays.").format(frappe.bold(format_date(self.work_from_date)), frappe.bold(format_date(self.work_end_date))) | ||||
| 			else: | ||||
| 				msg = _("{0} is not a holiday.").format(frappe.bold(format_date(self.work_from_date))) | ||||
| 
 | ||||
| 			frappe.throw(msg) | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		company = frappe.db.get_value("Employee", self.employee, "company") | ||||
| @ -63,7 +68,7 @@ class CompensatoryLeaveRequest(Document): | ||||
| 				leave_allocation = self.create_leave_allocation(leave_period, date_difference) | ||||
| 			self.leave_allocation=leave_allocation.name | ||||
| 		else: | ||||
| 			frappe.throw(_("There is no leave period in between {0} and {1}").format(self.work_from_date, self.work_end_date)) | ||||
| 			frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date))) | ||||
| 
 | ||||
| 	def on_cancel(self): | ||||
| 		if self.leave_allocation: | ||||
|  | ||||
| @ -8,7 +8,7 @@ from frappe.utils import getdate, validate_email_address, today, add_years, form | ||||
| from frappe.model.naming import set_name_by_naming_series | ||||
| from frappe import throw, _, scrub | ||||
| from frappe.permissions import add_user_permission, remove_user_permission, \ | ||||
| 	set_user_permission_if_allowed, has_permission | ||||
| 	set_user_permission_if_allowed, has_permission, get_doc_permissions | ||||
| from frappe.model.document import Document | ||||
| from erpnext.utilities.transaction_base import delete_events | ||||
| from frappe.utils.nestedset import NestedSet | ||||
| @ -66,7 +66,7 @@ class Employee(NestedSet): | ||||
| 	def validate_user_details(self): | ||||
| 		data = frappe.db.get_value('User', | ||||
| 			self.user_id, ['enabled', 'user_image'], as_dict=1) | ||||
| 		if data.get("user_image"): | ||||
| 		if data.get("user_image") and self.image == '': | ||||
| 			self.image = data.get("user_image") | ||||
| 		self.validate_for_enabled_user_id(data.get("enabled", 0)) | ||||
| 		self.validate_duplicate_user_id() | ||||
| @ -80,6 +80,7 @@ class Employee(NestedSet): | ||||
| 			self.update_user() | ||||
| 			self.update_user_permissions() | ||||
| 		self.reset_employee_emails_cache() | ||||
| 		self.update_approver_role() | ||||
| 
 | ||||
| 	def update_user_permissions(self): | ||||
| 		if not self.create_user_permission: return | ||||
| @ -145,6 +146,17 @@ class Employee(NestedSet): | ||||
| 
 | ||||
| 		user.save() | ||||
| 
 | ||||
| 	def update_approver_role(self): | ||||
| 		if self.leave_approver: | ||||
| 			user = frappe.get_doc("User", self.leave_approver) | ||||
| 			user.flags.ignore_permissions = True | ||||
| 			user.add_roles("Leave Approver") | ||||
| 
 | ||||
| 		if self.expense_approver: | ||||
| 			user = frappe.get_doc("User", self.expense_approver) | ||||
| 			user.flags.ignore_permissions = True | ||||
| 			user.add_roles("Expense Approver") | ||||
| 
 | ||||
| 	def validate_date(self): | ||||
| 		if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()): | ||||
| 			throw(_("Date of Birth cannot be greater than today.")) | ||||
| @ -501,3 +513,10 @@ def has_user_permission_for_employee(user_name, employee_name): | ||||
| 		'allow': 'Employee', | ||||
| 		'for_value': employee_name | ||||
| 	}) | ||||
| 
 | ||||
| def has_upload_permission(doc, ptype='read', user=None): | ||||
| 	if not user: | ||||
| 		user = frappe.session.user | ||||
| 	if get_doc_permissions(doc, user=user, ptype=ptype).get(ptype): | ||||
| 		return True | ||||
| 	return doc.user_id == user | ||||
| @ -181,7 +181,6 @@ | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "Company:company:default_currency", | ||||
|    "depends_on": "eval:(doc.docstatus==1 || doc.employee)", | ||||
|    "fieldname": "currency", | ||||
|    "fieldtype": "Link", | ||||
| @ -201,7 +200,7 @@ | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-11-25 12:01:55.980721", | ||||
|  "modified": "2021-03-31 14:42:47.321368", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "HR", | ||||
|  "name": "Employee Advance", | ||||
|  | ||||
| @ -6,7 +6,7 @@ import frappe, erpnext | ||||
| from frappe import _ | ||||
| from frappe.utils import get_fullname, flt, cstr, get_link_to_form | ||||
| from frappe.model.document import Document | ||||
| from erpnext.hr.utils import set_employee_name | ||||
| from erpnext.hr.utils import set_employee_name, share_doc_with_approver | ||||
| from erpnext.accounts.party import get_party_account | ||||
| from erpnext.accounts.general_ledger import make_gl_entries | ||||
| from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account | ||||
| @ -53,6 +53,9 @@ class ExpenseClaim(AccountsController): | ||||
| 		elif self.docstatus == 1 and self.approval_status == 'Rejected': | ||||
| 			self.status = 'Rejected' | ||||
| 
 | ||||
| 	def on_update(self): | ||||
| 		share_doc_with_approver(self, self.expense_approver) | ||||
| 
 | ||||
| 	def set_payable_account(self): | ||||
| 		if not self.payable_account and not self.is_paid: | ||||
| 			self.payable_account = frappe.get_cached_value('Company', self.company, 'default_expense_claim_payable_account') | ||||
| @ -211,6 +214,7 @@ class ExpenseClaim(AccountsController): | ||||
| 			self.total_claimed_amount += flt(d.amount) | ||||
| 			self.total_sanctioned_amount += flt(d.sanctioned_amount) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def calculate_taxes(self): | ||||
| 		self.total_taxes_and_charges = 0 | ||||
| 		for tax in self.taxes: | ||||
|  | ||||
| @ -95,12 +95,12 @@ class TestExpenseClaim(unittest.TestCase): | ||||
| 	def test_rejected_expense_claim(self): | ||||
| 		payable_account = get_payable_account(company_name) | ||||
| 		expense_claim = frappe.get_doc({ | ||||
| 			 "doctype": "Expense Claim", | ||||
| 			 "employee": "_T-Employee-00001", | ||||
| 			 "payable_account": payable_account, | ||||
| 			 "approval_status": "Rejected", | ||||
| 			 "expenses": | ||||
| 			 	[{ "expense_type": "Travel", "default_account": "Travel Expenses - _TC4", "amount": 300, "sanctioned_amount": 200 }] | ||||
| 			"doctype": "Expense Claim", | ||||
| 			"employee": "_T-Employee-00001", | ||||
| 			"payable_account": payable_account, | ||||
| 			"approval_status": "Rejected", | ||||
| 			"expenses": | ||||
| 				[{ "expense_type": "Travel", "default_account": "Travel Expenses - _TC4", "amount": 300, "sanctioned_amount": 200 }] | ||||
| 		}) | ||||
| 		expense_claim.submit() | ||||
| 
 | ||||
| @ -110,6 +110,34 @@ class TestExpenseClaim(unittest.TestCase): | ||||
| 		gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name}) | ||||
| 		self.assertEquals(len(gl_entry), 0) | ||||
| 
 | ||||
| 	def test_expense_approver_perms(self): | ||||
| 		user = "test_approver_perm_emp@example.com" | ||||
| 		make_employee(user, "_Test Company") | ||||
| 
 | ||||
| 		# check doc shared | ||||
| 		payable_account = get_payable_account("_Test Company") | ||||
| 		expense_claim = make_expense_claim(payable_account, 300, 200, "_Test Company", "Travel Expenses - _TC", do_not_submit=True) | ||||
| 		expense_claim.expense_approver = user | ||||
| 		expense_claim.save() | ||||
| 		self.assertTrue(expense_claim.name in frappe.share.get_shared("Expense Claim", user)) | ||||
| 
 | ||||
| 		# check shared doc revoked | ||||
| 		expense_claim.reload() | ||||
| 		expense_claim.expense_approver = "test@example.com" | ||||
| 		expense_claim.save() | ||||
| 		self.assertTrue(expense_claim.name not in frappe.share.get_shared("Expense Claim", user)) | ||||
| 
 | ||||
| 		expense_claim.reload() | ||||
| 		expense_claim.expense_approver = user | ||||
| 		expense_claim.save() | ||||
| 
 | ||||
| 		frappe.set_user(user) | ||||
| 		expense_claim.reload() | ||||
| 		expense_claim.status = "Approved" | ||||
| 		expense_claim.submit() | ||||
| 		frappe.set_user("Administrator") | ||||
| 
 | ||||
| 
 | ||||
| def get_payable_account(company): | ||||
| 	return frappe.get_cached_value('Company', company, 'default_payable_account') | ||||
| 
 | ||||
| @ -133,21 +161,21 @@ def make_expense_claim(payable_account, amount, sanctioned_amount, company, acco | ||||
| 
 | ||||
| 	currency, cost_center = frappe.db.get_value('Company', company, ['default_currency', 'cost_center']) | ||||
| 	expense_claim = { | ||||
| 		 "doctype": "Expense Claim", | ||||
| 		 "employee": employee, | ||||
| 		 "payable_account": payable_account, | ||||
| 		 "approval_status": "Approved", | ||||
| 		 "company": company, | ||||
| 		'currency': currency, | ||||
| 		 "expenses": [{ | ||||
| 		"doctype": "Expense Claim", | ||||
| 		"employee": employee, | ||||
| 		"payable_account": payable_account, | ||||
| 		"approval_status": "Approved", | ||||
| 		"company": company, | ||||
| 		"currency": currency, | ||||
| 		"expenses": [{ | ||||
| 			"expense_type": "Travel", | ||||
| 			"default_account": account, | ||||
| 			"currency": currency, | ||||
| 			"amount": amount, | ||||
| 			"sanctioned_amount": sanctioned_amount, | ||||
| 			"cost_center": cost_center | ||||
| 			}] | ||||
| 		} | ||||
| 		}] | ||||
| 	} | ||||
| 	if taxes: | ||||
| 		expense_claim.update(taxes) | ||||
| 
 | ||||
|  | ||||
| @ -99,6 +99,7 @@ class LeaveAllocation(Document): | ||||
| 				.format(formatdate(future_allocation[0].from_date), future_allocation[0].name), | ||||
| 					BackDatedAllocationError) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def set_total_leaves_allocated(self): | ||||
| 		self.unused_leaves = get_carry_forwarded_leaves(self.employee, | ||||
| 			self.leave_type, self.from_date, self.carry_forward) | ||||
|  | ||||
| @ -6,7 +6,7 @@ import frappe | ||||
| from frappe import _ | ||||
| from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \ | ||||
| 	comma_or, get_fullname, add_days, nowdate, get_datetime_str | ||||
| from erpnext.hr.utils import set_employee_name, get_leave_period | ||||
| from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver | ||||
| from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates | ||||
| from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee | ||||
| from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange | ||||
| @ -43,6 +43,8 @@ class LeaveApplication(Document): | ||||
| 			if frappe.db.get_single_value("HR Settings", "send_leave_notification"): | ||||
| 				self.notify_leave_approver() | ||||
| 
 | ||||
| 		share_doc_with_approver(self, self.leave_approver) | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		if self.status == "Open": | ||||
| 			frappe.throw(_("Only Leave Applications with status 'Approved' and 'Rejected' can be submitted")) | ||||
| @ -417,6 +419,7 @@ class LeaveApplication(Document): | ||||
| 			)) | ||||
| 			create_leave_ledger_entry(self, args, submit) | ||||
| 
 | ||||
| 
 | ||||
| def get_allocation_expiry(employee, leave_type, to_date, from_date): | ||||
| 	''' Returns expiry of carry forward allocation in leave ledger entry ''' | ||||
| 	expiry =  frappe.get_all("Leave Ledger Entry", | ||||
|  | ||||
| @ -11,6 +11,7 @@ from frappe.utils import add_days, nowdate, now_datetime, getdate, add_months | ||||
| from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type | ||||
| from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation | ||||
| from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees | ||||
| from erpnext.hr.doctype.employee.test_employee import make_employee | ||||
| 
 | ||||
| test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"] | ||||
| 
 | ||||
| @ -56,6 +57,7 @@ class TestLeaveApplication(unittest.TestCase): | ||||
| 	@classmethod | ||||
| 	def setUpClass(cls): | ||||
| 		set_leave_approver() | ||||
| 		frappe.db.sql("delete from tabAttendance where employee='_T-Employee-00001'") | ||||
| 
 | ||||
| 	def tearDown(self): | ||||
| 		frappe.set_user("Administrator") | ||||
| @ -230,8 +232,9 @@ class TestLeaveApplication(unittest.TestCase): | ||||
| 	def test_optional_leave(self): | ||||
| 		leave_period = get_leave_period() | ||||
| 		today = nowdate() | ||||
| 		from datetime import date | ||||
| 		holiday_list = 'Test Holiday List for Optional Holiday' | ||||
| 		optional_leave_date = add_days(today, 7) | ||||
| 
 | ||||
| 		if not frappe.db.exists('Holiday List', holiday_list): | ||||
| 			frappe.get_doc(dict( | ||||
| 				doctype = 'Holiday List', | ||||
| @ -239,7 +242,7 @@ class TestLeaveApplication(unittest.TestCase): | ||||
| 				from_date = add_months(today, -6), | ||||
| 				to_date = add_months(today, 6), | ||||
| 				holidays = [ | ||||
| 					dict(holiday_date = today, description = 'Test') | ||||
| 					dict(holiday_date = optional_leave_date, description = 'Test') | ||||
| 				] | ||||
| 			)).insert() | ||||
| 		employee = get_employee() | ||||
| @ -255,7 +258,7 @@ class TestLeaveApplication(unittest.TestCase): | ||||
| 
 | ||||
| 		allocate_leaves(employee, leave_period, leave_type, 10) | ||||
| 
 | ||||
| 		date = add_days(today, - 1) | ||||
| 		date = add_days(today, 6) | ||||
| 
 | ||||
| 		leave_application = frappe.get_doc(dict( | ||||
| 			doctype = 'Leave Application', | ||||
| @ -270,14 +273,14 @@ class TestLeaveApplication(unittest.TestCase): | ||||
| 		# can only apply on optional holidays | ||||
| 		self.assertRaises(NotAnOptionalHoliday, leave_application.insert) | ||||
| 
 | ||||
| 		leave_application.from_date = today | ||||
| 		leave_application.to_date = today | ||||
| 		leave_application.from_date = optional_leave_date | ||||
| 		leave_application.to_date = optional_leave_date | ||||
| 		leave_application.status = "Approved" | ||||
| 		leave_application.insert() | ||||
| 		leave_application.submit() | ||||
| 
 | ||||
| 		# check leave balance is reduced | ||||
| 		self.assertEqual(get_leave_balance_on(employee.name, leave_type, today), 9) | ||||
| 		self.assertEqual(get_leave_balance_on(employee.name, leave_type, optional_leave_date), 9) | ||||
| 
 | ||||
| 	def test_leaves_allowed(self): | ||||
| 		employee = get_employee() | ||||
| @ -341,7 +344,7 @@ class TestLeaveApplication(unittest.TestCase): | ||||
| 			to_date = add_days(date, 4), | ||||
| 			company = "_Test Company", | ||||
| 			docstatus = 1, | ||||
|             status = "Approved" | ||||
| 			status = "Approved" | ||||
| 		)) | ||||
| 
 | ||||
| 		self.assertRaises(frappe.ValidationError, leave_application.insert) | ||||
| @ -363,7 +366,7 @@ class TestLeaveApplication(unittest.TestCase): | ||||
| 			to_date = add_days(date, 4), | ||||
| 			company = "_Test Company", | ||||
| 			docstatus = 1, | ||||
|             status = "Approved" | ||||
| 			status = "Approved" | ||||
| 		)) | ||||
| 
 | ||||
| 		self.assertTrue(leave_application.insert()) | ||||
| @ -393,7 +396,7 @@ class TestLeaveApplication(unittest.TestCase): | ||||
| 			to_date = add_days(date, 4), | ||||
| 			company = "_Test Company", | ||||
| 			docstatus = 1, | ||||
|             status = "Approved" | ||||
| 			status = "Approved" | ||||
| 		)) | ||||
| 
 | ||||
| 		self.assertRaises(frappe.ValidationError, leave_application.insert) | ||||
| @ -508,7 +511,7 @@ class TestLeaveApplication(unittest.TestCase): | ||||
| 			description = "_Test Reason", | ||||
| 			company = "_Test Company", | ||||
| 			docstatus = 1, | ||||
|             status = "Approved" | ||||
| 			status = "Approved" | ||||
| 		)) | ||||
| 		leave_application.submit() | ||||
| 		leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name)) | ||||
| @ -540,7 +543,7 @@ class TestLeaveApplication(unittest.TestCase): | ||||
| 			description = "_Test Reason", | ||||
| 			company = "_Test Company", | ||||
| 			docstatus = 1, | ||||
|             status = "Approved" | ||||
| 			status = "Approved" | ||||
| 		)) | ||||
| 		leave_application.submit() | ||||
| 
 | ||||
| @ -565,6 +568,48 @@ class TestLeaveApplication(unittest.TestCase): | ||||
| 
 | ||||
| 		self.assertEquals(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0) | ||||
| 
 | ||||
| 	def test_leave_approver_perms(self): | ||||
| 		employee = get_employee() | ||||
| 		user = "test_approver_perm_emp@example.com" | ||||
| 		make_employee(user, "_Test Company") | ||||
| 
 | ||||
| 		# set approver for employee | ||||
| 		employee.reload() | ||||
| 		employee.leave_approver = user | ||||
| 		employee.save() | ||||
| 		self.assertTrue("Leave Approver" in frappe.get_roles(user)) | ||||
| 
 | ||||
| 		make_allocation_record(employee.name) | ||||
| 
 | ||||
| 		application = self.get_application(_test_records[0]) | ||||
| 		application.from_date = '2018-01-01' | ||||
| 		application.to_date = '2018-01-03' | ||||
| 		application.leave_approver = user | ||||
| 		application.insert() | ||||
| 		self.assertTrue(application.name in frappe.share.get_shared("Leave Application", user)) | ||||
| 
 | ||||
| 		# check shared doc revoked | ||||
| 		application.reload() | ||||
| 		application.leave_approver = "test@example.com" | ||||
| 		application.save() | ||||
| 		self.assertTrue(application.name not in frappe.share.get_shared("Leave Application", user)) | ||||
| 
 | ||||
| 		application.reload() | ||||
| 		application.leave_approver = user | ||||
| 		application.save() | ||||
| 
 | ||||
| 		frappe.set_user(user) | ||||
| 		application.reload() | ||||
| 		application.status = "Approved" | ||||
| 		application.submit() | ||||
| 
 | ||||
| 		# unset leave approver | ||||
| 		frappe.set_user("Administrator") | ||||
| 		employee.reload() | ||||
| 		employee.leave_approver = "" | ||||
| 		employee.save() | ||||
| 
 | ||||
| 
 | ||||
| def create_carry_forwarded_allocation(employee, leave_type): | ||||
| 		# initial leave allocation | ||||
| 		leave_allocation = create_leave_allocation( | ||||
|  | ||||
| @ -130,7 +130,6 @@ | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "Company:company:default_currency", | ||||
|    "depends_on": "eval:(doc.docstatus==1 || doc.employee)", | ||||
|    "fieldname": "currency", | ||||
|    "fieldtype": "Link", | ||||
| @ -155,7 +154,7 @@ | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-11-25 11:56:06.777241", | ||||
|  "modified": "2021-03-31 14:45:27.948207", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "HR", | ||||
|  "name": "Leave Encashment", | ||||
|  | ||||
| @ -63,6 +63,7 @@ class LeaveEncashment(Document): | ||||
| 				frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') - self.encashable_days) | ||||
| 		self.create_leave_ledger_entry(submit=False) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def get_leave_details_for_encashment(self): | ||||
| 		salary_structure = get_assigned_salary_structure(self.employee, self.encashment_date or getdate(nowdate())) | ||||
| 		if not salary_structure: | ||||
|  | ||||
| @ -34,8 +34,8 @@ def validate_leave_allocation_against_leave_application(ledger): | ||||
| 	""", (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date)) | ||||
| 
 | ||||
| 	if leave_application_records: | ||||
| 		frappe.throw(_("Leave allocation %s is linked with leave application %s" | ||||
| 			% (ledger.transaction_name, ', '.join(leave_application_records)))) | ||||
| 		frappe.throw(_("Leave allocation {0} is linked with the Leave Application {1}").format( | ||||
| 			ledger.transaction_name, ', '.join(leave_application_records))) | ||||
| 
 | ||||
| def create_leave_ledger_entry(ref_doc, args, submit=True): | ||||
| 	ledger = frappe._dict( | ||||
| @ -52,7 +52,9 @@ def create_leave_ledger_entry(ref_doc, args, submit=True): | ||||
| 	ledger.update(args) | ||||
| 
 | ||||
| 	if submit: | ||||
| 		frappe.get_doc(ledger).submit() | ||||
| 		doc = frappe.get_doc(ledger) | ||||
| 		doc.flags.ignore_permissions = 1 | ||||
| 		doc.submit() | ||||
| 	else: | ||||
| 		delete_ledger_entry(ledger) | ||||
| 
 | ||||
|  | ||||
| @ -36,6 +36,7 @@ class LeavePolicyAssignment(Document): | ||||
| 			frappe.throw(_("Leave Policy: {0} already assigned for Employee {1} for period {2} to {3}") | ||||
| 				.format(bold(self.leave_policy), bold(self.employee), bold(formatdate(self.effective_from)), bold(formatdate(self.effective_to)))) | ||||
| 
 | ||||
| 	@frappe.whitelist() | ||||
| 	def grant_leave_alloc_for_employee(self): | ||||
| 		if self.leaves_allocated: | ||||
| 			frappe.throw(_("Leave already have been assigned for this Leave Policy Assignment")) | ||||
|  | ||||
| @ -7,6 +7,7 @@ import frappe | ||||
| from frappe import _ | ||||
| from frappe.model.document import Document | ||||
| from frappe.utils import formatdate, getdate | ||||
| from erpnext.hr.utils import share_doc_with_approver | ||||
| 
 | ||||
| class OverlapError(frappe.ValidationError): pass | ||||
| 
 | ||||
| @ -17,6 +18,9 @@ class ShiftRequest(Document): | ||||
| 		self.validate_approver() | ||||
| 		self.validate_default_shift() | ||||
| 
 | ||||
| 	def on_update(self): | ||||
| 		share_doc_with_approver(self, self.approver) | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		if self.status not in ["Approved", "Rejected"]: | ||||
| 			frappe.throw(_("Only Shift Request with status 'Approved' and 'Rejected' can be submitted")) | ||||
| @ -29,6 +33,7 @@ class ShiftRequest(Document): | ||||
| 			if self.to_date: | ||||
| 				assignment_doc.end_date = self.to_date | ||||
| 			assignment_doc.shift_request = self.name | ||||
| 			assignment_doc.flags.ignore_permissions = 1 | ||||
| 			assignment_doc.insert() | ||||
| 			assignment_doc.submit() | ||||
| 
 | ||||
|  | ||||
| @ -6,6 +6,7 @@ from __future__ import unicode_literals | ||||
| import frappe | ||||
| import unittest | ||||
| from frappe.utils import nowdate, add_days | ||||
| from erpnext.hr.doctype.employee.test_employee import make_employee | ||||
| 
 | ||||
| test_dependencies = ["Shift Type"] | ||||
| 
 | ||||
| @ -19,19 +20,8 @@ class TestShiftRequest(unittest.TestCase): | ||||
| 		set_shift_approver(department) | ||||
| 		approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0] | ||||
| 
 | ||||
| 		shift_request = frappe.get_doc({ | ||||
| 			"doctype": "Shift Request", | ||||
| 			"shift_type": "Day Shift", | ||||
| 			"company": "_Test Company", | ||||
| 			"employee": "_T-Employee-00001", | ||||
| 			"employee_name": "_Test Employee", | ||||
| 			"from_date": nowdate(), | ||||
| 			"to_date": add_days(nowdate(), 10), | ||||
| 			"approver": approver, | ||||
| 			"status": "Approved" | ||||
| 		}) | ||||
| 		shift_request.insert() | ||||
| 		shift_request.submit() | ||||
| 		shift_request = make_shift_request(approver) | ||||
| 
 | ||||
| 		shift_assignments = frappe.db.sql(''' | ||||
| 				SELECT shift_request, employee | ||||
| 				FROM `tabShift Assignment` | ||||
| @ -44,8 +34,65 @@ class TestShiftRequest(unittest.TestCase): | ||||
| 			shift_assignment_doc = frappe.get_doc("Shift Assignment", {"shift_request": d.get('shift_request')}) | ||||
| 			self.assertEqual(shift_assignment_doc.docstatus, 2) | ||||
| 
 | ||||
| 	def test_shift_request_approver_perms(self): | ||||
| 		employee = frappe.get_doc("Employee", "_T-Employee-00001") | ||||
| 		user = "test_approver_perm_emp@example.com" | ||||
| 		make_employee(user, "_Test Company") | ||||
| 
 | ||||
| 		# set approver for employee | ||||
| 		employee.reload() | ||||
| 		employee.shift_request_approver = user | ||||
| 		employee.save() | ||||
| 
 | ||||
| 		shift_request = make_shift_request(user, do_not_submit=True) | ||||
| 		self.assertTrue(shift_request.name in frappe.share.get_shared("Shift Request", user)) | ||||
| 
 | ||||
| 		# check shared doc revoked | ||||
| 		shift_request.reload() | ||||
| 		department = frappe.get_value("Employee", "_T-Employee-00001", "department") | ||||
| 		set_shift_approver(department) | ||||
| 		department_approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0] | ||||
| 		shift_request.approver = department_approver | ||||
| 		shift_request.save() | ||||
| 		self.assertTrue(shift_request.name not in frappe.share.get_shared("Shift Request", user)) | ||||
| 
 | ||||
| 		shift_request.reload() | ||||
| 		shift_request.approver = user | ||||
| 		shift_request.save() | ||||
| 
 | ||||
| 		frappe.set_user(user) | ||||
| 		shift_request.reload() | ||||
| 		shift_request.status = "Approved" | ||||
| 		shift_request.submit() | ||||
| 
 | ||||
| 		# unset approver | ||||
| 		frappe.set_user("Administrator") | ||||
| 		employee.reload() | ||||
| 		employee.shift_request_approver = "" | ||||
| 		employee.save() | ||||
| 
 | ||||
| 
 | ||||
| def set_shift_approver(department): | ||||
| 	department_doc = frappe.get_doc("Department", department) | ||||
| 	department_doc.append('shift_request_approver',{'approver': "test1@example.com"}) | ||||
| 	department_doc.save() | ||||
| 	department_doc.reload() | ||||
| 
 | ||||
| def make_shift_request(approver, do_not_submit=0): | ||||
| 	shift_request = frappe.get_doc({ | ||||
| 		"doctype": "Shift Request", | ||||
| 		"shift_type": "Day Shift", | ||||
| 		"company": "_Test Company", | ||||
| 		"employee": "_T-Employee-00001", | ||||
| 		"employee_name": "_Test Employee", | ||||
| 		"from_date": nowdate(), | ||||
| 		"to_date": add_days(nowdate(), 10), | ||||
| 		"approver": approver, | ||||
| 		"status": "Approved" | ||||
| 	}).insert() | ||||
| 
 | ||||
| 	if do_not_submit: | ||||
| 		return shift_request | ||||
| 
 | ||||
| 	shift_request.submit() | ||||
| 	return shift_request | ||||
| @ -15,6 +15,7 @@ from erpnext.hr.doctype.attendance.attendance import mark_attendance | ||||
| from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee | ||||
| 
 | ||||
| class ShiftType(Document): | ||||
| 	@frappe.whitelist() | ||||
| 	def process_auto_attendance(self): | ||||
| 		if not cint(self.enable_auto_attendance) or not self.process_attendance_after or not self.last_sync_of_checkin: | ||||
| 			return | ||||
|  | ||||
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