Merge pull request #27039 from nextchamp-saqib/common-party-acc
feat: common party accounting
This commit is contained in:
		
						commit
						e2af9d5761
					
				| @ -19,6 +19,7 @@ | ||||
|   "delete_linked_ledger_entries", | ||||
|   "book_asset_depreciation_entry_automatically", | ||||
|   "unlink_advance_payment_on_cancelation_of_order", | ||||
|   "enable_common_party_accounting", | ||||
|   "post_change_gl_entries", | ||||
|   "enable_discount_accounting", | ||||
|   "tax_settings_section", | ||||
| @ -268,6 +269,12 @@ | ||||
|    "fieldname": "enable_discount_accounting", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Enable Discount Accounting" | ||||
|   }, | ||||
|   { | ||||
|    "default": "0", | ||||
|    "fieldname": "enable_common_party_accounting", | ||||
|    "fieldtype": "Check", | ||||
|    "label": "Enable Common Party Accounting" | ||||
|   } | ||||
|  ], | ||||
|  "icon": "icon-cog", | ||||
| @ -275,7 +282,7 @@ | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "issingle": 1, | ||||
|  "links": [], | ||||
|  "modified": "2021-08-09 13:08:04.335416", | ||||
|  "modified": "2021-08-19 11:17:38.788054", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Accounts Settings", | ||||
|  | ||||
							
								
								
									
										0
									
								
								erpnext/accounts/doctype/party_link/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								erpnext/accounts/doctype/party_link/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										33
									
								
								erpnext/accounts/doctype/party_link/party_link.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								erpnext/accounts/doctype/party_link/party_link.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| // Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
 | ||||
| // For license information, please see license.txt
 | ||||
| 
 | ||||
| frappe.ui.form.on('Party Link', { | ||||
| 	refresh: function(frm) { | ||||
| 		frm.set_query('primary_role', () => { | ||||
| 			return { | ||||
| 				filters: { | ||||
| 					name: ['in', ['Customer', 'Supplier']] | ||||
| 				} | ||||
| 			}; | ||||
| 		}); | ||||
| 
 | ||||
| 		frm.set_query('secondary_role', () => { | ||||
| 			let party_types = Object.keys(frappe.boot.party_account_types) | ||||
| 				.filter(p => p != frm.doc.primary_role); | ||||
| 			return { | ||||
| 				filters: { | ||||
| 					name: ['in', party_types] | ||||
| 				} | ||||
| 			}; | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	primary_role(frm) { | ||||
| 		frm.set_value('primary_party', ''); | ||||
| 		frm.set_value('secondary_role', ''); | ||||
| 	}, | ||||
| 
 | ||||
| 	secondary_role(frm) { | ||||
| 		frm.set_value('secondary_party', ''); | ||||
| 	} | ||||
| }); | ||||
							
								
								
									
										102
									
								
								erpnext/accounts/doctype/party_link/party_link.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								erpnext/accounts/doctype/party_link/party_link.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | ||||
| { | ||||
|  "actions": [], | ||||
|  "autoname": "ACC-PT-LNK-.###.", | ||||
|  "creation": "2021-08-18 21:06:53.027695", | ||||
|  "doctype": "DocType", | ||||
|  "editable_grid": 1, | ||||
|  "engine": "InnoDB", | ||||
|  "field_order": [ | ||||
|   "primary_role", | ||||
|   "secondary_role", | ||||
|   "column_break_2", | ||||
|   "primary_party", | ||||
|   "secondary_party" | ||||
|  ], | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "primary_role", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Primary Role", | ||||
|    "options": "DocType", | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "column_break_2", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "primary_role", | ||||
|    "fieldname": "secondary_role", | ||||
|    "fieldtype": "Link", | ||||
|    "label": "Secondary Role", | ||||
|    "mandatory_depends_on": "primary_role", | ||||
|    "options": "DocType" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "primary_role", | ||||
|    "fieldname": "primary_party", | ||||
|    "fieldtype": "Dynamic Link", | ||||
|    "label": "Primary Party", | ||||
|    "mandatory_depends_on": "primary_role", | ||||
|    "options": "primary_role" | ||||
|   }, | ||||
|   { | ||||
|    "depends_on": "secondary_role", | ||||
|    "fieldname": "secondary_party", | ||||
|    "fieldtype": "Dynamic Link", | ||||
|    "label": "Secondary Party", | ||||
|    "mandatory_depends_on": "secondary_role", | ||||
|    "options": "secondary_role" | ||||
|   } | ||||
|  ], | ||||
|  "index_web_pages_for_search": 1, | ||||
|  "links": [], | ||||
|  "modified": "2021-08-25 20:08:56.761150", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Accounts", | ||||
|  "name": "Party Link", | ||||
|  "owner": "Administrator", | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "System Manager", | ||||
|    "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 | ||||
|   }, | ||||
|   { | ||||
|    "create": 1, | ||||
|    "delete": 1, | ||||
|    "email": 1, | ||||
|    "export": 1, | ||||
|    "print": 1, | ||||
|    "read": 1, | ||||
|    "report": 1, | ||||
|    "role": "Accounts User", | ||||
|    "share": 1, | ||||
|    "write": 1 | ||||
|   } | ||||
|  ], | ||||
|  "sort_field": "modified", | ||||
|  "sort_order": "DESC", | ||||
|  "title_field": "primary_party", | ||||
|  "track_changes": 1 | ||||
| } | ||||
							
								
								
									
										26
									
								
								erpnext/accounts/doctype/party_link/party_link.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								erpnext/accounts/doctype/party_link/party_link.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| import frappe | ||||
