Merge branch 'develop' into develop
This commit is contained in:
		
						commit
						3bf04154b1
					
				| @ -13,13 +13,15 @@ form_grid_templates = { | ||||
| 
 | ||||
| class BankReconciliation(Document): | ||||
| 	def get_payment_entries(self): | ||||
| 		if not (self.bank_account and self.from_date and self.to_date): | ||||
| 			msgprint(_("Bank Account, From Date and To Date are Mandatory")) | ||||
| 			return | ||||
| 		if not (self.from_date and self.to_date): | ||||
| 			frappe.throw(_("From Date and To Date are Mandatory")) | ||||
| 
 | ||||
| 		if not self.account: | ||||
| 			frappe.throw(_("Account is mandatory to get payment entries")) | ||||
| 
 | ||||
| 		condition = "" | ||||
| 		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(""" | ||||
| 			select | ||||
| @ -32,10 +34,14 @@ class BankReconciliation(Document): | ||||
| 			where | ||||
| 				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 ifnull(t1.is_opening, 'No') = 'No' %(condition)s | ||||
| 				and ifnull(t1.is_opening, 'No') = 'No' {condition} | ||||
| 			group by t2.account, t1.name | ||||
| 			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(""" | ||||
| 			select | ||||
| @ -49,10 +55,10 @@ class BankReconciliation(Document): | ||||
| 			where | ||||
| 				(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1 | ||||
| 				and posting_date >= %(from)s and posting_date <= %(to)s | ||||
| 				and bank_account = %(bank_account)s | ||||
| 				{condition} | ||||
| 			order by | ||||
| 				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) | ||||
| 
 | ||||
| 		pos_entries = [] | ||||
|  | ||||
| @ -18,7 +18,8 @@ | ||||
|    "in_list_view": 1, | ||||
|    "label": "Invoice", | ||||
|    "options": "Sales Invoice", | ||||
|    "reqd": 1 | ||||
|    "reqd": 1, | ||||
|    "search_index": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fetch_from": "sales_invoice.customer", | ||||
| @ -60,7 +61,7 @@ | ||||
|   } | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "modified": "2019-09-26 11:05:36.016772", | ||||
|  "modified": "2020-02-20 16:16:20.724620", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Discounted Invoice", | ||||
|  | ||||
| @ -232,11 +232,36 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga | ||||
| 		if bal < 0 and not on_cancel: | ||||
| 			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"]: | ||||
| 		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.db_set('outstanding_amount', bal) | ||||
| 		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): | ||||
| 	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: | ||||
| 			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(): | ||||
| 	for doctype in ["GL Entry", "Stock Ledger Entry"]: | ||||
|  | ||||
| @ -125,6 +125,27 @@ class PurchaseInvoice(BuyingController): | ||||
| 			else: | ||||
| 				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): | ||||
| 		if not self.credit_to: | ||||
| 			self.credit_to = get_party_account("Supplier", self.supplier, self.company) | ||||
| @ -1007,6 +1028,34 @@ class PurchaseInvoice(BuyingController): | ||||
| 		# calculate totals again after applying TDS | ||||
| 		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): | ||||
| 	from erpnext.controllers.website_list_for_contact import get_list_context | ||||
| 	list_context = get_list_context(context) | ||||
|  | ||||
| @ -1217,9 +1217,31 @@ class SalesInvoice(SellingController): | ||||
| 
 | ||||
| 		self.set_missing_values(for_validate = True) | ||||
| 
 | ||||
| 	def get_discounting_status(self): | ||||
| 	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_discounted,  | ||||
| 				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 get_discounting_status(sales_invoice): | ||||
| 	status = None | ||||
| 		if self.is_discounted: | ||||
| 
 | ||||
