Credit Limit and credit days fixes #2031
This commit is contained in:
		
							parent
							
								
									be8ec39678
								
							
						
					
					
						commit
						e9daefe07f
					
				| @ -177,7 +177,7 @@ | |||||||
|  "icon": "icon-money",  |  "icon": "icon-money",  | ||||||
|  "idx": 1,  |  "idx": 1,  | ||||||
|  "in_create": 1,  |  "in_create": 1,  | ||||||
|  "modified": "2014-08-26 18:18:30.173409",  |  "modified": "2014-08-27 15:12:35.506765",  | ||||||
|  "modified_by": "Administrator",  |  "modified_by": "Administrator",  | ||||||
|  "module": "Accounts",  |  "module": "Accounts",  | ||||||
|  "name": "Account",  |  "name": "Account",  | ||||||
| @ -241,18 +241,6 @@ | |||||||
|    "submit": 0,  |    "submit": 0,  | ||||||
|    "write": 0 |    "write": 0 | ||||||
|   },  |   },  | ||||||
|   { |  | ||||||
|    "amend": 0,  |  | ||||||
|    "cancel": 0,  |  | ||||||
|    "create": 0,  |  | ||||||
|    "delete": 0,  |  | ||||||
|    "permlevel": 2,  |  | ||||||
|    "read": 1,  |  | ||||||
|    "report": 1,  |  | ||||||
|    "role": "Auditor",  |  | ||||||
|    "submit": 0,  |  | ||||||
|    "write": 0 |  | ||||||
|   },  |  | ||||||
|   { |   { | ||||||
|    "amend": 0,  |    "amend": 0,  | ||||||
|    "apply_user_permissions": 0,  |    "apply_user_permissions": 0,  | ||||||
| @ -269,30 +257,6 @@ | |||||||
|    "set_user_permissions": 1,  |    "set_user_permissions": 1,  | ||||||
|    "submit": 0,  |    "submit": 0,  | ||||||
|    "write": 1 |    "write": 1 | ||||||
|   },  |  | ||||||
|   { |  | ||||||
|    "amend": 0,  |  | ||||||
|    "cancel": 0,  |  | ||||||
|    "create": 0,  |  | ||||||
|    "delete": 0,  |  | ||||||
|    "permlevel": 2,  |  | ||||||
|    "read": 1,  |  | ||||||
|    "report": 1,  |  | ||||||
|    "role": "Accounts Manager",  |  | ||||||
|    "submit": 0,  |  | ||||||
|    "write": 1 |  | ||||||
|   },  |  | ||||||
|   { |  | ||||||
|    "amend": 0,  |  | ||||||
|    "cancel": 0,  |  | ||||||
|    "create": 0,  |  | ||||||
|    "delete": 0,  |  | ||||||
|    "permlevel": 2,  |  | ||||||
|    "read": 1,  |  | ||||||
|    "report": 1,  |  | ||||||
|    "role": "Accounts User",  |  | ||||||
|    "submit": 0,  |  | ||||||
|    "write": 0 |  | ||||||
|   } |   } | ||||||
|  ],  |  ],  | ||||||
|  "search_fields": "group_or_ledger" |  "search_fields": "group_or_ledger" | ||||||
|  | |||||||
| @ -85,8 +85,8 @@ class Account(Document): | |||||||
| 	def convert_ledger_to_group(self): | 	def convert_ledger_to_group(self): | ||||||
| 		if self.check_gle_exists(): | 		if self.check_gle_exists(): | ||||||
| 			throw(_("Account with existing transaction can not be converted to group.")) | 			throw(_("Account with existing transaction can not be converted to group.")) | ||||||
| 		elif self.master_type or self.account_type: | 		elif self.account_type: | ||||||
| 			throw(_("Cannot covert to Group because Master Type or Account Type is selected.")) | 			throw(_("Cannot covert to Group because Account Type is selected.")) | ||||||
| 		else: | 		else: | ||||||
| 			self.group_or_ledger = 'Group' | 			self.group_or_ledger = 'Group' | ||||||
| 			self.save() | 			self.save() | ||||||
| @ -135,47 +135,6 @@ class Account(Document): | |||||||
| 	def on_update(self): | 	def on_update(self): | ||||||
| 		self.update_nsm_model() | 		self.update_nsm_model() | ||||||
| 
 | 
 | ||||||
| 	def get_authorized_user(self): |  | ||||||
| 		# Check logged-in user is authorized |  | ||||||
| 		if frappe.db.get_value('Accounts Settings', None, 'credit_controller') \ |  | ||||||
| 				in frappe.user.get_roles(): |  | ||||||
| 			return 1 |  | ||||||
| 
 |  | ||||||
| 	def check_credit_limit(self, total_outstanding): |  | ||||||
| 		# Get credit limit |  | ||||||
| 		credit_limit_from = 'Customer' |  | ||||||
| 
 |  | ||||||
| 		cr_limit = frappe.db.sql("""select t1.credit_limit from tabCustomer t1, `tabAccount` t2 |  | ||||||
| 			where t2.name=%s and t1.name = t2.master_name""", self.name) |  | ||||||
| 		credit_limit = cr_limit and flt(cr_limit[0][0]) or 0 |  | ||||||
| 		if not credit_limit: |  | ||||||
| 			credit_limit = frappe.db.get_value('Company', self.company, 'credit_limit') |  | ||||||
| 			credit_limit_from = 'Company' |  | ||||||
| 
 |  | ||||||
