Fixed Stock Entry Test Cases frappe/frappe#478
This commit is contained in:
		
							parent
							
								
									103cc58cb6
								
							
						
					
					
						commit
						2ce39cf770
					
				| @ -21,22 +21,22 @@ class JournalVoucher(AccountsController): | |||||||
| 	def validate(self): | 	def validate(self): | ||||||
| 		if not self.is_opening: | 		if not self.is_opening: | ||||||
| 			self.is_opening='No' | 			self.is_opening='No' | ||||||
| 			 | 
 | ||||||
| 		self.clearance_date = None | 		self.clearance_date = None | ||||||
| 		 | 
 | ||||||
| 		super(JournalVoucher, self).validate_date_with_fiscal_year() | 		super(JournalVoucher, self).validate_date_with_fiscal_year() | ||||||
| 		 | 
 | ||||||
| 		self.validate_debit_credit() | 		self.validate_debit_credit() | ||||||
| 		self.validate_cheque_info() | 		self.validate_cheque_info() | ||||||
| 		self.validate_entries_for_advance() | 		self.validate_entries_for_advance() | ||||||
| 		self.validate_against_jv() | 		self.validate_against_jv() | ||||||
| 		 | 
 | ||||||
| 		self.set_against_account() | 		self.set_against_account() | ||||||
| 		self.create_remarks() | 		self.create_remarks() | ||||||
| 		self.set_aging_date() | 		self.set_aging_date() | ||||||
| 		self.set_print_format_fields() | 		self.set_print_format_fields() | ||||||
| 
 | 
 | ||||||
| 	 | 
 | ||||||
| 	def on_submit(self): | 	def on_submit(self): | ||||||
| 		if self.voucher_type in ['Bank Voucher', 'Contra Voucher', 'Journal Entry']: | 		if self.voucher_type in ['Bank Voucher', 'Contra Voucher', 'Journal Entry']: | ||||||
| 			self.check_credit_days() | 			self.check_credit_days() | ||||||
| @ -46,9 +46,9 @@ class JournalVoucher(AccountsController): | |||||||
| 	def on_cancel(self): | 	def on_cancel(self): | ||||||
| 		from erpnext.accounts.utils import remove_against_link_from_jv | 		from erpnext.accounts.utils import remove_against_link_from_jv | ||||||
| 		remove_against_link_from_jv(self.doctype, self.name, "against_jv") | 		remove_against_link_from_jv(self.doctype, self.name, "against_jv") | ||||||
| 		 | 
 | ||||||
| 		self.make_gl_entries(1) | 		self.make_gl_entries(1) | ||||||
| 		 | 
 | ||||||
| 	def on_trash(self): | 	def on_trash(self): | ||||||
| 		pass | 		pass | ||||||
| 		#if self.amended_from: | 		#if self.amended_from: | ||||||
| @ -57,7 +57,7 @@ class JournalVoucher(AccountsController): | |||||||
| 	def validate_debit_credit(self): | 	def validate_debit_credit(self): | ||||||
| 		for d in self.get('entries'): | 		for d in self.get('entries'): | ||||||
| 			if d.debit and d.credit: | 			if d.debit and d.credit: | ||||||
| 				msgprint("You cannot credit and debit same account at the same time.",  | 				msgprint("You cannot credit and debit same account at the same time.", | ||||||
| 				 	raise_exception=1) | 				 	raise_exception=1) | ||||||
| 
 | 
 | ||||||
| 	def validate_cheque_info(self): | 	def validate_cheque_info(self): | ||||||
| @ -65,7 +65,7 @@ class JournalVoucher(AccountsController): | |||||||
| 			if not self.cheque_no or not self.cheque_date: | 			if not self.cheque_no or not self.cheque_date: | ||||||
| 				msgprint("Reference No & Reference Date is required for %s" % | 				msgprint("Reference No & Reference Date is required for %s" % | ||||||
| 				self.voucher_type, raise_exception=1) | 				self.voucher_type, raise_exception=1) | ||||||
| 				 | 
 | ||||||
| 		if self.cheque_date and not self.cheque_no: | 		if self.cheque_date and not self.cheque_no: | ||||||
| 			msgprint("Reference No is mandatory if you entered Reference Date", raise_exception=1) | 			msgprint("Reference No is mandatory if you entered Reference Date", raise_exception=1) | ||||||
| 
 | 
 | ||||||
| @ -85,11 +85,11 @@ class JournalVoucher(AccountsController): | |||||||
| 				if d.against_jv == self.name: | 				if d.against_jv == self.name: | ||||||
| 					msgprint("You can not enter current voucher in 'Against JV' column", | 					msgprint("You can not enter current voucher in 'Against JV' column", | ||||||
| 						raise_exception=1) | 						raise_exception=1) | ||||||
| 				elif not frappe.db.sql("""select name from `tabJournal Voucher Detail`  | 				elif not frappe.db.sql("""select name from `tabJournal Voucher Detail` | ||||||
| 						where account = %s and docstatus = 1 and parent = %s""",  | 						where account = %s and docstatus = 1 and parent = %s""", | ||||||
| 						(d.account, d.against_jv)): | 						(d.account, d.against_jv)): | ||||||
| 					msgprint("Against JV: %s is not valid." % d.against_jv, raise_exception=1) | 					msgprint("Against JV: %s is not valid." % d.against_jv, raise_exception=1) | ||||||
| 		 | 
 | ||||||
| 	def set_against_account(self): | 	def set_against_account(self): | ||||||
| 		# Debit = Credit | 		# Debit = Credit | ||||||
| 		debit, credit = 0.0, 0.0 | 		debit, credit = 0.0, 0.0 | ||||||
| @ -104,9 +104,9 @@ class JournalVoucher(AccountsController): | |||||||
| 		self.total_credit = credit | 		self.total_credit = credit | ||||||
| 
 | 
 | ||||||
| 		if abs(self.total_debit-self.total_credit) > 0.001: | 		if abs(self.total_debit-self.total_credit) > 0.001: | ||||||
| 			msgprint("Debit must be equal to Credit. The difference is %s" %  | 			msgprint("Debit must be equal to Credit. The difference is %s" % | ||||||
| 			 	(self.total_debit-self.total_credit), raise_exception=1) | 			 	(self.total_debit-self.total_credit), raise_exception=1) | ||||||
| 		 | 
 | ||||||
| 		# update against account | 		# update against account | ||||||
| 		for d in self.get('entries'): | 		for d in self.get('entries'): | ||||||
| 			if flt(d.debit) > 0: d.against_account = ', '.join(credit_list) | 			if flt(d.debit) > 0: d.against_account = ', '.join(credit_list) | ||||||
| @ -114,28 +114,28 @@ class JournalVoucher(AccountsController): | |||||||
| 
 | 
 | ||||||
| 	def create_remarks(self): | 	def create_remarks(self): | ||||||
| 		r = [] | 		r = [] | ||||||
| 		if self.cheque_no : | 		if self.cheque_no: | ||||||
| 			if self.cheque_date: | 			if self.cheque_date: | ||||||
| 				r.append('Via Reference #%s dated %s' %  | 				r.append('Via Reference #%s dated %s' % | ||||||
| 					(self.cheque_no, formatdate(self.cheque_date))) | 					(self.cheque_no, formatdate(self.cheque_date))) | ||||||
| 			else : | 			else : | ||||||
| 				msgprint("Please enter Reference date", raise_exception=1) | 				msgprint("Please enter Reference date", raise_exception=1) | ||||||
| 		 | 
 | ||||||
| 		for d in self.get('entries'): | 		for d in self.get('entries'): | ||||||
| 			if d.against_invoice and d.credit: | 			if d.against_invoice and d.credit: | ||||||
| 				currency = frappe.db.get_value("Sales Invoice", d.against_invoice, "currency") | 				currency = frappe.db.get_value("Sales Invoice", d.against_invoice, "currency") | ||||||
| 				r.append('%s %s against Invoice: %s' %  | 				r.append('%s %s against Invoice: %s' % | ||||||
| 					(cstr(currency), fmt_money(flt(d.credit)), d.against_invoice)) | 					(cstr(currency), fmt_money(flt(d.credit)), d.against_invoice)) | ||||||
| 					 | 
 | ||||||
| 			if d.against_voucher and d.debit: | 			if d.against_voucher and d.debit: | ||||||
| 				bill_no = frappe.db.sql("""select bill_no, bill_date, currency  | 				bill_no = frappe.db.sql("""select bill_no, bill_date, currency | ||||||
| 					from `tabPurchase Invoice` where name=%s""", d.against_voucher) | 					from `tabPurchase Invoice` where name=%s""", d.against_voucher) | ||||||
| 				if bill_no and bill_no[0][0] and bill_no[0][0].lower().strip() \ | 				if bill_no and bill_no[0][0] and bill_no[0][0].lower().strip() \ | ||||||
| 						not in ['na', 'not applicable', 'none']: | 						not in ['na', 'not applicable', 'none']: | ||||||
| 					r.append('%s %s against Bill %s dated %s' %  | 					r.append('%s %s against Bill %s dated %s' % | ||||||
| 						(cstr(bill_no[0][2]), fmt_money(flt(d.debit)), bill_no[0][0],  | 						(cstr(bill_no[0][2]), fmt_money(flt(d.debit)), bill_no[0][0], | ||||||
| 						bill_no[0][1] and formatdate(bill_no[0][1].strftime('%Y-%m-%d')) or '')) | 						bill_no[0][1] and formatdate(bill_no[0][1].strftime('%Y-%m-%d')) or '')) | ||||||
| 	 | 
 | ||||||
| 		if self.user_remark: | 		if self.user_remark: | ||||||
| 			r.append("User Remark : %s"%self.user_remark) | 			r.append("User Remark : %s"%self.user_remark) | ||||||
| 
 | 
 | ||||||
| @ -157,25 +157,25 @@ class JournalVoucher(AccountsController): | |||||||
| 					break | 					break | ||||||
| 
 | 
 | ||||||
| 			# If customer/supplier account, aging date is mandatory | 			# If customer/supplier account, aging date is mandatory | ||||||
| 			if exists and not self.aging_date:  | 			if exists and not self.aging_date: | ||||||
| 				msgprint("Aging Date is mandatory for opening entry", raise_exception=1) | 				msgprint("Aging Date is mandatory for opening entry", raise_exception=1) | ||||||
| 			else: | 			else: | ||||||
| 				self.aging_date = self.posting_date | 				self.aging_date = self.posting_date | ||||||
| 
 | 
 | ||||||
| 	def set_print_format_fields(self): | 	def set_print_format_fields(self): | ||||||
| 		for d in self.get('entries'): | 		for d in self.get('entries'): | ||||||
| 			account_type, master_type = frappe.db.get_value("Account", d.account,  | 			account_type, master_type = frappe.db.get_value("Account", d.account, | ||||||
| 				["account_type", "master_type"]) | 				["account_type", "master_type"]) | ||||||
| 				 | 
 | ||||||
| 			if master_type in ['Supplier', 'Customer']: | 			if master_type in ['Supplier', 'Customer']: | ||||||
| 				if not self.pay_to_recd_from: | 				if not self.pay_to_recd_from: | ||||||
| 					self.pay_to_recd_from = frappe.db.get_value(master_type,  | 					self.pay_to_recd_from = frappe.db.get_value(master_type, | ||||||
| 						' - '.join(d.account.split(' - ')[:-1]),  | 						' - '.join(d.account.split(' - ')[:-1]), | ||||||
| 						master_type == 'Customer' and 'customer_name' or 'supplier_name') | 						master_type == 'Customer' and 'customer_name' or 'supplier_name') | ||||||
| 			 | 
 | ||||||
| 			if account_type in ['Bank', 'Cash']: | 			if account_type in ['Bank', 'Cash']: | ||||||
| 				company_currency = get_company_currency(self.company) | 				company_currency = get_company_currency(self.company) | ||||||
| 				amt = flt(d.debit) and d.debit or d.credit	 | 				amt = flt(d.debit) and d.debit or d.credit | ||||||
| 				self.total_amount = company_currency + ' ' + cstr(amt) | 				self.total_amount = company_currency + ' ' + cstr(amt) | ||||||
| 				from frappe.utils import money_in_words | 				from frappe.utils import money_in_words | ||||||
| 				self.total_amount_in_words = money_in_words(amt, company_currency) | 				self.total_amount_in_words = money_in_words(amt, company_currency) | ||||||
| @ -184,29 +184,29 @@ class JournalVoucher(AccountsController): | |||||||
| 		date_diff = 0 | 		date_diff = 0 | ||||||
| 		if self.cheque_date: | 		if self.cheque_date: | ||||||
| 			date_diff = (getdate(self.cheque_date)-getdate(self.posting_date)).days | 			date_diff = (getdate(self.cheque_date)-getdate(self.posting_date)).days | ||||||
| 		 | 
 | ||||||
| 		if date_diff <= 0: return | 		if date_diff <= 0: return | ||||||
| 		 | 
 | ||||||
| 		# Get List of Customer Account | 		# Get List of Customer Account | ||||||
| 		acc_list = filter(lambda d: frappe.db.get_value("Account", d.account,  | 		acc_list = filter(lambda d: frappe.db.get_value("Account", d.account, | ||||||
| 		 	"master_type")=='Customer', self.get('entries')) | 		 	"master_type")=='Customer', self.get('entries')) | ||||||
| 		 | 
 | ||||||
| 		for d in acc_list: | 		for d in acc_list: | ||||||
| 			credit_days = self.get_credit_days_for(d.account) | 			credit_days = self.get_credit_days_for(d.account) | ||||||
| 			# Check credit days | 			# Check credit days | ||||||
| 			if credit_days > 0 and not self.get_authorized_user() and cint(date_diff) > credit_days: | 			if credit_days > 0 and not self.get_authorized_user() and cint(date_diff) > credit_days: | ||||||
| 				msgprint("Credit Not Allowed: Cannot allow a check that is dated \ | 				msgprint("Credit Not Allowed: Cannot allow a check that is dated \ | ||||||
| 					more than %s days after the posting date" % credit_days, raise_exception=1) | 					more than %s days after the posting date" % credit_days, raise_exception=1) | ||||||
| 					 | 
 | ||||||
| 	def get_credit_days_for(self, ac): | 	def get_credit_days_for(self, ac): | ||||||
| 		if not self.credit_days_for.has_key(ac): | 		if not self.credit_days_for.has_key(ac): | ||||||
| 			self.credit_days_for[ac] = cint(frappe.db.get_value("Account", ac, "credit_days")) | 			self.credit_days_for[ac] = cint(frappe.db.get_value("Account", ac, "credit_days")) | ||||||
| 
 | 
 | ||||||
| 		if not self.credit_days_for[ac]: | 		if not self.credit_days_for[ac]: | ||||||
| 			if self.credit_days_global==-1: | 			if self.credit_days_global==-1: | ||||||
| 				self.credit_days_global = cint(frappe.db.get_value("Company",  | 				self.credit_days_global = cint(frappe.db.get_value("Company", | ||||||
| 					self.company, "credit_days")) | 					self.company, "credit_days")) | ||||||
| 					 | 
 | ||||||
| 			return self.credit_days_global | 			return self.credit_days_global | ||||||
| 		else: | 		else: | ||||||
| 			return self.credit_days_for[ac] | 			return self.credit_days_for[ac] | ||||||
| @ -216,33 +216,33 @@ class JournalVoucher(AccountsController): | |||||||
| 			self.is_approving_authority = 0 | 			self.is_approving_authority = 0 | ||||||
| 
 | 
 | ||||||
| 			# Fetch credit controller role | 			# Fetch credit controller role | ||||||
| 			approving_authority = frappe.db.get_value("Global Defaults", None,  | 			approving_authority = frappe.db.get_value("Global Defaults", None, | ||||||
| 				"credit_controller") | 				"credit_controller") | ||||||
| 			 | 
 | ||||||
| 			# Check logged-in user is authorized | 			# Check logged-in user is authorized | ||||||
| 			if approving_authority in frappe.user.get_roles(): | 			if approving_authority in frappe.user.get_roles(): | ||||||
| 				self.is_approving_authority = 1 | 				self.is_approving_authority = 1 | ||||||
| 				 | 
 | ||||||
| 		return self.is_approving_authority | 		return self.is_approving_authority | ||||||
| 
 | 
 | ||||||
| 	def check_account_against_entries(self): | 	def check_account_against_entries(self): | ||||||
| 		for d in self.get("entries"): | 		for d in self.get("entries"): | ||||||
| 			if d.against_invoice and frappe.db.get_value("Sales Invoice",  | 			if d.against_invoice and frappe.db.get_value("Sales Invoice", | ||||||
| 					d.against_invoice, "debit_to") != d.account: | 					d.against_invoice, "debit_to") != d.account: | ||||||
| 				frappe.throw(_("Row #") + cstr(d.idx) +  ": " + | 				frappe.throw(_("Row #") + cstr(d.idx) +  ": " + | ||||||
| 					_("Account is not matching with Debit To account of Sales Invoice")) | 					_("Account is not matching with Debit To account of Sales Invoice")) | ||||||
| 			 | 
 | ||||||
| 			if d.against_voucher and frappe.db.get_value("Purchase Invoice",  | 			if d.against_voucher and frappe.db.get_value("Purchase Invoice", | ||||||
| 					d.against_voucher, "credit_to") != d.account: | 					d.against_voucher, "credit_to") != d.account: | ||||||
| 				frappe.throw(_("Row #") + cstr(d.idx) + ": " + | 				frappe.throw(_("Row #") + cstr(d.idx) + ": " + | ||||||
| 					_("Account is not matching with Credit To account of Purchase Invoice")) | 					_("Account is not matching with Credit To account of Purchase Invoice")) | ||||||
| 
 | 
 | ||||||
| 	def make_gl_entries(self, cancel=0, adv_adj=0): | 	def make_gl_entries(self, cancel=0, adv_adj=0): | ||||||
| 		from erpnext.accounts.general_ledger import make_gl_entries | 		from erpnext.accounts.general_ledger import make_gl_entries | ||||||
| 		 | 
 | ||||||
| 		if not cancel: | 		if not cancel: | ||||||
| 			self.check_account_against_entries() | 			self.check_account_against_entries() | ||||||
| 		 | 
 | ||||||
| 		gl_map = [] | 		gl_map = [] | ||||||
| 		for d in self.get("entries"): | 		for d in self.get("entries"): | ||||||
| 			if d.debit or d.credit: | 			if d.debit or d.credit: | ||||||
| @ -252,8 +252,8 @@ class JournalVoucher(AccountsController): | |||||||
| 						"against": d.against_account, | 						"against": d.against_account, | ||||||
| 						"debit": d.debit, | 						"debit": d.debit, | ||||||
| 						"credit": d.credit, | 						"credit": d.credit, | ||||||
| 						"against_voucher_type": ((d.against_voucher and "Purchase Invoice")  | 						"against_voucher_type": ((d.against_voucher and "Purchase Invoice") | ||||||
| 							or (d.against_invoice and "Sales Invoice")  | 							or (d.against_invoice and "Sales Invoice") | ||||||
| 							or (d.against_jv and "Journal Voucher")), | 							or (d.against_jv and "Journal Voucher")), | ||||||
| 						"against_voucher": d.against_voucher or d.against_invoice or d.against_jv, | 						"against_voucher": d.against_voucher or d.against_invoice or d.against_jv, | ||||||
| 						"remarks": self.remark, | 						"remarks": self.remark, | ||||||
| @ -262,10 +262,10 @@ class JournalVoucher(AccountsController): | |||||||
| 				) | 				) | ||||||
| 		if gl_map: | 		if gl_map: | ||||||
| 			make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj) | 			make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj) | ||||||
| 			 | 
 | ||||||
| 	def check_credit_limit(self): | 	def check_credit_limit(self): | ||||||
| 		for d in self.get("entries"): | 		for d in self.get("entries"): | ||||||
| 			master_type, master_name = frappe.db.get_value("Account", d.account,  | 			master_type, master_name = frappe.db.get_value("Account", d.account, | ||||||
| 				["master_type", "master_name"]) | 				["master_type", "master_name"]) | ||||||
| 			if master_type == "Customer" and master_name: | 			if master_type == "Customer" and master_name: | ||||||
| 				super(JournalVoucher, self).check_credit_limit(d.account) | 				super(JournalVoucher, self).check_credit_limit(d.account) | ||||||
| @ -276,7 +276,7 @@ class JournalVoucher(AccountsController): | |||||||
| 		else: | 		else: | ||||||
| 			flag, self.total_debit, self.total_credit = 0, 0, 0 | 			flag, self.total_debit, self.total_credit = 0, 0, 0 | ||||||
| 			diff = flt(self.difference, 2) | 			diff = flt(self.difference, 2) | ||||||
| 			 | 
 | ||||||
| 			# If any row without amount, set the diff on that row | 			# If any row without amount, set the diff on that row | ||||||
| 			for d in self.get('entries'): | 			for d in self.get('entries'): | ||||||
| 				if not d.credit and not d.debit and diff != 0: | 				if not d.credit and not d.debit and diff != 0: | ||||||
| @ -285,7 +285,7 @@ class JournalVoucher(AccountsController): | |||||||
| 					elif diff<0: | 					elif diff<0: | ||||||
| 						d.debit = diff | 						d.debit = diff | ||||||
| 					flag = 1 | 					flag = 1 | ||||||
| 					 | 
 | ||||||
| 			# Set the diff in a new row | 			# Set the diff in a new row | ||||||
| 			if flag == 0 and diff != 0: | 			if flag == 0 and diff != 0: | ||||||
| 				jd = self.append('entries', {}) | 				jd = self.append('entries', {}) | ||||||
| @ -293,7 +293,7 @@ class JournalVoucher(AccountsController): | |||||||
| 					jd.credit = abs(diff) | 					jd.credit = abs(diff) | ||||||
| 				elif diff<0: | 				elif diff<0: | ||||||
| 					jd.debit = abs(diff) | 					jd.debit = abs(diff) | ||||||
| 					 | 
 | ||||||
| 			# Set the total debit, total credit and difference | 			# Set the total debit, total credit and difference | ||||||
| 			for d in self.get('entries'): | 			for d in self.get('entries'): | ||||||
| 				self.total_debit += flt(d.debit, 2) | 				self.total_debit += flt(d.debit, 2) | ||||||
| @ -326,12 +326,12 @@ class JournalVoucher(AccountsController): | |||||||
| 		cond = (flt(self.write_off_amount) > 0) and \ | 		cond = (flt(self.write_off_amount) > 0) and \ | ||||||
| 			' and outstanding_amount <= '+ self.write_off_amount or '' | 			' and outstanding_amount <= '+ self.write_off_amount or '' | ||||||
| 		if self.write_off_based_on == 'Accounts Receivable': | 		if self.write_off_based_on == 'Accounts Receivable': | ||||||
| 			return frappe.db.sql("""select name, debit_to, outstanding_amount  | 			return frappe.db.sql("""select name, debit_to, outstanding_amount | ||||||
| 				from `tabSales Invoice` where docstatus = 1 and company = %s  | 				from `tabSales Invoice` where docstatus = 1 and company = %s | ||||||
| 				and outstanding_amount > 0 %s""" % ('%s', cond), self.company) | 				and outstanding_amount > 0 %s""" % ('%s', cond), self.company) | ||||||
| 		elif self.write_off_based_on == 'Accounts Payable': | 		elif self.write_off_based_on == 'Accounts Payable': | ||||||
| 			return frappe.db.sql("""select name, credit_to, outstanding_amount  | 			return frappe.db.sql("""select name, credit_to, outstanding_amount | ||||||
| 				from `tabPurchase Invoice` where docstatus = 1 and company = %s  | 				from `tabPurchase Invoice` where docstatus = 1 and company = %s | ||||||
| 				and outstanding_amount > 0 %s""" % ('%s', cond), self.company) | 				and outstanding_amount > 0 %s""" % ('%s', cond), self.company) | ||||||
| 
 | 
 | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
| @ -344,7 +344,7 @@ def get_default_bank_cash_account(company, voucher_type): | |||||||
| 			"account": account, | 			"account": account, | ||||||
| 			"balance": get_balance_on(account) | 			"balance": get_balance_on(account) | ||||||
| 		} | 		} | ||||||
| 		 | 
 | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
| def get_payment_entry_from_sales_invoice(sales_invoice): | def get_payment_entry_from_sales_invoice(sales_invoice): | ||||||
| 	from erpnext.accounts.utils import get_balance_on | 	from erpnext.accounts.utils import get_balance_on | ||||||
| @ -360,7 +360,7 @@ def get_payment_entry_from_sales_invoice(sales_invoice): | |||||||
| 
 | 
 | ||||||
| 	# debit bank | 	# debit bank | ||||||
| 	jv.get("entries")[1].debit = si.outstanding_amount | 	jv.get("entries")[1].debit = si.outstanding_amount | ||||||
| 	 | 
 | ||||||
| 	return jv.as_dict() | 	return jv.as_dict() | ||||||
| 
 | 
 | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
| @ -369,7 +369,7 @@ def get_payment_entry_from_purchase_invoice(purchase_invoice): | |||||||
| 	pi = frappe.get_doc("Purchase Invoice", purchase_invoice) | 	pi = frappe.get_doc("Purchase Invoice", purchase_invoice) | ||||||
| 	jv = get_payment_entry(pi) | 	jv = get_payment_entry(pi) | ||||||
| 	jv.remark = 'Payment against Purchase Invoice {0}. {1}'.format(pi.name, pi.remarks) | 	jv.remark = 'Payment against Purchase Invoice {0}. {1}'.format(pi.name, pi.remarks) | ||||||
| 	 | 
 | ||||||