| 	invoice_discounting_list = frappe.db.sql(""" | ||||
| 		select status | ||||
| 		from `tabInvoice Discounting` id, `tabDiscounted Invoice` d | ||||
| @ -1228,51 +1250,50 @@ class SalesInvoice(SellingController): | ||||
| 			and d.sales_invoice=%s | ||||
| 			and id.docstatus=1 | ||||
| 			and status in ('Disbursed', 'Settled') | ||||
| 			""", self.name) | ||||
| 	""", sales_invoice) | ||||
| 
 | ||||
| 	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): | ||||
| 		if self.is_new(): | ||||
| 			if self.get('amended_from'): | ||||
| 				self.status = 'Draft' | ||||
| 			return | ||||
| def get_status(*args): | ||||
| 	sales_invoice, outstanding_amount, is_discounted, is_return, due_date, docstatus, precision = args[0] | ||||
| 	 | ||||
| 		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() | ||||
| 	discounting_status = None | ||||
| 	if is_discounted: | ||||
| 		discounting_status = get_discounting_status(sales_invoice) | ||||
| 
 | ||||
| 		if not status: | ||||
| 			if self.docstatus == 2: | ||||
| 	outstanding_amount = flt(outstanding_amount, precision) | ||||
| 	due_date = getdate(due_date) | ||||
| 	now_date = getdate() | ||||
| 
 | ||||
| 	if docstatus == 2: | ||||
| 		status = "Cancelled" | ||||
| 			elif self.docstatus == 1: | ||||
| 				if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed': | ||||
| 					self.status = "Overdue and Discounted" | ||||
| 				elif outstanding_amount > 0 and due_date < nowdate: | ||||
| 					self.status = "Overdue" | ||||
| 				elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discountng_status=='Disbursed': | ||||
| 					self.status = "Unpaid and Discounted" | ||||
| 				elif outstanding_amount > 0 and due_date >= nowdate: | ||||
| 					self.status = "Unpaid" | ||||
| 	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 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" | ||||
| 		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: | ||||
| 					self.status = "Submitted" | ||||
| 			status = "Submitted" | ||||
| 	else: | ||||
| 				self.status = "Draft" | ||||
| 		status = "Draft" | ||||
| 	 | ||||
| 		if update: | ||||
| 			self.db_set('status', self.status, update_modified = update_modified) | ||||
| 	return status | ||||
| 
 | ||||
| def validate_inter_company_party(doctype, party, company, inter_company_reference): | ||||
| 	if not party: | ||||
|  | ||||
| @ -140,8 +140,11 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): | ||||
| 	gle = frappe.get_doc(args) | ||||
| 	gle.flags.ignore_permissions = 1 | ||||
| 	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.flags.ignore_validate = True | ||||
| 	gle.submit() | ||||
| 
 | ||||
| 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.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) - | ||||
| 				flt(row.accumulated_depreciation_as_on_from_date)) | ||||
| @ -86,7 +86,6 @@ def get_asset_categories(filters): | ||||
| 		group by asset_category | ||||
| 	""", {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1) | ||||
| 
 | ||||
| 
 | ||||
| def get_assets(filters): | ||||
| 	return frappe.db.sql(""" | ||||
| 		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_amount_during_the_period) as depreciation_amount_during_the_period | ||||
| 		from (SELECT a.asset_category, | ||||
| 				   ifnull(sum(a.opening_accumulated_depreciation + | ||||
| 							  case when ds.schedule_date < %(from_date)s and | ||||
| 										(ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then | ||||
| 				   ifnull(sum(case when ds.schedule_date < %(from_date)s then | ||||
| 								   ds.depreciation_amount | ||||
| 							  else | ||||
| 								   0 | ||||
| @ -107,7 +104,6 @@ def get_assets(filters): | ||||
| 							  else | ||||
| 								   0 | ||||
| 							  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 | ||||
| 										and (ifnull(a.disposal_date, 0) = 0 or ds.schedule_date <= a.disposal_date) then | ||||
| 								   ds.depreciation_amount | ||||
| @ -120,7 +116,8 @@ def get_assets(filters): | ||||
| 			union | ||||
| 			SELECT a.asset_category, | ||||
| 				   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 | ||||
| 							   else | ||||
| 									a.opening_accumulated_depreciation | ||||
| @ -133,7 +130,6 @@ def get_assets(filters): | ||||
| 				   0 as depreciation_amount_during_the_period | ||||
| 			from `tabAsset` a | ||||
| 			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 results.asset_category | ||||
| 		""", {"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): | ||||
| 	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: | ||||
| 		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} | ||||
| 		from `tabPurchase Invoice`, `tabPurchase Invoice Item` | ||||
| 		where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and | ||||
| 		`tabPurchase Invoice`.docstatus = 1 %s %s | ||||
| 	""".format(additional_query_columns) % (conditions, match_conditions), filters, as_dict=1) | ||||
| 		`tabPurchase Invoice`.docstatus = 1 %s | ||||
| 	""".format(additional_query_columns) % (conditions), filters, as_dict=1) | ||||
| 
 | ||||