| 		# If outstanding greater than credit limit and not authorized person raise exception |  | ||||||
| 		if credit_limit > 0 and flt(total_outstanding) > credit_limit \ |  | ||||||
| 				and not self.get_authorized_user(): |  | ||||||
| 			throw(_("{0} Credit limit {1} crossed").format(_(credit_limit_from), credit_limit)) |  | ||||||
| 
 |  | ||||||
| 	def validate_due_date(self, posting_date, due_date): |  | ||||||
| 		credit_days = (self.credit_days or frappe.db.get_value("Company", self.company, "credit_days")) |  | ||||||
| 		posting_date, due_date = getdate(posting_date), getdate(due_date) |  | ||||||
| 		diff = (due_date - posting_date).days |  | ||||||
| 
 |  | ||||||
| 		if diff < 0: |  | ||||||
| 			frappe.throw(_("Due Date cannot be before Posting Date")) |  | ||||||
| 
 |  | ||||||
| 		elif credit_days is not None and diff > credit_days: |  | ||||||
| 			is_credit_controller = frappe.db.get_value("Accounts Settings", None, |  | ||||||
| 				"credit_controller") in frappe.user.get_roles() |  | ||||||
| 
 |  | ||||||
| 			if is_credit_controller: |  | ||||||
| 				msgprint(_("Note: Due Date exceeds the allowed credit days by {0} day(s)").format( |  | ||||||
| 					diff - credit_days)) |  | ||||||
| 			else: |  | ||||||
| 				max_due_date = formatdate(add_days(posting_date, credit_days)) |  | ||||||
| 				frappe.throw(_("Due Date cannot be after {0}").format(max_due_date)) |  | ||||||
| 
 |  | ||||||
| 	def validate_trash(self): | 	def validate_trash(self): | ||||||
| 		"""checks gl entries and if child exists""" | 		"""checks gl entries and if child exists""" | ||||||
| 		if not self.parent_account: | 		if not self.parent_account: | ||||||
|  | |||||||
| @ -3,11 +3,9 @@ | |||||||
| 
 | 
 | ||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| import frappe | import frappe | ||||||
| 
 | from frappe.utils import cstr, flt, fmt_money, formatdate, getdate | ||||||
| from frappe.utils import cint, cstr, flt, fmt_money, formatdate, getdate |  | ||||||
| from frappe import msgprint, _, scrub | from frappe import msgprint, _, scrub | ||||||
| from erpnext.setup.utils import get_company_currency | from erpnext.setup.utils import get_company_currency | ||||||
| 
 |  | ||||||
| from erpnext.controllers.accounts_controller import AccountsController | from erpnext.controllers.accounts_controller import AccountsController | ||||||
| 
 | 
 | ||||||
| class JournalVoucher(AccountsController): | class JournalVoucher(AccountsController): | ||||||
| @ -40,12 +38,11 @@ class JournalVoucher(AccountsController): | |||||||
| 		self.set_print_format_fields() | 		self.set_print_format_fields() | ||||||
| 		self.validate_against_sales_order() | 		self.validate_against_sales_order() | ||||||
| 		self.validate_against_purchase_order() | 		self.validate_against_purchase_order() | ||||||
|  | 		self.check_credit_limit() | ||||||
|  | 		self.check_credit_days() | ||||||
| 
 | 
 | ||||||
| 	def on_submit(self): | 	def on_submit(self): | ||||||
| 		if self.voucher_type in ['Bank Voucher', 'Contra Voucher', 'Journal Entry']: |  | ||||||
| 			self.check_credit_days() |  | ||||||
| 		self.make_gl_entries() | 		self.make_gl_entries() | ||||||
| 		self.check_credit_limit() |  | ||||||
| 		self.update_advance_paid() | 		self.update_advance_paid() | ||||||
| 
 | 
 | ||||||
| 	def update_advance_paid(self): | 	def update_advance_paid(self): | ||||||
| @ -68,6 +65,30 @@ class JournalVoucher(AccountsController): | |||||||
| 		self.make_gl_entries(1) | 		self.make_gl_entries(1) | ||||||
| 		self.update_advance_paid() | 		self.update_advance_paid() | ||||||
| 
 | 
 | ||||||
|  | 	def check_credit_limit(self): | ||||||
|  | 		customers = list(set([d.party for d in self.get("entries") if d.party_type=="Customer"])) | ||||||
|  | 		if customers: | ||||||
|  | 			from erpnext.selling.doctype.customer.customer import check_credit_limit | ||||||
|  | 			for customer in customers: | ||||||
|  | 				check_credit_limit(customer, self.company) | ||||||
|  | 
 | ||||||
|  | 	def check_credit_days(self): | ||||||
|  | 		posting_date = self.posting_date | ||||||
|  | 		company_credit_days = frappe.db.get_value("Company", self.company, "credit_days") | ||||||
|  | 		if self.cheque_date: | ||||||
|  | 			for d in self.get("entries"): | ||||||
|  | 				if d.party_type and d.party and d.get("credit" if d.party_type=="Customer" else "debit") > 0: | ||||||
|  | 					if d.against_invoice: | ||||||
|  | 						posting_date = frappe.db.get_value("Sales Invoice", d.against_invoice, "posting_date") | ||||||
|  | 					elif d.against_voucher: | ||||||
|  | 						posting_date = frappe.db.get_value("Purchase Invoice", d.against_voucher, "posting_date") | ||||||
|  | 
 | ||||||
