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",
|
"delete_linked_ledger_entries",
|
||||||
"book_asset_depreciation_entry_automatically",
|
"book_asset_depreciation_entry_automatically",
|
||||||
"unlink_advance_payment_on_cancelation_of_order",
|
"unlink_advance_payment_on_cancelation_of_order",
|
||||||
|
"enable_common_party_accounting",
|
||||||
"post_change_gl_entries",
|
"post_change_gl_entries",
|
||||||
"enable_discount_accounting",
|
"enable_discount_accounting",
|
||||||
"tax_settings_section",
|
"tax_settings_section",
|
||||||
@ -268,6 +269,12 @@
|
|||||||
"fieldname": "enable_discount_accounting",
|
"fieldname": "enable_discount_accounting",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Enable Discount Accounting"
|
"label": "Enable Discount Accounting"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "enable_common_party_accounting",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enable Common Party Accounting"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@ -275,7 +282,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-08-09 13:08:04.335416",
|
"modified": "2021-08-19 11:17:38.788054",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"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()
|
self.update_project()
|
||||||
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
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):
|
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||||
if not gl_entries:
|
if not gl_entries:
|
||||||
gl_entries = self.get_gl_entries()
|
gl_entries = self.get_gl_entries()
|
||||||
|
@ -253,6 +253,8 @@ class SalesInvoice(SellingController):
|
|||||||
if "Healthcare" in active_domains:
|
if "Healthcare" in active_domains:
|
||||||
manage_invoice_submit_cancel(self, "on_submit")
|
manage_invoice_submit_cancel(self, "on_submit")
|
||||||
|
|
||||||
|
self.process_common_party_accounting()
|
||||||
|
|
||||||
def validate_pos_return(self):
|
def validate_pos_return(self):
|
||||||
|
|
||||||
if self.is_pos and self.is_return:
|
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.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
|
||||||
self.assertTrue(schedule.journal_entry)
|
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():
|
def get_sales_invoice_for_e_invoice():
|
||||||
si = make_sales_invoice_for_ewaybill()
|
si = make_sales_invoice_for_ewaybill()
|
||||||
si.naming_series = 'INV-2020-.#####'
|
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.utilities.transaction_base import TransactionBase
|
||||||
from erpnext.buying.utils import update_last_purchase_rate
|
from erpnext.buying.utils import update_last_purchase_rate
|
||||||
from erpnext.controllers.sales_and_purchase_return import validate_return
|
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,
|
from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_transaction,
|
||||||
apply_pricing_rule_for_free_items, get_applied_pricing_rules)
|
apply_pricing_rule_for_free_items, get_applied_pricing_rules)
|
||||||
from erpnext.exceptions import InvalidCurrency
|
from erpnext.exceptions import InvalidCurrency
|
||||||
@ -1363,6 +1363,67 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
return False
|
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()
|
@frappe.whitelist()
|
||||||
def get_tax_rate(account_head):
|
def get_tax_rate(account_head):
|
||||||
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
|
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
|
||||||
|
Loading…
Reference in New Issue
Block a user