| def get_aii_accounts(): | ||||
| 	return dict(frappe.db.sql("select name, stock_received_but_not_billed from tabCompany")) | ||||
|  | ||||
| @ -370,10 +370,6 @@ def get_group_by_conditions(filters, doctype): | ||||
| 
 | ||||
| def get_items(filters, additional_query_columns): | ||||
| 	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: | ||||
| 		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} | ||||
| 		from `tabSales Invoice`, `tabSales Invoice Item` | ||||
| 		where `tabSales Invoice`.name = `tabSales Invoice Item`.parent | ||||
| 			and `tabSales Invoice`.docstatus = 1 {1} {2} | ||||
| 		""".format(additional_query_columns or '', conditions, match_conditions), filters, as_dict=1) #nosec | ||||
| 			and `tabSales Invoice`.docstatus = 1 {1} | ||||
| 		""".format(additional_query_columns or '', conditions), filters, as_dict=1) #nosec | ||||
| 
 | ||||
| def get_delivery_notes_against_sales_order(item_list): | ||||
| 	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): | ||||
| 	"""updates last_purchase_rate in item table for each item""" | ||||
| 
 | ||||
| 	import frappe.utils | ||||
| 	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 | ||||
| 		last_purchase_rate = None | ||||
| 		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'] | ||||
| 		elif is_submit == 1: | ||||
| 			# even if this transaction is the latest one, it should be submitted | ||||
|  | ||||
| @ -7,6 +7,13 @@ def get_data(): | ||||
| 			"label": _("Purchasing"), | ||||
| 			"icon": "fa fa-star", | ||||
| 			"items": [ | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Material Request", | ||||
| 					"onboard": 1, | ||||
| 					"dependencies": ["Item"], | ||||
| 					"description": _("Request for purchase."), | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Purchase Order", | ||||
| @ -20,13 +27,6 @@ def get_data(): | ||||
| 					"onboard": 1, | ||||
| 					"dependencies": ["Item", "Supplier"] | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Material Request", | ||||
| 					"onboard": 1, | ||||
| 					"dependencies": ["Item"], | ||||
| 					"description": _("Request for purchase."), | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Request for Quotation", | ||||
| @ -63,6 +63,11 @@ def get_data(): | ||||
| 					"name": "Price List", | ||||
| 					"description": _("Price List master.") | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Pricing Rule", | ||||
| 					"description": _("Rules for applying pricing and discount.") | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Product Bundle", | ||||
| @ -80,11 +85,6 @@ def get_data(): | ||||
| 					"type": "doctype", | ||||
| 					"name": "Promotional Scheme", | ||||
| 					"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", | ||||
| 					"onboard": 1 | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "report", | ||||
| 					"is_query_report": True, | ||||
| 					"name": "Supplier-Wise Sales Analytics", | ||||
| 					"reference_doctype": "Stock Ledger Entry", | ||||
| 					"onboard": 1 | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "report", | ||||
| 					"is_query_report": True, | ||||
| @ -177,6 +170,16 @@ def get_data(): | ||||
| 					"reference_doctype": "Material Request", | ||||
| 					"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", | ||||
| 					"is_query_report": True, | ||||
| 					"name": "Material Requests for which Supplier Quotations are not created", | ||||
| 					"reference_doctype": "Material Request" | ||||
| 					"name": "Supplier-Wise Sales Analytics", | ||||
| 					"reference_doctype": "Stock Ledger Entry", | ||||
| 					"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" | ||||
| 					} | ||||
| 					"name": "Material Requests for which Supplier Quotations are not created", | ||||
| 					"reference_doctype": "Material Request" | ||||
| 				} | ||||
| 			] | ||||
| 		}, | ||||
|  | ||||
| @ -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")) | ||||
| 		else: | ||||
| 			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): | ||||
| 			frappe.throw(_("Cannot set quantity less than delivered quantity")) | ||||
|  | ||||
| @ -44,17 +44,6 @@ status_map = { | ||||
| 		["Closed", "eval:self.status=='Closed'"], | ||||
| 		["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": [ | ||||
| 		["Draft", None], | ||||
| 		["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 | ||||
| 		from `tabStock Ledger Entry` sle | ||||
| 		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): | ||||
| 			future_stock_vouchers.append([d.voucher_type, d.voucher_no]) | ||||
| 
 | ||||
|  | ||||
| @ -11,5 +11,12 @@ frappe.ui.form.on('Student Admission', { | ||||
| 
 | ||||
| 	academic_year: function(frm) { | ||||
| 		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 | ||||
| 
 | ||||
| @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(""" | ||||
| 		select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date | ||||
| 		from `tabAdditional Salary` | ||||
| 		where employee=%(employee)s | ||||
| 			and docstatus = 1 | ||||
| 			and payroll_date between %(from_date)s and %(to_date)s | ||||
| 			and type = %(component_type)s | ||||
| 		group by salary_component, overwrite_salary_structure_amount | ||||
| 		order by salary_component, overwrite_salary_structure_amount | ||||
| 	""", { | ||||
| 		'employee': employee, | ||||
| 		'from_date': start_date, | ||||
| 		'to_date': end_date | ||||
| 		'to_date': end_date, | ||||
| 		'component_type': "Earning" if component_type == "earnings" else "Deduction" | ||||
| 	}, as_dict=1) | ||||
| 
 | ||||
| 	additional_components_list = [] | ||||
|  | ||||
| @ -64,6 +64,9 @@ class LeaveEncashment(Document): | ||||
| 
 | ||||
| 		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\ | ||||
| 			- get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) | ||||
| 
 | ||||
|  | ||||
| @ -299,9 +299,11 @@ class SalarySlip(TransactionBase): | ||||
| 
 | ||||
| 	def calculate_net_pay(self): | ||||
| 		if self.salary_structure: | ||||
| 			self.calculate_component_amounts() | ||||
| 
 | ||||
| 			self.calculate_component_amounts("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.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.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): | ||||
| 			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) | ||||
| 
 | ||||
| 		self.add_structure_components() | ||||
| 		self.add_structure_components(component_type) | ||||
| 		self.add_additional_salary_components(component_type) | ||||
| 		if component_type == "earnings": | ||||
| 			self.add_employee_benefits(payroll_period) | ||||
| 		self.add_additional_salary_components() | ||||
| 		else: | ||||
| 			self.add_tax_components(payroll_period) | ||||
| 		self.set_component_amounts_based_on_payment_days() | ||||
| 
 | ||||
| 	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() | ||||
| 		for key in ('earnings', 'deductions'): | ||||
| 			for struct_row in self._salary_structure_doc.get(key): | ||||
| 		for struct_row in self._salary_structure_doc.get(component_type): | ||||
| 			amount = self.eval_condition_and_formula(struct_row, data) | ||||
| 			if amount and struct_row.statistical_component == 0: | ||||
| 					self.update_component_row(struct_row, amount, key) | ||||
| 				self.update_component_row(struct_row, amount, component_type) | ||||
| 
 | ||||
| 	def get_data_for_eval(self): | ||||
| 		'''Returns data for evaluating formula''' | ||||
| @ -400,14 +404,15 @@ class SalarySlip(TransactionBase): | ||||
| 						amount = last_benefit.amount | ||||
| 						self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings") | ||||
| 
 | ||||
| 	def add_additional_salary_components(self): | ||||
| 		additional_components = get_additional_salary_component(self.employee, self.start_date, self.end_date) | ||||
| 	def add_additional_salary_components(self, component_type): | ||||
| 		additional_components = get_additional_salary_component(self.employee, | ||||
| 			self.start_date, self.end_date, component_type) | ||||
| 		if additional_components: | ||||
| 			for additional_component in additional_components: | ||||
| 				amount = additional_component.amount | ||||
| 				overwrite = additional_component.overwrite | ||||
| 				key = "earnings" if additional_component.type == "Earning" else "deductions" | ||||
| 				self.update_component_row(frappe._dict(additional_component.struct_row), amount, key, overwrite=overwrite) | ||||
| 				self.update_component_row(frappe._dict(additional_component.struct_row), amount, | ||||
| 					component_type, overwrite=overwrite) | ||||
| 
 | ||||
| 	def add_tax_components(self, payroll_period): | ||||
| 		# Calculate variable_based_on_taxable_salary after all components updated in salary slip | ||||
| @ -736,7 +741,7 @@ class SalarySlip(TransactionBase): | ||||
| 				total += d.amount | ||||
| 		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, | ||||
| 			["date_of_joining", "relieving_date"]) | ||||
| 
 | ||||
| @ -746,7 +751,6 @@ class SalarySlip(TransactionBase): | ||||
| 		if not joining_date: | ||||
| 			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): | ||||
| 			d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0] | ||||
| 
 | ||||
|  | ||||
| @ -25,7 +25,6 @@ class TestSalaryStructure(unittest.TestCase): | ||||
| 		make_employee("test_employee@salary.com") | ||||
| 		make_employee("test_employee_2@salary.com") | ||||
| 
 | ||||
| 
 | ||||
| 	def make_holiday_list(self): | ||||
| 		if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"): | ||||
| 			holiday_list = frappe.get_doc({ | ||||
| @ -38,6 +37,29 @@ class TestSalaryStructure(unittest.TestCase): | ||||
| 			holiday_list.get_weekly_off_dates() | ||||
| 			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): | ||||
| 		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"}) | ||||
|  | ||||
| @ -75,7 +75,7 @@ class ShiftType(Document): | ||||
| 		for date in dates: | ||||
| 			shift_details = get_employee_shift(employee, date, True) | ||||
| 			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): | ||||
| 		filters = {'date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'} | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "ACC-LOAP-.YYYY.-.#####", | ||||
|  "creation": "2019-08-29 17:46:49.201740", | ||||
|  "doctype": "DocType", | ||||
| @ -122,7 +123,6 @@ | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval: doc.is_term_loan == 1", | ||||
|    "fetch_from": "loan_type.repayment_method", | ||||
|    "fetch_if_empty": 1, | ||||
|    "fieldname": "repayment_method", | ||||
|    "fieldtype": "Select", | ||||
| @ -213,7 +213,8 @@ | ||||
|   } | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "modified": "2019-10-24 10:32:03.740558", | ||||
|  "links": [], | ||||
|  "modified": "2020-03-01 10:21:44.413353", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Loan Management", | ||||
|  "name": "Loan Application", | ||||
|  | ||||
| @ -229,13 +229,14 @@ | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-02-24 07:35:47.168123", | ||||
|  "modified": "2020-02-26 06:18:54.934538", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Loan Management", | ||||
|  "name": "Loan Repayment", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "cancel": 1, | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
| @ -245,9 +246,11 @@ | ||||
|    "report": 1, | ||||
|    "role": "System Manager", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "cancel": 1, | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
| @ -257,6 +260,7 @@ | ||||
|    "report": 1, | ||||
|    "role": "Loan Manager", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   } | ||||
|  ], | ||||
|  | ||||
| @ -55,7 +55,10 @@ def check_for_ltv_shortfall(process_loan_security_shortfall=None): | ||||
| 			"valid_upto": (">=", update_time) | ||||
| 		}, 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 | ||||
| 		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 | ||||
| 		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) | ||||
| 
 | ||||