|  | 					credit_days = frappe.db.get_value(d.party_type, d.party, "credit_days") or company_credit_days | ||||||
|  | 					if credit_days: | ||||||
|  | 						if (getdate(self.cheque_date) - getdate(posting_date)).days > flt(credit_days): | ||||||
|  | 							msgprint(_("Note: Reference Date is after allowed credit days {0} for {1} {2}") | ||||||
|  | 								.format(credit_days, d.party_type, d.party)) | ||||||
|  | 
 | ||||||
| 	def validate_cheque_info(self): | 	def validate_cheque_info(self): | ||||||
| 		if self.voucher_type in ['Bank Voucher']: | 		if self.voucher_type in ['Bank Voucher']: | ||||||
| 			if not self.cheque_no or not self.cheque_date: | 			if not self.cheque_no or not self.cheque_date: | ||||||
| @ -300,51 +321,6 @@ class JournalVoucher(AccountsController): | |||||||
| 				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) | ||||||
| 
 | 
 | ||||||
| 	def check_credit_days(self): |  | ||||||
| 		date_diff = 0 |  | ||||||
| 		if self.cheque_date: |  | ||||||
| 			date_diff = (getdate(self.cheque_date)-getdate(self.posting_date)).days |  | ||||||
| 
 |  | ||||||
| 		if date_diff <= 0: return |  | ||||||
| 
 |  | ||||||
| 		# Get List of Customer Account |  | ||||||
| 		acc_list = filter(lambda d: frappe.db.get_value("Account", d.account, |  | ||||||
| 		 	"master_type")=='Customer', self.get('entries')) |  | ||||||
| 
 |  | ||||||
| 		for d in acc_list: |  | ||||||
| 			credit_days = self.get_credit_days_for(d.account) |  | ||||||
| 			# Check credit days |  | ||||||
| 			if credit_days > 0 and not self.get_authorized_user() and cint(date_diff) > credit_days: |  | ||||||
| 				msgprint(_("Maximum allowed credit is {0} days after posting date").format(credit_days), |  | ||||||
| 					raise_exception=1) |  | ||||||
| 
 |  | ||||||
| 	def get_credit_days_for(self, ac): |  | ||||||
| 		if not self.credit_days_for.has_key(ac): |  | ||||||
| 			self.credit_days_for[ac] = cint(frappe.db.get_value("Account", ac, "credit_days")) |  | ||||||
| 
 |  | ||||||
| 		if not self.credit_days_for[ac]: |  | ||||||
| 			if self.credit_days_global==-1: |  | ||||||
| 				self.credit_days_global = cint(frappe.db.get_value("Company", |  | ||||||
| 					self.company, "credit_days")) |  | ||||||
| 
 |  | ||||||
| 			return self.credit_days_global |  | ||||||
| 		else: |  | ||||||
| 			return self.credit_days_for[ac] |  | ||||||
| 
 |  | ||||||
| 	def get_authorized_user(self): |  | ||||||
| 		if self.is_approving_authority==-1: |  | ||||||
| 			self.is_approving_authority = 0 |  | ||||||
| 
 |  | ||||||
| 			# Fetch credit controller role |  | ||||||
| 			approving_authority = frappe.db.get_value("Accounts Settings", None, |  | ||||||
| 				"credit_controller") |  | ||||||
| 
 |  | ||||||
| 			# Check logged-in user is authorized |  | ||||||
| 			if approving_authority in frappe.user.get_roles(): |  | ||||||
| 				self.is_approving_authority = 1 |  | ||||||
| 
 |  | ||||||
| 		return self.is_approving_authority |  | ||||||
| 
 |  | ||||||
| 	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 | ||||||
| 
 | 
 | ||||||
| @ -372,13 +348,6 @@ 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): |  | ||||||
| 		for d in self.get("entries"): |  | ||||||
| 			master_type, master_name = frappe.db.get_value("Account", d.account, |  | ||||||
| 				["master_type", "master_name"]) |  | ||||||
| 			if master_type == "Customer" and master_name: |  | ||||||
| 				super(JournalVoucher, self).check_credit_limit(d.account) |  | ||||||
| 
 |  | ||||||
| 	def get_balance(self): | 	def get_balance(self): | ||||||
| 		if not self.get('entries'): | 		if not self.get('entries'): | ||||||
| 			msgprint(_("'Entries' cannot be empty"), raise_exception=True) | 			msgprint(_("'Entries' cannot be empty"), raise_exception=True) | ||||||
|  | |||||||
| @ -54,7 +54,6 @@ class PurchaseInvoice(BuyingController): | |||||||
| 		self.validate_with_previous_doc() | 		self.validate_with_previous_doc() | ||||||
| 		self.validate_uom_is_integer("uom", "qty") | 		self.validate_uom_is_integer("uom", "qty") | ||||||
| 		self.set_aging_date() | 		self.set_aging_date() | ||||||
| 		frappe.get_doc("Account", self.credit_to).validate_due_date(self.posting_date, self.due_date) |  | ||||||
| 		self.set_against_expense_account() | 		self.set_against_expense_account() | ||||||
| 		self.validate_write_off_account() | 		self.validate_write_off_account() | ||||||
| 		self.update_valuation_rate("entries") | 		self.update_valuation_rate("entries") | ||||||
| @ -73,8 +72,7 @@ class PurchaseInvoice(BuyingController): | |||||||
| 		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, "Supplier", self.supplier, self.company) | ||||||
| 				self.credit_to, self.company) |  | ||||||
| 
 | 
 | ||||||
