Merge branch 'develop' into sales-report-total-row
This commit is contained in:
		
						commit
						88e6f72cad
					
				| @ -2,9 +2,10 @@ from __future__ import unicode_literals | ||||
| 
 | ||||
| import frappe | ||||
| from frappe import _ | ||||
| from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day | ||||
| from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day, cint, get_link_to_form | ||||
| from erpnext.accounts.utils import get_account_currency | ||||
| from frappe.email import sendmail_to_system_managers | ||||
| from frappe.utils.background_jobs import enqueue | ||||
| 
 | ||||
| def validate_service_stop_date(doc): | ||||
| 	''' Validates service_stop_date for Purchase Invoice and Sales Invoice ''' | ||||
| @ -32,8 +33,20 @@ def validate_service_stop_date(doc): | ||||
| 		if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name): | ||||
| 			frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx)) | ||||
| 
 | ||||
| def convert_deferred_expense_to_expense(start_date=None, end_date=None): | ||||
| def build_conditions(process_type, account, company): | ||||
| 	conditions='' | ||||
| 	deferred_account = "item.deferred_revenue_account" if process_type=="Income" else "item.deferred_expense_account" | ||||
| 
 | ||||
| 	if account: | ||||
| 		conditions += "AND %s='%s'"%(deferred_account, account) | ||||
| 	elif company: | ||||
| 		conditions += "AND p.company='%s'"%(company) | ||||
| 
 | ||||
| 	return conditions | ||||
| 
 | ||||
| def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=''): | ||||
| 	# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM | ||||
| 
 | ||||
| 	if not start_date: | ||||
| 		start_date = add_months(today(), -1) | ||||
| 	if not end_date: | ||||
| @ -41,18 +54,25 @@ def convert_deferred_expense_to_expense(start_date=None, end_date=None): | ||||
| 
 | ||||
| 	# check for the purchase invoice for which GL entries has to be done | ||||
| 	invoices = frappe.db.sql_list(''' | ||||
| 		select distinct parent from `tabPurchase Invoice Item` | ||||
| 		where service_start_date<=%s and service_end_date>=%s | ||||
| 		and enable_deferred_expense = 1 and docstatus = 1 and ifnull(amount, 0) > 0 | ||||
| 	''', (end_date, start_date)) | ||||
| 		select distinct item.parent | ||||
| 		from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p | ||||
| 		where item.service_start_date<=%s and item.service_end_date>=%s | ||||
| 		and item.enable_deferred_expense = 1 and item.parent=p.name | ||||
| 		and item.docstatus = 1 and ifnull(item.amount, 0) > 0 | ||||
| 		{0} | ||||
| 	'''.format(conditions), (end_date, start_date)) #nosec | ||||
| 
 | ||||
| 	# For each invoice, book deferred expense | ||||
| 	for invoice in invoices: | ||||
| 		doc = frappe.get_doc("Purchase Invoice", invoice) | ||||
| 		book_deferred_income_or_expense(doc, end_date) | ||||
| 		book_deferred_income_or_expense(doc, deferred_process, end_date) | ||||
| 
 | ||||
| def convert_deferred_revenue_to_income(start_date=None, end_date=None): | ||||
| 	if frappe.flags.deferred_accounting_error: | ||||
| 		send_mail(deferred_process) | ||||
| 
 | ||||
| def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=''): | ||||
| 	# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM | ||||
| 
 | ||||
| 	if not start_date: | ||||
| 		start_date = add_months(today(), -1) | ||||
| 	if not end_date: | ||||
| @ -60,14 +80,20 @@ def convert_deferred_revenue_to_income(start_date=None, end_date=None): | ||||
| 
 | ||||
| 	# check for the sales invoice for which GL entries has to be done | ||||
| 	invoices = frappe.db.sql_list(''' | ||||
| 		select distinct parent from `tabSales Invoice Item` | ||||
| 		where service_start_date<=%s and service_end_date>=%s | ||||
| 		and enable_deferred_revenue = 1 and docstatus = 1 and ifnull(amount, 0) > 0 | ||||
| 	''', (end_date, start_date)) | ||||
| 		select distinct item.parent | ||||
| 		from `tabSales Invoice Item` item, `tabSales Invoice` p | ||||
| 		where item.service_start_date<=%s and item.service_end_date>=%s | ||||
| 		and item.enable_deferred_revenue = 1 and item.parent=p.name | ||||
| 		and item.docstatus = 1 and ifnull(item.amount, 0) > 0 | ||||
| 		{0} | ||||
| 	'''.format(conditions), (end_date, start_date)) #nosec | ||||
| 
 | ||||
| 	for invoice in invoices: | ||||
| 		doc = frappe.get_doc("Sales Invoice", invoice) | ||||
| 		book_deferred_income_or_expense(doc, end_date) | ||||
| 		book_deferred_income_or_expense(doc, deferred_process, end_date) | ||||
| 
 | ||||
| 	if frappe.flags.deferred_accounting_error: | ||||
| 		send_mail(deferred_process) | ||||
| 
 | ||||
| def get_booking_dates(doc, item, posting_date=None): | ||||
| 	if not posting_date: | ||||
| @ -136,7 +162,7 @@ def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, a | ||||
| 
 | ||||
| 	return amount, base_amount | ||||
| 
 | ||||
| def book_deferred_income_or_expense(doc, posting_date=None): | ||||
| def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): | ||||
| 	enable_check = "enable_deferred_revenue" \ | ||||
| 		if doc.doctype=="Sales Invoice" else "enable_deferred_expense" | ||||
| 
 | ||||
| @ -159,7 +185,11 @@ def book_deferred_income_or_expense(doc, posting_date=None): | ||||
| 			total_days, total_booking_days, account_currency) | ||||
| 
 | ||||
| 		make_gl_entries(doc, credit_account, debit_account, against, | ||||
| 			amount, base_amount, end_date, project, account_currency, item.cost_center, item.name) | ||||
| 			amount, base_amount, end_date, project, account_currency, item.cost_center, item.name, deferred_process) | ||||
| 
 | ||||
| 		# Returned in case of any errors because it tries to submit the same record again and again in case of errors | ||||
| 		if frappe.flags.deferred_accounting_error: | ||||
| 			return | ||||
| 
 | ||||
| 		if getdate(end_date) < getdate(posting_date) and not last_gl_entry: | ||||
| 			_book_deferred_revenue_or_expense(item) | ||||
| @ -169,8 +199,30 @@ def book_deferred_income_or_expense(doc, posting_date=None): | ||||
| 		if item.get(enable_check): | ||||
| 			_book_deferred_revenue_or_expense(item) | ||||
| 
 | ||||
| def process_deferred_accounting(posting_date=today()): | ||||
| 	''' Converts deferred income/expense into income/expense | ||||
| 		Executed via background jobs on every month end ''' | ||||
| 
 | ||||
| 	if not cint(frappe.db.get_singles_value('Accounts Settings', 'automatically_process_deferred_accounting_entry')): | ||||
| 		return | ||||
| 
 | ||||
| 	start_date = add_months(today(), -1) | ||||
| 	end_date = add_days(today(), -1) | ||||
| 
 | ||||
| 	for record_type in ('Income', 'Expense'): | ||||
| 		doc = frappe.get_doc(dict( | ||||
| 			doctype='Process Deferred Accounting', | ||||
| 			posting_date=posting_date, | ||||
| 			start_date=start_date, | ||||
| 			end_date=end_date, | ||||
| 			type=record_type | ||||
| 		)) | ||||
| 
 | ||||
| 		doc.insert() | ||||
| 		doc.submit() | ||||
| 
 | ||||
| def make_gl_entries(doc, credit_account, debit_account, against, | ||||
| 	amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no): | ||||
| 	amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no, deferred_process=None): | ||||
| 	# GL Entry for crediting the amount in the deferred expense | ||||
| 	from erpnext.accounts.general_ledger import make_gl_entries | ||||
| 
 | ||||
| @ -186,7 +238,9 @@ def make_gl_entries(doc, credit_account, debit_account, against, | ||||
| 			"cost_center": cost_center, | ||||
| 			"voucher_detail_no": voucher_detail_no, | ||||
| 			'posting_date': posting_date, | ||||
| 			'project': project | ||||
| 			'project': project, | ||||
| 			'against_voucher_type': 'Process Deferred Accounting', | ||||
| 			'against_voucher': deferred_process | ||||
| 		}, account_currency) | ||||
| 	) | ||||
| 	# GL Entry to debit the amount from the expense | ||||
| @ -199,7 +253,9 @@ def make_gl_entries(doc, credit_account, debit_account, against, | ||||
| 			"cost_center": cost_center, | ||||
| 			"voucher_detail_no": voucher_detail_no, | ||||
| 			'posting_date': posting_date, | ||||
| 			'project': project | ||||
| 			'project': project, | ||||
| 			'against_voucher_type': 'Process Deferred Accounting', | ||||
| 			'against_voucher': deferred_process | ||||
| 		}, account_currency) | ||||
| 	) | ||||
| 
 | ||||
