Merge branch 'develop' into develop
This commit is contained in:
		
						commit
						3bf04154b1
					
				| @ -13,13 +13,15 @@ form_grid_templates = { | |||||||
| 
 | 
 | ||||||
| class BankReconciliation(Document): | class BankReconciliation(Document): | ||||||
| 	def get_payment_entries(self): | 	def get_payment_entries(self): | ||||||
| 		if not (self.bank_account and self.from_date and self.to_date): | 		if not (self.from_date and self.to_date): | ||||||
| 			msgprint(_("Bank Account, From Date and To Date are Mandatory")) | 			frappe.throw(_("From Date and To Date are Mandatory")) | ||||||
| 			return | 
 | ||||||
|  | 		if not self.account: | ||||||
|  | 			frappe.throw(_("Account is mandatory to get payment entries")) | ||||||
| 
 | 
 | ||||||
| 		condition = "" | 		condition = "" | ||||||
| 		if not self.include_reconciled_entries: | 		if not self.include_reconciled_entries: | ||||||
| 			condition = " and (clearance_date is null or clearance_date='0000-00-00')" | 			condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')" | ||||||
| 
 | 
 | ||||||
| 		journal_entries = frappe.db.sql(""" | 		journal_entries = frappe.db.sql(""" | ||||||
| 			select | 			select | ||||||
| @ -32,10 +34,14 @@ class BankReconciliation(Document): | |||||||
| 			where | 			where | ||||||
| 				t2.parent = t1.name and t2.account = %(account)s and t1.docstatus=1 | 				t2.parent = t1.name and t2.account = %(account)s and t1.docstatus=1 | ||||||
| 				and t1.posting_date >= %(from)s and t1.posting_date <= %(to)s | 				and t1.posting_date >= %(from)s and t1.posting_date <= %(to)s | ||||||
| 				and ifnull(t1.is_opening, 'No') = 'No' %(condition)s | 				and ifnull(t1.is_opening, 'No') = 'No' {condition} | ||||||
| 			group by t2.account, t1.name | 			group by t2.account, t1.name | ||||||
| 			order by t1.posting_date ASC, t1.name DESC | 			order by t1.posting_date ASC, t1.name DESC | ||||||
| 		""", {"condition":condition, "account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1) | 		""".format(condition=condition), {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1) | ||||||
|  | 		condition = '' | ||||||
|  | 
 | ||||||
|  | 		if self.bank_account: | ||||||
|  | 			condition += 'and bank_account = %(bank_account)s' | ||||||
| 
 | 
 | ||||||
| 		payment_entries = frappe.db.sql(""" | 		payment_entries = frappe.db.sql(""" | ||||||
| 			select | 			select | ||||||
| @ -49,10 +55,10 @@ class BankReconciliation(Document): | |||||||
| 			where | 			where | ||||||
| 				(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1 | 				(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1 | ||||||
| 				and posting_date >= %(from)s and posting_date <= %(to)s | 				and posting_date >= %(from)s and posting_date <= %(to)s | ||||||
| 				and bank_account = %(bank_account)s | 				{condition} | ||||||
| 			order by | 			order by | ||||||
| 				posting_date ASC, name DESC | 				posting_date ASC, name DESC | ||||||
| 		""", {"account": self.account, "from":self.from_date, | 		""".format(condition=condition), {"account": self.account, "from":self.from_date, | ||||||
| 				"to": self.to_date, "bank_account": self.bank_account}, as_dict=1) | 				"to": self.to_date, "bank_account": self.bank_account}, as_dict=1) | ||||||
| 
 | 
 | ||||||
| 		pos_entries = [] | 		pos_entries = [] | ||||||
|  | |||||||
| @ -18,7 +18,8 @@ | |||||||
|    "in_list_view": 1, |    "in_list_view": 1, | ||||||
|    "label": "Invoice", |    "label": "Invoice", | ||||||
|    "options": "Sales Invoice", |    "options": "Sales Invoice", | ||||||
|    "reqd": 1 |    "reqd": 1, | ||||||
|  |    "search_index": 1 | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "fetch_from": "sales_invoice.customer", |    "fetch_from": "sales_invoice.customer", | ||||||
| @ -60,7 +61,7 @@ | |||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "istable": 1, |  "istable": 1, | ||||||
|  "modified": "2019-09-26 11:05:36.016772", |  "modified": "2020-02-20 16:16:20.724620", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Accounts", |  "module": "Accounts", | ||||||
|  "name": "Discounted Invoice", |  "name": "Discounted Invoice", | ||||||
|  | |||||||
| @ -7,4 +7,4 @@ from __future__ import unicode_literals | |||||||
| from frappe.model.document import Document | from frappe.model.document import Document | ||||||
| 
 | 
 | ||||||
| class DiscountedInvoice(Document): | class DiscountedInvoice(Document): | ||||||
| 	pass | 	pass | ||||||
| @ -232,11 +232,36 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga | |||||||
| 		if bal < 0 and not on_cancel: | 		if bal < 0 and not on_cancel: | ||||||
| 			frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal))) | 			frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal))) | ||||||
| 
 | 
 | ||||||
| 	# Update outstanding amt on against voucher |  | ||||||
| 	if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]: | 	if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]: | ||||||
|  | 		update_outstanding_amt_in_ref(against_voucher, against_voucher_type, bal) | ||||||
|  | 
 | ||||||
|  | def update_outstanding_amt_in_ref(against_voucher, against_voucher_type, bal): | ||||||
|  | 	data = [] | ||||||
|  | 	# Update outstanding amt on against voucher | ||||||
|  | 	if against_voucher_type == "Fees": | ||||||
| 		ref_doc = frappe.get_doc(against_voucher_type, against_voucher) | 		ref_doc = frappe.get_doc(against_voucher_type, against_voucher) | ||||||
| 		ref_doc.db_set('outstanding_amount', bal) | 		ref_doc.db_set('outstanding_amount', bal) | ||||||
| 		ref_doc.set_status(update=True) | 		ref_doc.set_status(update=True) | ||||||
|  | 		return | ||||||
|  | 	elif against_voucher_type == "Purchase Invoice": | ||||||
|  | 		from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_status | ||||||
|  | 		data = frappe.db.get_value(against_voucher_type, against_voucher,  | ||||||
|  | 			["name as purchase_invoice", "outstanding_amount",  | ||||||
|  | 			"is_return", "due_date", "docstatus"]) | ||||||
|  | 	elif against_voucher_type == "Sales Invoice": | ||||||
|  | 		from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_status | ||||||
|  | 		data = frappe.db.get_value(against_voucher_type, against_voucher,  | ||||||
|  | 			["name as sales_invoice", "outstanding_amount", "is_discounted",  | ||||||
|  | 			"is_return", "due_date", "docstatus"]) | ||||||
|  | 
 | ||||||
|  | 	precision = frappe.get_precision(against_voucher_type, "outstanding_amount") | ||||||
|  | 	data = list(data) | ||||||
|  | 	data.append(precision) | ||||||
|  | 	status = get_status(data) | ||||||
|  | 	frappe.db.set_value(against_voucher_type, against_voucher, { | ||||||
|  | 		'outstanding_amount': bal, | ||||||
|  | 		'status': status | ||||||
|  | 	}) | ||||||
| 
 | 
 | ||||||
| def validate_frozen_account(account, adv_adj=None): | def validate_frozen_account(account, adv_adj=None): | ||||||
| 	frozen_account = frappe.db.get_value("Account", account, "freeze_account") | 	frozen_account = frappe.db.get_value("Account", account, "freeze_account") | ||||||
| @ -274,6 +299,9 @@ def update_against_account(voucher_type, voucher_no): | |||||||
| 		if d.against != new_against: | 		if d.against != new_against: | ||||||
| 			frappe.db.set_value("GL Entry", d.name, "against", new_against) | 			frappe.db.set_value("GL Entry", d.name, "against", new_against) | ||||||
| 
 | 
 | ||||||
|  | def on_doctype_update(): | ||||||
|  | 	frappe.db.add_index("GL Entry", ["against_voucher_type", "against_voucher"]) | ||||||
|  | 	frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"]) | ||||||
| 
 | 
 | ||||||
| def rename_gle_sle_docs(): | def rename_gle_sle_docs(): | ||||||
| 	for doctype in ["GL Entry", "Stock Ledger Entry"]: | 	for doctype in ["GL Entry", "Stock Ledger Entry"]: | ||||||
|  | |||||||
| @ -125,6 +125,27 @@ class PurchaseInvoice(BuyingController): | |||||||
| 			else: | 			else: | ||||||
| 				self.remarks = _("No Remarks") | 				self.remarks = _("No Remarks") | ||||||
| 
 | 
 | ||||||
|  | 	def set_status(self, update=False, status=None, update_modified=True): | ||||||
|  | 		if self.is_new(): | ||||||
|  | 			if self.get('amended_from'): | ||||||
|  | 				self.status = 'Draft' | ||||||
|  | 			return | ||||||
|  | 
 | ||||||
|  | 		if not status: | ||||||
|  | 			precision = self.precision("outstanding_amount") | ||||||
|  | 			args = [ | ||||||
|  | 				self.name, | ||||||
|  | 				self.outstanding_amount, | ||||||
|  | 				self.is_return,  | ||||||
|  | 				self.due_date,  | ||||||
|  | 				self.docstatus, | ||||||
|  | 				precision | ||||||
|  | 			] | ||||||
|  | 			status = get_status(args) | ||||||
|  | 
 | ||||||
|  | 		if update: | ||||||
|  | 			self.db_set('status', status, update_modified = update_modified) | ||||||
|  | 
 | ||||||