| 	# credit supplier | 	# credit supplier | ||||||
| 	jv.get("entries")[0].account = pi.credit_to | 	jv.get("entries")[0].account = pi.credit_to | ||||||
| 	jv.get("entries")[0].balance = get_balance_on(pi.credit_to) | 	jv.get("entries")[0].balance = get_balance_on(pi.credit_to) | ||||||
| @ -378,12 +378,12 @@ def get_payment_entry_from_purchase_invoice(purchase_invoice): | |||||||
| 
 | 
 | ||||||
| 	# credit bank | 	# credit bank | ||||||
| 	jv.get("entries")[1].credit = pi.outstanding_amount | 	jv.get("entries")[1].credit = pi.outstanding_amount | ||||||
| 	 | 
 | ||||||
| 	return jv.as_dict() | 	return jv.as_dict() | ||||||
| 
 | 
 | ||||||
| def get_payment_entry(doc): | def get_payment_entry(doc): | ||||||
| 	bank_account = get_default_bank_cash_account(doc.company, "Bank Voucher") | 	bank_account = get_default_bank_cash_account(doc.company, "Bank Voucher") | ||||||
| 	 | 
 | ||||||
| 	jv = frappe.new_doc('Journal Voucher') | 	jv = frappe.new_doc('Journal Voucher') | ||||||
| 	jv.voucher_type = 'Bank Voucher' | 	jv.voucher_type = 'Bank Voucher' | ||||||
| 
 | 
 | ||||||
| @ -396,63 +396,63 @@ def get_payment_entry(doc): | |||||||
| 	if bank_account: | 	if bank_account: | ||||||
| 		d2.account = bank_account["account"] | 		d2.account = bank_account["account"] | ||||||
| 		d2.balance = bank_account["balance"] | 		d2.balance = bank_account["balance"] | ||||||
| 	 | 
 | ||||||
| 	return jv | 	return jv | ||||||
| 	 | 
 | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
| def get_opening_accounts(company): | def get_opening_accounts(company): | ||||||
| 	"""get all balance sheet accounts for opening entry""" | 	"""get all balance sheet accounts for opening entry""" | ||||||
| 	from erpnext.accounts.utils import get_balance_on | 	from erpnext.accounts.utils import get_balance_on | ||||||
| 	accounts = frappe.db.sql_list("""select name from tabAccount  | 	accounts = frappe.db.sql_list("""select name from tabAccount | ||||||
| 		where group_or_ledger='Ledger' and report_type='Profit and Loss' and company=%s""", company) | 		where group_or_ledger='Ledger' and report_type='Profit and Loss' and company=%s""", company) | ||||||
| 	 | 
 | ||||||
| 	return [{"account": a, "balance": get_balance_on(a)} for a in accounts] | 	return [{"account": a, "balance": get_balance_on(a)} for a in accounts] | ||||||
| 	 | 
 | ||||||
| def get_against_purchase_invoice(doctype, txt, searchfield, start, page_len, filters): | def get_against_purchase_invoice(doctype, txt, searchfield, start, page_len, filters): | ||||||
| 	return frappe.db.sql("""select name, credit_to, outstanding_amount, bill_no, bill_date  | 	return frappe.db.sql("""select name, credit_to, outstanding_amount, bill_no, bill_date | ||||||
| 		from `tabPurchase Invoice` where credit_to = %s and docstatus = 1  | 		from `tabPurchase Invoice` where credit_to = %s and docstatus = 1 | ||||||
| 		and outstanding_amount > 0 and %s like %s order by name desc limit %s, %s""" % | 		and outstanding_amount > 0 and %s like %s order by name desc limit %s, %s""" % | ||||||
| 		("%s", searchfield, "%s", "%s", "%s"),  | 		("%s", searchfield, "%s", "%s", "%s"), | ||||||
| 		(filters["account"], "%%%s%%" % txt, start, page_len)) |  | ||||||
| 		 |  | ||||||
| def get_against_sales_invoice(doctype, txt, searchfield, start, page_len, filters): |  | ||||||
| 	return frappe.db.sql("""select name, debit_to, outstanding_amount  |  | ||||||
| 		from `tabSales Invoice` where debit_to = %s and docstatus = 1  |  | ||||||
| 		and outstanding_amount > 0 and `%s` like %s order by name desc limit %s, %s""" % |  | ||||||
| 		("%s", searchfield, "%s", "%s", "%s"),  |  | ||||||
| 		(filters["account"], "%%%s%%" % txt, start, page_len)) |  | ||||||
| 		 |  | ||||||
| def get_against_jv(doctype, txt, searchfield, start, page_len, filters): |  | ||||||
| 	return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark  |  | ||||||
| 		from `tabJournal Voucher` jv, `tabJournal Voucher Detail` jv_detail  |  | ||||||
| 		where jv_detail.parent = jv.name and jv_detail.account = %s and jv.docstatus = 1  |  | ||||||
| 		and jv.%s like %s order by jv.name desc limit %s, %s""" %  |  | ||||||
| 		("%s", searchfield, "%s", "%s", "%s"),  |  | ||||||
| 		(filters["account"], "%%%s%%" % txt, start, page_len)) | 		(filters["account"], "%%%s%%" % txt, start, page_len)) | ||||||
| 
 | 
 | ||||||
| @frappe.whitelist()		 | def get_against_sales_invoice(doctype, txt, searchfield, start, page_len, filters): | ||||||
|  | 	return frappe.db.sql("""select name, debit_to, outstanding_amount | ||||||
|  | 		from `tabSales Invoice` where debit_to = %s and docstatus = 1 | ||||||
|  | 		and outstanding_amount > 0 and `%s` like %s order by name desc limit %s, %s""" % | ||||||
|  | 		("%s", searchfield, "%s", "%s", "%s"), | ||||||
|  | 		(filters["account"], "%%%s%%" % txt, start, page_len)) | ||||||
|  | 
 | ||||||
|  | def get_against_jv(doctype, txt, searchfield, start, page_len, filters): | ||||||
|  | 	return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark | ||||||
|  | 		from `tabJournal Voucher` jv, `tabJournal Voucher Detail` jv_detail | ||||||
|  | 		where jv_detail.parent = jv.name and jv_detail.account = %s and jv.docstatus = 1 | ||||||
|  | 		and jv.%s like %s order by jv.name desc limit %s, %s""" % | ||||||
|  | 		("%s", searchfield, "%s", "%s", "%s"), | ||||||
|  | 		(filters["account"], "%%%s%%" % txt, start, page_len)) | ||||||
|  | 
 | ||||||
|  | @frappe.whitelist() | ||||||
| def get_outstanding(args): | def get_outstanding(args): | ||||||
| 	args = eval(args) | 	args = eval(args) | ||||||
| 	if args.get("doctype") == "Journal Voucher" and args.get("account"): | 	if args.get("doctype") == "Journal Voucher" and args.get("account"): | ||||||
| 		against_jv_amount = frappe.db.sql(""" | 		against_jv_amount = frappe.db.sql(""" | ||||||
| 			select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))  | 			select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) | ||||||
| 			from `tabJournal Voucher Detail` where parent=%s and account=%s  | 			from `tabJournal Voucher Detail` where parent=%s and account=%s | ||||||
| 			and ifnull(against_invoice, '')='' and ifnull(against_voucher, '')='' | 			and ifnull(against_invoice, '')='' and ifnull(against_voucher, '')='' | ||||||
| 			and ifnull(against_jv, '')=''""", (args['docname'], args['account'])) | 			and ifnull(against_jv, '')=''""", (args['docname'], args['account'])) | ||||||
| 			 | 
 | ||||||
| 		against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0 | 		against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0 | ||||||
| 		if against_jv_amount > 0: | 		if against_jv_amount > 0: | ||||||
| 			return {"credit": against_jv_amount} | 			return {"credit": against_jv_amount} | ||||||
| 		else: | 		else: | ||||||
| 			return {"debit": -1* against_jv_amount} | 			return {"debit": -1* against_jv_amount} | ||||||
| 		 | 
 | ||||||
| 	elif args.get("doctype") == "Sales Invoice": | 	elif args.get("doctype") == "Sales Invoice": | ||||||
| 		return { | 		return { | ||||||
| 			"credit": flt(frappe.db.get_value("Sales Invoice", args["docname"],  | 			"credit": flt(frappe.db.get_value("Sales Invoice", args["docname"], | ||||||
| 				"outstanding_amount")) | 				"outstanding_amount")) | ||||||
| 		} | 		} | ||||||
| 	elif args.get("doctype") == "Purchase Invoice": | 	elif args.get("doctype") == "Purchase Invoice": | ||||||
| 		return { | 		return { | ||||||
| 			"debit": flt(frappe.db.get_value("Purchase Invoice", args["docname"],  | 			"debit": flt(frappe.db.get_value("Purchase Invoice", args["docname"], | ||||||
| 				"outstanding_amount")) | 				"outstanding_amount")) | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -28,13 +28,13 @@ class PurchaseInvoice(BuyingController): | |||||||
| 		'source_field': 'amount', | 		'source_field': 'amount', | ||||||
| 		'percent_join_field': 'purchase_order', | 		'percent_join_field': 'purchase_order', | ||||||
| 	}] | 	}] | ||||||
| 		 | 
 | ||||||
| 	def validate(self): | 	def validate(self): | ||||||
| 		if not self.is_opening: | 		if not self.is_opening: | ||||||
| 			self.is_opening = 'No' | 			self.is_opening = 'No' | ||||||
| 			 | 
 | ||||||
| 		super(PurchaseInvoice, self).validate() | 		super(PurchaseInvoice, self).validate() | ||||||
| 		 | 
 | ||||||
| 		self.po_required() | 		self.po_required() | ||||||
| 		self.pr_required() | 		self.pr_required() | ||||||
| 		self.check_active_purchase_items() | 		self.check_active_purchase_items() | ||||||
| @ -51,22 +51,22 @@ class PurchaseInvoice(BuyingController): | |||||||
| 		self.validate_write_off_account() | 		self.validate_write_off_account() | ||||||
| 		self.update_raw_material_cost() | 		self.update_raw_material_cost() | ||||||
| 		self.update_valuation_rate("entries") | 		self.update_valuation_rate("entries") | ||||||
| 		self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount",  | 		self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", | ||||||
| 			"purchase_receipt_details") | 			"purchase_receipt_details") | ||||||
| 	 | 
 | ||||||
| 	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(self.company, self.supplier, "Supplier") | 			self.credit_to = get_party_account(self.company, self.supplier, "Supplier") | ||||||
| 		if not self.due_date: | 		if not self.due_date: | ||||||
| 			self.due_date = get_due_date(self.posting_date, self.supplier, "Supplier", | 			self.due_date = get_due_date(self.posting_date, self.supplier, "Supplier", | ||||||
| 				self.credit_to, self.company) | 				self.credit_to, self.company) | ||||||
| 		 | 
 | ||||||
| 		super(PurchaseInvoice, self).set_missing_values(for_validate) | 		super(PurchaseInvoice, self).set_missing_values(for_validate) | ||||||
| 	 | 
 | ||||||
| 	def get_advances(self): | 	def get_advances(self): | ||||||
| 		super(PurchaseInvoice, self).get_advances(self.credit_to,  | 		super(PurchaseInvoice, self).get_advances(self.credit_to, | ||||||
| 			"Purchase Invoice Advance", "advance_allocation_details", "debit") | 			"Purchase Invoice Advance", "advance_allocation_details", "debit") | ||||||
| 		 | 
 | ||||||
| 	def check_active_purchase_items(self): | 	def check_active_purchase_items(self): | ||||||
| 		for d in self.get('entries'): | 		for d in self.get('entries'): | ||||||
| 			if d.item_code:		# extra condn coz item_code is not mandatory in PV | 			if d.item_code:		# extra condn coz item_code is not mandatory in PV | ||||||
| @ -77,29 +77,29 @@ class PurchaseInvoice(BuyingController): | |||||||
| 				if not valid_item[0][1] == 'Yes': | 				if not valid_item[0][1] == 'Yes': | ||||||
| 					msgprint("Item : '%s' is not Purchase Item"%(d.item_code)) | 					msgprint("Item : '%s' is not Purchase Item"%(d.item_code)) | ||||||
| 					raise Exception | 					raise Exception | ||||||
| 						 | 
 | ||||||
| 	def check_conversion_rate(self): | 	def check_conversion_rate(self): | ||||||
| 		default_currency = get_company_currency(self.company)		 | 		default_currency = get_company_currency(self.company) | ||||||
| 		if not default_currency: | 		if not default_currency: | ||||||
| 			msgprint('Message: Please enter default currency in Company Master') | 			msgprint('Message: Please enter default currency in Company Master') | ||||||
| 			raise Exception | 			raise Exception | ||||||
| 		if (self.currency == default_currency and flt(self.conversion_rate) != 1.00) or not self.conversion_rate or (self.currency != default_currency and flt(self.conversion_rate) == 1.00): | 		if (self.currency == default_currency and flt(self.conversion_rate) != 1.00) or not self.conversion_rate or (self.currency != default_currency and flt(self.conversion_rate) == 1.00): | ||||||
| 			msgprint("Message: Please Enter Appropriate Conversion Rate.") | 			msgprint("Message: Please Enter Appropriate Conversion Rate.") | ||||||
| 			raise Exception				 | 			raise Exception | ||||||
| 			 | 
 | ||||||
| 	def validate_bill_no(self): | 	def validate_bill_no(self): | ||||||
| 		if self.bill_no and self.bill_no.lower().strip() \ | 		if self.bill_no and self.bill_no.lower().strip() \ | ||||||
| 				not in ['na', 'not applicable', 'none']: | 				not in ['na', 'not applicable', 'none']: | ||||||
| 			b_no = frappe.db.sql("""select bill_no, name, ifnull(is_opening,'') from `tabPurchase Invoice`  | 			b_no = frappe.db.sql("""select bill_no, name, ifnull(is_opening,'') from `tabPurchase Invoice` | ||||||
| 				where bill_no = %s and credit_to = %s and docstatus = 1 and name != %s""",  | 				where bill_no = %s and credit_to = %s and docstatus = 1 and name != %s""", | ||||||
| 				(self.bill_no, self.credit_to, self.name)) | 				(self.bill_no, self.credit_to, self.name)) | ||||||
| 			if b_no and cstr(b_no[0][2]) == cstr(self.is_opening): | 			if b_no and cstr(b_no[0][2]) == cstr(self.is_opening): | ||||||
| 				msgprint("Please check you have already booked expense against Bill No. %s \ | 				msgprint("Please check you have already booked expense against Bill No. %s \ | ||||||
| 					in Purchase Invoice %s" % (cstr(b_no[0][0]), cstr(b_no[0][1])),  | 					in Purchase Invoice %s" % (cstr(b_no[0][0]), cstr(b_no[0][1])), | ||||||
| 					raise_exception=1) | 					raise_exception=1) | ||||||
| 					 | 
 | ||||||
| 			if not self.remarks and self.bill_date: | 			if not self.remarks and self.bill_date: | ||||||
| 				self.remarks = (self.remarks or '') + "\n" + ("Against Bill %s dated %s"  | 				self.remarks = (self.remarks or '') + "\n" + ("Against Bill %s dated %s" | ||||||
| 					% (self.bill_no, formatdate(self.bill_date))) | 					% (self.bill_no, formatdate(self.bill_date))) | ||||||
| 
 | 
 | ||||||
| 		if not self.remarks: | 		if not self.remarks: | ||||||
| @ -108,28 +108,28 @@ class PurchaseInvoice(BuyingController): | |||||||
| 	def validate_credit_acc(self): | 	def validate_credit_acc(self): | ||||||
| 		if frappe.db.get_value("Account", self.credit_to, "report_type") != "Balance Sheet": | 		if frappe.db.get_value("Account", self.credit_to, "report_type") != "Balance Sheet": | ||||||
| 			frappe.throw(_("Account must be a balance sheet account")) | 			frappe.throw(_("Account must be a balance sheet account")) | ||||||
| 	 | 
 | ||||||
| 	# Validate Acc Head of Supplier and Credit To Account entered | 	# Validate Acc Head of Supplier and Credit To Account entered | ||||||
| 	# ------------------------------------------------------------ | 	# ------------------------------------------------------------ | ||||||
| 	def check_for_acc_head_of_supplier(self):  | 	def check_for_acc_head_of_supplier(self): | ||||||
| 		if self.supplier and self.credit_to: | 		if self.supplier and self.credit_to: | ||||||
| 			acc_head = frappe.db.sql("select master_name from `tabAccount` where name = %s", self.credit_to) | 			acc_head = frappe.db.sql("select master_name from `tabAccount` where name = %s", self.credit_to) | ||||||
| 			 | 
 | ||||||
| 			if (acc_head and cstr(acc_head[0][0]) != cstr(self.supplier)) or (not acc_head and (self.credit_to != cstr(self.supplier) + " - " + self.company_abbr)): | 			if (acc_head and cstr(acc_head[0][0]) != cstr(self.supplier)) or (not acc_head and (self.credit_to != cstr(self.supplier) + " - " + self.company_abbr)): | ||||||
| 				msgprint("Credit To: %s do not match with Supplier: %s for Company: %s.\n If both correctly entered, please select Master Type and Master Name in account master." %(self.credit_to,self.supplier,self.company), raise_exception=1) | 				msgprint("Credit To: %s do not match with Supplier: %s for Company: %s.\n If both correctly entered, please select Master Type and Master Name in account master." %(self.credit_to,self.supplier,self.company), raise_exception=1) | ||||||
| 				 | 
 | ||||||
| 	# Check for Stopped PO | 	# Check for Stopped PO | ||||||
| 	# --------------------- | 	# --------------------- | ||||||
| 	def check_for_stopped_status(self): | 	def check_for_stopped_status(self): | ||||||
| 		check_list = [] | 		check_list = [] | ||||||
| 		for d in self.get('entries'): | 		for d in self.get('entries'): | ||||||
| 			if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt: | 			if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt: | ||||||
| 				check_list.append(d.purhcase_order) | 				check_list.append(d.purchase_order) | ||||||
| 				stopped = frappe.db.sql("select name from `tabPurchase Order` where status = 'Stopped' and name = %s", d.purchase_order) | 				stopped = frappe.db.sql("select name from `tabPurchase Order` where status = 'Stopped' and name = %s", d.purchase_order) | ||||||
| 				if stopped: | 				if stopped: | ||||||
| 					msgprint("One cannot do any transaction against 'Purchase Order' : %s, it's status is 'Stopped'" % (d.purhcase_order)) | 					msgprint("One cannot do any transaction against 'Purchase Order' : %s, it's status is 'Stopped'" % (d.purhcase_order)) | ||||||
| 					raise Exception | 					raise Exception | ||||||
| 		 | 
 | ||||||
| 	def validate_with_previous_doc(self): | 	def validate_with_previous_doc(self): | ||||||
| 		super(PurchaseInvoice, self).validate_with_previous_doc(self.tname, { | 		super(PurchaseInvoice, self).validate_with_previous_doc(self.tname, { | ||||||
| 			"Purchase Order": { | 			"Purchase Order": { | ||||||
| @ -152,7 +152,7 @@ class PurchaseInvoice(BuyingController): | |||||||
| 				"is_child_table": True | 				"is_child_table": True | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 		 | 
 | ||||||
| 		if cint(frappe.defaults.get_global_default('maintain_same_rate')): | 		if cint(frappe.defaults.get_global_default('maintain_same_rate')): | ||||||
| 			super(PurchaseInvoice, self).validate_with_previous_doc(self.tname, { | 			super(PurchaseInvoice, self).validate_with_previous_doc(self.tname, { | ||||||
| 				"Purchase Order Item": { | 				"Purchase Order Item": { | ||||||
| @ -167,21 +167,21 @@ class PurchaseInvoice(BuyingController): | |||||||
| 					"is_child_table": True | 					"is_child_table": True | ||||||
| 				} | 				} | ||||||
| 			}) | 			}) | ||||||
| 			 | 
 | ||||||
| 					 | 
 | ||||||
| 	def set_aging_date(self): | 	def set_aging_date(self): | ||||||
| 		if self.is_opening != 'Yes': | 		if self.is_opening != 'Yes': | ||||||
| 			self.aging_date = self.posting_date | 			self.aging_date = self.posting_date | ||||||
| 		elif not self.aging_date: | 		elif not self.aging_date: | ||||||
| 			msgprint("Aging Date is mandatory for opening entry") | 			msgprint("Aging Date is mandatory for opening entry") | ||||||
| 			raise Exception | 			raise Exception | ||||||
| 			 | 
 | ||||||
| 	def set_against_expense_account(self): | 	def set_against_expense_account(self): | ||||||
| 		auto_accounting_for_stock = cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) | 		auto_accounting_for_stock = cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) | ||||||
| 
 | 
 | ||||||
| 		if auto_accounting_for_stock: | 		if auto_accounting_for_stock: | ||||||
| 			stock_not_billed_account = self.get_company_default("stock_received_but_not_billed") | 			stock_not_billed_account = self.get_company_default("stock_received_but_not_billed") | ||||||
| 		 | 
 | ||||||
| 		against_accounts = [] | 		against_accounts = [] | ||||||
| 		stock_items = self.get_stock_items() | 		stock_items = self.get_stock_items() | ||||||
| 		for item in self.get("entries"): | 		for item in self.get("entries"): | ||||||
| @ -191,18 +191,18 @@ class PurchaseInvoice(BuyingController): | |||||||
| 				# Stock Received But Not Billed for a stock item | 				# Stock Received But Not Billed for a stock item | ||||||
| 				item.expense_account = stock_not_billed_account | 				item.expense_account = stock_not_billed_account | ||||||
| 				item.cost_center = None | 				item.cost_center = None | ||||||
| 				 | 
 | ||||||
| 				if stock_not_billed_account not in against_accounts: | 				if stock_not_billed_account not in against_accounts: | ||||||
| 					against_accounts.append(stock_not_billed_account) | 					against_accounts.append(stock_not_billed_account) | ||||||
| 			 | 
 | ||||||
| 			elif not item.expense_account: | 			elif not item.expense_account: | ||||||
| 				msgprint(_("Expense account is mandatory for item") + ": " +  | 				msgprint(_("Expense account is mandatory for item") + ": " + | ||||||
| 					(item.item_code or item.item_name), raise_exception=1) | 					(item.item_code or item.item_name), raise_exception=1) | ||||||
| 			 | 
 | ||||||
| 			elif item.expense_account not in against_accounts: | 			elif item.expense_account not in against_accounts: | ||||||
| 				# if no auto_accounting_for_stock or not a stock item | 				# if no auto_accounting_for_stock or not a stock item | ||||||
| 				against_accounts.append(item.expense_account) | 				against_accounts.append(item.expense_account) | ||||||
| 				 | 
 | ||||||
| 		self.against_expense_account = ",".join(against_accounts) | 		self.against_expense_account = ",".join(against_accounts) | ||||||
| 
 | 
 | ||||||
| 	def po_required(self): | 	def po_required(self): | ||||||
| @ -233,8 +233,8 @@ class PurchaseInvoice(BuyingController): | |||||||
| 				submitted = frappe.db.sql("select name from `tabPurchase Receipt` where docstatus = 1 and name = %s", d.purchase_receipt) | 				submitted = frappe.db.sql("select name from `tabPurchase Receipt` where docstatus = 1 and name = %s", d.purchase_receipt) | ||||||
| 				if not submitted: | 				if not submitted: | ||||||
| 					frappe.throw("Purchase Receipt : "+ cstr(d.purchase_receipt) +" is not submitted") | 					frappe.throw("Purchase Receipt : "+ cstr(d.purchase_receipt) +" is not submitted") | ||||||
| 					 | 
 | ||||||
| 					 | 
 | ||||||
| 	def update_against_document_in_jv(self): | 	def update_against_document_in_jv(self): | ||||||
| 		""" | 		""" | ||||||
| 			Links invoice and advance voucher: | 			Links invoice and advance voucher: | ||||||
| @ -242,33 +242,33 @@ class PurchaseInvoice(BuyingController): | |||||||
| 				2. split into multiple rows if partially adjusted, assign against voucher | 				2. split into multiple rows if partially adjusted, assign against voucher | ||||||
| 				3. submit advance voucher | 				3. submit advance voucher | ||||||
| 		""" | 		""" | ||||||
| 		 | 
 | ||||||
| 		lst = [] | 		lst = [] | ||||||
| 		for d in self.get('advance_allocation_details'): | 		for d in self.get('advance_allocation_details'): | ||||||
| 			if flt(d.allocated_amount) > 0: | 			if flt(d.allocated_amount) > 0: | ||||||
| 				args = { | 				args = { | ||||||
| 					'voucher_no' : d.journal_voucher,  | 					'voucher_no' : d.journal_voucher, | ||||||
| 					'voucher_detail_no' : d.jv_detail_no,  | 					'voucher_detail_no' : d.jv_detail_no, | ||||||
| 					'against_voucher_type' : 'Purchase Invoice',  | 					'against_voucher_type' : 'Purchase Invoice', | ||||||
| 					'against_voucher'  : self.name, | 					'against_voucher'  : self.name, | ||||||
| 					'account' : self.credit_to,  | 					'account' : self.credit_to, | ||||||
| 					'is_advance' : 'Yes',  | 					'is_advance' : 'Yes', | ||||||
| 					'dr_or_cr' : 'debit',  | 					'dr_or_cr' : 'debit', | ||||||
| 					'unadjusted_amt' : flt(d.advance_amount), | 					'unadjusted_amt' : flt(d.advance_amount), | ||||||
| 					'allocated_amt' : flt(d.allocated_amount) | 					'allocated_amt' : flt(d.allocated_amount) | ||||||
| 				} | 				} | ||||||
| 				lst.append(args) | 				lst.append(args) | ||||||
| 		 | 
 | ||||||
| 		if lst: | 		if lst: | ||||||
| 			from erpnext.accounts.utils import reconcile_against_document | 			from erpnext.accounts.utils import reconcile_against_document | ||||||
| 			reconcile_against_document(lst) | 			reconcile_against_document(lst) | ||||||
| 
 | 
 | ||||||
| 	def on_submit(self): | 	def on_submit(self): | ||||||
| 		self.check_prev_docstatus() | 		self.check_prev_docstatus() | ||||||
| 		 | 
 | ||||||
| 		frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,  | 		frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, | ||||||
| 			self.company, self.grand_total) | 			self.company, self.grand_total) | ||||||
| 		 | 
 | ||||||
| 		# this sequence because outstanding may get -negative | 		# this sequence because outstanding may get -negative | ||||||
| 		self.make_gl_entries() | 		self.make_gl_entries() | ||||||
| 		self.update_against_document_in_jv() | 		self.update_against_document_in_jv() | ||||||
| @ -278,9 +278,9 @@ class PurchaseInvoice(BuyingController): | |||||||
| 	def make_gl_entries(self): | 	def make_gl_entries(self): | ||||||
| 		auto_accounting_for_stock = \ | 		auto_accounting_for_stock = \ | ||||||
| 			cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) | 			cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) | ||||||
| 		 | 
 | ||||||