| @ -209,7 +265,16 @@ def make_gl_entries(doc, credit_account, debit_account, against, | ||||
| 			frappe.db.commit() | ||||
| 		except: | ||||
| 			frappe.db.rollback() | ||||
| 			title = _("Error while processing deferred accounting for {0}").format(doc.name) | ||||
| 			traceback = frappe.get_traceback() | ||||
| 			frappe.log_error(message=traceback , title=title) | ||||
| 			sendmail_to_system_managers(title, traceback) | ||||
| 			frappe.log_error(message=traceback) | ||||
| 
 | ||||
| 			frappe.flags.deferred_accounting_error = True | ||||
| 
 | ||||
| def send_mail(deferred_process): | ||||
| 	title = _("Error while processing deferred accounting for {0}".format(deferred_process)) | ||||
| 	content = _(""" | ||||
| 		Deferred accounting failed for some invoices: | ||||
| 		Please check Process Deferred Accounting {0} | ||||
| 		and submit manually after resolving errors | ||||
| 	""").format(get_link_to_form('Process Deferred Accounting', deferred_process)) | ||||
| 	sendmail_to_system_managers(title, content) | ||||
|  | ||||
| @ -8,7 +8,7 @@ | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "label": "General Ledger", | ||||
|    "links": "[\n    {\n        \"description\": \"Accounting journal entries.\",\n        \"label\": \"Journal Entry\",\n        \"name\": \"Journal Entry\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"GL Entry\"\n        ],\n        \"doctype\": \"GL Entry\",\n        \"is_query_report\": true,\n        \"label\": \"General Ledger\",\n        \"name\": \"General Ledger\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Sales Invoice\"\n        ],\n        \"doctype\": \"Sales Invoice\",\n        \"is_query_report\": true,\n        \"label\": \"Customer Ledger Summary\",\n        \"name\": \"Customer Ledger Summary\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Sales Invoice\"\n        ],\n        \"doctype\": \"Sales Invoice\",\n        \"is_query_report\": true,\n        \"label\": \"Supplier Ledger Summary\",\n        \"name\": \"Supplier Ledger Summary\",\n        \"type\": \"report\"\n    }\n]" | ||||
|    "links": "[\n    {\n        \"description\": \"Accounting journal entries.\",\n        \"label\": \"Journal Entry\",\n        \"name\": \"Journal Entry\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Make journal entries from a template.\",\n        \"label\": \"Journal Entry Template\",\n        \"name\": \"Journal Entry Template\",\n        \"type\": \"doctype\"\n    },\n    \n    {\n        \"dependencies\": [\n            \"GL Entry\"\n        ],\n        \"doctype\": \"GL Entry\",\n        \"is_query_report\": true,\n        \"label\": \"General Ledger\",\n        \"name\": \"General Ledger\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Sales Invoice\"\n        ],\n        \"doctype\": \"Sales Invoice\",\n        \"is_query_report\": true,\n        \"label\": \"Customer Ledger Summary\",\n        \"name\": \"Customer Ledger Summary\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Sales Invoice\"\n        ],\n        \"doctype\": \"Sales Invoice\",\n        \"is_query_report\": true,\n        \"label\": \"Supplier Ledger Summary\",\n        \"name\": \"Supplier Ledger Summary\",\n        \"type\": \"report\"\n    }\n]" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
| @ -47,8 +47,8 @@ | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "label": "Banking and Payments", | ||||
|    "links": "[\n    {\n        \"description\": \"Match non-linked Invoices and Payments.\",\n        \"label\": \"Match Payments with Invoices\",\n        \"name\": \"Payment Reconciliation\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Update bank payment dates with journals.\",\n        \"label\": \"Update Bank Transaction Dates\",\n        \"name\": \"Bank Reconciliation\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Invoice Discounting\",\n        \"name\": \"Invoice Discounting\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Journal Entry\"\n        ],\n        \"doctype\": \"Journal Entry\",\n        \"is_query_report\": true,\n        \"label\": \"Bank Reconciliation Statement\",\n        \"name\": \"Bank Reconciliation Statement\",\n        \"type\": \"report\"\n    },\n    {\n        \"icon\": \"fa fa-bar-chart\",\n        \"label\": \"Bank Reconciliation\",\n        \"name\": \"bank-reconciliation\",\n        \"type\": \"page\"\n    },\n    {\n        \"dependencies\": [\n            \"Journal Entry\"\n        ],\n        \"doctype\": \"Journal Entry\",\n        \"is_query_report\": true,\n        \"label\": \"Bank Clearance Summary\",\n        \"name\": \"Bank Clearance Summary\",\n        \"type\": \"report\"\n    },\n    {\n        \"label\": \"Bank Guarantee\",\n        \"name\": \"Bank Guarantee\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Setup cheque dimensions for printing\",\n        \"label\": \"Cheque Print Template\",\n        \"name\": \"Cheque Print Template\",\n        \"type\": \"doctype\"\n    }\n]" | ||||
|    "links": "[\n    {\n        \"description\": \"Match non-linked Invoices and Payments.\",\n        \"label\": \"Match Payments with Invoices\",\n        \"name\": \"Payment Reconciliation\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Update bank payment dates with journals.\",\n        \"label\": \"Update Bank Clearance Dates\",\n        \"name\": \"Bank Clearance\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Invoice Discounting\",\n        \"name\": \"Invoice Discounting\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Journal Entry\"\n        ],\n        \"doctype\": \"Journal Entry\",\n        \"is_query_report\": true,\n        \"label\": \"Bank Reconciliation Statement\",\n        \"name\": \"Bank Reconciliation Statement\",\n        \"type\": \"report\"\n    },\n    {\n        \"icon\": \"fa fa-bar-chart\",\n        \"label\": \"Bank Reconciliation\",\n        \"name\": \"bank-reconciliation\",\n        \"type\": \"page\"\n    },\n    {\n        \"dependencies\": [\n            \"Journal Entry\"\n        ],\n        \"doctype\": \"Journal Entry\",\n        \"is_query_report\": true,\n        \"label\": \"Bank Clearance Summary\",\n        \"name\": \"Bank Clearance Summary\",\n        \"type\": \"report\"\n    },\n    {\n        \"label\": \"Bank Guarantee\",\n        \"name\": \"Bank Guarantee\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"description\": \"Setup cheque dimensions for printing\",\n        \"label\": \"Cheque Print Template\",\n        \"name\": \"Cheque Print Template\",\n        \"type\": \"doctype\"\n    }\n]", | ||||
|    "title": "Banking and Payments" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
| @ -103,7 +103,7 @@ | ||||
|  "idx": 0, | ||||
|  "is_standard": 1, | ||||
|  "label": "Accounting", | ||||
|  "modified": "2020-04-01 11:28:50.925719", | ||||
|  "modified": "2020-04-29 12:17:34.844397", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Accounting", | ||||
|  | ||||
| @ -206,12 +206,13 @@ def get_dimension_filters(): | ||||
| 		WHERE disabled = 0 | ||||
| 	""", as_dict=1) | ||||
| 
 | ||||
| 	default_dimensions = frappe.db.sql("""SELECT parent, company, default_dimension | ||||
| 		FROM `tabAccounting Dimension Detail`""", as_dict=1) | ||||
| 	default_dimensions = frappe.db.sql("""SELECT p.fieldname, c.company, c.default_dimension | ||||
| 		FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p | ||||
| 		WHERE c.parent = p.name""", as_dict=1) | ||||
| 
 | ||||
| 	default_dimensions_map = {} | ||||
| 	for dimension in default_dimensions: | ||||
| 		default_dimensions_map.setdefault(dimension['company'], {}) | ||||
| 		default_dimensions_map[dimension['company']][dimension['parent']] = dimension['default_dimension'] | ||||
| 		default_dimensions_map.setdefault(dimension.company, {}) | ||||
| 		default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension | ||||
| 
 | ||||
| 	return dimension_filters, default_dimensions_map | ||||
|  | ||||
| @ -42,7 +42,7 @@ class AccountingPeriod(Document): | ||||
| 	def get_doctypes_for_closing(self): | ||||
| 		docs_for_closing = [] | ||||
| 		doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \ | ||||
| 			"Bank Reconciliation", "Asset", "Stock Entry"] | ||||
| 			"Bank Clearance", "Asset", "Stock Entry"] | ||||
| 		closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes] | ||||
| 		for closed_doctype in closed_doctypes: | ||||
| 			docs_for_closing.append(closed_doctype) | ||||
|  | ||||
| @ -1,210 +1,226 @@ | ||||
| { | ||||
|    "creation": "2013-06-24 15:49:57", | ||||
|    "description": "Settings for Accounts", | ||||
|    "doctype": "DocType", | ||||
|    "document_type": "Other", | ||||
|    "editable_grid": 1, | ||||
|    "engine": "InnoDB", | ||||
|    "field_order": [ | ||||
|     "auto_accounting_for_stock", | ||||
|     "acc_frozen_upto", | ||||
|     "frozen_accounts_modifier", | ||||
|     "determine_address_tax_category_from", | ||||
|     "over_billing_allowance", | ||||
|     "column_break_4", | ||||
|     "credit_controller", | ||||
|     "check_supplier_invoice_uniqueness", | ||||
|     "make_payment_via_journal_entry", | ||||
|     "unlink_payment_on_cancellation_of_invoice", | ||||
|     "unlink_advance_payment_on_cancelation_of_order", | ||||
|     "book_asset_depreciation_entry_automatically", | ||||
|     "allow_cost_center_in_entry_of_bs_account", | ||||
|     "add_taxes_from_item_tax_template", | ||||
|     "automatically_fetch_payment_terms", | ||||
|     "print_settings", | ||||
|     "show_inclusive_tax_in_print", | ||||
|     "column_break_12", | ||||
|     "show_payment_schedule_in_print", | ||||
|     "currency_exchange_section", | ||||
|     "allow_stale", | ||||
|     "stale_days", | ||||
|     "report_settings_sb", | ||||
|     "use_custom_cash_flow" | ||||
|    ], | ||||
|    "fields": [ | ||||
|     { | ||||
|      "default": "1", | ||||
|      "description": "If enabled, the system will post accounting entries for inventory automatically.", | ||||
|      "fieldname": "auto_accounting_for_stock", | ||||
|      "fieldtype": "Check", | ||||
|      "hidden": 1, | ||||
|      "in_list_view": 1, | ||||
|      "label": "Make Accounting Entry For Every Stock Movement" | ||||
|     }, | ||||
|     { | ||||
|      "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.", | ||||
|      "fieldname": "acc_frozen_upto", | ||||
|      "fieldtype": "Date", | ||||
|      "in_list_view": 1, | ||||
|      "label": "Accounts Frozen Upto" | ||||
|     }, | ||||
|     { | ||||
|      "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", | ||||
|      "fieldname": "frozen_accounts_modifier", | ||||
|      "fieldtype": "Link", | ||||
|      "in_list_view": 1, | ||||
|      "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries", | ||||
|      "options": "Role" | ||||
|     }, | ||||
|     { | ||||
|      "default": "Billing Address", | ||||
|      "description": "Address used to determine Tax Category in transactions.", | ||||
|      "fieldname": "determine_address_tax_category_from", | ||||
|      "fieldtype": "Select", | ||||
|      "label": "Determine Address Tax Category From", | ||||
|      "options": "Billing Address\nShipping Address" | ||||
|     }, | ||||
|     { | ||||
|      "fieldname": "column_break_4", | ||||
|      "fieldtype": "Column Break" | ||||
|     }, | ||||
|     { | ||||
|      "description": "Role that is allowed to submit transactions that exceed credit limits set.", | ||||
|      "fieldname": "credit_controller", | ||||
|      "fieldtype": "Link", | ||||
|      "in_list_view": 1, | ||||
|      "label": "Credit Controller", | ||||
|      "options": "Role" | ||||
|     }, | ||||
|     { | ||||
|      "fieldname": "check_supplier_invoice_uniqueness", | ||||
|      "fieldtype": "Check", | ||||
|      "label": "Check Supplier Invoice Number Uniqueness" | ||||
|     }, | ||||
|     { | ||||
|      "fieldname": "make_payment_via_journal_entry", | ||||
|      "fieldtype": "Check", | ||||
|      "label": "Make Payment via Journal Entry" | ||||
|     }, | ||||
|     { | ||||
|      "default": "1", | ||||
|      "fieldname": "unlink_payment_on_cancellation_of_invoice", | ||||
|      "fieldtype": "Check", | ||||
|      "label": "Unlink Payment on Cancellation of Invoice" | ||||
|     }, | ||||
|     { | ||||
|      "default": "1", | ||||
|      "fieldname": "unlink_advance_payment_on_cancelation_of_order", | ||||
|      "fieldtype": "Check", | ||||
|      "label": "Unlink Advance Payment on Cancelation of Order" | ||||
|     }, | ||||
|     { | ||||
|      "default": "1", | ||||
|      "fieldname": "book_asset_depreciation_entry_automatically", | ||||
|      "fieldtype": "Check", | ||||
|      "label": "Book Asset Depreciation Entry Automatically" | ||||
|     }, | ||||
|     { | ||||
|      "fieldname": "allow_cost_center_in_entry_of_bs_account", | ||||
|      "fieldtype": "Check", | ||||
|      "label": "Allow Cost Center In Entry of Balance Sheet Account" | ||||
|     }, | ||||
|     { | ||||
|      "default": "1", | ||||
|      "fieldname": "add_taxes_from_item_tax_template", | ||||
|      "fieldtype": "Check", | ||||
|      "label": "Automatically Add Taxes and Charges from Item Tax Template" | ||||
|     }, | ||||
|     { | ||||
|      "fieldname": "print_settings", | ||||
|      "fieldtype": "Section Break", | ||||
|      "label": "Print Settings" | ||||
|     }, | ||||
|     { | ||||
|      "fieldname": "show_inclusive_tax_in_print", | ||||
|      "fieldtype": "Check", | ||||
|      "label": "Show Inclusive Tax In Print" | ||||
|     }, | ||||
|     { | ||||
|      "fieldname": "column_break_12", | ||||
|      "fieldtype": "Column Break" | ||||
|     }, | ||||
|     { | ||||
|      "fieldname": "show_payment_schedule_in_print", | ||||
|      "fieldtype": "Check", | ||||
|      "label": "Show Payment Schedule in Print" | ||||
|     }, | ||||
|     { | ||||
|      "fieldname": "currency_exchange_section", | ||||
|      "fieldtype": "Section Break", | ||||
|      "label": "Currency Exchange Settings" | ||||
|     }, | ||||
|     { | ||||
|      "default": "1", | ||||
|      "fieldname": "allow_stale", | ||||
|      "fieldtype": "Check", | ||||
|      "in_list_view": 1, | ||||
|      "label": "Allow Stale Exchange Rates" | ||||
|     }, | ||||
|     { | ||||
|      "default": "1", | ||||
|      "depends_on": "eval:doc.allow_stale==0", | ||||
|      "fieldname": "stale_days", | ||||
|      "fieldtype": "Int", | ||||
|      "label": "Stale Days" | ||||
|     }, | ||||
|     { | ||||
|      "fieldname": "report_settings_sb", | ||||
|      "fieldtype": "Section Break", | ||||
|      "label": "Report Settings" | ||||
|     }, | ||||
|     { | ||||
|      "default": "0", | ||||
|      "description": "Only select if you have setup Cash Flow Mapper documents", | ||||
|      "fieldname": "use_custom_cash_flow", | ||||
|      "fieldtype": "Check", | ||||
|      "label": "Use Custom Cash Flow Format" | ||||
|     }, | ||||
|     { | ||||
|      "fieldname": "automatically_fetch_payment_terms", | ||||
|      "fieldtype": "Check", | ||||
|      "label": "Automatically Fetch Payment Terms" | ||||
|     }, | ||||
|     { | ||||
|      "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.", | ||||
|      "fieldname": "over_billing_allowance", | ||||
|      "fieldtype": "Currency", | ||||
|      "label": "Over Billing Allowance (%)" | ||||
|     } | ||||
|    ], | ||||
|    "icon": "icon-cog", | ||||
|    "idx": 1, | ||||
|    "issingle": 1, | ||||
|    "modified": "2019-07-04 18:20:55.789946", | ||||
|    "modified_by": "Administrator", | ||||
|    "module": "Accounts", | ||||
|    "name": "Accounts Settings", | ||||
|    "owner": "Administrator", | ||||
|    "permissions": [ | ||||
|     { | ||||
|      "create": 1, | ||||
|      "email": 1, | ||||
|      "print": 1, | ||||
|      "read": 1, | ||||
|      "role": "Accounts Manager", | ||||
|      "share": 1, | ||||
|      "write": 1 | ||||
|     }, | ||||
|     { | ||||
|      "read": 1, | ||||
|      "role": "Sales User" | ||||
|     }, | ||||
|     { | ||||
|      "read": 1, | ||||
|      "role": "Purchase User" | ||||
|     } | ||||
|    ], | ||||
|    "quick_entry": 1, | ||||
|    "sort_order": "ASC", | ||||
|    "track_changes": 1 | ||||
|  "actions": [], | ||||
|  "creation": "2013-06-24 15:49:57", | ||||
|  "description": "Settings for Accounts", | ||||
|  "doctype": "DocType", | ||||
|  "document_type": "Other", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "auto_accounting_for_stock", | ||||
|   "acc_frozen_upto", | ||||
|   "frozen_accounts_modifier", | ||||
|   "determine_address_tax_category_from", | ||||
|   "over_billing_allowance", | ||||
|   "column_break_4", | ||||
|   "credit_controller", | ||||
|   "check_supplier_invoice_uniqueness", | ||||
|   "make_payment_via_journal_entry", | ||||
|   "unlink_payment_on_cancellation_of_invoice", | ||||
|   "unlink_advance_payment_on_cancelation_of_order", | ||||
|   "book_asset_depreciation_entry_automatically", | ||||
|   "allow_cost_center_in_entry_of_bs_account", | ||||
|   "add_taxes_from_item_tax_template", | ||||
|   "automatically_fetch_payment_terms", | ||||
|   "automatically_process_deferred_accounting_entry", | ||||
|   "print_settings", | ||||
|   "show_inclusive_tax_in_print", | ||||
|   "column_break_12", | ||||
|   "show_payment_schedule_in_print", | ||||
|   "currency_exchange_section", | ||||
|   "allow_stale", | ||||
|   "stale_days", | ||||
|   "report_settings_sb", | ||||
|   "use_custom_cash_flow" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "default": "1", | ||||
|    "description": "If enabled, the system will post accounting entries for inventory automatically.", | ||||
|    "fieldname": "auto_accounting_for_stock", | ||||
|    "fieldtype": "Check", | ||||
|    "hidden": 1, | ||||
|    "in_list_view": 1, | ||||
|    "label": "Make Accounting Entry For Every Stock Movement" | ||||
|   }, | ||||
|   { | ||||
|    "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.", | ||||
|    "fieldname": "acc_frozen_upto", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Accounts Frozen Upto" | ||||
|   }, | ||||
|   { | ||||
|    "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", | ||||
|    "fieldname": "frozen_accounts_modifier", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries", | ||||
|    "options": "Role" | ||||
|   }, | ||||
|   { | ||||
|    "default": "Billing Address", | ||||
|    "description": "Address used to determine Tax Category in transactions.", | ||||
|    "fieldname": "determine_address_tax_category_from", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Determine Address Tax Category From", | ||||
|    "options": "Billing Address\nShipping Address" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_4", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "description": "Role that is allowed to submit transactions that exceed credit limits set.", | ||||
|    "fieldname": "credit_controller", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Credit Controller", | ||||
|    "options": "Role" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "check_supplier_invoice_uniqueness", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Check Supplier Invoice Number Uniqueness" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "make_payment_via_journal_entry", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Make Payment via Journal Entry" | ||||
|   }, | ||||
|   { | ||||
|    "default": "1", | ||||
|    "fieldname": "unlink_payment_on_cancellation_of_invoice", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Unlink Payment on Cancellation of Invoice" | ||||
|   }, | ||||
|   { | ||||
|    "default": "1", | ||||
|    "fieldname": "unlink_advance_payment_on_cancelation_of_order", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Unlink Advance Payment on Cancelation of Order" | ||||
|   }, | ||||
|   { | ||||
|    "default": "1", | ||||
|    "fieldname": "book_asset_depreciation_entry_automatically", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Book Asset Depreciation Entry Automatically" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "allow_cost_center_in_entry_of_bs_account", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Allow Cost Center In Entry of Balance Sheet Account" | ||||
|   }, | ||||
|   { | ||||
|    "default": "1", | ||||
|    "fieldname": "add_taxes_from_item_tax_template", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Automatically Add Taxes and Charges from Item Tax Template" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "print_settings", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Print Settings" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "show_inclusive_tax_in_print", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Show Inclusive Tax In Print" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_12", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "show_payment_schedule_in_print", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Show Payment Schedule in Print" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "currency_exchange_section", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Currency Exchange Settings" | ||||
|   }, | ||||
|   { | ||||
|    "default": "1", | ||||
|    "fieldname": "allow_stale", | ||||
|    "fieldtype": "Check", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Allow Stale Exchange Rates" | ||||
|   }, | ||||
|   { | ||||
|    "default": "1", | ||||
|    "depends_on": "eval:doc.allow_stale==0", | ||||
|    "fieldname": "stale_days", | ||||
|    "fieldtype": "Int", | ||||
|    "label": "Stale Days" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "report_settings_sb", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Report Settings" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "description": "Only select if you have setup Cash Flow Mapper documents", | ||||
|    "fieldname": "use_custom_cash_flow", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Use Custom Cash Flow Format" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "automatically_fetch_payment_terms", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Automatically Fetch Payment Terms" | ||||
|   }, | ||||
|   { | ||||
|    "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.", | ||||
|    "fieldname": "over_billing_allowance", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Over Billing Allowance (%)" | ||||
|   }, | ||||
|   { | ||||
|    "default": "1", | ||||
|    "fieldname": "automatically_process_deferred_accounting_entry", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Automatically Process Deferred Accounting Entry" | ||||
|   } | ||||
|  ], | ||||
|  "icon": "icon-cog", | ||||
|  "idx": 1, | ||||
|  "issingle": 1, | ||||
|  "links": [], | ||||
|  "modified": "2019-12-19 16:58:17.395595", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Accounts Settings", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "create": 1, | ||||
|    "email": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "role": "Accounts Manager", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "read": 1, | ||||
|    "role": "Sales User" | ||||
|   }, | ||||
|   { | ||||
|    "read": 1, | ||||
|    "role": "Purchase User" | ||||
|   } | ||||
|  ], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "ASC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -1,74 +1,32 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_guest_to_view": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "beta": 0,  | ||||
|  "creation": "2018-04-16 21:50:05.860195",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "",  | ||||
|  "editable_grid": 1,  | ||||
|  "engine": "InnoDB",  | ||||
|  "actions": [], | ||||
|  "creation": "2018-04-16 21:50:05.860195", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "company" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "default": "",  | ||||
|    "fieldname": "company",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Company",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Company",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|    "fieldname": "company", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Company", | ||||
|    "options": "Company", | ||||
|    "reqd": 1 | ||||
|   } | ||||
|  ],  | ||||
|  "has_web_view": 0,  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "idx": 0,  | ||||
|  "image_view": 0,  | ||||
|  "in_create": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 1,  | ||||
|  "max_attachments": 0,  | ||||
|  "modified": "2018-04-20 14:00:46.014502",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Accounts",  | ||||
|  "name": "Allowed To Transact With",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [],  | ||||
|  "quick_entry": 1,  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "show_name_in_global_search": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC",  | ||||
|  "track_changes": 1,  | ||||
|  "track_seen": 0 | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-01 12:32:34.044911", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Allowed To Transact With", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -1,7 +1,7 @@ | ||||
| // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 | ||||
| // License: GNU General Public License v3. See license.txt
 | ||||
| 
 | ||||
| frappe.ui.form.on("Bank Reconciliation", { | ||||
| frappe.ui.form.on("Bank Clearance", { | ||||
| 	setup: function(frm) { | ||||
| 		frm.add_fetch("account", "account_currency", "account_currency"); | ||||
| 	}, | ||||
							
								
								
									
										130
									
								
								erpnext/accounts/doctype/bank_clearance/bank_clearance.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								erpnext/accounts/doctype/bank_clearance/bank_clearance.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,130 @@ | ||||
| { | ||||
|  "allow_copy": 1, | ||||
|  "creation": "2013-01-10 16:34:05", | ||||
|  "doctype": "DocType", | ||||
|  "document_type": "Document", | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "account", | ||||
|   "account_currency", | ||||
|   "from_date", | ||||
|   "to_date", | ||||
|   "column_break_5", | ||||
|   "bank_account", | ||||
|   "include_reconciled_entries", | ||||
|   "include_pos_transactions", | ||||
|   "get_payment_entries", | ||||
|   "section_break_10", | ||||
|   "payment_entries", | ||||
|   "update_clearance_date", | ||||
|   "total_amount" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fetch_from": "bank_account.account", | ||||
|    "fetch_if_empty": 1, | ||||
|    "fieldname": "account", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Account", | ||||
|    "options": "Account", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "account_currency", | ||||
|    "fieldtype": "Link", | ||||
|    "hidden": 1, | ||||
|    "label": "Account Currency", | ||||
|    "options": "Currency", | ||||
|    "print_hide": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "from_date", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "From Date", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "to_date", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "To Date", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_5", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "description": "Select the Bank Account to reconcile.", | ||||
|    "fieldname": "bank_account", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Bank Account", | ||||
|    "options": "Bank Account" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "include_reconciled_entries", | ||||
|    "fieldtype": "Check", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Include Reconciled Entries" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "include_pos_transactions", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Include POS Transactions" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "get_payment_entries", | ||||
|    "fieldtype": "Button", | ||||
|    "label": "Get Payment Entries" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_10", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 1, | ||||
|    "fieldname": "payment_entries", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "Payment Entries", | ||||
|    "options": "Bank Clearance Detail" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "update_clearance_date", | ||||
|    "fieldtype": "Button", | ||||
|    "label": "Update Clearance Date" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "total_amount", | ||||
|    "fieldtype": "Currency", | ||||
|    "label": "Total Amount", | ||||
|    "options": "account_currency", | ||||
|    "read_only": 1 | ||||
|   } | ||||
|  ], | ||||
|  "hide_toolbar": 1, | ||||
|  "icon": "fa fa-check", | ||||
|  "idx": 1, | ||||
|  "issingle": 1, | ||||
|  "modified": "2020-04-06 16:12:06.628008", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Bank Clearance", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "create": 1, | ||||
|    "read": 1, | ||||
|    "role": "Accounts User", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   } | ||||
|  ], | ||||
|  "quick_entry": 1, | ||||
|  "read_only": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "ASC" | ||||
| } | ||||
| @ -11,7 +11,7 @@ form_grid_templates = { | ||||
| 	"journal_entries": "templates/form_grid/bank_reconciliation_grid.html" | ||||
| } | ||||
| 
 | ||||
| class BankReconciliation(Document): | ||||
| class BankClearance(Document): | ||||
| 	def get_payment_entries(self): | ||||
| 		if not (self.from_date and self.to_date): | ||||
| 			frappe.throw(_("From Date and To Date are Mandatory")) | ||||
| @ -0,0 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| # import frappe | ||||
| import unittest | ||||
| 
 | ||||
| class TestBankClearance(unittest.TestCase): | ||||
| 	pass | ||||
							
								
								
									
										1
									
								
								erpnext/accounts/doctype/bank_clearance_detail/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								erpnext/accounts/doctype/bank_clearance_detail/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| Detail of transaction for parent Bank Clearance. | ||||
| @ -326,7 +326,7 @@ | ||||
|  "modified": "2019-01-07 16:52:07.174687",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Accounts",  | ||||
|  "name": "Bank Reconciliation Detail",  | ||||
|  "name": "Bank Clearance Detail",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [],  | ||||
|  "quick_entry": 1,  | ||||
| @ -5,5 +5,5 @@ from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class BankReconciliationDetail(Document): | ||||
| class BankClearanceDetail(Document): | ||||
| 	pass | ||||
| @ -1,484 +0,0 @@ | ||||
| { | ||||
|  "allow_copy": 1,  | ||||
|  "allow_events_in_timeline": 0,  | ||||
|  "allow_guest_to_view": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "beta": 0,  | ||||
|  "creation": "2013-01-10 16:34:05",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "Document",  | ||||
|  "editable_grid": 0,  | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_from": "bank_account.account",  | ||||
|    "fetch_if_empty": 1,  | ||||
|    "fieldname": "account",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Account",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Account",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "account_currency",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 1,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Account Currency",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Currency",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 1,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "from_date",  | ||||
|    "fieldtype": "Date",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "From Date",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "to_date",  | ||||
|    "fieldtype": "Date",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "To Date",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "column_break_5",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "description": "Select the Bank Account to reconcile.", | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "bank_account",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Bank Account",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Bank Account",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "include_reconciled_entries",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Include Reconciled Entries",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "include_pos_transactions",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Include POS Transactions",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "get_payment_entries",  | ||||
|    "fieldtype": "Button",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Get Payment Entries",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "section_break_10",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 1,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "payment_entries",  | ||||
|    "fieldtype": "Table",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Payment Entries",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Bank Reconciliation Detail",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "update_clearance_date",  | ||||
|    "fieldtype": "Button",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Update Clearance Date",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_in_quick_entry": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fetch_if_empty": 0,  | ||||
|    "fieldname": "total_amount",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Total Amount",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "account_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 1,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "translatable": 0,  | ||||
|    "unique": 0 | ||||
|   } | ||||
|  ],  | ||||
|  "has_web_view": 0,  | ||||
|  "hide_toolbar": 1,  | ||||
|  "icon": "fa fa-check",  | ||||
|  "idx": 1,  | ||||
|  "in_create": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 1,  | ||||
|  "istable": 0,  | ||||
|  "max_attachments": 0,  | ||||
|  "menu_index": 0,  | ||||
|  "modified": "2020-01-22 00:00:00.000000",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Accounts",  | ||||
|  "name": "Bank Reconciliation",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 1,  | ||||
|    "delete": 0,  | ||||
|    "email": 0,  | ||||
|    "export": 0,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 0,  | ||||
|    "read": 1,  | ||||
|    "report": 0,  | ||||
|    "role": "Accounts User",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 1,  | ||||
|    "submit": 0,  | ||||
|    "write": 1 | ||||
|   } | ||||
|  ],  | ||||
|  "quick_entry": 1,  | ||||
|  "read_only": 1,  | ||||
|  "show_name_in_global_search": 0,  | ||||
|  "sort_order": "ASC",  | ||||
|  "track_changes": 0,  | ||||
|  "track_seen": 0,  | ||||
|  "track_views": 0 | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| QUnit.module('Account'); | ||||
| 
 | ||||
| QUnit.test("test Bank Reconciliation", function(assert) { | ||||
| 	assert.expect(0); | ||||
| 	let done = assert.async(); | ||||
| 	frappe.run_serially([ | ||||
| 		() => frappe.set_route('Form', 'Bank Reconciliation'), | ||||
| 		() => cur_frm.set_value('bank_account','Cash - FT'), | ||||
| 		() => frappe.click_button('Get Payment Entries'), | ||||
| 		() => { | ||||
| 			for(var i=0;i<=cur_frm.doc.payment_entries.length-1;i++){ | ||||
| 				cur_frm.doc.payment_entries[i].clearance_date = frappe.datetime.add_days(frappe.datetime.now_date(), 2); | ||||
| 			} | ||||
| 		}, | ||||
| 		() => {cur_frm.refresh_fields('payment_entries');}, | ||||
| 		() => frappe.click_button('Update Clearance Date'), | ||||
| 		() => frappe.timeout(0.5), | ||||
| 		() => frappe.click_button('Close'), | ||||
| 		() => done() | ||||
| 	]); | ||||
| }); | ||||
| 
 | ||||
| @ -1,8 +0,0 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| import unittest | ||||
| 
 | ||||
| class TestBankReconciliation(unittest.TestCase): | ||||
| 	pass | ||||
| @ -1 +0,0 @@ | ||||
| Detail of transaction for parent Bank Reconciliation. | ||||
| @ -1 +0,0 @@ | ||||
| from __future__ import unicode_literals | ||||
| @ -5,7 +5,7 @@ from __future__ import unicode_literals | ||||
| 
 | ||||
| import frappe | ||||
| import unittest | ||||
| from frappe.utils import nowdate | ||||
| from frappe.utils import nowdate, now_datetime | ||||
| from erpnext.accounts.utils import get_fiscal_year | ||||
| from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order | ||||
| from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError | ||||
| @ -13,27 +13,28 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ | ||||
| 
 | ||||
| class TestBudget(unittest.TestCase): | ||||
| 	def test_monthly_budget_crossed_ignore(self): | ||||
| 		set_total_expense_zero("2013-02-28", "cost_center") | ||||
| 		set_total_expense_zero(nowdate(), "cost_center") | ||||
| 
 | ||||
| 		budget = make_budget(budget_against="Cost Center") | ||||
| 
 | ||||
| 		jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) | ||||
| 			"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True) | ||||
| 
 | ||||
| 		self.assertTrue(frappe.db.get_value("GL Entry", | ||||
| 			{"voucher_type": "Journal Entry", "voucher_no": jv.name})) | ||||
| 
 | ||||
| 		budget.cancel() | ||||
| 		jv.cancel() | ||||
| 
 | ||||
| 	def test_monthly_budget_crossed_stop1(self): | ||||
| 		set_total_expense_zero("2013-02-28", "cost_center") | ||||
| 		set_total_expense_zero(nowdate(), "cost_center") | ||||
| 
 | ||||
| 		budget = make_budget(budget_against="Cost Center") | ||||
| 
 | ||||
| 		frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") | ||||
| 
 | ||||
| 		jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28") | ||||
| 			"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate()) | ||||
| 
 | ||||
| 		self.assertRaises(BudgetError, jv.submit) | ||||
| 
 | ||||
| @ -41,14 +42,14 @@ class TestBudget(unittest.TestCase): | ||||
| 		budget.cancel() | ||||
| 
 | ||||
| 	def test_exception_approver_role(self): | ||||
| 		set_total_expense_zero("2013-02-28", "cost_center") | ||||
| 		set_total_expense_zero(nowdate(), "cost_center") | ||||
| 
 | ||||
| 		budget = make_budget(budget_against="Cost Center") | ||||
| 
 | ||||
| 		frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") | ||||
| 
 | ||||
| 		jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-03-02") | ||||
| 			"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date=nowdate()) | ||||
| 
 | ||||
| 		self.assertRaises(BudgetError, jv.submit) | ||||
| 
 | ||||
| @ -112,16 +113,17 @@ class TestBudget(unittest.TestCase): | ||||
| 
 | ||||
| 		budget.load_from_db() | ||||
| 		budget.cancel() | ||||
| 		po.cancel() | ||||
| 
 | ||||
| 	def test_monthly_budget_crossed_stop2(self): | ||||
| 		set_total_expense_zero("2013-02-28", "project") | ||||
| 		set_total_expense_zero(nowdate(), "project") | ||||
| 
 | ||||
| 		budget = make_budget(budget_against="Project") | ||||
| 
 | ||||
| 		frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") | ||||
| 
 | ||||
| 		jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-02-28") | ||||
| 			"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate()) | ||||
| 
 | ||||
| 		self.assertRaises(BudgetError, jv.submit) | ||||
| 
 | ||||
| @ -129,86 +131,76 @@ class TestBudget(unittest.TestCase): | ||||
| 		budget.cancel() | ||||
| 
 | ||||
| 	def test_yearly_budget_crossed_stop1(self): | ||||
| 		set_total_expense_zero("2013-02-28", "cost_center") | ||||
| 		set_total_expense_zero(nowdate(), "cost_center") | ||||
| 
 | ||||
| 		budget = make_budget(budget_against="Cost Center") | ||||
| 
 | ||||
| 		jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", 150000, "_Test Cost Center - _TC", posting_date="2013-03-28") | ||||
| 			"_Test Bank - _TC", 250000, "_Test Cost Center - _TC", posting_date=nowdate()) | ||||
| 
 | ||||
| 		self.assertRaises(BudgetError, jv.submit) | ||||
| 
 | ||||
| 		budget.cancel() | ||||
| 
 | ||||
| 	def test_yearly_budget_crossed_stop2(self): | ||||
| 		set_total_expense_zero("2013-02-28", "project") | ||||
| 		set_total_expense_zero(nowdate(), "project") | ||||
| 
 | ||||
| 		budget = make_budget(budget_against="Project") | ||||
| 
 | ||||
| 		jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", 150000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-03-28") | ||||
| 			"_Test Bank - _TC", 250000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate()) | ||||
| 
 | ||||
| 		self.assertRaises(BudgetError, jv.submit) | ||||
| 
 | ||||
| 		budget.cancel() | ||||
| 
 | ||||
| 	def test_monthly_budget_on_cancellation1(self): | ||||
| 		set_total_expense_zero("2013-02-28", "cost_center") | ||||
| 		set_total_expense_zero(nowdate(), "cost_center") | ||||
| 
 | ||||
| 		budget = make_budget(budget_against="Cost Center") | ||||
| 
 | ||||
| 		jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) | ||||
| 		for i in range(now_datetime().month): | ||||
| 			jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 				"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True) | ||||
| 
 | ||||
| 		self.assertTrue(frappe.db.get_value("GL Entry", | ||||
| 			{"voucher_type": "Journal Entry", "voucher_no": jv1.name})) | ||||
| 
 | ||||
| 		jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) | ||||
| 
 | ||||
| 		self.assertTrue(frappe.db.get_value("GL Entry", | ||||
| 			{"voucher_type": "Journal Entry", "voucher_no": jv2.name})) | ||||
| 			self.assertTrue(frappe.db.get_value("GL Entry", | ||||
| 				{"voucher_type": "Journal Entry", "voucher_no": jv.name})) | ||||
| 
 | ||||
| 		frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") | ||||
| 
 | ||||
| 		self.assertRaises(BudgetError, jv1.cancel) | ||||
| 		self.assertRaises(BudgetError, jv.cancel) | ||||
| 
 | ||||
| 		budget.load_from_db() | ||||
| 		budget.cancel() | ||||
| 
 | ||||
| 	def test_monthly_budget_on_cancellation2(self): | ||||
| 		set_total_expense_zero("2013-02-28", "project") | ||||
| 		set_total_expense_zero(nowdate(), "project") | ||||
| 
 | ||||
| 		budget = make_budget(budget_against="Project") | ||||
| 
 | ||||
| 		jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True, project="_Test Project") | ||||
| 		for i in range(now_datetime().month): | ||||
| 			jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 				"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project") | ||||
| 
 | ||||
| 		self.assertTrue(frappe.db.get_value("GL Entry", | ||||
| 			{"voucher_type": "Journal Entry", "voucher_no": jv1.name})) | ||||
| 
 | ||||
| 		jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True, project="_Test Project") | ||||
| 
 | ||||
| 		self.assertTrue(frappe.db.get_value("GL Entry", | ||||
| 			{"voucher_type": "Journal Entry", "voucher_no": jv2.name})) | ||||
| 			self.assertTrue(frappe.db.get_value("GL Entry", | ||||
| 				{"voucher_type": "Journal Entry", "voucher_no": jv.name})) | ||||
| 
 | ||||
| 		frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") | ||||
| 
 | ||||
| 		self.assertRaises(BudgetError, jv1.cancel) | ||||
| 		self.assertRaises(BudgetError, jv.cancel) | ||||
| 
 | ||||
| 		budget.load_from_db() | ||||
| 		budget.cancel() | ||||
| 
 | ||||
| 	def test_monthly_budget_against_group_cost_center(self): | ||||
| 		set_total_expense_zero("2013-02-28", "cost_center") | ||||
| 		set_total_expense_zero("2013-02-28", "cost_center", "_Test Cost Center 2 - _TC") | ||||
| 		set_total_expense_zero(nowdate(), "cost_center") | ||||
| 		set_total_expense_zero(nowdate(), "cost_center", "_Test Cost Center 2 - _TC") | ||||
| 
 | ||||
| 		budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC") | ||||
| 		frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") | ||||
| 
 | ||||
| 		jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date="2013-02-28") | ||||
| 			"_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date=nowdate()) | ||||
| 
 | ||||
| 		self.assertRaises(BudgetError, jv.submit) | ||||
| 
 | ||||
| @ -231,7 +223,7 @@ class TestBudget(unittest.TestCase): | ||||
| 		frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") | ||||
| 
 | ||||
| 		jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", 40000, cost_center, posting_date="2013-02-28") | ||||
| 			"_Test Bank - _TC", 40000, cost_center, posting_date=nowdate()) | ||||
| 
 | ||||
| 		self.assertRaises(BudgetError, jv.submit) | ||||
| 
 | ||||
| @ -246,12 +238,14 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again | ||||
| 	else: | ||||
| 		budget_against = budget_against_CC or "_Test Cost Center - _TC" | ||||
| 
 | ||||
| 	fiscal_year = get_fiscal_year(nowdate())[0] | ||||
| 
 | ||||
| 	args = frappe._dict({ | ||||
| 		"account": "_Test Account Cost for Goods Sold - _TC", | ||||
| 		"cost_center": "_Test Cost Center - _TC", | ||||
| 		"monthly_end_date": posting_date, | ||||
| 		"company": "_Test Company", | ||||
| 		"fiscal_year": "_Test Fiscal Year 2013", | ||||
| 		"fiscal_year": fiscal_year, | ||||
| 		"budget_against_field": budget_against_field, | ||||
| 	}) | ||||
| 
 | ||||
| @ -263,10 +257,10 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again | ||||
| 	if existing_expense: | ||||
| 		if budget_against_field == "cost_center": | ||||
| 			make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) | ||||
| 			"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True) | ||||
| 		elif budget_against_field == "project": | ||||
| 			make_journal_entry("_Test Account Cost for Goods Sold - _TC", | ||||
| 			"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date="2013-02-28") | ||||
| 			"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date=nowdate()) | ||||
| 
 | ||||
| def make_budget(**args): | ||||
| 	args = frappe._dict(args) | ||||
| @ -274,10 +268,13 @@ def make_budget(**args): | ||||
| 	budget_against=args.budget_against | ||||
| 	cost_center=args.cost_center | ||||
| 
 | ||||
| 	fiscal_year = get_fiscal_year(nowdate())[0] | ||||
| 
 | ||||
| 	if budget_against == "Project": | ||||
| 		budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", "_Test Project/_Test Fiscal Year 2013%")}) | ||||
| 		project_name = "{0}%".format("_Test Project/" + fiscal_year) | ||||
| 		budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", project_name)}) | ||||
| 	else: | ||||
| 		cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/_Test Fiscal Year 2013") | ||||
| 		cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year) | ||||
| 		budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", cost_center_name)}) | ||||
| 	for d in budget_list: | ||||
| 		frappe.db.sql("delete from `tabBudget` where name = %(name)s", d) | ||||
| @ -290,8 +287,10 @@ def make_budget(**args): | ||||
| 	else: | ||||
| 		budget.cost_center =cost_center or "_Test Cost Center - _TC" | ||||
| 
 | ||||
| 	monthly_distribution = frappe.get_doc("Monthly Distribution", "_Test Distribution") | ||||
| 	monthly_distribution.fiscal_year = fiscal_year | ||||
| 
 | ||||
| 	budget.fiscal_year = "_Test Fiscal Year 2013" | ||||
| 	budget.fiscal_year = fiscal_year | ||||
| 	budget.monthly_distribution = "_Test Distribution" | ||||
| 	budget.company = "_Test Company" | ||||
| 	budget.applicable_on_booking_actual_expenses = 1 | ||||
| @ -300,7 +299,7 @@ def make_budget(**args): | ||||
| 	budget.budget_against = budget_against | ||||
| 	budget.append("accounts", { | ||||
| 		"account": "_Test Account Cost for Goods Sold - _TC", | ||||
| 		"budget_amount": 100000 | ||||
| 		"budget_amount": 200000 | ||||
| 	}) | ||||
| 
 | ||||
| 	if args.applicable_on_material_request: | ||||
|  | ||||
| @ -18,7 +18,7 @@ frappe.ui.form.on('Cost Center', { | ||||
| 	}, | ||||
| 	refresh: function(frm) { | ||||
| 		if (!frm.is_new()) { | ||||
| 			frm.add_custom_button(__('Update Cost Center Number'), function () { | ||||
| 			frm.add_custom_button(__('Update Cost Center Name / Number'), function () { | ||||
| 				frm.trigger("update_cost_center_number"); | ||||
| 			}); | ||||
| 		} | ||||
| @ -47,35 +47,45 @@ frappe.ui.form.on('Cost Center', { | ||||
| 	}, | ||||
| 	update_cost_center_number: function(frm) { | ||||
| 		var d = new frappe.ui.Dialog({ | ||||
| 			title: __('Update Cost Center Number'), | ||||
| 			title: __('Update Cost Center Name / Number'), | ||||
| 			fields: [ | ||||
| 				{ | ||||
| 					"label": 'Cost Center Number', | ||||
| 					"label": "Cost Center Name", | ||||
| 					"fieldname": "cost_center_name", | ||||
| 					"fieldtype": "Data", | ||||
| 					"reqd": 1, | ||||
| 					"default": frm.doc.cost_center_name | ||||
| 				}, | ||||
| 				{ | ||||
| 					"label": "Cost Center Number", | ||||
| 					"fieldname": "cost_center_number", | ||||
| 					"fieldtype": "Data", | ||||
| 					"reqd": 1 | ||||
| 					"reqd": 1, | ||||
| 					"default": frm.doc.cost_center_number | ||||
| 				} | ||||
| 			], | ||||
| 			primary_action: function() { | ||||
| 				var data = d.get_values(); | ||||
| 				if(data.cost_center_number === frm.doc.cost_center_number) { | ||||
| 				if(data.cost_center_name === frm.doc.cost_center_name && data.cost_center_number === frm.doc.cost_center_number) { | ||||
| 					d.hide(); | ||||
| 					return; | ||||
| 				} | ||||
| 				frappe.dom.freeze(); | ||||
| 				frappe.call({ | ||||
| 					method: "erpnext.accounts.utils.update_number_field", | ||||
| 					method: "erpnext.accounts.utils.update_cost_center", | ||||
| 					args: { | ||||
| 						doctype_name: frm.doc.doctype, | ||||
| 						name: frm.doc.name, | ||||
| 						field_name: d.fields[0].fieldname, | ||||
| 						number_value: data.cost_center_number, | ||||
| 						docname: frm.doc.name, | ||||
| 						cost_center_name: data.cost_center_name, | ||||
| 						cost_center_number: data.cost_center_number, | ||||
| 						company: frm.doc.company | ||||
| 					}, | ||||
| 					callback: function(r) { | ||||
| 						frappe.dom.unfreeze(); | ||||
| 						if(!r.exc) { | ||||
| 							if(r.message) { | ||||
| 								frappe.set_route("Form", "Cost Center", r.message); | ||||
| 							} else { | ||||
| 								me.frm.set_value("cost_center_name", data.cost_center_name); | ||||
| 								me.frm.set_value("cost_center_number", data.cost_center_number); | ||||
| 							} | ||||
| 							d.hide(); | ||||
|  | ||||
| @ -2,7 +2,6 @@ | ||||
|  "actions": [], | ||||
|  "allow_copy": 1, | ||||
|  "allow_import": 1, | ||||
|  "allow_rename": 1, | ||||
|  "creation": "2013-01-23 19:57:17", | ||||
|  "description": "Track separate Income and Expense for product verticals or divisions.", | ||||
|  "doctype": "DocType", | ||||
| @ -126,7 +125,7 @@ | ||||
|  "idx": 1, | ||||
|  "is_tree": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-03-18 17:59:04.321637", | ||||
|  "modified": "2020-04-29 16:09:30.025214", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Cost Center", | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "ACC-GLE-.YYYY.-.#####", | ||||
|  "creation": "2013-01-10 16:34:06", | ||||
|  "doctype": "DocType", | ||||
| @ -30,7 +31,8 @@ | ||||
|   "company", | ||||
|   "finance_book", | ||||
|   "to_rename", | ||||
|   "due_date" | ||||
|   "due_date", | ||||
|   "is_cancelled" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
| @ -245,12 +247,18 @@ | ||||
|    "fieldname": "due_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Due Date" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "is_cancelled", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Is Cancelled" | ||||
|   } | ||||
|  ], | ||||
|  "icon": "fa fa-list", | ||||
|  "idx": 1, | ||||
|  "in_create": 1, | ||||
|  "modified": "2020-03-28 16:22:33.766994", | ||||
|  "modified": "2020-04-07 16:22:33.766994", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "GL Entry", | ||||
|  | ||||
| @ -30,23 +30,20 @@ class GLEntry(Document): | ||||
| 		self.pl_must_have_cost_center() | ||||
| 		self.validate_cost_center() | ||||
| 
 | ||||
| 		if not self.flags.from_repost: | ||||
| 			self.check_pl_account() | ||||
| 			self.validate_party() | ||||
| 			self.validate_currency() | ||||
| 		self.check_pl_account() | ||||
| 		self.validate_party() | ||||
| 		self.validate_currency() | ||||
| 
 | ||||
| 	def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False): | ||||
| 		if not from_repost: | ||||
| 			self.validate_account_details(adv_adj) | ||||
| 			self.validate_dimensions_for_pl_and_bs() | ||||
| 			check_freezing_date(self.posting_date, adv_adj) | ||||
| 	def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'): | ||||
| 		self.validate_account_details(adv_adj) | ||||
| 		self.validate_dimensions_for_pl_and_bs() | ||||
| 
 | ||||
| 		validate_frozen_account(self.account, adv_adj) | ||||
| 		validate_balance_type(self.account, adv_adj) | ||||
| 
 | ||||
| 		# Update outstanding amt on against voucher | ||||
| 		if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \ | ||||
| 			and self.against_voucher and update_outstanding == 'Yes' and not from_repost: | ||||
| 			and self.against_voucher and update_outstanding == 'Yes': | ||||
| 				update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type, | ||||
| 					self.against_voucher) | ||||
| 
 | ||||
| @ -159,7 +156,6 @@ class GLEntry(Document): | ||||
| 		if self.party_type and self.party: | ||||
| 			validate_party_gle_currency(self.party_type, self.party, self.company, self.account_currency) | ||||
| 
 | ||||
| 
 | ||||
| 	def validate_and_set_fiscal_year(self): | ||||
| 		if not self.fiscal_year: | ||||
| 			self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0] | ||||
| @ -176,19 +172,6 @@ def validate_balance_type(account, adv_adj=False): | ||||
| 				(balance_must_be=="Credit" and flt(balance) > 0): | ||||
| 				frappe.throw(_("Balance for Account {0} must always be {1}").format(account, _(balance_must_be))) | ||||
| 
 | ||||
| def check_freezing_date(posting_date, adv_adj=False): | ||||
| 	""" | ||||
| 		Nobody can do GL Entries where posting date is before freezing date | ||||
| 		except authorized person | ||||
| 	""" | ||||
| 	if not adv_adj: | ||||
| 		acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') | ||||
| 		if acc_frozen_upto: | ||||
| 			frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier') | ||||
| 			if getdate(posting_date) <= getdate(acc_frozen_upto) \ | ||||
| 					and not frozen_accounts_modifier in frappe.get_roles(): | ||||
| 				frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto))) | ||||
| 
 | ||||
| def update_outstanding_amt(account, party_type, party, against_voucher_type, against_voucher, on_cancel=False): | ||||
| 	if party_type and party: | ||||
| 		party_condition = " and party_type={0} and party={1}"\ | ||||
|  | ||||
| @ -12,7 +12,6 @@ frappe.ui.form.on("Journal Entry", { | ||||
| 
 | ||||
| 	refresh: function(frm) { | ||||
| 		erpnext.toggle_naming_series(); | ||||
| 		frm.cscript.voucher_type(frm.doc); | ||||
| 
 | ||||
| 		if(frm.doc.docstatus==1) { | ||||
| 			frm.add_custom_button(__('Ledger'), function() { | ||||
| @ -120,9 +119,78 @@ frappe.ui.form.on("Journal Entry", { | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	voucher_type: function(frm){ | ||||
| 
 | ||||
| 		if(!frm.doc.company) return null; | ||||
| 
 | ||||
| 		if((!(frm.doc.accounts || []).length) || ((frm.doc.accounts || []).length === 1 && !frm.doc.accounts[0].account)) { | ||||
| 			if(in_list(["Bank Entry", "Cash Entry"], frm.doc.voucher_type)) { | ||||
| 				return frappe.call({ | ||||
| 					type: "GET", | ||||
| 					method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account", | ||||
| 					args: { | ||||
| 						"account_type": (frm.doc.voucher_type=="Bank Entry" ? | ||||
| 							"Bank" : (frm.doc.voucher_type=="Cash Entry" ? "Cash" : null)), | ||||
| 						"company": frm.doc.company | ||||
| 					}, | ||||
| 					callback: function(r) { | ||||
| 						if(r.message) { | ||||
| 							// If default company bank account not set
 | ||||
| 							if(!$.isEmptyObject(r.message)){ | ||||
| 								update_jv_details(frm.doc, [r.message]); | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				}); | ||||
| 			} | ||||
| 			else if(frm.doc.voucher_type=="Opening Entry") { | ||||
| 				return frappe.call({ | ||||
| 					type:"GET", | ||||
| 					method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts", | ||||
| 					args: { | ||||
| 						"company": frm.doc.company | ||||
| 					}, | ||||
| 					callback: function(r) { | ||||
| 						frappe.model.clear_table(frm.doc, "accounts"); | ||||
| 						if(r.message) { | ||||
| 							update_jv_details(frm.doc, r.message); | ||||
| 						} | ||||
| 						cur_frm.set_value("is_opening", "Yes"); | ||||
| 					} | ||||
| 				}); | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	from_template: function(frm){ | ||||
| 		if (frm.doc.from_template){ | ||||
| 			frappe.db.get_doc("Journal Entry Template", frm.doc.from_template) | ||||
| 				.then((doc) => { | ||||
| 					frappe.model.clear_table(frm.doc, "accounts"); | ||||
| 					frm.set_value({ | ||||
| 						"company": doc.company, | ||||
| 						"voucher_type": doc.voucher_type, | ||||
| 						"naming_series": doc.naming_series, | ||||
| 						"is_opening": doc.is_opening, | ||||
| 						"multi_currency": doc.multi_currency | ||||
| 					}) | ||||
| 					update_jv_details(frm.doc, doc.accounts); | ||||
| 				}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| var update_jv_details = function(doc, r) { | ||||
| 	$.each(r, function(i, d) { | ||||
| 		var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts"); | ||||
| 		row.account = d.account; | ||||
| 		row.balance = d.balance; | ||||
| 	}); | ||||
| 	refresh_field("accounts"); | ||||
| } | ||||
| 
 | ||||
| erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({ | ||||
| 	onload: function() { | ||||
| 		this.load_defaults(); | ||||
| @ -375,56 +443,6 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){ | ||||
| 		cur_frm.pformat.print_heading = __("Journal Entry"); | ||||
| } | ||||
| 
 | ||||
| cur_frm.cscript.voucher_type = function(doc, cdt, cdn) { | ||||
| 	cur_frm.set_df_property("cheque_no", "reqd", doc.voucher_type=="Bank Entry"); | ||||
| 	cur_frm.set_df_property("cheque_date", "reqd", doc.voucher_type=="Bank Entry"); | ||||
| 
 | ||||
| 	if(!doc.company) return; | ||||
| 
 | ||||
| 	var update_jv_details = function(doc, r) { | ||||
| 		$.each(r, function(i, d) { | ||||
| 			var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts"); | ||||
| 			row.account = d.account; | ||||
| 			row.balance = d.balance; | ||||
| 		}); | ||||
| 		refresh_field("accounts"); | ||||
| 	} | ||||
| 
 | ||||
| 	if((!(doc.accounts || []).length) || ((doc.accounts || []).length==1 && !doc.accounts[0].account)) { | ||||
| 		if(in_list(["Bank Entry", "Cash Entry"], doc.voucher_type)) { | ||||
| 			return frappe.call({ | ||||
| 				type: "GET", | ||||
| 				method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account", | ||||
| 				args: { | ||||
| 					"account_type": (doc.voucher_type=="Bank Entry" ? | ||||
| 						"Bank" : (doc.voucher_type=="Cash Entry" ? "Cash" : null)), | ||||
| 					"company": doc.company | ||||
| 				}, | ||||
| 				callback: function(r) { | ||||
| 					if(r.message) { | ||||
| 						update_jv_details(doc, [r.message]); | ||||
| 					} | ||||
| 				} | ||||
| 			}) | ||||
| 		} else if(doc.voucher_type=="Opening Entry") { | ||||
| 			return frappe.call({ | ||||
| 				type:"GET", | ||||
| 				method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts", | ||||
| 				args: { | ||||
| 					"company": doc.company | ||||
| 				}, | ||||
| 				callback: function(r) { | ||||
| 					frappe.model.clear_table(doc, "accounts"); | ||||
| 					if(r.message) { | ||||
| 						update_jv_details(doc, r.message); | ||||
| 					} | ||||
| 					cur_frm.set_value("is_opening", "Yes") | ||||
| 				} | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| frappe.ui.form.on("Journal Entry Account", { | ||||
| 	party: function(frm, cdt, cdn) { | ||||
| 		var d = frappe.get_doc(cdt, cdn); | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "allow_import": 1, | ||||
|  "autoname": "naming_series:", | ||||
|  "creation": "2013-03-25 10:53:52", | ||||
| @ -10,10 +11,11 @@ | ||||
|   "title", | ||||
|   "voucher_type", | ||||
|   "naming_series", | ||||
|   "column_break1", | ||||
|   "posting_date", | ||||
|   "company", | ||||
|   "finance_book", | ||||
|   "column_break1", | ||||
|   "from_template", | ||||
|   "company", | ||||
|   "posting_date", | ||||
|   "2_add_edit_gl_entries", | ||||
|   "accounts", | ||||
|   "section_break99", | ||||
| @ -157,6 +159,7 @@ | ||||
|    "in_global_search": 1, | ||||
|    "in_list_view": 1, | ||||
|    "label": "Reference Number", | ||||
|    "mandatory_depends_on": "eval:doc.voucher_type == \"Bank Entry\"", | ||||
|    "no_copy": 1, | ||||
|    "oldfieldname": "cheque_no", | ||||
|    "oldfieldtype": "Data", | ||||
| @ -166,6 +169,7 @@ | ||||
|    "fieldname": "cheque_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Reference Date", | ||||
|    "mandatory_depends_on": "eval:doc.voucher_type == \"Bank Entry\"", | ||||
|    "no_copy": 1, | ||||
|    "oldfieldname": "cheque_date", | ||||
|    "oldfieldtype": "Date", | ||||
| @ -484,12 +488,22 @@ | ||||
|    "options": "Journal Entry", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "from_template", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "From Template", | ||||
|    "no_copy": 1, | ||||
|    "options": "Journal Entry Template", | ||||
|    "print_hide": 1, | ||||
|    "report_hide": 1 | ||||
|   } | ||||
|  ], | ||||
|  "icon": "fa fa-file-text", | ||||
|  "idx": 176, | ||||
|  "is_submittable": 1, | ||||
|  "modified": "2020-01-16 13:05:30.634226", | ||||
|  "links": [], | ||||
|  "modified": "2020-04-29 10:55:28.240916", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Journal Entry", | ||||
|  | ||||
| @ -57,6 +57,7 @@ class JournalEntry(AccountsController): | ||||
| 		from erpnext.hr.doctype.salary_slip.salary_slip import unlink_ref_doc_from_salary_slip | ||||
| 		unlink_ref_doc_from_payment_entries(self) | ||||
| 		unlink_ref_doc_from_salary_slip(self.name) | ||||
| 		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') | ||||
| 		self.make_gl_entries(1) | ||||
| 		self.update_advance_paid() | ||||
| 		self.update_expense_claim() | ||||
| @ -559,20 +560,20 @@ class JournalEntry(AccountsController): | ||||
| 
 | ||||
| 			if self.write_off_based_on == 'Accounts Receivable': | ||||
| 				jd1.party_type = "Customer" | ||||
| 				jd1.credit = flt(d.outstanding_amount, self.precision("credit", "accounts")) | ||||
| 				jd1.credit_in_account_currency = flt(d.outstanding_amount, self.precision("credit", "accounts")) | ||||
| 				jd1.reference_type = "Sales Invoice" | ||||
| 				jd1.reference_name = cstr(d.name) | ||||
| 			elif self.write_off_based_on == 'Accounts Payable': | ||||
| 				jd1.party_type = "Supplier" | ||||
| 				jd1.debit = flt(d.outstanding_amount, self.precision("debit", "accounts")) | ||||
| 				jd1.debit_in_account_currency = flt(d.outstanding_amount, self.precision("debit", "accounts")) | ||||
| 				jd1.reference_type = "Purchase Invoice" | ||||
| 				jd1.reference_name = cstr(d.name) | ||||
| 
 | ||||
| 		jd2 = self.append('accounts', {}) | ||||
| 		if self.write_off_based_on == 'Accounts Receivable': | ||||
| 			jd2.debit = total | ||||
| 			jd2.debit_in_account_currency = total | ||||
| 		elif self.write_off_based_on == 'Accounts Payable': | ||||
| 			jd2.credit = total | ||||
| 			jd2.credit_in_account_currency = total | ||||
| 
 | ||||
| 		self.validate_total_debit_and_credit() | ||||
| 
 | ||||
| @ -594,7 +595,7 @@ class JournalEntry(AccountsController): | ||||
| 		for d in self.accounts: | ||||
| 			if d.reference_type=="Expense Claim" and d.reference_name: | ||||
| 				doc = frappe.get_doc("Expense Claim", d.reference_name) | ||||
| 				update_reimbursed_amount(doc) | ||||
| 				update_reimbursed_amount(doc, jv=self.name) | ||||
| 
 | ||||
| 
 | ||||
| 	def validate_expense_claim(self): | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "hash", | ||||
|  "creation": "2013-02-22 01:27:39", | ||||
|  "doctype": "DocType", | ||||
| @ -271,7 +272,8 @@ | ||||
|  ], | ||||
|  "idx": 1, | ||||
|  "istable": 1, | ||||
|  "modified": "2020-01-13 12:41:33.968025", | ||||
|  "links": [], | ||||
|  "modified": "2020-04-25 01:47:49.060128", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Journal Entry Account", | ||||
| @ -280,4 +282,4 @@ | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| } | ||||
| @ -0,0 +1,91 @@ | ||||
| // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| 
 | ||||
| frappe.ui.form.on("Journal Entry Template", { | ||||
| 	setup: function(frm) { | ||||
| 		frappe.model.set_default_values(frm.doc); | ||||
| 
 | ||||
| 		frm.set_query("account" ,"accounts", function(){ | ||||
| 			var filters = { | ||||
| 				company: frm.doc.company, | ||||
| 				is_group: 0 | ||||
| 			}; | ||||
| 
 | ||||
| 			if(!frm.doc.multi_currency) { | ||||
| 				$.extend(filters, { | ||||
| 					account_currency: frappe.get_doc(":Company", frm.doc.company).default_currency | ||||
| 				}); | ||||
| 			} | ||||
| 
 | ||||
| 			return { filters: filters }; | ||||
| 		}); | ||||
| 
 | ||||
| 		frappe.call({ | ||||
| 			type: "GET", | ||||
| 			method: "erpnext.accounts.doctype.journal_entry_template.journal_entry_template.get_naming_series", | ||||
| 			callback: function(r){ | ||||
| 				if(r.message){ | ||||
| 					frm.set_df_property("naming_series", "options", r.message.split("\n")); | ||||
| 					frm.set_value("naming_series", r.message.split("\n")[0]); | ||||
| 					frm.refresh_field("naming_series"); | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 	}, | ||||
| 	voucher_type: function(frm) { | ||||
| 		var add_accounts = function(doc, r) { | ||||
| 			$.each(r, function(i, d) { | ||||
| 				var row = frappe.model.add_child(doc, "Journal Entry Template Account", "accounts"); | ||||
| 				row.account = d.account; | ||||
| 			}); | ||||
| 			refresh_field("accounts"); | ||||
| 		}; | ||||
| 
 | ||||
| 		if(!frm.doc.company) return; | ||||
| 
 | ||||
| 		frm.trigger("clear_child"); | ||||
| 		switch(frm.doc.voucher_type){ | ||||
| 			case "Opening Entry": | ||||
| 				frm.set_value("is_opening", "Yes"); | ||||
| 				frappe.call({ | ||||
| 					type:"GET", | ||||
| 					method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts", | ||||
| 					args: { | ||||
| 						"company": frm.doc.company | ||||
| 					}, | ||||
| 					callback: function(r) { | ||||
| 						if(r.message) { | ||||
| 							add_accounts(frm.doc, r.message); | ||||
| 						} | ||||
| 					} | ||||
| 				}); | ||||
| 				break; | ||||
| 			case "Bank Entry": | ||||
| 			case "Cash Entry": | ||||
| 				frappe.call({ | ||||
| 					type: "GET", | ||||
| 					method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account", | ||||
| 					args: { | ||||
| 						"account_type": (frm.doc.voucher_type=="Bank Entry" ? | ||||
| 							"Bank" : (frm.doc.voucher_type=="Cash Entry" ? "Cash" : null)), | ||||
| 						"company": frm.doc.company | ||||
| 					}, | ||||
| 					callback: function(r) { | ||||
| 						if(r.message) { | ||||
| 							// If default company bank account not set
 | ||||
| 							if(!$.isEmptyObject(r.message)){ | ||||
| 								add_accounts(frm.doc, [r.message]); | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				}); | ||||
| 				break; | ||||
| 			default: | ||||
| 				frm.trigger("clear_child"); | ||||
| 		} | ||||
| 	}, | ||||
| 	clear_child: function(frm){ | ||||
| 		frappe.model.clear_table(frm.doc, "accounts"); | ||||
| 		frm.refresh_field("accounts"); | ||||
| 	} | ||||
| }); | ||||
| @ -0,0 +1,134 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "field:template_title", | ||||
|  "creation": "2020-04-09 01:32:51.332301", | ||||
|  "doctype": "DocType", | ||||
|  "document_type": "Document", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "section_break_1", | ||||
|   "template_title", | ||||
|   "voucher_type", | ||||
|   "naming_series", | ||||
|   "column_break_3", | ||||
|   "company", | ||||
|   "is_opening", | ||||
|   "multi_currency", | ||||
|   "section_break_3", | ||||
|   "accounts" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "section_break_1", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "voucher_type", | ||||
|    "fieldtype": "Select", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Journal Entry Type", | ||||
|    "options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nExchange Rate Revaluation", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "company", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "in_standard_filter": 1, | ||||
|    "label": "Company", | ||||
|    "options": "Company", | ||||
|    "remember_last_selected_value": 1, | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_3", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "default": "No", | ||||
|    "fieldname": "is_opening", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Is Opening", | ||||
|    "options": "No\nYes" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "accounts", | ||||
|    "fieldtype": "Table", | ||||
|    "label": "Accounting Entries", | ||||
|    "options": "Journal Entry Template Account" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "naming_series", | ||||
|    "fieldtype": "Select", | ||||
|    "label": "Series", | ||||
|    "no_copy": 1, | ||||
|    "print_hide": 1, | ||||
|    "reqd": 1, | ||||
|    "set_only_once": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "template_title", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "Template Title", | ||||
|    "reqd": 1, | ||||
|    "unique": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_3", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "multi_currency", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Multi Currency" | ||||
|   } | ||||
|  ], | ||||
|  "links": [], | ||||
|  "modified": "2020-05-01 18:32:01.420488", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Journal Entry Template", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Accounts User", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Accounts Manager", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Auditor", | ||||
|    "share": 1 | ||||
|   } | ||||
|  ], | ||||
|  "search_fields": "voucher_type, company", | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "title_field": "template_title", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class JournalEntryTemplate(Document): | ||||
| 	pass | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_naming_series(): | ||||
| 	return frappe.get_meta("Journal Entry").get_field("naming_series").options | ||||
| @ -0,0 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| # import frappe | ||||
| import unittest | ||||
| 
 | ||||
| class TestJournalEntryTemplate(unittest.TestCase): | ||||
| 	pass | ||||
| @ -0,0 +1,31 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "creation": "2020-04-09 01:48:42.783620", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "account" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "account", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Account", | ||||
|    "options": "Account", | ||||
|    "reqd": 1 | ||||
|   } | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-04-25 01:15:44.879839", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Journal Entry Template Account", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -1,13 +1,10 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| # import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class Video(Document): | ||||
| 
 | ||||
| 
 | ||||
| 	def get_video(self): | ||||
| 		pass | ||||
| class JournalEntryTemplateAccount(Document): | ||||
| 	pass | ||||
| @ -60,6 +60,7 @@ class PaymentEntry(AccountsController): | ||||
| 		self.set_remarks() | ||||
| 		self.validate_duplicate_entry() | ||||
| 		self.validate_allocated_amount() | ||||
| 		self.validate_paid_invoices() | ||||
| 		self.ensure_supplier_is_not_blocked() | ||||
| 		self.set_status() | ||||
| 
 | ||||
| @ -75,6 +76,7 @@ class PaymentEntry(AccountsController): | ||||
| 		self.set_status() | ||||
| 
 | ||||
| 	def on_cancel(self): | ||||
| 		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') | ||||
| 		self.setup_party_account_field() | ||||
| 		self.make_gl_entries(cancel=1) | ||||
| 		self.update_outstanding_amounts() | ||||
| @ -226,6 +228,8 @@ class PaymentEntry(AccountsController): | ||||
| 			valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry") | ||||
| 		elif self.party_type == "Employee": | ||||
| 			valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance") | ||||
| 		elif self.party_type == "Shareholder": | ||||
| 			valid_reference_doctypes = ("Journal Entry") | ||||
| 
 | ||||
| 		for d in self.get("references"): | ||||
| 			if not d.allocated_amount: | ||||
| @ -265,6 +269,25 @@ class PaymentEntry(AccountsController): | ||||
| 						frappe.throw(_("{0} {1} must be submitted") | ||||
| 							.format(d.reference_doctype, d.reference_name)) | ||||
| 
 | ||||
| 	def validate_paid_invoices(self): | ||||
| 		no_oustanding_refs = {} | ||||
| 
 | ||||
| 		for d in self.get("references"): | ||||
| 			if not d.allocated_amount: | ||||
| 				continue | ||||
| 
 | ||||
| 			if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Fees"): | ||||
| 				outstanding_amount, is_return = frappe.get_cached_value(d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"]) | ||||
| 				if outstanding_amount <= 0 and not is_return: | ||||
| 					no_oustanding_refs.setdefault(d.reference_doctype, []).append(d) | ||||
| 		 | ||||
| 		for k, v in no_oustanding_refs.items(): | ||||
| 			frappe.msgprint(_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.<br><br>\ | ||||
| 					If this is undesirable please cancel the corresponding Payment Entry.") | ||||
| 				.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount")), | ||||
| 				title=_("Warning"), indicator="orange") | ||||
| 
 | ||||
| 
 | ||||
| 	def validate_journal_entry(self): | ||||
| 		for d in self.get("references"): | ||||
| 			if d.allocated_amount and d.reference_doctype == "Journal Entry": | ||||
| @ -571,7 +594,7 @@ class PaymentEntry(AccountsController): | ||||
| 			for d in self.get("references"): | ||||
| 				if d.reference_doctype=="Expense Claim" and d.reference_name: | ||||
| 					doc = frappe.get_doc("Expense Claim", d.reference_name) | ||||
| 					update_reimbursed_amount(doc) | ||||
| 					update_reimbursed_amount(doc, self.name) | ||||
| 
 | ||||
| 	def on_recurring(self, reference_doc, auto_repeat_doc): | ||||
| 		self.reference_no = reference_doc.name | ||||
|  | ||||
							
								
								
									
										12
									
								
								erpnext/accounts/doctype/payment_entry/payment_entry_list.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								erpnext/accounts/doctype/payment_entry/payment_entry_list.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| frappe.listview_settings['Payment Entry'] = { | ||||
| 
 | ||||
| 	onload: function(listview) { | ||||
| 		listview.page.fields_dict.party_type.get_query = function() { | ||||
| 			return { | ||||
| 				"filters": { | ||||
| 					"name": ["in", Object.keys(frappe.boot.party_account_types)], | ||||
| 				} | ||||
| 			}; | ||||
| 		}; | ||||
| 	} | ||||
| }; | ||||
| @ -35,8 +35,6 @@ class TestPaymentEntry(unittest.TestCase): | ||||
| 
 | ||||
| 		pe.cancel() | ||||
| 
 | ||||
| 		self.assertFalse(self.get_gle(pe.name)) | ||||
| 
 | ||||
| 		so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid") | ||||
| 		self.assertEqual(so_advance_paid, 0) | ||||
| 
 | ||||
| @ -124,7 +122,6 @@ class TestPaymentEntry(unittest.TestCase): | ||||
| 		self.assertEqual(outstanding_amount, 0) | ||||
| 
 | ||||
| 		pe.cancel() | ||||
| 		self.assertFalse(self.get_gle(pe.name)) | ||||
| 
 | ||||
| 		outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) | ||||
| 		self.assertEqual(outstanding_amount, 100) | ||||
| @ -381,7 +378,6 @@ class TestPaymentEntry(unittest.TestCase): | ||||
| 		self.assertEqual(outstanding_amount, 0) | ||||
| 
 | ||||
| 		pe3.cancel() | ||||
| 		self.assertFalse(self.get_gle(pe3.name)) | ||||
| 
 | ||||
| 		outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount")) | ||||
| 		self.assertEqual(outstanding_amount, -100) | ||||
|  | ||||
| @ -5,7 +5,7 @@ frappe.ui.form.on('Period Closing Voucher', { | ||||
| 	onload: function(frm) { | ||||
| 		if (!frm.doc.transaction_date) frm.doc.transaction_date = frappe.datetime.obj_to_str(new Date()); | ||||
| 	}, | ||||
| 	 | ||||
| 
 | ||||
| 	setup: function(frm) { | ||||
| 		frm.set_query("closing_account_head", function() { | ||||
| 			return { | ||||
| @ -18,9 +18,9 @@ frappe.ui.form.on('Period Closing Voucher', { | ||||
| 			} | ||||
| 		}); | ||||
| 	}, | ||||
| 	 | ||||
| 
 | ||||
| 	refresh: function(frm) { | ||||
| 		if(frm.doc.docstatus==1) { | ||||
| 		if(frm.doc.docstatus > 0) { | ||||
| 			frm.add_custom_button(__('Ledger'), function() { | ||||
| 				frappe.route_options = { | ||||
| 					"voucher_no": frm.doc.name, | ||||
| @ -33,5 +33,5 @@ frappe.ui.form.on('Period Closing Voucher', { | ||||
| 			}, "fa fa-table"); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| }) | ||||
|  | ||||
| @ -7,6 +7,8 @@ from frappe.utils import flt | ||||
| from frappe import _ | ||||
| from erpnext.accounts.utils import get_account_currency | ||||
| from erpnext.controllers.accounts_controller import AccountsController | ||||
| from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions, | ||||
| 	get_dimension_filters) | ||||
| 
 | ||||
| class PeriodClosingVoucher(AccountsController): | ||||
| 	def validate(self): | ||||
| @ -17,8 +19,9 @@ class PeriodClosingVoucher(AccountsController): | ||||
| 		self.make_gl_entries() | ||||
| 
 | ||||
| 	def on_cancel(self): | ||||
| 		frappe.db.sql("""delete from `tabGL Entry` | ||||
| 			where voucher_type = 'Period Closing Voucher' and voucher_no=%s""", self.name) | ||||
| 		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') | ||||
| 		from erpnext.accounts.general_ledger import make_reverse_gl_entries | ||||
| 		make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name) | ||||
| 
 | ||||
| 	def validate_account_head(self): | ||||
| 		closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type") | ||||
| @ -49,7 +52,15 @@ class PeriodClosingVoucher(AccountsController): | ||||
| 	def make_gl_entries(self): | ||||
| 		gl_entries = [] | ||||
| 		net_pl_balance = 0 | ||||
| 		pl_accounts = self.get_pl_balances() | ||||
| 		dimension_fields = ['t1.cost_center'] | ||||
| 
 | ||||
| 		accounting_dimensions = get_accounting_dimensions() | ||||
| 		for dimension in accounting_dimensions: | ||||
| 			dimension_fields.append('t1.{0}'.format(dimension)) | ||||
| 
 | ||||
| 		dimension_filters, default_dimensions = get_dimension_filters() | ||||
| 
 | ||||
| 		pl_accounts = self.get_pl_balances(dimension_fields) | ||||
| 
 | ||||
| 		for acc in pl_accounts: | ||||
| 			if flt(acc.balance_in_company_currency): | ||||
| @ -65,34 +76,41 @@ class PeriodClosingVoucher(AccountsController): | ||||
| 						if flt(acc.balance_in_account_currency) > 0 else 0, | ||||
| 					"credit": abs(flt(acc.balance_in_company_currency)) \ | ||||
| 						if flt(acc.balance_in_company_currency) > 0 else 0 | ||||
| 				})) | ||||
| 				}, item=acc)) | ||||
| 
 | ||||
| 				net_pl_balance += flt(acc.balance_in_company_currency) | ||||
| 
 | ||||
| 		if net_pl_balance: | ||||
| 			cost_center = frappe.db.get_value("Company", self.company, "cost_center") | ||||
| 			gl_entries.append(self.get_gl_dict({ | ||||
| 			gl_entry = self.get_gl_dict({ | ||||
| 				"account": self.closing_account_head, | ||||
| 				"debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0, | ||||
| 				"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0, | ||||
| 				"credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0, | ||||
| 				"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0, | ||||
| 				"cost_center": cost_center | ||||
| 			})) | ||||
| 			}) | ||||
| 
 | ||||
| 			for dimension in accounting_dimensions: | ||||
| 				gl_entry.update({ | ||||
| 					dimension: default_dimensions.get(self.company, {}).get(dimension) | ||||
| 				}) | ||||
| 
 | ||||
| 			gl_entries.append(gl_entry) | ||||
| 
 | ||||
| 		from erpnext.accounts.general_ledger import make_gl_entries | ||||
| 		make_gl_entries(gl_entries) | ||||
| 
 | ||||
| 	def get_pl_balances(self): | ||||
| 	def get_pl_balances(self, dimension_fields): | ||||
| 		"""Get balance for pl accounts""" | ||||
| 		return frappe.db.sql(""" | ||||
| 			select | ||||
| 				t1.account, t1.cost_center, t2.account_currency, | ||||
| 				t1.account, t2.account_currency, {dimension_fields}, | ||||
| 				sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency, | ||||
| 				sum(t1.debit) - sum(t1.credit) as balance_in_company_currency | ||||
| 			from `tabGL Entry` t1, `tabAccount` t2 | ||||
| 			where t1.account = t2.name and t2.report_type = 'Profit and Loss' | ||||
| 			and t2.docstatus < 2 and t2.company = %s | ||||
| 			and t1.posting_date between %s and %s | ||||
| 			group by t1.account, t1.cost_center | ||||
| 		""", (self.company, self.get("year_start_date"), self.posting_date), as_dict=1) | ||||
| 			group by t1.account, {dimension_fields} | ||||
| 		""".format(dimension_fields = ', '.join(dimension_fields)), (self.company, self.get("year_start_date"), self.posting_date), as_dict=1) | ||||
|  | ||||
| @ -1,123 +1,39 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_guest_to_view": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "beta": 0,  | ||||
|  "creation": "2017-10-27 16:46:06.060930",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "",  | ||||
|  "editable_grid": 1,  | ||||
|  "engine": "InnoDB",  | ||||
|  "actions": [], | ||||
|  "creation": "2017-10-27 16:46:06.060930", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "default", | ||||
|   "user" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "default",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "Default",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|    "default": "0", | ||||
|    "fieldname": "default", | ||||
|    "fieldtype": "Check", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Default" | ||||
|   }, | ||||
|   { | ||||
|    "allow_bulk_edit": 0,  | ||||
|    "allow_on_submit": 0,  | ||||
|    "bold": 0,  | ||||
|    "collapsible": 0,  | ||||
|    "columns": 0,  | ||||
|    "fieldname": "user",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "ignore_xss_filter": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_global_search": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "in_standard_filter": 0,  | ||||
|    "label": "User",  | ||||
|    "length": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "options": "User",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "print_hide_if_no_value": 0,  | ||||
|    "read_only": 0,  | ||||
|    "remember_last_selected_value": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|    "fieldname": "user", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "User", | ||||
|    "options": "User" | ||||
|   } | ||||
|  ],  | ||||
|  "has_web_view": 0,  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "idx": 0,  | ||||
|  "image_view": 0,  | ||||
|  "in_create": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 0,  | ||||
|  "max_attachments": 0,  | ||||
|  "modified": "2017-11-23 17:13:16.005475",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Accounts",  | ||||
|  "name": "POS Profile User",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "apply_user_permissions": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 1,  | ||||
|    "delete": 1,  | ||||
|    "email": 1,  | ||||
|    "export": 1,  | ||||
|    "if_owner": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 1,  | ||||
|    "read": 1,  | ||||
|    "report": 1,  | ||||
|    "role": "System Manager",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "share": 1,  | ||||
|    "submit": 0,  | ||||
|    "write": 1 | ||||
|   } | ||||
|  ],  | ||||
|  "quick_entry": 1,  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "show_name_in_global_search": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC",  | ||||
|  "track_changes": 1,  | ||||
|  "track_seen": 0 | ||||
|  ], | ||||
|  "istable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-05-01 09:46:47.599173", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "POS Profile User", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [], | ||||
|  "quick_entry": 1, | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -0,0 +1,39 @@ | ||||
| // Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| 
 | ||||