| 	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) | ||||
| 
 | ||||
| def create_loan_security_shortfall(loan, value, process_loan_security_shortfall): | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "field:loan_security_type", | ||||
|  "creation": "2019-08-29 18:46:07.322056", | ||||
|  "doctype": "DocType", | ||||
| @ -8,7 +9,9 @@ | ||||
|   "loan_security_type", | ||||
|   "unit_of_measure", | ||||
|   "haircut", | ||||
|   "disabled" | ||||
|   "disabled", | ||||
|   "column_break_5", | ||||
|   "loan_to_value_ratio" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
| @ -33,9 +36,19 @@ | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Unit Of Measure", | ||||
|    "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", | ||||
|  "module": "Loan Management", | ||||
|  "name": "Loan Security Type", | ||||
|  | ||||
| @ -112,4 +112,4 @@ def delete_communications(doctype, company_name, company_fieldname): | ||||
| 		communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]}) | ||||
| 		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): | ||||
| 	"""returns last purchase details in stock uom""" | ||||
| 	# get last purchase order item details | ||||
| 
 | ||||
| 	last_purchase_order = frappe.db.sql("""\ | ||||
| 		select po.name, po.transaction_date, po.conversion_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 | ||||
| 		limit 1""", (item_code, cstr(doc_name)), as_dict=1) | ||||
| 
 | ||||