| 		gl_entries = [] | 		gl_entries = [] | ||||||
| 		 | 
 | ||||||
| 		# parent's gl entry | 		# parent's gl entry | ||||||
| 		if self.grand_total: | 		if self.grand_total: | ||||||
| 			gl_entries.append( | 			gl_entries.append( | ||||||
| @ -293,7 +293,7 @@ class PurchaseInvoice(BuyingController): | |||||||
| 					"against_voucher_type": self.doctype, | 					"against_voucher_type": self.doctype, | ||||||
| 				}) | 				}) | ||||||
| 			) | 			) | ||||||
| 	 | 
 | ||||||
| 		# tax table gl entries | 		# tax table gl entries | ||||||
| 		valuation_tax = {} | 		valuation_tax = {} | ||||||
| 		for tax in self.get("other_charges"): | 		for tax in self.get("other_charges"): | ||||||
| @ -308,31 +308,31 @@ class PurchaseInvoice(BuyingController): | |||||||
| 						"cost_center": tax.cost_center | 						"cost_center": tax.cost_center | ||||||
| 					}) | 					}) | ||||||
| 				) | 				) | ||||||
| 			 | 
 | ||||||
| 			# accumulate valuation tax | 			# accumulate valuation tax | ||||||
| 			if tax.category in ("Valuation", "Valuation and Total") and flt(tax.tax_amount): | 			if tax.category in ("Valuation", "Valuation and Total") and flt(tax.tax_amount): | ||||||
| 				if auto_accounting_for_stock and not tax.cost_center: | 				if auto_accounting_for_stock and not tax.cost_center: | ||||||
| 					frappe.throw(_("Row %(row)s: Cost Center is mandatory \ | 					frappe.throw(_("Row %(row)s: Cost Center is mandatory \ | ||||||
| 						if tax/charges category is Valuation or Valuation and Total" %  | 						if tax/charges category is Valuation or Valuation and Total" % | ||||||
| 						{"row": tax.idx})) | 						{"row": tax.idx})) | ||||||
| 				valuation_tax.setdefault(tax.cost_center, 0) | 				valuation_tax.setdefault(tax.cost_center, 0) | ||||||
| 				valuation_tax[tax.cost_center] += \ | 				valuation_tax[tax.cost_center] += \ | ||||||
| 					(tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.tax_amount) | 					(tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.tax_amount) | ||||||
| 					 | 
 | ||||||
| 		# item gl entries | 		# item gl entries | ||||||
| 		stock_item_and_auto_accounting_for_stock = False | 		stock_item_and_auto_accounting_for_stock = False | ||||||
| 		stock_items = self.get_stock_items() | 		stock_items = self.get_stock_items() | ||||||
| 		for item in self.get("entries"): | 		for item in self.get("entries"): | ||||||
| 			if auto_accounting_for_stock and item.item_code in stock_items: | 			if auto_accounting_for_stock and item.item_code in stock_items: | ||||||
| 				if flt(item.valuation_rate): | 				if flt(item.valuation_rate): | ||||||
| 					# if auto inventory accounting enabled and stock item,  | 					# if auto inventory accounting enabled and stock item, | ||||||
| 					# then do stock related gl entries | 					# then do stock related gl entries | ||||||
| 					# expense will be booked in sales invoice | 					# expense will be booked in sales invoice | ||||||
| 					stock_item_and_auto_accounting_for_stock = True | 					stock_item_and_auto_accounting_for_stock = True | ||||||
| 					 | 
 | ||||||
| 					valuation_amt = flt(item.base_amount + item.item_tax_amount + item.rm_supp_cost,  | 					valuation_amt = flt(item.base_amount + item.item_tax_amount + item.rm_supp_cost, | ||||||
| 						self.precision("base_amount", item)) | 						self.precision("base_amount", item)) | ||||||
| 					 | 
 | ||||||
| 					gl_entries.append( | 					gl_entries.append( | ||||||
| 						self.get_gl_dict({ | 						self.get_gl_dict({ | ||||||
| 							"account": item.expense_account, | 							"account": item.expense_account, | ||||||
| @ -341,7 +341,7 @@ class PurchaseInvoice(BuyingController): | |||||||
| 							"remarks": self.remarks or "Accounting Entry for Stock" | 							"remarks": self.remarks or "Accounting Entry for Stock" | ||||||
| 						}) | 						}) | ||||||
| 					) | 					) | ||||||
| 			 | 
 | ||||||
| 			elif flt(item.base_amount): | 			elif flt(item.base_amount): | ||||||
| 				# if not a stock item or auto inventory accounting disabled, book the expense | 				# if not a stock item or auto inventory accounting disabled, book the expense | ||||||
| 				gl_entries.append( | 				gl_entries.append( | ||||||
| @ -353,13 +353,13 @@ class PurchaseInvoice(BuyingController): | |||||||
| 						"cost_center": item.cost_center | 						"cost_center": item.cost_center | ||||||
| 					}) | 					}) | ||||||
| 				) | 				) | ||||||
| 				 | 
 | ||||||
| 		if stock_item_and_auto_accounting_for_stock and valuation_tax: | 		if stock_item_and_auto_accounting_for_stock and valuation_tax: | ||||||
| 			# credit valuation tax amount in "Expenses Included In Valuation" | 			# credit valuation tax amount in "Expenses Included In Valuation" | ||||||
| 			# this will balance out valuation amount included in cost of goods sold | 			# this will balance out valuation amount included in cost of goods sold | ||||||
| 			expenses_included_in_valuation = \ | 			expenses_included_in_valuation = \ | ||||||
| 				self.get_company_default("expenses_included_in_valuation") | 				self.get_company_default("expenses_included_in_valuation") | ||||||
| 			 | 
 | ||||||
| 			for cost_center, amount in valuation_tax.items(): | 			for cost_center, amount in valuation_tax.items(): | ||||||
| 				gl_entries.append( | 				gl_entries.append( | ||||||
| 					self.get_gl_dict({ | 					self.get_gl_dict({ | ||||||
| @ -370,8 +370,8 @@ class PurchaseInvoice(BuyingController): | |||||||
| 						"remarks": self.remarks or "Accounting Entry for Stock" | 						"remarks": self.remarks or "Accounting Entry for Stock" | ||||||
| 					}) | 					}) | ||||||
| 				) | 				) | ||||||
| 		 | 
 | ||||||
| 		# writeoff account includes petty difference in the invoice amount  | 		# writeoff account includes petty difference in the invoice amount | ||||||
| 		# and the amount that is paid | 		# and the amount that is paid | ||||||
| 		if self.write_off_account and flt(self.write_off_amount): | 		if self.write_off_account and flt(self.write_off_amount): | ||||||
| 			gl_entries.append( | 			gl_entries.append( | ||||||
| @ -383,7 +383,7 @@ class PurchaseInvoice(BuyingController): | |||||||
| 					"cost_center": self.write_off_cost_center | 					"cost_center": self.write_off_cost_center | ||||||
| 				}) | 				}) | ||||||
| 			) | 			) | ||||||
| 		 | 
 | ||||||
| 		if gl_entries: | 		if gl_entries: | ||||||
| 			from erpnext.accounts.general_ledger import make_gl_entries | 			from erpnext.accounts.general_ledger import make_gl_entries | ||||||
| 			make_gl_entries(gl_entries, cancel=(self.docstatus == 2)) | 			make_gl_entries(gl_entries, cancel=(self.docstatus == 2)) | ||||||
| @ -391,43 +391,43 @@ class PurchaseInvoice(BuyingController): | |||||||
| 	def on_cancel(self): | 	def on_cancel(self): | ||||||
| 		from erpnext.accounts.utils import remove_against_link_from_jv | 		from erpnext.accounts.utils import remove_against_link_from_jv | ||||||
| 		remove_against_link_from_jv(self.doctype, self.name, "against_voucher") | 		remove_against_link_from_jv(self.doctype, self.name, "against_voucher") | ||||||
| 		 | 
 | ||||||
| 		self.update_prevdoc_status() | 		self.update_prevdoc_status() | ||||||
| 		self.update_billing_status_for_zero_amount_refdoc("Purchase Order") | 		self.update_billing_status_for_zero_amount_refdoc("Purchase Order") | ||||||
| 		self.make_cancel_gl_entries() | 		self.make_cancel_gl_entries() | ||||||
| 		 | 
 | ||||||
| 	def on_update(self): | 	def on_update(self): | ||||||
| 		pass | 		pass | ||||||
| 		 | 
 | ||||||
| 	def update_raw_material_cost(self): | 	def update_raw_material_cost(self): | ||||||
| 		if self.sub_contracted_items: | 		if self.sub_contracted_items: | ||||||
| 			for d in self.get("entries"): | 			for d in self.get("entries"): | ||||||
| 				rm_cost = frappe.db.sql("""select raw_material_cost / quantity  | 				rm_cost = frappe.db.sql("""select raw_material_cost / quantity | ||||||
| 					from `tabBOM` where item = %s and is_default = 1 and docstatus = 1  | 					from `tabBOM` where item = %s and is_default = 1 and docstatus = 1 | ||||||
| 					and is_active = 1 """, (d.item_code,)) | 					and is_active = 1 """, (d.item_code,)) | ||||||
| 				rm_cost = rm_cost and flt(rm_cost[0][0]) or 0 | 				rm_cost = rm_cost and flt(rm_cost[0][0]) or 0 | ||||||
| 				 | 
 | ||||||
| 				d.conversion_factor = d.conversion_factor or flt(frappe.db.get_value( | 				d.conversion_factor = d.conversion_factor or flt(frappe.db.get_value( | ||||||
| 					"UOM Conversion Detail", {"parent": d.item_code, "uom": d.uom},  | 					"UOM Conversion Detail", {"parent": d.item_code, "uom": d.uom}, | ||||||
| 					"conversion_factor")) or 1 | 					"conversion_factor")) or 1 | ||||||
| 		 | 
 | ||||||
| 				d.rm_supp_cost = rm_cost * flt(d.qty) * flt(d.conversion_factor) | 				d.rm_supp_cost = rm_cost * flt(d.qty) * flt(d.conversion_factor) | ||||||
| 				 | 
 | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
| def get_expense_account(doctype, txt, searchfield, start, page_len, filters): | def get_expense_account(doctype, txt, searchfield, start, page_len, filters): | ||||||
| 	from erpnext.controllers.queries import get_match_cond | 	from erpnext.controllers.queries import get_match_cond | ||||||
| 	 | 
 | ||||||
| 	# expense account can be any Debit account,  | 	# expense account can be any Debit account, | ||||||
| 	# but can also be a Liability account with account_type='Expense Account' in special circumstances.  | 	# but can also be a Liability account with account_type='Expense Account' in special circumstances. | ||||||
| 	# Hence the first condition is an "OR" | 	# Hence the first condition is an "OR" | ||||||
| 	return frappe.db.sql("""select tabAccount.name from `tabAccount`  | 	return frappe.db.sql("""select tabAccount.name from `tabAccount` | ||||||
| 			where (tabAccount.report_type = "Profit and Loss" | 			where (tabAccount.report_type = "Profit and Loss" | ||||||
| 					or tabAccount.account_type = "Expense Account") | 					or tabAccount.account_type = "Expense Account") | ||||||
| 				and tabAccount.group_or_ledger="Ledger"  | 				and tabAccount.group_or_ledger="Ledger" | ||||||
| 				and tabAccount.docstatus!=2  | 				and tabAccount.docstatus!=2 | ||||||
| 				and ifnull(tabAccount.master_type, "")="" | 				and ifnull(tabAccount.master_type, "")="" | ||||||
| 				and ifnull(tabAccount.master_name, "")="" | 				and ifnull(tabAccount.master_name, "")="" | ||||||
| 				and tabAccount.company = '%(company)s'  | 				and tabAccount.company = '%(company)s' | ||||||
| 				and tabAccount.%(key)s LIKE '%(txt)s' | 				and tabAccount.%(key)s LIKE '%(txt)s' | ||||||
| 				%(mcond)s""" % {'company': filters['company'], 'key': searchfield,  | 				%(mcond)s""" % {'company': filters['company'], 'key': searchfield, | ||||||
| 			'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype)}) | 			'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype)}) | ||||||
|  | |||||||
| @ -14,29 +14,29 @@ class StockController(AccountsController): | |||||||
| 	def make_gl_entries(self, repost_future_gle=True): | 	def make_gl_entries(self, repost_future_gle=True): | ||||||
| 		if self.docstatus == 2: | 		if self.docstatus == 2: | ||||||
| 			delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) | 			delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) | ||||||
| 			 | 
 | ||||||
| 		if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")): | 		if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")): | ||||||
| 			warehouse_account = self.get_warehouse_account() | 			warehouse_account = self.get_warehouse_account() | ||||||
| 		 | 
 | ||||||
| 			if self.docstatus==1: | 			if self.docstatus==1: | ||||||
| 				gl_entries = self.get_gl_entries(warehouse_account) | 				gl_entries = self.get_gl_entries(warehouse_account) | ||||||
| 				make_gl_entries(gl_entries) | 				make_gl_entries(gl_entries) | ||||||
| 
 | 
 | ||||||
| 			if repost_future_gle: | 			if repost_future_gle: | ||||||
| 				items, warehouse_account = self.get_items_and_warehouse_accounts(warehouse_account) | 				items, warehouse_account = self.get_items_and_warehouse_accounts(warehouse_account) | ||||||
| 				update_gl_entries_after(self.posting_date, self.posting_time,  | 				update_gl_entries_after(self.posting_date, self.posting_time, | ||||||
| 					warehouse_account, items) | 					warehouse_account, items) | ||||||
| 	 | 
 | ||||||
| 	def get_gl_entries(self, warehouse_account=None, default_expense_account=None, | 	def get_gl_entries(self, warehouse_account=None, default_expense_account=None, | ||||||
| 			default_cost_center=None): | 			default_cost_center=None): | ||||||
| 		from erpnext.accounts.general_ledger import process_gl_map | 		from erpnext.accounts.general_ledger import process_gl_map | ||||||
| 		if not warehouse_account: | 		if not warehouse_account: | ||||||
| 			warehouse_account = get_warehouse_account() | 			warehouse_account = get_warehouse_account() | ||||||
| 		 | 
 | ||||||
| 		stock_ledger = self.get_stock_ledger_details() | 		stock_ledger = self.get_stock_ledger_details() | ||||||
| 		voucher_details = self.get_voucher_details(stock_ledger, default_expense_account,  | 		voucher_details = self.get_voucher_details(stock_ledger, default_expense_account, | ||||||
| 			default_cost_center) | 			default_cost_center) | ||||||
| 		 | 
 | ||||||
| 		gl_list = [] | 		gl_list = [] | ||||||
| 		warehouse_with_no_account = [] | 		warehouse_with_no_account = [] | ||||||
| 		for detail in voucher_details: | 		for detail in voucher_details: | ||||||
| @ -63,13 +63,13 @@ class StockController(AccountsController): | |||||||
| 						})) | 						})) | ||||||
| 					elif sle.warehouse not in warehouse_with_no_account: | 					elif sle.warehouse not in warehouse_with_no_account: | ||||||
| 						warehouse_with_no_account.append(sle.warehouse) | 						warehouse_with_no_account.append(sle.warehouse) | ||||||
| 						 | 
 | ||||||
| 		if warehouse_with_no_account:				 | 		if warehouse_with_no_account: | ||||||
| 			msgprint(_("No accounting entries for following warehouses") + ": \n" +  | 			msgprint(_("No accounting entries for following warehouses") + ": \n" + | ||||||
| 				"\n".join(warehouse_with_no_account)) | 				"\n".join(warehouse_with_no_account)) | ||||||
| 		 | 
 | ||||||
| 		return process_gl_map(gl_list) | 		return process_gl_map(gl_list) | ||||||
| 			 | 
 | ||||||
| 	def get_voucher_details(self, stock_ledger, default_expense_account, default_cost_center): | 	def get_voucher_details(self, stock_ledger, default_expense_account, default_cost_center): | ||||||
| 		if not default_expense_account: | 		if not default_expense_account: | ||||||
| 			details = self.get(self.fname) | 			details = self.get(self.fname) | ||||||
| @ -77,18 +77,18 @@ class StockController(AccountsController): | |||||||
| 				self.check_expense_account(d) | 				self.check_expense_account(d) | ||||||
| 		else: | 		else: | ||||||
| 			details = [frappe._dict({ | 			details = [frappe._dict({ | ||||||
| 				"name":d,  | 				"name":d, | ||||||
| 				"expense_account": default_expense_account,  | 				"expense_account": default_expense_account, | ||||||
| 				"cost_center": default_cost_center | 				"cost_center": default_cost_center | ||||||
| 			}) for d in stock_ledger.keys()] | 			}) for d in stock_ledger.keys()] | ||||||
| 			 | 
 | ||||||
| 		return details | 		return details | ||||||
| 		 | 
 | ||||||
| 	def get_items_and_warehouse_accounts(self, warehouse_account=None): | 	def get_items_and_warehouse_accounts(self, warehouse_account=None): | ||||||
| 		items, warehouses = [], [] | 		items, warehouses = [], [] | ||||||
| 		if not warehouse_account: | 		if not warehouse_account: | ||||||
| 			warehouse_account = get_warehouse_account() | 			warehouse_account = get_warehouse_account() | ||||||
| 		 | 
 | ||||||
| 		if hasattr(self, "fname"): | 		if hasattr(self, "fname"): | ||||||
| 			item_doclist = self.get(self.fname) | 			item_doclist = self.get(self.fname) | ||||||
| 		elif self.doctype == "Stock Reconciliation": | 		elif self.doctype == "Stock Reconciliation": | ||||||
| @ -98,26 +98,26 @@ class StockController(AccountsController): | |||||||
| 			for row in data[data.index(self.head_row)+1:]: | 			for row in data[data.index(self.head_row)+1:]: | ||||||
| 				d = frappe._dict(zip(["item_code", "warehouse", "qty", "valuation_rate"], row)) | 				d = frappe._dict(zip(["item_code", "warehouse", "qty", "valuation_rate"], row)) | ||||||
| 				item_doclist.append(d) | 				item_doclist.append(d) | ||||||
| 			 | 
 | ||||||
| 		if item_doclist: | 		if item_doclist: | ||||||
| 			for d in item_doclist: | 			for d in item_doclist: | ||||||
| 				if d.item_code and d.item_code not in items: | 				if d.item_code and d.item_code not in items: | ||||||
| 					items.append(d.item_code) | 					items.append(d.item_code) | ||||||
| 				 | 
 | ||||||
| 				if d.get("warehouse") and d.warehouse not in warehouses: | 				if d.get("warehouse") and d.warehouse not in warehouses: | ||||||
| 					warehouses.append(d.warehouse) | 					warehouses.append(d.warehouse) | ||||||
| 				 | 
 | ||||||
| 				if self.doctype == "Stock Entry": | 				if self.doctype == "Stock Entry": | ||||||
| 					if d.get("s_warehouse") and d.s_warehouse not in warehouses: | 					if d.get("s_warehouse") and d.s_warehouse not in warehouses: | ||||||
| 						warehouses.append(d.s_warehouse) | 						warehouses.append(d.s_warehouse) | ||||||
| 					if d.get("t_warehouse") and d.t_warehouse not in warehouses: | 					if d.get("t_warehouse") and d.t_warehouse not in warehouses: | ||||||
| 						warehouses.append(d.t_warehouse) | 						warehouses.append(d.t_warehouse) | ||||||
| 
 | 
 | ||||||
| 			warehouse_account = {wh: warehouse_account[wh] for wh in warehouses  | 			warehouse_account = {wh: warehouse_account[wh] for wh in warehouses | ||||||
| 				if warehouse_account.get(wh)} | 				if warehouse_account.get(wh)} | ||||||
| 	 | 
 | ||||||
| 		return items, warehouse_account | 		return items, warehouse_account | ||||||
| 		 | 
 | ||||||
| 	def get_stock_ledger_details(self): | 	def get_stock_ledger_details(self): | ||||||
| 		stock_ledger = {} | 		stock_ledger = {} | ||||||
| 		for sle in frappe.db.sql("""select warehouse, stock_value_difference, voucher_detail_no | 		for sle in frappe.db.sql("""select warehouse, stock_value_difference, voucher_detail_no | ||||||
| @ -125,12 +125,12 @@ class StockController(AccountsController): | |||||||
| 			(self.doctype, self.name), as_dict=True): | 			(self.doctype, self.name), as_dict=True): | ||||||
| 				stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle) | 				stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle) | ||||||
| 		return stock_ledger | 		return stock_ledger | ||||||
| 		 | 
 | ||||||
| 	def get_warehouse_account(self): | 	def get_warehouse_account(self): | ||||||
| 		warehouse_account = dict(frappe.db.sql("""select master_name, name from tabAccount  | 		warehouse_account = dict(frappe.db.sql("""select master_name, name from tabAccount | ||||||
| 			where account_type = 'Warehouse' and ifnull(master_name, '') != ''""")) | 			where account_type = 'Warehouse' and ifnull(master_name, '') != ''""")) | ||||||
| 		return warehouse_account | 		return warehouse_account | ||||||
| 		 | 
 | ||||||
| 	def update_gl_entries_after(self, warehouse_account=None): | 	def update_gl_entries_after(self, warehouse_account=None): | ||||||
| 		future_stock_vouchers = self.get_future_stock_vouchers() | 		future_stock_vouchers = self.get_future_stock_vouchers() | ||||||
| 		gle = self.get_voucherwise_gl_entries(future_stock_vouchers) | 		gle = self.get_voucherwise_gl_entries(future_stock_vouchers) | ||||||
| @ -153,53 +153,53 @@ class StockController(AccountsController): | |||||||
| 										break | 										break | ||||||
| 				else: | 				else: | ||||||
| 					matched = False | 					matched = False | ||||||
| 									 | 
 | ||||||
| 				if not matched: | 				if not matched: | ||||||
| 					self.delete_gl_entries(voucher_type, voucher_no) | 					self.delete_gl_entries(voucher_type, voucher_no) | ||||||
| 					voucher_obj.make_gl_entries(repost_future_gle=False) | 					voucher_obj.make_gl_entries(repost_future_gle=False) | ||||||
| 			else: | 			else: | ||||||
| 				self.delete_gl_entries(voucher_type, voucher_no) | 				self.delete_gl_entries(voucher_type, voucher_no) | ||||||
| 				 | 
 | ||||||
| 		 | 
 | ||||||
| 	def get_future_stock_vouchers(self): | 	def get_future_stock_vouchers(self): | ||||||
| 		future_stock_vouchers = [] | 		future_stock_vouchers = [] | ||||||
| 		 | 
 | ||||||
| 		if hasattr(self, "fname"): | 		if hasattr(self, "fname"): | ||||||
| 			item_list = [d.item_code for d in self.get(self.fname)] | 			item_list = [d.item_code for d in self.get(self.fname)] | ||||||
| 			condition = ''.join(['and item_code in (\'', '\', \''.join(item_list) ,'\')']) | 			condition = ''.join(['and item_code in (\'', '\', \''.join(item_list) ,'\')']) | ||||||
| 		else: | 		else: | ||||||
| 			condition = "" | 			condition = "" | ||||||
| 		 | 
 | ||||||
| 		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) %s | 			where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) %s | ||||||
| 			order by timestamp(sle.posting_date, sle.posting_time) asc, name asc""" %  | 			order by timestamp(sle.posting_date, sle.posting_time) asc, name asc""" % | ||||||
| 			('%s', '%s', condition), (self.posting_date, self.posting_time),  | 			('%s', '%s', condition), (self.posting_date, self.posting_time), | ||||||
| 			as_dict=True): | 			as_dict=True): | ||||||
| 				future_stock_vouchers.append([d.voucher_type, d.voucher_no]) | 				future_stock_vouchers.append([d.voucher_type, d.voucher_no]) | ||||||
| 		 | 
 | ||||||