| 		super(PurchaseInvoice, self).set_missing_values(for_validate) | 		super(PurchaseInvoice, self).set_missing_values(for_validate) | ||||||
| 
 | 
 | ||||||
| @ -408,8 +406,6 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters): | |||||||
| 					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_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, | ||||||
|  | |||||||
| @ -66,9 +66,6 @@ class SalesInvoice(SellingController): | |||||||
| 			self.is_opening = 'No' | 			self.is_opening = 'No' | ||||||
| 
 | 
 | ||||||
| 		self.set_aging_date() | 		self.set_aging_date() | ||||||
| 
 |  | ||||||
| 		frappe.get_doc("Account", self.debit_to).validate_due_date(self.posting_date, self.due_date) |  | ||||||
| 
 |  | ||||||
| 		self.set_against_income_account() | 		self.set_against_income_account() | ||||||
| 		self.validate_c_form() | 		self.validate_c_form() | ||||||
| 		self.validate_time_logs_are_submitted() | 		self.validate_time_logs_are_submitted() | ||||||
| @ -91,10 +88,9 @@ class SalesInvoice(SellingController): | |||||||
| 		self.update_status_updater_args() | 		self.update_status_updater_args() | ||||||
| 		self.update_prevdoc_status() | 		self.update_prevdoc_status() | ||||||
| 		self.update_billing_status_for_zero_amount_refdoc("Sales Order") | 		self.update_billing_status_for_zero_amount_refdoc("Sales Order") | ||||||
| 
 | 		self.check_credit_limit() | ||||||
| 		# this sequence because outstanding may get -ve | 		# this sequence because outstanding may get -ve | ||||||
| 		self.make_gl_entries() | 		self.make_gl_entries() | ||||||
| 		self.check_credit_limit(self.debit_to) |  | ||||||
| 
 | 
 | ||||||
| 		if not cint(self.is_pos) == 1: | 		if not cint(self.is_pos) == 1: | ||||||
| 			self.update_against_document_in_jv() | 			self.update_against_document_in_jv() | ||||||
| @ -149,8 +145,7 @@ class SalesInvoice(SellingController): | |||||||
| 		if not self.debit_to: | 		if not self.debit_to: | ||||||
| 			self.debit_to = get_party_account(self.company, self.customer, "Customer") | 			self.debit_to = get_party_account(self.company, self.customer, "Customer") | ||||||
| 		if not self.due_date: | 		if not self.due_date: | ||||||
| 			self.due_date = get_due_date(self.posting_date, self.customer, "Customer", | 			self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company) | ||||||
| 				self.debit_to, self.company) |  | ||||||
| 
 | 
 | ||||||
| 		super(SalesInvoice, self).set_missing_values(for_validate) | 		super(SalesInvoice, self).set_missing_values(for_validate) | ||||||
| 
 | 
 | ||||||
