Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: common party accounting #27039

Merged
merged 13 commits into from
Aug 26, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -268,14 +269,20 @@
"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",
"idx": 1,
"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",
Expand Down
Empty file.
33 changes: 33 additions & 0 deletions erpnext/accounts/doctype/party_link/party_link.js
Original file line number Diff line number Diff line change
@@ -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', '');
}
});
78 changes: 78 additions & 0 deletions erpnext/accounts/doctype/party_link/party_link.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"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-19 17:53:43.456752",
"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
}
],
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "primary_party",
"track_changes": 1
}
12 changes: 12 additions & 0 deletions erpnext/accounts/doctype/party_link/party_link.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 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"))
8 changes: 8 additions & 0 deletions erpnext/accounts/doctype/party_link/test_party_link.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,8 @@ def on_submit(self):
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()
Expand Down
2 changes: 2 additions & 0 deletions erpnext/accounts/doctype/sales_invoice/sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ def on_submit(self):
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:
Expand Down
44 changes: 44 additions & 0 deletions erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -2163,6 +2163,50 @@ def test_asset_depreciation_on_sale(self):
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)

# 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-.#####'
Expand Down
62 changes: 61 additions & 1 deletion erpnext/controllers/accounts_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
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
Expand Down Expand Up @@ -1362,6 +1362,66 @@ def is_internal_transfer(self):

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()
party_link = frappe.db.exists('Party Link', {'secondary_role': party_type, 'secondary_party': party})
if party_link:
return frappe.db.get_value('Party Link', party_link, ['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.naming_series = 'ACC-JV-.YYYY.-'
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()
cost_center = erpnext.get_default_cost_center(self.company)

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 = cost_center

advance_entry.account = primary_account
advance_entry.party_type = primary_party_type
advance_entry.party = primary_party
advance_entry.cost_center = 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)
Expand Down