| 		return future_stock_vouchers | 		return future_stock_vouchers | ||||||
| 				 | 
 | ||||||
| 	def get_voucherwise_gl_entries(self, future_stock_vouchers): | 	def get_voucherwise_gl_entries(self, future_stock_vouchers): | ||||||
| 		gl_entries = {} | 		gl_entries = {} | ||||||
| 		if future_stock_vouchers: | 		if future_stock_vouchers: | ||||||
| 			for d in frappe.db.sql("""select * from `tabGL Entry`  | 			for d in frappe.db.sql("""select * from `tabGL Entry` | ||||||
| 				where posting_date >= %s and voucher_no in (%s)""" %  | 				where posting_date >= %s and voucher_no in (%s)""" % | ||||||
| 				('%s', ', '.join(['%s']*len(future_stock_vouchers))),  | 				('%s', ', '.join(['%s']*len(future_stock_vouchers))), | ||||||
| 				tuple([self.posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1): | 				tuple([self.posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1): | ||||||
| 					gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) | 					gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) | ||||||
| 		 | 
 | ||||||
| 		return gl_entries | 		return gl_entries | ||||||
| 		 | 
 | ||||||
| 	def delete_gl_entries(self, voucher_type, voucher_no): | 	def delete_gl_entries(self, voucher_type, voucher_no): | ||||||
| 		frappe.db.sql("""delete from `tabGL Entry`  | 		frappe.db.sql("""delete from `tabGL Entry` | ||||||
| 			where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) | 			where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) | ||||||
| 					 | 
 | ||||||
| 	def make_adjustment_entry(self, expected_gle, voucher_obj): | 	def make_adjustment_entry(self, expected_gle, voucher_obj): | ||||||
| 		from erpnext.accounts.utils import get_stock_and_account_difference | 		from erpnext.accounts.utils import get_stock_and_account_difference | ||||||
| 		account_list = [d.account for d in expected_gle] | 		account_list = [d.account for d in expected_gle] | ||||||
| 		acc_diff = get_stock_and_account_difference(account_list, expected_gle[0].posting_date) | 		acc_diff = get_stock_and_account_difference(account_list, expected_gle[0].posting_date) | ||||||
| 		 | 
 | ||||||
| 		cost_center = self.get_company_default("cost_center") | 		cost_center = self.get_company_default("cost_center") | ||||||
| 		stock_adjustment_account = self.get_company_default("stock_adjustment_account") | 		stock_adjustment_account = self.get_company_default("stock_adjustment_account") | ||||||
| 
 | 
 | ||||||
| @ -214,7 +214,7 @@ class StockController(AccountsController): | |||||||
| 						"debit": diff, | 						"debit": diff, | ||||||
| 						"remarks": "Adjustment Accounting Entry for Stock", | 						"remarks": "Adjustment Accounting Entry for Stock", | ||||||
| 					}), | 					}), | ||||||
| 				 | 
 | ||||||
| 					# account against stock in hand | 					# account against stock in hand | ||||||
| 					voucher_obj.get_gl_dict({ | 					voucher_obj.get_gl_dict({ | ||||||
| 						"account": stock_adjustment_account, | 						"account": stock_adjustment_account, | ||||||
| @ -224,21 +224,21 @@ class StockController(AccountsController): | |||||||
| 						"remarks": "Adjustment Accounting Entry for Stock", | 						"remarks": "Adjustment Accounting Entry for Stock", | ||||||
| 					}), | 					}), | ||||||
| 				]) | 				]) | ||||||
| 				 | 
 | ||||||
| 		if gl_entries: | 		if gl_entries: | ||||||
| 			from erpnext.accounts.general_ledger import make_gl_entries | 			from erpnext.accounts.general_ledger import make_gl_entries | ||||||
| 			make_gl_entries(gl_entries) | 			make_gl_entries(gl_entries) | ||||||
| 			 | 
 | ||||||
| 	def check_expense_account(self, item): | 	def check_expense_account(self, item): | ||||||
| 		if item.meta.get_field("expense_account") and not item.expense_account: | 		if item.meta.get_field("expense_account") and not item.expense_account: | ||||||
| 			msgprint(_("""Expense/Difference account is mandatory for item: """) + item.item_code,  | 			msgprint(_("""Expense/Difference account is mandatory for item: """) + item.item_code, | ||||||
| 				raise_exception=1) | 				raise_exception=1) | ||||||
| 				 | 
 | ||||||
| 		if item.meta.get_field("expense_account") and not item.cost_center: | 		if item.meta.get_field("expense_account") and not item.cost_center: | ||||||
| 			msgprint(_("""Cost Center is mandatory for item: """) + item.item_code,  | 			msgprint(_("""Cost Center is mandatory for item: """) + item.item_code, | ||||||
| 				raise_exception=1) | 				raise_exception=1) | ||||||
| 				 | 
 | ||||||
| 	def get_sl_entries(self, d, args):		 | 	def get_sl_entries(self, d, args): | ||||||
| 		sl_dict = { | 		sl_dict = { | ||||||
| 			"item_code": d.item_code, | 			"item_code": d.item_code, | ||||||
| 			"warehouse": d.get("warehouse", None), | 			"warehouse": d.get("warehouse", None), | ||||||
| @ -257,27 +257,27 @@ class StockController(AccountsController): | |||||||
| 			"project": d.get("project_name"), | 			"project": d.get("project_name"), | ||||||
| 			"is_cancelled": self.docstatus==2 and "Yes" or "No" | 			"is_cancelled": self.docstatus==2 and "Yes" or "No" | ||||||
| 		} | 		} | ||||||
| 		 | 
 | ||||||
| 		sl_dict.update(args) | 		sl_dict.update(args) | ||||||
| 		return sl_dict | 		return sl_dict | ||||||
| 		 | 
 | ||||||
| 	def make_sl_entries(self, sl_entries, is_amended=None): | 	def make_sl_entries(self, sl_entries, is_amended=None): | ||||||
| 		from erpnext.stock.stock_ledger import make_sl_entries | 		from erpnext.stock.stock_ledger import make_sl_entries | ||||||
| 		make_sl_entries(sl_entries, is_amended) | 		make_sl_entries(sl_entries, is_amended) | ||||||
| 		 | 
 | ||||||
| 	def make_cancel_gl_entries(self): | 	def make_cancel_gl_entries(self): | ||||||
| 		if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s  | 		if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s | ||||||
| 			and voucher_no=%s""", (self.doctype, self.name)): | 			and voucher_no=%s""", (self.doctype, self.name)): | ||||||
| 				self.make_gl_entries() | 				self.make_gl_entries() | ||||||
| 				 | 
 | ||||||
| def update_gl_entries_after(posting_date, posting_time, warehouse_account=None, for_items=None): | def update_gl_entries_after(posting_date, posting_time, warehouse_account=None, for_items=None): | ||||||
| 	def _delete_gl_entries(voucher_type, voucher_no): | 	def _delete_gl_entries(voucher_type, voucher_no): | ||||||
| 		frappe.db.sql("""delete from `tabGL Entry`  | 		frappe.db.sql("""delete from `tabGL Entry` | ||||||
| 			where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) | 			where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) | ||||||
| 	 | 
 | ||||||
| 	if not warehouse_account: | 	if not warehouse_account: | ||||||
| 		warehouse_account = get_warehouse_account() | 		warehouse_account = get_warehouse_account() | ||||||
| 	future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time,  | 	future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, | ||||||
| 		warehouse_account, for_items) | 		warehouse_account, for_items) | ||||||
| 	gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date) | 	gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date) | ||||||
| 
 | 
 | ||||||
| @ -286,13 +286,13 @@ def update_gl_entries_after(posting_date, posting_time, warehouse_account=None, | |||||||
| 		voucher_obj = frappe.get_doc(voucher_type, voucher_no) | 		voucher_obj = frappe.get_doc(voucher_type, voucher_no) | ||||||
| 		expected_gle = voucher_obj.get_gl_entries(warehouse_account) | 		expected_gle = voucher_obj.get_gl_entries(warehouse_account) | ||||||
| 		if expected_gle: | 		if expected_gle: | ||||||
| 			if not existing_gle or not compare_existing_and_expected_gle(existing_gle,  | 			if not existing_gle or not compare_existing_and_expected_gle(existing_gle, | ||||||
| 				expected_gle): | 				expected_gle): | ||||||
| 					_delete_gl_entries(voucher_type, voucher_no) | 					_delete_gl_entries(voucher_type, voucher_no) | ||||||
| 					voucher_obj.make_gl_entries(repost_future_gle=False) | 					voucher_obj.make_gl_entries(repost_future_gle=False) | ||||||
| 		else: | 		else: | ||||||
| 			_delete_gl_entries(voucher_type, voucher_no) | 			_delete_gl_entries(voucher_type, voucher_no) | ||||||
| 			 | 
 | ||||||
| def compare_existing_and_expected_gle(existing_gle, expected_gle): | def compare_existing_and_expected_gle(existing_gle, expected_gle): | ||||||
| 	matched = True | 	matched = True | ||||||
| 	for entry in expected_gle: | 	for entry in expected_gle: | ||||||
| @ -306,36 +306,36 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle): | |||||||
| 
 | 
 | ||||||
| def get_future_stock_vouchers(posting_date, posting_time, warehouse_account=None, for_items=None): | def get_future_stock_vouchers(posting_date, posting_time, warehouse_account=None, for_items=None): | ||||||
| 	future_stock_vouchers = [] | 	future_stock_vouchers = [] | ||||||
| 	 | 
 | ||||||
| 	condition = "" | 	condition = "" | ||||||
| 	if for_items: | 	if for_items: | ||||||
| 		condition = ''.join([' and item_code in (\'', '\', \''.join(for_items) ,'\')']) | 		condition = ''.join([' and item_code in (\'', '\', \''.join(for_items) ,'\')']) | ||||||
| 	 | 
 | ||||||
| 	if warehouse_account: | 	if warehouse_account: | ||||||
| 		condition += ''.join([' and warehouse in (\'', '\', \''.join(warehouse_account.keys()) ,'\')']) | 		condition += ''.join([' and warehouse in (\'', '\', \''.join(warehouse_account.keys()) ,'\')']) | ||||||
| 	 | 
 | ||||||
| 	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) %s | 		where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) %s | ||||||
| 		order by timestamp(sle.posting_date, sle.posting_time) asc, name asc""" %  | 		order by timestamp(sle.posting_date, sle.posting_time) asc, name asc""" % | ||||||
| 		('%s', '%s', condition), (posting_date, posting_time),  | 		('%s', '%s', condition), (posting_date, posting_time), | ||||||
| 		as_dict=True): | 		as_dict=True): | ||||||
| 			future_stock_vouchers.append([d.voucher_type, d.voucher_no]) | 			future_stock_vouchers.append([d.voucher_type, d.voucher_no]) | ||||||
| 	 | 
 | ||||||
| 	return future_stock_vouchers | 	return future_stock_vouchers | ||||||
| 			 | 
 | ||||||
| def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): | def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): | ||||||
| 	gl_entries = {} | 	gl_entries = {} | ||||||
| 	if future_stock_vouchers: | 	if future_stock_vouchers: | ||||||
| 		for d in frappe.db.sql("""select * from `tabGL Entry`  | 		for d in frappe.db.sql("""select * from `tabGL Entry` | ||||||
| 			where posting_date >= %s and voucher_no in (%s)""" %  | 			where posting_date >= %s and voucher_no in (%s)""" % | ||||||
| 			('%s', ', '.join(['%s']*len(future_stock_vouchers))),  | 			('%s', ', '.join(['%s']*len(future_stock_vouchers))), | ||||||
| 			tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1): | 			tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1): | ||||||
| 				gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) | 				gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) | ||||||
| 	 | 
 | ||||||
| 	return gl_entries | 	return gl_entries | ||||||
| 
 | 
 | ||||||
| def get_warehouse_account(): | def get_warehouse_account(): | ||||||
| 	warehouse_account = dict(frappe.db.sql("""select master_name, name from tabAccount  | 	warehouse_account = dict(frappe.db.sql("""select master_name, name from tabAccount | ||||||
| 		where account_type = 'Warehouse' and ifnull(master_name, '') != ''""")) | 		where account_type = 'Warehouse' and ifnull(master_name, '') != ''""")) | ||||||
| 	return warehouse_account | 	return warehouse_account | ||||||
|  | |||||||
| @ -19,12 +19,12 @@ class StockOverReturnError(frappe.ValidationError): pass | |||||||
| class IncorrectValuationRateError(frappe.ValidationError): pass | class IncorrectValuationRateError(frappe.ValidationError): pass | ||||||
| class DuplicateEntryForProductionOrderError(frappe.ValidationError): pass | class DuplicateEntryForProductionOrderError(frappe.ValidationError): pass | ||||||
| class StockOverProductionError(frappe.ValidationError): pass | class StockOverProductionError(frappe.ValidationError): pass | ||||||
| 	 | 
 | ||||||
| from erpnext.controllers.stock_controller import StockController | from erpnext.controllers.stock_controller import StockController | ||||||
| 
 | 
 | ||||||
| class StockEntry(StockController): | class StockEntry(StockController): | ||||||
| 	fname = 'mtn_details'  | 	fname = 'mtn_details' | ||||||
| 		 | 
 | ||||||
| 	def validate(self): | 	def validate(self): | ||||||
| 		self.validate_posting_time() | 		self.validate_posting_time() | ||||||
| 		self.validate_purpose() | 		self.validate_purpose() | ||||||
| @ -44,7 +44,7 @@ class StockEntry(StockController): | |||||||
| 		self.validate_with_material_request() | 		self.validate_with_material_request() | ||||||
| 		self.validate_fiscal_year() | 		self.validate_fiscal_year() | ||||||
| 		self.set_total_amount() | 		self.set_total_amount() | ||||||
| 		 | 
 | ||||||
| 	def on_submit(self): | 	def on_submit(self): | ||||||
| 		self.update_stock_ledger() | 		self.update_stock_ledger() | ||||||
| 
 | 
 | ||||||
| @ -57,32 +57,32 @@ class StockEntry(StockController): | |||||||
| 		self.update_stock_ledger() | 		self.update_stock_ledger() | ||||||
| 		self.update_production_order() | 		self.update_production_order() | ||||||
| 		self.make_cancel_gl_entries() | 		self.make_cancel_gl_entries() | ||||||
| 		 | 
 | ||||||
| 	def validate_fiscal_year(self): | 	def validate_fiscal_year(self): | ||||||
| 		from erpnext.accounts.utils import validate_fiscal_year | 		from erpnext.accounts.utils import validate_fiscal_year | ||||||
| 		validate_fiscal_year(self.posting_date, self.fiscal_year, | 		validate_fiscal_year(self.posting_date, self.fiscal_year, | ||||||
| 			self.meta.get_label("posting_date")) | 			self.meta.get_label("posting_date")) | ||||||
| 		 | 
 | ||||||
| 	def validate_purpose(self): | 	def validate_purpose(self): | ||||||
| 		valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer",  | 		valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer", | ||||||
| 			"Manufacture/Repack", "Subcontract", "Sales Return", "Purchase Return"] | 			"Manufacture/Repack", "Subcontract", "Sales Return", "Purchase Return"] | ||||||
| 		if self.purpose not in valid_purposes: | 		if self.purpose not in valid_purposes: | ||||||
| 			msgprint(_("Purpose must be one of ") + comma_or(valid_purposes), | 			msgprint(_("Purpose must be one of ") + comma_or(valid_purposes), | ||||||
| 				raise_exception=True) | 				raise_exception=True) | ||||||
| 		 | 
 | ||||||
| 	def validate_item(self): | 	def validate_item(self): | ||||||
| 		stock_items = self.get_stock_items() | 		stock_items = self.get_stock_items() | ||||||
| 		for item in self.get("mtn_details"): | 		for item in self.get("mtn_details"): | ||||||
| 			if item.item_code not in stock_items: | 			if item.item_code not in stock_items: | ||||||
| 				msgprint(_("""Only Stock Items are allowed for Stock Entry"""), | 				msgprint(_("""Only Stock Items are allowed for Stock Entry"""), | ||||||
| 					raise_exception=True) | 					raise_exception=True) | ||||||
| 		 | 
 | ||||||
| 	def validate_warehouse(self, pro_obj): | 	def validate_warehouse(self, pro_obj): | ||||||
| 		"""perform various (sometimes conditional) validations on warehouse""" | 		"""perform various (sometimes conditional) validations on warehouse""" | ||||||
| 		 | 
 | ||||||
| 		source_mandatory = ["Material Issue", "Material Transfer", "Purchase Return"] | 		source_mandatory = ["Material Issue", "Material Transfer", "Purchase Return"] | ||||||
| 		target_mandatory = ["Material Receipt", "Material Transfer", "Sales Return"] | 		target_mandatory = ["Material Receipt", "Material Transfer", "Sales Return"] | ||||||
| 		 | 
 | ||||||
| 		validate_for_manufacture_repack = any([d.bom_no for d in self.get("mtn_details")]) | 		validate_for_manufacture_repack = any([d.bom_no for d in self.get("mtn_details")]) | ||||||
| 
 | 
 | ||||||
| 		if self.purpose in source_mandatory and self.purpose not in target_mandatory: | 		if self.purpose in source_mandatory and self.purpose not in target_mandatory: | ||||||
| @ -101,11 +101,11 @@ class StockEntry(StockController): | |||||||
| 
 | 
 | ||||||
| 			if not (d.s_warehouse or d.t_warehouse): | 			if not (d.s_warehouse or d.t_warehouse): | ||||||
| 				msgprint(_("Atleast one warehouse is mandatory"), raise_exception=1) | 				msgprint(_("Atleast one warehouse is mandatory"), raise_exception=1) | ||||||
| 			 | 
 | ||||||
| 			if self.purpose in source_mandatory and not d.s_warehouse: | 			if self.purpose in source_mandatory and not d.s_warehouse: | ||||||
| 				msgprint(_("Row # ") + "%s: " % cint(d.idx) | 				msgprint(_("Row # ") + "%s: " % cint(d.idx) | ||||||
| 					+ _("Source Warehouse") + _(" is mandatory"), raise_exception=1) | 					+ _("Source Warehouse") + _(" is mandatory"), raise_exception=1) | ||||||
| 				 | 
 | ||||||
| 			if self.purpose in target_mandatory and not d.t_warehouse: | 			if self.purpose in target_mandatory and not d.t_warehouse: | ||||||
| 				msgprint(_("Row # ") + "%s: " % cint(d.idx) | 				msgprint(_("Row # ") + "%s: " % cint(d.idx) | ||||||
| 					+ _("Target Warehouse") + _(" is mandatory"), raise_exception=1) | 					+ _("Target Warehouse") + _(" is mandatory"), raise_exception=1) | ||||||
| @ -114,39 +114,39 @@ class StockEntry(StockController): | |||||||
| 				if validate_for_manufacture_repack: | 				if validate_for_manufacture_repack: | ||||||
| 					if d.bom_no: | 					if d.bom_no: | ||||||
| 						d.s_warehouse = None | 						d.s_warehouse = None | ||||||
| 						 | 
 | ||||||
| 						if not d.t_warehouse: | 						if not d.t_warehouse: | ||||||
| 							msgprint(_("Row # ") + "%s: " % cint(d.idx) | 							msgprint(_("Row # ") + "%s: " % cint(d.idx) | ||||||
| 								+ _("Target Warehouse") + _(" is mandatory"), raise_exception=1) | 								+ _("Target Warehouse") + _(" is mandatory"), raise_exception=1) | ||||||
| 						 | 
 | ||||||
| 						elif pro_obj and cstr(d.t_warehouse) != pro_obj.fg_warehouse: | 						elif pro_obj and cstr(d.t_warehouse) != pro_obj.fg_warehouse: | ||||||
| 							msgprint(_("Row # ") + "%s: " % cint(d.idx) | 							msgprint(_("Row # ") + "%s: " % cint(d.idx) | ||||||
| 								+ _("Target Warehouse") + _(" should be same as that in ") | 								+ _("Target Warehouse") + _(" should be same as that in ") | ||||||
| 								+ _("Production Order"), raise_exception=1) | 								+ _("Production Order"), raise_exception=1) | ||||||
| 					 | 
 | ||||||
| 					else: | 					else: | ||||||
| 						d.t_warehouse = None | 						d.t_warehouse = None | ||||||
| 						if not d.s_warehouse: | 						if not d.s_warehouse: | ||||||
| 							msgprint(_("Row # ") + "%s: " % cint(d.idx) | 							msgprint(_("Row # ") + "%s: " % cint(d.idx) | ||||||
| 								+ _("Source Warehouse") + _(" is mandatory"), raise_exception=1) | 								+ _("Source Warehouse") + _(" is mandatory"), raise_exception=1) | ||||||
| 			 | 
 | ||||||
| 			if cstr(d.s_warehouse) == cstr(d.t_warehouse): | 			if cstr(d.s_warehouse) == cstr(d.t_warehouse): | ||||||
| 				msgprint(_("Source and Target Warehouse cannot be same"),  | 				msgprint(_("Source and Target Warehouse cannot be same"), | ||||||
| 					raise_exception=1) | 					raise_exception=1) | ||||||
| 				 | 
 | ||||||
| 	def validate_production_order(self, pro_obj=None): | 	def validate_production_order(self, pro_obj=None): | ||||||
| 		if not pro_obj: | 		if not pro_obj: | ||||||
| 			if self.production_order: | 			if self.production_order: | ||||||
| 				pro_obj = frappe.get_doc('Production Order', self.production_order) | 				pro_obj = frappe.get_doc('Production Order', self.production_order) | ||||||
| 			else: | 			else: | ||||||
| 				return | 				return | ||||||
| 		 | 
 | ||||||
| 		if self.purpose == "Manufacture/Repack": | 		if self.purpose == "Manufacture/Repack": | ||||||
| 			# check for double entry | 			# check for double entry | ||||||
| 			self.check_duplicate_entry_for_production_order() | 			self.check_duplicate_entry_for_production_order() | ||||||
| 		elif self.purpose != "Material Transfer": | 		elif self.purpose != "Material Transfer": | ||||||
| 			self.production_order = None | 			self.production_order = None | ||||||
| 	 | 
 | ||||||
| 	def check_duplicate_entry_for_production_order(self): | 	def check_duplicate_entry_for_production_order(self): | ||||||
| 		other_ste = [t[0] for t in frappe.db.get_values("Stock Entry",  { | 		other_ste = [t[0] for t in frappe.db.get_values("Stock Entry",  { | ||||||
| 			"production_order": self.production_order, | 			"production_order": self.production_order, | ||||||
| @ -154,24 +154,24 @@ class StockEntry(StockController): | |||||||
| 			"docstatus": ["!=", 2], | 			"docstatus": ["!=", 2], | ||||||
| 			"name": ["!=", self.name] | 			"name": ["!=", self.name] | ||||||
| 		}, "name")] | 		}, "name")] | ||||||
| 		 | 
 | ||||||
| 		if other_ste: | 		if other_ste: | ||||||
| 			production_item, qty = frappe.db.get_value("Production Order",  | 			production_item, qty = frappe.db.get_value("Production Order", | ||||||
| 				self.production_order, ["production_item", "qty"]) | 				self.production_order, ["production_item", "qty"]) | ||||||
| 			args = other_ste + [production_item] | 			args = other_ste + [production_item] | ||||||
| 			fg_qty_already_entered = frappe.db.sql("""select sum(actual_qty) | 			fg_qty_already_entered = frappe.db.sql("""select sum(actual_qty) | ||||||
| 				from `tabStock Entry Detail`  | 				from `tabStock Entry Detail` | ||||||
| 				where parent in (%s)  | 				where parent in (%s) | ||||||
| 					and item_code = %s  | 					and item_code = %s | ||||||
| 					and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0] | 					and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0] | ||||||
| 			 | 
 | ||||||
| 			if fg_qty_already_entered >= qty: | 			if fg_qty_already_entered >= qty: | ||||||
| 				frappe.throw(_("Stock Entries already created for Production Order ")  | 				frappe.throw(_("Stock Entries already created for Production Order ") | ||||||
| 					+ self.production_order + ":" + ", ".join(other_ste), DuplicateEntryForProductionOrderError) | 					+ self.production_order + ":" + ", ".join(other_ste), DuplicateEntryForProductionOrderError) | ||||||
| 
 | 
 | ||||||
| 	def set_total_amount(self): | 	def set_total_amount(self): | ||||||
| 		self.total_amount = sum([flt(item.amount) for item in self.get("mtn_details")]) | 		self.total_amount = sum([flt(item.amount) for item in self.get("mtn_details")]) | ||||||
| 			 | 
 | ||||||
| 	def get_stock_and_rate(self): | 	def get_stock_and_rate(self): | ||||||
| 		"""get stock and incoming rate on posting date""" | 		"""get stock and incoming rate on posting date""" | ||||||
| 		for d in self.get('mtn_details'): | 		for d in self.get('mtn_details'): | ||||||
| @ -186,21 +186,21 @@ class StockEntry(StockController): | |||||||
| 			}) | 			}) | ||||||
| 			# get actual stock at source warehouse | 			# get actual stock at source warehouse | ||||||
| 			d.actual_qty = get_previous_sle(args).get("qty_after_transaction") or 0 | 			d.actual_qty = get_previous_sle(args).get("qty_after_transaction") or 0 | ||||||
| 			 | 
 | ||||||