| frappe.ui.form.on('Process Deferred Accounting', { | ||||
| 	setup: function(frm) { | ||||
| 		frm.set_query("document_type", function() { | ||||
| 			return { | ||||
| 				filters: { | ||||
| 					'name': ['in', ['Sales Invoice', 'Purchase Invoice']] | ||||
| 				} | ||||
| 			}; | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	validate: function() { | ||||
| 		return new Promise((resolve) => { | ||||
| 			return frappe.db.get_single_value('Accounts Settings', 'automatically_process_deferred_accounting_entry') | ||||
| 				.then(value => { | ||||
| 					if(value) { | ||||
| 						frappe.throw(__('Manual entry cannot be created! Disable automatic entry for deferred accounting in accounts settings and try again')); | ||||
| 					} | ||||
| 					resolve(value); | ||||
| 				}); | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	end_date: function(frm) { | ||||
| 		if (frm.doc.end_date && frm.doc.end_date < frm.doc.start_date) { | ||||
| 			frappe.throw(__("End date cannot be before start date")); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	onload: function(frm) { | ||||
| 		if (frm.doc.posting_date && frm.doc.docstatus === 0) { | ||||
| 			frm.set_value('start_date', frappe.datetime.add_months(frm.doc.posting_date, -1)); | ||||
| 			frm.set_value('end_date', frm.doc.posting_date); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| @ -0,0 +1,128 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "ACC-PDA-.#####", | ||||
|  "creation": "2019-11-04 18:01:23.454775", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "company", | ||||
|   "type", | ||||
|   "account", | ||||
|   "column_break_3", | ||||
|   "posting_date", | ||||
|   "start_date", | ||||
|   "end_date", | ||||
|   "amended_from" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "type", | ||||
|    "fieldtype": "Select", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Type", | ||||
|    "options": "\nIncome\nExpense", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "amended_from", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Amended From", | ||||
|    "no_copy": 1, | ||||
|    "options": "Process Deferred Accounting", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "start_date", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Service Start Date", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "end_date", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Service End Date", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_3", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "default": "Today", | ||||
|    "fieldname": "posting_date", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Posting Date", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "account", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Account", | ||||
|    "options": "Account" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "company", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Company", | ||||
|    "options": "Company", | ||||
|    "reqd": 1 | ||||
|   } | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-02-06 18:18:09.852844", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Process Deferred Accounting", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "cancel": 1, | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "System Manager", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "cancel": 1, | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Accounts Manager", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Accounts User", | ||||
|    "share": 1, | ||||
|    "submit": 1, | ||||
|    "write": 1 | ||||
|   } | ||||
|  ], | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC" | ||||
| } | ||||
| @ -0,0 +1,34 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| import erpnext | ||||
| from frappe import _ | ||||
| from frappe.model.document import Document | ||||
| from erpnext.accounts.general_ledger import make_reverse_gl_entries | ||||
| from erpnext.accounts.deferred_revenue import convert_deferred_expense_to_expense, \ | ||||
| 	convert_deferred_revenue_to_income, build_conditions | ||||
| 
 | ||||
| class ProcessDeferredAccounting(Document): | ||||
| 	def validate(self): | ||||
| 		if self.end_date < self.start_date: | ||||
| 			frappe.throw(_("End date cannot be before start date")) | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		conditions = build_conditions(self.type, self.account, self.company) | ||||
| 		if self.type == 'Income': | ||||
| 			convert_deferred_revenue_to_income(self.name, self.start_date, self.end_date, conditions) | ||||
| 		else: | ||||
| 			convert_deferred_expense_to_expense(self.name, self.start_date, self.end_date, conditions) | ||||
| 
 | ||||
| 	def on_cancel(self): | ||||
| 		self.ignore_linked_doctypes = ['GL Entry'] | ||||
| 		gl_entries = frappe.get_all('GL Entry', fields = ['*'], | ||||
| 			filters={ | ||||
| 				'against_voucher_type': self.doctype, | ||||
| 				'against_voucher': self.name | ||||
| 			}) | ||||
| 
 | ||||
| 		make_reverse_gl_entries(gl_entries=gl_entries) | ||||
| @ -0,0 +1,48 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| import frappe | ||||
| import unittest | ||||
| from erpnext.accounts.doctype.account.test_account import create_account | ||||
| from erpnext.stock.doctype.item.test_item import create_item | ||||
| from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice, check_gl_entries | ||||
| 
 | ||||
| class TestProcessDeferredAccounting(unittest.TestCase): | ||||
| 	def test_creation_of_ledger_entry_on_submit(self): | ||||
| 		''' test creation of gl entries on submission of document ''' | ||||
| 		deferred_account = create_account(account_name="Deferred Revenue", | ||||
| 			parent_account="Current Liabilities - _TC", company="_Test Company") | ||||
| 
 | ||||
| 		item = create_item("_Test Item for Deferred Accounting") | ||||
| 		item.enable_deferred_revenue = 1 | ||||
| 		item.deferred_revenue_account = deferred_account | ||||
| 		item.no_of_months = 12 | ||||
| 		item.save() | ||||
| 
 | ||||
| 		si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True) | ||||
| 		si.items[0].enable_deferred_revenue = 1 | ||||
| 		si.items[0].service_start_date = "2019-01-10" | ||||
| 		si.items[0].service_end_date = "2019-03-15" | ||||
| 		si.items[0].deferred_revenue_account = deferred_account | ||||
| 		si.save() | ||||
| 		si.submit() | ||||
| 
 | ||||
| 		process_deferred_accounting = doc = frappe.get_doc(dict( | ||||
| 			doctype='Process Deferred Accounting', | ||||
| 			posting_date="2019-01-01", | ||||
| 			start_date="2019-01-01", | ||||
| 			end_date="2019-01-31", | ||||
| 			type="Income" | ||||
| 		)) | ||||
| 
 | ||||
| 		process_deferred_accounting.insert() | ||||
| 		process_deferred_accounting.submit() | ||||
| 
 | ||||
| 		expected_gle = [ | ||||
| 			[deferred_account, 33.85, 0.0, "2019-01-31"], | ||||
| 			["Sales - _TC", 0.0, 33.85, "2019-01-31"] | ||||
| 		] | ||||
| 
 | ||||
| 		check_gl_entries(self, si.name, expected_gle, "2019-01-10") | ||||
| @ -382,11 +382,6 @@ function hide_fields(doc) { | ||||
| 	cur_frm.refresh_fields(); | ||||
| } | ||||
| 
 | ||||
| cur_frm.cscript.update_stock = function(doc, dt, dn) { | ||||
| 	hide_fields(doc, dt, dn); | ||||
| 	this.frm.fields_dict.items.grid.toggle_reqd("item_code", doc.update_stock? true: false) | ||||
| } | ||||
| 
 | ||||
| cur_frm.fields_dict.cash_bank_account.get_query = function(doc) { | ||||
| 	return { | ||||
| 		filters: [ | ||||
| @ -528,5 +523,10 @@ frappe.ui.form.on("Purchase Invoice", { | ||||
| 			erpnext.buying.get_default_bom(frm); | ||||
| 		} | ||||
| 		frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes"); | ||||
| 	}, | ||||
| 
 | ||||
| 	update_stock: function(frm) { | ||||
| 		hide_fields(frm.doc); | ||||
| 		frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false); | ||||
| 	} | ||||
| }) | ||||
|  | ||||
| @ -14,7 +14,7 @@ from erpnext.accounts.party import get_party_account, get_due_date | ||||
| from erpnext.accounts.utils import get_account_currency, get_fiscal_year | ||||
| from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billed_amount_based_on_po | ||||
| from erpnext.stock import get_warehouse_account_map | ||||
| from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, delete_gl_entries | ||||
| from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, make_reverse_gl_entries | ||||
| from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt | ||||
| from erpnext.buying.utils import check_on_hold_or_closed_status | ||||
| from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center | ||||
| @ -382,7 +382,7 @@ class PurchaseInvoice(BuyingController): | ||||
| 		self.update_project() | ||||
| 		update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) | ||||
| 
 | ||||
| 	def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): | ||||
| 	def make_gl_entries(self, gl_entries=None): | ||||
| 		if not self.grand_total: | ||||
| 			return | ||||
| 		if not gl_entries: | ||||
| @ -391,21 +391,17 @@ class PurchaseInvoice(BuyingController): | ||||
| 		if gl_entries: | ||||
| 			update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" | ||||
| 
 | ||||
| 			make_gl_entries(gl_entries,  cancel=(self.docstatus == 2), | ||||
| 				update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost) | ||||
| 			if self.docstatus == 1: | ||||
| 				make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False) | ||||
| 			elif self.docstatus == 2: | ||||
| 				make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) | ||||
| 
 | ||||
| 			if update_outstanding == "No": | ||||
| 				update_outstanding_amt(self.credit_to, "Supplier", self.supplier, | ||||
| 					self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name) | ||||
| 
 | ||||
| 			if (repost_future_gle or self.flags.repost_future_gle) and cint(self.update_stock) and self.auto_accounting_for_stock: | ||||
| 				from erpnext.controllers.stock_controller import update_gl_entries_after | ||||
| 				items, warehouses = self.get_items_and_warehouses() | ||||
| 				update_gl_entries_after(self.posting_date, self.posting_time, | ||||
| 					warehouses, items, company = self.company) | ||||
| 
 | ||||
| 		elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock: | ||||
| 			delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) | ||||
| 			make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) | ||||
| 
 | ||||
| 	def get_gl_entries(self, warehouse_account=None): | ||||
| 		self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) | ||||
| @ -934,6 +930,7 @@ class PurchaseInvoice(BuyingController): | ||||
| 		frappe.db.set(self, 'status', 'Cancelled') | ||||
| 
 | ||||