| @ -603,8 +598,6 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters): | |||||||
| 					or tabAccount.account_type = "Income Account") | 					or tabAccount.account_type = "Income 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_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, | ||||||
|  | |||||||
| @ -230,7 +230,6 @@ erpnext.AccountsChart = Class.extend({ | |||||||
| 
 | 
 | ||||||
| 			var node = me.tree.get_selected_node(); | 			var node = me.tree.get_selected_node(); | ||||||
| 			v.parent_account = node.label; | 			v.parent_account = node.label; | ||||||
| 			v.master_type = ''; |  | ||||||
| 			v.company = me.company; | 			v.company = me.company; | ||||||
| 
 | 
 | ||||||
| 			return frappe.call({ | 			return frappe.call({ | ||||||
|  | |||||||
| @ -4,9 +4,9 @@ | |||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| 
 | 
 | ||||||
| import frappe | import frappe | ||||||
| from frappe import _ | from frappe import _, msgprint | ||||||
| from frappe.defaults import get_user_permissions | from frappe.defaults import get_user_permissions | ||||||
| from frappe.utils import add_days | from frappe.utils import add_days, getdate, formatdate, flt | ||||||
| from erpnext.utilities.doctype.address.address import get_address_display | from erpnext.utilities.doctype.address.address import get_address_display | ||||||
| from erpnext.utilities.doctype.contact.contact import get_contact_details | from erpnext.utilities.doctype.contact.contact import get_contact_details | ||||||
| 
 | 
 | ||||||
| @ -124,7 +124,7 @@ def set_account_and_due_date(party, account, party_type, company, posting_date, | |||||||
| 	out = { | 	out = { | ||||||
| 		party_type.lower(): party, | 		party_type.lower(): party, | ||||||
| 		account_fieldname : account, | 		account_fieldname : account, | ||||||
| 		"due_date": get_due_date(posting_date, party, party_type, account, company) | 		"due_date": get_due_date(posting_date, party_type, party, company) | ||||||
| 	} | 	} | ||||||
| 	return out | 	return out | ||||||
| 
 | 
 | ||||||
| @ -138,19 +138,34 @@ def get_party_account(company, party, party_type): | |||||||
| 
 | 
 | ||||||
| 		return acc_head | 		return acc_head | ||||||
| 
 | 
 | ||||||
| def get_due_date(posting_date, party, party_type, account, company): | def get_due_date(posting_date, party_type, party, company): | ||||||
| 	"""Set Due Date = Posting Date + Credit Days""" | 	"""Set Due Date = Posting Date + Credit Days""" | ||||||
| 	due_date = None | 	due_date = None | ||||||
| 	if posting_date: | 	if posting_date: | ||||||
| 		credit_days = 0 | 		credit_days = get_credit_days(party_type, party, company) | ||||||
| 		if account: |  | ||||||
| 			credit_days = frappe.db.get_value("Account", account, "credit_days") |  | ||||||
| 		if party and not credit_days: |  | ||||||
| 			credit_days = frappe.db.get_value(party_type, party, "credit_days") |  | ||||||
| 		if company and not credit_days: |  | ||||||
| 			credit_days = frappe.db.get_value("Company", company, "credit_days") |  | ||||||
| 
 |  | ||||||
| 		due_date = add_days(posting_date, credit_days) if credit_days else posting_date | 		due_date = add_days(posting_date, credit_days) if credit_days else posting_date | ||||||
| 
 | 
 | ||||||
| 	return due_date | 	return due_date | ||||||
| 
 | 
 | ||||||
|  | def get_credit_days(self, party_type, party, company): | ||||||
|  | 	return frappe.db.get_value(party_type, party, "credit_days") or \ | ||||||
|  | 			frappe.db.get_value("Company", company, "credit_days") if company else 0 | ||||||
|  | 
 | ||||||
|  | def validate_due_date(posting_date, due_date, party_type, party, company): | ||||||
|  | 	credit_days = get_credit_days(party_type, party, company) | ||||||
|  | 
 | ||||||
|  | 	posting_date, due_date = getdate(posting_date), getdate(due_date) | ||||||
|  | 	diff = (due_date - posting_date).days | ||||||
|  | 
 | ||||||
|  | 	if diff < 0: | ||||||
|  | 		frappe.throw(_("Due Date cannot be before Posting Date")) | ||||||
|  | 	elif credit_days is not None and diff > flt(credit_days): | ||||||
|  | 		is_credit_controller = frappe.db.get_value("Accounts Settings", None, | ||||||
|  | 			"credit_controller") in frappe.user.get_roles() | ||||||
|  | 
 | ||||||
|  | 		if is_credit_controller: | ||||||
|  | 			msgprint(_("Note: Due / Reference Date exceeds the allowed credit days by {0} day(s)").format( | ||||||
|  | 				diff - flt(credit_days))) | ||||||
|  | 		else: | ||||||
|  | 			max_due_date = formatdate(add_days(posting_date, credit_days)) | ||||||
|  | 			frappe.throw(_("Due / Reference Date cannot be after {0}").format(max_due_date)) | ||||||
|  | |||||||
| @ -61,17 +61,9 @@ class Supplier(TransactionBase): | |||||||
| 			where supplier=%s""", self.name): | 			where supplier=%s""", self.name): | ||||||
| 				frappe.delete_doc("Contact", contact) | 				frappe.delete_doc("Contact", contact) | ||||||
| 
 | 
 | ||||||
| 	def delete_supplier_account(self): |  | ||||||
| 		"""delete supplier's ledger if exist and check balance before deletion""" |  | ||||||
| 		acc = frappe.db.sql("select name from `tabAccount` where master_type = 'Supplier' \ |  | ||||||
| 			and master_name = %s and docstatus < 2", self.name) |  | ||||||
| 		if acc: |  | ||||||
| 			frappe.delete_doc('Account', acc[0][0]) |  | ||||||
| 
 |  | ||||||
| 	def on_trash(self): | 	def on_trash(self): | ||||||
| 		self.delete_supplier_address() | 		self.delete_supplier_address() | ||||||
| 		self.delete_supplier_contact() | 		self.delete_supplier_contact() | ||||||
| 		self.delete_supplier_account() |  | ||||||
| 
 | 
 | ||||||
| 	def after_rename(self, olddn, newdn, merge=False): | 	def after_rename(self, olddn, newdn, merge=False): | ||||||
| 		set_field = '' | 		set_field = '' | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ class AccountsController(TransactionBase): | |||||||
| 			self.set_total_in_words() | 			self.set_total_in_words() | ||||||
| 
 | 
 | ||||||
| 		self.validate_for_freezed_account() | 		self.validate_for_freezed_account() | ||||||
|  | 		self.validate_due_date() | ||||||
| 
 | 
 | ||||||
| 		if self.meta.get_field("is_recurring"): | 		if self.meta.get_field("is_recurring"): | ||||||
| 			validate_recurring_document(self) | 			validate_recurring_document(self) | ||||||
| @ -61,6 +62,13 @@ class AccountsController(TransactionBase): | |||||||
| 				validate_fiscal_year(self.get(date_field), self.fiscal_year, | 				validate_fiscal_year(self.get(date_field), self.fiscal_year, | ||||||
| 					label=self.meta.get_label(date_field)) | 					label=self.meta.get_label(date_field)) | ||||||
| 
 | 
 | ||||||
|  | 	def validate_due_date(self): | ||||||
|  | 		from erpnext.accounts.party import validate_due_date | ||||||
|  | 		if self.doctype == "Sales Invoice": | ||||||
|  | 			validate_due_date(self.posting_date, self.due_date, "Customer", self.customer, self.company) | ||||||
|  | 		elif self.doctype == "Purchase Invoice": | ||||||
|  | 			validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier, self.company) | ||||||
|  | 
 | ||||||
| 	def validate_for_freezed_account(self): | 	def validate_for_freezed_account(self): | ||||||
| 		for fieldname in ["customer", "supplier"]: | 		for fieldname in ["customer", "supplier"]: | ||||||
| 			if self.meta.get_field(fieldname) and self.get(fieldname): | 			if self.meta.get_field(fieldname) and self.get(fieldname): | ||||||
| @ -515,15 +523,6 @@ class AccountsController(TransactionBase): | |||||||
| 
 | 
 | ||||||
| 		return self._abbr | 		return self._abbr | ||||||
| 
 | 
 | ||||||
| 	def check_credit_limit(self, account): |  | ||||||
| 		total_outstanding = frappe.db.sql(""" |  | ||||||
| 			select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) |  | ||||||
| 			from `tabGL Entry` where account = %s""", account) |  | ||||||
| 
 |  | ||||||
| 		total_outstanding = total_outstanding[0][0] if total_outstanding else 0 |  | ||||||
| 		if total_outstanding: |  | ||||||
| 			frappe.get_doc('Account', account).check_credit_limit(total_outstanding) |  | ||||||
| 
 |  | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
| def get_tax_rate(account_head): | def get_tax_rate(account_head): | ||||||
| 	return frappe.db.get_value("Account", account_head, "tax_rate") | 	return frappe.db.get_value("Account", account_head, "tax_rate") | ||||||
|  | |||||||
| @ -33,6 +33,10 @@ class SellingController(StockController): | |||||||
| 		self.validate_max_discount() | 		self.validate_max_discount() | ||||||
| 		check_active_sales_items(self) | 		check_active_sales_items(self) | ||||||
| 
 | 
 | ||||||
|  | 	def check_credit_limit(self): | ||||||
|  | 		from erpnext.selling.doctype.customer.customer import check_credit_limit | ||||||
|  | 		check_credit_limit(self.customer, self.company) | ||||||
|  | 
 | ||||||
| 	def set_missing_values(self, for_validate=False): | 	def set_missing_values(self, for_validate=False): | ||||||
| 		super(SellingController, self).set_missing_values(for_validate) | 		super(SellingController, self).set_missing_values(for_validate) | ||||||
| 
 | 
 | ||||||
| @ -301,28 +305,6 @@ class SellingController(StockController): | |||||||
| 		elif self.order_type not in valid_types: | 		elif self.order_type not in valid_types: | ||||||
| 			throw(_("Order Type must be one of {0}").format(comma_or(valid_types))) | 			throw(_("Order Type must be one of {0}").format(comma_or(valid_types))) | ||||||
| 
 | 
 | ||||||
| 	def check_credit(self, grand_total): |  | ||||||
| 		customer_account = frappe.db.get_value("Account", {"company": self.company, |  | ||||||
| 			"master_name": self.customer}, "name") |  | ||||||
| 		if customer_account: |  | ||||||
| 			invoice_outstanding = frappe.db.sql("""select |  | ||||||
| 				sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) |  | ||||||
| 				from `tabGL Entry` where account = %s""", customer_account) |  | ||||||
| 			invoice_outstanding = flt(invoice_outstanding[0][0]) if invoice_outstanding else 0 |  | ||||||
| 
 |  | ||||||
| 			ordered_amount_to_be_billed = frappe.db.sql(""" |  | ||||||
| 				select sum(grand_total*(100 - ifnull(per_billed, 0))/100) |  | ||||||
| 				from `tabSales Order` |  | ||||||
| 				where customer=%s and docstatus = 1 |  | ||||||
| 				and ifnull(per_billed, 0) < 100 and status != 'Stopped'""", self.customer) |  | ||||||
| 
 |  | ||||||
| 			ordered_amount_to_be_billed = flt(ordered_amount_to_be_billed[0][0]) \ |  | ||||||
| 				if ordered_amount_to_be_billed else 0.0 |  | ||||||
| 
 |  | ||||||
| 			total_outstanding = invoice_outstanding + ordered_amount_to_be_billed |  | ||||||
| 
 |  | ||||||
| 			frappe.get_doc('Account', customer_account).check_credit_limit(total_outstanding) |  | ||||||
| 
 |  | ||||||
| 	def validate_max_discount(self): | 	def validate_max_discount(self): | ||||||
| 		for d in self.get(self.fname): | 		for d in self.get(self.fname): | ||||||
| 			discount = flt(frappe.db.get_value("Item", d.item_code, "max_discount")) | 			discount = flt(frappe.db.get_value("Item", d.item_code, "max_discount")) | ||||||
|  | |||||||
| @ -4,8 +4,9 @@ | |||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| import frappe | import frappe | ||||||
| from frappe.model.naming import make_autoname | from frappe.model.naming import make_autoname | ||||||
| from frappe import _ | from frappe import _, msgprint, throw | ||||||
| import frappe.defaults | import frappe.defaults | ||||||
|  | from frappe.utils import flt | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| from erpnext.utilities.transaction_base import TransactionBase | from erpnext.utilities.transaction_base import TransactionBase | ||||||
| @ -92,17 +93,9 @@ class Customer(TransactionBase): | |||||||
| 			where customer=%s""", self.name): | 			where customer=%s""", self.name): | ||||||
| 				frappe.delete_doc("Contact", contact) | 				frappe.delete_doc("Contact", contact) | ||||||
| 
 | 
 | ||||||
| 	def delete_customer_account(self): |  | ||||||
| 		"""delete customer's ledger if exist and check balance before deletion""" |  | ||||||
| 		acc = frappe.db.sql("select name from `tabAccount` where master_type = 'Customer' \ |  | ||||||
| 			and master_name = %s and docstatus < 2", self.name) |  | ||||||
| 		if acc: |  | ||||||
| 			frappe.delete_doc('Account', acc[0][0]) |  | ||||||
| 
 |  | ||||||
| 	def on_trash(self): | 	def on_trash(self): | ||||||
| 		self.delete_customer_address() | 		self.delete_customer_address() | ||||||
| 		self.delete_customer_contact() | 		self.delete_customer_contact() | ||||||
| 		self.delete_customer_account() |  | ||||||
| 		if self.lead_name: | 		if self.lead_name: | ||||||
| 			frappe.db.sql("update `tabLead` set status='Interested' where name=%s",self.lead_name) | 			frappe.db.sql("update `tabLead` set status='Interested' where name=%s",self.lead_name) | ||||||
| 
 | 
 | ||||||
| @ -154,3 +147,61 @@ def get_customer_list(doctype, txt, searchfield, start, page_len, filters): | |||||||
| 		name, customer_name limit %s, %s""" % | 		name, customer_name limit %s, %s""" % | ||||||
| 		(", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"), | 		(", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"), | ||||||
| 		("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len)) | 		("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def check_credit_limit(customer, company): | ||||||
|  | 	customer_outstanding = get_customer_outstanding(customer, company) | ||||||
|  | 
 | ||||||
|  | 	credit_limit = frappe.db.get_value("Customer", customer, "credit_limit") or \ | ||||||
|  | 		frappe.db.get_value('Company', company, 'credit_limit') | ||||||
|  | 
 | ||||||
|  | 	if credit_limit > 0 and flt(customer_outstanding) > credit_limit: | ||||||
|  | 		msgprint(_("Credit limit has been crossed for customer {0} {1}/{2}") | ||||||
|  | 			.format(customer, customer_outstanding, credit_limit)) | ||||||
|  | 
 | ||||||
|  | 		# If not authorized person raise exception | ||||||
|  | 		credit_controller = frappe.db.get_value('Accounts Settings', None, 'credit_controller') | ||||||
|  | 		if not credit_controller or credit_controller not in frappe.user.get_roles(): | ||||||
|  | 			throw(_("Please contact to the user who have Sales Master Manager {0} role") | ||||||
|  | 				.format(" / " + credit_controller if credit_controller else "")) | ||||||
|  | 
 | ||||||
|  | def get_customer_outstanding(customer, company): | ||||||
|  | 	# Outstanding based on GL Entries | ||||||
|  | 	outstanding_based_on_gle = frappe.db.sql("""select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) | ||||||
|  | 		from `tabGL Entry` where party_type = 'Customer' and party = %s and company=%s""", (customer, company)) | ||||||
|  | 
 | ||||||
|  | 	outstanding_based_on_gle = flt(outstanding_based_on_gle[0][0]) if outstanding_based_on_gle else 0 | ||||||
|  | 
 | ||||||
|  | 	# Outstanding based on Sales Order | ||||||
|  | 	outstanding_based_on_so = frappe.db.sql(""" | ||||||
|  | 		select sum(grand_total*(100 - ifnull(per_billed, 0))/100) | ||||||
|  | 		from `tabSales Order` | ||||||
|  | 		where customer=%s and docstatus = 1 and company=%s | ||||||
|  | 		and ifnull(per_billed, 0) < 100 and status != 'Stopped'""", (customer, company)) | ||||||
|  | 
 | ||||||
|  | 	outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0.0 | ||||||
|  | 
 | ||||||
|  | 	# Outstanding based on Delivery Note | ||||||
|  | 	outstanding_based_on_dn = frappe.db.sql(""" | ||||||
|  | 		select | ||||||
|  | 			sum( | ||||||
|  | 				( | ||||||
|  | 					(ifnull(dn_item.amount) - (select sum(ifnull(amount, 0)) | ||||||
|  | 						from `tabSales Invoice Item` | ||||||
|  | 						where ifnull(dn_detail, '') = dn_item.name and docstatus = 1) | ||||||
|  | 					)/dn.net_total | ||||||
|  | 				)*dn.grand_total | ||||||
|  | 			) | ||||||
|  | 		from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item | ||||||
|  | 		where | ||||||
|  | 			dn.name = dn_item.parent and dn.customer=%s and dn.company=%s | ||||||
|  | 			and dn.docstatus = 1 and dn.status != 'Stopped' | ||||||
|  | 			and ifnull(dn_item.against_sales_order, '') = '' | ||||||
|  | 			and ifnull(dn_item.against_sales_invoice, '') = '' | ||||||
|  | 			and ifnull(dn_item.amount) > (select sum(ifnull(amount, 0)) | ||||||
|  | 				from `tabSales Invoice Item` | ||||||
|  | 				where ifnull(dn_detail, '') = dn_item.name and docstatus = 1)""", (customer, company)) | ||||||
|  | 
 | ||||||
|  | 	outstanding_based_on_dn = flt(outstanding_based_on_dn[0][0]) if outstanding_based_on_dn else 0.0 | ||||||
|  | 
 | ||||||
|  | 	return outstanding_based_on_gle + outstanding_based_on_so + outstanding_based_on_dn | ||||||
|  | |||||||
| @ -153,10 +153,9 @@ class SalesOrder(SellingController): | |||||||
| 	def on_submit(self): | 	def on_submit(self): | ||||||
| 		super(SalesOrder, self).on_submit() | 		super(SalesOrder, self).on_submit() | ||||||
| 
 | 
 | ||||||
|  | 		self.check_credit_limit() | ||||||
| 		self.update_stock_ledger(update_stock = 1) | 		self.update_stock_ledger(update_stock = 1) | ||||||
| 
 | 
 | ||||||
| 		self.check_credit(self.grand_total) |  | ||||||
| 
 |  | ||||||
| 		frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.grand_total, self) | 		frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.grand_total, self) | ||||||
| 
 | 
 | ||||||
| 		self.update_prevdoc_status('submit') | 		self.update_prevdoc_status('submit') | ||||||
| @ -357,17 +356,6 @@ def make_sales_invoice(source_name, target_doc=None): | |||||||
| 		} | 		} | ||||||
| 	}, target_doc, postprocess) | 	}, target_doc, postprocess) | ||||||
| 
 | 
 | ||||||