| 			# get incoming rate | 			# get incoming rate | ||||||
| 			if not flt(d.incoming_rate): | 			if not flt(d.incoming_rate): | ||||||
| 				d.incoming_rate = self.get_incoming_rate(args) | 				d.incoming_rate = self.get_incoming_rate(args) | ||||||
| 				 | 
 | ||||||
| 			d.amount = flt(d.transfer_qty) * flt(d.incoming_rate) | 			d.amount = flt(d.transfer_qty) * flt(d.incoming_rate) | ||||||
| 			 | 
 | ||||||
| 	def get_incoming_rate(self, args): | 	def get_incoming_rate(self, args): | ||||||
| 		incoming_rate = 0 | 		incoming_rate = 0 | ||||||
| 		if self.purpose == "Sales Return" and \ | 		if self.purpose == "Sales Return" and \ | ||||||
| 				(self.delivery_note_no or self.sales_invoice_no): | 				(self.delivery_note_no or self.sales_invoice_no): | ||||||
| 			sle = frappe.db.sql("""select name, posting_date, posting_time,  | 			sle = frappe.db.sql("""select name, posting_date, posting_time, | ||||||
| 				actual_qty, stock_value, warehouse from `tabStock Ledger Entry`  | 				actual_qty, stock_value, warehouse from `tabStock Ledger Entry` | ||||||
| 				where voucher_type = %s and voucher_no = %s and  | 				where voucher_type = %s and voucher_no = %s and | ||||||
| 				item_code = %s limit 1""",  | 				item_code = %s limit 1""", | ||||||
| 				((self.delivery_note_no and "Delivery Note" or "Sales Invoice"), | 				((self.delivery_note_no and "Delivery Note" or "Sales Invoice"), | ||||||
| 				self.delivery_note_no or self.sales_invoice_no, args.item_code), as_dict=1) | 				self.delivery_note_no or self.sales_invoice_no, args.item_code), as_dict=1) | ||||||
| 			if sle: | 			if sle: | ||||||
| @ -215,14 +215,14 @@ class StockEntry(StockController): | |||||||
| 					flt(sle[0].actual_qty) | 					flt(sle[0].actual_qty) | ||||||
| 		else: | 		else: | ||||||
| 			incoming_rate = get_incoming_rate(args) | 			incoming_rate = get_incoming_rate(args) | ||||||
| 			 | 
 | ||||||
| 		return incoming_rate | 		return incoming_rate | ||||||
| 		 | 
 | ||||||
| 	def validate_incoming_rate(self): | 	def validate_incoming_rate(self): | ||||||
| 		for d in self.get('mtn_details'): | 		for d in self.get('mtn_details'): | ||||||
| 			if d.t_warehouse: | 			if d.t_warehouse: | ||||||
| 				self.validate_value("incoming_rate", ">", 0, d, raise_exception=IncorrectValuationRateError) | 				self.validate_value("incoming_rate", ">", 0, d, raise_exception=IncorrectValuationRateError) | ||||||
| 					 | 
 | ||||||
| 	def validate_bom(self): | 	def validate_bom(self): | ||||||
| 		for d in self.get('mtn_details'): | 		for d in self.get('mtn_details'): | ||||||
| 			if d.bom_no and not frappe.db.sql("""select name from `tabBOM` | 			if d.bom_no and not frappe.db.sql("""select name from `tabBOM` | ||||||
| @ -231,72 +231,72 @@ class StockEntry(StockController): | |||||||
| 				msgprint(_("Item") + " %s: " % cstr(d.item_code) | 				msgprint(_("Item") + " %s: " % cstr(d.item_code) | ||||||
| 					+ _("does not belong to BOM: ") + cstr(d.bom_no) | 					+ _("does not belong to BOM: ") + cstr(d.bom_no) | ||||||
| 					+ _(" or the BOM is cancelled or inactive"), raise_exception=1) | 					+ _(" or the BOM is cancelled or inactive"), raise_exception=1) | ||||||
| 					 | 
 | ||||||
| 	def validate_finished_goods(self): | 	def validate_finished_goods(self): | ||||||
| 		"""validation: finished good quantity should be same as manufacturing quantity""" | 		"""validation: finished good quantity should be same as manufacturing quantity""" | ||||||
| 		for d in self.get('mtn_details'): | 		for d in self.get('mtn_details'): | ||||||
| 			if d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty): | 			if d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty): | ||||||
| 				msgprint(_("Row #") + " %s: " % d.idx  | 				msgprint(_("Row #") + " %s: " % d.idx | ||||||
| 					+ _("Quantity should be equal to Manufacturing Quantity. To fetch items again, click on 'Get Items' button or update the Quantity manually."), raise_exception=1) | 					+ _("Quantity should be equal to Manufacturing Quantity. To fetch items again, click on 'Get Items' button or update the Quantity manually."), raise_exception=1) | ||||||
| 						 | 
 | ||||||
| 	def validate_return_reference_doc(self): | 	def validate_return_reference_doc(self): | ||||||
| 		"""validate item with reference doc""" | 		"""validate item with reference doc""" | ||||||
| 		ref = get_return_doc_and_details(self) | 		ref = get_return_doc_and_details(self) | ||||||
| 		 | 
 | ||||||
| 		if ref.doc: | 		if ref.doc: | ||||||
| 			# validate docstatus | 			# validate docstatus | ||||||
| 			if ref.doc.docstatus != 1: | 			if ref.doc.docstatus != 1: | ||||||
| 				frappe.msgprint(_(ref.doc.doctype) + ' "' + ref.doc.name + '": '  | 				frappe.msgprint(_(ref.doc.doctype) + ' "' + ref.doc.name + '": ' | ||||||
| 					+ _("Status should be Submitted"), raise_exception=frappe.InvalidStatusError) | 					+ _("Status should be Submitted"), raise_exception=frappe.InvalidStatusError) | ||||||
| 			 | 
 | ||||||
| 			# update stock check | 			# update stock check | ||||||
| 			if ref.doc.doctype == "Sales Invoice" and cint(ref.doc.update_stock) != 1: | 			if ref.doc.doctype == "Sales Invoice" and cint(ref.doc.update_stock) != 1: | ||||||
| 				frappe.msgprint(_(ref.doc.doctype) + ' "' + ref.doc.name + '": '  | 				frappe.msgprint(_(ref.doc.doctype) + ' "' + ref.doc.name + '": ' | ||||||
| 					+ _("Update Stock should be checked."),  | 					+ _("Update Stock should be checked."), | ||||||
| 					raise_exception=NotUpdateStockError) | 					raise_exception=NotUpdateStockError) | ||||||
| 			 | 
 | ||||||
| 			# posting date check | 			# posting date check | ||||||
| 			ref_posting_datetime = "%s %s" % (cstr(ref.doc.posting_date),  | 			ref_posting_datetime = "%s %s" % (cstr(ref.doc.posting_date), | ||||||
| 				cstr(ref.doc.posting_time) or "00:00:00") | 				cstr(ref.doc.posting_time) or "00:00:00") | ||||||
| 			this_posting_datetime = "%s %s" % (cstr(self.posting_date),  | 			this_posting_datetime = "%s %s" % (cstr(self.posting_date), | ||||||
| 				cstr(self.posting_time)) | 				cstr(self.posting_time)) | ||||||
| 			if this_posting_datetime < ref_posting_datetime: | 			if this_posting_datetime < ref_posting_datetime: | ||||||
| 				from frappe.utils.dateutils import datetime_in_user_format | 				from frappe.utils.dateutils import datetime_in_user_format | ||||||
| 				frappe.msgprint(_("Posting Date Time cannot be before") | 				frappe.msgprint(_("Posting Date Time cannot be before") | ||||||
| 					+ ": " + datetime_in_user_format(ref_posting_datetime), | 					+ ": " + datetime_in_user_format(ref_posting_datetime), | ||||||
| 					raise_exception=True) | 					raise_exception=True) | ||||||
| 			 | 
 | ||||||
| 			stock_items = get_stock_items_for_return(ref.doc, ref.parentfields) | 			stock_items = get_stock_items_for_return(ref.doc, ref.parentfields) | ||||||
| 			already_returned_item_qty = self.get_already_returned_item_qty(ref.fieldname) | 			already_returned_item_qty = self.get_already_returned_item_qty(ref.fieldname) | ||||||
| 			 | 
 | ||||||
| 			for item in self.get("mtn_details"): | 			for item in self.get("mtn_details"): | ||||||
| 				# validate if item exists in the ref doc and that it is a stock item | 				# validate if item exists in the ref doc and that it is a stock item | ||||||
| 				if item.item_code not in stock_items: | 				if item.item_code not in stock_items: | ||||||
| 					msgprint(_("Item") + ': "' + item.item_code + _("\" does not exist in ") + | 					msgprint(_("Item") + ': "' + item.item_code + _("\" does not exist in ") + | ||||||
| 						ref.doc.doctype + ": " + ref.doc.name,  | 						ref.doc.doctype + ": " + ref.doc.name, | ||||||
| 						raise_exception=frappe.DoesNotExistError) | 						raise_exception=frappe.DoesNotExistError) | ||||||
| 				 | 
 | ||||||
| 				# validate quantity <= ref item's qty - qty already returned | 				# validate quantity <= ref item's qty - qty already returned | ||||||
| 				ref_item = ref.doc.getone({"item_code": item.item_code}) | 				ref_item = ref.doc.getone({"item_code": item.item_code}) | ||||||
| 				returnable_qty = ref_item.qty - flt(already_returned_item_qty.get(item.item_code)) | 				returnable_qty = ref_item.qty - flt(already_returned_item_qty.get(item.item_code)) | ||||||
| 				if not returnable_qty: | 				if not returnable_qty: | ||||||
| 					frappe.throw("{item}: {item_code} {returned}".format( | 					frappe.throw("{item}: {item_code} {returned}".format( | ||||||
| 						item=_("Item"), item_code=item.item_code,  | 						item=_("Item"), item_code=item.item_code, | ||||||
| 						returned=_("already returned though some other documents")),  | 						returned=_("already returned though some other documents")), | ||||||
| 						StockOverReturnError) | 						StockOverReturnError) | ||||||
| 				elif item.transfer_qty > returnable_qty: | 				elif item.transfer_qty > returnable_qty: | ||||||
| 					frappe.throw("{item}: {item_code}, {returned}: {qty}".format( | 					frappe.throw("{item}: {item_code}, {returned}: {qty}".format( | ||||||
| 						item=_("Item"), item_code=item.item_code, | 						item=_("Item"), item_code=item.item_code, | ||||||
| 						returned=_("Max Returnable Qty"), qty=returnable_qty), StockOverReturnError) | 						returned=_("Max Returnable Qty"), qty=returnable_qty), StockOverReturnError) | ||||||
| 						 | 
 | ||||||
| 	def get_already_returned_item_qty(self, ref_fieldname): | 	def get_already_returned_item_qty(self, ref_fieldname): | ||||||
| 		return dict(frappe.db.sql("""select item_code, sum(transfer_qty) as qty | 		return dict(frappe.db.sql("""select item_code, sum(transfer_qty) as qty | ||||||
| 			from `tabStock Entry Detail` where parent in ( | 			from `tabStock Entry Detail` where parent in ( | ||||||
| 				select name from `tabStock Entry` where `%s`=%s and docstatus=1) | 				select name from `tabStock Entry` where `%s`=%s and docstatus=1) | ||||||
| 			group by item_code""" % (ref_fieldname, "%s"), (self.get(ref_fieldname),))) | 			group by item_code""" % (ref_fieldname, "%s"), (self.get(ref_fieldname),))) | ||||||
| 						 | 
 | ||||||
| 	def update_stock_ledger(self): | 	def update_stock_ledger(self): | ||||||
| 		sl_entries = []			 | 		sl_entries = [] | ||||||
| 		for d in self.get('mtn_details'): | 		for d in self.get('mtn_details'): | ||||||
| 			if cstr(d.s_warehouse) and self.docstatus == 1: | 			if cstr(d.s_warehouse) and self.docstatus == 1: | ||||||
| 				sl_entries.append(self.get_sl_entries(d, { | 				sl_entries.append(self.get_sl_entries(d, { | ||||||
| @ -304,57 +304,57 @@ class StockEntry(StockController): | |||||||
| 					"actual_qty": -flt(d.transfer_qty), | 					"actual_qty": -flt(d.transfer_qty), | ||||||
| 					"incoming_rate": 0 | 					"incoming_rate": 0 | ||||||
| 				})) | 				})) | ||||||
| 				 | 
 | ||||||
| 			if cstr(d.t_warehouse): | 			if cstr(d.t_warehouse): | ||||||
| 				sl_entries.append(self.get_sl_entries(d, { | 				sl_entries.append(self.get_sl_entries(d, { | ||||||
| 					"warehouse": cstr(d.t_warehouse), | 					"warehouse": cstr(d.t_warehouse), | ||||||
| 					"actual_qty": flt(d.transfer_qty), | 					"actual_qty": flt(d.transfer_qty), | ||||||
| 					"incoming_rate": flt(d.incoming_rate) | 					"incoming_rate": flt(d.incoming_rate) | ||||||
| 				})) | 				})) | ||||||
| 			 | 
 | ||||||
| 			# On cancellation, make stock ledger entry for  | 			# On cancellation, make stock ledger entry for | ||||||
| 			# target warehouse first, to update serial no values properly | 			# target warehouse first, to update serial no values properly | ||||||
| 			 | 
 | ||||||
| 			if cstr(d.s_warehouse) and self.docstatus == 2: | 			if cstr(d.s_warehouse) and self.docstatus == 2: | ||||||
| 				sl_entries.append(self.get_sl_entries(d, { | 				sl_entries.append(self.get_sl_entries(d, { | ||||||
| 					"warehouse": cstr(d.s_warehouse), | 					"warehouse": cstr(d.s_warehouse), | ||||||
| 					"actual_qty": -flt(d.transfer_qty), | 					"actual_qty": -flt(d.transfer_qty), | ||||||
| 					"incoming_rate": 0 | 					"incoming_rate": 0 | ||||||
| 				})) | 				})) | ||||||
| 				 | 
 | ||||||
| 		self.make_sl_entries(sl_entries, self.amended_from and 'Yes' or 'No') | 		self.make_sl_entries(sl_entries, self.amended_from and 'Yes' or 'No') | ||||||
| 
 | 
 | ||||||
| 	def update_production_order(self): | 	def update_production_order(self): | ||||||
| 		def _validate_production_order(pro_doc): | 		def _validate_production_order(pro_doc): | ||||||
| 			if flt(pro_doc.docstatus) != 1: | 			if flt(pro_doc.docstatus) != 1: | ||||||
| 				frappe.throw(_("Production Order must be submitted") + ": " +  | 				frappe.throw(_("Production Order must be submitted") + ": " + | ||||||
| 					self.production_order) | 					self.production_order) | ||||||
| 					 | 
 | ||||||
| 			if pro_doc.status == 'Stopped': | 			if pro_doc.status == 'Stopped': | ||||||
| 				msgprint(_("Transaction not allowed against stopped Production Order") + ": " +  | 				msgprint(_("Transaction not allowed against stopped Production Order") + ": " + | ||||||
| 					self.production_order) | 					self.production_order) | ||||||
| 		 | 
 | ||||||
| 		if self.production_order: | 		if self.production_order: | ||||||
| 			pro_doc = frappe.get_doc("Production Order", self.production_order) | 			pro_doc = frappe.get_doc("Production Order", self.production_order) | ||||||
| 			_validate_production_order(pro_doc) | 			_validate_production_order(pro_doc) | ||||||
| 			self.update_produced_qty(pro_doc) | 			self.update_produced_qty(pro_doc) | ||||||
| 			if self.purpose == "Manufacture/Repack": | 			if self.purpose == "Manufacture/Repack": | ||||||
| 				self.update_planned_qty(pro_doc) | 				self.update_planned_qty(pro_doc) | ||||||
| 			 | 
 | ||||||
| 	def update_produced_qty(self, pro_doc): | 	def update_produced_qty(self, pro_doc): | ||||||
| 		if self.purpose == "Manufacture/Repack": | 		if self.purpose == "Manufacture/Repack": | ||||||
| 			produced_qty = flt(pro_doc.produced_qty) + \ | 			produced_qty = flt(pro_doc.produced_qty) + \ | ||||||
| 				(self.docstatus==1 and 1 or -1 ) * flt(self.fg_completed_qty) | 				(self.docstatus==1 and 1 or -1 ) * flt(self.fg_completed_qty) | ||||||
| 				 | 
 | ||||||
| 			if produced_qty > flt(pro_doc.qty): | 			if produced_qty > flt(pro_doc.qty): | ||||||
| 				frappe.throw(_("Production Order") + ": " + self.production_order + "\n" + | 				frappe.throw(_("Production Order") + ": " + self.production_order + "\n" + | ||||||
| 					_("Total Manufactured Qty can not be greater than Planned qty to manufacture")  | 					_("Total Manufactured Qty can not be greater than Planned qty to manufacture") | ||||||
| 					+ "(%s/%s)" % (produced_qty, flt(pro_doc.qty)), StockOverProductionError) | 					+ "(%s/%s)" % (produced_qty, flt(pro_doc.qty)), StockOverProductionError) | ||||||
| 					 | 
 | ||||||
| 			status = 'Completed' if flt(produced_qty) >= flt(pro_doc.qty) else 'In Process' | 			status = 'Completed' if flt(produced_qty) >= flt(pro_doc.qty) else 'In Process' | ||||||
| 			frappe.db.sql("""update `tabProduction Order` set status=%s, produced_qty=%s  | 			frappe.db.sql("""update `tabProduction Order` set status=%s, produced_qty=%s | ||||||
| 				where name=%s""", (status, produced_qty, self.production_order)) | 				where name=%s""", (status, produced_qty, self.production_order)) | ||||||
| 			 | 
 | ||||||
| 	def update_planned_qty(self, pro_doc): | 	def update_planned_qty(self, pro_doc): | ||||||
| 		from erpnext.stock.utils import update_bin | 		from erpnext.stock.utils import update_bin | ||||||
| 		update_bin({ | 		update_bin({ | ||||||
| @ -363,16 +363,16 @@ class StockEntry(StockController): | |||||||
| 			"posting_date": self.posting_date, | 			"posting_date": self.posting_date, | ||||||
| 			"planned_qty": (self.docstatus==1 and -1 or 1 ) * flt(self.fg_completed_qty) | 			"planned_qty": (self.docstatus==1 and -1 or 1 ) * flt(self.fg_completed_qty) | ||||||
| 		}) | 		}) | ||||||
| 					 | 
 | ||||||
| 	def get_item_details(self, arg): | 	def get_item_details(self, arg): | ||||||
| 		arg = json.loads(arg) | 		arg = json.loads(arg) | ||||||
| 		item = frappe.db.sql("""select stock_uom, description, item_name,  | 		item = frappe.db.sql("""select stock_uom, description, item_name, | ||||||
| 			expense_account, buying_cost_center from `tabItem`  | 			expense_account, buying_cost_center from `tabItem` | ||||||
| 			where name = %s and (ifnull(end_of_life,'')='' or end_of_life ='0000-00-00'  | 			where name = %s and (ifnull(end_of_life,'')='' or end_of_life ='0000-00-00' | ||||||
| 			or end_of_life > now())""", (arg.get('item_code')), as_dict = 1) | 			or end_of_life > now())""", (arg.get('item_code')), as_dict = 1) | ||||||
| 		if not item:  | 		if not item: | ||||||
| 			msgprint("Item is not active", raise_exception=1) | 			msgprint("Item is not active", raise_exception=1) | ||||||
| 						 | 
 | ||||||
| 		ret = { | 		ret = { | ||||||
| 			'uom'			      	: item and item[0]['stock_uom'] or '', | 			'uom'			      	: item and item[0]['stock_uom'] or '', | ||||||
| 			'stock_uom'			  	: item and item[0]['stock_uom'] or '', | 			'stock_uom'			  	: item and item[0]['stock_uom'] or '', | ||||||
| @ -394,7 +394,7 @@ class StockEntry(StockController): | |||||||
| 
 | 
 | ||||||
| 	def get_uom_details(self, arg = ''): | 	def get_uom_details(self, arg = ''): | ||||||
| 		arg, ret = eval(arg), {} | 		arg, ret = eval(arg), {} | ||||||
| 		uom = frappe.db.sql("""select conversion_factor from `tabUOM Conversion Detail`  | 		uom = frappe.db.sql("""select conversion_factor from `tabUOM Conversion Detail` | ||||||
| 			where parent = %s and uom = %s""", (arg['item_code'], arg['uom']), as_dict = 1) | 			where parent = %s and uom = %s""", (arg['item_code'], arg['uom']), as_dict = 1) | ||||||
| 		if not uom or not flt(uom[0].conversion_factor): | 		if not uom or not flt(uom[0].conversion_factor): | ||||||
| 			msgprint("There is no Conversion Factor for UOM '%s' in Item '%s'" % (arg['uom'], | 			msgprint("There is no Conversion Factor for UOM '%s' in Item '%s'" % (arg['uom'], | ||||||
| @ -406,7 +406,7 @@ class StockEntry(StockController): | |||||||
| 				'transfer_qty'			: flt(arg['qty']) * flt(uom[0]['conversion_factor']), | 				'transfer_qty'			: flt(arg['qty']) * flt(uom[0]['conversion_factor']), | ||||||
| 			} | 			} | ||||||
| 		return ret | 		return ret | ||||||
| 		 | 
 | ||||||
| 	def get_warehouse_details(self, args): | 	def get_warehouse_details(self, args): | ||||||
| 		args = json.loads(args) | 		args = json.loads(args) | ||||||
| 		ret = {} | 		ret = {} | ||||||
| @ -416,14 +416,14 @@ class StockEntry(StockController): | |||||||
| 				"posting_time": self.posting_time, | 				"posting_time": self.posting_time, | ||||||
| 			}) | 			}) | ||||||
| 			args = frappe._dict(args) | 			args = frappe._dict(args) | ||||||
| 		 | 
 | ||||||
| 			ret = { | 			ret = { | ||||||
| 				"actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0, | 				"actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0, | ||||||
| 				"incoming_rate" : self.get_incoming_rate(args) | 				"incoming_rate" : self.get_incoming_rate(args) | ||||||
| 			} | 			} | ||||||
| 		return ret | 		return ret | ||||||
| 		 | 
 | ||||||
| 	def get_items(self):		 | 	def get_items(self): | ||||||
| 		pro_obj = None | 		pro_obj = None | ||||||
| 		if self.production_order: | 		if self.production_order: | ||||||
| 			# common validations | 			# common validations | ||||||
| @ -434,7 +434,7 @@ class StockEntry(StockController): | |||||||
| 			else: | 			else: | ||||||
| 				# invalid production order | 				# invalid production order | ||||||
| 				self.production_order = None | 				self.production_order = None | ||||||
| 		 | 
 | ||||||
| 		if self.bom_no: | 		if self.bom_no: | ||||||
| 			if self.purpose in ["Material Issue", "Material Transfer", "Manufacture/Repack", | 			if self.purpose in ["Material Issue", "Material Transfer", "Manufacture/Repack", | ||||||
| 					"Subcontract"]: | 					"Subcontract"]: | ||||||
| @ -451,10 +451,10 @@ class StockEntry(StockController): | |||||||
| 
 | 
 | ||||||
| 				# add raw materials to Stock Entry Detail table | 				# add raw materials to Stock Entry Detail table | ||||||
| 				idx = self.add_to_stock_entry_detail(item_dict) | 				idx = self.add_to_stock_entry_detail(item_dict) | ||||||
| 					 | 
 | ||||||
| 			# add finished good item to Stock Entry Detail table -- along with bom_no | 			# add finished good item to Stock Entry Detail table -- along with bom_no | ||||||
| 			if self.production_order and self.purpose == "Manufacture/Repack": | 			if self.production_order and self.purpose == "Manufacture/Repack": | ||||||
| 				item = frappe.db.get_value("Item", pro_obj.production_item, ["item_name",  | 				item = frappe.db.get_value("Item", pro_obj.production_item, ["item_name", | ||||||
| 					"description", "stock_uom", "expense_account", "buying_cost_center"], as_dict=1) | 					"description", "stock_uom", "expense_account", "buying_cost_center"], as_dict=1) | ||||||
| 				self.add_to_stock_entry_detail({ | 				self.add_to_stock_entry_detail({ | ||||||
| 					cstr(pro_obj.production_item): { | 					cstr(pro_obj.production_item): { | ||||||
| @ -468,14 +468,14 @@ class StockEntry(StockController): | |||||||
| 						"cost_center": item.buying_cost_center, | 						"cost_center": item.buying_cost_center, | ||||||
| 					} | 					} | ||||||
| 				}, bom_no=pro_obj.bom_no, idx=idx) | 				}, bom_no=pro_obj.bom_no, idx=idx) | ||||||
| 								 | 
 | ||||||
| 			elif self.purpose in ["Material Receipt", "Manufacture/Repack"]: | 			elif self.purpose in ["Material Receipt", "Manufacture/Repack"]: | ||||||
| 				if self.purpose=="Material Receipt": | 				if self.purpose=="Material Receipt": | ||||||
| 					self.from_warehouse = "" | 					self.from_warehouse = "" | ||||||
| 					 | 
 | ||||||
| 				item = frappe.db.sql("""select name, item_name, description,  | 				item = frappe.db.sql("""select name, item_name, description, | ||||||
| 					stock_uom, expense_account, buying_cost_center from `tabItem`  | 					stock_uom, expense_account, buying_cost_center from `tabItem` | ||||||
| 					where name=(select item from tabBOM where name=%s)""",  | 					where name=(select item from tabBOM where name=%s)""", | ||||||
| 					self.bom_no, as_dict=1) | 					self.bom_no, as_dict=1) | ||||||
| 				self.add_to_stock_entry_detail({ | 				self.add_to_stock_entry_detail({ | ||||||
| 					item[0]["name"] : { | 					item[0]["name"] : { | ||||||
| @ -488,20 +488,20 @@ class StockEntry(StockController): | |||||||
| 						"cost_center": item[0].buying_cost_center, | 						"cost_center": item[0].buying_cost_center, | ||||||
| 					} | 					} | ||||||
| 				}, bom_no=self.bom_no, idx=idx) | 				}, bom_no=self.bom_no, idx=idx) | ||||||
| 		 | 
 | ||||||