| 		unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference) | ||||
| 		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') | ||||
| 
 | ||||
| 	def update_project(self): | ||||
| 		project_list = [] | ||||
|  | ||||
| @ -138,13 +138,12 @@ | ||||
|     "row_id": 7 | ||||
|    } | ||||
|   ], | ||||
|   "posting_date": "2013-02-03", | ||||
|   "supplier": "_Test Supplier", | ||||
|   "supplier_name": "_Test Supplier" | ||||
|  }, | ||||
|   | ||||
|   | ||||
|   | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  { | ||||
|   "bill_no": "NA", | ||||
|   "buying_price_list": "_Test Price List", | ||||
| @ -204,7 +203,6 @@ | ||||
|     "tax_amount": 150.0 | ||||
|    } | ||||
|   ], | ||||
|   "posting_date": "2013-02-03", | ||||
|   "supplier": "_Test Supplier", | ||||
|   "supplier_name": "_Test Supplier" | ||||
|  } | ||||
|  | ||||
| @ -345,7 +345,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte | ||||
| 
 | ||||
| 	set_dynamic_labels: function() { | ||||
| 		this._super(); | ||||
| 		this.hide_fields(this.frm.doc); | ||||
| 		this.frm.events.hide_fields(this.frm) | ||||
| 	}, | ||||
| 
 | ||||
| 	items_on_form_rendered: function() { | ||||
| @ -404,7 +404,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte | ||||
| 							if(r.message && r.message.print_format) { | ||||
| 								me.frm.pos_print_format = r.message.print_format; | ||||
| 							} | ||||
| 							me.frm.script_manager.trigger("update_stock"); | ||||
| 							me.frm.trigger("update_stock"); | ||||
| 							if(me.frm.doc.taxes_and_charges) { | ||||
| 								me.frm.script_manager.trigger("taxes_and_charges"); | ||||
| 							} | ||||
| @ -446,35 +446,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte | ||||
| // for backward compatibility: combine new and previous states
 | ||||
| $.extend(cur_frm.cscript, new erpnext.accounts.SalesInvoiceController({frm: cur_frm})); | ||||
| 
 | ||||
| // Hide Fields
 | ||||
| // ------------
 | ||||
| cur_frm.cscript.hide_fields = function(doc) { | ||||
| 	var parent_fields = ['project', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances', | ||||
| 		'advances', 'from_date', 'to_date']; | ||||
| 
 | ||||
| 	if(cint(doc.is_pos) == 1) { | ||||
| 		hide_field(parent_fields); | ||||
| 	} else { | ||||
| 		for (var i in parent_fields) { | ||||
| 			var docfield = frappe.meta.docfield_map[doc.doctype][parent_fields[i]]; | ||||
| 			if(!docfield.hidden) unhide_field(parent_fields[i]); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// India related fields
 | ||||
| 	if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']); | ||||
| 	else hide_field(['c_form_applicable', 'c_form_no']); | ||||
| 
 | ||||
| 	this.frm.toggle_enable("write_off_amount", !!!cint(doc.write_off_outstanding_amount_automatically)); | ||||
| 
 | ||||
| 	cur_frm.refresh_fields(); | ||||
| } | ||||
| 
 | ||||
| cur_frm.cscript.update_stock = function(doc, dt, dn) { | ||||
| 	cur_frm.cscript.hide_fields(doc, dt, dn); | ||||
| 	this.frm.fields_dict.items.grid.toggle_reqd("item_code", doc.update_stock? true: false) | ||||
| } | ||||
| 
 | ||||
| cur_frm.cscript['Make Delivery Note'] = function() { | ||||
| 	frappe.model.open_mapped_doc({ | ||||
| 		method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_delivery_note", | ||||
| @ -719,6 +690,12 @@ frappe.ui.form.on('Sales Invoice', { | ||||
| 		frm.redemption_conversion_factor = null; | ||||
| 	}, | ||||
| 
 | ||||
| 	update_stock: function(frm, dt, dn) { | ||||
| 		frm.events.hide_fields(frm); | ||||
| 		frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock); | ||||
| 		frm.trigger('reset_posting_time'); | ||||
| 	}, | ||||
| 
 | ||||
| 	redeem_loyalty_points: function(frm) { | ||||
| 		frm.events.get_loyalty_details(frm); | ||||
| 	}, | ||||
| @ -742,6 +719,29 @@ frappe.ui.form.on('Sales Invoice', { | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	hide_fields: function(frm) { | ||||
| 		let doc = frm.doc; | ||||
| 		var parent_fields = ['project', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances', | ||||
| 		'advances', 'from_date', 'to_date']; | ||||
| 
 | ||||
| 		if(cint(doc.is_pos) == 1) { | ||||
| 			hide_field(parent_fields); | ||||
| 		} else { | ||||
| 			for (var i in parent_fields) { | ||||
| 				var docfield = frappe.meta.docfield_map[doc.doctype][parent_fields[i]]; | ||||
| 				if(!docfield.hidden) unhide_field(parent_fields[i]); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// India related fields
 | ||||
| 		if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']); | ||||
| 		else hide_field(['c_form_applicable', 'c_form_no']); | ||||
| 
 | ||||
| 		frm.toggle_enable("write_off_amount", !!!cint(doc.write_off_outstanding_amount_automatically)); | ||||
| 
 | ||||
| 		frm.refresh_fields(); | ||||
| 	}, | ||||
| 
 | ||||
| 	get_loyalty_details: function(frm) { | ||||
| 		if (frm.doc.customer && frm.doc.redeem_loyalty_points) { | ||||
| 			frappe.call({ | ||||
|  | ||||
| @ -149,9 +149,9 @@ | ||||
|   "edit_printing_settings", | ||||
|   "letter_head", | ||||
|   "group_same_items", | ||||
|   "language", | ||||
|   "column_break_84", | ||||
|   "select_print_heading", | ||||
|   "column_break_84", | ||||
|   "language", | ||||
|   "more_information", | ||||
|   "inter_company_invoice_reference", | ||||
|   "is_internal_customer", | ||||
| @ -1579,7 +1579,7 @@ | ||||
|  "idx": 181, | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-04-17 12:38:41.435728", | ||||
|  "modified": "2020-04-29 13:37:09.355300", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Sales Invoice", | ||||
|  | ||||
| @ -7,7 +7,6 @@ import frappe.defaults | ||||
| from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate | ||||
| from frappe import _, msgprint, throw | ||||
| from erpnext.accounts.party import get_party_account, get_due_date | ||||
| from erpnext.controllers.stock_controller import update_gl_entries_after | ||||
| from frappe.model.mapper import get_mapped_doc | ||||
| from erpnext.accounts.doctype.sales_invoice.pos import update_multi_mode_option | ||||
| 
 | ||||
| @ -282,6 +281,8 @@ class SalesInvoice(SellingController): | ||||
| 		if "Healthcare" in active_domains: | ||||
| 			manage_invoice_submit_cancel(self, "on_cancel") | ||||
| 
 | ||||
| 		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') | ||||
| 
 | ||||
| 	def update_status_updater_args(self): | ||||
| 		if cint(self.update_stock): | ||||
| 			self.status_updater.append({ | ||||
| @ -717,7 +718,9 @@ class SalesInvoice(SellingController): | ||||
| 			if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1: | ||||
| 				throw(_("Delivery Note {0} is not submitted").format(d.delivery_note)) | ||||
| 
 | ||||
| 	def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): | ||||
| 	def make_gl_entries(self, gl_entries=None): | ||||
| 		from erpnext.accounts.general_ledger import make_reverse_gl_entries | ||||
| 
 | ||||
| 		auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) | ||||
| 		if not gl_entries: | ||||
| 			gl_entries = self.get_gl_entries() | ||||
| @ -729,23 +732,19 @@ class SalesInvoice(SellingController): | ||||
| 			update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or | ||||
| 				cint(self.redeem_loyalty_points)) else "Yes" | ||||
| 
 | ||||
| 			make_gl_entries(gl_entries, cancel=(self.docstatus == 2), | ||||
| 				update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost) | ||||
| 			if self.docstatus == 1: | ||||
| 				make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False) | ||||
| 			elif self.docstatus == 2: | ||||
| 				make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) | ||||
| 
 | ||||
| 			if update_outstanding == "No": | ||||
| 				from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt | ||||
| 				update_outstanding_amt(self.debit_to, "Customer", self.customer, | ||||
| 					self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name) | ||||
| 
 | ||||
| 			if (repost_future_gle or self.flags.repost_future_gle) and cint(self.update_stock) \ | ||||
| 				and cint(auto_accounting_for_stock): | ||||
| 					items, warehouses = self.get_items_and_warehouses() | ||||
| 					update_gl_entries_after(self.posting_date, self.posting_time, | ||||
| 						warehouses, items, company = self.company) | ||||
| 		elif self.docstatus == 2 and cint(self.update_stock) \ | ||||
| 			and cint(auto_accounting_for_stock): | ||||
| 				from erpnext.accounts.general_ledger import delete_gl_entries | ||||
| 				delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) | ||||
| 				make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) | ||||
| 
 | ||||
| 	def get_gl_entries(self, warehouse_account=None): | ||||
| 		from erpnext.accounts.general_ledger import merge_similar_entries | ||||
|  | ||||
| @ -5,7 +5,7 @@ from __future__ import unicode_literals | ||||
| import frappe | ||||
| 
 | ||||
| import unittest, copy, time | ||||
| from frappe.utils import nowdate, flt, getdate, cint, add_days | ||||
| from frappe.utils import nowdate, flt, getdate, cint, add_days, add_months | ||||
| from frappe.model.dynamic_links import get_dynamic_link_map | ||||
| from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction | ||||
| from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice | ||||
| @ -364,7 +364,7 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 		gle = frappe.db.sql("""select * from `tabGL Entry` | ||||
| 			where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) | ||||
| 
 | ||||
| 		self.assertFalse(gle) | ||||
| 		self.assertTrue(gle) | ||||
| 
 | ||||
| 	def test_tax_calculation_with_multiple_items(self): | ||||
| 		si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True) | ||||
| @ -678,14 +678,15 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 		gle = frappe.db.sql("""select * from `tabGL Entry` | ||||
| 			where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) | ||||
| 
 | ||||
| 		self.assertFalse(gle) | ||||
| 		self.assertTrue(gle) | ||||
| 
 | ||||
| 	def test_pos_gl_entry_with_perpetual_inventory(self): | ||||
| 		make_pos_profile() | ||||
| 
 | ||||
| 		pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") | ||||
| 		pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") | ||||
| 
 | ||||
| 		pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) | ||||
| 		pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", | ||||
| 			income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) | ||||
| 
 | ||||
| 		pos.is_pos = 1 | ||||
| 		pos.update_stock = 1 | ||||
| @ -766,9 +767,13 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 	def test_pos_change_amount(self): | ||||
| 		make_pos_profile() | ||||
| 
 | ||||
| 		pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") | ||||
| 		pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", | ||||
| 			item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1") | ||||
| 
 | ||||
| 		pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) | ||||
| 		pos = create_sales_invoice(company= "_Test Company with perpetual inventory", | ||||
| 			debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", | ||||
| 			income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", | ||||
| 			cost_center = "Main - TCP1", do_not_save=True) | ||||
| 
 | ||||
| 		pos.is_pos = 1 | ||||
| 		pos.update_stock = 1 | ||||
| @ -787,8 +792,15 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 		from erpnext.accounts.doctype.sales_invoice.pos import make_invoice | ||||
| 
 | ||||
| 		pos_profile = make_pos_profile() | ||||
| 		pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") | ||||
| 		pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) | ||||
| 
 | ||||
| 		pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", | ||||
| 			item_code= "_Test FG Item", | ||||
| 			warehouse= "Stores - TCP1", cost_center= "Main - TCP1") | ||||
| 
 | ||||
| 		pos = create_sales_invoice(company= "_Test Company with perpetual inventory", | ||||
| 			debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", | ||||
| 			income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", | ||||
| 			cost_center = "Main - TCP1", do_not_save=True) | ||||
| 
 | ||||
| 		pos.is_pos = 1 | ||||
| 		pos.update_stock = 1 | ||||
| @ -891,11 +903,9 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 		gle = frappe.db.sql("""select * from `tabGL Entry` | ||||
| 			where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) | ||||
| 
 | ||||
| 		self.assertFalse(gle) | ||||
| 
 | ||||
| 		self.assertTrue(gle) | ||||
| 
 | ||||
| 		frappe.db.sql("delete from `tabPOS Profile`") | ||||
| 		si.delete() | ||||
| 
 | ||||
| 	def test_pos_si_without_payment(self): | ||||
| 		set_perpetual_inventory() | ||||
| @ -1012,9 +1022,6 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 
 | ||||
| 		si.cancel() | ||||
| 
 | ||||
| 		self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account` | ||||
| 			where reference_name=%s""", si.name)) | ||||
| 
 | ||||
| 	def test_serialized(self): | ||||
| 		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item | ||||
| 		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos | ||||
| @ -1230,7 +1237,7 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 		gle = frappe.db.sql("""select name from `tabGL Entry` | ||||
| 			where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) | ||||
| 
 | ||||
| 		self.assertFalse(gle) | ||||
| 		self.assertTrue(gle) | ||||
| 
 | ||||
| 	def test_invalid_currency(self): | ||||
| 		# Customer currency = USD | ||||
| @ -1714,37 +1721,76 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 		si.submit() | ||||
| 
 | ||||
| 		from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income | ||||
| 		convert_deferred_revenue_to_income(start_date="2019-01-01", end_date="2019-01-31") | ||||
| 
 | ||||
| 		pda1 = frappe.get_doc(dict( | ||||
| 			doctype='Process Deferred Accounting', | ||||
| 			posting_date=nowdate(), | ||||
| 			start_date="2019-01-01", | ||||
| 			end_date="2019-03-31", | ||||
| 			type="Income", | ||||
| 			company="_Test Company" | ||||
| 		)) | ||||
| 
 | ||||
| 		pda1.insert() | ||||
| 		pda1.submit() | ||||
| 
 | ||||
| 		expected_gle = [ | ||||
| 			[deferred_account, 33.85, 0.0, "2019-01-31"], | ||||
| 			["Sales - _TC", 0.0, 33.85, "2019-01-31"] | ||||
| 		] | ||||
| 
 | ||||
| 		self.check_gl_entries(si.name, expected_gle, "2019-01-10") | ||||
| 
 | ||||
| 		convert_deferred_revenue_to_income(start_date="2019-01-01", end_date="2019-03-31") | ||||
| 
 | ||||
| 		expected_gle = [ | ||||
| 			["Sales - _TC", 0.0, 33.85, "2019-01-31"], | ||||
| 			[deferred_account, 43.08, 0.0, "2019-02-28"], | ||||
| 			["Sales - _TC", 0.0, 43.08, "2019-02-28"], | ||||
| 			[deferred_account, 23.07, 0.0, "2019-03-15"], | ||||
| 			["Sales - _TC", 0.0, 23.07, "2019-03-15"] | ||||
| 		] | ||||
| 
 | ||||
| 		self.check_gl_entries(si.name, expected_gle, "2019-01-31") | ||||
| 		check_gl_entries(self, si.name, expected_gle, "2019-01-30") | ||||
| 
 | ||||
| 	def check_gl_entries(self, voucher_no, expected_gle, posting_date): | ||||
| 		gl_entries = frappe.db.sql("""select account, debit, credit, posting_date | ||||
| 			from `tabGL Entry` | ||||
| 			where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s | ||||
| 			order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1) | ||||
| 	def test_deferred_error_email(self): | ||||
| 		deferred_account = create_account(account_name="Deferred Revenue", | ||||
| 			parent_account="Current Liabilities - _TC", company="_Test Company") | ||||
| 
 | ||||
| 		for i, gle in enumerate(gl_entries): | ||||
| 			self.assertEqual(expected_gle[i][0], gle.account) | ||||
| 			self.assertEqual(expected_gle[i][1], gle.debit) | ||||
| 			self.assertEqual(expected_gle[i][2], gle.credit) | ||||
| 			self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) | ||||
| 		item = create_item("_Test Item for Deferred Accounting") | ||||
| 		item.enable_deferred_revenue = 1 | ||||
| 		item.deferred_revenue_account = deferred_account | ||||
| 		item.no_of_months = 12 | ||||
| 		item.save() | ||||
| 
 | ||||
| 		si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True) | ||||
| 		si.items[0].enable_deferred_revenue = 1 | ||||
| 		si.items[0].service_start_date = "2019-01-10" | ||||
| 		si.items[0].service_end_date = "2019-03-15" | ||||
| 		si.items[0].deferred_revenue_account = deferred_account | ||||
| 		si.save() | ||||
| 		si.submit() | ||||
| 
 | ||||
| 		from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income | ||||
| 
 | ||||
| 		acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') | ||||
| 		acc_settings.acc_frozen_upto = '2019-01-31' | ||||
| 		acc_settings.save() | ||||
| 
 | ||||
| 		pda = frappe.get_doc(dict( | ||||
| 			doctype='Process Deferred Accounting', | ||||
| 			posting_date=nowdate(), | ||||
| 			start_date="2019-01-01", | ||||
| 			end_date="2019-03-31", | ||||
| 			type="Income", | ||||
| 			company="_Test Company" | ||||
| 		)) | ||||
| 
 | ||||
| 		pda.insert() | ||||
| 		pda.submit() | ||||
| 
 | ||||
| 		email = frappe.db.sql(""" select name from `tabEmail Queue` | ||||
| 		where message like %(txt)s """, { | ||||
| 			'txt': "%%%s%%" % "Error while processing deferred accounting for {0}".format(pda.name) | ||||
| 		}) | ||||
| 
 | ||||
| 		self.assertTrue(email) | ||||
| 
 | ||||
| 		acc_settings.load_from_db() | ||||
| 		acc_settings.acc_frozen_upto = None | ||||
| 		acc_settings.save() | ||||
| 
 | ||||
| 	def test_inter_company_transaction(self): | ||||
| 
 | ||||
| @ -1905,6 +1951,18 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 		self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234') | ||||
| 		self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000) | ||||
| 
 | ||||