| 	def set_advance_vouchers(source, target): |  | ||||||
| 		advance_voucher_list = [] |  | ||||||
| 
 |  | ||||||
| 		advance_voucher = frappe.db.sql(""" |  | ||||||
| 			select |  | ||||||
| 				t1.name as voucher_no, t1.posting_date, t1.remark, t2.account, |  | ||||||
| 				t2.name as voucher_detail_no, {amount_query} as payment_amount, t2.is_advance |  | ||||||
| 			from |  | ||||||
| 				`tabJournal Voucher` t1, `tabJournal Voucher Detail` t2 |  | ||||||
| 			""") |  | ||||||
| 
 |  | ||||||
| 	return doclist | 	return doclist | ||||||
| 
 | 
 | ||||||
| @frappe.whitelist() | @frappe.whitelist() | ||||||
|  | |||||||
| @ -94,7 +94,6 @@ class Company(Document): | |||||||
| 		account = frappe.get_doc({ | 		account = frappe.get_doc({ | ||||||
| 			"doctype": "Account", | 			"doctype": "Account", | ||||||
| 			"freeze_account": "No", | 			"freeze_account": "No", | ||||||
| 			"master_type": "", |  | ||||||
| 			"company": self.name | 			"company": self.name | ||||||
| 		}) | 		}) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -73,7 +73,6 @@ def install(company): | |||||||
| 	# 	account = frappe.get_doc({ | 	# 	account = frappe.get_doc({ | ||||||
| 	# 		"doctype": "Account", | 	# 		"doctype": "Account", | ||||||
| 	# 		"freeze_account": "No", | 	# 		"freeze_account": "No", | ||||||
| 	# 		"master_type": "", |  | ||||||
| 	# 		"company": company.name | 	# 		"company": company.name | ||||||
| 	# 	}) | 	# 	}) | ||||||
| 	# | 	# | ||||||
|  | |||||||
| @ -174,11 +174,11 @@ class DeliveryNote(SellingController): | |||||||
| 		# update delivered qty in sales order | 		# update delivered qty in sales order | ||||||
| 		self.update_prevdoc_status() | 		self.update_prevdoc_status() | ||||||
| 
 | 
 | ||||||