| 	def set_missing_values(self, for_validate=False): | 	def set_missing_values(self, for_validate=False): | ||||||
| 		if not self.credit_to: | 		if not self.credit_to: | ||||||
| 			self.credit_to = get_party_account("Supplier", self.supplier, self.company) | 			self.credit_to = get_party_account("Supplier", self.supplier, self.company) | ||||||
| @ -1007,6 +1028,34 @@ class PurchaseInvoice(BuyingController): | |||||||
| 		# calculate totals again after applying TDS | 		# calculate totals again after applying TDS | ||||||
| 		self.calculate_taxes_and_totals() | 		self.calculate_taxes_and_totals() | ||||||
| 
 | 
 | ||||||
|  | def get_status(*args): | ||||||
|  | 	purchase_invoice, outstanding_amount, is_return, due_date, docstatus, precision = args[0] | ||||||
|  | 
 | ||||||
|  | 	outstanding_amount = flt(outstanding_amount, precision) | ||||||
|  | 	due_date = getdate(due_date) | ||||||
|  | 	now_date = getdate() | ||||||
|  | 
 | ||||||
|  | 	if docstatus == 2: | ||||||
|  | 		status = "Cancelled" | ||||||
|  | 	elif docstatus == 1: | ||||||
|  | 		if outstanding_amount > 0 and due_date < now_date: | ||||||
|  | 			status = "Overdue" | ||||||
|  | 		elif outstanding_amount > 0 and due_date >= now_date: | ||||||
|  | 			status = "Unpaid" | ||||||
|  | 		#Check if outstanding amount is 0 due to debit note issued against invoice | ||||||
|  | 		elif outstanding_amount <= 0 and is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': purchase_invoice, 'docstatus': 1}): | ||||||
|  | 			status = "Debit Note Issued" | ||||||
|  | 		elif is_return == 1: | ||||||
|  | 			status = "Return" | ||||||
|  | 		elif outstanding_amount <=0: | ||||||
|  | 			status = "Paid" | ||||||
|  | 		else: | ||||||
|  | 			status = "Submitted" | ||||||
|  | 	else: | ||||||
|  | 		status = "Draft" | ||||||
|  | 	 | ||||||
|  | 	return status | ||||||
|  | 
 | ||||||
| def get_list_context(context=None): | def get_list_context(context=None): | ||||||
| 	from erpnext.controllers.website_list_for_contact import get_list_context | 	from erpnext.controllers.website_list_for_contact import get_list_context | ||||||
| 	list_context = get_list_context(context) | 	list_context = get_list_context(context) | ||||||
|  | |||||||
| @ -1217,62 +1217,83 @@ class SalesInvoice(SellingController): | |||||||
| 
 | 
 | ||||||
| 		self.set_missing_values(for_validate = True) | 		self.set_missing_values(for_validate = True) | ||||||
| 
 | 
 | ||||||
| 	def get_discounting_status(self): |  | ||||||
| 		status = None |  | ||||||
| 		if self.is_discounted: |  | ||||||
| 			invoice_discounting_list = frappe.db.sql(""" |  | ||||||
| 				select status |  | ||||||
| 				from `tabInvoice Discounting` id, `tabDiscounted Invoice` d |  | ||||||
| 				where |  | ||||||
| 					id.name = d.parent |  | ||||||
| 					and d.sales_invoice=%s |  | ||||||
| 					and id.docstatus=1 |  | ||||||
| 					and status in ('Disbursed', 'Settled') |  | ||||||
| 			""", self.name) |  | ||||||
| 			for d in invoice_discounting_list: |  | ||||||
| 				status = d[0] |  | ||||||
| 				if status == "Disbursed": |  | ||||||
| 					break |  | ||||||
| 		return status |  | ||||||
| 
 |  | ||||||
| 	def set_status(self, update=False, status=None, update_modified=True): | 	def set_status(self, update=False, status=None, update_modified=True): | ||||||
| 		if self.is_new(): | 		if self.is_new(): | ||||||
| 			if self.get('amended_from'): | 			if self.get('amended_from'): | ||||||
| 				self.status = 'Draft' | 				self.status = 'Draft' | ||||||
| 			return | 			return | ||||||
| 
 | 
 | ||||||
| 		precision = self.precision("outstanding_amount") |  | ||||||
| 		outstanding_amount = flt(self.outstanding_amount, precision) |  | ||||||
| 		due_date = getdate(self.due_date) |  | ||||||
| 		nowdate = getdate() |  | ||||||
| 		discountng_status = self.get_discounting_status() |  | ||||||
| 
 |  | ||||||
| 		if not status: | 		if not status: | ||||||
| 			if self.docstatus == 2: | 			precision = self.precision("outstanding_amount") | ||||||
| 				status = "Cancelled" | 			args = [ | ||||||
| 			elif self.docstatus == 1: | 				self.name, | ||||||
| 				if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed': | 				self.outstanding_amount, | ||||||
| 					self.status = "Overdue and Discounted" | 				self.is_discounted,  | ||||||
| 				elif outstanding_amount > 0 and due_date < nowdate: | 				self.is_return,  | ||||||
| 					self.status = "Overdue" | 				self.due_date,  | ||||||
| 				elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discountng_status=='Disbursed': | 				self.docstatus, | ||||||
| 					self.status = "Unpaid and Discounted" | 				precision, | ||||||
| 				elif outstanding_amount > 0 and due_date >= nowdate: | 			] | ||||||
| 					self.status = "Unpaid" | 			status = get_status(args) | ||||||
| 				#Check if outstanding amount is 0 due to credit note issued against invoice |  | ||||||
| 				elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): |  | ||||||
| 					self.status = "Credit Note Issued" |  | ||||||
| 				elif self.is_return == 1: |  | ||||||
| 					self.status = "Return" |  | ||||||
| 				elif outstanding_amount<=0: |  | ||||||
| 					self.status = "Paid" |  | ||||||
| 				else: |  | ||||||
| 					self.status = "Submitted" |  | ||||||
| 			else: |  | ||||||
| 				self.status = "Draft" |  | ||||||
| 
 | 
 | ||||||
| 		if update: | 		if update: | ||||||
| 			self.db_set('status', self.status, update_modified = update_modified) | 			self.db_set('status', status, update_modified = update_modified) | ||||||
|  | 
 | ||||||
|  | def get_discounting_status(sales_invoice): | ||||||
|  | 	status = None | ||||||
|  | 
 | ||||||
|  | 	invoice_discounting_list = frappe.db.sql(""" | ||||||
|  | 		select status | ||||||
|  | 		from `tabInvoice Discounting` id, `tabDiscounted Invoice` d | ||||||
|  | 		where | ||||||
|  | 			id.name = d.parent | ||||||
|  | 			and d.sales_invoice=%s | ||||||
|  | 			and id.docstatus=1 | ||||||
|  | 			and status in ('Disbursed', 'Settled') | ||||||
|  | 	""", sales_invoice) | ||||||
|  | 
 | ||||||
|  | 	for d in invoice_discounting_list: | ||||||
|  | 		status = d[0] | ||||||
|  | 		if status == "Disbursed": | ||||||
|  | 			break | ||||||
|  | 
 | ||||||
|  | 	return status | ||||||
|  | 
 | ||||||
|  | def get_status(*args): | ||||||
|  | 	sales_invoice, outstanding_amount, is_discounted, is_return, due_date, docstatus, precision = args[0] | ||||||
|  | 	 | ||||||
|  | 	discounting_status = None | ||||||
|  | 	if is_discounted: | ||||||
|  | 		discounting_status = get_discounting_status(sales_invoice) | ||||||
|  | 
 | ||||||
|  | 	outstanding_amount = flt(outstanding_amount, precision) | ||||||
|  | 	due_date = getdate(due_date) | ||||||
|  | 	now_date = getdate() | ||||||
|  | 
 | ||||||
|  | 	if docstatus == 2: | ||||||
|  | 		status = "Cancelled" | ||||||
|  | 	elif docstatus == 1: | ||||||
|  | 		if outstanding_amount > 0 and due_date < now_date and is_discounted and discounting_status=='Disbursed': | ||||||
|  | 			status = "Overdue and Discounted" | ||||||
|  | 		elif outstanding_amount > 0 and due_date < now_date: | ||||||
|  | 			status = "Overdue" | ||||||
|  | 		elif outstanding_amount > 0 and due_date >= now_date and is_discounted and discounting_status=='Disbursed': | ||||||
|  | 			status = "Unpaid and Discounted" | ||||||
|  | 		elif outstanding_amount > 0 and due_date >= now_date: | ||||||
|  | 			status = "Unpaid" | ||||||
|  | 		#Check if outstanding amount is 0 due to credit note issued against invoice | ||||||
|  | 		elif outstanding_amount <= 0 and is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': sales_invoice, 'docstatus': 1}): | ||||||
|  | 			status = "Credit Note Issued" | ||||||
|  | 		elif is_return == 1: | ||||||
|  | 			status = "Return" | ||||||
|  | 		elif outstanding_amount <=0: | ||||||
|  | 			status = "Paid" | ||||||
|  | 		else: | ||||||
|  | 			status = "Submitted" | ||||||
|  | 	else: | ||||||
|  | 		status = "Draft" | ||||||
|  | 	 | ||||||
|  | 	return status | ||||||
| 
 | 
 | ||||||
| def validate_inter_company_party(doctype, party, company, inter_company_reference): | def validate_inter_company_party(doctype, party, company, inter_company_reference): | ||||||
| 	if not party: | 	if not party: | ||||||
| @ -1444,7 +1465,7 @@ def get_inter_company_details(doc, doctype): | |||||||
| 		"party": party, | 		"party": party, | ||||||
| 		"company": company | 		"company": company | ||||||
| 	} | 	} | ||||||
| 
 |    | ||||||