| def check_gl_entries(doc, voucher_no, expected_gle, posting_date): | ||||
| 	gl_entries = frappe.db.sql("""select account, debit, credit, posting_date | ||||
| 		from `tabGL Entry` | ||||
| 		where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s | ||||
| 		order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1) | ||||
| 
 | ||||
| 	for i, gle in enumerate(gl_entries): | ||||
| 		doc.assertEqual(expected_gle[i][0], gle.account) | ||||
| 		doc.assertEqual(expected_gle[i][1], gle.debit) | ||||
| 		doc.assertEqual(expected_gle[i][2], gle.credit) | ||||
| 		doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) | ||||
| 
 | ||||
| 	def test_item_tax_validity(self): | ||||
| 		item = frappe.get_doc("Item", "_Test Item 2") | ||||
| 
 | ||||
|  | ||||
| @ -168,18 +168,20 @@ class ShareTransfer(Document): | ||||
| 		return 'Outside' | ||||
| 
 | ||||
| 	def folio_no_validation(self): | ||||
| 		shareholders = ['from_shareholder', 'to_shareholder'] | ||||
| 		shareholders = [shareholder for shareholder in shareholders if self.get(shareholder) is not ''] | ||||
| 		for shareholder in shareholders: | ||||
| 			doc = self.get_shareholder_doc(self.get(shareholder)) | ||||
| 		shareholder_fields = ['from_shareholder', 'to_shareholder'] | ||||
| 		for shareholder_field in shareholder_fields: | ||||
| 			shareholder_name = self.get(shareholder_field) | ||||
| 			if not shareholder_name: | ||||
| 				continue | ||||
| 			doc = self.get_shareholder_doc(shareholder_name) | ||||
| 			if doc.company != self.company: | ||||
| 				frappe.throw(_('The shareholder does not belong to this company')) | ||||
| 			if not doc.folio_no: | ||||
| 				doc.folio_no = self.from_folio_no \ | ||||
| 					if (shareholder == 'from_shareholder') else self.to_folio_no | ||||
| 					if (shareholder_field == 'from_shareholder') else self.to_folio_no | ||||
| 				doc.save() | ||||
| 			else: | ||||
| 				if doc.folio_no and doc.folio_no != (self.from_folio_no if (shareholder == 'from_shareholder') else self.to_folio_no): | ||||
| 				if doc.folio_no and doc.folio_no != (self.from_folio_no if (shareholder_field == 'from_shareholder') else self.to_folio_no): | ||||
| 					frappe.throw(_('The folio numbers are not matching')) | ||||
| 
 | ||||
| 	def autoname_folio(self, shareholder, is_company=False): | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe, erpnext | ||||
| from frappe.utils import flt, cstr, cint, comma_and | ||||
| from frappe.utils import flt, cstr, cint, comma_and, today, getdate, formatdate, now | ||||
| from frappe import _ | ||||
| from erpnext.accounts.utils import get_stock_and_account_balance | ||||
| from frappe.model.meta import get_field_precision | ||||
| @ -15,17 +15,17 @@ class ClosedAccountingPeriod(frappe.ValidationError): pass | ||||
| class StockAccountInvalidTransaction(frappe.ValidationError): pass | ||||
| class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass | ||||
| 
 | ||||
| def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False): | ||||
| def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes'): | ||||
| 	if gl_map: | ||||
| 		if not cancel: | ||||
| 			validate_accounting_period(gl_map) | ||||
| 			gl_map = process_gl_map(gl_map, merge_entries) | ||||
| 			if gl_map and len(gl_map) > 1: | ||||
| 				save_entries(gl_map, adv_adj, update_outstanding, from_repost) | ||||
| 				save_entries(gl_map, adv_adj, update_outstanding) | ||||
| 			else: | ||||
| 				frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction.")) | ||||
| 		else: | ||||
| 			delete_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding) | ||||
| 			make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding) | ||||
| 
 | ||||
| def validate_accounting_period(gl_map): | ||||
| 	accounting_periods = frappe.db.sql(""" SELECT | ||||
| @ -119,33 +119,36 @@ def check_if_in_list(gle, gl_map, dimensions=None): | ||||
| 		if same_head: | ||||
| 			return e | ||||
| 
 | ||||
| def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): | ||||
| 	if not from_repost: | ||||
| 		validate_cwip_accounts(gl_map) | ||||
| def save_entries(gl_map, adv_adj, update_outstanding): | ||||
| 	validate_cwip_accounts(gl_map) | ||||
| 
 | ||||
| 	round_off_debit_credit(gl_map) | ||||
| 
 | ||||
| 	if gl_map: | ||||
| 		check_freezing_date(gl_map[0]["posting_date"], adv_adj) | ||||
| 
 | ||||
| 	for entry in gl_map: | ||||
| 		make_entry(entry, adv_adj, update_outstanding, from_repost) | ||||
| 		make_entry(entry, adv_adj, update_outstanding) | ||||
| 
 | ||||
| 		# check against budget | ||||
| 		if not from_repost: | ||||
| 			validate_expense_against_budget(entry) | ||||
| 		validate_expense_against_budget(entry) | ||||
| 
 | ||||
| 	if not from_repost: | ||||
| 		validate_account_for_perpetual_inventory(gl_map) | ||||
| 	validate_account_for_perpetual_inventory(gl_map) | ||||
| 
 | ||||
| 
 | ||||
| def make_entry(args, adv_adj, update_outstanding, from_repost=False): | ||||
| def make_entry(args, adv_adj, update_outstanding): | ||||
| 	gle = frappe.new_doc("GL Entry") | ||||
| 	gle.update(args) | ||||
| 	gle.flags.ignore_permissions = 1 | ||||
| 	gle.flags.from_repost = from_repost | ||||
| 	gle.validate() | ||||
| 	gle.db_insert() | ||||
| 	gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost) | ||||
| 	gle.run_method("on_update_with_args", adv_adj, update_outstanding) | ||||
| 	gle.flags.ignore_validate = True | ||||
| 	gle.submit() | ||||
| 
 | ||||
| 	# check against budget | ||||
| 	validate_expense_against_budget(args) | ||||
| 
 | ||||
| def validate_account_for_perpetual_inventory(gl_map): | ||||
| 	if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)): | ||||
| 		account_list = [gl_entries.account for gl_entries in gl_map] | ||||
| @ -169,33 +172,33 @@ def validate_account_for_perpetual_inventory(gl_map): | ||||
| 						.format(account), StockAccountInvalidTransaction) | ||||
| 
 | ||||
| 			# This has been comment for a temporary, will add this code again on release of immutable ledger | ||||
| 			# elif account_bal != stock_bal: | ||||
| 			# 	precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), | ||||
| 			# 		currency=frappe.get_cached_value('Company',  gl_map[0].company,  "default_currency")) | ||||
| 			elif account_bal != stock_bal: | ||||
| 				precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), | ||||
| 					currency=frappe.get_cached_value('Company',  gl_map[0].company,  "default_currency")) | ||||
| 
 | ||||
| 			# 	diff = flt(stock_bal - account_bal, precision) | ||||
| 			# 	error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format( | ||||
| 			# 		stock_bal, account_bal, frappe.bold(account)) | ||||
| 			# 	error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff)) | ||||
| 			# 	stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account") | ||||
| 				diff = flt(stock_bal - account_bal, precision) | ||||
| 				error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format( | ||||
| 					stock_bal, account_bal, frappe.bold(account)) | ||||
| 				error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff)) | ||||
| 				stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account") | ||||
| 
 | ||||
| 			# 	db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency') | ||||
| 			# 	db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency') | ||||
| 				db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency') | ||||
| 				db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency') | ||||
| 
 | ||||
| 			# 	journal_entry_args = { | ||||
| 			# 	'accounts':[ | ||||
| 			# 		{'account': account, db_or_cr_warehouse_account : abs(diff)}, | ||||
| 			# 		{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }] | ||||
| 			# 	} | ||||
| 				journal_entry_args = { | ||||
| 				'accounts':[ | ||||
| 					{'account': account, db_or_cr_warehouse_account : abs(diff)}, | ||||
| 					{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }] | ||||
| 				} | ||||
| 
 | ||||
| 			# 	frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution), | ||||
| 			# 		raise_exception=StockValueAndAccountBalanceOutOfSync, | ||||
| 			# 		title=_('Values Out Of Sync'), | ||||
| 			# 		primary_action={ | ||||
| 			# 			'label': _('Make Journal Entry'), | ||||
| 			# 			'client_action': 'erpnext.route_to_adjustment_jv', | ||||
| 			# 			'args': journal_entry_args | ||||
| 			# 		}) | ||||
| 				frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution), | ||||
| 					raise_exception=StockValueAndAccountBalanceOutOfSync, | ||||
| 					title=_('Values Out Of Sync'), | ||||
| 					primary_action={ | ||||
| 						'label': _('Make Journal Entry'), | ||||
| 						'client_action': 'erpnext.route_to_adjustment_jv', | ||||
| 						'args': journal_entry_args | ||||
| 					}) | ||||
| 
 | ||||
| def validate_cwip_accounts(gl_map): | ||||
| 	cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")]) | ||||
| @ -282,31 +285,64 @@ def get_round_off_account_and_cost_center(company): | ||||
| 
 | ||||
| 	return round_off_account, round_off_cost_center | ||||
| 
 | ||||
| def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, | ||||
| 		adv_adj=False, update_outstanding="Yes"): | ||||
| 
 | ||||
| 	from erpnext.accounts.doctype.gl_entry.gl_entry import validate_balance_type, \ | ||||
| 		check_freezing_date, update_outstanding_amt, validate_frozen_account | ||||
| def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, | ||||
| 	adv_adj=False, update_outstanding="Yes"): | ||||
| 	""" | ||||
| 		Get original gl entries of the voucher | ||||
| 		and make reverse gl entries by swapping debit and credit | ||||
| 	""" | ||||
| 
 | ||||
| 	if not gl_entries: | ||||
| 		gl_entries = frappe.db.sql(""" | ||||
| 			select account, posting_date, party_type, party, cost_center, fiscal_year,voucher_type, | ||||
| 			voucher_no, against_voucher_type, against_voucher, cost_center, company | ||||
| 			from `tabGL Entry` | ||||
| 			where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no), as_dict=True) | ||||
| 		gl_entries = frappe.get_all("GL Entry", | ||||
| 			fields = ["*"], | ||||
| 			filters = { | ||||
| 				"voucher_type": voucher_type, | ||||
| 				"voucher_no": voucher_no | ||||
| 			}) | ||||
| 
 | ||||
| 	if gl_entries: | ||||
| 		set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no']) | ||||
| 		check_freezing_date(gl_entries[0]["posting_date"], adv_adj) | ||||
| 
 | ||||
| 	frappe.db.sql("""delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", | ||||
| 		(voucher_type or gl_entries[0]["voucher_type"], voucher_no or gl_entries[0]["voucher_no"])) | ||||
| 		for entry in gl_entries: | ||||
| 			entry['name'] = None | ||||
| 			debit = entry.get('debit', 0) | ||||
| 			credit = entry.get('credit', 0) | ||||
| 
 | ||||
| 	for entry in gl_entries: | ||||
| 		validate_frozen_account(entry["account"], adv_adj) | ||||
| 		validate_balance_type(entry["account"], adv_adj) | ||||
| 		if not adv_adj: | ||||
| 			validate_expense_against_budget(entry) | ||||
| 			debit_in_account_currency = entry.get('debit_in_account_currency', 0) | ||||
| 			credit_in_account_currency = entry.get('credit_in_account_currency', 0) | ||||
| 
 | ||||
| 		if entry.get("against_voucher") and update_outstanding == 'Yes' and not adv_adj: | ||||
| 			update_outstanding_amt(entry["account"], entry.get("party_type"), entry.get("party"), entry.get("against_voucher_type"), | ||||
| 				entry.get("against_voucher"), on_cancel=True) | ||||
| 			entry['debit'] = credit | ||||
| 			entry['credit'] = debit | ||||
| 			entry['debit_in_account_currency'] = credit_in_account_currency | ||||
| 			entry['credit_in_account_currency'] = debit_in_account_currency | ||||
| 
 | ||||
| 			entry['remarks'] = "On cancellation of " + entry['voucher_no'] | ||||
| 			entry['is_cancelled'] = 1 | ||||
| 			entry['posting_date'] = today() | ||||
| 
 | ||||
| 			if entry['debit'] or entry['credit']: | ||||
| 				make_entry(entry, adv_adj, "Yes") | ||||
| 
 | ||||
| 
 | ||||
| def check_freezing_date(posting_date, adv_adj=False): | ||||
| 	""" | ||||
| 		Nobody can do GL Entries where posting date is before freezing date | ||||
| 		except authorized person | ||||
| 	""" | ||||
| 	if not adv_adj: | ||||
| 		acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') | ||||
| 		if acc_frozen_upto: | ||||
| 			frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier') | ||||
| 			if getdate(posting_date) <= getdate(acc_frozen_upto) \ | ||||
| 					and not frozen_accounts_modifier in frappe.get_roles(): | ||||
| 				frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto))) | ||||
| 
 | ||||
| def set_as_cancel(voucher_type, voucher_no): | ||||
| 	""" | ||||
| 		Set is_cancelled=1 in all original gl entries for the voucher | ||||
| 	""" | ||||
| 	frappe.db.sql("""update `tabGL Entry` set is_cancelled = 1, | ||||
| 		modified=%s, modified_by=%s | ||||
| 		where voucher_type=%s and voucher_no=%s and is_cancelled = 0""", | ||||
| 		(now(), frappe.session.user, voucher_type, voucher_no)) | ||||
|  | ||||
| @ -162,7 +162,7 @@ def get_default_price_list(party): | ||||
| def set_price_list(party_details, party, party_type, given_price_list, pos=None): | ||||
| 	# price list | ||||
| 	price_list = get_permitted_documents('Price List') | ||||
| 	 | ||||
| 
 | ||||
| 	# if there is only one permitted document based on user permissions, set it | ||||
| 	if price_list and len(price_list) == 1: | ||||
| 		price_list = price_list[0] | ||||
| @ -465,23 +465,25 @@ def get_timeline_data(doctype, name): | ||||
| 	from frappe.desk.form.load import get_communication_data | ||||
| 
 | ||||
| 	out = {} | ||||
| 	fields = 'date(creation), count(name)' | ||||
| 	fields = 'creation, count(*)' | ||||
| 	after = add_years(None, -1).strftime('%Y-%m-%d') | ||||
| 	group_by='group by date(creation)' | ||||
| 	group_by='group by Date(creation)' | ||||
| 
 | ||||
| 	data = get_communication_data(doctype, name, after=after, group_by='group by date(creation)', | ||||
| 		fields='date(C.creation) as creation, count(C.name)',as_dict=False) | ||||
| 	data = get_communication_data(doctype, name, after=after, group_by='group by creation', | ||||
| 		fields='C.creation as creation, count(C.name)',as_dict=False) | ||||
| 
 | ||||
| 	# fetch and append data from Activity Log | ||||
| 	data += frappe.db.sql("""select {fields} | ||||
| 		from `tabActivity Log` | ||||
| 		where (reference_doctype="{doctype}" and reference_name="{name}") | ||||
| 		or (timeline_doctype in ("{doctype}") and timeline_name="{name}") | ||||
| 		or (reference_doctype in ("Quotation", "Opportunity") and timeline_name="{name}") | ||||
| 		where (reference_doctype=%(doctype)s and reference_name=%(name)s) | ||||
| 		or (timeline_doctype in (%(doctype)s) and timeline_name=%(name)s) | ||||
| 		or (reference_doctype in ("Quotation", "Opportunity") and timeline_name=%(name)s) | ||||
| 		and status!='Success' and creation > {after} | ||||
| 		{group_by} order by creation desc | ||||
| 		""".format(doctype=frappe.db.escape(doctype), name=frappe.db.escape(name), fields=fields, | ||||
| 			group_by=group_by, after=after), as_dict=False) | ||||
| 		""".format(fields=fields, group_by=group_by, after=after), { | ||||
| 			"doctype": doctype, | ||||
| 			"name": name | ||||
| 		}, as_dict=False) | ||||
| 
 | ||||
| 	timeline_items = dict(data) | ||||
| 
 | ||||
| @ -600,10 +602,12 @@ def get_party_shipping_address(doctype, name): | ||||
| 	else: | ||||
| 		return '' | ||||
| 
 | ||||
| def get_partywise_advanced_payment_amount(party_type, posting_date = None): | ||||
| def get_partywise_advanced_payment_amount(party_type, posting_date = None, company=None): | ||||
| 	cond = "1=1" | ||||
| 	if posting_date: | ||||
| 		cond = "posting_date <= '{0}'".format(posting_date) | ||||
| 	if company: | ||||
| 		cond += "and company = '{0}'".format(company) | ||||
| 
 | ||||
| 	data = frappe.db.sql(""" SELECT party, sum({0}) as amount | ||||
| 		FROM `tabGL Entry` | ||||
|  | ||||
| @ -33,7 +33,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): | ||||
| 		self.get_party_total(args) | ||||
| 
 | ||||
| 		party_advance_amount = get_partywise_advanced_payment_amount(self.party_type, | ||||
| 			self.filters.report_date) or {} | ||||
| 			self.filters.report_date, self.filters.company) or {} | ||||
| 
 | ||||
| 		for party, party_dict in iteritems(self.party_total): | ||||
| 			if party_dict.outstanding == 0: | ||||
|  | ||||
| @ -154,8 +154,12 @@ frappe.query_reports["General Ledger"] = { | ||||
| 		{ | ||||
| 			"fieldname": "include_default_book_entries", | ||||
| 			"label": __("Include Default Book Entries"), | ||||
| 			"fieldtype": "Check", | ||||
| 			"default": 1 | ||||
| 			"fieldtype": "Check" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fieldname": "show_cancelled_entries", | ||||
| 			"label": __("Show Cancelled Entries"), | ||||
| 			"fieldtype": "Check" | ||||
| 		} | ||||
| 	] | ||||
| } | ||||
|  | ||||
| @ -188,6 +188,9 @@ def get_conditions(filters): | ||||
| 		else: | ||||
| 			conditions.append("finance_book in (%(finance_book)s)") | ||||
| 
 | ||||
| 	if not filters.get("show_cancelled_entries"): | ||||
| 		conditions.append("is_cancelled = 0") | ||||
| 
 | ||||
| 	from frappe.desk.reportview import build_match_conditions | ||||
| 	match_conditions = build_match_conditions("GL Entry") | ||||
| 
 | ||||
| @ -293,6 +296,9 @@ def get_accountwise_gle(filters, gl_entries, gle_map): | ||||
| 		data[key].debit_in_account_currency += flt(gle.debit_in_account_currency) | ||||
| 		data[key].credit_in_account_currency += flt(gle.credit_in_account_currency) | ||||
| 
 | ||||
| 		if data[key].against_voucher: | ||||
| 			data[key].against_voucher += ', ' + gle.against_voucher | ||||
| 
 | ||||
| 	from_date, to_date = getdate(filters.from_date), getdate(filters.to_date) | ||||
| 	for gle in gl_entries: | ||||
| 		if (gle.posting_date < from_date or | ||||
|  | ||||
| @ -35,6 +35,12 @@ def execute(filters=None): | ||||
| 		}) | ||||
| 		return columns, data | ||||
| 
 | ||||
| 	# to avoid error eg: gross_income[0] : list index out of range | ||||
| 	if not gross_income: | ||||
| 		gross_income = [{}] | ||||
| 	if not gross_expense: | ||||
| 		gross_expense = [{}] | ||||
| 
 | ||||