| 
 | ||||
| 	# get last purchase receipt item details | ||||
| 	last_purchase_receipt = frappe.db.sql("""\ | ||||
| 		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 | ||||
| 		limit 1""", (item_code, cstr(doc_name)), as_dict=1) | ||||
| 
 | ||||
| 	 | ||||
| 	 | ||||
| 	purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date | ||||
| 							   or "1900-01-01") | ||||
| 	purchase_receipt_date = getdate(last_purchase_receipt and | ||||
| 								 last_purchase_receipt[0].posting_date or "1900-01-01") | ||||
| 
 | ||||
| 	if (purchase_order_date > purchase_receipt_date) or \ | ||||
| 				(last_purchase_order and not last_purchase_receipt): | ||||
| 	if last_purchase_order and (purchase_order_date >= purchase_receipt_date or not last_purchase_receipt): | ||||
| 		# use purchase order | ||||
| 		 | ||||
| 		last_purchase = last_purchase_order[0] | ||||
| 		purchase_date = purchase_order_date | ||||
| 
 | ||||
| 	elif (purchase_receipt_date > purchase_order_date) or \ | ||||
| 				(last_purchase_receipt and not last_purchase_order): | ||||
| 	elif last_purchase_receipt and (purchase_receipt_date > purchase_order_date or not last_purchase_order): | ||||
| 		# use purchase receipt | ||||
| 		last_purchase = last_purchase_receipt[0] | ||||
| 		purchase_date = purchase_receipt_date | ||||
| @ -1026,11 +1029,12 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): | ||||
| 	out = frappe._dict({ | ||||
| 		"base_price_list_rate": flt(last_purchase.base_price_list_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), | ||||
| 		"purchase_date": purchase_date | ||||
| 	}) | ||||
| 	 | ||||
| 
 | ||||