| def get_internal_party(parties, link_doctype, doc): | def get_internal_party(parties, link_doctype, doc): | ||||||
| 	if len(parties) == 1: | 	if len(parties) == 1: | ||||||
| 			party = parties[0].name | 			party = parties[0].name | ||||||
|  | |||||||
| @ -140,8 +140,11 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): | |||||||
| 	gle = frappe.get_doc(args) | 	gle = frappe.get_doc(args) | ||||||
| 	gle.flags.ignore_permissions = 1 | 	gle.flags.ignore_permissions = 1 | ||||||
| 	gle.flags.from_repost = from_repost | 	gle.flags.from_repost = from_repost | ||||||
| 	gle.insert() | 	gle.validate() | ||||||
|  | 	gle.flags.ignore_permissions = True | ||||||
|  | 	gle.db_insert() | ||||||
| 	gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost) | 	gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost) | ||||||
|  | 	gle.flags.ignore_validate = True | ||||||
| 	gle.submit() | 	gle.submit() | ||||||
| 
 | 
 | ||||||
| def validate_account_for_perpetual_inventory(gl_map): | def validate_account_for_perpetual_inventory(gl_map): | ||||||
|  | |||||||
| @ -29,7 +29,7 @@ def get_data(filters): | |||||||
| 
 | 
 | ||||||
| 		row.update(next(asset for asset in assets if asset["asset_category"] == asset_category.get("asset_category", ""))) | 		row.update(next(asset for asset in assets if asset["asset_category"] == asset_category.get("asset_category", ""))) | ||||||
| 		row.accumulated_depreciation_as_on_to_date = (flt(row.accumulated_depreciation_as_on_from_date) + | 		row.accumulated_depreciation_as_on_to_date = (flt(row.accumulated_depreciation_as_on_from_date) + | ||||||
| 				flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated)) | 				flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated_during_the_period)) | ||||||
| 
 | 
 | ||||||
| 		row.net_asset_value_as_on_from_date = (flt(row.cost_as_on_from_date) - | 		row.net_asset_value_as_on_from_date = (flt(row.cost_as_on_from_date) - | ||||||
| 				flt(row.accumulated_depreciation_as_on_from_date)) | 				flt(row.accumulated_depreciation_as_on_from_date)) | ||||||
| @ -86,7 +86,6 @@ def get_asset_categories(filters): | |||||||
| 		group by asset_category | 		group by asset_category | ||||||
| 	""", {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1) | 	""", {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def get_assets(filters): | def get_assets(filters): | ||||||
| 	return frappe.db.sql(""" | 	return frappe.db.sql(""" | ||||||
| 		SELECT results.asset_category, | 		SELECT results.asset_category, | ||||||
| @ -94,9 +93,7 @@ def get_assets(filters): | |||||||
| 			   sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period, | 			   sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period, | ||||||
| 			   sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period | 			   sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period | ||||||
| 		from (SELECT a.asset_category, | 		from (SELECT a.asset_category, | ||||||
| 				   ifnull(sum(a.opening_accumulated_depreciation + | 				   ifnull(sum(case when ds.schedule_date < %(from_date)s then | ||||||
| 							  case when ds.schedule_date < %(from_date)s and |  | ||||||
| 										(ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then |  | ||||||
| 								   ds.depreciation_amount | 								   ds.depreciation_amount | ||||||
| 							  else | 							  else | ||||||
| 								   0 | 								   0 | ||||||
| @ -107,7 +104,6 @@ def get_assets(filters): | |||||||
| 							  else | 							  else | ||||||
| 								   0 | 								   0 | ||||||
| 							  end), 0) as depreciation_eliminated_during_the_period, | 							  end), 0) as depreciation_eliminated_during_the_period, | ||||||
| 
 |  | ||||||
| 				   ifnull(sum(case when ds.schedule_date >= %(from_date)s and ds.schedule_date <= %(to_date)s | 				   ifnull(sum(case when ds.schedule_date >= %(from_date)s and ds.schedule_date <= %(to_date)s | ||||||
| 										and (ifnull(a.disposal_date, 0) = 0 or ds.schedule_date <= a.disposal_date) then | 										and (ifnull(a.disposal_date, 0) = 0 or ds.schedule_date <= a.disposal_date) then | ||||||
| 								   ds.depreciation_amount | 								   ds.depreciation_amount | ||||||
| @ -120,7 +116,8 @@ def get_assets(filters): | |||||||
| 			union | 			union | ||||||
| 			SELECT a.asset_category, | 			SELECT a.asset_category, | ||||||
| 				   ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 | 				   ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 | ||||||
| 										and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then | 										and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s)  | ||||||
|  | 										then | ||||||
| 									0 | 									0 | ||||||
| 							   else | 							   else | ||||||
| 									a.opening_accumulated_depreciation | 									a.opening_accumulated_depreciation | ||||||
| @ -133,7 +130,6 @@ def get_assets(filters): | |||||||
| 				   0 as depreciation_amount_during_the_period | 				   0 as depreciation_amount_during_the_period | ||||||
| 			from `tabAsset` a | 			from `tabAsset` a | ||||||
| 			where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s | 			where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s | ||||||
| 			and not exists(select * from `tabDepreciation Schedule` ds where a.name = ds.parent) |  | ||||||
| 			group by a.asset_category) as results | 			group by a.asset_category) as results | ||||||
| 		group by results.asset_category | 		group by results.asset_category | ||||||
| 		""", {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1) | 		""", {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1) | ||||||
|  | |||||||
| @ -306,10 +306,6 @@ def get_conditions(filters): | |||||||
| 
 | 
 | ||||||
| def get_items(filters, additional_query_columns): | def get_items(filters, additional_query_columns): | ||||||
| 	conditions = get_conditions(filters) | 	conditions = get_conditions(filters) | ||||||
| 	match_conditions = frappe.build_match_conditions("Purchase Invoice") |  | ||||||
| 
 |  | ||||||
| 	if match_conditions: |  | ||||||
| 		match_conditions = " and {0} ".format(match_conditions) |  | ||||||
| 
 | 
 | ||||||
| 	if additional_query_columns: | 	if additional_query_columns: | ||||||
| 		additional_query_columns = ', ' + ', '.join(additional_query_columns) | 		additional_query_columns = ', ' + ', '.join(additional_query_columns) | ||||||
| @ -327,8 +323,8 @@ def get_items(filters, additional_query_columns): | |||||||
| 			`tabPurchase Invoice`.supplier_name, `tabPurchase Invoice`.mode_of_payment {0} | 			`tabPurchase Invoice`.supplier_name, `tabPurchase Invoice`.mode_of_payment {0} | ||||||
| 		from `tabPurchase Invoice`, `tabPurchase Invoice Item` | 		from `tabPurchase Invoice`, `tabPurchase Invoice Item` | ||||||
| 		where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and | 		where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and | ||||||
| 		`tabPurchase Invoice`.docstatus = 1 %s %s | 		`tabPurchase Invoice`.docstatus = 1 %s | ||||||
| 	""".format(additional_query_columns) % (conditions, match_conditions), filters, as_dict=1) | 	""".format(additional_query_columns) % (conditions), filters, as_dict=1) | ||||||
| 
 | 
 | ||||||
| def get_aii_accounts(): | def get_aii_accounts(): | ||||||
| 	return dict(frappe.db.sql("select name, stock_received_but_not_billed from tabCompany")) | 	return dict(frappe.db.sql("select name, stock_received_but_not_billed from tabCompany")) | ||||||
|  | |||||||
| @ -119,7 +119,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum | |||||||
| 		add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns) | 		add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns) | ||||||
| 		data.append(total_row_map.get('total_row')) | 		data.append(total_row_map.get('total_row')) | ||||||
| 		skip_total_row = 1 | 		skip_total_row = 1 | ||||||
| 	 | 
 | ||||||
| 	return columns, data, None, None, None, skip_total_row | 	return columns, data, None, None, None, skip_total_row | ||||||
| 
 | 
 | ||||||
| def get_columns(additional_table_columns, filters): | def get_columns(additional_table_columns, filters): | ||||||
| @ -370,10 +370,6 @@ def get_group_by_conditions(filters, doctype): | |||||||
| 
 | 
 | ||||||
| def get_items(filters, additional_query_columns): | def get_items(filters, additional_query_columns): | ||||||
| 	conditions = get_conditions(filters) | 	conditions = get_conditions(filters) | ||||||
| 	match_conditions = frappe.build_match_conditions("Sales Invoice") |  | ||||||
| 
 |  | ||||||
| 	if match_conditions: |  | ||||||
| 		match_conditions = " and {0} ".format(match_conditions) |  | ||||||
| 
 | 
 | ||||||
| 	if additional_query_columns: | 	if additional_query_columns: | ||||||
| 		additional_query_columns = ', ' + ', '.join(additional_query_columns) | 		additional_query_columns = ', ' + ', '.join(additional_query_columns) | ||||||
| @ -394,8 +390,8 @@ def get_items(filters, additional_query_columns): | |||||||
| 			`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0} | 			`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0} | ||||||
| 		from `tabSales Invoice`, `tabSales Invoice Item` | 		from `tabSales Invoice`, `tabSales Invoice Item` | ||||||
| 		where `tabSales Invoice`.name = `tabSales Invoice Item`.parent | 		where `tabSales Invoice`.name = `tabSales Invoice Item`.parent | ||||||
| 			and `tabSales Invoice`.docstatus = 1 {1} {2} | 			and `tabSales Invoice`.docstatus = 1 {1} | ||||||
| 		""".format(additional_query_columns or '', conditions, match_conditions), filters, as_dict=1) #nosec | 		""".format(additional_query_columns or '', conditions), filters, as_dict=1) #nosec | ||||||
| 
 | 
 | ||||||
| def get_delivery_notes_against_sales_order(item_list): | def get_delivery_notes_against_sales_order(item_list): | ||||||
| 	so_dn_map = frappe._dict() | 	so_dn_map = frappe._dict() | ||||||
|  | |||||||
| @ -12,7 +12,6 @@ from erpnext.stock.doctype.item.item import validate_end_of_life | |||||||
| 
 | 
 | ||||||
| def update_last_purchase_rate(doc, is_submit): | def update_last_purchase_rate(doc, is_submit): | ||||||
| 	"""updates last_purchase_rate in item table for each item""" | 	"""updates last_purchase_rate in item table for each item""" | ||||||
| 
 |  | ||||||
| 	import frappe.utils | 	import frappe.utils | ||||||
| 	this_purchase_date = frappe.utils.getdate(doc.get('posting_date') or doc.get('transaction_date')) | 	this_purchase_date = frappe.utils.getdate(doc.get('posting_date') or doc.get('transaction_date')) | ||||||
| 
 | 
 | ||||||
| @ -23,7 +22,7 @@ def update_last_purchase_rate(doc, is_submit): | |||||||
| 		# compare last purchase date and this transaction's date | 		# compare last purchase date and this transaction's date | ||||||
| 		last_purchase_rate = None | 		last_purchase_rate = None | ||||||
| 		if last_purchase_details and \ | 		if last_purchase_details and \ | ||||||
| 				(last_purchase_details.purchase_date > this_purchase_date): | 				(doc.get('docstatus') == 2 or last_purchase_details.purchase_date > this_purchase_date): | ||||||
| 			last_purchase_rate = last_purchase_details['base_net_rate'] | 			last_purchase_rate = last_purchase_details['base_net_rate'] | ||||||
| 		elif is_submit == 1: | 		elif is_submit == 1: | ||||||
| 			# even if this transaction is the latest one, it should be submitted | 			# even if this transaction is the latest one, it should be submitted | ||||||
|  | |||||||
| @ -7,6 +7,13 @@ def get_data(): | |||||||
| 			"label": _("Purchasing"), | 			"label": _("Purchasing"), | ||||||
| 			"icon": "fa fa-star", | 			"icon": "fa fa-star", | ||||||
| 			"items": [ | 			"items": [ | ||||||
|  | 				{ | ||||||
|  | 					"type": "doctype", | ||||||
|  | 					"name": "Material Request", | ||||||
|  | 					"onboard": 1, | ||||||
|  | 					"dependencies": ["Item"], | ||||||
|  | 					"description": _("Request for purchase."), | ||||||
|  | 				}, | ||||||
| 				{ | 				{ | ||||||
| 					"type": "doctype", | 					"type": "doctype", | ||||||
| 					"name": "Purchase Order", | 					"name": "Purchase Order", | ||||||
| @ -20,13 +27,6 @@ def get_data(): | |||||||
| 					"onboard": 1, | 					"onboard": 1, | ||||||
| 					"dependencies": ["Item", "Supplier"] | 					"dependencies": ["Item", "Supplier"] | ||||||
| 				}, | 				}, | ||||||
| 				{ |  | ||||||
| 					"type": "doctype", |  | ||||||
| 					"name": "Material Request", |  | ||||||
| 					"onboard": 1, |  | ||||||
| 					"dependencies": ["Item"], |  | ||||||
| 					"description": _("Request for purchase."), |  | ||||||
| 				}, |  | ||||||
| 				{ | 				{ | ||||||
| 					"type": "doctype", | 					"type": "doctype", | ||||||
| 					"name": "Request for Quotation", | 					"name": "Request for Quotation", | ||||||
| @ -63,6 +63,11 @@ def get_data(): | |||||||
| 					"name": "Price List", | 					"name": "Price List", | ||||||
| 					"description": _("Price List master.") | 					"description": _("Price List master.") | ||||||
| 				}, | 				}, | ||||||
|  | 				{ | ||||||
|  | 					"type": "doctype", | ||||||
|  | 					"name": "Pricing Rule", | ||||||
|  | 					"description": _("Rules for applying pricing and discount.") | ||||||
|  | 				}, | ||||||
| 				{ | 				{ | ||||||
| 					"type": "doctype", | 					"type": "doctype", | ||||||
| 					"name": "Product Bundle", | 					"name": "Product Bundle", | ||||||
| @ -80,11 +85,6 @@ def get_data(): | |||||||
| 					"type": "doctype", | 					"type": "doctype", | ||||||
| 					"name": "Promotional Scheme", | 					"name": "Promotional Scheme", | ||||||
| 					"description": _("Rules for applying different promotional schemes.") | 					"description": _("Rules for applying different promotional schemes.") | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					"type": "doctype", |  | ||||||
| 					"name": "Pricing Rule", |  | ||||||
| 					"description": _("Rules for applying pricing and discount.") |  | ||||||
| 				} | 				} | ||||||
| 			] | 			] | ||||||
| 		}, | 		}, | ||||||
| @ -149,13 +149,6 @@ def get_data(): | |||||||
| 					"reference_doctype": "Purchase Order", | 					"reference_doctype": "Purchase Order", | ||||||
| 					"onboard": 1 | 					"onboard": 1 | ||||||
| 				}, | 				}, | ||||||
| 				{ |  | ||||||
| 					"type": "report", |  | ||||||
| 					"is_query_report": True, |  | ||||||
| 					"name": "Supplier-Wise Sales Analytics", |  | ||||||
| 					"reference_doctype": "Stock Ledger Entry", |  | ||||||
| 					"onboard": 1 |  | ||||||
| 				}, |  | ||||||
| 				{ | 				{ | ||||||
| 					"type": "report", | 					"type": "report", | ||||||
| 					"is_query_report": True, | 					"is_query_report": True, | ||||||
| @ -177,6 +170,16 @@ def get_data(): | |||||||
| 					"reference_doctype": "Material Request", | 					"reference_doctype": "Material Request", | ||||||
| 					"onboard": 1, | 					"onboard": 1, | ||||||
| 				}, | 				}, | ||||||
|  | 				{ | ||||||
|  | 					"type": "report", | ||||||
|  | 					"is_query_report": True, | ||||||
|  | 					"name": "Address And Contacts", | ||||||
|  | 					"label": _("Supplier Addresses And Contacts"), | ||||||
|  | 					"reference_doctype": "Address", | ||||||
|  | 					"route_options": { | ||||||
|  | 						"party_type": "Supplier" | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
| 			] | 			] | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| @ -226,18 +229,15 @@ def get_data(): | |||||||
| 				{ | 				{ | ||||||
| 					"type": "report", | 					"type": "report", | ||||||
| 					"is_query_report": True, | 					"is_query_report": True, | ||||||
| 					"name": "Material Requests for which Supplier Quotations are not created", | 					"name": "Supplier-Wise Sales Analytics", | ||||||
| 					"reference_doctype": "Material Request" | 					"reference_doctype": "Stock Ledger Entry", | ||||||
|  | 					"onboard": 1 | ||||||
| 				}, | 				}, | ||||||
| 				{ | 				{ | ||||||
| 					"type": "report", | 					"type": "report", | ||||||
| 					"is_query_report": True, | 					"is_query_report": True, | ||||||
| 					"name": "Address And Contacts", | 					"name": "Material Requests for which Supplier Quotations are not created", | ||||||
| 					"label": _("Supplier Addresses And Contacts"), | 					"reference_doctype": "Material Request" | ||||||
| 					"reference_doctype": "Address", |  | ||||||
| 					"route_options": { |  | ||||||
| 						"party_type": "Supplier" |  | ||||||
| 					} |  | ||||||
| 				} | 				} | ||||||
| 			] | 			] | ||||||
| 		}, | 		}, | ||||||
|  | |||||||
| @ -1200,6 +1200,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil | |||||||
| 				child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d.get("item_code")) | 				child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d.get("item_code")) | ||||||
| 		else: | 		else: | ||||||
| 			child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) | 			child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) | ||||||
|  | 			if flt(child_item.get("rate")) == flt(d.get("rate")) and flt(child_item.get("qty")) == flt(d.get("qty")): | ||||||
|  | 				continue | ||||||
| 
 | 
 | ||||||