| 	data.append({ | ||||
| 		"account_name": "'" + _("Included in Gross Profit") + "'", | ||||
| 		"account": "'" + _("Included in Gross Profit") + "'" | ||||
|  | ||||
| @ -817,48 +817,37 @@ def create_payment_gateway_account(gateway): | ||||
| 		pass | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def update_number_field(doctype_name, name, field_name, number_value, company): | ||||
| def update_cost_center(docname, cost_center_name, cost_center_number, company): | ||||
| 	''' | ||||
| 		doctype_name = Name of the DocType | ||||
| 		name = Docname being referred | ||||
| 		field_name = Name of the field thats holding the 'number' attribute | ||||
| 		number_value = Numeric value entered in field_name | ||||
| 
 | ||||
| 		Stores the number entered in the dialog to the DocType's field. | ||||
| 
 | ||||
| 		Renames the document by adding the number as a prefix to the current name and updates | ||||
| 		all transaction where it was present. | ||||
| 	''' | ||||
| 	doc_title = frappe.db.get_value(doctype_name, name, frappe.scrub(doctype_name)+"_name") | ||||
| 	validate_field_number("Cost Center", docname, cost_center_number, company, "cost_center_number") | ||||
| 
 | ||||
| 	validate_field_number(doctype_name, name, number_value, company, field_name) | ||||
| 	if cost_center_number: | ||||
| 		frappe.db.set_value("Cost Center", docname, "cost_center_number", cost_center_number.strip()) | ||||
| 	else: | ||||
| 		frappe.db.set_value("Cost Center", docname, "cost_center_number", "") | ||||
| 
 | ||||
| 	frappe.db.set_value(doctype_name, name, field_name, number_value) | ||||
| 	frappe.db.set_value("Cost Center", docname, "cost_center_name", cost_center_name.strip()) | ||||
| 
 | ||||
| 	if doc_title[0].isdigit(): | ||||
| 		separator = " - " if " - " in doc_title else " " | ||||
| 		doc_title = doc_title.split(separator, 1)[1] | ||||
| 
 | ||||
| 	frappe.db.set_value(doctype_name, name, frappe.scrub(doctype_name)+"_name", doc_title) | ||||
| 
 | ||||
| 	new_name = get_autoname_with_number(number_value, doc_title, name, company) | ||||
| 
 | ||||
| 	if name != new_name: | ||||
| 		frappe.rename_doc(doctype_name, name, new_name) | ||||
| 	new_name = get_autoname_with_number(cost_center_number, cost_center_name, docname, company) | ||||
| 	if docname != new_name: | ||||
| 		frappe.rename_doc("Cost Center", docname, new_name, force=1) | ||||
| 		return new_name | ||||
| 
 | ||||
| def validate_field_number(doctype_name, name, number_value, company, field_name): | ||||
| def validate_field_number(doctype_name, docname, number_value, company, field_name): | ||||
| 	''' Validate if the number entered isn't already assigned to some other document. ''' | ||||
| 	if number_value: | ||||
| 		filters = {field_name: number_value, "name": ["!=", docname]} | ||||
| 		if company: | ||||
| 			doctype_with_same_number = frappe.db.get_value(doctype_name, | ||||
| 				{field_name: number_value, "company": company, "name": ["!=", name]}) | ||||
| 		else: | ||||
| 			doctype_with_same_number = frappe.db.get_value(doctype_name, | ||||
| 				{field_name: number_value, "name": ["!=", name]}) | ||||
| 			filters["company"] = company | ||||
| 
 | ||||
| 		doctype_with_same_number = frappe.db.get_value(doctype_name, filters) | ||||
| 
 | ||||
| 		if doctype_with_same_number: | ||||
| 			frappe.throw(_("{0} Number {1} already used in account {2}") | ||||
| 				.format(doctype_name, number_value, doctype_with_same_number)) | ||||
| 			frappe.throw(_("{0} Number {1} is already used in {2} {3}") | ||||
| 				.format(doctype_name, number_value, doctype_name.lower(), doctype_with_same_number)) | ||||
| 
 | ||||
| def get_autoname_with_number(number_value, doc_title, name, company): | ||||
| 	''' append title with prefix as number and suffix as company's abbreviation separated by '-' ''' | ||||
| @ -897,4 +886,60 @@ def get_stock_accounts(company): | ||||
| 	return frappe.get_all("Account", filters = { | ||||
| 		"account_type": "Stock", | ||||
| 		"company": company | ||||
| 	}) | ||||
| 	}) | ||||
| 
 | ||||
| def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None, | ||||
| 		warehouse_account=None, company=None): | ||||
| 	def _delete_gl_entries(voucher_type, voucher_no): | ||||
| 		frappe.db.sql("""delete from `tabGL Entry` | ||||
| 			where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) | ||||
| 
 | ||||
| 	if not warehouse_account: | ||||
| 		warehouse_account = get_warehouse_account_map(company) | ||||
| 
 | ||||
| 	future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items) | ||||
| 	gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date) | ||||
| 
 | ||||
| 	for voucher_type, voucher_no in future_stock_vouchers: | ||||
| 		existing_gle = gle.get((voucher_type, voucher_no), []) | ||||
| 		voucher_obj = frappe.get_doc(voucher_type, voucher_no) | ||||
| 		expected_gle = voucher_obj.get_gl_entries(warehouse_account) | ||||
| 		if expected_gle: | ||||
| 			if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle): | ||||
| 				_delete_gl_entries(voucher_type, voucher_no) | ||||
| 				voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True) | ||||
| 		else: | ||||
| 			_delete_gl_entries(voucher_type, voucher_no) | ||||
| 
 | ||||
| def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None): | ||||
| 	future_stock_vouchers = [] | ||||
| 
 | ||||
| 	values = [] | ||||
| 	condition = "" | ||||
| 	if for_items: | ||||
| 		condition += " and item_code in ({})".format(", ".join(["%s"] * len(for_items))) | ||||
| 		values += for_items | ||||
| 
 | ||||
| 	if for_warehouses: | ||||
| 		condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses))) | ||||
| 		values += for_warehouses | ||||
| 
 | ||||
| 	for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no | ||||
| 		from `tabStock Ledger Entry` sle | ||||
| 		where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition} | ||||
| 		order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition), | ||||
| 		tuple([posting_date, posting_time] + values), as_dict=True): | ||||
| 			future_stock_vouchers.append([d.voucher_type, d.voucher_no]) | ||||
| 
 | ||||
| 	return future_stock_vouchers | ||||
| 
 | ||||
| def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): | ||||
| 	gl_entries = {} | ||||
| 	if future_stock_vouchers: | ||||
| 		for d in frappe.db.sql("""select * from `tabGL Entry` | ||||
| 			where posting_date >= %s and voucher_no in (%s)""" % | ||||
| 			('%s', ', '.join(['%s']*len(future_stock_vouchers))), | ||||
| 			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) | ||||
| 
 | ||||
| 	return gl_entries | ||||
| @ -11,7 +11,7 @@ from frappe.model.document import Document | ||||
| from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account | ||||
| from erpnext.assets.doctype.asset.depreciation \ | ||||
| 	import get_disposal_account_and_cost_center, get_depreciation_accounts | ||||
| from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries | ||||
| from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries | ||||
| from erpnext.accounts.utils import get_account_currency | ||||
| from erpnext.controllers.accounts_controller import AccountsController | ||||
| 
 | ||||
| @ -22,6 +22,7 @@ class Asset(AccountsController): | ||||
| 		self.validate_item() | ||||
| 		self.set_missing_values() | ||||
| 		self.prepare_depreciation_data() | ||||
| 		self.validate_gross_and_purchase_amount() | ||||
| 		if self.get("schedules"): | ||||
| 			self.validate_expected_value_after_useful_life() | ||||
| 
 | ||||
| @ -31,7 +32,7 @@ class Asset(AccountsController): | ||||
| 		self.validate_in_use_date() | ||||
| 		self.set_status() | ||||
| 		self.make_asset_movement() | ||||
| 		if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.asset_category): | ||||
| 		if not self.booked_fixed_asset and self.validate_make_gl_entry(): | ||||
| 			self.make_gl_entries() | ||||
| 
 | ||||
| 	def before_cancel(self): | ||||
| @ -41,7 +42,8 @@ class Asset(AccountsController): | ||||
| 		self.validate_cancellation() | ||||
| 		self.delete_depreciation_entries() | ||||
| 		self.set_status() | ||||
| 		delete_gl_entries(voucher_type='Asset', voucher_no=self.name) | ||||
| 		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') | ||||
| 		make_reverse_gl_entries(voucher_type='Asset', voucher_no=self.name) | ||||
| 		self.db_set('booked_fixed_asset', 0) | ||||
| 
 | ||||
| 	def validate_asset_and_reference(self): | ||||
| @ -123,6 +125,12 @@ class Asset(AccountsController): | ||||
| 
 | ||||
| 		if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date): | ||||
| 			frappe.throw(_("Available-for-use Date should be after purchase date")) | ||||
| 	 | ||||
| 	def validate_gross_and_purchase_amount(self): | ||||
| 		if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount: | ||||
| 			frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\ | ||||
| 				Please do not book expense of multiple assets against one single Asset.") | ||||
| 				.format(frappe.bold("equal"), "<br>"), title=_("Invalid Gross Purchase Amount")) | ||||
| 
 | ||||
| 	def cancel_auto_gen_movement(self): | ||||
| 		movements = frappe.db.sql( | ||||
| @ -447,18 +455,55 @@ class Asset(AccountsController): | ||||
| 			for d in self.get('finance_books'): | ||||
| 				if d.finance_book == self.default_finance_book: | ||||
| 					return cint(d.idx) - 1 | ||||
| 	 | ||||
| 	def validate_make_gl_entry(self): | ||||
| 		purchase_document = self.get_purchase_document() | ||||
| 		asset_bought_with_invoice = purchase_document == self.purchase_invoice | ||||
| 		fixed_asset_account, cwip_account = self.get_asset_accounts() | ||||
| 		cwip_enabled = is_cwip_accounting_enabled(self.asset_category) | ||||
| 		# check if expense already has been booked in case of cwip was enabled after purchasing asset | ||||
| 		expense_booked = False | ||||
| 		cwip_booked = False | ||||
| 
 | ||||
| 		if asset_bought_with_invoice: | ||||
| 			expense_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""", | ||||
| 				(purchase_document, fixed_asset_account), as_dict=1) | ||||
| 		else: | ||||
| 			cwip_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""", | ||||
| 				(purchase_document, cwip_account), as_dict=1) | ||||
| 
 | ||||
| 		if cwip_enabled and (expense_booked or not cwip_booked): | ||||
| 			# if expense has already booked from invoice or cwip is booked from receipt | ||||
| 			return False | ||||
| 		elif not cwip_enabled and (not expense_booked or cwip_booked): | ||||
| 			# if cwip is disabled but expense hasn't been booked yet | ||||
| 			return True | ||||
| 		elif cwip_enabled: | ||||
| 			# default condition | ||||
| 			return True | ||||
| 
 | ||||
| 	def get_purchase_document(self): | ||||
| 		asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock') | ||||
| 		purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt | ||||
| 
 | ||||
| 		return purchase_document | ||||
| 	 | ||||
| 	def get_asset_accounts(self): | ||||
| 		fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name, | ||||
| 					asset_category = self.asset_category, company = self.company) | ||||
| 
 | ||||
| 		cwip_account = get_asset_account("capital_work_in_progress_account", | ||||
| 			self.name, self.asset_category, self.company) | ||||
| 		 | ||||
| 		return fixed_asset_account, cwip_account | ||||
| 
 | ||||
| 	def make_gl_entries(self): | ||||
| 		gl_entries = [] | ||||
| 
 | ||||
| 		if ((self.purchase_receipt \ | ||||
| 			or (self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock'))) | ||||
| 			and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()): | ||||
| 			fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name, | ||||
| 					asset_category = self.asset_category, company = self.company) | ||||
| 		purchase_document = self.get_purchase_document() | ||||
| 		fixed_asset_account, cwip_account = self.get_asset_accounts() | ||||
| 
 | ||||
| 			cwip_account = get_asset_account("capital_work_in_progress_account", | ||||
| 				self.name, self.asset_category, self.company) | ||||
| 		if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()): | ||||
| 
 | ||||
| 			gl_entries.append(self.get_gl_dict({ | ||||
| 				"account": cwip_account, | ||||
|  | ||||
| @ -66,9 +66,6 @@ class TestAsset(unittest.TestCase): | ||||
| 		pr.cancel() | ||||
| 		self.assertEqual(asset.docstatus, 2) | ||||
| 
 | ||||
| 		self.assertFalse(frappe.db.get_value("GL Entry", | ||||
| 			{"voucher_type": "Purchase Invoice", "voucher_no": pi.name})) | ||||
| 
 | ||||
| 	def test_is_fixed_asset_set(self): | ||||
| 		asset = create_asset(is_existing_asset = 1) | ||||
| 		doc = frappe.new_doc('Purchase Invoice') | ||||
| @ -82,7 +79,6 @@ class TestAsset(unittest.TestCase): | ||||
| 		doc.set_missing_values() | ||||
| 		self.assertEquals(doc.items[0].is_fixed_asset, 1) | ||||
| 
 | ||||
| 
 | ||||
| 	def test_schedule_for_straight_line_method(self): | ||||
| 		pr = make_purchase_receipt(item_code="Macbook Pro", | ||||
| 			qty=1, rate=100000.0, location="Test Location") | ||||
| @ -564,6 +560,81 @@ class TestAsset(unittest.TestCase): | ||||
| 
 | ||||
| 		self.assertEqual(gle, expected_gle) | ||||
| 
 | ||||
| 	def test_gle_with_cwip_toggling(self): | ||||
| 		# TEST: purchase an asset with cwip enabled and then disable cwip and try submitting the asset | ||||
| 		frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1) | ||||
| 
 | ||||
| 		pr = make_purchase_receipt(item_code="Macbook Pro", | ||||
| 			qty=1, rate=5000, do_not_submit=True, location="Test Location") | ||||
| 		pr.set('taxes', [{ | ||||
| 			'category': 'Total', | ||||
| 			'add_deduct_tax': 'Add', | ||||
| 			'charge_type': 'On Net Total', | ||||
| 			'account_head': '_Test Account Service Tax - _TC', | ||||
| 			'description': '_Test Account Service Tax', | ||||
| 			'cost_center': 'Main - _TC', | ||||
| 			'rate': 5.0 | ||||
| 		}, { | ||||
| 			'category': 'Valuation and Total', | ||||
| 			'add_deduct_tax': 'Add', | ||||
| 			'charge_type': 'On Net Total', | ||||
| 			'account_head': '_Test Account Shipping Charges - _TC', | ||||
| 			'description': '_Test Account Shipping Charges', | ||||
| 			'cost_center': 'Main - _TC', | ||||
| 			'rate': 5.0 | ||||
| 		}]) | ||||
| 		pr.submit() | ||||
| 		expected_gle = ( | ||||
| 			("Asset Received But Not Billed - _TC", 0.0, 5250.0), | ||||
| 			("CWIP Account - _TC", 5250.0, 0.0) | ||||
| 		) | ||||
| 		pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` | ||||
| 			where voucher_type='Purchase Receipt' and voucher_no = %s | ||||
| 			order by account""", pr.name) | ||||
| 		self.assertEqual(pr_gle, expected_gle) | ||||
| 
 | ||||
| 		pi = make_invoice(pr.name) | ||||
| 		pi.submit() | ||||
| 		expected_gle = ( | ||||
| 			("_Test Account Service Tax - _TC", 250.0, 0.0), | ||||
| 			("_Test Account Shipping Charges - _TC", 250.0, 0.0), | ||||
| 			("Asset Received But Not Billed - _TC", 5250.0, 0.0), | ||||
| 			("Creditors - _TC", 0.0, 5500.0), | ||||
| 			("Expenses Included In Asset Valuation - _TC", 0.0, 250.0), | ||||
| 		) | ||||
| 		pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` | ||||
| 			where voucher_type='Purchase Invoice' and voucher_no = %s | ||||
| 			order by account""", pi.name) | ||||
| 		self.assertEqual(pi_gle, expected_gle) | ||||
| 
 | ||||
| 		asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') | ||||
| 		asset_doc = frappe.get_doc('Asset', asset) | ||||
| 		month_end_date = get_last_day(nowdate()) | ||||
| 		asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) | ||||
| 		self.assertEqual(asset_doc.gross_purchase_amount, 5250.0) | ||||
| 		asset_doc.append("finance_books", { | ||||
| 			"expected_value_after_useful_life": 200, | ||||
| 			"depreciation_method": "Straight Line", | ||||
| 			"total_number_of_depreciations": 3, | ||||
| 			"frequency_of_depreciation": 10, | ||||
| 			"depreciation_start_date": month_end_date | ||||
| 		}) | ||||
| 
 | ||||
| 		# disable cwip and try submitting | ||||
| 		frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0) | ||||
| 		asset_doc.submit() | ||||
| 		# asset should have gl entries even if cwip is disabled | ||||
| 		expected_gle = ( | ||||
| 			("_Test Fixed Asset - _TC", 5250.0, 0.0), | ||||
| 			("CWIP Account - _TC", 0.0, 5250.0) | ||||
| 		) | ||||
| 		gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` | ||||
| 			where voucher_type='Asset' and voucher_no = %s | ||||
| 			order by account""", asset_doc.name) | ||||
| 		self.assertEqual(gle, expected_gle) | ||||
| 
 | ||||
| 		frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1) | ||||
| 
 | ||||
| 	def test_expense_head(self): | ||||
| 		pr = make_purchase_receipt(item_code="Macbook Pro", | ||||
| 			qty=2, rate=200000.0, location="Test Location") | ||||
| @ -599,6 +670,7 @@ def create_asset(**args): | ||||
| 		"purchase_date": "2015-01-01", | ||||
| 		"calculate_depreciation": 0, | ||||
| 		"gross_purchase_amount": 100000, | ||||
| 		"purchase_receipt_amount": 100000, | ||||
| 		"expected_value_after_useful_life": 10000, | ||||
| 		"warehouse": args.warehouse or "_Test Warehouse - _TC", | ||||
| 		"available_for_use_date": "2020-06-06", | ||||
|  | ||||
| @ -11,7 +11,8 @@ from frappe.model.document import Document | ||||
| class AssetCategory(Document): | ||||
| 	def validate(self): | ||||
| 		self.validate_finance_books() | ||||
| 		self.validate_accounts() | ||||
| 		self.validate_account_types() | ||||
| 		self.validate_account_currency() | ||||
| 
 | ||||
| 	def validate_finance_books(self): | ||||
| 		for d in self.finance_books: | ||||
| @ -19,7 +20,26 @@ class AssetCategory(Document): | ||||
| 				if cint(d.get(frappe.scrub(field)))<1: | ||||
| 					frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError) | ||||
| 	 | ||||
| 	def validate_accounts(self): | ||||
| 	def validate_account_currency(self): | ||||
| 		account_types = [ | ||||
| 			'fixed_asset_account', 'accumulated_depreciation_account', 'depreciation_expense_account', 'capital_work_in_progress_account' | ||||
| 		] | ||||
| 		invalid_accounts = [] | ||||
| 		for d in self.accounts: | ||||
| 			company_currency = frappe.get_value('Company', d.get('company_name'), 'default_currency') | ||||
| 			for type_of_account in account_types: | ||||
| 				if d.get(type_of_account): | ||||
| 					account_currency = frappe.get_value("Account", d.get(type_of_account), "account_currency") | ||||
| 					if account_currency != company_currency: | ||||
| 						invalid_accounts.append(frappe._dict({ 'type': type_of_account, 'idx': d.idx, 'account': d.get(type_of_account) })) | ||||
| 	 | ||||
| 		for d in invalid_accounts: | ||||
| 			frappe.throw(_("Row #{}: Currency of {} - {} doesn't matches company currency.") | ||||
| 				.format(d.idx, frappe.bold(frappe.unscrub(d.type)), frappe.bold(d.account)), | ||||
| 				title=_("Invalid Account")) | ||||
| 
 | ||||
| 	 | ||||
| 	def validate_account_types(self): | ||||
| 		account_type_map = { | ||||
| 			'fixed_asset_account': { 'account_type': 'Fixed Asset' }, | ||||
| 			'accumulated_depreciation_account': { 'account_type': 'Accumulated Depreciation' }, | ||||
|  | ||||
| @ -110,6 +110,7 @@ class AssetMovement(Document): | ||||
| 				ORDER BY | ||||
| 					asm.transaction_date asc | ||||
| 				""", (d.asset, self.company, 'Receipt'), as_dict=1) | ||||
| 
 | ||||
| 			if auto_gen_movement_entry and auto_gen_movement_entry[0].get('name') == self.name: | ||||
| 				frappe.throw(_('{0} will be cancelled automatically on asset cancellation as it was \ | ||||
| 					auto generated for Asset {1}').format(self.name, d.asset)) | ||||
|  | ||||
| @ -183,8 +183,8 @@ def get_data(): | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"label": _("Update Bank Transaction Dates"), | ||||
| 					"name": "Bank Reconciliation", | ||||
| 					"label": _("Update Bank Clearance Dates"), | ||||
| 					"name": "Bank Clearance", | ||||
| 					"description": _("Update bank payment dates with journals.") | ||||
| 				}, | ||||
| 				{ | ||||
| @ -245,6 +245,10 @@ def get_data(): | ||||
| 					"name": "Supplier Ledger Summary", | ||||
| 					"doctype": "Sales Invoice", | ||||
| 					"is_query_report": True, | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Process Deferred Accounting" | ||||
| 				} | ||||
| 			] | ||||
| 		}, | ||||
|  | ||||
| @ -170,6 +170,10 @@ def get_data(): | ||||
| 					"type": "doctype", | ||||
| 					"name": "Payroll Period", | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Income Tax Slab", | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Salary Component", | ||||
| @ -209,6 +213,10 @@ def get_data(): | ||||
| 					"name": "Employee Tax Exemption Proof Submission", | ||||
| 					"dependencies": ["Employee"] | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Employee Other Income", | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Employee Benefit Application", | ||||
|  | ||||
| @ -664,23 +664,26 @@ class AccountsController(TransactionBase): | ||||
| 	def set_total_advance_paid(self): | ||||
| 		if self.doctype == "Sales Order": | ||||
| 			dr_or_cr = "credit_in_account_currency" | ||||
| 			rev_dr_or_cr = "debit_in_account_currency" | ||||
| 			party = self.customer | ||||
| 		else: | ||||
| 			dr_or_cr = "debit_in_account_currency" | ||||
| 			rev_dr_or_cr = "credit_in_account_currency" | ||||
| 			party = self.supplier | ||||
| 
 | ||||
| 		advance = frappe.db.sql(""" | ||||
| 			select | ||||
| 				account_currency, sum({dr_or_cr}) as amount | ||||
| 				account_currency, sum({dr_or_cr}) - sum({rev_dr_cr}) as amount | ||||
| 			from | ||||
| 				`tabGL Entry` | ||||
| 			where | ||||
| 				against_voucher_type = %s and against_voucher = %s and party=%s | ||||
| 				and docstatus = 1 | ||||
| 		""".format(dr_or_cr=dr_or_cr), (self.doctype, self.name, party), as_dict=1) | ||||
| 		""".format(dr_or_cr=dr_or_cr, rev_dr_cr=rev_dr_or_cr), (self.doctype, self.name, party), as_dict=1) #nosec | ||||
| 
 | ||||
| 		if advance: | ||||
| 			advance = advance[0] | ||||
| 
 | ||||
| 			advance_paid = flt(advance.amount, self.precision("advance_paid")) | ||||
| 			formatted_advance_paid = fmt_money(advance_paid, precision=self.precision("advance_paid"), | ||||
| 											   currency=advance.account_currency) | ||||
|  | ||||
| @ -101,7 +101,7 @@ class BuyingController(StockController): | ||||
| 				for d in tax_for_valuation: | ||||
| 					d.category = 'Total' | ||||
| 				msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items')) | ||||
| 	 | ||||
| 
 | ||||
| 	def validate_asset_return(self): | ||||
| 		if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return: | ||||
| 			return | ||||
| @ -691,10 +691,10 @@ class BuyingController(StockController): | ||||
| 						for qty in range(cint(d.qty)): | ||||
| 							asset = self.make_asset(d) | ||||
| 							created_assets.append(asset) | ||||
| 						 | ||||
| 
 | ||||
| 						if len(created_assets) > 5: | ||||
| 							# dont show asset form links if more than 5 assets are created | ||||
| 							messages.append(_('{} Asset{} created for {}').format(len(created_assets), is_plural, frappe.bold(d.item_code))) | ||||
| 							messages.append(_('{} Assets created for {}').format(len(created_assets), frappe.bold(d.item_code))) | ||||
| 						else: | ||||
| 							assets_link = list(map(lambda d: frappe.utils.get_link_to_form('Asset', d), created_assets)) | ||||
| 							assets_link = frappe.bold(','.join(assets_link)) | ||||
|  | ||||
| @ -74,7 +74,7 @@ def validate_returned_items(doc): | ||||
| 	for d in doc.get("items"): | ||||
| 		if d.item_code and (flt(d.qty) < 0 or flt(d.get('received_qty')) < 0): | ||||
| 			if d.item_code not in valid_items: | ||||
| 				frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}") | ||||
| 				frappe.throw(_("Row # {0}: Returned Item {1} does not exist in {2} {3}") | ||||
| 					.format(d.idx, d.item_code, doc.doctype, doc.return_against)) | ||||
| 			else: | ||||
| 				ref = valid_items.get(d.item_code, frappe._dict()) | ||||
| @ -266,6 +266,8 @@ def make_return_doc(doctype, source_name, target_doc=None): | ||||
| 			target_doc.purchase_order = source_doc.purchase_order | ||||
| 			target_doc.purchase_order_item = source_doc.purchase_order_item | ||||
| 			target_doc.rejected_warehouse = source_doc.rejected_warehouse | ||||
| 			target_doc.purchase_receipt_item = source_doc.name | ||||
| 
 | ||||
| 		elif doctype == "Purchase Invoice": | ||||
| 			target_doc.received_qty = -1* source_doc.received_qty | ||||
| 			target_doc.rejected_qty = -1* source_doc.rejected_qty | ||||
| @ -282,6 +284,7 @@ def make_return_doc(doctype, source_name, target_doc=None): | ||||
| 			target_doc.so_detail = source_doc.so_detail | ||||
| 			target_doc.si_detail = source_doc.si_detail | ||||
| 			target_doc.expense_account = source_doc.expense_account | ||||
| 			target_doc.dn_detail = source_doc.name | ||||
| 			if default_warehouse_for_sales_return: | ||||
| 				target_doc.warehouse = default_warehouse_for_sales_return | ||||
| 		elif doctype == "Sales Invoice": | ||||
|  | ||||
| @ -165,9 +165,9 @@ class SellingController(StockController): | ||||
| 				d.stock_qty = flt(d.qty) * flt(d.conversion_factor) | ||||
| 
 | ||||
| 	def validate_selling_price(self): | ||||
| 		def throw_message(item_name, rate, ref_rate_field): | ||||
| 			frappe.throw(_("""Selling rate for item {0} is lower than its {1}. Selling rate should be atleast {2}""") | ||||
| 				.format(item_name, ref_rate_field, rate)) | ||||
| 		def throw_message(idx, item_name, rate, ref_rate_field): | ||||
| 			frappe.throw(_("""Row #{}: Selling rate for item {} is lower than its {}. Selling rate should be atleast {}""") | ||||
| 				.format(idx, item_name, ref_rate_field, rate)) | ||||
| 
 | ||||
| 		if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): | ||||
| 			return | ||||
| @ -181,8 +181,8 @@ class SellingController(StockController): | ||||
| 
 | ||||
| 			last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"]) | ||||
| 			last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1) | ||||
| 			if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom) and not self.get('is_internal_customer'): | ||||
| 				throw_message(it.item_name, last_purchase_rate_in_sales_uom, "last purchase rate") | ||||
| 			if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom): | ||||
| 				throw_message(it.idx, frappe.bold(it.item_name), last_purchase_rate_in_sales_uom, "last purchase rate") | ||||
| 
 | ||||