| 		self.get_stock_and_rate() | 		self.get_stock_and_rate() | ||||||
| 	 | 
 | ||||||
| 	def get_bom_raw_materials(self, qty): | 	def get_bom_raw_materials(self, qty): | ||||||
| 		from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict | 		from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict | ||||||
| 		 | 
 | ||||||
| 		# item dict = { item_code: {qty, description, stock_uom} } | 		# item dict = { item_code: {qty, description, stock_uom} } | ||||||
| 		item_dict = get_bom_items_as_dict(self.bom_no, qty=qty, fetch_exploded = self.use_multi_level_bom) | 		item_dict = get_bom_items_as_dict(self.bom_no, qty=qty, fetch_exploded = self.use_multi_level_bom) | ||||||
| 		 | 
 | ||||||
| 		for item in item_dict.values(): | 		for item in item_dict.values(): | ||||||
| 			item.from_warehouse = item.default_warehouse | 			item.from_warehouse = item.default_warehouse | ||||||
| 			 | 
 | ||||||
| 		return item_dict | 		return item_dict | ||||||
| 			 | 
 | ||||||
| 	def get_pending_raw_materials(self, pro_obj): | 	def get_pending_raw_materials(self, pro_obj): | ||||||
| 		""" | 		""" | ||||||
| 			issue (item quantity) that is pending to issue or desire to transfer, | 			issue (item quantity) that is pending to issue or desire to transfer, | ||||||
| @ -509,10 +509,10 @@ class StockEntry(StockController): | |||||||
| 		""" | 		""" | ||||||
| 		item_dict = self.get_bom_raw_materials(1) | 		item_dict = self.get_bom_raw_materials(1) | ||||||
| 		issued_item_qty = self.get_issued_qty() | 		issued_item_qty = self.get_issued_qty() | ||||||
| 		 | 
 | ||||||
| 		max_qty = flt(pro_obj.qty) | 		max_qty = flt(pro_obj.qty) | ||||||
| 		only_pending_fetched = [] | 		only_pending_fetched = [] | ||||||
| 		 | 
 | ||||||
| 		for item in item_dict: | 		for item in item_dict: | ||||||
| 			pending_to_issue = (max_qty * item_dict[item]["qty"]) - issued_item_qty.get(item, 0) | 			pending_to_issue = (max_qty * item_dict[item]["qty"]) - issued_item_qty.get(item, 0) | ||||||
| 			desire_to_transfer = flt(self.fg_completed_qty) * item_dict[item]["qty"] | 			desire_to_transfer = flt(self.fg_completed_qty) * item_dict[item]["qty"] | ||||||
| @ -522,17 +522,17 @@ class StockEntry(StockController): | |||||||
| 				item_dict[item]["qty"] = pending_to_issue | 				item_dict[item]["qty"] = pending_to_issue | ||||||
| 				if pending_to_issue: | 				if pending_to_issue: | ||||||
| 					only_pending_fetched.append(item) | 					only_pending_fetched.append(item) | ||||||
| 		 | 
 | ||||||
| 		# delete items with 0 qty | 		# delete items with 0 qty | ||||||
| 		for item in item_dict.keys(): | 		for item in item_dict.keys(): | ||||||
| 			if not item_dict[item]["qty"]: | 			if not item_dict[item]["qty"]: | ||||||
| 				del item_dict[item] | 				del item_dict[item] | ||||||
| 		 | 
 | ||||||
| 		# show some message | 		# show some message | ||||||
| 		if not len(item_dict): | 		if not len(item_dict): | ||||||
| 			frappe.msgprint(_("""All items have already been transferred \ | 			frappe.msgprint(_("""All items have already been transferred \ | ||||||
| 				for this Production Order.""")) | 				for this Production Order.""")) | ||||||
| 			 | 
 | ||||||
| 		elif only_pending_fetched: | 		elif only_pending_fetched: | ||||||
| 			frappe.msgprint(_("""Only quantities pending to be transferred \ | 			frappe.msgprint(_("""Only quantities pending to be transferred \ | ||||||
| 				were fetched for the following items:\n""" + "\n".join(only_pending_fetched))) | 				were fetched for the following items:\n""" + "\n".join(only_pending_fetched))) | ||||||
| @ -548,7 +548,7 @@ class StockEntry(StockController): | |||||||
| 			group by t1.item_code""", self.production_order) | 			group by t1.item_code""", self.production_order) | ||||||
| 		for t in result: | 		for t in result: | ||||||
| 			issued_item_qty[t[0]] = flt(t[1]) | 			issued_item_qty[t[0]] = flt(t[1]) | ||||||
| 		 | 
 | ||||||
| 		return issued_item_qty | 		return issued_item_qty | ||||||
| 
 | 
 | ||||||
| 	def add_to_stock_entry_detail(self, item_dict, bom_no=None, idx=None): | 	def add_to_stock_entry_detail(self, item_dict, bom_no=None, idx=None): | ||||||
| @ -569,79 +569,79 @@ class StockEntry(StockController): | |||||||
| 			se_child.qty = flt(item_dict[d]["qty"]) | 			se_child.qty = flt(item_dict[d]["qty"]) | ||||||
| 			se_child.expense_account = item_dict[d]["expense_account"] or expense_account | 			se_child.expense_account = item_dict[d]["expense_account"] or expense_account | ||||||
| 			se_child.cost_center = item_dict[d]["cost_center"] or cost_center | 			se_child.cost_center = item_dict[d]["cost_center"] or cost_center | ||||||
| 			 | 
 | ||||||
| 			# in stock uom | 			# in stock uom | ||||||
| 			se_child.transfer_qty = flt(item_dict[d]["qty"]) | 			se_child.transfer_qty = flt(item_dict[d]["qty"]) | ||||||
| 			se_child.conversion_factor = 1.00 | 			se_child.conversion_factor = 1.00 | ||||||
| 			 | 
 | ||||||
| 			# to be assigned for finished item | 			# to be assigned for finished item | ||||||
| 			se_child.bom_no = bom_no | 			se_child.bom_no = bom_no | ||||||
| 
 | 
 | ||||||
| 			# increment idx by 1 | 			# increment idx by 1 | ||||||
| 			idx += 1 | 			idx += 1 | ||||||
| 		return idx | 		return idx | ||||||
| 		 | 
 | ||||||
| 	def validate_with_material_request(self): | 	def validate_with_material_request(self): | ||||||
| 		for item in self.get("mtn_details"): | 		for item in self.get("mtn_details"): | ||||||
| 			if item.material_request: | 			if item.material_request: | ||||||
| 				mreq_item = frappe.db.get_value("Material Request Item",  | 				mreq_item = frappe.db.get_value("Material Request Item", | ||||||
| 					{"name": item.material_request_item, "parent": item.material_request}, | 					{"name": item.material_request_item, "parent": item.material_request}, | ||||||
| 					["item_code", "warehouse", "idx"], as_dict=True) | 					["item_code", "warehouse", "idx"], as_dict=True) | ||||||
| 				if mreq_item.item_code != item.item_code or mreq_item.warehouse != item.t_warehouse: | 				if mreq_item.item_code != item.item_code or mreq_item.warehouse != item.t_warehouse: | ||||||
| 					msgprint(_("Row #") + (" %d: " % item.idx) + _("does not match") | 					msgprint(_("Row #") + (" %d: " % item.idx) + _("does not match") | ||||||
| 						+ " " + _("Row #") + (" %d %s " % (mreq_item.idx, _("of"))) | 						+ " " + _("Row #") + (" %d %s " % (mreq_item.idx, _("of"))) | ||||||
| 						+ _("Material Request") + (" - %s" % item.material_request),  | 						+ _("Material Request") + (" - %s" % item.material_request), | ||||||
| 						raise_exception=frappe.MappingMismatchError) | 						raise_exception=frappe.MappingMismatchError) | ||||||
| 	 | 
 | ||||||
| @frappe.whitelist()					 | @frappe.whitelist() | ||||||
| def get_party_details(ref_dt, ref_dn): | def get_party_details(ref_dt, ref_dn): | ||||||
| 	if ref_dt in ["Delivery Note", "Sales Invoice"]: | 	if ref_dt in ["Delivery Note", "Sales Invoice"]: | ||||||
| 		res = frappe.db.get_value(ref_dt, ref_dn,  | 		res = frappe.db.get_value(ref_dt, ref_dn, | ||||||
| 			["customer", "customer_name", "address_display as customer_address"], as_dict=1) | 			["customer", "customer_name", "address_display as customer_address"], as_dict=1) | ||||||
| 	else: | 	else: | ||||||
| 		res = frappe.db.get_value(ref_dt, ref_dn,  | 		res = frappe.db.get_value(ref_dt, ref_dn, | ||||||
| 			["supplier", "supplier_name", "address_display as supplier_address"], as_dict=1) | 			["supplier", "supplier_name", "address_display as supplier_address"], as_dict=1) | ||||||
| 	return res or {} | 	return res or {} | ||||||
| 	 | 
 | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
| def get_production_order_details(production_order): | def get_production_order_details(production_order): | ||||||
| 	result = frappe.db.sql("""select bom_no,  | 	result = frappe.db.sql("""select bom_no, | ||||||
| 		ifnull(qty, 0) - ifnull(produced_qty, 0) as fg_completed_qty, use_multi_level_bom,  | 		ifnull(qty, 0) - ifnull(produced_qty, 0) as fg_completed_qty, use_multi_level_bom, | ||||||
| 		wip_warehouse from `tabProduction Order` where name = %s""", production_order, as_dict=1) | 		wip_warehouse from `tabProduction Order` where name = %s""", production_order, as_dict=1) | ||||||
| 	return result and result[0] or {} | 	return result and result[0] or {} | ||||||
| 	 | 
 | ||||||
| def query_sales_return_doc(doctype, txt, searchfield, start, page_len, filters): | def query_sales_return_doc(doctype, txt, searchfield, start, page_len, filters): | ||||||
| 	conditions = "" | 	conditions = "" | ||||||
| 	if doctype == "Sales Invoice": | 	if doctype == "Sales Invoice": | ||||||
| 		conditions = "and update_stock=1" | 		conditions = "and update_stock=1" | ||||||
| 	 | 
 | ||||||
| 	return frappe.db.sql("""select name, customer, customer_name | 	return frappe.db.sql("""select name, customer, customer_name | ||||||
| 		from `tab%s` where docstatus = 1 | 		from `tab%s` where docstatus = 1 | ||||||
| 			and (`%s` like %%(txt)s  | 			and (`%s` like %%(txt)s | ||||||
| 				or `customer` like %%(txt)s) %s %s | 				or `customer` like %%(txt)s) %s %s | ||||||
| 		order by name, customer, customer_name | 		order by name, customer, customer_name | ||||||
| 		limit %s""" % (doctype, searchfield, conditions,  | 		limit %s""" % (doctype, searchfield, conditions, | ||||||
| 		get_match_cond(doctype), "%(start)s, %(page_len)s"),  | 		get_match_cond(doctype), "%(start)s, %(page_len)s"), | ||||||
| 		{"txt": "%%%s%%" % txt, "start": start, "page_len": page_len},  | 		{"txt": "%%%s%%" % txt, "start": start, "page_len": page_len}, | ||||||
| 		as_list=True) | 		as_list=True) | ||||||
| 	 | 
 | ||||||
| def query_purchase_return_doc(doctype, txt, searchfield, start, page_len, filters): | def query_purchase_return_doc(doctype, txt, searchfield, start, page_len, filters): | ||||||
| 	return frappe.db.sql("""select name, supplier, supplier_name | 	return frappe.db.sql("""select name, supplier, supplier_name | ||||||
| 		from `tab%s` where docstatus = 1 | 		from `tab%s` where docstatus = 1 | ||||||
| 			and (`%s` like %%(txt)s  | 			and (`%s` like %%(txt)s | ||||||
| 				or `supplier` like %%(txt)s) %s | 				or `supplier` like %%(txt)s) %s | ||||||
| 		order by name, supplier, supplier_name | 		order by name, supplier, supplier_name | ||||||
| 		limit %s""" % (doctype, searchfield, get_match_cond(doctype),  | 		limit %s""" % (doctype, searchfield, get_match_cond(doctype), | ||||||
| 		"%(start)s, %(page_len)s"),	{"txt": "%%%s%%" % txt, "start":  | 		"%(start)s, %(page_len)s"),	{"txt": "%%%s%%" % txt, "start": | ||||||
| 		start, "page_len": page_len}, as_list=True) | 		start, "page_len": page_len}, as_list=True) | ||||||
| 		 | 
 | ||||||
| def query_return_item(doctype, txt, searchfield, start, page_len, filters): | def query_return_item(doctype, txt, searchfield, start, page_len, filters): | ||||||
| 	txt = txt.replace("%", "") | 	txt = txt.replace("%", "") | ||||||
| 
 | 
 | ||||||
| 	ref = get_return_doc_and_details(filters) | 	ref = get_return_doc_and_details(filters) | ||||||
| 			 | 
 | ||||||
| 	stock_items = get_stock_items_for_return(ref.doc, ref.parentfields) | 	stock_items = get_stock_items_for_return(ref.doc, ref.parentfields) | ||||||
| 	 | 
 | ||||||
| 	result = [] | 	result = [] | ||||||
| 	for item in ref.doc.get_all_children(): | 	for item in ref.doc.get_all_children(): | ||||||
| 		if getattr(item, "item_code", None) in stock_items: | 		if getattr(item, "item_code", None) in stock_items: | ||||||
| @ -649,8 +649,8 @@ def query_return_item(doctype, txt, searchfield, start, page_len, filters): | |||||||
| 			item.description = cstr(item.description) | 			item.description = cstr(item.description) | ||||||
| 			if (txt in item.item_code) or (txt in item.item_name) or (txt in item.description): | 			if (txt in item.item_code) or (txt in item.item_name) or (txt in item.description): | ||||||
| 				val = [ | 				val = [ | ||||||
| 					item.item_code,  | 					item.item_code, | ||||||
| 					(len(item.item_name) > 40) and (item.item_name[:40] + "...") or item.item_name,  | 					(len(item.item_name) > 40) and (item.item_name[:40] + "...") or item.item_name, | ||||||
| 					(len(item.description) > 40) and (item.description[:40] + "...") or \ | 					(len(item.description) > 40) and (item.description[:40] + "...") or \ | ||||||
| 						item.description | 						item.description | ||||||
| 				] | 				] | ||||||
| @ -662,45 +662,45 @@ def query_return_item(doctype, txt, searchfield, start, page_len, filters): | |||||||
| def get_batch_no(doctype, txt, searchfield, start, page_len, filters): | def get_batch_no(doctype, txt, searchfield, start, page_len, filters): | ||||||
| 	if not filters.get("posting_date"): | 	if not filters.get("posting_date"): | ||||||
| 		filters["posting_date"] = nowdate() | 		filters["posting_date"] = nowdate() | ||||||
| 		 | 
 | ||||||
| 	batch_nos = None | 	batch_nos = None | ||||||
| 	args = { | 	args = { | ||||||
| 		'item_code': filters['item_code'],  | 		'item_code': filters['item_code'], | ||||||
| 		's_warehouse': filters['s_warehouse'],  | 		's_warehouse': filters['s_warehouse'], | ||||||
| 		'posting_date': filters['posting_date'],  | 		'posting_date': filters['posting_date'], | ||||||
| 		'txt': "%%%s%%" % txt,  | 		'txt': "%%%s%%" % txt, | ||||||
| 		'mcond':get_match_cond(doctype),  | 		'mcond':get_match_cond(doctype), | ||||||
| 		"start": start,  | 		"start": start, | ||||||
| 		"page_len": page_len | 		"page_len": page_len | ||||||
| 	} | 	} | ||||||
| 	 | 
 | ||||||
| 	if filters.get("s_warehouse"): | 	if filters.get("s_warehouse"): | ||||||
| 		batch_nos = frappe.db.sql("""select batch_no  | 		batch_nos = frappe.db.sql("""select batch_no | ||||||
| 			from `tabStock Ledger Entry` sle  | 			from `tabStock Ledger Entry` sle | ||||||
| 			where item_code = '%(item_code)s'  | 			where item_code = '%(item_code)s' | ||||||
| 				and warehouse = '%(s_warehouse)s' | 				and warehouse = '%(s_warehouse)s' | ||||||
| 				and batch_no like '%(txt)s'  | 				and batch_no like '%(txt)s' | ||||||
| 				and exists(select * from `tabBatch`  | 				and exists(select * from `tabBatch` | ||||||
| 					where name = sle.batch_no  | 					where name = sle.batch_no | ||||||
| 					and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s  | 					and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s | ||||||
| 						or expiry_date = '') | 						or expiry_date = '') | ||||||
| 					and docstatus != 2)  | 					and docstatus != 2) | ||||||
| 			%(mcond)s | 			%(mcond)s | ||||||
| 			group by batch_no having sum(actual_qty) > 0  | 			group by batch_no having sum(actual_qty) > 0 | ||||||
| 			order by batch_no desc  | 			order by batch_no desc | ||||||
| 			limit %(start)s, %(page_len)s """  | 			limit %(start)s, %(page_len)s """ | ||||||
| 			% args) | 			% args) | ||||||
| 	 | 
 | ||||||
| 	if batch_nos: | 	if batch_nos: | ||||||
| 		return batch_nos | 		return batch_nos | ||||||
| 	else: | 	else: | ||||||
| 		return frappe.db.sql("""select name from `tabBatch`  | 		return frappe.db.sql("""select name from `tabBatch` | ||||||
| 			where item = '%(item_code)s' | 			where item = '%(item_code)s' | ||||||
| 			and docstatus < 2 | 			and docstatus < 2 | ||||||
| 			and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s  | 			and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s | ||||||
| 				or expiry_date = '' or expiry_date = "0000-00-00") | 				or expiry_date = '' or expiry_date = "0000-00-00") | ||||||
| 			%(mcond)s | 			%(mcond)s | ||||||
| 			order by name desc  | 			order by name desc | ||||||
| 			limit %(start)s, %(page_len)s | 			limit %(start)s, %(page_len)s | ||||||
| 		""" % args) | 		""" % args) | ||||||
| 
 | 
 | ||||||
| @ -708,19 +708,19 @@ def get_stock_items_for_return(ref_doc, parentfields): | |||||||
| 	"""return item codes filtered from doc, which are stock items""" | 	"""return item codes filtered from doc, which are stock items""" | ||||||
| 	if isinstance(parentfields, basestring): | 	if isinstance(parentfields, basestring): | ||||||
| 		parentfields = [parentfields] | 		parentfields = [parentfields] | ||||||
| 	 | 
 | ||||||
| 	all_items = list(set([d.item_code for d in  | 	all_items = list(set([d.item_code for d in | ||||||
| 		ref_doc.get_all_children() if d.get("item_code")])) | 		ref_doc.get_all_children() if d.get("item_code")])) | ||||||
| 	stock_items = frappe.db.sql_list("""select name from `tabItem` | 	stock_items = frappe.db.sql_list("""select name from `tabItem` | ||||||
| 		where is_stock_item='Yes' and name in (%s)""" % (", ".join(["%s"] * len(all_items))), | 		where is_stock_item='Yes' and name in (%s)""" % (", ".join(["%s"] * len(all_items))), | ||||||
| 		tuple(all_items)) | 		tuple(all_items)) | ||||||
| 
 | 
 | ||||||
| 	return stock_items | 	return stock_items | ||||||
| 	 | 
 | ||||||
| def get_return_doc_and_details(args): | def get_return_doc_and_details(args): | ||||||
| 	ref = frappe._dict() | 	ref = frappe._dict() | ||||||
| 	 | 
 | ||||||
| 	# get ref_doc	 | 	# get ref_doc | ||||||
| 	if args.get("purpose") in return_map: | 	if args.get("purpose") in return_map: | ||||||
| 		for fieldname, val in return_map[args.get("purpose")].items(): | 		for fieldname, val in return_map[args.get("purpose")].items(): | ||||||
| 			if args.get(fieldname): | 			if args.get(fieldname): | ||||||
| @ -728,9 +728,9 @@ def get_return_doc_and_details(args): | |||||||
| 				ref.doc = frappe.get_doc(val[0], args.get(fieldname)) | 				ref.doc = frappe.get_doc(val[0], args.get(fieldname)) | ||||||
| 				ref.parentfields = val[1] | 				ref.parentfields = val[1] | ||||||
| 				break | 				break | ||||||
| 				 | 
 | ||||||
| 	return ref | 	return ref | ||||||
| 	 | 
 | ||||||