| 		if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): | 		if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): | ||||||
| 			frappe.throw(_("Cannot set quantity less than delivered quantity")) | 			frappe.throw(_("Cannot set quantity less than delivered quantity")) | ||||||
|  | |||||||
| @ -44,17 +44,6 @@ status_map = { | |||||||
| 		["Closed", "eval:self.status=='Closed'"], | 		["Closed", "eval:self.status=='Closed'"], | ||||||
| 		["On Hold", "eval:self.status=='On Hold'"], | 		["On Hold", "eval:self.status=='On Hold'"], | ||||||
| 	], | 	], | ||||||
| 	"Purchase Invoice": [ |  | ||||||
| 		["Draft", None], |  | ||||||
| 		["Submitted", "eval:self.docstatus==1"], |  | ||||||
| 		["Paid", "eval:self.outstanding_amount==0 and self.docstatus==1"], |  | ||||||
| 		["Return", "eval:self.is_return==1 and self.docstatus==1"], |  | ||||||
| 		["Debit Note Issued", |  | ||||||
| 		 "eval:self.outstanding_amount <= 0 and self.docstatus==1 and self.is_return==0 and get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1})"], |  | ||||||
| 		["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"], |  | ||||||
| 		["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"], |  | ||||||
| 		["Cancelled", "eval:self.docstatus==2"], |  | ||||||
| 	], |  | ||||||
| 	"Purchase Order": [ | 	"Purchase Order": [ | ||||||
| 		["Draft", None], | 		["Draft", None], | ||||||
| 		["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"], | 		["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"], | ||||||
|  | |||||||
| @ -429,7 +429,7 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f | |||||||
| 	for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no | 	for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no | ||||||
| 		from `tabStock Ledger Entry` sle | 		from `tabStock Ledger Entry` sle | ||||||
| 		where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition} | 		where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition} | ||||||
| 		order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc""".format(condition=condition), | 		order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition), | ||||||
| 		tuple([posting_date, posting_time] + values), as_dict=True): | 		tuple([posting_date, posting_time] + values), as_dict=True): | ||||||
| 			future_stock_vouchers.append([d.voucher_type, d.voucher_no]) | 			future_stock_vouchers.append([d.voucher_type, d.voucher_no]) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -11,5 +11,12 @@ frappe.ui.form.on('Student Admission', { | |||||||
| 
 | 
 | ||||||
| 	academic_year: function(frm) { | 	academic_year: function(frm) { | ||||||
| 		frm.trigger("program"); | 		frm.trigger("program"); | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	admission_end_date: function(frm) { | ||||||
|  | 		if(frm.doc.admission_end_date && frm.doc.admission_end_date <= frm.doc.admission_start_date){ | ||||||
|  | 			frm.set_value("admission_end_date", ""); | ||||||
|  | 			frappe.throw(__("Admission End Date should be greater than Admission Start Date.")); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -39,19 +39,21 @@ class AdditionalSalary(Document): | |||||||
| 		return amount_per_day * no_of_days | 		return amount_per_day * no_of_days | ||||||
| 
 | 
 | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
| def get_additional_salary_component(employee, start_date, end_date): | def get_additional_salary_component(employee, start_date, end_date, component_type): | ||||||
| 	additional_components = frappe.db.sql(""" | 	additional_components = frappe.db.sql(""" | ||||||
| 		select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date | 		select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date | ||||||
| 		from `tabAdditional Salary` | 		from `tabAdditional Salary` | ||||||
| 		where employee=%(employee)s | 		where employee=%(employee)s | ||||||
| 			and docstatus = 1 | 			and docstatus = 1 | ||||||
| 			and payroll_date between %(from_date)s and %(to_date)s | 			and payroll_date between %(from_date)s and %(to_date)s | ||||||
|  | 			and type = %(component_type)s | ||||||
| 		group by salary_component, overwrite_salary_structure_amount | 		group by salary_component, overwrite_salary_structure_amount | ||||||
| 		order by salary_component, overwrite_salary_structure_amount | 		order by salary_component, overwrite_salary_structure_amount | ||||||
| 	""", { | 	""", { | ||||||
| 		'employee': employee, | 		'employee': employee, | ||||||
| 		'from_date': start_date, | 		'from_date': start_date, | ||||||
| 		'to_date': end_date | 		'to_date': end_date, | ||||||
|  | 		'component_type': "Earning" if component_type == "earnings" else "Deduction" | ||||||
| 	}, as_dict=1) | 	}, as_dict=1) | ||||||
| 
 | 
 | ||||||
| 	additional_components_list = [] | 	additional_components_list = [] | ||||||
|  | |||||||
| @ -64,6 +64,9 @@ class LeaveEncashment(Document): | |||||||
| 
 | 
 | ||||||
| 		allocation = self.get_leave_allocation() | 		allocation = self.get_leave_allocation() | ||||||
| 
 | 
 | ||||||
|  | 		if not allocation: | ||||||
|  | 			frappe.throw(_("No Leaves Allocated to Employee: {0} for Leave Type: {1}").format(self.employee, self.leave_type)) | ||||||
|  | 
 | ||||||
| 		self.leave_balance = allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count\ | 		self.leave_balance = allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count\ | ||||||
| 			- get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) | 			- get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) | ||||||
| 
 | 
 | ||||||
| @ -116,4 +119,4 @@ def create_leave_encashment(leave_allocation): | |||||||
| 			leave_type=allocation.leave_type, | 			leave_type=allocation.leave_type, | ||||||
| 			encashment_date=allocation.to_date | 			encashment_date=allocation.to_date | ||||||
| 		)) | 		)) | ||||||
| 		leave_encashment.insert(ignore_permissions=True) | 		leave_encashment.insert(ignore_permissions=True) | ||||||
|  | |||||||
| @ -299,9 +299,11 @@ class SalarySlip(TransactionBase): | |||||||
| 
 | 
 | ||||||
| 	def calculate_net_pay(self): | 	def calculate_net_pay(self): | ||||||
| 		if self.salary_structure: | 		if self.salary_structure: | ||||||
| 			self.calculate_component_amounts() | 			self.calculate_component_amounts("earnings") | ||||||
| 
 |  | ||||||
| 		self.gross_pay = self.get_component_totals("earnings") | 		self.gross_pay = self.get_component_totals("earnings") | ||||||
|  | 
 | ||||||
|  | 		if self.salary_structure: | ||||||
|  | 			self.calculate_component_amounts("deductions") | ||||||
| 		self.total_deduction = self.get_component_totals("deductions") | 		self.total_deduction = self.get_component_totals("deductions") | ||||||
| 
 | 
 | ||||||
| 		self.set_loan_repayment() | 		self.set_loan_repayment() | ||||||
| @ -309,25 +311,27 @@ class SalarySlip(TransactionBase): | |||||||
| 		self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment)) | 		self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment)) | ||||||
| 		self.rounded_total = rounded(self.net_pay) | 		self.rounded_total = rounded(self.net_pay) | ||||||
| 
 | 
 | ||||||
| 	def calculate_component_amounts(self): | 	def calculate_component_amounts(self, component_type): | ||||||
| 		if not getattr(self, '_salary_structure_doc', None): | 		if not getattr(self, '_salary_structure_doc', None): | ||||||
| 			self._salary_structure_doc = frappe.get_doc('Salary Structure', self.salary_structure) | 			self._salary_structure_doc = frappe.get_doc('Salary Structure', self.salary_structure) | ||||||
| 
 | 
 | ||||||
| 		payroll_period = get_payroll_period(self.start_date, self.end_date, self.company) | 		payroll_period = get_payroll_period(self.start_date, self.end_date, self.company) | ||||||
| 
 | 
 | ||||||
| 		self.add_structure_components() | 		self.add_structure_components(component_type) | ||||||
| 		self.add_employee_benefits(payroll_period) | 		self.add_additional_salary_components(component_type) | ||||||
| 		self.add_additional_salary_components() | 		if component_type == "earnings": | ||||||
| 		self.add_tax_components(payroll_period) | 			self.add_employee_benefits(payroll_period) | ||||||
| 		self.set_component_amounts_based_on_payment_days() | 		else: | ||||||
|  | 			self.add_tax_components(payroll_period) | ||||||
| 
 | 
 | ||||||
| 	def add_structure_components(self): | 		self.set_component_amounts_based_on_payment_days(component_type) | ||||||
|  | 
 | ||||||
|  | 	def add_structure_components(self, component_type): | ||||||
| 		data = self.get_data_for_eval() | 		data = self.get_data_for_eval() | ||||||
| 		for key in ('earnings', 'deductions'): | 		for struct_row in self._salary_structure_doc.get(component_type): | ||||||
| 			for struct_row in self._salary_structure_doc.get(key): | 			amount = self.eval_condition_and_formula(struct_row, data) | ||||||
| 				amount = self.eval_condition_and_formula(struct_row, data) | 			if amount and struct_row.statistical_component == 0: | ||||||
| 				if amount and struct_row.statistical_component == 0: | 				self.update_component_row(struct_row, amount, component_type) | ||||||
| 					self.update_component_row(struct_row, amount, key) |  | ||||||
| 
 | 
 | ||||||
| 	def get_data_for_eval(self): | 	def get_data_for_eval(self): | ||||||
| 		'''Returns data for evaluating formula''' | 		'''Returns data for evaluating formula''' | ||||||
| @ -400,14 +404,15 @@ class SalarySlip(TransactionBase): | |||||||
| 						amount = last_benefit.amount | 						amount = last_benefit.amount | ||||||
| 						self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings") | 						self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings") | ||||||
| 
 | 
 | ||||||
| 	def add_additional_salary_components(self): | 	def add_additional_salary_components(self, component_type): | ||||||
| 		additional_components = get_additional_salary_component(self.employee, self.start_date, self.end_date) | 		additional_components = get_additional_salary_component(self.employee, | ||||||
|  | 			self.start_date, self.end_date, component_type) | ||||||
| 		if additional_components: | 		if additional_components: | ||||||
| 			for additional_component in additional_components: | 			for additional_component in additional_components: | ||||||
| 				amount = additional_component.amount | 				amount = additional_component.amount | ||||||
| 				overwrite = additional_component.overwrite | 				overwrite = additional_component.overwrite | ||||||
| 				key = "earnings" if additional_component.type == "Earning" else "deductions" | 				self.update_component_row(frappe._dict(additional_component.struct_row), amount, | ||||||
| 				self.update_component_row(frappe._dict(additional_component.struct_row), amount, key, overwrite=overwrite) | 					component_type, overwrite=overwrite) | ||||||
| 
 | 
 | ||||||
| 	def add_tax_components(self, payroll_period): | 	def add_tax_components(self, payroll_period): | ||||||
| 		# Calculate variable_based_on_taxable_salary after all components updated in salary slip | 		# Calculate variable_based_on_taxable_salary after all components updated in salary slip | ||||||
| @ -736,7 +741,7 @@ class SalarySlip(TransactionBase): | |||||||
| 				total += d.amount | 				total += d.amount | ||||||
| 		return total | 		return total | ||||||
| 
 | 
 | ||||||
| 	def set_component_amounts_based_on_payment_days(self): | 	def set_component_amounts_based_on_payment_days(self, component_type): | ||||||
| 		joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, | 		joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, | ||||||
| 			["date_of_joining", "relieving_date"]) | 			["date_of_joining", "relieving_date"]) | ||||||
| 
 | 
 | ||||||
| @ -746,9 +751,8 @@ class SalarySlip(TransactionBase): | |||||||
| 		if not joining_date: | 		if not joining_date: | ||||||
| 			frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name))) | 			frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name))) | ||||||
| 
 | 
 | ||||||
| 		for component_type in ("earnings", "deductions"): | 		for d in self.get(component_type): | ||||||
| 			for d in self.get(component_type): | 			d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0] | ||||||
| 				d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0] |  | ||||||
| 
 | 
 | ||||||
| 	def set_loan_repayment(self): | 	def set_loan_repayment(self): | ||||||
| 		self.set('loans', []) | 		self.set('loans', []) | ||||||
|  | |||||||
| @ -25,7 +25,6 @@ class TestSalaryStructure(unittest.TestCase): | |||||||
| 		make_employee("test_employee@salary.com") | 		make_employee("test_employee@salary.com") | ||||||
| 		make_employee("test_employee_2@salary.com") | 		make_employee("test_employee_2@salary.com") | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 	def make_holiday_list(self): | 	def make_holiday_list(self): | ||||||
| 		if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"): | 		if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"): | ||||||
| 			holiday_list = frappe.get_doc({ | 			holiday_list = frappe.get_doc({ | ||||||
| @ -38,6 +37,29 @@ class TestSalaryStructure(unittest.TestCase): | |||||||
| 			holiday_list.get_weekly_off_dates() | 			holiday_list.get_weekly_off_dates() | ||||||
| 			holiday_list.save() | 			holiday_list.save() | ||||||
| 
 | 
 | ||||||
|  | 	def test_salary_structure_deduction_based_on_gross_pay(self): | ||||||
|  | 
 | ||||||
|  | 		emp = make_employee("test_employee_3@salary.com") | ||||||
|  | 
 | ||||||
|  | 		sal_struct = make_salary_structure("Salary Structure 2", "Monthly", dont_submit = True) | ||||||
|  | 
 | ||||||
|  | 		sal_struct.earnings = [sal_struct.earnings[0]] | ||||||
|  | 		sal_struct.earnings[0].amount_based_on_formula = 1 | ||||||
|  | 		sal_struct.earnings[0].formula =  "base" | ||||||
|  | 
 | ||||||
|  | 		sal_struct.deductions = [sal_struct.deductions[0]] | ||||||
|  | 
 | ||||||
|  | 		sal_struct.deductions[0].amount_based_on_formula = 1 | ||||||
|  | 		sal_struct.deductions[0].condition = "gross_pay > 100" | ||||||
|  | 		sal_struct.deductions[0].formula =  "gross_pay * 0.2" | ||||||
|  | 
 | ||||||
|  | 		sal_struct.submit() | ||||||
|  | 
 | ||||||
|  | 		assignment = create_salary_structure_assignment(emp, "Salary Structure 2") | ||||||
|  | 		ss = make_salary_slip(sal_struct.name, employee = emp) | ||||||
|  | 
 | ||||||
|  | 		self.assertEqual(assignment.base * 0.2, ss.deductions[0].amount) | ||||||
|  | 
 | ||||||
| 	def test_amount_totals(self): | 	def test_amount_totals(self): | ||||||
| 		frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0) | 		frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0) | ||||||
| 		sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee_2@salary.com"}) | 		sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee_2@salary.com"}) | ||||||
|  | |||||||
| @ -75,7 +75,7 @@ class ShiftType(Document): | |||||||
| 		for date in dates: | 		for date in dates: | ||||||
| 			shift_details = get_employee_shift(employee, date, True) | 			shift_details = get_employee_shift(employee, date, True) | ||||||
| 			if shift_details and shift_details.shift_type.name == self.name: | 			if shift_details and shift_details.shift_type.name == self.name: | ||||||
| 				mark_attendance(employee, date, self.name, 'Absent') | 				mark_attendance(employee, date, 'Absent', self.name) | ||||||
| 
 | 
 | ||||||
| 	def get_assigned_employee(self, from_date=None, consider_default_shift=False): | 	def get_assigned_employee(self, from_date=None, consider_default_shift=False): | ||||||
| 		filters = {'date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'} | 		filters = {'date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'} | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| { | { | ||||||
|  |  "actions": [], | ||||||
|  "autoname": "ACC-LOAP-.YYYY.-.#####", |  "autoname": "ACC-LOAP-.YYYY.-.#####", | ||||||
|  "creation": "2019-08-29 17:46:49.201740", |  "creation": "2019-08-29 17:46:49.201740", | ||||||
|  "doctype": "DocType", |  "doctype": "DocType", | ||||||
| @ -122,7 +123,6 @@ | |||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|    "depends_on": "eval: doc.is_term_loan == 1", |    "depends_on": "eval: doc.is_term_loan == 1", | ||||||
|    "fetch_from": "loan_type.repayment_method", |  | ||||||
|    "fetch_if_empty": 1, |    "fetch_if_empty": 1, | ||||||
|    "fieldname": "repayment_method", |    "fieldname": "repayment_method", | ||||||
|    "fieldtype": "Select", |    "fieldtype": "Select", | ||||||
| @ -213,7 +213,8 @@ | |||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "is_submittable": 1, |  "is_submittable": 1, | ||||||
|  "modified": "2019-10-24 10:32:03.740558", |  "links": [], | ||||||
|  |  "modified": "2020-03-01 10:21:44.413353", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Loan Management", |  "module": "Loan Management", | ||||||
|  "name": "Loan Application", |  "name": "Loan Application", | ||||||
|  | |||||||
| @ -229,13 +229,14 @@ | |||||||
|  ], |  ], | ||||||
|  "is_submittable": 1, |  "is_submittable": 1, | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-02-24 07:35:47.168123", |  "modified": "2020-02-26 06:18:54.934538", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Loan Management", |  "module": "Loan Management", | ||||||
|  "name": "Loan Repayment", |  "name": "Loan Repayment", | ||||||
|  "owner": "Administrator", |  "owner": "Administrator", | ||||||
|  "permissions": [ |  "permissions": [ | ||||||
|   { |   { | ||||||
|  |    "cancel": 1, | ||||||
|    "create": 1, |    "create": 1, | ||||||
|    "delete": 1, |    "delete": 1, | ||||||
|    "email": 1, |    "email": 1, | ||||||
| @ -245,9 +246,11 @@ | |||||||
|    "report": 1, |    "report": 1, | ||||||
|    "role": "System Manager", |    "role": "System Manager", | ||||||
|    "share": 1, |    "share": 1, | ||||||
|  |    "submit": 1, | ||||||
|    "write": 1 |    "write": 1 | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|  |    "cancel": 1, | ||||||
|    "create": 1, |    "create": 1, | ||||||
|    "delete": 1, |    "delete": 1, | ||||||
|    "email": 1, |    "email": 1, | ||||||
| @ -257,6 +260,7 @@ | |||||||
|    "report": 1, |    "report": 1, | ||||||
|    "role": "Loan Manager", |    "role": "Loan Manager", | ||||||
|    "share": 1, |    "share": 1, | ||||||
|  |    "submit": 1, | ||||||
|    "write": 1 |    "write": 1 | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  | |||||||
| @ -55,7 +55,10 @@ def check_for_ltv_shortfall(process_loan_security_shortfall=None): | |||||||
| 			"valid_upto": (">=", update_time) | 			"valid_upto": (">=", update_time) | ||||||
| 		}, as_list=1)) | 		}, as_list=1)) | ||||||
| 
 | 
 | ||||||
| 	loans = frappe.db.sql(""" SELECT l.name, l.loan_amount, l.total_principal_paid, lp.loan_security, lp.haircut, lp.qty | 	ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type", | ||||||
|  | 		fields=["name", "loan_to_value_ratio"], as_list=1)) | ||||||
|  | 
 | ||||||