| 			last_valuation_rate = frappe.db.sql(""" | ||||
| 				SELECT valuation_rate FROM `tabStock Ledger Entry` WHERE item_code = %s | ||||
| @ -193,7 +193,7 @@ class SellingController(StockController): | ||||
| 				last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1) | ||||
| 				if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate_in_sales_uom) \ | ||||
| 					and not self.get('is_internal_customer'): | ||||
| 					throw_message(it.name, last_valuation_rate_in_sales_uom, "valuation rate") | ||||
| 					throw_message(it.idx, frappe.bold(it.item_name), last_valuation_rate_in_sales_uom, "valuation rate") | ||||
| 
 | ||||
| 
 | ||||
| 	def get_item_list(self): | ||||
|  | ||||
| @ -7,7 +7,7 @@ from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate | ||||
| from frappe import _ | ||||
| import frappe.defaults | ||||
| from erpnext.accounts.utils import get_fiscal_year | ||||
| from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map | ||||
| from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map | ||||
| from erpnext.controllers.accounts_controller import AccountsController | ||||
| from erpnext.stock.stock_ledger import get_valuation_rate | ||||
| from erpnext.stock import get_warehouse_account_map | ||||
| @ -23,9 +23,9 @@ class StockController(AccountsController): | ||||
| 		self.validate_serialized_batch() | ||||
| 		self.validate_customer_provided_item() | ||||
| 
 | ||||
| 	def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): | ||||
| 	def make_gl_entries(self, gl_entries=None): | ||||
| 		if self.docstatus == 2: | ||||
| 			delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) | ||||
| 			make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) | ||||
| 
 | ||||
| 		if cint(erpnext.is_perpetual_inventory_enabled(self.company)): | ||||
| 			warehouse_account = get_warehouse_account_map(self.company) | ||||
| @ -33,16 +33,12 @@ class StockController(AccountsController): | ||||
| 			if self.docstatus==1: | ||||
| 				if not gl_entries: | ||||
| 					gl_entries = self.get_gl_entries(warehouse_account) | ||||
| 				make_gl_entries(gl_entries, from_repost=from_repost) | ||||
| 				make_gl_entries(gl_entries) | ||||
| 
 | ||||
| 			if (repost_future_gle or self.flags.repost_future_gle): | ||||
| 				items, warehouses = self.get_items_and_warehouses() | ||||
| 				update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items, | ||||
| 					warehouse_account, company=self.company) | ||||
| 		elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1: | ||||
| 			gl_entries = [] | ||||
| 			gl_entries = self.get_asset_gl_entry(gl_entries) | ||||
| 			make_gl_entries(gl_entries, from_repost=from_repost) | ||||
| 			make_gl_entries(gl_entries) | ||||
| 
 | ||||
| 	def validate_serialized_batch(self): | ||||
| 		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos | ||||
| @ -274,21 +270,21 @@ class StockController(AccountsController): | ||||
| 			"batch_no": cstr(d.get("batch_no")).strip(), | ||||
| 			"serial_no": d.get("serial_no"), | ||||
| 			"project": d.get("project") or self.get('project'), | ||||
| 			"is_cancelled": self.docstatus==2 and "Yes" or "No" | ||||
| 			"is_cancelled": 1 if self.docstatus==2 else 0 | ||||
| 		}) | ||||
| 
 | ||||
| 		sl_dict.update(args) | ||||
| 		return sl_dict | ||||
| 
 | ||||
| 	def make_sl_entries(self, sl_entries, is_amended=None, allow_negative_stock=False, | ||||
| 	def make_sl_entries(self, sl_entries, allow_negative_stock=False, | ||||
| 			via_landed_cost_voucher=False): | ||||
| 		from erpnext.stock.stock_ledger import make_sl_entries | ||||
| 		make_sl_entries(sl_entries, is_amended, allow_negative_stock, via_landed_cost_voucher) | ||||
| 		make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher) | ||||
| 
 | ||||
| 	def make_gl_entries_on_cancel(self, repost_future_gle=True): | ||||
| 	def make_gl_entries_on_cancel(self): | ||||
| 		if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s | ||||
| 			and voucher_no=%s""", (self.doctype, self.name)): | ||||
| 				self.make_gl_entries(repost_future_gle=repost_future_gle) | ||||
| 				self.make_gl_entries() | ||||
| 
 | ||||
| 	def get_serialized_items(self): | ||||
| 		serialized_items = [] | ||||
| @ -391,29 +387,6 @@ class StockController(AccountsController): | ||||
| 			if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): | ||||
| 				d.allow_zero_valuation_rate = 1 | ||||
| 
 | ||||