| return_map = { | return_map = { | ||||||
| 	"Sales Return": { | 	"Sales Return": { | ||||||
| 		# [Ref DocType, [Item tables' parentfields]] | 		# [Ref DocType, [Item tables' parentfields]] | ||||||
| @ -747,29 +747,28 @@ def make_return_jv(stock_entry): | |||||||
| 	se = frappe.get_doc("Stock Entry", stock_entry) | 	se = frappe.get_doc("Stock Entry", stock_entry) | ||||||
| 	if not se.purpose in ["Sales Return", "Purchase Return"]: | 	if not se.purpose in ["Sales Return", "Purchase Return"]: | ||||||
| 		return | 		return | ||||||
| 	 | 
 | ||||||
| 	ref = get_return_doc_and_details(se) | 	ref = get_return_doc_and_details(se) | ||||||
| 	 | 
 | ||||||
| 	if ref.doc.doctype == "Delivery Note": | 	if ref.doc.doctype == "Delivery Note": | ||||||
| 		result = make_return_jv_from_delivery_note(se, ref) | 		result = make_return_jv_from_delivery_note(se, ref) | ||||||
| 	elif ref.doc.doctype == "Sales Invoice": | 	elif ref.doc.doctype == "Sales Invoice": | ||||||
| 		result = make_return_jv_from_sales_invoice(se, ref) | 		result = make_return_jv_from_sales_invoice(se, ref) | ||||||
| 	elif ref.doc.doctype == "Purchase Receipt": | 	elif ref.doc.doctype == "Purchase Receipt": | ||||||
| 		result = make_return_jv_from_purchase_receipt(se, ref) | 		result = make_return_jv_from_purchase_receipt(se, ref) | ||||||
| 	 | 
 | ||||||
| 	# create jv doc and fetch balance for each unique row item | 	# create jv doc and fetch balance for each unique row item | ||||||
| 	jv_list = [{ | 	jv = frappe.new_doc("Journal Voucher") | ||||||
| 		"__islocal": 1, | 	jv.update({ | ||||||
| 		"doctype": "Journal Voucher", |  | ||||||
| 		"posting_date": se.posting_date, | 		"posting_date": se.posting_date, | ||||||
| 		"voucher_type": se.purpose == "Sales Return" and "Credit Note" or "Debit Note", | 		"voucher_type": se.purpose == "Sales Return" and "Credit Note" or "Debit Note", | ||||||
| 		"fiscal_year": se.fiscal_year, | 		"fiscal_year": se.fiscal_year, | ||||||
| 		"company": se.company | 		"company": se.company | ||||||
| 	}] | 	}) | ||||||
| 	 | 
 | ||||||
| 	from erpnext.accounts.utils import get_balance_on | 	from erpnext.accounts.utils import get_balance_on | ||||||
| 	for r in result: | 	for r in result: | ||||||
| 		jv_list.append({ | 		jv.append("entries", { | ||||||
| 			"__islocal": 1, | 			"__islocal": 1, | ||||||
| 			"doctype": "Journal Voucher Detail", | 			"doctype": "Journal Voucher Detail", | ||||||
| 			"parentfield": "entries", | 			"parentfield": "entries", | ||||||
| @ -779,139 +778,140 @@ def make_return_jv(stock_entry): | |||||||
| 			"balance": get_balance_on(r.get("account"), se.posting_date) \ | 			"balance": get_balance_on(r.get("account"), se.posting_date) \ | ||||||
| 				if r.get("account") else 0 | 				if r.get("account") else 0 | ||||||
| 		}) | 		}) | ||||||
| 		 | 
 | ||||||
| 	return jv_list | 	return jv | ||||||
| 	 | 
 | ||||||
| def make_return_jv_from_sales_invoice(se, ref): | def make_return_jv_from_sales_invoice(se, ref): | ||||||
| 	# customer account entry | 	# customer account entry | ||||||
| 	parent = { | 	parent = { | ||||||
| 		"account": ref.doc.debit_to, | 		"account": ref.doc.debit_to, | ||||||
| 		"against_invoice": ref.doc.name, | 		"against_invoice": ref.doc.name, | ||||||
| 	} | 	} | ||||||
| 	 | 
 | ||||||
| 	# income account entries | 	# income account entries | ||||||
| 	children = [] | 	children = [] | ||||||
| 	for se_item in se.get("mtn_details"): | 	for se_item in se.get("mtn_details"): | ||||||
| 		# find item in ref.doc | 		# find item in ref.doc | ||||||
| 		ref_item = ref.doc.get({"item_code": se_item.item_code})[0] | 		ref_item = ref.doc.get({"item_code": se_item.item_code})[0] | ||||||
| 		 | 
 | ||||||
| 		account = get_sales_account_from_item(ref.doc, ref_item) | 		account = get_sales_account_from_item(ref.doc, ref_item) | ||||||
| 		 | 
 | ||||||
| 		if account not in children: | 		if account not in children: | ||||||
| 			children.append(account) | 			children.append(account) | ||||||
| 			 | 
 | ||||||
| 	return [parent] + [{"account": account} for account in children] | 	return [parent] + [{"account": account} for account in children] | ||||||
| 	 | 
 | ||||||
| def get_sales_account_from_item(doc, ref_item): | def get_sales_account_from_item(doc, ref_item): | ||||||
| 	account = None | 	account = None | ||||||
| 	if not ref_item.income_account: | 	if not getattr(ref_item, "income_account", None): | ||||||
| 		if ref_item.parent_item: | 		if ref_item.parent_item: | ||||||
| 			parent_item = doc.get({"item_code": ref_item.parent_item})[0] | 			parent_item = doc.get(doc.fname, {"item_code": ref_item.parent_item})[0] | ||||||
| 			account = parent_item.income_account | 			account = parent_item.income_account | ||||||
| 	else: | 	else: | ||||||
| 		account = ref_item.income_account | 		account = ref_item.income_account | ||||||
| 	 | 
 | ||||||
| 	return account | 	return account | ||||||
| 	 | 
 | ||||||
| def make_return_jv_from_delivery_note(se, ref): | def make_return_jv_from_delivery_note(se, ref): | ||||||
| 	invoices_against_delivery = get_invoice_list("Sales Invoice Item", "delivery_note", | 	invoices_against_delivery = get_invoice_list("Sales Invoice Item", "delivery_note", | ||||||
| 		ref.doc.name) | 		ref.doc.name) | ||||||
| 	 | 
 | ||||||
| 	if not invoices_against_delivery: | 	if not invoices_against_delivery: | ||||||
| 		sales_orders_against_delivery = [d.against_sales_order for d in ref.doc.get_all_children() if d.against_sales_order] | 		sales_orders_against_delivery = [d.against_sales_order for d in ref.doc.get_all_children() if getattr(d, "against_sales_order", None)] | ||||||
| 		 | 
 | ||||||
| 		if sales_orders_against_delivery: | 		if sales_orders_against_delivery: | ||||||
| 			invoices_against_delivery = get_invoice_list("Sales Invoice Item", "sales_order", | 			invoices_against_delivery = get_invoice_list("Sales Invoice Item", "sales_order", | ||||||
| 				sales_orders_against_delivery) | 				sales_orders_against_delivery) | ||||||
| 			 | 
 | ||||||
| 	if not invoices_against_delivery: | 	if not invoices_against_delivery: | ||||||
| 		return [] | 		return [] | ||||||
| 		 | 
 | ||||||
| 	packing_item_parent_map = dict([[d.item_code, d.parent_item] for d in ref.doc.get(ref.parentfields[1])]) | 	packing_item_parent_map = dict([[d.item_code, d.parent_item] for d in ref.doc.get(ref.parentfields[1])]) | ||||||
| 	 | 
 | ||||||
| 	parent = {} | 	parent = {} | ||||||
| 	children = [] | 	children = [] | ||||||
| 	 | 
 | ||||||
| 	for se_item in se.get("mtn_details"): | 	for se_item in se.get("mtn_details"): | ||||||
| 		for sales_invoice in invoices_against_delivery: | 		for sales_invoice in invoices_against_delivery: | ||||||
| 			si = frappe.get_doc("Sales Invoice", sales_invoice) | 			si = frappe.get_doc("Sales Invoice", sales_invoice) | ||||||
| 			 | 
 | ||||||
| 			if se_item.item_code in packing_item_parent_map: | 			if se_item.item_code in packing_item_parent_map: | ||||||
| 				ref_item = si.get({"item_code": packing_item_parent_map[se_item.item_code]}) | 				ref_item = si.get({"item_code": packing_item_parent_map[se_item.item_code]}) | ||||||
| 			else: | 			else: | ||||||
| 				ref_item = si.get({"item_code": se_item.item_code}) | 				ref_item = si.get({"item_code": se_item.item_code}) | ||||||
| 			 | 
 | ||||||
| 			if not ref_item: | 			if not ref_item: | ||||||
| 				continue | 				continue | ||||||
| 				 | 
 | ||||||
| 			ref_item = ref_item[0] | 			ref_item = ref_item[0] | ||||||
| 			 | 
 | ||||||
| 			account = get_sales_account_from_item(si, ref_item) | 			account = get_sales_account_from_item(si, ref_item) | ||||||
| 			 | 
 | ||||||
| 			if account not in children: | 			if account not in children: | ||||||
| 				children.append(account) | 				children.append(account) | ||||||
| 			 | 
 | ||||||
| 			if not parent: | 			if not parent: | ||||||
| 				parent = {"account": si.debit_to} | 				parent = {"account": si.debit_to} | ||||||
| 
 | 
 | ||||||
| 			break | 			break | ||||||
| 			 | 
 | ||||||
| 	if len(invoices_against_delivery) == 1: | 	if len(invoices_against_delivery) == 1: | ||||||
| 		parent["against_invoice"] = invoices_against_delivery[0] | 		parent["against_invoice"] = invoices_against_delivery[0] | ||||||
| 	 | 
 | ||||||
| 	result = [parent] + [{"account": account} for account in children] | 	result = [parent] + [{"account": account} for account in children] | ||||||
| 	 | 
 | ||||||
| 	return result | 	return result | ||||||
| 	 | 
 | ||||||
| def get_invoice_list(doctype, link_field, value): | def get_invoice_list(doctype, link_field, value): | ||||||
| 	if isinstance(value, basestring): | 	if isinstance(value, basestring): | ||||||
| 		value = [value] | 		value = [value] | ||||||
| 	 | 
 | ||||||
| 	return frappe.db.sql_list("""select distinct parent from `tab%s` | 	return frappe.db.sql_list("""select distinct parent from `tab%s` | ||||||
| 		where docstatus = 1 and `%s` in (%s)""" % (doctype, link_field, | 		where docstatus = 1 and `%s` in (%s)""" % (doctype, link_field, | ||||||
| 			", ".join(["%s"]*len(value))), tuple(value)) | 			", ".join(["%s"]*len(value))), tuple(value)) | ||||||
| 			 | 
 | ||||||
| def make_return_jv_from_purchase_receipt(se, ref): | def make_return_jv_from_purchase_receipt(se, ref): | ||||||
| 	invoice_against_receipt = get_invoice_list("Purchase Invoice Item", "purchase_receipt", | 	invoice_against_receipt = get_invoice_list("Purchase Invoice Item", "purchase_receipt", | ||||||
| 		ref.doc.name) | 		ref.doc.name) | ||||||
| 	 | 
 | ||||||
| 	if not invoice_against_receipt: | 	if not invoice_against_receipt: | ||||||
| 		purchase_orders_against_receipt = [d.prevdoc_docname for d in  | 		purchase_orders_against_receipt = [d.prevdoc_docname for d in | ||||||
| 			ref.get({"prevdoc_doctype": "Purchase Order"}) if d.prevdoc_docname] | 			ref.doc.get(ref.doc.fname, {"prevdoc_doctype": "Purchase Order"}) | ||||||
| 		 | 			if getattr(d, "prevdoc_docname", None)] | ||||||
|  | 
 | ||||||
| 		if purchase_orders_against_receipt: | 		if purchase_orders_against_receipt: | ||||||
| 			invoice_against_receipt = get_invoice_list("Purchase Invoice Item", "purchase_order", | 			invoice_against_receipt = get_invoice_list("Purchase Invoice Item", "purchase_order", | ||||||
| 				purchase_orders_against_receipt) | 				purchase_orders_against_receipt) | ||||||
| 			 | 
 | ||||||
| 	if not invoice_against_receipt: | 	if not invoice_against_receipt: | ||||||
| 		return [] | 		return [] | ||||||
| 	 | 
 | ||||||
| 	parent = {} | 	parent = {} | ||||||
| 	children = [] | 	children = [] | ||||||
| 	 | 
 | ||||||
| 	for se_item in se.get("mtn_details"): | 	for se_item in se.get("mtn_details"): | ||||||
| 		for purchase_invoice in invoice_against_receipt: | 		for purchase_invoice in invoice_against_receipt: | ||||||
| 			pi = frappe.get_doc("Purchase Invoice", purchase_invoice) | 			pi = frappe.get_doc("Purchase Invoice", purchase_invoice) | ||||||
| 			ref_item = pi.get({"item_code": se_item.item_code}) | 			ref_item = pi.get({"item_code": se_item.item_code}) | ||||||
| 			 | 
 | ||||||
| 			if not ref_item: | 			if not ref_item: | ||||||
| 				continue | 				continue | ||||||
| 				 | 
 | ||||||
| 			ref_item = ref_item[0] | 			ref_item = ref_item[0] | ||||||
| 			 | 
 | ||||||
| 			account = ref_item.expense_account | 			account = ref_item.expense_account | ||||||
| 			 | 
 | ||||||
| 			if account not in children: | 			if account not in children: | ||||||
| 				children.append(account) | 				children.append(account) | ||||||
| 			 | 
 | ||||||
| 			if not parent: | 			if not parent: | ||||||
| 				parent = {"account": pi.credit_to} | 				parent = {"account": pi.credit_to} | ||||||
| 
 | 
 | ||||||
| 			break | 			break | ||||||
| 			 | 
 | ||||||
| 	if len(invoice_against_receipt) == 1: | 	if len(invoice_against_receipt) == 1: | ||||||
| 		parent["against_voucher"] = invoice_against_receipt[0] | 		parent["against_voucher"] = invoice_against_receipt[0] | ||||||
| 	 | 
 | ||||||
| 	result = [parent] + [{"account": account} for account in children] | 	result = [parent] + [{"account": account} for account in children] | ||||||
| 	 | 
 | ||||||
| 	return result | 	return result | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
| 
 | 
 | ||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| import frappe, unittest | import frappe, unittest | ||||||
|  | import frappe.defaults | ||||||
| from frappe.utils import flt, getdate | from frappe.utils import flt, getdate | ||||||
| from erpnext.stock.doctype.serial_no.serial_no import * | from erpnext.stock.doctype.serial_no.serial_no import * | ||||||
| from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory | from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory | ||||||
| @ -31,7 +32,6 @@ class TestStockEntry(unittest.TestCase): | |||||||
| 		st2.submit() | 		st2.submit() | ||||||
| 
 | 
 | ||||||
| 		from erpnext.stock.utils import reorder_item | 		from erpnext.stock.utils import reorder_item | ||||||
| 
 |  | ||||||
| 		reorder_item() | 		reorder_item() | ||||||
| 
 | 
 | ||||||
| 		mr_name = frappe.db.sql("""select parent from `tabMaterial Request Item` | 		mr_name = frappe.db.sql("""select parent from `tabMaterial Request Item` | ||||||
| @ -39,8 +39,6 @@ class TestStockEntry(unittest.TestCase): | |||||||
| 
 | 
 | ||||||
| 		self.assertTrue(mr_name) | 		self.assertTrue(mr_name) | ||||||
| 
 | 
 | ||||||
| 		frappe.db.set_default("company", self.old_default_company) |  | ||||||
| 
 |  | ||||||
| 	def test_material_receipt_gl_entry(self): | 	def test_material_receipt_gl_entry(self): | ||||||
| 		self._clear_stock_account_balance() | 		self._clear_stock_account_balance() | ||||||
| 		set_perpetual_inventory() | 		set_perpetual_inventory() | ||||||
| @ -367,14 +365,14 @@ class TestStockEntry(unittest.TestCase): | |||||||
| 
 | 
 | ||||||
| 	def _test_sales_return_jv(self, se): | 	def _test_sales_return_jv(self, se): | ||||||
| 		from erpnext.stock.doctype.stock_entry.stock_entry import make_return_jv | 		from erpnext.stock.doctype.stock_entry.stock_entry import make_return_jv | ||||||
| 		jv_list = make_return_jv(se.name) | 		jv = make_return_jv(se.name) | ||||||
| 
 | 
 | ||||||
| 		self.assertEqual(len(jv_list), 3) | 		self.assertEqual(len(jv.get("entries")), 2) | ||||||
| 		self.assertEqual(jv_list[0].get("voucher_type"), "Credit Note") | 		self.assertEqual(jv.get("voucher_type"), "Credit Note") | ||||||
| 		self.assertEqual(jv_list[0].get("posting_date"), se.posting_date) | 		self.assertEqual(jv.get("posting_date"), se.posting_date) | ||||||
| 		self.assertEqual(jv_list[1].get("account"), "_Test Customer - _TC") | 		self.assertEqual(jv.get("entries")[0].get("account"), "_Test Customer - _TC") | ||||||
| 		self.assertEqual(jv_list[2].get("account"), "Sales - _TC") | 		self.assertEqual(jv.get("entries")[1].get("account"), "Sales - _TC") | ||||||
| 		self.assertTrue(jv_list[1].get("against_invoice")) | 		self.assertTrue(jv.get("entries")[0].get("against_invoice")) | ||||||
| 
 | 
 | ||||||
| 	def test_make_return_jv_for_sales_invoice_non_packing_item(self): | 	def test_make_return_jv_for_sales_invoice_non_packing_item(self): | ||||||
| 		self._clear_stock_account_balance() | 		self._clear_stock_account_balance() | ||||||
| @ -527,14 +525,14 @@ class TestStockEntry(unittest.TestCase): | |||||||
| 
 | 
 | ||||||
| 	def _test_purchase_return_jv(self, se): | 	def _test_purchase_return_jv(self, se): | ||||||
| 		from erpnext.stock.doctype.stock_entry.stock_entry import make_return_jv | 		from erpnext.stock.doctype.stock_entry.stock_entry import make_return_jv | ||||||
| 		jv_list = make_return_jv(se.name) | 		jv = make_return_jv(se.name) | ||||||
| 
 | 
 | ||||||
| 		self.assertEqual(len(jv_list), 3) | 		self.assertEqual(len(jv.get("entries")), 2) | ||||||
| 		self.assertEqual(jv_list[0].get("voucher_type"), "Debit Note") | 		self.assertEqual(jv.get("voucher_type"), "Debit Note") | ||||||
| 		self.assertEqual(jv_list[0].get("posting_date"), se.posting_date) | 		self.assertEqual(jv.get("posting_date"), se.posting_date) | ||||||
| 		self.assertEqual(jv_list[1].get("account"), "_Test Supplier - _TC") | 		self.assertEqual(jv.get("entries")[0].get("account"), "_Test Supplier - _TC") | ||||||
| 		self.assertEqual(jv_list[2].get("account"), "_Test Account Cost for Goods Sold - _TC") | 		self.assertEqual(jv.get("entries")[1].get("account"), "_Test Account Cost for Goods Sold - _TC") | ||||||
| 		self.assertTrue(jv_list[1].get("against_voucher")) | 		self.assertTrue(jv.get("entries")[0].get("against_voucher")) | ||||||
| 
 | 
 | ||||||
| 	def test_make_return_jv_for_purchase_receipt(self): | 	def test_make_return_jv_for_purchase_receipt(self): | ||||||
| 		self._clear_stock_account_balance() | 		self._clear_stock_account_balance() | ||||||
| @ -774,10 +772,9 @@ class TestStockEntry(unittest.TestCase): | |||||||
| 
 | 
 | ||||||
| 	# permission tests | 	# permission tests | ||||||
| 	def test_warehouse_user(self): | 	def test_warehouse_user(self): | ||||||
| 		import frappe.defaults |  | ||||||
| 		set_perpetual_inventory(0) | 		set_perpetual_inventory(0) | ||||||
| 
 | 
 | ||||||
| 		frappe.defaults.add_default("Warehouse", "_Test Warehouse 1 - _TC1", "test@example.com", "Restriction") | 		frappe.defaults.add_default("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com", "Restriction") | ||||||
| 		frappe.defaults.add_default("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com", "Restriction") | 		frappe.defaults.add_default("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com", "Restriction") | ||||||
| 		frappe.get_doc("User", "test@example.com")\ | 		frappe.get_doc("User", "test@example.com")\ | ||||||
| 			.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager") | 			.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager") | ||||||
| @ -797,15 +794,17 @@ class TestStockEntry(unittest.TestCase): | |||||||
| 		st1.insert() | 		st1.insert() | ||||||
| 		st1.submit() | 		st1.submit() | ||||||
| 
 | 
 | ||||||
| 		frappe.defaults.clear_default("Warehouse", "_Test Warehouse 1 - _TC1", "test@example.com", parenttype="Restriction") | 		frappe.defaults.clear_default("Warehouse", "_Test Warehouse 1 - _TC", | ||||||
| 		frappe.defaults.clear_default("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com", parenttype="Restriction") | 			"test@example.com", parenttype="Restriction") | ||||||
|  | 		frappe.defaults.clear_default("Warehouse", "_Test Warehouse 2 - _TC1", | ||||||
|  | 			"test2@example.com", parenttype="Restriction") | ||||||
| 
 | 
 | ||||||
| 	def test_freeze_stocks (self): | 	def test_freeze_stocks (self): | ||||||
| 		self._clear_stock_account_balance() | 		self._clear_stock_account_balance() | ||||||
| 		frappe.db.set_value('Stock Settings', None,'stock_auth_role', '') | 		frappe.db.set_value('Stock Settings', None,'stock_auth_role', '') | ||||||
| 
 | 
 | ||||||
| 		# test freeze_stocks_upto | 		# test freeze_stocks_upto | ||||||
| 		date_newer_than_test_records = add_days(getdate(test_records[0][0]['posting_date']), 5) | 		date_newer_than_test_records = add_days(getdate(test_records[0]['posting_date']), 5) | ||||||
| 		frappe.db.set_value("Stock Settings", None, "stock_frozen_upto", date_newer_than_test_records) | 		frappe.db.set_value("Stock Settings", None, "stock_frozen_upto", date_newer_than_test_records) | ||||||
| 		se = frappe.copy_doc(test_records[0]).insert() | 		se = frappe.copy_doc(test_records[0]).insert() | ||||||
| 		self.assertRaises (StockFreezeError, se.submit) | 		self.assertRaises (StockFreezeError, se.submit) | ||||||
|  | |||||||
| @ -19,15 +19,15 @@ class StockLedgerEntry(DocListController): | |||||||
| 		self.validate_item() | 		self.validate_item() | ||||||
| 		validate_warehouse_company(self.warehouse, self.company) | 		validate_warehouse_company(self.warehouse, self.company) | ||||||
| 		self.scrub_posting_time() | 		self.scrub_posting_time() | ||||||
| 		 | 
 | ||||||
| 		from erpnext.accounts.utils import validate_fiscal_year | 		from erpnext.accounts.utils import validate_fiscal_year | ||||||
| 		validate_fiscal_year(self.posting_date, self.fiscal_year,  | 		validate_fiscal_year(self.posting_date, self.fiscal_year, | ||||||
| 			self.meta.get_label("posting_date")) | 			self.meta.get_label("posting_date")) | ||||||
| 
 | 
 | ||||||
| 	def on_submit(self): | 	def on_submit(self): | ||||||
| 		self.check_stock_frozen_date() | 		self.check_stock_frozen_date() | ||||||
| 		self.actual_amt_check() | 		self.actual_amt_check() | ||||||
| 		 | 
 | ||||||
| 		from erpnext.stock.doctype.serial_no.serial_no import process_serial_no | 		from erpnext.stock.doctype.serial_no.serial_no import process_serial_no | ||||||
| 		process_serial_no(self) | 		process_serial_no(self) | ||||||
| 
 | 
 | ||||||
| @ -58,7 +58,7 @@ class StockLedgerEntry(DocListController): | |||||||
| 				msgprint("Stock Ledger Entry: '%s' is mandatory" % k, raise_exception = 1) | 				msgprint("Stock Ledger Entry: '%s' is mandatory" % k, raise_exception = 1) | ||||||
| 			elif k == 'warehouse': | 			elif k == 'warehouse': | ||||||
| 				if not frappe.db.exists("Warehouse", self.get(k)): | 				if not frappe.db.exists("Warehouse", self.get(k)): | ||||||
| 					msgprint("Warehouse: '%s' does not exist in the system. Please check." %  | 					msgprint("Warehouse: '%s' does not exist in the system. Please check." % | ||||||
| 						self.get(k), raise_exception = 1) | 						self.get(k), raise_exception = 1) | ||||||
| 
 | 
 | ||||||
| 	def validate_item(self): | 	def validate_item(self): | ||||||
| @ -76,9 +76,9 @@ class StockLedgerEntry(DocListController): | |||||||
| 				frappe.throw("Batch number is mandatory for Item '%s'" % self.item_code) | 				frappe.throw("Batch number is mandatory for Item '%s'" % self.item_code) | ||||||
| 
 | 
 | ||||||
| 			# check if batch belongs to item | 			# check if batch belongs to item | ||||||
| 			if not frappe.db.get_value("Batch",  | 			if not frappe.db.get_value("Batch", | ||||||
| 					{"item": self.item_code, "name": self.batch_no}): | 					{"item": self.item_code, "name": self.batch_no}): | ||||||
| 				frappe.throw("'%s' is not a valid Batch Number for Item '%s'" %  | 				frappe.throw("'%s' is not a valid Batch Number for Item '%s'" % | ||||||
| 					(self.batch_no, self.item_code)) | 					(self.batch_no, self.item_code)) | ||||||
| 
 | 
 | ||||||
| 		if not self.stock_uom: | 		if not self.stock_uom: | ||||||
| @ -108,4 +108,4 @@ def on_doctype_update(): | |||||||
| 		where Key_name="posting_sort_index" """): | 		where Key_name="posting_sort_index" """): | ||||||
| 		frappe.db.commit() | 		frappe.db.commit() | ||||||
| 		frappe.db.sql("""alter table `tabStock Ledger Entry` | 		frappe.db.sql("""alter table `tabStock Ledger Entry` | ||||||
| 			add index posting_sort_index(posting_date, posting_time, name)""") | 			add index posting_sort_index(posting_date, posting_time, name)""") | ||||||
|  | |||||||
| @ -1,25 +1,25 @@ | |||||||
| [ | [ | ||||||
|  { |  { | ||||||
|   "company": "_Test Company",  |   "company": "_Test Company", | ||||||
|   "create_account_under": "Stock Assets - _TC",  |   "create_account_under": "Stock Assets - _TC", | ||||||
|   "doctype": "Warehouse",  |   "doctype": "Warehouse", | ||||||
|   "warehouse_name": "_Test Warehouse" |   "warehouse_name": "_Test Warehouse" | ||||||
|  },  |  }, | ||||||
|  { |  { | ||||||
|   "company": "_Test Company",  |   "company": "_Test Company", | ||||||
|   "create_account_under": "Fixed Assets - _TC",  |   "create_account_under": "Fixed Assets - _TC", | ||||||
|   "doctype": "Warehouse",  |   "doctype": "Warehouse", | ||||||
|   "warehouse_name": "_Test Warehouse 1" |   "warehouse_name": "_Test Warehouse 1" | ||||||
|  },  |  }, | ||||||
|  { |  { | ||||||
|   "company": "_Test Company 1",  |   "company": "_Test Company 1", | ||||||
|   "create_account_under": "Stock Assets - _TC",  |   "create_account_under": "Stock Assets - _TC", | ||||||
|   "doctype": "Warehouse",  |   "doctype": "Warehouse", | ||||||
|   "warehouse_name": "_Test Warehouse 2" |   "warehouse_name": "_Test Warehouse 2" | ||||||
|  },  |  }, | ||||||
|  { |  { | ||||||
|   "company": "_Test Company",  |   "company": "_Test Company", | ||||||
|   "doctype": "Warehouse",  |   "doctype": "Warehouse", | ||||||
|   "warehouse_name": "_Test Warehouse No Account" |   "warehouse_name": "_Test Warehouse No Account" | ||||||
|  } |  } | ||||||
| ] | ] | ||||||
|  | |||||||
| @ -169,9 +169,13 @@ def get_sle_before_datetime(args, for_update=False): | |||||||
| def get_sle_after_datetime(args, for_update=False): | def get_sle_after_datetime(args, for_update=False): | ||||||
| 	"""get Stock Ledger Entries after a particular datetime, for reposting""" | 	"""get Stock Ledger Entries after a particular datetime, for reposting""" | ||||||
| 	# NOTE: using for update of | 	# NOTE: using for update of | ||||||
| 	return get_stock_ledger_entries(args, | 	conditions = ["timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s)"] | ||||||
| 		["timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s)"], | 
 | ||||||
| 		"asc", for_update=for_update) | 	# Excluding name: Workaround for MariaDB timestamp() floating microsecond issue | ||||||
|  | 	if args.get("name"): | ||||||
|  | 		conditions.append("name!=%(name)s") | ||||||
|  | 
 | ||||||
|  | 	return get_stock_ledger_entries(args, conditions, "asc", for_update=for_update) | ||||||
| 
 | 
 | ||||||
| def get_stock_ledger_entries(args, conditions=None, order="desc", limit=None, for_update=False): | def get_stock_ledger_entries(args, conditions=None, order="desc", limit=None, for_update=False): | ||||||
| 	"""get stock ledger entries filtered by specific posting datetime conditions""" | 	"""get stock ledger entries filtered by specific posting datetime conditions""" | ||||||
| @ -180,7 +184,7 @@ def get_stock_ledger_entries(args, conditions=None, order="desc", limit=None, fo | |||||||
| 	if not args.get("posting_time"): | 	if not args.get("posting_time"): | ||||||
| 		args["posting_time"] = "00:00" | 		args["posting_time"] = "00:00" | ||||||
| 
 | 
 | ||||||
| 	return frappe.db.sql("""select * from `tabStock Ledger Entry` | 	return frappe.db.sql("""select *, timestamp(posting_date, posting_time) as "timestamp" from `tabStock Ledger Entry` | ||||||
| 		where item_code = %%(item_code)s | 		where item_code = %%(item_code)s | ||||||
| 		and warehouse = %%(warehouse)s | 		and warehouse = %%(warehouse)s | ||||||
| 		and ifnull(is_cancelled, 'No')='No' | 		and ifnull(is_cancelled, 'No')='No' | ||||||
|  | |||||||
| @ -9,34 +9,34 @@ from frappe.defaults import get_global_default | |||||||
| from frappe.utils.email_lib import sendmail | from frappe.utils.email_lib import sendmail | ||||||
| 
 | 
 | ||||||
| class InvalidWarehouseCompany(frappe.ValidationError): pass | class InvalidWarehouseCompany(frappe.ValidationError): pass | ||||||
| 	 | 
 | ||||||
| def get_stock_balance_on(warehouse, posting_date=None): | def get_stock_balance_on(warehouse, posting_date=None): | ||||||
| 	if not posting_date: posting_date = nowdate() | 	if not posting_date: posting_date = nowdate() | ||||||
| 	 | 
 | ||||||
| 	stock_ledger_entries = frappe.db.sql(""" | 	stock_ledger_entries = frappe.db.sql(""" | ||||||
| 		SELECT  | 		SELECT | ||||||
| 			item_code, stock_value | 			item_code, stock_value | ||||||
| 		FROM  | 		FROM | ||||||
| 			`tabStock Ledger Entry` | 			`tabStock Ledger Entry` | ||||||
| 		WHERE  | 		WHERE | ||||||
| 			warehouse=%s AND posting_date <= %s | 			warehouse=%s AND posting_date <= %s | ||||||
| 		ORDER BY timestamp(posting_date, posting_time) DESC, name DESC | 		ORDER BY timestamp(posting_date, posting_time) DESC, name DESC | ||||||
| 	""", (warehouse, posting_date), as_dict=1) | 	""", (warehouse, posting_date), as_dict=1) | ||||||
| 	  | 
 | ||||||
| 	sle_map = {} | 	sle_map = {} | ||||||
| 	for sle in stock_ledger_entries: | 	for sle in stock_ledger_entries: | ||||||
| 		sle_map.setdefault(sle.item_code, flt(sle.stock_value)) | 		sle_map.setdefault(sle.item_code, flt(sle.stock_value)) | ||||||
| 		 | 
 | ||||||
| 	return sum(sle_map.values()) | 	return sum(sle_map.values()) | ||||||
| 	 | 
 | ||||||
| def get_latest_stock_balance(): | def get_latest_stock_balance(): | ||||||
| 	bin_map = {} | 	bin_map = {} | ||||||
| 	for d in frappe.db.sql("""SELECT item_code, warehouse, stock_value as stock_value  | 	for d in frappe.db.sql("""SELECT item_code, warehouse, stock_value as stock_value | ||||||
| 		FROM tabBin""", as_dict=1): | 		FROM tabBin""", as_dict=1): | ||||||
| 			bin_map.setdefault(d.warehouse, {}).setdefault(d.item_code, flt(d.stock_value)) | 			bin_map.setdefault(d.warehouse, {}).setdefault(d.item_code, flt(d.stock_value)) | ||||||
| 			 | 
 | ||||||
| 	return bin_map | 	return bin_map | ||||||
| 	 | 
 | ||||||
| def get_bin(item_code, warehouse): | def get_bin(item_code, warehouse): | ||||||
| 	bin = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}) | 	bin = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}) | ||||||
| 	if not bin: | 	if not bin: | ||||||
| @ -58,18 +58,18 @@ def update_bin(args): | |||||||
| 		bin.update_stock(args) | 		bin.update_stock(args) | ||||||
| 		return bin | 		return bin | ||||||
| 	else: | 	else: | ||||||
| 		msgprint("[Stock Update] Ignored %s since it is not a stock item"  | 		msgprint("[Stock Update] Ignored %s since it is not a stock item" | ||||||
| 			% args.get("item_code")) | 			% args.get("item_code")) | ||||||
| 
 | 
 | ||||||