|  | 	loans = frappe.db.sql(""" SELECT l.name, l.loan_amount, l.total_principal_paid, lp.loan_security, lp.haircut, lp.qty, lp.loan_security_type | ||||||
| 		FROM `tabLoan` l, `tabPledge` lp , `tabLoan Security Pledge`p WHERE lp.parent = p.name and p.loan = l.name and l.docstatus = 1 | 		FROM `tabLoan` l, `tabPledge` lp , `tabLoan Security Pledge`p WHERE lp.parent = p.name and p.loan = l.name and l.docstatus = 1 | ||||||
| 		and l.is_secured_loan and l.status = 'Disbursed' and p.status in ('Pledged', 'Partially Unpledged')""", as_dict=1) | 		and l.is_secured_loan and l.status = 'Disbursed' and p.status in ('Pledged', 'Partially Unpledged')""", as_dict=1) | ||||||
| 
 | 
 | ||||||
| @ -68,11 +71,12 @@ def check_for_ltv_shortfall(process_loan_security_shortfall=None): | |||||||
| 		}) | 		}) | ||||||
| 
 | 
 | ||||||
| 		current_loan_security_amount = loan_security_price_map.get(loan.loan_security, 0) * loan.qty | 		current_loan_security_amount = loan_security_price_map.get(loan.loan_security, 0) * loan.qty | ||||||
|  | 		ltv_ratio = ltv_ratio_map.get(loan.loan_security_type) | ||||||
| 
 | 
 | ||||||
| 		loan_security_map[loan.name]['security_value'] += current_loan_security_amount - (current_loan_security_amount * loan.haircut/100) | 		loan_security_map[loan.name]['security_value'] += current_loan_security_amount - (current_loan_security_amount * loan.haircut/100) | ||||||
| 
 | 
 | ||||||
| 	for loan, value in iteritems(loan_security_map): | 	for loan, value in iteritems(loan_security_map): | ||||||
| 		if value["security_value"] < value["loan_amount"]: | 		if (value["security_value"]/value["loan_amount"]) < ltv_ratio: | ||||||
| 			create_loan_security_shortfall(loan, value, process_loan_security_shortfall) | 			create_loan_security_shortfall(loan, value, process_loan_security_shortfall) | ||||||
| 
 | 
 | ||||||
| def create_loan_security_shortfall(loan, value, process_loan_security_shortfall): | def create_loan_security_shortfall(loan, value, process_loan_security_shortfall): | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| { | { | ||||||
|  |  "actions": [], | ||||||
|  "autoname": "field:loan_security_type", |  "autoname": "field:loan_security_type", | ||||||
|  "creation": "2019-08-29 18:46:07.322056", |  "creation": "2019-08-29 18:46:07.322056", | ||||||
|  "doctype": "DocType", |  "doctype": "DocType", | ||||||
| @ -8,7 +9,9 @@ | |||||||
|   "loan_security_type", |   "loan_security_type", | ||||||
|   "unit_of_measure", |   "unit_of_measure", | ||||||
|   "haircut", |   "haircut", | ||||||
|   "disabled" |   "disabled", | ||||||
|  |   "column_break_5", | ||||||
|  |   "loan_to_value_ratio" | ||||||
|  ], |  ], | ||||||
|  "fields": [ |  "fields": [ | ||||||
|   { |   { | ||||||
| @ -33,9 +36,19 @@ | |||||||
|    "fieldtype": "Link", |    "fieldtype": "Link", | ||||||
|    "label": "Unit Of Measure", |    "label": "Unit Of Measure", | ||||||
|    "options": "UOM" |    "options": "UOM" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "fieldname": "column_break_5", | ||||||
|  |    "fieldtype": "Column Break" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |    "fieldname": "loan_to_value_ratio", | ||||||
|  |    "fieldtype": "Percent", | ||||||
|  |    "label": "Loan To Value Ratio" | ||||||
|   } |   } | ||||||
|  ], |  ], | ||||||
|  "modified": "2019-10-10 03:05:37.912866", |  "links": [], | ||||||
|  |  "modified": "2020-02-28 12:43:20.364447", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Loan Management", |  "module": "Loan Management", | ||||||
|  "name": "Loan Security Type", |  "name": "Loan Security Type", | ||||||
|  | |||||||
| @ -108,8 +108,8 @@ def delete_lead_addresses(company_name): | |||||||
| def delete_communications(doctype, company_name, company_fieldname): | def delete_communications(doctype, company_name, company_fieldname): | ||||||
| 		reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name}) | 		reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name}) | ||||||
| 		reference_doc_names = [r.name for r in reference_docs] | 		reference_doc_names = [r.name for r in reference_docs] | ||||||
| 		 | 
 | ||||||
| 		communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]}) | 		communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]}) | ||||||
| 		communication_names = [c.name for c in communications] | 		communication_names = [c.name for c in communications] | ||||||
| 
 | 
 | ||||||
| 		frappe.delete_doc("Communication", communication_names) | 		frappe.delete_doc("Communication", communication_names, ignore_permissions=True) | ||||||
|  | |||||||
| @ -981,6 +981,7 @@ def _msgprint(msg, verbose): | |||||||
| def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): | def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): | ||||||
| 	"""returns last purchase details in stock uom""" | 	"""returns last purchase details in stock uom""" | ||||||
| 	# get last purchase order item details | 	# get last purchase order item details | ||||||
|  | 
 | ||||||
| 	last_purchase_order = frappe.db.sql("""\ | 	last_purchase_order = frappe.db.sql("""\ | ||||||
| 		select po.name, po.transaction_date, po.conversion_rate, | 		select po.name, po.transaction_date, po.conversion_rate, | ||||||
| 			po_item.conversion_factor, po_item.base_price_list_rate, | 			po_item.conversion_factor, po_item.base_price_list_rate, | ||||||
| @ -991,6 +992,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): | |||||||
| 		order by po.transaction_date desc, po.name desc | 		order by po.transaction_date desc, po.name desc | ||||||
| 		limit 1""", (item_code, cstr(doc_name)), as_dict=1) | 		limit 1""", (item_code, cstr(doc_name)), as_dict=1) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| 	# get last purchase receipt item details | 	# get last purchase receipt item details | ||||||
| 	last_purchase_receipt = frappe.db.sql("""\ | 	last_purchase_receipt = frappe.db.sql("""\ | ||||||
| 		select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate, | 		select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate, | ||||||
| @ -1002,19 +1004,20 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): | |||||||
| 		order by pr.posting_date desc, pr.posting_time desc, pr.name desc | 		order by pr.posting_date desc, pr.posting_time desc, pr.name desc | ||||||
| 		limit 1""", (item_code, cstr(doc_name)), as_dict=1) | 		limit 1""", (item_code, cstr(doc_name)), as_dict=1) | ||||||
| 
 | 
 | ||||||
|  | 	 | ||||||
|  | 	 | ||||||
| 	purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date | 	purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date | ||||||
| 							   or "1900-01-01") | 							   or "1900-01-01") | ||||||
| 	purchase_receipt_date = getdate(last_purchase_receipt and | 	purchase_receipt_date = getdate(last_purchase_receipt and | ||||||
| 								 last_purchase_receipt[0].posting_date or "1900-01-01") | 								 last_purchase_receipt[0].posting_date or "1900-01-01") | ||||||
| 
 | 
 | ||||||
| 	if (purchase_order_date > purchase_receipt_date) or \ | 	if last_purchase_order and (purchase_order_date >= purchase_receipt_date or not last_purchase_receipt): | ||||||
| 				(last_purchase_order and not last_purchase_receipt): |  | ||||||
| 		# use purchase order | 		# use purchase order | ||||||
|  | 		 | ||||||
| 		last_purchase = last_purchase_order[0] | 		last_purchase = last_purchase_order[0] | ||||||
| 		purchase_date = purchase_order_date | 		purchase_date = purchase_order_date | ||||||
| 
 | 
 | ||||||
| 	elif (purchase_receipt_date > purchase_order_date) or \ | 	elif last_purchase_receipt and (purchase_receipt_date > purchase_order_date or not last_purchase_order): | ||||||
| 				(last_purchase_receipt and not last_purchase_order): |  | ||||||
| 		# use purchase receipt | 		# use purchase receipt | ||||||
| 		last_purchase = last_purchase_receipt[0] | 		last_purchase = last_purchase_receipt[0] | ||||||
| 		purchase_date = purchase_receipt_date | 		purchase_date = purchase_receipt_date | ||||||
| @ -1026,10 +1029,11 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): | |||||||
| 	out = frappe._dict({ | 	out = frappe._dict({ | ||||||
| 		"base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor, | 		"base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor, | ||||||
| 		"base_rate": flt(last_purchase.base_rate) / conversion_factor, | 		"base_rate": flt(last_purchase.base_rate) / conversion_factor, | ||||||
| 		"base_net_rate": flt(last_purchase.net_rate) / conversion_factor, | 		"base_net_rate": flt(last_purchase.base_net_rate) / conversion_factor, | ||||||
| 		"discount_percentage": flt(last_purchase.discount_percentage), | 		"discount_percentage": flt(last_purchase.discount_percentage), | ||||||
| 		"purchase_date": purchase_date | 		"purchase_date": purchase_date | ||||||
| 	}) | 	}) | ||||||
|  | 	 | ||||||
| 
 | 
 | ||||||
| 	conversion_rate = flt(conversion_rate) or 1.0 | 	conversion_rate = flt(conversion_rate) or 1.0 | ||||||
| 	out.update({ | 	out.update({ | ||||||
|  | |||||||
| @ -205,6 +205,7 @@ def process_serial_no(sle): | |||||||
| 
 | 
 | ||||||
| def validate_serial_no(sle, item_det): | def validate_serial_no(sle, item_det): | ||||||
| 	serial_nos = get_serial_nos(sle.serial_no) if sle.serial_no else [] | 	serial_nos = get_serial_nos(sle.serial_no) if sle.serial_no else [] | ||||||
|  | 	validate_material_transfer_entry(sle) | ||||||
| 
 | 
 | ||||||
| 	if item_det.has_serial_no==0: | 	if item_det.has_serial_no==0: | ||||||
| 		if serial_nos: | 		if serial_nos: | ||||||
| @ -224,7 +225,9 @@ def validate_serial_no(sle, item_det): | |||||||
| 
 | 
 | ||||||
| 			for serial_no in serial_nos: | 			for serial_no in serial_nos: | ||||||
| 				if frappe.db.exists("Serial No", serial_no): | 				if frappe.db.exists("Serial No", serial_no): | ||||||
| 					sr = frappe.get_doc("Serial No", serial_no) | 					sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order", | ||||||
|  | 						"delivery_document_no", "delivery_document_type", "warehouse", | ||||||
|  | 						"purchase_document_no", "company"], as_dict=1) | ||||||
| 
 | 
 | ||||||
| 					if sr.item_code!=sle.item_code: | 					if sr.item_code!=sle.item_code: | ||||||
| 						if not allow_serial_nos_with_different_item(serial_no, sle): | 						if not allow_serial_nos_with_different_item(serial_no, sle): | ||||||
| @ -305,6 +308,19 @@ def validate_serial_no(sle, item_det): | |||||||
| 				frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}") | 				frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}") | ||||||
| 					.format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse)) | 					.format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse)) | ||||||
| 
 | 
 | ||||||
|  | def validate_material_transfer_entry(sle_doc): | ||||||
|  | 	sle_doc.update({ | ||||||
|  | 		"skip_update_serial_no": False, | ||||||
|  | 		"skip_serial_no_validaiton": False | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	if (sle_doc.voucher_type == "Stock Entry" and sle_doc.is_cancelled == "No" and | ||||||
|  | 		frappe.get_cached_value("Stock Entry", sle_doc.voucher_no, "purpose") == "Material Transfer"): | ||||||
|  | 		if sle_doc.actual_qty < 0: | ||||||
|  | 			sle_doc.skip_update_serial_no = True | ||||||
|  | 		else: | ||||||
|  | 			sle_doc.skip_serial_no_validaiton = True | ||||||
|  | 
 | ||||||
| def validate_so_serial_no(sr, sales_order,): | def validate_so_serial_no(sr, sales_order,): | ||||||
| 	if not sr.sales_order or sr.sales_order!= sales_order: | 	if not sr.sales_order or sr.sales_order!= sales_order: | ||||||
| 		frappe.throw(_("""Sales Order {0} has reservation for item {1}, you can | 		frappe.throw(_("""Sales Order {0} has reservation for item {1}, you can | ||||||
| @ -312,7 +328,8 @@ def validate_so_serial_no(sr, sales_order,): | |||||||
| 		be delivered""").format(sales_order, sr.item_code, sr.name)) | 		be delivered""").format(sales_order, sr.item_code, sr.name)) | ||||||
| 
 | 
 | ||||||
| def has_duplicate_serial_no(sn, sle): | def has_duplicate_serial_no(sn, sle): | ||||||
| 	if sn.warehouse and sle.voucher_type != 'Stock Reconciliation': | 	if (sn.warehouse and not sle.skip_serial_no_validaiton | ||||||
|  | 		and sle.voucher_type != 'Stock Reconciliation'): | ||||||
| 		return True | 		return True | ||||||
| 
 | 
 | ||||||
| 	if sn.company != sle.company: | 	if sn.company != sle.company: | ||||||
| @ -337,7 +354,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): | |||||||
| 	""" | 	""" | ||||||
| 	allow_serial_nos = False | 	allow_serial_nos = False | ||||||
| 	if sle.voucher_type=="Stock Entry" and cint(sle.actual_qty) > 0: | 	if sle.voucher_type=="Stock Entry" and cint(sle.actual_qty) > 0: | ||||||
| 		stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no) | 		stock_entry = frappe.get_cached_doc("Stock Entry", sle.voucher_no) | ||||||
| 		if stock_entry.purpose in ("Repack", "Manufacture"): | 		if stock_entry.purpose in ("Repack", "Manufacture"): | ||||||
| 			for d in stock_entry.get("items"): | 			for d in stock_entry.get("items"): | ||||||
| 				if d.serial_no and (d.s_warehouse if sle.is_cancelled=="No" else d.t_warehouse): | 				if d.serial_no and (d.s_warehouse if sle.is_cancelled=="No" else d.t_warehouse): | ||||||
| @ -348,6 +365,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): | |||||||
| 	return allow_serial_nos | 	return allow_serial_nos | ||||||
| 
 | 
 | ||||||
| def update_serial_nos(sle, item_det): | def update_serial_nos(sle, item_det): | ||||||
|  | 	if sle.skip_update_serial_no: return | ||||||
| 	if sle.is_cancelled == "No" and not sle.serial_no and cint(sle.actual_qty) > 0 \ | 	if sle.is_cancelled == "No" and not sle.serial_no and cint(sle.actual_qty) > 0 \ | ||||||
| 			and item_det.has_serial_no == 1 and item_det.serial_no_series: | 			and item_det.has_serial_no == 1 and item_det.serial_no_series: | ||||||
| 		serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty) | 		serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty) | ||||||
| @ -369,22 +387,16 @@ def auto_make_serial_nos(args): | |||||||
| 	voucher_type = args.get('voucher_type') | 	voucher_type = args.get('voucher_type') | ||||||
| 	item_code = args.get('item_code') | 	item_code = args.get('item_code') | ||||||
| 	for serial_no in serial_nos: | 	for serial_no in serial_nos: | ||||||
|  | 		is_new = False | ||||||
| 		if frappe.db.exists("Serial No", serial_no): | 		if frappe.db.exists("Serial No", serial_no): | ||||||
| 			sr = frappe.get_doc("Serial No", serial_no) | 			sr = frappe.get_cached_doc("Serial No", serial_no) | ||||||
| 			sr.via_stock_ledger = True |  | ||||||
| 			sr.item_code = item_code |  | ||||||
| 			sr.warehouse = args.get('warehouse') if args.get('actual_qty', 0) > 0 else None |  | ||||||
| 			sr.batch_no = args.get('batch_no') |  | ||||||
| 			sr.location = args.get('location') |  | ||||||
| 			sr.company = args.get('company') |  | ||||||
| 			sr.supplier = args.get('supplier') |  | ||||||
| 			if sr.sales_order and voucher_type == "Stock Entry" \ |  | ||||||
| 				and not args.get('actual_qty', 0) > 0: |  | ||||||
| 				sr.sales_order = None |  | ||||||
| 			sr.update_serial_no_reference() |  | ||||||
| 			sr.save(ignore_permissions=True) |  | ||||||
| 		elif args.get('actual_qty', 0) > 0: | 		elif args.get('actual_qty', 0) > 0: | ||||||
| 			created_numbers.append(make_serial_no(serial_no, args)) | 			sr = frappe.new_doc("Serial No") | ||||||
|  | 			is_new = True | ||||||
|  | 
 | ||||||
|  | 		sr = update_args_for_serial_no(sr, serial_no, args, is_new=is_new) | ||||||
|  | 		if is_new: | ||||||
|  | 			created_numbers.append(sr.name) | ||||||
| 
 | 
 | ||||||
| 	form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers)) | 	form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers)) | ||||||
| 
 | 
 | ||||||
| @ -419,20 +431,34 @@ def get_serial_nos(serial_no): | |||||||
| 	return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n') | 	return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n') | ||||||
| 		if s.strip()] | 		if s.strip()] | ||||||
| 
 | 
 | ||||||
| def make_serial_no(serial_no, args): | def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False): | ||||||
| 	sr = frappe.new_doc("Serial No") | 	serial_no_doc.update({ | ||||||
| 	sr.serial_no = serial_no | 		"item_code": args.get("item_code"), | ||||||
| 	sr.item_code = args.get('item_code') | 		"company": args.get("company"), | ||||||
| 	sr.company = args.get('company') | 		"batch_no": args.get("batch_no"), | ||||||
| 	sr.batch_no = args.get('batch_no') | 		"via_stock_ledger": args.get("via_stock_ledger") or True, | ||||||
| 	sr.via_stock_ledger = args.get('via_stock_ledger') or True | 		"supplier": args.get("supplier"), | ||||||
| 	sr.warehouse = args.get('warehouse') | 		"location": args.get("location"), | ||||||
|  | 		"warehouse": (args.get("warehouse") | ||||||
|  | 			if args.get("actual_qty", 0) > 0 else None) | ||||||
|  | 	}) | ||||||
| 
 | 
 | ||||||
| 	sr.validate_item() | 	if is_new: | ||||||
| 	sr.update_serial_no_reference(serial_no) | 		serial_no_doc.serial_no = serial_no | ||||||
| 	sr.db_insert() |  | ||||||
| 
 | 
 | ||||||
| 	return sr.name | 	if (serial_no_doc.sales_order and args.get("voucher_type") == "Stock Entry" | ||||||
|  | 		and not args.get("actual_qty", 0) > 0): | ||||||
|  | 		serial_no_doc.sales_order = None | ||||||
|  | 
 | ||||||
|  | 	serial_no_doc.validate_item() | ||||||
|  | 	serial_no_doc.update_serial_no_reference(serial_no) | ||||||
|  | 
 | ||||||
|  | 	if is_new: | ||||||
|  | 		serial_no_doc.db_insert() | ||||||
|  | 	else: | ||||||
|  | 		serial_no_doc.db_update() | ||||||
|  | 
 | ||||||
|  | 	return serial_no_doc | ||||||
| 
 | 
 | ||||||
| def update_serial_nos_after_submit(controller, parentfield): | def update_serial_nos_after_submit(controller, parentfield): | ||||||
| 	stock_ledger_entries = frappe.db.sql("""select voucher_detail_no, serial_no, actual_qty, warehouse | 	stock_ledger_entries = frappe.db.sql("""select voucher_detail_no, serial_no, actual_qty, warehouse | ||||||
|  | |||||||
| @ -240,6 +240,7 @@ | |||||||
|    "options": "Company", |    "options": "Company", | ||||||
|    "print_width": "150px", |    "print_width": "150px", | ||||||
|    "read_only": 1, |    "read_only": 1, | ||||||
|  |    "search_index": 1, | ||||||
|    "width": "150px" |    "width": "150px" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
| @ -274,7 +275,7 @@ | |||||||
|  "icon": "fa fa-list", |  "icon": "fa fa-list", | ||||||
|  "idx": 1, |  "idx": 1, | ||||||
|  "in_create": 1, |  "in_create": 1, | ||||||
|  "modified": "2019-11-27 12:17:31.522675", |  "modified": "2020-02-25 22:53:33.504681", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Stock", |  "module": "Stock", | ||||||
|  "name": "Stock Ledger Entry", |  "name": "Stock Ledger Entry", | ||||||
|  | |||||||
| @ -136,7 +136,7 @@ def get_bin(item_code, warehouse): | |||||||
| 		bin_obj.flags.ignore_permissions = 1 | 		bin_obj.flags.ignore_permissions = 1 | ||||||
| 		bin_obj.insert() | 		bin_obj.insert() | ||||||
| 	else: | 	else: | ||||||
| 		bin_obj = frappe.get_doc('Bin', bin) | 		bin_obj = frappe.get_cached_doc('Bin', bin) | ||||||
| 	bin_obj.flags.ignore_permissions = True | 	bin_obj.flags.ignore_permissions = True | ||||||
| 	return bin_obj | 	return bin_obj | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -366,7 +366,7 @@ | |||||||
|  "icon": "fa fa-ticket", |  "icon": "fa fa-ticket", | ||||||
|  "idx": 7, |  "idx": 7, | ||||||
|  "links": [], |  "links": [], | ||||||
|  "modified": "2020-02-18 21:26:35.636013", |  "modified": "2020-02-26 02:19:49.477928", | ||||||
|  "modified_by": "Administrator", |  "modified_by": "Administrator", | ||||||
|  "module": "Support", |  "module": "Support", | ||||||
|  "name": "Issue", |  "name": "Issue", | ||||||
| @ -387,9 +387,9 @@ | |||||||
|  "quick_entry": 1, |  "quick_entry": 1, | ||||||
|  "search_fields": "status,customer,subject,raised_by", |  "search_fields": "status,customer,subject,raised_by", | ||||||
|  "sort_field": "modified", |  "sort_field": "modified", | ||||||
|  "sort_order": "ASC", |  "sort_order": "DESC", | ||||||
|  "timeline_field": "customer", |  "timeline_field": "customer", | ||||||
|  "title_field": "subject", |  "title_field": "subject", | ||||||
|  "track_changes": 1, |  "track_changes": 1, | ||||||
|  "track_seen": 1 |  "track_seen": 1 | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user