| from frappe import _ | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class PartyLink(Document): | ||||
| 	def validate(self): | ||||
| 		if self.primary_role not in ['Customer', 'Supplier']: | ||||
| 			frappe.throw(_("Allowed primary roles are 'Customer' and 'Supplier'. Please select one of these roles only."), | ||||
| 				title=_("Invalid Primary Role")) | ||||
| 		 | ||||
| 		existing_party_link = frappe.get_all('Party Link', { | ||||
| 			'primary_party': self.secondary_party | ||||
| 		}, pluck="primary_role") | ||||
| 		if existing_party_link: | ||||
| 			frappe.throw(_('{} {} is already linked with another {}') | ||||
| 				.format(self.secondary_role, self.secondary_party, existing_party_link[0])) | ||||
| 		 | ||||
| 		existing_party_link = frappe.get_all('Party Link', { | ||||
| 			'secondary_party': self.primary_party | ||||
| 		}, pluck="primary_role") | ||||
| 		if existing_party_link: | ||||
| 			frappe.throw(_('{} {} is already linked with another {}') | ||||
| 				.format(self.primary_role, self.primary_party, existing_party_link[0])) | ||||
							
								
								
									
										8
									
								
								erpnext/accounts/doctype/party_link/test_party_link.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								erpnext/accounts/doctype/party_link/test_party_link.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| 
 | ||||
| # import frappe | ||||
| import unittest | ||||
| 
 | ||||
| class TestPartyLink(unittest.TestCase): | ||||
| 	pass | ||||
| @ -415,6 +415,8 @@ class PurchaseInvoice(BuyingController): | ||||
| 		self.update_project() | ||||
| 		update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) | ||||
| 
 | ||||
| 		self.process_common_party_accounting() | ||||
| 
 | ||||
| 	def make_gl_entries(self, gl_entries=None, from_repost=False): | ||||
| 		if not gl_entries: | ||||
| 			gl_entries = self.get_gl_entries() | ||||
|  | ||||
| @ -253,6 +253,8 @@ class SalesInvoice(SellingController): | ||||
| 		if "Healthcare" in active_domains: | ||||
| 			manage_invoice_submit_cancel(self, "on_submit") | ||||
| 
 | ||||
| 		self.process_common_party_accounting() | ||||
| 
 | ||||
| 	def validate_pos_return(self): | ||||
| 
 | ||||
| 		if self.is_pos and self.is_return: | ||||
|  | ||||
| @ -2175,6 +2175,50 @@ class TestSalesInvoice(unittest.TestCase): | ||||
| 			self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) | ||||
| 			self.assertTrue(schedule.journal_entry) | ||||
| 
 | ||||
| 	def test_sales_invoice_against_supplier(self): | ||||
| 		from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import make_customer | ||||
| 		from erpnext.buying.doctype.supplier.test_supplier import create_supplier | ||||
| 
 | ||||
| 		# create a customer | ||||
| 		customer = make_customer(customer="_Test Common Supplier") | ||||
| 		# create a supplier | ||||
| 		supplier = create_supplier(supplier_name="_Test Common Supplier").name | ||||
| 
 | ||||
| 		# create a party link between customer & supplier | ||||
| 		# set primary role as supplier | ||||
| 		party_link = frappe.new_doc("Party Link") | ||||
| 		party_link.primary_role = "Supplier" | ||||
| 		party_link.primary_party = supplier | ||||
| 		party_link.secondary_role = "Customer" | ||||
| 		party_link.secondary_party = customer | ||||
| 		party_link.save() | ||||
| 
 | ||||
| 		# enable common party accounting | ||||
| 		frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1) | ||||
| 
 | ||||
| 		# create a sales invoice | ||||
| 		si = create_sales_invoice(customer=customer, parent_cost_center="_Test Cost Center - _TC") | ||||
| 
 | ||||
| 		# check outstanding of sales invoice | ||||
| 		si.reload() | ||||
| 		self.assertEqual(si.status, 'Paid') | ||||
| 		self.assertEqual(flt(si.outstanding_amount), 0.0) | ||||
| 
 | ||||