| def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None, | ||||
| 		warehouse_account=None, company=None): | ||||
| 	def _delete_gl_entries(voucher_type, voucher_no): | ||||
| 		frappe.db.sql("""delete from `tabGL Entry` | ||||
| 			where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) | ||||
| 
 | ||||
| 	if not warehouse_account: | ||||
| 		warehouse_account = get_warehouse_account_map(company) | ||||
| 
 | ||||
| 	future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items) | ||||
| 	gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date) | ||||
| 
 | ||||
| 	for voucher_type, voucher_no in future_stock_vouchers: | ||||
| 		existing_gle = gle.get((voucher_type, voucher_no), []) | ||||
| 		voucher_obj = frappe.get_doc(voucher_type, voucher_no) | ||||
| 		expected_gle = voucher_obj.get_gl_entries(warehouse_account) | ||||
| 		if expected_gle: | ||||
| 			if not existing_gle or not compare_existing_and_expected_gle(existing_gle, expected_gle): | ||||
| 				_delete_gl_entries(voucher_type, voucher_no) | ||||
| 				voucher_obj.make_gl_entries(gl_entries=expected_gle, repost_future_gle=False, from_repost=True) | ||||
| 		else: | ||||
| 			_delete_gl_entries(voucher_type, voucher_no) | ||||
| 
 | ||||
| def compare_existing_and_expected_gle(existing_gle, expected_gle): | ||||
| 	matched = True | ||||
| 	for entry in expected_gle: | ||||
| @ -430,36 +403,3 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle): | ||||
| 			matched = False | ||||
| 			break | ||||
| 	return matched | ||||
| 
 | ||||
| def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None): | ||||
| 	future_stock_vouchers = [] | ||||
| 
 | ||||
| 	values = [] | ||||
| 	condition = "" | ||||
| 	if for_items: | ||||
| 		condition += " and item_code in ({})".format(", ".join(["%s"] * len(for_items))) | ||||
| 		values += for_items | ||||
| 
 | ||||
| 	if for_warehouses: | ||||
| 		condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses))) | ||||
| 		values += for_warehouses | ||||
| 
 | ||||
| 	for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no | ||||
| 		from `tabStock Ledger Entry` sle | ||||
| 		where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition} | ||||
| 		order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition), | ||||
| 		tuple([posting_date, posting_time] + values), as_dict=True): | ||||
| 			future_stock_vouchers.append([d.voucher_type, d.voucher_no]) | ||||
| 
 | ||||
| 	return future_stock_vouchers | ||||
| 
 | ||||
| def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): | ||||
| 	gl_entries = {} | ||||
| 	if future_stock_vouchers: | ||||
| 		for d in frappe.db.sql("""select * from `tabGL Entry` | ||||
| 			where posting_date >= %s and voucher_no in (%s)""" % | ||||
| 			('%s', ', '.join(['%s']*len(future_stock_vouchers))), | ||||
| 			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) | ||||
| 
 | ||||
| 	return gl_entries | ||||
|  | ||||
							
								
								
									
										24
									
								
								erpnext/crm/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								erpnext/crm/utils.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| import frappe | ||||
| 
 | ||||
| 
 | ||||
| def update_lead_phone_numbers(contact, method): | ||||
| 	if contact.phone_nos: | ||||
| 		contact_lead = contact.get_link_for("Lead") | ||||
| 		if contact_lead: | ||||
| 			phone = mobile_no = contact.phone_nos[0].phone | ||||
| 
 | ||||
| 			if len(contact.phone_nos) > 1: | ||||
| 				# get the default phone number | ||||
| 				primary_phones = [phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_phone] | ||||
| 				if primary_phones: | ||||
| 					phone = primary_phones[0] | ||||
| 
 | ||||
| 				# get the default mobile number | ||||
| 				primary_mobile_nos = [phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_mobile_no] | ||||
| 				if primary_mobile_nos: | ||||
| 					mobile_no = primary_mobile_nos[0] | ||||
| 
 | ||||
| 			lead = frappe.get_doc("Lead", contact_lead) | ||||
| 			lead.phone = phone | ||||
| 			lead.mobile_no = mobile_no | ||||
| 			lead.save() | ||||
| @ -10,7 +10,7 @@ from frappe.utils import money_in_words | ||||
| from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request | ||||
| from frappe.utils.csvutils import getlink | ||||
| from erpnext.controllers.accounts_controller import AccountsController | ||||
| from erpnext.accounts.general_ledger import delete_gl_entries | ||||
| from erpnext.accounts.general_ledger import make_reverse_gl_entries | ||||
| 
 | ||||
| 
 | ||||
| class Fees(AccountsController): | ||||
| @ -81,7 +81,8 @@ class Fees(AccountsController): | ||||
| 			frappe.msgprint(_("Payment request {0} created").format(getlink("Payment Request", pr.name))) | ||||
| 
 | ||||
| 	def on_cancel(self): | ||||
| 		delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) | ||||
| 		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') | ||||
| 		make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) | ||||
| 		# frappe.db.set(self, 'status', 'Cancelled') | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -1,23 +0,0 @@ | ||||
| /* eslint-disable */ | ||||
| // rename this file from _test_[name] to test_[name] to activate
 | ||||
| // and remove above this line
 | ||||
| 
 | ||||
| QUnit.test("test: Video", function (assert) { | ||||
| 	let done = assert.async(); | ||||
| 
 | ||||
| 	// number of asserts
 | ||||
| 	assert.expect(1); | ||||
| 
 | ||||
| 	frappe.run_serially([ | ||||
| 		// insert a new Video
 | ||||
| 		() => frappe.tests.make('Video', [ | ||||
| 			// values to be set
 | ||||
| 			{key: 'value'} | ||||
| 		]), | ||||
| 		() => { | ||||
| 			assert.equal(cur_frm.doc.key, 'value'); | ||||
| 		}, | ||||
| 		() => done() | ||||
| 	]); | ||||
| 
 | ||||
| }); | ||||
| @ -1,10 +0,0 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| import frappe | ||||
| import unittest | ||||
| 
 | ||||
| class TestVideo(unittest.TestCase): | ||||
| 	pass | ||||
| @ -1,8 +0,0 @@ | ||||
| // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| 
 | ||||
| frappe.ui.form.on('Video', { | ||||
| 	refresh: function(frm) { | ||||
| 
 | ||||
| 	} | ||||
| }); | ||||
| @ -1,112 +0,0 @@ | ||||
| { | ||||
|  "allow_import": 1, | ||||
|  "allow_rename": 1, | ||||
|  "autoname": "field:title", | ||||
|  "creation": "2018-10-17 05:47:13.087395", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "title", | ||||
|   "provider", | ||||
|   "url", | ||||
|   "column_break_4", | ||||
|   "publish_date", | ||||
|   "duration", | ||||
|   "section_break_7", | ||||
|   "description" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "title", | ||||
|    "fieldtype": "Data", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Title", | ||||
|    "reqd": 1, | ||||
|    "unique": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "description", | ||||
|    "fieldtype": "Text Editor", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Description", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "duration", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "Duration" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "url", | ||||
|    "fieldtype": "Data", | ||||
|    "in_list_view": 1, | ||||
|    "label": "URL", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "publish_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "Publish Date" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "provider", | ||||
|    "fieldtype": "Select", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Provider", | ||||
|    "options": "YouTube\nVimeo", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_4", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_7", | ||||
|    "fieldtype": "Section Break" | ||||
|   } | ||||
|  ], | ||||
|  "modified": "2019-06-12 12:36:48.753092", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Education", | ||||
|  "name": "Video", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Academics User", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Instructor", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   }, | ||||
|   { | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "LMS User", | ||||
|    "share": 1 | ||||
|   } | ||||
|  ], | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "track_changes": 1 | ||||
| } | ||||
| @ -47,7 +47,12 @@ | ||||
|   } | ||||
|  ], | ||||
|  "category": "Domains", | ||||
|  "charts": [], | ||||
|  "charts": [ | ||||
|   { | ||||
|    "chart_name": "Patient Appointments", | ||||
|    "label": "Patient Appointments" | ||||
|   } | ||||
|  ], | ||||
|  "charts_label": "", | ||||
|  "creation": "2020-03-02 17:23:17.919682", | ||||
|  "developer_mode_only": 0, | ||||
| @ -58,7 +63,7 @@ | ||||
|  "idx": 0, | ||||
|  "is_standard": 1, | ||||
|  "label": "Healthcare", | ||||
|  "modified": "2020-04-20 11:42:43.889576", | ||||
|  "modified": "2020-04-25 22:31:36.576444", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Healthcare", | ||||
|  "name": "Healthcare", | ||||
|  | ||||
| @ -80,6 +80,7 @@ frappe.ui.form.on('Clinical Procedure', { | ||||
| 							frappe.call({ | ||||
| 								method: 'complete_procedure', | ||||
| 								doc: frm.doc, | ||||
| 								freeze: true, | ||||
| 								callback: function(r) { | ||||
| 									if (r.message) { | ||||
| 										frappe.show_alert({ | ||||
| @ -87,8 +88,8 @@ frappe.ui.form.on('Clinical Procedure', { | ||||
| 												['<a class="bold" href="#Form/Stock Entry/'+ r.message + '">' + r.message + '</a>']), | ||||
| 											indicator: 'green' | ||||
| 										}); | ||||
| 										frm.reload_doc(); | ||||
| 									} | ||||
| 									frm.reload_doc(); | ||||
| 								} | ||||
| 							}); | ||||
| 						} | ||||
| @ -111,9 +112,10 @@ frappe.ui.form.on('Clinical Procedure', { | ||||
| 											frappe.call({ | ||||
| 												doc: frm.doc, | ||||
| 												method: 'make_material_receipt', | ||||
| 												freeze: true, | ||||
| 												callback: function(r) { | ||||
| 													if (!r.exc) { | ||||
| 														cur_frm.reload_doc(); | ||||
| 														frm.reload_doc(); | ||||
| 														let doclist = frappe.model.sync(r.message); | ||||
| 														frappe.set_route('Form', doclist[0].doctype, doclist[0].name); | ||||
| 													} | ||||
| @ -122,7 +124,7 @@ frappe.ui.form.on('Clinical Procedure', { | ||||
| 										} | ||||
| 									); | ||||
| 								} else { | ||||
| 									cur_frm.reload_doc(); | ||||
| 									frm.reload_doc(); | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
|  | ||||
| @ -87,7 +87,8 @@ class ClinicalProcedure(Document): | ||||
| 			else: | ||||
| 				frappe.throw(_('Please set Customer in Patient {0}').format(frappe.bold(self.patient)), title=_('Customer Not Found')) | ||||
| 
 | ||||
| 		frappe.db.set_value('Clinical Procedure', self.name, 'status', 'Completed') | ||||
| 		self.db_set('status', 'Completed') | ||||
| 
 | ||||
| 		if self.consume_stock and self.items: | ||||
| 			return stock_entry | ||||
| 
 | ||||
| @ -245,9 +246,9 @@ def make_procedure(source_name, target_doc=None): | ||||
| 
 | ||||
| 
 | ||||
| def insert_clinical_procedure_to_medical_record(doc): | ||||
| 	subject = cstr(doc.procedure_template) | ||||
| 	subject = frappe.bold(_("Clinical Procedure conducted: ")) + cstr(doc.procedure_template) + "<br>" | ||||
| 	if doc.practitioner: | ||||
| 		subject += ' ' + doc.practitioner | ||||
| 		subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner | ||||
| 	if subject and doc.notes: | ||||
| 		subject += '<br/>' + doc.notes | ||||
| 
 | ||||
|  | ||||
| @ -24,6 +24,8 @@ erpnext.ExerciseEditor = Class.extend({ | ||||
| 
 | ||||
| 		this.exercise_cards = $('<div class="exercise-cards"></div>').appendTo(this.wrapper); | ||||
| 
 | ||||
| 		this.row = $('<div class="exercise-row"></div>').appendTo(this.wrapper); | ||||
| 
 | ||||
| 		let me = this; | ||||
| 
 | ||||
| 		this.exercise_toolbar.find(".btn-add") | ||||
| @ -32,7 +34,7 @@ erpnext.ExerciseEditor = Class.extend({ | ||||
| 				me.show_add_card_dialog(frm); | ||||
| 			}); | ||||
| 
 | ||||
| 		if (frm.doc.steps_table.length > 0) { | ||||
| 		if (frm.doc.steps_table && frm.doc.steps_table.length > 0) { | ||||
| 			this.make_cards(frm); | ||||
| 			this.make_buttons(frm); | ||||
| 		} | ||||
| @ -41,7 +43,6 @@ erpnext.ExerciseEditor = Class.extend({ | ||||
| 	make_cards: function(frm) { | ||||
| 		var me = this; | ||||
| 		$(me.exercise_cards).empty(); | ||||
| 		this.row = $('<div class="exercise-row"></div>').appendTo(me.exercise_cards); | ||||
| 
 | ||||
| 		$.each(frm.doc.steps_table, function(i, step) { | ||||
| 			$(repl(` | ||||
| @ -78,6 +79,7 @@ erpnext.ExerciseEditor = Class.extend({ | ||||
| 				frm.doc.steps_table.pop(id); | ||||
| 				frm.refresh_field('steps_table'); | ||||
| 				$('#col-'+id).remove(); | ||||
| 				frm.dirty(); | ||||
| 			}, 300); | ||||
| 		}); | ||||
| 	}, | ||||
| @ -106,7 +108,10 @@ erpnext.ExerciseEditor = Class.extend({ | ||||
| 			], | ||||
| 			primary_action: function() { | ||||
| 				let data = d.get_values(); | ||||
| 				let i = frm.doc.steps_table.length; | ||||
| 				let i = 0; | ||||
| 				if (frm.doc.steps_table) { | ||||
| 					i = frm.doc.steps_table.length; | ||||
| 				} | ||||
| 				$(repl(` | ||||
| 					<div class="exercise-col col-sm-4" id="%(col_id)s"> | ||||
| 						<div class="card h-100 exercise-card" id="%(card_id)s"> | ||||
| @ -165,9 +170,10 @@ erpnext.ExerciseEditor = Class.extend({ | ||||
| 				frm.doc.steps_table[id].image = data.image; | ||||
| 				frm.doc.steps_table[id].description = data.step_description; | ||||
| 				refresh_field('steps_table'); | ||||
| 				frm.dirty(); | ||||
| 				new_dialog.hide(); | ||||
| 			}, | ||||
| 			primary_action_label: __("Save"), | ||||
| 			primary_action_label: __("Edit"), | ||||
| 		}); | ||||
| 
 | ||||
| 		new_dialog.set_values({ | ||||
|  | ||||
| @ -288,23 +288,23 @@ def insert_lab_test_to_medical_record(doc): | ||||
| 	table_row = False | ||||
| 	subject = cstr(doc.lab_test_name) | ||||
| 	if doc.practitioner: | ||||
| 		subject += " "+ doc.practitioner | ||||
| 		subject += frappe.bold(_("Healthcare Practitioner: "))+ doc.practitioner + "<br>" | ||||
| 	if doc.normal_test_items: | ||||
| 		item = doc.normal_test_items[0] | ||||
| 		comment = "" | ||||
| 		if item.lab_test_comment: | ||||
| 			comment = str(item.lab_test_comment) | ||||
| 		table_row = item.lab_test_name | ||||
| 		table_row = frappe.bold(_("Lab Test Conducted: ")) + item.lab_test_name | ||||
| 
 | ||||
| 		if item.lab_test_event: | ||||
| 			table_row += " " + item.lab_test_event | ||||
| 			table_row += frappe.bold(_("Lab Test Event: ")) + item.lab_test_event | ||||
| 
 | ||||
| 		if item.result_value: | ||||
| 			table_row += " " + item.result_value | ||||
| 			table_row += " " + frappe.bold(_("Lab Test Result: ")) + item.result_value | ||||
| 
 | ||||
| 		if item.normal_range: | ||||
| 			table_row += " normal_range("+item.normal_range+")" | ||||
| 		table_row += " "+comment | ||||
| 			table_row += " " + _("Normal Range:") + item.normal_range | ||||
| 		table_row += " " + comment | ||||
| 
 | ||||
| 	elif doc.special_test_items: | ||||
| 		item = doc.special_test_items[0] | ||||
| @ -316,12 +316,12 @@ def insert_lab_test_to_medical_record(doc): | ||||
| 		item = doc.sensitivity_test_items[0] | ||||
| 
 | ||||
| 		if item.antibiotic and item.antibiotic_sensitivity: | ||||
| 			table_row = item.antibiotic +" "+ item.antibiotic_sensitivity | ||||
| 			table_row = item.antibiotic + " " + item.antibiotic_sensitivity | ||||
| 
 | ||||
| 	if table_row: | ||||
| 		subject += "<br/>"+table_row | ||||
| 		subject += "<br>" + table_row | ||||
| 	if doc.lab_test_comment: | ||||
| 		subject += "<br/>"+ cstr(doc.lab_test_comment) | ||||
| 		subject += "<br>" + cstr(doc.lab_test_comment) | ||||
| 
 | ||||
| 	medical_record = frappe.new_doc("Patient Medical Record") | ||||
| 	medical_record.patient = doc.patient | ||||
|  | ||||
| @ -325,7 +325,11 @@ def update_status(appointment_id, status): | ||||
| def send_confirmation_msg(doc): | ||||
| 	if frappe.db.get_single_value('Healthcare Settings', 'send_appointment_confirmation'): | ||||
| 		message = frappe.db.get_single_value('Healthcare Settings', 'appointment_confirmation_msg') | ||||
| 		send_message(doc, message) | ||||
| 		try: | ||||
| 			send_message(doc, message) | ||||
| 		except Exception: | ||||
| 			frappe.log_error(frappe.get_traceback(), _('Appointment Confirmation Message Not Sent')) | ||||
| 			frappe.msgprint(_('Appointment Confirmation Message Not Sent'), indicator='orange') | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
|  | ||||
| @ -18,6 +18,9 @@ class PatientEncounter(Document): | ||||
| 	def after_insert(self): | ||||
| 		insert_encounter_to_medical_record(self) | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		update_encounter_medical_record(self) | ||||
| 
 | ||||
| 	def on_cancel(self): | ||||
| 		if self.appointment: | ||||
| 			frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open') | ||||
| @ -66,22 +69,26 @@ def delete_medical_record(encounter): | ||||
| 	frappe.db.delete_doc_if_exists('Patient Medical Record', 'reference_name', encounter.name) | ||||
| 
 | ||||
| def set_subject_field(encounter): | ||||
| 	subject = encounter.practitioner + '\n' | ||||
| 	subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>' | ||||
| 	if encounter.symptoms: | ||||
| 		subject += _('Symptoms: ') + cstr(encounter.symptoms) + '\n' | ||||
| 		subject += frappe.bold(_('Symptoms: ')) + '<br>' | ||||
| 		for entry in encounter.symptoms: | ||||
| 			subject += cstr(entry.complaint) + '<br>' | ||||
| 	else: | ||||
| 		subject +=  _('No Symptoms') + '\n' | ||||
| 		subject += frappe.bold(_('No Symptoms')) + '<br>' | ||||
| 
 | ||||
| 	if encounter.diagnosis: | ||||
| 		subject += _('Diagnosis: ') + cstr(encounter.diagnosis) + '\n' | ||||
| 		subject += frappe.bold(_('Diagnosis: ')) + '<br>' | ||||
| 		for entry in encounter.diagnosis: | ||||
| 			subject += cstr(entry.diagnosis) + '<br>' | ||||
| 	else: | ||||
| 		subject += _('No Diagnosis') + '\n' | ||||
| 		subject += frappe.bold(_('No Diagnosis')) + '<br>' | ||||
| 
 | ||||
| 	if encounter.drug_prescription: | ||||
| 		subject += '\n' + _('Drug(s) Prescribed.') | ||||
| 		subject += '<br>' + _('Drug(s) Prescribed.') | ||||
| 	if encounter.lab_test_prescription: | ||||
| 		subject += '\n' + _('Test(s) Prescribed.') | ||||
| 		subject += '<br>' + _('Test(s) Prescribed.') | ||||
| 	if encounter.procedure_prescription: | ||||
| 		subject += '\n' + _('Procedure(s) Prescribed.') | ||||
| 		subject += '<br>' + _('Procedure(s) Prescribed.') | ||||
| 
 | ||||
| 	return subject | ||||
|  | ||||
| @ -57,7 +57,7 @@ | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "subject", | ||||
|    "fieldtype": "Small Text", | ||||
|    "fieldtype": "Text Editor", | ||||
|    "ignore_xss_filter": 1, | ||||
|    "label": "Subject" | ||||
|   }, | ||||
| @ -125,7 +125,7 @@ | ||||
|  ], | ||||
|  "in_create": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-03-23 19:26:59.308383", | ||||
|  "modified": "2020-04-29 12:26:57.679402", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Healthcare", | ||||
|  "name": "Patient Medical Record", | ||||
|  | ||||
| @ -21,8 +21,14 @@ class TherapyPlan(Document): | ||||
| 				self.status = 'Completed' | ||||
| 
 | ||||
| 	def set_totals(self): | ||||
| 		total_sessions = sum([int(d.no_of_sessions) for d in self.get('therapy_plan_details')]) | ||||
| 		total_sessions_completed = sum([int(d.sessions_completed) for d in self.get('therapy_plan_details')]) | ||||
| 		total_sessions = 0 | ||||
| 		total_sessions_completed = 0 | ||||
| 		for entry in self.therapy_plan_details: | ||||
| 			if entry.no_of_sessions: | ||||
| 				total_sessions += entry.no_of_sessions | ||||
| 			if entry.sessions_completed: | ||||
| 				total_sessions_completed += entry.sessions_completed | ||||
| 
 | ||||
| 		self.db_set('total_sessions', total_sessions) | ||||
| 		self.db_set('total_sessions_completed', total_sessions_completed) | ||||
| 
 | ||||
|  | ||||
| @ -13,23 +13,92 @@ frappe.ui.form.on('Therapy Session', { | ||||
| 
 | ||||
| 	refresh: function(frm) { | ||||
| 		if (!frm.doc.__islocal) { | ||||
| 			let target = 0; | ||||
| 			let completed = 0; | ||||
| 			$.each(frm.doc.exercises, function(_i, e) { | ||||
| 				target += e.counts_target; | ||||
| 				completed += e.counts_completed; | ||||
| 			}); | ||||
| 			frm.dashboard.add_indicator(__('Counts Targetted: {0}', [target]), 'blue'); | ||||
| 			frm.dashboard.add_indicator(__('Counts Completed: {0}', [completed]), (completed < target) ? 'orange' : 'green'); | ||||
| 			frm.dashboard.add_indicator(__('Counts Targeted: {0}', [frm.doc.total_counts_targeted]), 'blue'); | ||||
| 			frm.dashboard.add_indicator(__('Counts Completed: {0}', [frm.doc.total_counts_completed]), | ||||
| 				(frm.doc.total_counts_completed < frm.doc.total_counts_targeted) ? 'orange' : 'green'); | ||||
| 		} | ||||
| 
 | ||||
| 		if (frm.doc.docstatus === 1) { | ||||
| 			frm.add_custom_button(__('Patient Assessment'),function() { | ||||
| 			frm.add_custom_button(__('Patient Assessment'), function() { | ||||
| 				frappe.model.open_mapped_doc({ | ||||
| 					method: 'erpnext.healthcare.doctype.patient_assessment.patient_assessment.create_patient_assessment', | ||||
| 					frm: frm, | ||||
| 				}) | ||||
| 			}, 'Create'); | ||||
| 
 | ||||
| 			frm.add_custom_button(__('Sales Invoice'), function() { | ||||
| 				frappe.model.open_mapped_doc({ | ||||
| 					method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.invoice_therapy_session', | ||||
| 					frm: frm, | ||||
| 				}) | ||||
| 			}, 'Create'); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	patient: function(frm) { | ||||
| 		if (frm.doc.patient) { | ||||
| 			frappe.call({ | ||||
| 				'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail', | ||||
| 				args: { | ||||
| 					patient: frm.doc.patient | ||||
| 				}, | ||||
| 				callback: function (data) { | ||||
| 					let age = ''; | ||||
| 					if (data.message.dob) { | ||||
| 						age = calculate_age(data.message.dob); | ||||
| 					} else if (data.message.age) { | ||||
| 						age = data.message.age; | ||||
| 						if (data.message.age_as_on) { | ||||
| 							age = __('{0} as on {1}', [age, data.message.age_as_on]); | ||||
| 						} | ||||
| 					} | ||||
| 					frm.set_value('patient_age', age); | ||||
| 					frm.set_value('gender', data.message.sex); | ||||
| 					frm.set_value('patient_name', data.message.patient_name); | ||||
| 				} | ||||
| 			}); | ||||
| 		} else { | ||||
| 			frm.set_value('patient_age', ''); | ||||
| 			frm.set_value('gender', ''); | ||||
| 			frm.set_value('patient_name', ''); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	appointment: function(frm) { | ||||
| 		if (frm.doc.appointment) { | ||||
| 			frappe.call({ | ||||
| 				'method': 'frappe.client.get', | ||||
| 				args: { | ||||
| 					doctype: 'Patient Appointment', | ||||
| 					name: frm.doc.appointment | ||||
| 				}, | ||||
| 				callback: function(data) { | ||||
| 					let values = { | ||||
| 						'patient':data.message.patient, | ||||
| 						'therapy_type': data.message.therapy_type, | ||||
| 						'therapy_plan': data.message.therapy_plan, | ||||
| 						'practitioner': data.message.practitioner, | ||||
| 						'department': data.message.department, | ||||
| 						'start_date': data.message.appointment_date, | ||||
| 						'start_time': data.message.appointment_time, | ||||
| 						'service_unit': data.message.service_unit, | ||||
| 						'company': data.message.company | ||||
| 					}; | ||||
| 					frm.set_value(values); | ||||
| 				} | ||||
| 			}); | ||||
| 		} else { | ||||
| 			let values = { | ||||
| 				'patient': '', | ||||
| 				'therapy_type': '', | ||||
| 				'therapy_plan': '', | ||||
| 				'practitioner': '', | ||||
| 				'department': '', | ||||
| 				'start_date': '', | ||||
| 				'start_time': '', | ||||
| 				'service_unit': '', | ||||
| 			}; | ||||
| 			frm.set_value(values); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| @ -44,6 +113,8 @@ frappe.ui.form.on('Therapy Session', { | ||||
| 				callback: function(data) { | ||||
| 					frm.set_value('duration', data.message.default_duration); | ||||
| 					frm.set_value('rate', data.message.rate); | ||||
| 					frm.set_value('service_unit', data.message.healthcare_service_unit); | ||||
| 					frm.set_value('department', data.message.medical_department); | ||||
| 					frm.doc.exercises = []; | ||||
| 					$.each(data.message.exercises, function(_i, e) { | ||||
| 						let exercise = frm.add_child('exercises'); | ||||
|  | ||||
| @ -9,9 +9,11 @@ | ||||
|   "naming_series", | ||||
|   "appointment", | ||||
|   "patient", | ||||
|   "patient_name", | ||||
|   "patient_age", | ||||
|   "gender", | ||||
|   "column_break_5", | ||||
|   "company", | ||||
|   "therapy_plan", | ||||
|   "therapy_type", | ||||
|   "practitioner", | ||||
| @ -20,7 +22,6 @@ | ||||
|   "duration", | ||||
|   "rate", | ||||
|   "location", | ||||
|   "company", | ||||
|   "column_break_12", | ||||
|   "service_unit", | ||||
|   "start_date", | ||||
| @ -28,6 +29,10 @@ | ||||
|   "invoiced", | ||||
|   "exercises_section", | ||||
|   "exercises", | ||||
|   "section_break_23", | ||||
|   "total_counts_targeted", | ||||
|   "column_break_25", | ||||
|   "total_counts_completed", | ||||
|   "amended_from" | ||||
|  ], | ||||
|  "fields": [ | ||||
| @ -159,7 +164,8 @@ | ||||
|    "fieldname": "company", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Company", | ||||
|    "options": "Company" | ||||
|    "options": "Company", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
| @ -173,11 +179,38 @@ | ||||
|    "fieldtype": "Data", | ||||
|    "label": "Patient Age", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "total_counts_targeted", | ||||
|    "fieldtype": "Int", | ||||
|    "label": "Total Counts Targeted", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "total_counts_completed", | ||||
|    "fieldtype": "Int", | ||||
|    "label": "Total Counts Completed", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "section_break_23", | ||||
|    "fieldtype": "Section Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_25", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fetch_from": "patient.patient_name", | ||||
|    "fieldname": "patient_name", | ||||
|    "fieldtype": "Data", | ||||
|    "label": "Patient Name", | ||||
|    "read_only": 1 | ||||
|   } | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-04-21 13:16:46.378798", | ||||
|  "modified": "2020-04-29 16:49:16.286006", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Healthcare", | ||||
|  "name": "Therapy Session", | ||||
|  | ||||
| @ -6,10 +6,17 @@ from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe.model.document import Document | ||||
| from frappe.model.mapper import get_mapped_doc | ||||
| from frappe import _ | ||||
| from frappe.utils import cstr, getdate | ||||
| from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account | ||||
| 
 | ||||
| class TherapySession(Document): | ||||
| 	def validate(self): | ||||
| 		self.set_total_counts() | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		self.update_sessions_count_in_therapy_plan() | ||||
| 		insert_session_medical_record(self) | ||||
| 
 | ||||
| 	def on_cancel(self): | ||||
| 		self.update_sessions_count_in_therapy_plan(on_cancel=True) | ||||
| @ -24,6 +31,18 @@ class TherapySession(Document): | ||||
| 					entry.sessions_completed += 1 | ||||
| 		therapy_plan.save() | ||||
| 
 | ||||
| 	def set_total_counts(self): | ||||
| 		target_total = 0 | ||||
| 		counts_completed = 0 | ||||
| 		for entry in self.exercises: | ||||
| 			if entry.counts_target: | ||||
| 				target_total += entry.counts_target | ||||
| 			if entry.counts_completed: | ||||
| 				counts_completed += entry.counts_completed | ||||
| 
 | ||||
| 		self.db_set('total_counts_targeted', target_total) | ||||
| 		self.db_set('total_counts_completed', counts_completed) | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def create_therapy_session(source_name, target_doc=None): | ||||
| @ -52,4 +71,62 @@ def create_therapy_session(source_name, target_doc=None): | ||||
| 			} | ||||
| 		}, target_doc, set_missing_values) | ||||
| 
 | ||||
| 	return doc | ||||
| 	return doc | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def invoice_therapy_session(source_name, target_doc=None): | ||||
| 	def set_missing_values(source, target): | ||||
| 		target.customer = frappe.db.get_value('Patient', source.patient, 'customer') | ||||
| 		target.due_date = getdate() | ||||
| 		target.debit_to = get_receivable_account(source.company) | ||||
| 		item = target.append('items', {}) | ||||
| 		item = get_therapy_item(source, item) | ||||
| 		target.set_missing_values(for_validate=True) | ||||
| 
 | ||||
| 	doc = get_mapped_doc('Therapy Session', source_name, { | ||||
| 			'Therapy Session': { | ||||
| 				'doctype': 'Sales Invoice', | ||||
| 				'field_map': [ | ||||
| 					['patient', 'patient'], | ||||
| 					['referring_practitioner', 'practitioner'], | ||||
| 					['company', 'company'], | ||||
| 					['due_date', 'start_date'] | ||||
| 				] | ||||
| 			} | ||||
| 		}, target_doc, set_missing_values) | ||||
| 
 | ||||
| 	return doc | ||||
| 
 | ||||
| 
 | ||||
| def get_therapy_item(therapy, item): | ||||
| 	item.item_code = frappe.db.get_value('Therapy Type', therapy.therapy_type, 'item') | ||||
| 	item.description = _('Therapy Session Charges: {0}').format(therapy.practitioner) | ||||
| 	item.income_account = get_income_account(therapy.practitioner, therapy.company) | ||||
| 	item.cost_center = frappe.get_cached_value('Company', therapy.company, 'cost_center') | ||||
| 	item.rate = therapy.rate | ||||
| 	item.amount = therapy.rate | ||||
| 	item.qty = 1 | ||||
| 	item.reference_dt = 'Therapy Session' | ||||
| 	item.reference_dn = therapy.name | ||||
| 	return item | ||||
| 
 | ||||
| 
 | ||||
| def insert_session_medical_record(doc): | ||||
| 	subject = frappe.bold(_('Therapy: ')) + cstr(doc.therapy_type) + '<br>' | ||||
| 	if doc.therapy_plan: | ||||
| 		subject += frappe.bold(_('Therapy Plan: ')) + cstr(doc.therapy_plan) + '<br>' | ||||
| 	if doc.practitioner: | ||||
| 		subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner | ||||
| 	subject += frappe.bold(_('Total Counts Targeted: ')) + cstr(doc.total_counts_targeted) + '<br>' | ||||
| 	subject += frappe.bold(_('Total Counts Completed: ')) + cstr(doc.total_counts_completed) + '<br>' | ||||
| 
 | ||||
| 	medical_record = frappe.new_doc('Patient Medical Record') | ||||
| 	medical_record.patient = doc.patient | ||||
| 	medical_record.subject = subject | ||||
| 	medical_record.status = 'Open' | ||||
| 	medical_record.communication_date = doc.start_date | ||||
| 	medical_record.reference_doctype = 'Therapy Session' | ||||
| 	medical_record.reference_name = doc.name | ||||
| 	medical_record.reference_owner = doc.owner | ||||
| 	medical_record.save(ignore_permissions=True) | ||||
| @ -35,17 +35,17 @@ def delete_vital_signs_from_medical_record(doc): | ||||
| 
 | ||||
| def set_subject_field(doc): | ||||
| 	subject = '' | ||||
| 	if(doc.temperature): | ||||
| 		subject += _('Temperature: ') + '\n'+ cstr(doc.temperature) + '. ' | ||||
| 	if(doc.pulse): | ||||
| 		subject += _('Pulse: ') + '\n' + cstr(doc.pulse) + '. ' | ||||
| 	if(doc.respiratory_rate): | ||||
| 		subject += _('Respiratory Rate: ') + '\n' + cstr(doc.respiratory_rate) + '. ' | ||||
| 	if(doc.bp): | ||||
| 		subject += _('BP: ') + '\n' + cstr(doc.bp) + '. ' | ||||
| 	if(doc.bmi): | ||||
| 		subject += _('BMI: ') + '\n' + cstr(doc.bmi) + '. ' | ||||
| 	if(doc.nutrition_note): | ||||
| 		subject += _('Note: ') + '\n' + cstr(doc.nutrition_note) + '. ' | ||||
| 	if doc.temperature: | ||||
| 		subject += frappe.bold(_('Temperature: ')) + cstr(doc.temperature) + '<br>' | ||||
| 	if doc.pulse: | ||||
| 		subject += frappe.bold(_('Pulse: ')) + cstr(doc.pulse) + '<br>' | ||||
| 	if doc.respiratory_rate: | ||||
| 		subject += frappe.bold(_('Respiratory Rate: ')) + cstr(doc.respiratory_rate) + '<br>' | ||||
| 	if doc.bp: | ||||
| 		subject += frappe.bold(_('BP: ')) + cstr(doc.bp) + '<br>' | ||||
| 	if doc.bmi: | ||||
| 		subject += frappe.bold(_('BMI: ')) + cstr(doc.bmi) + '<br>' | ||||
| 	if doc.nutrition_note: | ||||
| 		subject += frappe.bold(_('Note: ')) + cstr(doc.nutrition_note) + '<br>' | ||||
| 
 | ||||
| 	return subject | ||||
|  | ||||
| @ -250,7 +250,8 @@ doc_events = { | ||||
| 	}, | ||||
| 	"Contact": { | ||||
| 		"on_trash": "erpnext.support.doctype.issue.issue.update_issue", | ||||
| 		"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information" | ||||
| 		"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information", | ||||
| 		"validate": "erpnext.crm.utils.update_lead_phone_numbers" | ||||
| 	}, | ||||
| 	"Lead": { | ||||
| 		"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information" | ||||
| @ -537,4 +538,4 @@ global_search_doctypes = { | ||||
| 		{'doctype': 'Hotel Room Package', 'index': 3}, | ||||
| 		{'doctype': 'Hotel Room Type', 'index': 4} | ||||
| 	] | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -23,7 +23,7 @@ | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "label": "Payroll", | ||||
|    "links": "[\n    {\n        \"label\": \"Salary Structure\",\n        \"name\": \"Salary Structure\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Salary Structure\",\n            \"Employee\"\n        ],\n        \"label\": \"Salary Structure Assignment\",\n        \"name\": \"Salary Structure Assignment\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Payroll Entry\",\n        \"name\": \"Payroll Entry\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Salary Slip\",\n        \"name\": \"Salary Slip\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Payroll Period\",\n        \"name\": \"Payroll Period\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Salary Component\",\n        \"name\": \"Salary Component\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Additional Salary\",\n        \"name\": \"Additional Salary\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Retention Bonus\",\n        \"name\": \"Retention Bonus\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Incentive\",\n        \"name\": \"Employee Incentive\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Salary Slip\"\n        ],\n        \"doctype\": \"Salary Slip\",\n        \"is_query_report\": true,\n        \"label\": \"Salary Register\",\n        \"name\": \"Salary Register\",\n        \"type\": \"report\"\n    }\n]" | ||||
|    "links": "[\n    {\n        \"label\": \"Salary Structure\",\n        \"name\": \"Salary Structure\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Salary Structure\",\n            \"Employee\"\n        ],\n        \"label\": \"Salary Structure Assignment\",\n        \"name\": \"Salary Structure Assignment\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Payroll Entry\",\n        \"name\": \"Payroll Entry\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Salary Slip\",\n        \"name\": \"Salary Slip\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Payroll Period\",\n        \"name\": \"Payroll Period\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Income Tax Slab\",\n        \"name\": \"Income Tax Slab\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Salary Component\",\n        \"name\": \"Salary Component\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Additional Salary\",\n        \"name\": \"Additional Salary\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Retention Bonus\",\n        \"name\": \"Retention Bonus\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Incentive\",\n        \"name\": \"Employee Incentive\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Salary Slip\"\n        ],\n        \"doctype\": \"Salary Slip\",\n        \"is_query_report\": true,\n        \"label\": \"Salary Register\",\n        \"name\": \"Salary Register\",\n        \"type\": \"report\"\n    }\n]" | ||||
|   }, | ||||
|   { | ||||
|    "hidden": 0, | ||||
| @ -73,7 +73,7 @@ | ||||
|   { | ||||
|    "hidden": 0, | ||||
|    "label": "Employee Tax and Benefits", | ||||
|    "links": "[\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Tax Exemption Declaration\",\n        \"name\": \"Employee Tax Exemption Declaration\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Tax Exemption Proof Submission\",\n        \"name\": \"Employee Tax Exemption Proof Submission\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Benefit Application\",\n        \"name\": \"Employee Benefit Application\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Benefit Claim\",\n        \"name\": \"Employee Benefit Claim\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Tax Exemption Category\",\n        \"name\": \"Employee Tax Exemption Category\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Tax Exemption Sub Category\",\n        \"name\": \"Employee Tax Exemption Sub Category\",\n        \"type\": \"doctype\"\n    }\n]" | ||||
|    "links": "[\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Tax Exemption Declaration\",\n        \"name\": \"Employee Tax Exemption Declaration\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Tax Exemption Proof Submission\",\n        \"name\": \"Employee Tax Exemption Proof Submission\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\",\n            \"Payroll Period\"\n        ],\n        \"label\": \"Employee Other Income\",\n        \"name\": \"Employee Other Income\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Benefit Application\",\n        \"name\": \"Employee Benefit Application\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Benefit Claim\",\n        \"name\": \"Employee Benefit Claim\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Tax Exemption Category\",\n        \"name\": \"Employee Tax Exemption Category\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Employee\"\n        ],\n        \"label\": \"Employee Tax Exemption Sub Category\",\n        \"name\": \"Employee Tax Exemption Sub Category\",\n        \"type\": \"doctype\"\n    }\n]" | ||||
|   } | ||||
|  ], | ||||
|  "category": "Modules", | ||||
| @ -88,7 +88,7 @@ | ||||
|  "idx": 0, | ||||
|  "is_standard": 1, | ||||
|  "label": "HR", | ||||
|  "modified": "2020-04-01 11:28:50.860012", | ||||
|  "modified": "2020-04-29 20:29:22.114309", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "HR", | ||||
|  "name": "HR", | ||||
|  | ||||
| @ -13,5 +13,5 @@ frappe.ui.form.on('Additional Salary', { | ||||
| 				} | ||||
| 			}; | ||||
| 		}); | ||||
| 	} | ||||
| 	}, | ||||
| }); | ||||
|  | ||||
| @ -13,10 +13,14 @@ | ||||
|   "salary_component", | ||||
|   "overwrite_salary_structure_amount", | ||||
|   "deduct_full_tax_on_selected_payroll_date", | ||||
|   "ref_doctype", | ||||
|   "ref_docname", | ||||
|   "column_break_5", | ||||
|   "company", | ||||
|   "is_recurring", | ||||
|   "from_date", | ||||
|   "to_date", | ||||
|   "payroll_date", | ||||
|   "salary_slip", | ||||
|   "type", | ||||
|   "department", | ||||
|   "amount", | ||||
| @ -74,12 +78,13 @@ | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:(doc.is_recurring==0)", | ||||
|    "description": "Date on which this component is applied", | ||||
|    "fieldname": "payroll_date", | ||||
|    "fieldtype": "Date", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Payroll Date", | ||||
|    "reqd": 1, | ||||
|    "mandatory_depends_on": "eval:(doc.is_recurring==0)", | ||||
|    "search_index": 1 | ||||
|   }, | ||||
|   { | ||||
| @ -105,13 +110,6 @@ | ||||
|    "options": "Company", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "salary_slip", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Salary Slip", | ||||
|    "options": "Salary Slip", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fetch_from": "salary_component.type", | ||||
|    "fieldname": "type", | ||||
| @ -127,11 +125,45 @@ | ||||
|    "options": "Additional Salary", | ||||
|    "print_hide": 1, | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "is_recurring", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Is Recurring" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:(doc.is_recurring==1)", | ||||
|    "fieldname": "from_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "From Date", | ||||
|    "mandatory_depends_on": "eval:(doc.is_recurring==1)" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "eval:(doc.is_recurring==1)", | ||||
|    "fieldname": "to_date", | ||||
|    "fieldtype": "Date", | ||||
|    "label": "To Date", | ||||
|    "mandatory_depends_on": "eval:(doc.is_recurring==1)" | ||||
|   }, | ||||
|    { | ||||
|    "fieldname": "ref_doctype", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Reference Document Type", | ||||
|    "options": "DocType", | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "ref_docname", | ||||
|    "fieldtype": "Dynamic Link", | ||||
|    "label": "Reference Document", | ||||
|    "options": "ref_doctype", | ||||
|    "read_only": 1 | ||||
|   } | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2019-12-12 19:07:23.635901", | ||||
|  "modified": "2020-04-04 18:06:29.170878", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "HR", | ||||
|  "name": "Additional Salary", | ||||
|  | ||||
| @ -9,6 +9,11 @@ from frappe import _ | ||||
| from frappe.utils import getdate, date_diff | ||||
| 
 | ||||
| class AdditionalSalary(Document): | ||||
| 
 | ||||
| 	def on_submit(self): | ||||
| 		if self.ref_doctype == "Employee Advance" and self.ref_docname: | ||||
| 			frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount) | ||||
| 
 | ||||
| 	def before_insert(self): | ||||
| 		if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component, | ||||
| 			"amount": self.amount, "payroll_date": self.payroll_date, "company": self.company, "docstatus": 1}): | ||||
| @ -21,10 +26,19 @@ class AdditionalSalary(Document): | ||||
| 			frappe.throw(_("Amount should not be less than zero.")) | ||||
| 
 | ||||
| 	def validate_dates(self): | ||||
|  		date_of_joining, relieving_date = frappe.db.get_value("Employee", self.employee, | ||||
| 		date_of_joining, relieving_date = frappe.db.get_value("Employee", self.employee, | ||||
| 			["date_of_joining", "relieving_date"]) | ||||
|  		if date_of_joining and getdate(self.payroll_date) < getdate(date_of_joining): | ||||
|  			frappe.throw(_("Payroll date can not be less than employee's joining date")) | ||||
| 
 | ||||
| 		if getdate(self.from_date) > getdate(self.to_date): | ||||
| 			frappe.throw(_("From Date can not be greater than To Date.")) | ||||
| 
 | ||||
| 		if date_of_joining: | ||||
| 			if getdate(self.payroll_date) < getdate(date_of_joining): | ||||
| 				frappe.throw(_("Payroll date can not be less than employee's joining date.")) | ||||
| 			elif getdate(self.from_date) < getdate(date_of_joining): | ||||
| 				frappe.throw(_("From date can not be less than employee's joining date.")) | ||||
| 			elif getdate(self.to_date) > getdate(relieving_date): | ||||
| 				frappe.throw(_("To date can not be greater than employee's relieving date.")) | ||||
| 
 | ||||
| 	def get_amount(self, sal_start_date, sal_end_date): | ||||
| 		start_date = getdate(sal_start_date) | ||||
| @ -40,15 +54,18 @@ class AdditionalSalary(Document): | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_additional_salary_component(employee, start_date, end_date, component_type): | ||||
| 	additional_components = frappe.db.sql(""" | ||||
| 		select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date | ||||
| 	additional_salaries = frappe.db.sql(""" | ||||
| 		select name, salary_component, type, amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date | ||||
| 		from `tabAdditional Salary` | ||||
| 		where employee=%(employee)s | ||||
| 			and docstatus = 1 | ||||
| 			and payroll_date between %(from_date)s and %(to_date)s | ||||
| 			and type = %(component_type)s | ||||
| 		group by salary_component, overwrite_salary_structure_amount | ||||
| 		order by salary_component, overwrite_salary_structure_amount | ||||
| 			and ( | ||||
| 					payroll_date between %(from_date)s and %(to_date)s | ||||
| 				or | ||||
| 					from_date <= %(to_date)s and to_date >= %(to_date)s | ||||
| 				) | ||||
| 		and type = %(component_type)s | ||||
| 		order by salary_component, overwrite_salary_structure_amount DESC | ||||
| 	""", { | ||||
| 		'employee': employee, | ||||
| 		'from_date': start_date, | ||||
| @ -56,21 +73,38 @@ def get_additional_salary_component(employee, start_date, end_date, component_ty | ||||
| 		'component_type': "Earning" if component_type == "earnings" else "Deduction" | ||||
| 	}, as_dict=1) | ||||
| 
 | ||||
| 	additional_components_list = [] | ||||
| 	existing_salary_components= [] | ||||
| 	salary_components_details = {} | ||||
| 	additional_salary_details = [] | ||||
| 
 | ||||
| 	overwrites_components = [ele.salary_component for ele in additional_salaries if ele.overwrite_salary_structure_amount == 1] | ||||
| 
 | ||||
| 	component_fields = ["depends_on_payment_days", "salary_component_abbr", "is_tax_applicable", "variable_based_on_taxable_salary", 'type'] | ||||
| 	for d in additional_components: | ||||
| 		struct_row = frappe._dict({'salary_component': d.salary_component}) | ||||
| 		component = frappe.get_all("Salary Component", filters={'name': d.salary_component}, fields=component_fields) | ||||
| 		if component: | ||||
| 			struct_row.update(component[0]) | ||||
| 	for d in additional_salaries: | ||||
| 
 | ||||
| 		struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date | ||||
| 		struct_row['is_additional_component'] = 1 | ||||
| 		if d.salary_component not in existing_salary_components: | ||||
| 			component = frappe.get_all("Salary Component", filters={'name': d.salary_component}, fields=component_fields) | ||||
| 			struct_row = frappe._dict({'salary_component': d.salary_component}) | ||||
| 			if component: | ||||
| 				struct_row.update(component[0]) | ||||
| 
 | ||||
| 		additional_components_list.append(frappe._dict({ | ||||
| 			'amount': d.amount, | ||||
| 			'type': component[0].type, | ||||
| 			'struct_row': struct_row, | ||||
| 			'overwrite': d.overwrite_salary_structure_amount, | ||||
| 		})) | ||||
| 	return additional_components_list | ||||
| 			struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date | ||||
| 			struct_row['is_additional_component'] = 1 | ||||
| 
 | ||||
| 			salary_components_details[d.salary_component] = struct_row | ||||
| 
 | ||||
| 
 | ||||
| 		if overwrites_components.count(d.salary_component) > 1: | ||||
| 			frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component: {0} between {1} and {2}.".format(d.salary_component, start_date, end_date)), title=_("Error")) | ||||
| 		else: | ||||
| 			additional_salary_details.append({ | ||||
| 				'name': d.name, | ||||
| 				'component': d.salary_component, | ||||
| 				'amount': d.amount, | ||||
| 				'type': d.type, | ||||
| 				'overwrite': d.overwrite_salary_structure_amount, | ||||
| 			}) | ||||
| 
 | ||||
| 		existing_salary_components.append(d.salary_component) | ||||
| 
 | ||||
| 	return salary_components_details, additional_salary_details | ||||
| @ -3,6 +3,44 @@ | ||||
| # See license.txt | ||||
| from __future__ import unicode_literals | ||||
| import unittest | ||||
| import frappe, erpnext | ||||
| from frappe.utils import nowdate, add_days | ||||
| from erpnext.hr.doctype.employee.test_employee import make_employee | ||||
| from erpnext.hr.doctype.salary_component.test_salary_component import create_salary_component | ||||
| from erpnext.hr.doctype.salary_slip.test_salary_slip import make_employee_salary_slip, setup_test | ||||
| 
 | ||||
| 
 | ||||
| class TestAdditionalSalary(unittest.TestCase): | ||||
| 	pass | ||||
| 
 | ||||
| 	def setUp(self): | ||||
| 		setup_test() | ||||
| 
 | ||||
| 	def test_recurring_additional_salary(self): | ||||
| 		emp_id = make_employee("test_additional@salary.com") | ||||
| 		frappe.db.set_value("Employee", emp_id, "relieving_date", add_days(nowdate(), 1800)) | ||||
| 		add_sal = get_additional_salary(emp_id) | ||||
| 
 | ||||
| 		ss = make_employee_salary_slip("test_additional@salary.com", "Monthly") | ||||
| 		for earning in ss.earnings: | ||||
| 			if earning.salary_component == "Recurring Salary Component": | ||||
| 				amount = earning.amount | ||||
| 				salary_component = earning.salary_component | ||||
| 
 | ||||
| 		self.assertEqual(amount, add_sal.amount) | ||||
| 		self.assertEqual(salary_component, add_sal.salary_component) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| def get_additional_salary(emp_id): | ||||
| 	create_salary_component("Recurring Salary Component") | ||||
| 	add_sal = frappe.new_doc("Additional Salary") | ||||
| 	add_sal.employee = emp_id | ||||
| 	add_sal.salary_component = "Recurring Salary Component" | ||||
| 	add_sal.is_recurring = 1 | ||||
| 	add_sal.from_date = add_days(nowdate(), -50) | ||||
| 	add_sal.to_date = add_days(nowdate(), 180) | ||||
| 	add_sal.amount = 5000 | ||||
| 	add_sal.save() | ||||
| 	add_sal.submit() | ||||
| 
 | ||||
| 	return add_sal | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user