| def get_incoming_rate(args): | def get_incoming_rate(args): | ||||||
| 	"""Get Incoming Rate based on valuation method""" | 	"""Get Incoming Rate based on valuation method""" | ||||||
| 	from erpnext.stock.stock_ledger import get_previous_sle | 	from erpnext.stock.stock_ledger import get_previous_sle | ||||||
| 		 | 
 | ||||||
| 	in_rate = 0 | 	in_rate = 0 | ||||||
| 	if args.get("serial_no"): | 	if args.get("serial_no"): | ||||||
| 		in_rate = get_avg_purchase_rate(args.get("serial_no")) | 		in_rate = get_avg_purchase_rate(args.get("serial_no")) | ||||||
| 	elif args.get("bom_no"): | 	elif args.get("bom_no"): | ||||||
| 		result = frappe.db.sql("""select ifnull(total_cost, 0) / ifnull(quantity, 1)  | 		result = frappe.db.sql("""select ifnull(total_cost, 0) / ifnull(quantity, 1) | ||||||
| 			from `tabBOM` where name = %s and docstatus=1 and is_active=1""", args.get("bom_no")) | 			from `tabBOM` where name = %s and docstatus=1 and is_active=1""", args.get("bom_no")) | ||||||
| 		in_rate = result and flt(result[0][0]) or 0 | 		in_rate = result and flt(result[0][0]) or 0 | ||||||
| 	else: | 	else: | ||||||
| @ -84,12 +84,12 @@ def get_incoming_rate(args): | |||||||
| 		elif valuation_method == 'Moving Average': | 		elif valuation_method == 'Moving Average': | ||||||
| 			in_rate = previous_sle.get('valuation_rate') or 0 | 			in_rate = previous_sle.get('valuation_rate') or 0 | ||||||
| 	return in_rate | 	return in_rate | ||||||
| 	 | 
 | ||||||
| def get_avg_purchase_rate(serial_nos): | def get_avg_purchase_rate(serial_nos): | ||||||
| 	"""get average value of serial numbers""" | 	"""get average value of serial numbers""" | ||||||
| 	 | 
 | ||||||
| 	serial_nos = get_valid_serial_nos(serial_nos) | 	serial_nos = get_valid_serial_nos(serial_nos) | ||||||
| 	return flt(frappe.db.sql("""select avg(ifnull(purchase_rate, 0)) from `tabSerial No`  | 	return flt(frappe.db.sql("""select avg(ifnull(purchase_rate, 0)) from `tabSerial No` | ||||||
| 		where name in (%s)""" % ", ".join(["%s"] * len(serial_nos)), | 		where name in (%s)""" % ", ".join(["%s"] * len(serial_nos)), | ||||||
| 		tuple(serial_nos))[0][0]) | 		tuple(serial_nos))[0][0]) | ||||||
| 
 | 
 | ||||||
| @ -99,11 +99,11 @@ def get_valuation_method(item_code): | |||||||
| 	if not val_method: | 	if not val_method: | ||||||
| 		val_method = get_global_default('valuation_method') or "FIFO" | 		val_method = get_global_default('valuation_method') or "FIFO" | ||||||
| 	return val_method | 	return val_method | ||||||
| 		 | 
 | ||||||
| def get_fifo_rate(previous_stock_queue, qty): | def get_fifo_rate(previous_stock_queue, qty): | ||||||
| 	"""get FIFO (average) Rate from Queue""" | 	"""get FIFO (average) Rate from Queue""" | ||||||
| 	if qty >= 0: | 	if qty >= 0: | ||||||
| 		total = sum(f[0] for f in previous_stock_queue)	 | 		total = sum(f[0] for f in previous_stock_queue) | ||||||
| 		return total and sum(f[0] * f[1] for f in previous_stock_queue) / flt(total) or 0.0 | 		return total and sum(f[0] * f[1] for f in previous_stock_queue) / flt(total) or 0.0 | ||||||
| 	else: | 	else: | ||||||
| 		outgoing_cost = 0 | 		outgoing_cost = 0 | ||||||
| @ -123,12 +123,12 @@ def get_fifo_rate(previous_stock_queue, qty): | |||||||
| 				qty_to_pop = 0 | 				qty_to_pop = 0 | ||||||
| 		# if queue gets blank and qty_to_pop remaining, get average rate of full queue | 		# if queue gets blank and qty_to_pop remaining, get average rate of full queue | ||||||
| 		return outgoing_cost / abs(qty) - qty_to_pop | 		return outgoing_cost / abs(qty) - qty_to_pop | ||||||
| 	 | 
 | ||||||
| def get_valid_serial_nos(sr_nos, qty=0, item_code=''): | def get_valid_serial_nos(sr_nos, qty=0, item_code=''): | ||||||
| 	"""split serial nos, validate and return list of valid serial nos""" | 	"""split serial nos, validate and return list of valid serial nos""" | ||||||
| 	# TODO: remove duplicates in client side | 	# TODO: remove duplicates in client side | ||||||
| 	serial_nos = cstr(sr_nos).strip().replace(',', '\n').split('\n') | 	serial_nos = cstr(sr_nos).strip().replace(',', '\n').split('\n') | ||||||
| 	 | 
 | ||||||
| 	valid_serial_nos = [] | 	valid_serial_nos = [] | ||||||
| 	for val in serial_nos: | 	for val in serial_nos: | ||||||
| 		if val: | 		if val: | ||||||
| @ -137,12 +137,12 @@ def get_valid_serial_nos(sr_nos, qty=0, item_code=''): | |||||||
| 				msgprint("You have entered duplicate serial no: '%s'" % val, raise_exception=1) | 				msgprint("You have entered duplicate serial no: '%s'" % val, raise_exception=1) | ||||||
| 			else: | 			else: | ||||||
| 				valid_serial_nos.append(val) | 				valid_serial_nos.append(val) | ||||||
| 	 | 
 | ||||||
| 	if qty and len(valid_serial_nos) != abs(qty): | 	if qty and len(valid_serial_nos) != abs(qty): | ||||||
| 		msgprint("Please enter serial nos for " | 		msgprint("Please enter serial nos for " | ||||||
| 			+ cstr(abs(qty)) + " quantity against item code: " + item_code, | 			+ cstr(abs(qty)) + " quantity against item code: " + item_code, | ||||||
| 			raise_exception=1) | 			raise_exception=1) | ||||||
| 		 | 
 | ||||||
| 	return valid_serial_nos | 	return valid_serial_nos | ||||||
| 
 | 
 | ||||||
| def validate_warehouse_company(warehouse, company): | def validate_warehouse_company(warehouse, company): | ||||||
| @ -151,48 +151,48 @@ def validate_warehouse_company(warehouse, company): | |||||||
| 		frappe.msgprint(_("Warehouse does not belong to company.") + " (" + \ | 		frappe.msgprint(_("Warehouse does not belong to company.") + " (" + \ | ||||||
| 			warehouse + ", " + company +")", raise_exception=InvalidWarehouseCompany) | 			warehouse + ", " + company +")", raise_exception=InvalidWarehouseCompany) | ||||||
| 
 | 
 | ||||||
| def get_sales_bom_buying_amount(item_code, warehouse, voucher_type, voucher_no, voucher_detail_no,  | def get_sales_bom_buying_amount(item_code, warehouse, voucher_type, voucher_no, voucher_detail_no, | ||||||
| 		stock_ledger_entries, item_sales_bom): | 		stock_ledger_entries, item_sales_bom): | ||||||
| 	# sales bom item | 	# sales bom item | ||||||
| 	buying_amount = 0.0 | 	buying_amount = 0.0 | ||||||
| 	for bom_item in item_sales_bom[item_code]: | 	for bom_item in item_sales_bom[item_code]: | ||||||
| 		if bom_item.get("parent_detail_docname")==voucher_detail_no: | 		if bom_item.get("parent_detail_docname")==voucher_detail_no: | ||||||
| 			buying_amount += get_buying_amount(voucher_type, voucher_no, voucher_detail_no,  | 			buying_amount += get_buying_amount(voucher_type, voucher_no, voucher_detail_no, | ||||||
| 				stock_ledger_entries.get((bom_item.item_code, warehouse), [])) | 				stock_ledger_entries.get((bom_item.item_code, warehouse), [])) | ||||||
| 
 | 
 | ||||||
| 	return buying_amount | 	return buying_amount | ||||||
| 		 | 
 | ||||||
| def get_buying_amount(voucher_type, voucher_no, item_row, stock_ledger_entries): | def get_buying_amount(voucher_type, voucher_no, item_row, stock_ledger_entries): | ||||||
| 	# IMP NOTE | 	# IMP NOTE | ||||||
| 	# stock_ledger_entries should already be filtered by item_code and warehouse and  | 	# stock_ledger_entries should already be filtered by item_code and warehouse and | ||||||
| 	# sorted by posting_date desc, posting_time desc | 	# sorted by posting_date desc, posting_time desc | ||||||
| 	for i, sle in enumerate(stock_ledger_entries): | 	for i, sle in enumerate(stock_ledger_entries): | ||||||
| 		if sle.voucher_type == voucher_type and sle.voucher_no == voucher_no and \ | 		if sle.voucher_type == voucher_type and sle.voucher_no == voucher_no and \ | ||||||
| 			sle.voucher_detail_no == item_row: | 			sle.voucher_detail_no == item_row: | ||||||
| 				previous_stock_value = len(stock_ledger_entries) > i+1 and \ | 				previous_stock_value = len(stock_ledger_entries) > i+1 and \ | ||||||
| 					flt(stock_ledger_entries[i+1].stock_value) or 0.0 | 					flt(stock_ledger_entries[i+1].stock_value) or 0.0 | ||||||
| 				buying_amount =  previous_stock_value - flt(sle.stock_value)						 | 				buying_amount =  previous_stock_value - flt(sle.stock_value) | ||||||
| 				 | 
 | ||||||
| 				return buying_amount | 				return buying_amount | ||||||
| 	return 0.0 | 	return 0.0 | ||||||
| 	 | 
 | ||||||
| 
 | 
 | ||||||
| def reorder_item(): | def reorder_item(): | ||||||
| 	""" Reorder item if stock reaches reorder level""" | 	""" Reorder item if stock reaches reorder level""" | ||||||
| 	if getattr(frappe.local, "auto_indent", None) is None: | 	if getattr(frappe.local, "auto_indent", None) is None: | ||||||
| 		frappe.local.auto_indent = cint(frappe.db.get_value('Stock Settings', None, 'auto_indent')) | 		frappe.local.auto_indent = cint(frappe.db.get_value('Stock Settings', None, 'auto_indent')) | ||||||
| 	 | 
 | ||||||
| 	if frappe.local.auto_indent: | 	if frappe.local.auto_indent: | ||||||
| 		material_requests = {} | 		material_requests = {} | ||||||
| 		bin_list = frappe.db.sql("""select item_code, warehouse, projected_qty | 		bin_list = frappe.db.sql("""select item_code, warehouse, projected_qty | ||||||
| 			from tabBin where ifnull(item_code, '') != '' and ifnull(warehouse, '') != '' | 			from tabBin where ifnull(item_code, '') != '' and ifnull(warehouse, '') != '' | ||||||
| 			and exists (select name from `tabItem`  | 			and exists (select name from `tabItem` | ||||||
| 				where `tabItem`.name = `tabBin`.item_code and  | 				where `tabItem`.name = `tabBin`.item_code and | ||||||
| 				is_stock_item='Yes' and (is_purchase_item='Yes' or is_sub_contracted_item='Yes') and | 				is_stock_item='Yes' and (is_purchase_item='Yes' or is_sub_contracted_item='Yes') and | ||||||
| 				(ifnull(end_of_life, '')='' or end_of_life > now()))""", as_dict=True) | 				(ifnull(end_of_life, '')='' or end_of_life > curdate()))""", as_dict=True) | ||||||
| 		for bin in bin_list: | 		for bin in bin_list: | ||||||
| 			#check if re-order is required | 			#check if re-order is required | ||||||
| 			item_reorder = frappe.db.get("Item Reorder",  | 			item_reorder = frappe.db.get("Item Reorder", | ||||||
| 				{"parent": bin.item_code, "warehouse": bin.warehouse}) | 				{"parent": bin.item_code, "warehouse": bin.warehouse}) | ||||||
| 			if item_reorder: | 			if item_reorder: | ||||||
| 				reorder_level = item_reorder.warehouse_reorder_level | 				reorder_level = item_reorder.warehouse_reorder_level | ||||||
| @ -202,15 +202,15 @@ def reorder_item(): | |||||||
| 				reorder_level, reorder_qty = frappe.db.get_value("Item", bin.item_code, | 				reorder_level, reorder_qty = frappe.db.get_value("Item", bin.item_code, | ||||||
| 					["re_order_level", "re_order_qty"]) | 					["re_order_level", "re_order_qty"]) | ||||||
| 				material_request_type = "Purchase" | 				material_request_type = "Purchase" | ||||||
| 		 | 
 | ||||||
| 			if flt(reorder_level) and flt(bin.projected_qty) < flt(reorder_level): | 			if flt(reorder_level) and flt(bin.projected_qty) < flt(reorder_level): | ||||||
| 				if flt(reorder_level) - flt(bin.projected_qty) > flt(reorder_qty): | 				if flt(reorder_level) - flt(bin.projected_qty) > flt(reorder_qty): | ||||||
| 					reorder_qty = flt(reorder_level) - flt(bin.projected_qty) | 					reorder_qty = flt(reorder_level) - flt(bin.projected_qty) | ||||||
| 					 | 
 | ||||||
| 				company = frappe.db.get_value("Warehouse", bin.warehouse, "company") or \ | 				company = frappe.db.get_value("Warehouse", bin.warehouse, "company") or \ | ||||||
| 					frappe.defaults.get_defaults()["company"] or \ | 					frappe.defaults.get_defaults()["company"] or \ | ||||||
| 					frappe.db.sql("""select name from tabCompany limit 1""")[0][0] | 					frappe.db.sql("""select name from tabCompany limit 1""")[0][0] | ||||||
| 					 | 
 | ||||||
| 				material_requests.setdefault(material_request_type, frappe._dict()).setdefault( | 				material_requests.setdefault(material_request_type, frappe._dict()).setdefault( | ||||||
| 					company, []).append(frappe._dict({ | 					company, []).append(frappe._dict({ | ||||||
| 						"item_code": bin.item_code, | 						"item_code": bin.item_code, | ||||||
| @ -218,7 +218,7 @@ def reorder_item(): | |||||||
| 						"reorder_qty": reorder_qty | 						"reorder_qty": reorder_qty | ||||||
| 					}) | 					}) | ||||||
| 				) | 				) | ||||||
| 		 | 
 | ||||||
| 		create_material_request(material_requests) | 		create_material_request(material_requests) | ||||||
| 
 | 
 | ||||||
| def create_material_request(material_requests): | def create_material_request(material_requests): | ||||||
| @ -234,21 +234,19 @@ def create_material_request(material_requests): | |||||||
| 				items = material_requests[request_type][company] | 				items = material_requests[request_type][company] | ||||||
| 				if not items: | 				if not items: | ||||||
| 					continue | 					continue | ||||||
| 					 | 
 | ||||||
| 				mr = [{ | 				mr = frappe.new_doc("Material Request") | ||||||
| 					"doctype": "Material Request", | 				mr.update({ | ||||||
| 					"company": company, | 					"company": company, | ||||||
| 					"fiscal_year": current_fiscal_year, | 					"fiscal_year": current_fiscal_year, | ||||||
| 					"transaction_date": nowdate(), | 					"transaction_date": nowdate(), | ||||||
| 					"material_request_type": request_type | 					"material_request_type": request_type | ||||||
| 				}] | 				}) | ||||||
| 			 | 
 | ||||||
| 				for d in items: | 				for d in items: | ||||||
| 					item = frappe.get_doc("Item", d.item_code) | 					item = frappe.get_doc("Item", d.item_code) | ||||||
| 					mr.append({ | 					mr.append("indent_details", { | ||||||
| 						"doctype": "Material Request Item", | 						"doctype": "Material Request Item", | ||||||
| 						"parenttype": "Material Request", |  | ||||||
| 						"parentfield": "indent_details", |  | ||||||
| 						"item_code": d.item_code, | 						"item_code": d.item_code, | ||||||
| 						"schedule_date": add_days(nowdate(),cint(item.lead_time_days)), | 						"schedule_date": add_days(nowdate(),cint(item.lead_time_days)), | ||||||
| 						"uom":	item.stock_uom, | 						"uom":	item.stock_uom, | ||||||
| @ -259,11 +257,10 @@ def create_material_request(material_requests): | |||||||
| 						"qty": d.reorder_qty, | 						"qty": d.reorder_qty, | ||||||
| 						"brand": item.brand, | 						"brand": item.brand, | ||||||
| 					}) | 					}) | ||||||
| 			 | 
 | ||||||
| 				mr_doc = frappe.get_doc(mr) | 				mr.insert() | ||||||
| 				mr_doc.insert() | 				mr.submit() | ||||||
| 				mr_doc.submit() | 				mr_list.append(mr) | ||||||
| 				mr_list.append(mr_doc) |  | ||||||
| 
 | 
 | ||||||
| 			except: | 			except: | ||||||
| 				if frappe.local.message_log: | 				if frappe.local.message_log: | ||||||
| @ -274,24 +271,24 @@ def create_material_request(material_requests): | |||||||
| 
 | 
 | ||||||
| 	if mr_list: | 	if mr_list: | ||||||
| 		if getattr(frappe.local, "reorder_email_notify", None) is None: | 		if getattr(frappe.local, "reorder_email_notify", None) is None: | ||||||
| 			frappe.local.reorder_email_notify = cint(frappe.db.get_value('Stock Settings', None,  | 			frappe.local.reorder_email_notify = cint(frappe.db.get_value('Stock Settings', None, | ||||||
| 				'reorder_email_notify')) | 				'reorder_email_notify')) | ||||||
| 			 | 
 | ||||||
| 		if(frappe.local.reorder_email_notify): | 		if(frappe.local.reorder_email_notify): | ||||||
| 			send_email_notification(mr_list) | 			send_email_notification(mr_list) | ||||||
| 
 | 
 | ||||||
| 	if exceptions_list: | 	if exceptions_list: | ||||||
| 		notify_errors(exceptions_list) | 		notify_errors(exceptions_list) | ||||||
| 		 | 
 | ||||||
| def send_email_notification(mr_list): | def send_email_notification(mr_list): | ||||||
| 	""" Notify user about auto creation of indent""" | 	""" Notify user about auto creation of indent""" | ||||||
| 	 | 
 | ||||||
| 	email_list = frappe.db.sql_list("""select distinct r.parent  | 	email_list = frappe.db.sql_list("""select distinct r.parent | ||||||
| 		from tabUserRole r, tabUser p | 		from tabUserRole r, tabUser p | ||||||
| 		where p.name = r.parent and p.enabled = 1 and p.docstatus < 2 | 		where p.name = r.parent and p.enabled = 1 and p.docstatus < 2 | ||||||
| 		and r.role in ('Purchase Manager','Material Manager')  | 		and r.role in ('Purchase Manager','Material Manager') | ||||||
| 		and p.name not in ('Administrator', 'All', 'Guest')""") | 		and p.name not in ('Administrator', 'All', 'Guest')""") | ||||||
| 	 | 
 | ||||||
| 	msg="""<h3>Following Material Requests has been raised automatically \ | 	msg="""<h3>Following Material Requests has been raised automatically \ | ||||||
| 		based on item reorder level:</h3>""" | 		based on item reorder level:</h3>""" | ||||||
| 	for mr in mr_list: | 	for mr in mr_list: | ||||||
| @ -302,13 +299,13 @@ def send_email_notification(mr_list): | |||||||
| 				cstr(item.qty) + "</td><td>" + cstr(item.uom) + "</td></tr>" | 				cstr(item.qty) + "</td><td>" + cstr(item.uom) + "</td></tr>" | ||||||
| 		msg += "</table>" | 		msg += "</table>" | ||||||
| 	sendmail(email_list, subject='Auto Material Request Generation Notification', msg = msg) | 	sendmail(email_list, subject='Auto Material Request Generation Notification', msg = msg) | ||||||
| 	 | 
 | ||||||
| def notify_errors(exceptions_list): | def notify_errors(exceptions_list): | ||||||
| 	subject = "[Important] [ERPNext] Error(s) while creating Material Requests based on Re-order Levels" | 	subject = "[Important] [ERPNext] Error(s) while creating Material Requests based on Re-order Levels" | ||||||
| 	msg = """Dear System Manager, | 	msg = """Dear System Manager, | ||||||
| 
 | 
 | ||||||
| 		An error occured for certain Items while creating Material Requests based on Re-order level. | 		An error occured for certain Items while creating Material Requests based on Re-order level. | ||||||
| 		 | 
 | ||||||
| 		Please rectify these issues: | 		Please rectify these issues: | ||||||
| 		--- | 		--- | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user