| 		# check creation of journal entry | ||||
| 		jv = frappe.get_all('Journal Entry Account', { | ||||
| 			'account': si.debit_to, | ||||
| 			'party_type': 'Customer', | ||||
| 			'party': si.customer, | ||||
| 			'reference_type': si.doctype, | ||||
| 			'reference_name': si.name | ||||
| 		}, pluck='credit_in_account_currency') | ||||
| 
 | ||||
| 		self.assertTrue(jv) | ||||
| 		self.assertEqual(jv[0], si.grand_total) | ||||
| 
 | ||||
| 		party_link.delete() | ||||
| 		frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 0) | ||||
| 
 | ||||
| def get_sales_invoice_for_e_invoice(): | ||||
| 	si = make_sales_invoice_for_ewaybill() | ||||
| 	si.naming_series = 'INV-2020-.#####' | ||||
|  | ||||
| @ -14,7 +14,7 @@ from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_a | ||||
| from erpnext.utilities.transaction_base import TransactionBase | ||||
| from erpnext.buying.utils import update_last_purchase_rate | ||||
| from erpnext.controllers.sales_and_purchase_return import validate_return | ||||
| from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled | ||||
| from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled, get_party_account | ||||
| from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_transaction, | ||||
| 	apply_pricing_rule_for_free_items, get_applied_pricing_rules) | ||||
| from erpnext.exceptions import InvalidCurrency | ||||
| @ -1363,6 +1363,67 @@ class AccountsController(TransactionBase): | ||||
| 
 | ||||
| 		return False | ||||
| 
 | ||||
| 	def process_common_party_accounting(self): | ||||
| 		is_invoice = self.doctype in ['Sales Invoice', 'Purchase Invoice'] | ||||
| 		if not is_invoice: | ||||
| 			return | ||||
| 
 | ||||
| 		if frappe.db.get_single_value('Accounts Settings', 'enable_common_party_accounting'): | ||||
| 			party_link = self.get_common_party_link() | ||||
| 			if party_link and self.outstanding_amount: | ||||
| 				self.create_advance_and_reconcile(party_link) | ||||
| 
 | ||||
| 	def get_common_party_link(self): | ||||
| 		party_type, party = self.get_party() | ||||
| 		return frappe.db.get_value( | ||||
| 			doctype='Party Link', | ||||
| 			filters={'secondary_role': party_type, 'secondary_party': party}, | ||||
| 			fieldname=['primary_role', 'primary_party'], | ||||
| 			as_dict=True | ||||
| 		) | ||||
| 
 | ||||
| 	def create_advance_and_reconcile(self, party_link): | ||||
| 		secondary_party_type, secondary_party = self.get_party() | ||||
| 		primary_party_type, primary_party = party_link.primary_role, party_link.primary_party | ||||
| 
 | ||||
| 		primary_account = get_party_account(primary_party_type, primary_party, self.company) | ||||
| 		secondary_account = get_party_account(secondary_party_type, secondary_party, self.company) | ||||
| 
 | ||||
| 		jv = frappe.new_doc('Journal Entry') | ||||
| 		jv.voucher_type = 'Journal Entry' | ||||
| 		jv.posting_date = self.posting_date | ||||
| 		jv.company = self.company | ||||
| 		jv.remark = 'Adjustment for {} {}'.format(self.doctype, self.name) | ||||
| 
 | ||||
| 		reconcilation_entry = frappe._dict() | ||||
| 		advance_entry = frappe._dict() | ||||
| 
 | ||||
| 		reconcilation_entry.account = secondary_account | ||||
| 		reconcilation_entry.party_type = secondary_party_type | ||||
| 		reconcilation_entry.party = secondary_party | ||||
| 		reconcilation_entry.reference_type = self.doctype | ||||
| 		reconcilation_entry.reference_name = self.name | ||||
| 		reconcilation_entry.cost_center = self.cost_center | ||||
| 
 | ||||
| 		advance_entry.account = primary_account | ||||
| 		advance_entry.party_type = primary_party_type | ||||
| 		advance_entry.party = primary_party | ||||
| 		advance_entry.cost_center = self.cost_center | ||||
| 		advance_entry.is_advance = 'Yes' | ||||
| 
 | ||||
| 		if self.doctype == 'Sales Invoice': | ||||
| 			reconcilation_entry.credit_in_account_currency = self.outstanding_amount | ||||
| 			advance_entry.debit_in_account_currency = self.outstanding_amount | ||||
| 		else: | ||||
| 			advance_entry.credit_in_account_currency = self.outstanding_amount | ||||
| 			reconcilation_entry.debit_in_account_currency = self.outstanding_amount | ||||
| 
 | ||||
| 		jv.append('accounts', reconcilation_entry) | ||||
| 		jv.append('accounts', advance_entry) | ||||
| 
 | ||||
| 		jv.save() | ||||
| 		jv.submit() | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_tax_rate(account_head): | ||||
| 	return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user