|  | 		self.check_credit_limit() | ||||||
|  | 
 | ||||||
| 		# create stock ledger entry | 		# create stock ledger entry | ||||||
| 		self.update_stock_ledger() | 		self.update_stock_ledger() | ||||||
| 
 | 
 | ||||||
| 		self.credit_limit() |  | ||||||
| 
 |  | ||||||
| 		self.make_gl_entries() | 		self.make_gl_entries() | ||||||
| 
 | 
 | ||||||
| 		# set DN status | 		# set DN status | ||||||
| @ -271,16 +271,6 @@ class DeliveryNote(SellingController): | |||||||
| 			} | 			} | ||||||
| 			update_bin(args) | 			update_bin(args) | ||||||
| 
 | 
 | ||||||
| 	def credit_limit(self): |  | ||||||
| 		"""check credit limit of items in DN Detail which are not fetched from sales order""" |  | ||||||
| 		amount, total = 0, 0 |  | ||||||
| 		for d in self.get('delivery_note_details'): |  | ||||||
| 			if not (d.against_sales_order or d.against_sales_invoice): |  | ||||||
| 				amount += d.base_amount |  | ||||||
| 		if amount != 0: |  | ||||||
| 			total = (amount/self.net_total)*self.grand_total |  | ||||||
| 			self.check_credit(total) |  | ||||||
| 
 |  | ||||||
| def get_invoiced_qty_map(delivery_note): | def get_invoiced_qty_map(delivery_note): | ||||||
| 	"""returns a map: {dn_detail: invoiced_qty}""" | 	"""returns a map: {dn_detail: invoiced_qty}""" | ||||||
| 	invoiced_qty_map = {} | 	invoiced_qty_map = {} | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user