| 	conversion_rate = flt(conversion_rate) or 1.0 | ||||
| 	out.update({ | ||||
| 		"price_list_rate": out.base_price_list_rate / conversion_rate, | ||||
|  | ||||
| @ -205,6 +205,7 @@ def process_serial_no(sle): | ||||
| 
 | ||||
| def validate_serial_no(sle, item_det): | ||||
| 	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 serial_nos: | ||||
| @ -224,7 +225,9 @@ def validate_serial_no(sle, item_det): | ||||
| 
 | ||||
| 			for serial_no in serial_nos: | ||||
| 				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 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}") | ||||
| 					.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,): | ||||
| 	if not sr.sales_order or sr.sales_order!= sales_order: | ||||
| 		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)) | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| 	if sn.company != sle.company: | ||||
| @ -337,7 +354,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): | ||||
| 	""" | ||||
| 	allow_serial_nos = False | ||||
| 	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"): | ||||
| 			for d in stock_entry.get("items"): | ||||
| 				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 | ||||
| 
 | ||||
| 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 \ | ||||
| 			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) | ||||
| @ -369,22 +387,16 @@ def auto_make_serial_nos(args): | ||||
| 	voucher_type = args.get('voucher_type') | ||||
| 	item_code = args.get('item_code') | ||||
| 	for serial_no in serial_nos: | ||||
| 		is_new = False | ||||
| 		if frappe.db.exists("Serial No", serial_no): | ||||
| 			sr = frappe.get_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) | ||||
| 			sr = frappe.get_cached_doc("Serial No", serial_no) | ||||
| 		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)) | ||||
| 
 | ||||
| @ -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') | ||||
| 		if s.strip()] | ||||
| 
 | ||||
| def make_serial_no(serial_no, args): | ||||
| 	sr = frappe.new_doc("Serial No") | ||||
| 	sr.serial_no = serial_no | ||||
| 	sr.item_code = args.get('item_code') | ||||
| 	sr.company = args.get('company') | ||||
| 	sr.batch_no = args.get('batch_no') | ||||
| 	sr.via_stock_ledger = args.get('via_stock_ledger') or True | ||||
| 	sr.warehouse = args.get('warehouse') | ||||
| def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False): | ||||
| 	serial_no_doc.update({ | ||||
| 		"item_code": args.get("item_code"), | ||||
| 		"company": args.get("company"), | ||||
| 		"batch_no": args.get("batch_no"), | ||||
| 		"via_stock_ledger": args.get("via_stock_ledger") or True, | ||||
| 		"supplier": args.get("supplier"), | ||||
| 		"location": args.get("location"), | ||||
| 		"warehouse": (args.get("warehouse") | ||||
| 			if args.get("actual_qty", 0) > 0 else None) | ||||
| 	}) | ||||
| 
 | ||||
| 	sr.validate_item() | ||||
| 	sr.update_serial_no_reference(serial_no) | ||||
| 	sr.db_insert() | ||||
| 	if is_new: | ||||
| 		serial_no_doc.serial_no = serial_no | ||||
| 
 | ||||
| 	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): | ||||
| 	stock_ledger_entries = frappe.db.sql("""select voucher_detail_no, serial_no, actual_qty, warehouse | ||||
|  | ||||
| @ -240,6 +240,7 @@ | ||||
|    "options": "Company", | ||||
|    "print_width": "150px", | ||||
|    "read_only": 1, | ||||
|    "search_index": 1, | ||||
|    "width": "150px" | ||||
|   }, | ||||
|   { | ||||
| @ -274,7 +275,7 @@ | ||||
|  "icon": "fa fa-list", | ||||
|  "idx": 1, | ||||
|  "in_create": 1, | ||||
|  "modified": "2019-11-27 12:17:31.522675", | ||||
|  "modified": "2020-02-25 22:53:33.504681", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Stock", | ||||
|  "name": "Stock Ledger Entry", | ||||
|  | ||||
| @ -136,7 +136,7 @@ def get_bin(item_code, warehouse): | ||||
| 		bin_obj.flags.ignore_permissions = 1 | ||||
| 		bin_obj.insert() | ||||
| 	else: | ||||
| 		bin_obj = frappe.get_doc('Bin', bin) | ||||
| 		bin_obj = frappe.get_cached_doc('Bin', bin) | ||||
| 	bin_obj.flags.ignore_permissions = True | ||||
| 	return bin_obj | ||||
| 
 | ||||
|  | ||||
| @ -366,7 +366,7 @@ | ||||
|  "icon": "fa fa-ticket", | ||||
|  "idx": 7, | ||||
|  "links": [], | ||||
|  "modified": "2020-02-18 21:26:35.636013", | ||||
|  "modified": "2020-02-26 02:19:49.477928", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Support", | ||||
|  "name": "Issue", | ||||
| @ -387,7 +387,7 @@ | ||||
|  "quick_entry": 1, | ||||
|  "search_fields": "status,customer,subject,raised_by", | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "ASC", | ||||
|  "sort_order": "DESC", | ||||
|  "timeline_field": "customer", | ||||
|  "title_field": "subject", | ||||
|  "track_changes": 1, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user