From 74619269f0fa5d380c820bc12ab59cb7a06669c8 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 2 Jun 2023 17:13:51 +0530 Subject: [PATCH 01/40] feat: Record Advance Payments as Liability Ability to let user record advance payments as liability instead of a negative asset. Issue #34282 --- .../doctype/party_account/party_account.json | 20 +- .../doctype/payment_entry/payment_entry.js | 53 +++- .../doctype/payment_entry/payment_entry.py | 5 +- .../payment_reconciliation.js | 53 +++- .../payment_reconciliation.json | 23 +- .../payment_reconciliation.py | 27 +- .../purchase_invoice/purchase_invoice.py | 7 + .../purchase_invoice_advance.json | 15 +- .../doctype/sales_invoice/sales_invoice.py | 6 + .../sales_invoice_advance.json | 15 +- erpnext/accounts/party.py | 32 ++- erpnext/accounts/utils.py | 77 ++--- erpnext/buying/doctype/supplier/supplier.js | 23 ++ erpnext/buying/doctype/supplier/supplier.json | 9 +- erpnext/controllers/accounts_controller.py | 264 ++++++++++++------ erpnext/selling/doctype/customer/customer.js | 22 ++ .../selling/doctype/customer/customer.json | 7 +- erpnext/setup/doctype/company/company.js | 4 +- erpnext/setup/doctype/company/company.json | 38 ++- .../doctype/customer_group/customer_group.js | 26 +- .../customer_group/customer_group.json | 5 +- 21 files changed, 559 insertions(+), 172 deletions(-) diff --git a/erpnext/accounts/doctype/party_account/party_account.json b/erpnext/accounts/doctype/party_account/party_account.json index 69330577ab..719b474f1f 100644 --- a/erpnext/accounts/doctype/party_account/party_account.json +++ b/erpnext/accounts/doctype/party_account/party_account.json @@ -6,7 +6,9 @@ "engine": "InnoDB", "field_order": [ "company", - "account" + "account", + "advances_received_account", + "advances_paid_account" ], "fields": [ { @@ -22,14 +24,26 @@ "fieldname": "account", "fieldtype": "Link", "in_list_view": 1, - "label": "Account", + "label": "Default Account", + "options": "Account" + }, + { + "fieldname": "advances_received_account", + "fieldtype": "Link", + "label": "Advances Received Account", + "options": "Account" + }, + { + "fieldname": "advances_paid_account", + "fieldtype": "Link", + "label": "Advances Paid Account", "options": "Account" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-04-04 12:31:02.994197", + "modified": "2023-06-02 13:00:06.885744", "modified_by": "Administrator", "module": "Accounts", "name": "Party Account", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 2843824934..7bd3bb8710 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -18,14 +18,20 @@ frappe.ui.form.on('Payment Entry', { }, setup: function(frm) { + advance_payments_as_liability = frappe.db.get_value("Company", {"company_name": frm.doc.company}, "book_advance_payments_as_liability"); + + if(advance_payments_as_liability && frm.doc.payment_type == 'Receive'){ + account_type = "Payable"; + } + else{ + account_type = "Receivable"; + } + frm.set_query("paid_from", function() { frm.events.validate_company(frm); - - var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ? - ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; return { filters: { - "account_type": ["in", account_types], + "account_type": account_type, "is_group": 0, "company": frm.doc.company } @@ -74,12 +80,15 @@ frappe.ui.form.on('Payment Entry', { frm.set_query("paid_to", function() { frm.events.validate_company(frm); - - var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ? - ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; + if(advance_payments_as_liability && in_list(['Receive', 'Internal Transfer'], cur_frm.doc.payment_type)){ + account_type = ["Bank", "Cash"]; + } + else{ + account_type = "Receivable"; + } return { filters: { - "account_type": ["in", account_types], + "account_type": ["in", account_type], "is_group": 0, "company": frm.doc.company } @@ -270,6 +279,25 @@ frappe.ui.form.on('Payment Entry', { }, payment_type: function(frm) { + advance_payments_as_liability = frappe.db.get_value("Company", {"company_name": frm.doc.company}, "book_advance_payments_as_liability"); + + if(advance_payments_as_liability && frm.doc.payment_type == 'Receive'){ + account_type = ["Payable"]; + } + else{ + account_type = ["Bank", "Cash"]; + } + + frm.set_query("paid_from", function() { + frm.events.validate_company(frm); + return { + filters: { + "account_type": ["in", account_type], + "is_group": 0, + "company": frm.doc.company + } + } + }); if(frm.doc.payment_type == "Internal Transfer") { $.each(["party", "party_balance", "paid_from", "paid_to", "references", "total_allocated_amount"], function(i, field) { @@ -318,6 +346,10 @@ frappe.ui.form.on('Payment Entry', { } }, + company: function(frm){ + frm.trigger('party'); + }, + party: function(frm) { if (frm.doc.contact_email || frm.doc.contact_person) { frm.set_value("contact_email", ""); @@ -332,7 +364,7 @@ frappe.ui.form.on('Payment Entry', { frm.set_party_account_based_on_party = true; let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; - + return frappe.call({ method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_details", args: { @@ -340,7 +372,8 @@ frappe.ui.form.on('Payment Entry', { party_type: frm.doc.party_type, party: frm.doc.party, date: frm.doc.posting_date, - cost_center: frm.doc.cost_center + cost_center: frm.doc.cost_center, + is_advance: !(frm.doc.references) }, callback: function(r, rt) { if(r.message) { diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 3df48e22ad..ed2158a649 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1563,13 +1563,12 @@ def get_negative_outstanding_invoices( @frappe.whitelist() -def get_party_details(company, party_type, party, date, cost_center=None): +def get_party_details(company, party_type, party, date, cost_center=None, is_advance=False): bank_account = "" if not frappe.db.exists(party_type, party): frappe.throw(_("Invalid {0}: {1}").format(party_type, party)) - party_account = get_party_account(party_type, party, company) - + party_account = get_party_account(party_type, party, company, is_advance) account_currency = get_account_currency(party_account) account_balance = get_balance_on(party_account, date, cost_center=cost_center) _party_name = "title" if party_type == "Shareholder" else party_type.lower() + "_name" diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 08d38dde47..2e9628c9f0 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -29,6 +29,26 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo }; }); + this.frm.set_query('default_advances_received_account', () => { + return { + filters: { + "company": this.frm.doc.company, + "is_group": 0, + "root_type": "Liability" + } + }; + }); + + this.frm.set_query('default_advances_paid_account', () => { + return { + filters: { + "company": this.frm.doc.company, + "is_group": 0, + "root_type": "Asset" + } + }; + }); + this.frm.set_query('bank_cash_account', () => { return { filters:[ @@ -124,7 +144,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo this.frm.trigger("clear_child_tables"); if (!this.frm.doc.receivable_payable_account && this.frm.doc.party_type && this.frm.doc.party) { - return frappe.call({ + frappe.call({ method: "erpnext.accounts.party.get_party_account", args: { company: this.frm.doc.company, @@ -136,7 +156,38 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo this.frm.set_value("receivable_payable_account", r.message); } this.frm.refresh(); + } + }); + frappe.call({ + method: "erpnext.accounts.party.get_party_account", + args: { + company: this.frm.doc.company, + party_type: this.frm.doc.party_type, + party: this.frm.doc.party, + is_advance: 1 + }, + callback: (r) => { + if (!r.exc && r.message) { + this.frm.set_value("default_advances_received_account", r.message); + } + this.frm.refresh(); + } + }); + + frappe.call({ + method: "erpnext.accounts.party.get_party_account", + args: { + company: this.frm.doc.company, + party_type: (this.frm.doc.party_type == 'Customer')?'Supplier':'Customer', + party: this.frm.doc.party, + is_advance: 1 + }, + callback: (r) => { + if (!r.exc && r.message) { + this.frm.set_value("default_advances_paid_account", r.message); + } + this.frm.refresh(); } }); } diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index 18d3485085..21b8392d60 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -7,9 +7,11 @@ "field_order": [ "company", "party_type", - "column_break_4", "party", + "column_break_4", "receivable_payable_account", + "default_advances_received_account", + "default_advances_paid_account", "col_break1", "from_invoice_date", "from_payment_date", @@ -185,13 +187,30 @@ "fieldtype": "Link", "label": "Cost Center", "options": "Cost Center" + }, + { + "depends_on": "eval:doc.party_type", + "fieldname": "default_advances_received_account", + "fieldtype": "Link", + "label": "Default Advances Received Account", + "mandatory_depends_on": "doc.party_type", + "options": "Account", + "reqd": 1 + }, + { + "depends_on": "eval:doc.party_type", + "fieldname": "default_advances_paid_account", + "fieldtype": "Link", + "label": "Default Advances Paid Account", + "mandatory_depends_on": "doc.party_type", + "options": "Account" } ], "hide_toolbar": 1, "icon": "icon-resize-horizontal", "issingle": 1, "links": [], - "modified": "2022-04-29 15:37:10.246831", + "modified": "2023-06-02 14:32:27.276083", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index cc2b9420cc..9b03d36a8e 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -19,7 +19,8 @@ from erpnext.accounts.utils import ( reconcile_against_document, ) from erpnext.controllers.accounts_controller import get_advance_payment_entries - +from erpnext.controllers.accounts_controller import make_advance_liability_entry +from erpnext.accounts.general_ledger import make_gl_entries class PaymentReconciliation(Document): def __init__(self, *args, **kwargs): @@ -56,12 +57,26 @@ class PaymentReconciliation(Document): self.add_payment_entries(non_reconciled_payments) def get_payment_entries(self): + receivable_payable_account = self.receivable_payable_account + default_advances_account = self.default_advances_received_account + party_account = [receivable_payable_account, default_advances_account] order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order" - condition = self.get_conditions(get_payments=True) + condition = frappe._dict( + { + "company": self.get("company"), + "get_payments": True, + "cost_center": self.get("cost_center"), + "from_payment_date": self.get("from_payment_date"), + "to_payment_date": self.get("to_payment_date"), + "maximum_payment_amount": self.get("maximum_payment_amount"), + "minimum_payment_amount": self.get("minimum_payment_amount") + } + ) + payment_entries = get_advance_payment_entries( - self.party_type, + self.party_type, self.party, - self.receivable_payable_account, + party_account, order_doctype, against_all_orders=True, limit=self.payment_limit, @@ -319,6 +334,10 @@ class PaymentReconciliation(Document): for row in self.get("allocation"): reconciled_entry = [] if row.invoice_number and row.allocated_amount: + if row.invoice_type in ["Sales Invoice", "Purchase Invoice"]: + gl_entries = [] + make_advance_liability_entry(gl_entries, row.reference_name, row.allocated_amount, row.invoice_number, self.party_type) + make_gl_entries(gl_entries) if row.reference_type in ["Sales Invoice", "Purchase Invoice"]: reconciled_entry = dr_or_cr_notes else: diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 868a150edf..dfea8e9049 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -34,6 +34,7 @@ from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accoun from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.accounts_controller import validate_account_head +from erpnext.controllers.accounts_controller import make_advance_liability_entry from erpnext.controllers.buying_controller import BuyingController from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( @@ -580,6 +581,12 @@ class PurchaseInvoice(BuyingController): gl_entries = [] self.make_supplier_gl_entry(gl_entries) + + advance_payments_as_liability = frappe.db.get_value("Company", {"company_name": self.company}, "book_advance_payments_as_liability") + if advance_payments_as_liability: + for advance_entry in self.advances: + make_advance_liability_entry(gl_entries, advance_entry.reference_name, advance_entry.allocated_amount, invoice=self.name, party_type="Supplier") + self.make_item_gl_entries(gl_entries) self.make_precision_loss_gl_entry(gl_entries) diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json index 9fcbf5c633..9082115f23 100644 --- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json +++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json @@ -14,7 +14,8 @@ "advance_amount", "allocated_amount", "exchange_gain_loss", - "ref_exchange_rate" + "ref_exchange_rate", + "account" ], "fields": [ { @@ -111,13 +112,20 @@ "label": "Reference Exchange Rate", "non_negative": 1, "read_only": 1 + }, + { + "fieldname": "account", + "fieldtype": "Link", + "label": "Account", + "options": "Account", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-09-26 15:47:28.167371", + "modified": "2023-06-01 16:56:48.530169", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Advance", @@ -125,5 +133,6 @@ "permissions": [], "quick_entry": 1, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 7454332cd3..419628eaff 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -33,6 +33,7 @@ from erpnext.assets.doctype.asset.depreciation import ( reverse_depreciation_entry_made_after_disposal, ) from erpnext.controllers.accounts_controller import validate_account_head +from erpnext.controllers.accounts_controller import make_advance_liability_entry from erpnext.controllers.selling_controller import SellingController from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data from erpnext.setup.doctype.company.company import update_company_current_month_sales @@ -1064,6 +1065,11 @@ class SalesInvoice(SellingController): gl_entries = [] self.make_customer_gl_entry(gl_entries) + + advance_payments_as_liability = frappe.db.get_value("Company", {"company_name": self.company}, "book_advance_payments_as_liability") + if advance_payments_as_liability: + for advance_entry in self.advances: + make_advance_liability_entry(gl_entries, advance_entry.reference_name, advance_entry.allocated_amount, invoice=self.name, party_type="Customer") self.make_tax_gl_entries(gl_entries) self.make_exchange_gain_loss_gl_entries(gl_entries) diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json index f92b57a45e..aa52b1cac2 100644 --- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json +++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json @@ -14,7 +14,8 @@ "advance_amount", "allocated_amount", "exchange_gain_loss", - "ref_exchange_rate" + "ref_exchange_rate", + "account" ], "fields": [ { @@ -112,13 +113,20 @@ "label": "Reference Exchange Rate", "non_negative": 1, "read_only": 1 + }, + { + "fieldname": "account", + "fieldtype": "Link", + "label": "Account", + "options": "Account", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-09-26 15:47:46.911595", + "modified": "2023-05-31 11:47:00.191681", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Advance", @@ -126,5 +134,6 @@ "permissions": [], "quick_entry": 1, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index f86dd8f57e..3be4275888 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -365,7 +365,7 @@ def set_account_and_due_date( @frappe.whitelist() -def get_party_account(party_type, party=None, company=None): +def get_party_account(party_type, party=None, company=None, is_advance=False): """Returns the account for the given `party`. Will first search in party (Customer / Supplier) record, if not found, will search in group (Customer Group / Supplier Group), @@ -380,6 +380,11 @@ def get_party_account(party_type, party=None, company=None): return frappe.get_cached_value("Company", company, default_account_name) + advance_payments_as_liability = frappe.db.get_value("Company", {"company_name": company}, "book_advance_payments_as_liability") + + if is_advance and advance_payments_as_liability and party_type in ["Customer", "Supplier"]: + return get_party_advance_account(party_type, party, company) + account = frappe.db.get_value( "Party Account", {"parenttype": party_type, "parent": party, "company": company}, "account" ) @@ -409,6 +414,26 @@ def get_party_account(party_type, party=None, company=None): return account +def get_party_advance_account(party_type, party, company): + account_name = 'advances_received_account' if party_type == 'Customer' else 'advances_paid_account' + account = frappe.db.get_value( + "Party Account", {"parenttype": party_type, "parent": party, "company": company}, account_name + ) + + if not account: + party_group_doctype = "Customer Group" if party_type == "Customer" else "Supplier Group" + group = frappe.get_cached_value(party_type, party, scrub(party_group_doctype)) + account = frappe.db.get_value( + "Party Account", + {"parenttype": party_group_doctype, "parent": group, "company": company}, + account_name, + ) + + if not account: + account = frappe.get_cached_value("Company", company, "default_" + account_name) + + return account + @frappe.whitelist() def get_party_bank_account(party_type, party): return frappe.db.get_value( @@ -515,7 +540,10 @@ def validate_party_accounts(doc): ) # validate if account is mapped for same company - validate_account_head(account.idx, account.account, account.company) + if account.account: + validate_account_head(account.idx, account.account, account.company) + if account.advance_account: + validate_account_head(account.idx, account.advance_account, account.company) @frappe.whitelist() diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 0ee06e8239..5abc64315c 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -495,50 +495,51 @@ def check_if_advance_entry_modified(args): ret = None if args.voucher_type == "Journal Entry": - ret = frappe.db.sql( - """ - select t2.{dr_or_cr} from `tabJournal Entry` t1, `tabJournal Entry Account` t2 - where t1.name = t2.parent and t2.account = %(account)s - and t2.party_type = %(party_type)s and t2.party = %(party)s - and (t2.reference_type is null or t2.reference_type in ('', 'Sales Order', 'Purchase Order')) - and t1.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s - and t1.docstatus=1 """.format( - dr_or_cr=args.get("dr_or_cr") - ), - args, + journal_entry = frappe.qb.DocType("Journal Entry") + journal_acc = frappe.qb.DocType("Journal Entry Account") + + q = (frappe.qb.from_(journal_entry) + .innerjoin(journal_acc) + .on(journal_entry.name == journal_acc.parent) ) + + if args.get("dr_or_cr") == 'debit_in_account_currency': + q = q.select(journal_acc.debit_in_account_currency) + else: + q = q.select(journal_acc.credit_in_account_currency) + + q = q.where((journal_acc.account == args.get("account")) + &((journal_acc.party_type == args.get("party_type"))) + &((journal_acc.party == args.get("party"))) + &((journal_acc.reference_type == None) | (journal_acc.reference_type.isin(['', 'Sales Order', 'Purchase Order']))) + &((journal_entry.name == args.get("voucher_no"))) + &((journal_acc.name == args.get("voucher_detail_no"))) + &((journal_entry.docstatus == 1)) + ) + else: - party_account_field = ( - "paid_from" if erpnext.get_party_account_type(args.party_type) == "Receivable" else "paid_to" + payment_entry = frappe.qb.DocType("Payment Entry") + payment_ref = frappe.qb.DocType("Payment Entry Reference") + + q = (frappe.qb.from_(payment_entry) + .select(payment_entry.name) + .where(payment_entry.name == args.get("voucher_no")) + .where(payment_entry.docstatus == 1) + .where(payment_entry.party_type == args.get("party_type")) + .where(payment_entry.party == args.get("party")) ) if args.voucher_detail_no: - ret = frappe.db.sql( - """select t1.name - from `tabPayment Entry` t1, `tabPayment Entry Reference` t2 - where - t1.name = t2.parent and t1.docstatus = 1 - and t1.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s - and t1.party_type = %(party_type)s and t1.party = %(party)s and t1.{0} = %(account)s - and t2.reference_doctype in ('', 'Sales Order', 'Purchase Order') - and t2.allocated_amount = %(unreconciled_amount)s - """.format( - party_account_field - ), - args, - ) + q = ( q.inner_join(payment_ref) + .on(payment_entry.name == payment_ref.parent) + .where(payment_ref.name == args.get("voucher_detail_no")) + .where(payment_ref.reference_doctype.isin(('', 'Sales Order', 'Purchase Order'))) + .where(payment_ref.allocated_amount == args.get("unreconciled_amount")) + ) else: - ret = frappe.db.sql( - """select name from `tabPayment Entry` - where - name = %(voucher_no)s and docstatus = 1 - and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s - and unallocated_amount = %(unreconciled_amount)s - """.format( - party_account_field - ), - args, - ) + q = q.where(payment_entry.unallocated_amount == args.get("unreconciled_amount")) + + ret = q.run(as_dict=True) if not ret: throw(_("""Payment Entry has been modified after you pulled it. Please pull it again.""")) diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js index 1ae6f03647..9e217b5113 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -17,6 +17,29 @@ frappe.ui.form.on("Supplier", { } } }); + + frm.set_query('advances_received_account', 'accounts', function (doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + filters: { + "root_type": 'Asset', + "company": d.company, + "is_group": 0 + } + } + }); + + frm.set_query('advances_paid_account', 'accounts', function (doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + filters: { + "root_type": 'Liability', + "company": d.company, + "is_group": 0 + } + } + }); + frm.set_query("default_bank_account", function() { return { filters: { diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 1bf7f589e2..86e5eeecbf 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -53,6 +53,7 @@ "primary_address", "accounting_tab", "payment_terms", + "default_accounts_section", "accounts", "settings_tab", "allow_purchase_invoice_creation_without_purchase_order", @@ -445,6 +446,11 @@ { "fieldname": "column_break_59", "fieldtype": "Column Break" + }, + { + "fieldname": "default_accounts_section", + "fieldtype": "Section Break", + "label": "Default Accounts" } ], "icon": "fa fa-user", @@ -457,7 +463,7 @@ "link_fieldname": "party" } ], - "modified": "2023-02-18 11:05:50.592270", + "modified": "2023-05-29 15:23:11.709415", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", @@ -489,7 +495,6 @@ "read": 1, "report": 1, "role": "Purchase Master Manager", - "set_user_permissions": 1, "share": 1, "write": 1 }, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 20b332e782..ac32fd352c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -8,6 +8,8 @@ import frappe from frappe import _, bold, throw from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied from frappe.query_builder.functions import Abs, Sum +from frappe.query_builder.custom import ConstantColumn + from frappe.utils import ( add_days, add_months, @@ -871,24 +873,30 @@ class AccountsController(TransactionBase): "allocated_amount": allocated_amount, "ref_exchange_rate": flt(d.exchange_rate), # exchange_rate of advance entry } + if d.get("paid_from"): + advance_row["account"] = d.paid_from + if d.get("paid_to"): + advance_row["account"] = d.paid_to self.append("advances", advance_row) def get_advance_entries(self, include_unallocated=True): if self.doctype == "Sales Invoice": - party_account = self.debit_to party_type = "Customer" party = self.customer amount_field = "credit_in_account_currency" order_field = "sales_order" order_doctype = "Sales Order" + party_account = frappe.db.get_values("Company", {"company_name": self.company}, ["default_receivable_account", "default_advances_received_account"]) else: - party_account = self.credit_to party_type = "Supplier" party = self.supplier amount_field = "debit_in_account_currency" order_field = "purchase_order" order_doctype = "Purchase Order" + party_account = frappe.db.get_values("Company", {"company_name": self.company}, ["default_receivable_account", "default_advances_paid_account"]) + + party_account = list(party_account[0]) order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field))) @@ -2136,45 +2144,41 @@ def get_advance_journal_entries( order_list, include_unallocated=True, ): - dr_or_cr = ( - "credit_in_account_currency" if party_type == "Customer" else "debit_in_account_currency" - ) - - conditions = [] - if include_unallocated: - conditions.append("ifnull(t2.reference_name, '')=''") + journal_entry = frappe.qb.DocType('Journal Entry') + journal_acc = frappe.qb.DocType('Journal Entry Account') + q = (frappe.qb.from_(journal_entry) + .inner_join(journal_acc) + .on(journal_entry.name == journal_acc.parent) + .select( + ConstantColumn('Journal Entry').as_('reference_type'), + (journal_entry.name).as_('reference_name'), + (journal_entry.remark).as_('remarks'), + (journal_acc.debit_in_account_currency if party_type == 'Supplier' else journal_acc.credit_in_account_currency).as_('amount'), + (journal_acc.name).as_('reference_row'), + (journal_acc.reference_name).as_('against_order'), + (journal_acc.exchange_rate) + ) + .where(journal_acc.account.isin(party_account) + & (journal_acc.party_type == party_type) + & (journal_acc.party == party) + & (journal_acc.is_advance == 'Yes') + & (journal_entry.docstatus == 1) + ) + ) + if party_type == "Customer": + q = q.where(journal_acc.credit_in_account_currency > 0) if order_list: - order_condition = ", ".join(["%s"] * len(order_list)) - conditions.append( - " (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))".format( - order_doctype, order_condition - ) - ) + q = q.where(journal_acc.reference_type == order_doctype) + if include_unallocated: + q = q.where(journal_acc.reference_name.isin(order_list) + |(journal_acc.reference_name == '')) + else: + q = q.where(journal_acc.reference_name.isin(order_list)) - reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else "" - - # nosemgrep - journal_entries = frappe.db.sql( - """ - select - 'Journal Entry' as reference_type, t1.name as reference_name, - t1.remark as remarks, t2.{0} as amount, t2.name as reference_row, - t2.reference_name as against_order, t2.exchange_rate - from - `tabJournal Entry` t1, `tabJournal Entry Account` t2 - where - t1.name = t2.parent and t2.account = %s - and t2.party_type = %s and t2.party = %s - and t2.is_advance = 'Yes' and t1.docstatus = 1 - and {1} > 0 {2} - order by t1.posting_date""".format( - amount_field, dr_or_cr, reference_condition - ), - [party_account, party_type, party] + order_list, - as_dict=1, - ) + q = q.orderby(journal_entry.posting_date) + journal_entries = q.run(as_dict=True) return list(journal_entries) @@ -2189,65 +2193,76 @@ def get_advance_payment_entries( limit=None, condition=None, ): - party_account_field = "paid_from" if party_type == "Customer" else "paid_to" - currency_field = ( - "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency" - ) + + q = build_query(party_type, party, party_account, order_doctype, order_list, include_unallocated, against_all_orders, limit, condition) + + payment_entries = q.run(as_dict=True) + + return list(payment_entries) + +def build_query(party_type, party, party_account, order_doctype, order_list, include_unallocated, against_all_orders, limit, condition): payment_type = "Receive" if party_type == "Customer" else "Pay" - exchange_rate_field = ( - "source_exchange_rate" if payment_type == "Receive" else "target_exchange_rate" - ) + payment_entry = frappe.qb.DocType('Payment Entry') + payment_ref = frappe.qb.DocType('Payment Entry Reference') - payment_entries_against_order, unallocated_payment_entries = [], [] - limit_cond = "limit %s" % limit if limit else "" - - if order_list or against_all_orders: - if order_list: - reference_condition = " and t2.reference_name in ({0})".format( - ", ".join(["%s"] * len(order_list)) - ) - else: - reference_condition = "" - order_list = [] - - payment_entries_against_order = frappe.db.sql( - """ - select - 'Payment Entry' as reference_type, t1.name as reference_name, - t1.remarks, t2.allocated_amount as amount, t2.name as reference_row, - t2.reference_name as against_order, t1.posting_date, - t1.{0} as currency, t1.{4} as exchange_rate - from `tabPayment Entry` t1, `tabPayment Entry Reference` t2 - where - t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s - and t1.party_type = %s and t1.party = %s and t1.docstatus = 1 - and t2.reference_doctype = %s {2} - order by t1.posting_date {3} - """.format( - currency_field, party_account_field, reference_condition, limit_cond, exchange_rate_field - ), - [party_account, payment_type, party_type, party, order_doctype] + order_list, - as_dict=1, + q = (frappe.qb.from_(payment_entry) + .select( + ConstantColumn('Payment Entry').as_('reference_type'), + (payment_entry.name).as_('reference_name'), + payment_entry.posting_date, + (payment_entry.remarks).as_('remarks') ) + .where(payment_entry.payment_type == payment_type) + .where(payment_entry.party_type == party_type) + .where(payment_entry.party == party) + .where(payment_entry.docstatus == 1) + ) + + if party_type == "Customer": + q = q.select(payment_entry.paid_from_account_currency) + q = q.select(payment_entry.paid_from) + q = q.where(payment_entry.paid_from.isin(party_account)) + else: + q = q.select(payment_entry.paid_to_account_currency) + q = q.select(payment_entry.paid_to) + q = q.where(payment_entry.paid_to.isin(party_account)) + + if payment_type == "Receive": + q = q.select(payment_entry.source_exchange_rate) + else: + q.select(payment_entry.target_exchange_rate) if include_unallocated: - unallocated_payment_entries = frappe.db.sql( - """ - select 'Payment Entry' as reference_type, name as reference_name, posting_date, - remarks, unallocated_amount as amount, {2} as exchange_rate, {3} as currency - from `tabPayment Entry` - where - {0} = %s and party_type = %s and party = %s and payment_type = %s - and docstatus = 1 and unallocated_amount > 0 {condition} - order by posting_date {1} - """.format( - party_account_field, limit_cond, exchange_rate_field, currency_field, condition=condition or "" - ), - (party_account, party_type, party, payment_type), - as_dict=1, - ) + q = q.select((payment_entry.unallocated_amount).as_('amount')) + q = q.where(payment_entry.unallocated_amount>0) - return list(payment_entries_against_order) + list(unallocated_payment_entries) + if condition: + q = q.where(payment_entry.company == condition["company"]) + q = q.where(payment_entry.posting_date >= condition["from_payment_date"]) if condition.get("from_payment_date") else q + q = q.where(payment_entry.posting_date <= condition["to_payment_date"]) if condition.get("to_payment_date") else q + if condition.get("get_payments") == True: + q = q.where(payment_entry.cost_center == condition["cost_center"]) if condition.get("cost_center") else q + q = q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"]) if condition.get("minimum_payment_amount") else q + q = q.where(payment_entry.unallocated_amount <= condition["maximum_payment_amount"]) if condition.get("maximum_payment_amount") else q + else: + q = q.where(payment_entry.total_debit >= condition["minimum_payment_amount"]) if condition.get("minimum_payment_amount") else q + q = q.where(payment_entry.total_debit <= condition["maximum_payment_amount"]) if condition.get("maximum_payment_amount") else q + + elif order_list or against_all_orders: + q = q.inner_join(payment_ref).on(payment_entry.name == payment_ref.parent) + q = q.select( + (payment_ref.allocated_amount).as_('amount'), + (payment_ref.name).as_('reference_row'), + (payment_ref.reference_name).as_('against_order'), + payment_ref.reference_doctype == order_doctype + ) + + if order_list: + q = q.where(payment_ref.reference_name.isin(order_list)) + + q = q.orderby(payment_entry.posting_date) + q = q.limit(limit) if limit else q + return q def update_invoice_status(): @@ -2846,3 +2861,72 @@ def validate_regional(doc): @erpnext.allow_regional def validate_einvoice_fields(doc): pass + +def make_advance_liability_entry(gl_entries, pe, allocated_amount, invoice, party_type): + pe = frappe.get_doc("Payment Entry", pe) + if party_type=="Customer": + invoice = frappe.get_doc("Sales Invoice", invoice) + account = pe.paid_from + dr_or_cr = "debit" + rev = "credit" + against = invoice.debit_to + party = invoice.customer + voucher_type = "Sales Invoice" + else: + invoice = frappe.get_doc("Purchase Invoice", invoice) + account = pe.paid_to + dr_or_cr = "credit" + rev = "debit" + against = invoice.credit_to + party = invoice.supplier + voucher_type = "Purchase Invoice" + gl_entries.append(invoice.get_gl_dict( + { + "account": account, + "party_type": party_type, + "party": party, + "due_date": invoice.due_date, + "against": against, + dr_or_cr: allocated_amount, + dr_or_cr + "_in_account_currency": allocated_amount, + rev: 0, + rev + "_in_account_currency": 0, + "against_voucher": invoice.return_against + if cint(invoice.is_return) and invoice.return_against + else invoice.name, + "against_voucher_type": invoice.doctype, + "cost_center": invoice.cost_center, + "project": invoice.project, + "voucher_type": voucher_type, + "voucher_no": invoice.name + }, + invoice.party_account_currency, + item=invoice, + )) + + (dr_or_cr, rev) = ("credit", "debit") if party_type=="Customer" else ("debit", "credit") + gl_entries.append(invoice.get_gl_dict( + { + "account": against, + "party_type": party_type, + "party": party, + "due_date": invoice.due_date, + "against": account, + dr_or_cr: allocated_amount, + dr_or_cr + "_in_account_currency": allocated_amount, + rev: 0, + rev + "_in_account_currency": 0, + "against_voucher": invoice.return_against + if cint(invoice.is_return) and invoice.return_against + else invoice.name, + "against_voucher_type": invoice.doctype, + "cost_center": invoice.cost_center, + "project": invoice.project, + "voucher_type": voucher_type, + "voucher_no": invoice.name + }, + invoice.party_account_currency, + item=invoice, + )) + + diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index b53f339229..6dac692747 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -34,6 +34,28 @@ frappe.ui.form.on("Customer", { filters: filters } }); + + frm.set_query('advances_received_account', 'accounts', function (doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + filters: { + "root_type": 'Liability', + "company": d.company, + "is_group": 0 + } + } + }); + + frm.set_query('advances_paid_account', 'accounts', function (doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + filters: { + "root_type": 'Asset', + "company": d.company, + "is_group": 0 + } + } + }); if (frm.doc.__islocal == 1) { frm.set_value("represents_company", ""); diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index c133cd3152..0050279a4b 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -334,13 +334,13 @@ { "fieldname": "default_receivable_accounts", "fieldtype": "Section Break", - "label": "Default Receivable Accounts" + "label": "Default Accounts" }, { "description": "Mention if a non-standard receivable account", "fieldname": "accounts", "fieldtype": "Table", - "label": "Receivable Accounts", + "label": "Accounts", "options": "Party Account" }, { @@ -568,7 +568,7 @@ "link_fieldname": "party" } ], - "modified": "2023-02-18 11:04:46.343527", + "modified": "2023-05-29 14:29:17.789578", "modified_by": "Administrator", "module": "Selling", "name": "Customer", @@ -607,7 +607,6 @@ "read": 1, "report": 1, "role": "Sales Master Manager", - "set_user_permissions": 1, "share": 1, "write": 1 }, diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index e50ce449e4..81919af4e1 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -226,7 +226,9 @@ erpnext.company.setup_queries = function(frm) { ["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}], ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}], ["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}], - ["default_provisional_account", {"root_type": ["in", ["Liability", "Asset"]]}] + ["default_provisional_account", {"root_type": ["in", ["Liability", "Asset"]]}], + ["default_advances_received_account", {"root_type": "Liability"}], + ["default_advances_paid_account", {"root_type": "Asset"}], ], function(i, v) { erpnext.company.set_custom_query(frm, v); }); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index f087d996ff..369e139eef 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -70,6 +70,11 @@ "payment_terms", "cost_center", "default_finance_book", + "advance_payments_section", + "book_advance_payments_as_liability", + "default_advances_received_account", + "column_break_cui0", + "default_advances_paid_account", "auto_accounting_for_stock_settings", "enable_perpetual_inventory", "enable_provisional_accounting_for_non_stock_items", @@ -694,6 +699,37 @@ "label": "Default Provisional Account", "no_copy": 1, "options": "Account" + }, + { + "default": "0", + "fieldname": "book_advance_payments_as_liability", + "fieldtype": "Check", + "label": "Book Advance Payments as Liability" + }, + { + "fieldname": "advance_payments_section", + "fieldtype": "Section Break", + "label": "Advance Payments" + }, + { + "depends_on": "eval:doc.book_advance_payments_as_liability", + "fieldname": "default_advances_received_account", + "fieldtype": "Link", + "label": "Default Advances Received Account", + "mandatory_depends_on": "book_advance_payments_as_liability", + "options": "Account" + }, + { + "depends_on": "eval:doc.book_advance_payments_as_liability", + "fieldname": "default_advances_paid_account", + "fieldtype": "Link", + "label": "Default Advances Paid Account", + "mandatory_depends_on": "book_advance_payments_as_liability", + "options": "Account" + }, + { + "fieldname": "column_break_cui0", + "fieldtype": "Column Break" } ], "icon": "fa fa-building", @@ -701,7 +737,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2022-08-16 16:09:02.327724", + "modified": "2023-06-02 13:11:41.939016", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/setup/doctype/customer_group/customer_group.js b/erpnext/setup/doctype/customer_group/customer_group.js index 44a5019120..ef556c774d 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.js +++ b/erpnext/setup/doctype/customer_group/customer_group.js @@ -30,9 +30,31 @@ cur_frm.fields_dict['accounts'].grid.get_field('account').get_query = function(d var d = locals[cdt][cdn]; return { filters: { - 'account_type': 'Receivable', - 'company': d.company, + "account_type": 'Receivable', + "company": d.company, "is_group": 0 } } } + +cur_frm.fields_dict['accounts'].grid.get_field('advances_received_account').get_query = function(doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + filters: { + "root_type": 'Liability', + "company": d.company, + "is_group": 0 + } + } +} + +cur_frm.fields_dict['accounts'].grid.get_field('advances_paid_account').get_query = function(doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + filters: { + "root_type": 'Asset', + "company": d.company, + "is_group": 0 + } + } +} \ No newline at end of file diff --git a/erpnext/setup/doctype/customer_group/customer_group.json b/erpnext/setup/doctype/customer_group/customer_group.json index d6a431ea61..4c36bc77ab 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.json +++ b/erpnext/setup/doctype/customer_group/customer_group.json @@ -113,7 +113,7 @@ { "fieldname": "default_receivable_account", "fieldtype": "Section Break", - "label": "Default Receivable Account" + "label": "Default Accounts" }, { "depends_on": "eval:!doc.__islocal", @@ -139,7 +139,7 @@ "idx": 1, "is_tree": 1, "links": [], - "modified": "2022-12-24 11:15:17.142746", + "modified": "2023-06-02 13:40:34.435822", "modified_by": "Administrator", "module": "Setup", "name": "Customer Group", @@ -171,7 +171,6 @@ "read": 1, "report": 1, "role": "Sales Master Manager", - "set_user_permissions": 1, "share": 1, "write": 1 }, From 4ee163742a705083bd9b00e36aac9099efadaabd Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 8 Jun 2023 13:15:23 +0530 Subject: [PATCH 02/40] fix: Using one field for both advance liability accounts --- .../doctype/party_account/party_account.json | 15 +- .../doctype/payment_entry/payment_entry.js | 54 +--- .../doctype/payment_entry/payment_entry.json | 2 +- .../doctype/payment_entry/payment_entry.py | 116 ++++++-- .../payment_entry_reference.json | 11 +- .../payment_reconciliation.js | 32 +- .../payment_reconciliation.json | 17 +- .../payment_reconciliation.py | 31 +- .../purchase_invoice/purchase_invoice.json | 5 +- .../sales_invoice/test_sales_invoice.py | 4 +- erpnext/accounts/party.py | 18 +- erpnext/accounts/utils.py | 38 ++- erpnext/buying/doctype/supplier/supplier.js | 13 +- erpnext/controllers/accounts_controller.py | 275 +++++++++++------- erpnext/selling/doctype/customer/customer.js | 14 +- .../selling/doctype/customer/customer.json | 14 +- erpnext/setup/doctype/company/company.js | 3 +- erpnext/setup/doctype/company/company.json | 29 +- .../doctype/customer_group/customer_group.js | 13 +- .../doctype/supplier_group/supplier_group.js | 11 + 20 files changed, 385 insertions(+), 330 deletions(-) diff --git a/erpnext/accounts/doctype/party_account/party_account.json b/erpnext/accounts/doctype/party_account/party_account.json index 719b474f1f..6ac6e56086 100644 --- a/erpnext/accounts/doctype/party_account/party_account.json +++ b/erpnext/accounts/doctype/party_account/party_account.json @@ -7,8 +7,7 @@ "field_order": [ "company", "account", - "advances_received_account", - "advances_paid_account" + "advance_account" ], "fields": [ { @@ -28,22 +27,16 @@ "options": "Account" }, { - "fieldname": "advances_received_account", + "fieldname": "advance_account", "fieldtype": "Link", - "label": "Advances Received Account", - "options": "Account" - }, - { - "fieldname": "advances_paid_account", - "fieldtype": "Link", - "label": "Advances Paid Account", + "label": "Advance Account", "options": "Account" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-06-02 13:00:06.885744", + "modified": "2023-06-05 14:15:42.053150", "modified_by": "Administrator", "module": "Accounts", "name": "Party Account", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 7bd3bb8710..1f0e45fc11 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -18,20 +18,14 @@ frappe.ui.form.on('Payment Entry', { }, setup: function(frm) { - advance_payments_as_liability = frappe.db.get_value("Company", {"company_name": frm.doc.company}, "book_advance_payments_as_liability"); - - if(advance_payments_as_liability && frm.doc.payment_type == 'Receive'){ - account_type = "Payable"; - } - else{ - account_type = "Receivable"; - } - frm.set_query("paid_from", function() { frm.events.validate_company(frm); + + var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ? + ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; return { filters: { - "account_type": account_type, + "account_type": ["in", account_types], "is_group": 0, "company": frm.doc.company } @@ -80,15 +74,12 @@ frappe.ui.form.on('Payment Entry', { frm.set_query("paid_to", function() { frm.events.validate_company(frm); - if(advance_payments_as_liability && in_list(['Receive', 'Internal Transfer'], cur_frm.doc.payment_type)){ - account_type = ["Bank", "Cash"]; - } - else{ - account_type = "Receivable"; - } + + var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ? + ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; return { filters: { - "account_type": ["in", account_type], + "account_type": ["in", account_types], "is_group": 0, "company": frm.doc.company } @@ -279,25 +270,6 @@ frappe.ui.form.on('Payment Entry', { }, payment_type: function(frm) { - advance_payments_as_liability = frappe.db.get_value("Company", {"company_name": frm.doc.company}, "book_advance_payments_as_liability"); - - if(advance_payments_as_liability && frm.doc.payment_type == 'Receive'){ - account_type = ["Payable"]; - } - else{ - account_type = ["Bank", "Cash"]; - } - - frm.set_query("paid_from", function() { - frm.events.validate_company(frm); - return { - filters: { - "account_type": ["in", account_type], - "is_group": 0, - "company": frm.doc.company - } - } - }); if(frm.doc.payment_type == "Internal Transfer") { $.each(["party", "party_balance", "paid_from", "paid_to", "references", "total_allocated_amount"], function(i, field) { @@ -364,7 +336,7 @@ frappe.ui.form.on('Payment Entry', { frm.set_party_account_based_on_party = true; let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; - + return frappe.call({ method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_details", args: { @@ -372,8 +344,7 @@ frappe.ui.form.on('Payment Entry', { party_type: frm.doc.party_type, party: frm.doc.party, date: frm.doc.posting_date, - cost_center: frm.doc.cost_center, - is_advance: !(frm.doc.references) + cost_center: frm.doc.cost_center }, callback: function(r, rt) { if(r.message) { @@ -741,7 +712,7 @@ frappe.ui.form.on('Payment Entry', { if(r.message) { var total_positive_outstanding = 0; var total_negative_outstanding = 0; - + console.log(r.message); $.each(r.message, function(i, d) { var c = frm.add_child("references"); c.reference_doctype = d.voucher_type; @@ -752,6 +723,7 @@ frappe.ui.form.on('Payment Entry', { c.bill_no = d.bill_no; c.payment_term = d.payment_term; c.allocated_amount = d.allocated_amount; + c.account = d.account; if(!in_list(frm.events.get_order_doctypes(frm), d.voucher_type)) { if(flt(d.outstanding_amount) > 0) @@ -1467,4 +1439,4 @@ frappe.ui.form.on('Payment Entry', { }); } }, -}) +}) \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 3927ecae43..1c330b841e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -733,7 +733,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-02-14 04:52:30.478523", + "modified": "2023-06-07 14:36:50.521884", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index ed2158a649..291f8e4411 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -86,12 +86,36 @@ class PaymentEntry(AccountsController): def on_submit(self): if self.difference_amount: frappe.throw(_("Difference Amount must be zero")) + book_advance_payments_as_liability = frappe.get_value( + "Company", {"company_name": self.company}, "book_advance_payments_as_liability" + ) + if book_advance_payments_as_liability: + self.get_liability_account() self.make_gl_entries() self.update_outstanding_amounts() self.update_advance_paid() self.update_payment_schedule() self.set_status() + def get_liability_account(self): + liability_account = get_party_account(self.party_type, self.party, self.company, is_advance=True) + if self.party_type == "Customer": + msg = "Book Advance Payments as Liability option is chosen. Paid From account changed from {0} to {1}.".format( + frappe.bold(self.paid_from), + frappe.bold(liability_account), + ) + frappe.db.set_value("Payment Entry", self.name, "paid_from", liability_account) + self.paid_from = liability_account + else: + msg = "Book Advance Payments as Liability option is chosen. Paid To account changed from {0} to {1}.".format( + frappe.bold(self.paid_to), + frappe.bold(liability_account), + ) + frappe.db.set_value("Payment Entry", self.name, "paid_to", liability_account) + self.paid_to = liability_account + frappe.msgprint(_(msg), title="Warning", indicator="orange") + return liability_account + def on_cancel(self): self.ignore_linked_doctypes = ( "GL Entry", @@ -869,10 +893,14 @@ class PaymentEntry(AccountsController): if self.party_account: if self.payment_type == "Receive": against_account = self.paid_to + self.party_account = self.paid_from + dr_or_cr = "credit" else: against_account = self.paid_from + self.party_account = self.paid_to + dr_or_cr = "debit" - party_gl_dict = self.get_gl_dict( + party_dict = self.get_gl_dict( { "account": self.party_account, "party_type": self.party_type, @@ -883,30 +911,24 @@ class PaymentEntry(AccountsController): }, item=self, ) - - dr_or_cr = ( - "credit" if erpnext.get_party_account_type(self.party_type) == "Receivable" else "debit" - ) - for d in self.get("references"): - cost_center = self.cost_center - if d.reference_doctype == "Sales Invoice" and not cost_center: - cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center") - gle = party_gl_dict.copy() - gle.update( - { - "against_voucher_type": d.reference_doctype, - "against_voucher": d.reference_name, - "cost_center": cost_center, - } + book_advance_payments_as_liability = frappe.get_value( + "Company", {"company_name": self.company}, "book_advance_payments_as_liability" ) + if ( + d.reference_doctype in ["Sales Invoice", "Purchase Invoice"] + and book_advance_payments_as_liability + ): + self.make_invoice_liability_entry(gl_entries, d) allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d) - + gle = party_dict.copy() gle.update( { - dr_or_cr + "_in_account_currency": d.allocated_amount, dr_or_cr: allocated_amount_in_company_currency, + dr_or_cr + "_in_account_currency": d.allocated_amount, + "against_voucher_type": d.reference_doctype, + "against_voucher": d.reference_name, } ) @@ -916,8 +938,7 @@ class PaymentEntry(AccountsController): exchange_rate = self.get_exchange_rate() base_unallocated_amount = self.unallocated_amount * exchange_rate - gle = party_gl_dict.copy() - + gle = party_dict.copy() gle.update( { dr_or_cr + "_in_account_currency": self.unallocated_amount, @@ -927,6 +948,40 @@ class PaymentEntry(AccountsController): gl_entries.append(gle) + def make_invoice_liability_entry(self, gl_entries, invoice): + args_dict = { + "party_type": self.party_type, + "party": self.party, + "account_currency": self.party_account_currency, + "cost_center": self.cost_center, + "voucher_type": invoice.reference_doctype, + "voucher_no": invoice.reference_name, + "against_voucher_type": invoice.reference_doctype, + "against_voucher": invoice.reference_name, + } + + dr_or_cr = "credit" if invoice.reference_doctype == "Sales Invoice" else "debit" + args_dict["account"] = invoice.account + args_dict[dr_or_cr] = invoice.allocated_amount + args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount + gle = self.get_gl_dict( + args_dict, + item=self, + ) + gl_entries.append(gle) + + args_dict[dr_or_cr] = 0 + args_dict[dr_or_cr + "_in_account_currency"] = 0 + dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" + args_dict["account"] = self.get_liability_account() + args_dict[dr_or_cr] = invoice.allocated_amount + args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount + gle = self.get_gl_dict( + args_dict, + item=self, + ) + gl_entries.append(gle) + def add_bank_gl_entries(self, gl_entries): if self.payment_type in ("Pay", "Internal Transfer"): gl_entries.append( @@ -1401,6 +1456,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): "outstanding_amount": flt(d.outstanding_amount), "payment_amount": payment_term.payment_amount, "payment_term": payment_term.payment_term, + "account": d.account, } ) ) @@ -1449,7 +1505,7 @@ def get_orders_to_be_billed( if voucher_type: doc = frappe.get_doc({"doctype": voucher_type}) condition = "" - if doc and hasattr(doc, "cost_center"): + if cost_center and doc and hasattr(doc, "cost_center"): condition = " and cost_center='%s'" % cost_center orders = [] @@ -1495,9 +1551,13 @@ def get_orders_to_be_billed( order_list = [] for d in orders: - if not ( - flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than")) - and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than")) + if filters.get("oustanding_amt_greater_than") and flt(d.outstanding_amount) < flt( + filters.get("outstanding_amt_greater_than") + ): + continue + + if filters.get("oustanding_amt_less_than") and flt(d.outstanding_amount) > flt( + filters.get("outstanding_amt_less_than") ): continue @@ -1519,6 +1579,7 @@ def get_negative_outstanding_invoices( condition=None, ): voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice" + account = "debit_to" if voucher_type == "Sales Invoice" else "credit_to" supplier_condition = "" if voucher_type == "Purchase Invoice": supplier_condition = "and (release_date is null or release_date <= CURRENT_DATE)" @@ -1532,7 +1593,7 @@ def get_negative_outstanding_invoices( return frappe.db.sql( """ select - "{voucher_type}" as voucher_type, name as voucher_no, + "{voucher_type}" as voucher_type, name as voucher_no, {account} as account, if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount, outstanding_amount, posting_date, due_date, conversion_rate as exchange_rate @@ -1555,6 +1616,7 @@ def get_negative_outstanding_invoices( "party_type": scrub(party_type), "party_account": "debit_to" if party_type == "Customer" else "credit_to", "cost_center": cost_center, + "account": account, } ), (party, party_account), @@ -1563,12 +1625,12 @@ def get_negative_outstanding_invoices( @frappe.whitelist() -def get_party_details(company, party_type, party, date, cost_center=None, is_advance=False): +def get_party_details(company, party_type, party, date, cost_center=None): bank_account = "" if not frappe.db.exists(party_type, party): frappe.throw(_("Invalid {0}: {1}").format(party_type, party)) - party_account = get_party_account(party_type, party, company, is_advance) + party_account = get_party_account(party_type, party, company) account_currency = get_account_currency(party_account) account_balance = get_balance_on(party_account, date, cost_center=cost_center) _party_name = "title" if party_type == "Shareholder" else party_type.lower() + "_name" diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index 3003c68196..c318ea53bd 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json @@ -15,7 +15,8 @@ "outstanding_amount", "allocated_amount", "exchange_rate", - "exchange_gain_loss" + "exchange_gain_loss", + "account" ], "fields": [ { @@ -101,12 +102,18 @@ "label": "Exchange Gain/Loss", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "fieldname": "account", + "fieldtype": "Link", + "label": "Account", + "options": "Account" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-12-12 12:31:44.919895", + "modified": "2023-06-07 14:35:06.166907", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 2e9628c9f0..d8743bb69e 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -29,22 +29,12 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo }; }); - this.frm.set_query('default_advances_received_account', () => { + this.frm.set_query('default_advance_account', () => { return { filters: { "company": this.frm.doc.company, "is_group": 0, - "root_type": "Liability" - } - }; - }); - - this.frm.set_query('default_advances_paid_account', () => { - return { - filters: { - "company": this.frm.doc.company, - "is_group": 0, - "root_type": "Asset" + "root_type": (this.frm.party_type == 'Customer') ? "Liability": "Asset" } }; }); @@ -169,23 +159,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo }, callback: (r) => { if (!r.exc && r.message) { - this.frm.set_value("default_advances_received_account", r.message); - } - this.frm.refresh(); - } - }); - - frappe.call({ - method: "erpnext.accounts.party.get_party_account", - args: { - company: this.frm.doc.company, - party_type: (this.frm.doc.party_type == 'Customer')?'Supplier':'Customer', - party: this.frm.doc.party, - is_advance: 1 - }, - callback: (r) => { - if (!r.exc && r.message) { - this.frm.set_value("default_advances_paid_account", r.message); + this.frm.set_value("default_advance_account", r.message); } this.frm.refresh(); } diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index 21b8392d60..0e166ffd5d 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -10,8 +10,7 @@ "party", "column_break_4", "receivable_payable_account", - "default_advances_received_account", - "default_advances_paid_account", + "default_advance_account", "col_break1", "from_invoice_date", "from_payment_date", @@ -190,27 +189,19 @@ }, { "depends_on": "eval:doc.party_type", - "fieldname": "default_advances_received_account", + "fieldname": "default_advance_account", "fieldtype": "Link", - "label": "Default Advances Received Account", + "label": "Default Advance Account", "mandatory_depends_on": "doc.party_type", "options": "Account", "reqd": 1 - }, - { - "depends_on": "eval:doc.party_type", - "fieldname": "default_advances_paid_account", - "fieldtype": "Link", - "label": "Default Advances Paid Account", - "mandatory_depends_on": "doc.party_type", - "options": "Account" } ], "hide_toolbar": 1, "icon": "icon-resize-horizontal", "issingle": 1, "links": [], - "modified": "2023-06-02 14:32:27.276083", + "modified": "2023-06-05 20:09:58.925427", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 9b03d36a8e..e7d7f2c1e4 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -13,14 +13,17 @@ import erpnext from erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation import ( is_any_doc_running, ) +from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.utils import ( QueryPaymentLedger, get_outstanding_invoices, reconcile_against_document, ) -from erpnext.controllers.accounts_controller import get_advance_payment_entries -from erpnext.controllers.accounts_controller import make_advance_liability_entry -from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.controllers.accounts_controller import ( + get_advance_payment_entries, + make_advance_liability_entry, +) + class PaymentReconciliation(Document): def __init__(self, *args, **kwargs): @@ -57,9 +60,17 @@ class PaymentReconciliation(Document): self.add_payment_entries(non_reconciled_payments) def get_payment_entries(self): - receivable_payable_account = self.receivable_payable_account - default_advances_account = self.default_advances_received_account - party_account = [receivable_payable_account, default_advances_account] + advance_accounts = [] + if self.party_type == "Customer": + advance_accounts = frappe.db.get_list( + "Account", filters={"root_type": "Liability", "company": self.company}, pluck="name" + ) + elif self.party_type == "Supplier": + advance_accounts = frappe.db.get_list( + "Account", filters={"root_type": "Asset", "company": self.company}, pluck="name" + ) + party_account = [self.receivable_payable_account] + advance_accounts + order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order" condition = frappe._dict( { @@ -69,12 +80,12 @@ class PaymentReconciliation(Document): "from_payment_date": self.get("from_payment_date"), "to_payment_date": self.get("to_payment_date"), "maximum_payment_amount": self.get("maximum_payment_amount"), - "minimum_payment_amount": self.get("minimum_payment_amount") + "minimum_payment_amount": self.get("minimum_payment_amount"), } ) payment_entries = get_advance_payment_entries( - self.party_type, + self.party_type, self.party, party_account, order_doctype, @@ -336,7 +347,9 @@ class PaymentReconciliation(Document): if row.invoice_number and row.allocated_amount: if row.invoice_type in ["Sales Invoice", "Purchase Invoice"]: gl_entries = [] - make_advance_liability_entry(gl_entries, row.reference_name, row.allocated_amount, row.invoice_number, self.party_type) + make_advance_liability_entry( + gl_entries, row.reference_name, row.allocated_amount, row.invoice_number, self.party_type + ) make_gl_entries(gl_entries) if row.reference_type in ["Sales Invoice", "Purchase Invoice"]: reconciled_entry = dr_or_cr_notes diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 60f9d62bf2..f0f1684b4d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1086,6 +1086,7 @@ "fieldtype": "Button", "label": "Get Advances Paid", "oldfieldtype": "Button", + "options": "set_advances", "print_hide": 1 }, { @@ -1364,12 +1365,12 @@ "depends_on": "eval:doc.update_stock && doc.is_internal_supplier", "fieldname": "set_from_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Set From Warehouse", "no_copy": 1, "options": "Warehouse", "print_hide": 1, "print_width": "50px", - "ignore_user_permissions": 1, "width": "50px" }, { @@ -1573,7 +1574,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2023-04-29 12:57:50.832598", + "modified": "2023-06-05 17:40:35.320635", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 6051c9915d..dbc277044a 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3353,14 +3353,16 @@ 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 + where voucher_type='Sales Invoice' and voucher_no=%s and posting_date >= %s and is_cancelled = 0 order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1, + debug=True, ) for i, gle in enumerate(gl_entries): + print(i, gle) doc.assertEqual(expected_gle[i][0], gle.account) doc.assertEqual(expected_gle[i][1], gle.debit) doc.assertEqual(expected_gle[i][2], gle.credit) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 3be4275888..782c41e529 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -380,9 +380,7 @@ def get_party_account(party_type, party=None, company=None, is_advance=False): return frappe.get_cached_value("Company", company, default_account_name) - advance_payments_as_liability = frappe.db.get_value("Company", {"company_name": company}, "book_advance_payments_as_liability") - - if is_advance and advance_payments_as_liability and party_type in ["Customer", "Supplier"]: + if is_advance and party_type in ["Customer", "Supplier"]: return get_party_advance_account(party_type, party, company) account = frappe.db.get_value( @@ -415,9 +413,10 @@ def get_party_account(party_type, party=None, company=None, is_advance=False): def get_party_advance_account(party_type, party, company): - account_name = 'advances_received_account' if party_type == 'Customer' else 'advances_paid_account' account = frappe.db.get_value( - "Party Account", {"parenttype": party_type, "parent": party, "company": company}, account_name + "Party Account", + {"parenttype": party_type, "parent": party, "company": company}, + "advance_account", ) if not account: @@ -426,14 +425,15 @@ def get_party_advance_account(party_type, party, company): account = frappe.db.get_value( "Party Account", {"parenttype": party_group_doctype, "parent": group, "company": company}, - account_name, + "advance_account", ) - + if not account: - account = frappe.get_cached_value("Company", company, "default_" + account_name) + account = frappe.get_cached_value("Company", company, "default_advance_account") return account - + + @frappe.whitelist() def get_party_bank_account(party_type, party): return frappe.db.get_value( diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 5abc64315c..506279c1e5 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -498,30 +498,36 @@ def check_if_advance_entry_modified(args): journal_entry = frappe.qb.DocType("Journal Entry") journal_acc = frappe.qb.DocType("Journal Entry Account") - q = (frappe.qb.from_(journal_entry) + q = ( + frappe.qb.from_(journal_entry) .innerjoin(journal_acc) .on(journal_entry.name == journal_acc.parent) ) - if args.get("dr_or_cr") == 'debit_in_account_currency': + if args.get("dr_or_cr") == "debit_in_account_currency": q = q.select(journal_acc.debit_in_account_currency) else: q = q.select(journal_acc.credit_in_account_currency) - - q = q.where((journal_acc.account == args.get("account")) - &((journal_acc.party_type == args.get("party_type"))) - &((journal_acc.party == args.get("party"))) - &((journal_acc.reference_type == None) | (journal_acc.reference_type.isin(['', 'Sales Order', 'Purchase Order']))) - &((journal_entry.name == args.get("voucher_no"))) - &((journal_acc.name == args.get("voucher_detail_no"))) - &((journal_entry.docstatus == 1)) + + q = q.where( + (journal_acc.account == args.get("account")) + & ((journal_acc.party_type == args.get("party_type"))) + & ((journal_acc.party == args.get("party"))) + & ( + (journal_acc.reference_type == None) + | (journal_acc.reference_type.isin(["", "Sales Order", "Purchase Order"])) + ) + & ((journal_entry.name == args.get("voucher_no"))) + & ((journal_acc.name == args.get("voucher_detail_no"))) + & ((journal_entry.docstatus == 1)) ) else: payment_entry = frappe.qb.DocType("Payment Entry") payment_ref = frappe.qb.DocType("Payment Entry Reference") - q = (frappe.qb.from_(payment_entry) + q = ( + frappe.qb.from_(payment_entry) .select(payment_entry.name) .where(payment_entry.name == args.get("voucher_no")) .where(payment_entry.docstatus == 1) @@ -530,15 +536,16 @@ def check_if_advance_entry_modified(args): ) if args.voucher_detail_no: - q = ( q.inner_join(payment_ref) + q = ( + q.inner_join(payment_ref) .on(payment_entry.name == payment_ref.parent) .where(payment_ref.name == args.get("voucher_detail_no")) - .where(payment_ref.reference_doctype.isin(('', 'Sales Order', 'Purchase Order'))) + .where(payment_ref.reference_doctype.isin(("", "Sales Order", "Purchase Order"))) .where(payment_ref.allocated_amount == args.get("unreconciled_amount")) - ) + ) else: q = q.where(payment_entry.unallocated_amount == args.get("unreconciled_amount")) - + ret = q.run(as_dict=True) if not ret: @@ -921,6 +928,7 @@ def get_outstanding_invoices( "outstanding_amount": outstanding_amount, "due_date": d.due_date, "currency": d.currency, + "account": d.account, } ) ) diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js index 9e217b5113..6da47c5c69 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -18,7 +18,7 @@ frappe.ui.form.on("Supplier", { } }); - frm.set_query('advances_received_account', 'accounts', function (doc, cdt, cdn) { + frm.set_query('advance_account', 'accounts', function (doc, cdt, cdn) { var d = locals[cdt][cdn]; return { filters: { @@ -29,17 +29,6 @@ frappe.ui.form.on("Supplier", { } }); - frm.set_query('advances_paid_account', 'accounts', function (doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - filters: { - "root_type": 'Liability', - "company": d.company, - "is_group": 0 - } - } - }); - frm.set_query("default_bank_account", function() { return { filters: { diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ac32fd352c..0589f4a89a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -7,9 +7,8 @@ import json import frappe from frappe import _, bold, throw from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied -from frappe.query_builder.functions import Abs, Sum from frappe.query_builder.custom import ConstantColumn - +from frappe.query_builder.functions import Abs, Sum from frappe.utils import ( add_days, add_months, @@ -859,7 +858,6 @@ class AccountsController(TransactionBase): amount = self.get("base_rounded_total") or self.base_grand_total else: amount = self.get("rounded_total") or self.grand_total - allocated_amount = min(amount - advance_allocated, d.amount) advance_allocated += flt(allocated_amount) @@ -887,16 +885,18 @@ class AccountsController(TransactionBase): amount_field = "credit_in_account_currency" order_field = "sales_order" order_doctype = "Sales Order" - party_account = frappe.db.get_values("Company", {"company_name": self.company}, ["default_receivable_account", "default_advances_received_account"]) + party_account = [ + get_party_account(party_type, party=party, company=self.company, is_advance=True) + ] else: party_type = "Supplier" party = self.supplier amount_field = "debit_in_account_currency" order_field = "purchase_order" order_doctype = "Purchase Order" - party_account = frappe.db.get_values("Company", {"company_name": self.company}, ["default_receivable_account", "default_advances_paid_account"]) - - party_account = list(party_account[0]) + party_account = [ + get_party_account(party_type, party=party, company=self.company, is_advance=True) + ] order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field))) @@ -2144,35 +2144,43 @@ def get_advance_journal_entries( order_list, include_unallocated=True, ): - journal_entry = frappe.qb.DocType('Journal Entry') - journal_acc = frappe.qb.DocType('Journal Entry Account') - q = (frappe.qb.from_(journal_entry) + journal_entry = frappe.qb.DocType("Journal Entry") + journal_acc = frappe.qb.DocType("Journal Entry Account") + q = ( + frappe.qb.from_(journal_entry) .inner_join(journal_acc) .on(journal_entry.name == journal_acc.parent) .select( - ConstantColumn('Journal Entry').as_('reference_type'), - (journal_entry.name).as_('reference_name'), - (journal_entry.remark).as_('remarks'), - (journal_acc.debit_in_account_currency if party_type == 'Supplier' else journal_acc.credit_in_account_currency).as_('amount'), - (journal_acc.name).as_('reference_row'), - (journal_acc.reference_name).as_('against_order'), - (journal_acc.exchange_rate) + ConstantColumn("Journal Entry").as_("reference_type"), + (journal_entry.name).as_("reference_name"), + (journal_entry.remark).as_("remarks"), + ( + journal_acc.debit_in_account_currency + if party_type == "Supplier" + else journal_acc.credit_in_account_currency + ).as_("amount"), + (journal_acc.name).as_("reference_row"), + (journal_acc.reference_name).as_("against_order"), + (journal_acc.exchange_rate), ) - .where(journal_acc.account.isin(party_account) - & (journal_acc.party_type == party_type) - & (journal_acc.party == party) - & (journal_acc.is_advance == 'Yes') - & (journal_entry.docstatus == 1) - ) - ) + .where( + journal_acc.account.isin(party_account) + & (journal_acc.party_type == party_type) + & (journal_acc.party == party) + & (journal_acc.is_advance == "Yes") + & (journal_entry.docstatus == 1) + ) + ) if party_type == "Customer": q = q.where(journal_acc.credit_in_account_currency > 0) + else: + q = q.where(journal_acc.debit_in_account_currency > 0) + if order_list: q = q.where(journal_acc.reference_type == order_doctype) if include_unallocated: - q = q.where(journal_acc.reference_name.isin(order_list) - |(journal_acc.reference_name == '')) + q = q.where(journal_acc.reference_name.isin(order_list) | (journal_acc.reference_name == "")) else: q = q.where(journal_acc.reference_name.isin(order_list)) @@ -2194,69 +2202,119 @@ def get_advance_payment_entries( condition=None, ): - q = build_query(party_type, party, party_account, order_doctype, order_list, include_unallocated, against_all_orders, limit, condition) + q = build_query( + party_type, + party, + party_account, + order_doctype, + order_list, + include_unallocated, + against_all_orders, + limit, + condition, + ) payment_entries = q.run(as_dict=True) return list(payment_entries) -def build_query(party_type, party, party_account, order_doctype, order_list, include_unallocated, against_all_orders, limit, condition): - payment_type = "Receive" if party_type == "Customer" else "Pay" - payment_entry = frappe.qb.DocType('Payment Entry') - payment_ref = frappe.qb.DocType('Payment Entry Reference') - q = (frappe.qb.from_(payment_entry) +def build_query( + party_type, + party, + party_account, + order_doctype, + order_list, + include_unallocated, + against_all_orders, + limit, + condition, +): + payment_type = "Receive" if party_type == "Customer" else "Pay" + payment_entry = frappe.qb.DocType("Payment Entry") + payment_ref = frappe.qb.DocType("Payment Entry Reference") + + q = ( + frappe.qb.from_(payment_entry) .select( - ConstantColumn('Payment Entry').as_('reference_type'), - (payment_entry.name).as_('reference_name'), + ConstantColumn("Payment Entry").as_("reference_type"), + (payment_entry.name).as_("reference_name"), payment_entry.posting_date, - (payment_entry.remarks).as_('remarks') + (payment_entry.remarks).as_("remarks"), ) .where(payment_entry.payment_type == payment_type) .where(payment_entry.party_type == party_type) .where(payment_entry.party == party) .where(payment_entry.docstatus == 1) ) - + if party_type == "Customer": - q = q.select(payment_entry.paid_from_account_currency) - q = q.select(payment_entry.paid_from) - q = q.where(payment_entry.paid_from.isin(party_account)) + q = q.select(payment_entry.paid_from_account_currency) + q = q.select(payment_entry.paid_from) + q = q.where(payment_entry.paid_from.isin(party_account)) else: q = q.select(payment_entry.paid_to_account_currency) q = q.select(payment_entry.paid_to) - q = q.where(payment_entry.paid_to.isin(party_account)) + q = q.where(payment_entry.paid_to.isin(party_account)) if payment_type == "Receive": q = q.select(payment_entry.source_exchange_rate) - else: + else: q.select(payment_entry.target_exchange_rate) if include_unallocated: - q = q.select((payment_entry.unallocated_amount).as_('amount')) - q = q.where(payment_entry.unallocated_amount>0) + q = q.select((payment_entry.unallocated_amount).as_("amount")) + q = q.where(payment_entry.unallocated_amount > 0) if condition: q = q.where(payment_entry.company == condition["company"]) - q = q.where(payment_entry.posting_date >= condition["from_payment_date"]) if condition.get("from_payment_date") else q - q = q.where(payment_entry.posting_date <= condition["to_payment_date"]) if condition.get("to_payment_date") else q + q = ( + q.where(payment_entry.posting_date >= condition["from_payment_date"]) + if condition.get("from_payment_date") + else q + ) + q = ( + q.where(payment_entry.posting_date <= condition["to_payment_date"]) + if condition.get("to_payment_date") + else q + ) if condition.get("get_payments") == True: - q = q.where(payment_entry.cost_center == condition["cost_center"]) if condition.get("cost_center") else q - q = q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"]) if condition.get("minimum_payment_amount") else q - q = q.where(payment_entry.unallocated_amount <= condition["maximum_payment_amount"]) if condition.get("maximum_payment_amount") else q + q = ( + q.where(payment_entry.cost_center == condition["cost_center"]) + if condition.get("cost_center") + else q + ) + q = ( + q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"]) + if condition.get("minimum_payment_amount") + else q + ) + q = ( + q.where(payment_entry.unallocated_amount <= condition["maximum_payment_amount"]) + if condition.get("maximum_payment_amount") + else q + ) else: - q = q.where(payment_entry.total_debit >= condition["minimum_payment_amount"]) if condition.get("minimum_payment_amount") else q - q = q.where(payment_entry.total_debit <= condition["maximum_payment_amount"]) if condition.get("maximum_payment_amount") else q + q = ( + q.where(payment_entry.total_debit >= condition["minimum_payment_amount"]) + if condition.get("minimum_payment_amount") + else q + ) + q = ( + q.where(payment_entry.total_debit <= condition["maximum_payment_amount"]) + if condition.get("maximum_payment_amount") + else q + ) elif order_list or against_all_orders: q = q.inner_join(payment_ref).on(payment_entry.name == payment_ref.parent) q = q.select( - (payment_ref.allocated_amount).as_('amount'), - (payment_ref.name).as_('reference_row'), - (payment_ref.reference_name).as_('against_order'), - payment_ref.reference_doctype == order_doctype + (payment_ref.allocated_amount).as_("amount"), + (payment_ref.name).as_("reference_row"), + (payment_ref.reference_name).as_("against_order"), + payment_ref.reference_doctype == order_doctype, ) - + if order_list: q = q.where(payment_ref.reference_name.isin(order_list)) @@ -2862,9 +2920,12 @@ def validate_regional(doc): def validate_einvoice_fields(doc): pass -def make_advance_liability_entry(gl_entries, pe, allocated_amount, invoice, party_type): + +def make_advance_liability_entry( + gl_entries, pe, allocated_amount, invoice, party_type, references=False +): pe = frappe.get_doc("Payment Entry", pe) - if party_type=="Customer": + if party_type == "Customer": invoice = frappe.get_doc("Sales Invoice", invoice) account = pe.paid_from dr_or_cr = "debit" @@ -2880,53 +2941,55 @@ def make_advance_liability_entry(gl_entries, pe, allocated_amount, invoice, part against = invoice.credit_to party = invoice.supplier voucher_type = "Purchase Invoice" - gl_entries.append(invoice.get_gl_dict( - { - "account": account, - "party_type": party_type, - "party": party, - "due_date": invoice.due_date, - "against": against, - dr_or_cr: allocated_amount, - dr_or_cr + "_in_account_currency": allocated_amount, - rev: 0, - rev + "_in_account_currency": 0, - "against_voucher": invoice.return_against - if cint(invoice.is_return) and invoice.return_against - else invoice.name, - "against_voucher_type": invoice.doctype, - "cost_center": invoice.cost_center, - "project": invoice.project, - "voucher_type": voucher_type, - "voucher_no": invoice.name - }, - invoice.party_account_currency, - item=invoice, - )) - - (dr_or_cr, rev) = ("credit", "debit") if party_type=="Customer" else ("debit", "credit") - gl_entries.append(invoice.get_gl_dict( - { - "account": against, - "party_type": party_type, - "party": party, - "due_date": invoice.due_date, - "against": account, - dr_or_cr: allocated_amount, - dr_or_cr + "_in_account_currency": allocated_amount, - rev: 0, - rev + "_in_account_currency": 0, - "against_voucher": invoice.return_against - if cint(invoice.is_return) and invoice.return_against - else invoice.name, - "against_voucher_type": invoice.doctype, - "cost_center": invoice.cost_center, - "project": invoice.project, - "voucher_type": voucher_type, - "voucher_no": invoice.name - }, - invoice.party_account_currency, - item=invoice, - )) - + gl_entries.append( + invoice.get_gl_dict( + { + "account": account, + "party_type": party_type, + "party": party, + "due_date": invoice.due_date, + "against": against, + dr_or_cr: allocated_amount, + dr_or_cr + "_in_account_currency": allocated_amount, + rev: 0, + rev + "_in_account_currency": 0, + "against_voucher": invoice.return_against + if cint(invoice.is_return) and invoice.return_against + else invoice.name, + "against_voucher_type": invoice.doctype, + "cost_center": invoice.cost_center, + "project": invoice.project, + "voucher_type": voucher_type, + "voucher_no": invoice.name, + }, + invoice.party_account_currency, + item=invoice, + ) + ) + (dr_or_cr, rev) = ("credit", "debit") if party_type == "Customer" else ("debit", "credit") + gl_entries.append( + invoice.get_gl_dict( + { + "account": against, + "party_type": party_type, + "party": party, + "due_date": invoice.due_date, + "against": account, + dr_or_cr: allocated_amount, + dr_or_cr + "_in_account_currency": allocated_amount, + rev: 0, + rev + "_in_account_currency": 0, + "against_voucher": invoice.return_against + if cint(invoice.is_return) and invoice.return_against + else invoice.name, + "against_voucher_type": invoice.doctype, + "cost_center": invoice.cost_center, + "project": invoice.project, + "voucher_type": "Payment Entry" if references else voucher_type, + "voucher_no": pe.name if references else invoice.name, + }, + invoice.party_account_currency, + item=invoice, + ) + ) diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 6dac692747..35cf8c53ee 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -34,8 +34,8 @@ frappe.ui.form.on("Customer", { filters: filters } }); - - frm.set_query('advances_received_account', 'accounts', function (doc, cdt, cdn) { + + frm.set_query('advance_account', 'accounts', function (doc, cdt, cdn) { var d = locals[cdt][cdn]; return { filters: { @@ -46,16 +46,6 @@ frappe.ui.form.on("Customer", { } }); - frm.set_query('advances_paid_account', 'accounts', function (doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - filters: { - "root_type": 'Asset', - "company": d.company, - "is_group": 0 - } - } - }); if (frm.doc.__islocal == 1) { frm.set_value("represents_company", ""); diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 0050279a4b..46b10351fd 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -337,12 +337,12 @@ "label": "Default Accounts" }, { - "description": "Mention if a non-standard receivable account", - "fieldname": "accounts", - "fieldtype": "Table", - "label": "Accounts", - "options": "Party Account" - }, + "description": "Mention if non-standard Receivable account", + "fieldname": "accounts", + "fieldtype": "Table", + "label": "Accounts", + "options": "Party Account" + }, { "fieldname": "credit_limit_section", "fieldtype": "Section Break", @@ -568,7 +568,7 @@ "link_fieldname": "party" } ], - "modified": "2023-05-29 14:29:17.789578", + "modified": "2023-06-05 13:48:46.152659", "modified_by": "Administrator", "module": "Selling", "name": "Customer", diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 81919af4e1..fb0ee7f07a 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -227,8 +227,7 @@ erpnext.company.setup_queries = function(frm) { ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}], ["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}], ["default_provisional_account", {"root_type": ["in", ["Liability", "Asset"]]}], - ["default_advances_received_account", {"root_type": "Liability"}], - ["default_advances_paid_account", {"root_type": "Asset"}], + ["default_advance_account", {"root_type": ["in", ["Liability", "Asset"]]}], ], function(i, v) { erpnext.company.set_custom_query(frm, v); }); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 369e139eef..5b3d3bb086 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -72,9 +72,8 @@ "default_finance_book", "advance_payments_section", "book_advance_payments_as_liability", - "default_advances_received_account", + "default_advance_account", "column_break_cui0", - "default_advances_paid_account", "auto_accounting_for_stock_settings", "enable_perpetual_inventory", "enable_provisional_accounting_for_non_stock_items", @@ -711,25 +710,17 @@ "fieldtype": "Section Break", "label": "Advance Payments" }, - { - "depends_on": "eval:doc.book_advance_payments_as_liability", - "fieldname": "default_advances_received_account", - "fieldtype": "Link", - "label": "Default Advances Received Account", - "mandatory_depends_on": "book_advance_payments_as_liability", - "options": "Account" - }, - { - "depends_on": "eval:doc.book_advance_payments_as_liability", - "fieldname": "default_advances_paid_account", - "fieldtype": "Link", - "label": "Default Advances Paid Account", - "mandatory_depends_on": "book_advance_payments_as_liability", - "options": "Account" - }, { "fieldname": "column_break_cui0", "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.book_advance_payments_as_liability", + "fieldname": "default_advance_account", + "fieldtype": "Link", + "label": "Default Account", + "mandatory_depends_on": "book_advance_payments_as_liability", + "options": "Account" } ], "icon": "fa fa-building", @@ -737,7 +728,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2023-06-02 13:11:41.939016", + "modified": "2023-06-05 14:12:37.946451", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/setup/doctype/customer_group/customer_group.js b/erpnext/setup/doctype/customer_group/customer_group.js index ef556c774d..3a71b43ee8 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.js +++ b/erpnext/setup/doctype/customer_group/customer_group.js @@ -37,7 +37,7 @@ cur_frm.fields_dict['accounts'].grid.get_field('account').get_query = function(d } } -cur_frm.fields_dict['accounts'].grid.get_field('advances_received_account').get_query = function(doc, cdt, cdn) { +cur_frm.fields_dict['accounts'].grid.get_field('advance_account').get_query = function(doc, cdt, cdn) { var d = locals[cdt][cdn]; return { filters: { @@ -46,15 +46,4 @@ cur_frm.fields_dict['accounts'].grid.get_field('advances_received_account').get_ "is_group": 0 } } -} - -cur_frm.fields_dict['accounts'].grid.get_field('advances_paid_account').get_query = function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - filters: { - "root_type": 'Asset', - "company": d.company, - "is_group": 0 - } - } } \ No newline at end of file diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.js b/erpnext/setup/doctype/supplier_group/supplier_group.js index e75030d441..58ab7fa928 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.js +++ b/erpnext/setup/doctype/supplier_group/supplier_group.js @@ -36,3 +36,14 @@ cur_frm.fields_dict['accounts'].grid.get_field('account').get_query = function(d } }; }; + +cur_frm.fields_dict['accounts'].grid.get_field('advance_account').get_query = function(doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + filters: { + "root_type": 'Asset', + "company": d.company, + "is_group": 0 + } + } +}; \ No newline at end of file From b65e58c1ae215215a73ad92e24791e9c6090cfa5 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 8 Jun 2023 18:15:37 +0530 Subject: [PATCH 03/40] test: add tests for advance liability entries Add Sales and Purchase Invoice Tests to check if GL entries and Outstanding Amount are generated correctly when advance entries are recorded as liability. Few changes to return value of added column in Payment Entry References. --- .../doctype/payment_entry/payment_entry.py | 8 ++- .../payment_entry_reference.json | 2 +- .../purchase_invoice/test_purchase_invoice.py | 60 +++++++++++++++--- .../sales_invoice/test_sales_invoice.py | 63 +++++++++++++++---- erpnext/accounts/utils.py | 1 + 5 files changed, 113 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 291f8e4411..236a78a370 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1741,6 +1741,9 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre if reference_doctype in ("Sales Invoice", "Purchase Invoice"): outstanding_amount = ref_doc.get("outstanding_amount") + account = ( + ref_doc.get("debit_to") if reference_doctype == "Sales Invoice" else ref_doc.get("credit_to") + ) else: outstanding_amount = flt(total_amount) - flt(ref_doc.get("advance_paid")) @@ -1748,7 +1751,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre # Get the exchange rate based on the posting date of the ref doc. exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date) - return frappe._dict( + res = frappe._dict( { "due_date": ref_doc.get("due_date"), "total_amount": flt(total_amount), @@ -1757,6 +1760,9 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre "bill_no": ref_doc.get("bill_no"), } ) + if account: + res.update({"account": account}) + return res @frappe.whitelist() diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index c318ea53bd..12aa0b520e 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json @@ -113,7 +113,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-06-07 14:35:06.166907", + "modified": "2023-06-08 07:40:38.487874", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index a6d7df6971..ebb4970b3d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1662,22 +1662,66 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): self.assertTrue(return_pi.docstatus == 1) + def test_advance_entries_as_liability(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry + from erpnext.accounts.party import get_party_account + + frappe.db.set_value( + "Company", + "_Test Company", + {"book_advance_payments_as_liability": 1, "default_advance_account": "Debtors - _TC"}, + ) + pe = create_payment_entry( + company="_Test Company", + payment_type="Pay", + party_type="Supplier", + party="_Test Supplier", + paid_from="Cash - _TC", + paid_to=get_party_account("Supplier", "_Test Supplier", "_Test Company", is_advance=True), + paid_amount=1000, + ) + pe.submit() + + pi = make_purchase_invoice( + company="_Test Company", customer="_Test Supplier", do_not_save=True, do_not_submit=True + ) + pi.base_grand_total = 100 + pi.grand_total = 100 + pi.set_advances() + self.assertEqual(pi.advances[0].allocated_amount, 100) + pi.advances[0].allocated_amount = 50 + pi.advances = [pi.advances[0]] + pi.save() + pi.submit() + expected_gle = [ + ["Creditors - _TC", 50, 100], + ["Debtors - _TC", 0.0, 50], + ["Stock Received But Not Billed - _TC", 100, 0.0], + ] + + check_gl_entries(self, pi.name, expected_gle, nowdate()) + self.assertEqual(pi.outstanding_amount, 200) + 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='Purchase Invoice' and voucher_no=%s and posting_date >= %s - order by posting_date asc, account asc""", - (voucher_no, posting_date), - as_dict=1, + gl = frappe.qb.DocType("GL Entry") + q = ( + frappe.qb.from_(gl) + .select(gl.account, gl.debit, gl.credit, gl.posting_date) + .where( + (gl.voucher_type == "Sales Invoice") + & (gl.voucher_no == voucher_no) + & (gl.posting_date >= posting_date) + & (gl.is_cancelled == 0) + ) + .orderby(gl.posting_date, gl.account) ) + gl_entries = q.run(as_dict=True) 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 create_tax_witholding_category(category_name, company, account): diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index dbc277044a..d10fa05a19 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3313,6 +3313,46 @@ class TestSalesInvoice(unittest.TestCase): ) self.assertRaises(frappe.ValidationError, si.submit) + def test_advance_entries_as_liability(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry + from erpnext.accounts.party import get_party_account + + frappe.db.set_value( + "Company", + "_Test Company", + {"book_advance_payments_as_liability": 1, "default_advance_account": "Creditors - _TC"}, + ) + pe = create_payment_entry( + company="_Test Company", + payment_type="Receive", + party_type="Customer", + party="_Test Customer", + paid_from=get_party_account("Customer", "_Test Customer", "_Test Company", is_advance=True), + paid_to="Cash - _TC", + paid_amount=1000, + ) + pe.submit() + + si = create_sales_invoice( + company="_Test Company", customer="_Test Customer", do_not_save=True, do_not_submit=True + ) + si.base_grand_total = 100 + si.grand_total = 100 + si.set_advances() + self.assertEqual(si.advances[0].allocated_amount, 100) + si.advances[0].allocated_amount = 50 + si.advances = [si.advances[0]] + si.save() + si.submit() + expected_gle = [ + ["Creditors - _TC", 50, 0.0], + ["Debtors - _TC", 100, 50], + ["Sales - _TC", 0.0, 100], + ] + + check_gl_entries(self, si.name, expected_gle, nowdate()) + self.assertEqual(si.outstanding_amount, 50) + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() @@ -3350,23 +3390,24 @@ def get_sales_invoice_for_e_invoice(): 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 - and is_cancelled = 0 - order by posting_date asc, account asc""", - (voucher_no, posting_date), - as_dict=1, - debug=True, + gl = frappe.qb.DocType("GL Entry") + q = ( + frappe.qb.from_(gl) + .select(gl.account, gl.debit, gl.credit, gl.posting_date) + .where( + (gl.voucher_type == "Sales Invoice") + & (gl.voucher_no == voucher_no) + & (gl.posting_date >= posting_date) + & (gl.is_cancelled == 0) + ) + .orderby(gl.posting_date, gl.account) ) + gl_entries = q.run(as_dict=True) for i, gle in enumerate(gl_entries): - print(i, gle) 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 create_sales_invoice(**args): diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 506279c1e5..c1d365304f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -625,6 +625,7 @@ def update_reference_in_payment_entry( if not d.exchange_gain_loss else payment_entry.get_exchange_rate(), "exchange_gain_loss": d.exchange_gain_loss, # only populated from invoice in case of advance allocation + "account": d.account, } if d.voucher_detail_no: From 7591f1010b761adcb647f92988b3886bc9c6c40c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 12 Jun 2023 11:06:03 +0530 Subject: [PATCH 04/40] fix: Make get party account method return a list instead of a single default account. --- .../doctype/party_account/party_account.json | 2 +- .../doctype/payment_entry/payment_entry.js | 1 - .../doctype/payment_entry/payment_entry.py | 61 ++++++++----------- .../payment_reconciliation.js | 20 +----- .../payment_reconciliation.json | 9 ++- .../purchase_invoice/test_purchase_invoice.py | 2 +- .../sales_invoice/test_sales_invoice.py | 2 +- erpnext/accounts/party.py | 8 +-- erpnext/buying/doctype/supplier/supplier.js | 4 +- erpnext/controllers/accounts_controller.py | 9 +-- erpnext/selling/doctype/customer/customer.js | 6 +- .../doctype/customer_group/customer_group.js | 57 ++++++++--------- .../doctype/supplier_group/supplier_group.js | 57 ++++++++--------- 13 files changed, 106 insertions(+), 132 deletions(-) diff --git a/erpnext/accounts/doctype/party_account/party_account.json b/erpnext/accounts/doctype/party_account/party_account.json index 6ac6e56086..7e345d84ea 100644 --- a/erpnext/accounts/doctype/party_account/party_account.json +++ b/erpnext/accounts/doctype/party_account/party_account.json @@ -36,7 +36,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-06-05 14:15:42.053150", + "modified": "2023-06-06 14:15:42.053150", "modified_by": "Administrator", "module": "Accounts", "name": "Party Account", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 1f0e45fc11..4cebb7b2fc 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -712,7 +712,6 @@ frappe.ui.form.on('Payment Entry', { if(r.message) { var total_positive_outstanding = 0; var total_negative_outstanding = 0; - console.log(r.message); $.each(r.message, function(i, d) { var c = frm.add_child("references"); c.reference_doctype = d.voucher_type; diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 236a78a370..5249f171fb 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -60,6 +60,7 @@ class PaymentEntry(AccountsController): def validate(self): self.setup_party_account_field() self.set_missing_values() + self.set_liability_account() self.set_missing_ref_details() self.validate_payment_type() self.validate_party_details() @@ -86,35 +87,34 @@ class PaymentEntry(AccountsController): def on_submit(self): if self.difference_amount: frappe.throw(_("Difference Amount must be zero")) - book_advance_payments_as_liability = frappe.get_value( - "Company", {"company_name": self.company}, "book_advance_payments_as_liability" - ) - if book_advance_payments_as_liability: - self.get_liability_account() self.make_gl_entries() self.update_outstanding_amounts() self.update_advance_paid() self.update_payment_schedule() self.set_status() - def get_liability_account(self): - liability_account = get_party_account(self.party_type, self.party, self.company, is_advance=True) - if self.party_type == "Customer": - msg = "Book Advance Payments as Liability option is chosen. Paid From account changed from {0} to {1}.".format( - frappe.bold(self.paid_from), - frappe.bold(liability_account), - ) - frappe.db.set_value("Payment Entry", self.name, "paid_from", liability_account) - self.paid_from = liability_account - else: - msg = "Book Advance Payments as Liability option is chosen. Paid To account changed from {0} to {1}.".format( - frappe.bold(self.paid_to), - frappe.bold(liability_account), - ) - frappe.db.set_value("Payment Entry", self.name, "paid_to", liability_account) - self.paid_to = liability_account - frappe.msgprint(_(msg), title="Warning", indicator="orange") - return liability_account + def set_liability_account(self): + book_advance_payments_as_liability = frappe.get_value( + "Company", {"company_name": self.company}, "book_advance_payments_as_liability" + ) + if not book_advance_payments_as_liability: + return + root_type = frappe.get_value( + "Account", {"name": self.party_account, "company": self.company}, "root_type" + ) + if (root_type == "Liability" and self.party_type == "Customer") or ( + root_type == "Asset" and self.party_type == "Supplier" + ): + return + liability_account = get_party_account( + self.party_type, self.party, self.company, include_advance=True + )[1] + self.set(self.party_account_field, liability_account) + msg = "Book Advance Payments as Liability option is chosen. Paid From account changed from {0} to {1}.".format( + frappe.bold(self.party_account), + frappe.bold(liability_account), + ) + frappe.msgprint(_(msg), alert=True) def on_cancel(self): self.ignore_linked_doctypes = ( @@ -354,13 +354,6 @@ class PaymentEntry(AccountsController): elif self.party_type == "Employee": ref_party_account = ref_doc.payable_account - if ref_party_account != self.party_account: - frappe.throw( - _("{0} {1} is associated with {2}, but Party Account is {3}").format( - d.reference_doctype, d.reference_name, ref_party_account, self.party_account - ) - ) - if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"): frappe.throw( _("{0} {1} is on hold").format(d.reference_doctype, d.reference_name), @@ -893,11 +886,9 @@ class PaymentEntry(AccountsController): if self.party_account: if self.payment_type == "Receive": against_account = self.paid_to - self.party_account = self.paid_from dr_or_cr = "credit" else: against_account = self.paid_from - self.party_account = self.paid_to dr_or_cr = "debit" party_dict = self.get_gl_dict( @@ -927,8 +918,8 @@ class PaymentEntry(AccountsController): { dr_or_cr: allocated_amount_in_company_currency, dr_or_cr + "_in_account_currency": d.allocated_amount, - "against_voucher_type": d.reference_doctype, - "against_voucher": d.reference_name, + "against_voucher_type": "Payment Entry", + "against_voucher": self.name, } ) @@ -973,7 +964,7 @@ class PaymentEntry(AccountsController): args_dict[dr_or_cr] = 0 args_dict[dr_or_cr + "_in_account_currency"] = 0 dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" - args_dict["account"] = self.get_liability_account() + args_dict["account"] = self.party_account args_dict[dr_or_cr] = invoice.allocated_amount args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount gle = self.get_gl_dict( diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index d8743bb69e..a40e6f274f 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -134,32 +134,18 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo this.frm.trigger("clear_child_tables"); if (!this.frm.doc.receivable_payable_account && this.frm.doc.party_type && this.frm.doc.party) { - frappe.call({ - method: "erpnext.accounts.party.get_party_account", - args: { - company: this.frm.doc.company, - party_type: this.frm.doc.party_type, - party: this.frm.doc.party - }, - callback: (r) => { - if (!r.exc && r.message) { - this.frm.set_value("receivable_payable_account", r.message); - } - this.frm.refresh(); - } - }); - frappe.call({ method: "erpnext.accounts.party.get_party_account", args: { company: this.frm.doc.company, party_type: this.frm.doc.party_type, party: this.frm.doc.party, - is_advance: 1 + include_advance: 1 }, callback: (r) => { if (!r.exc && r.message) { - this.frm.set_value("default_advance_account", r.message); + this.frm.set_value("receivable_payable_account", r.message[0]); + this.frm.set_value("default_advance_account", r.message[1]); } this.frm.refresh(); } diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index 0e166ffd5d..5f6c7034ed 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -7,8 +7,8 @@ "field_order": [ "company", "party_type", - "party", "column_break_4", + "party", "receivable_payable_account", "default_advance_account", "col_break1", @@ -188,20 +188,19 @@ "options": "Cost Center" }, { - "depends_on": "eval:doc.party_type", + "depends_on": "eval:doc.party", "fieldname": "default_advance_account", "fieldtype": "Link", "label": "Default Advance Account", "mandatory_depends_on": "doc.party_type", - "options": "Account", - "reqd": 1 + "options": "Account" } ], "hide_toolbar": 1, "icon": "icon-resize-horizontal", "issingle": 1, "links": [], - "modified": "2023-06-05 20:09:58.925427", + "modified": "2023-06-09 13:02:48.718362", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index ebb4970b3d..9ade46e045 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1677,7 +1677,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): party_type="Supplier", party="_Test Supplier", paid_from="Cash - _TC", - paid_to=get_party_account("Supplier", "_Test Supplier", "_Test Company", is_advance=True), + paid_to="Creditors - _TC", paid_amount=1000, ) pe.submit() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index d10fa05a19..364bf6898a 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3327,7 +3327,7 @@ class TestSalesInvoice(unittest.TestCase): payment_type="Receive", party_type="Customer", party="_Test Customer", - paid_from=get_party_account("Customer", "_Test Customer", "_Test Company", is_advance=True), + paid_from="Debtors - _TC", paid_to="Cash - _TC", paid_amount=1000, ) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 782c41e529..ccd54b344f 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -365,7 +365,7 @@ def set_account_and_due_date( @frappe.whitelist() -def get_party_account(party_type, party=None, company=None, is_advance=False): +def get_party_account(party_type, party=None, company=None, include_advance=False): """Returns the account for the given `party`. Will first search in party (Customer / Supplier) record, if not found, will search in group (Customer Group / Supplier Group), @@ -380,9 +380,6 @@ def get_party_account(party_type, party=None, company=None, is_advance=False): return frappe.get_cached_value("Company", company, default_account_name) - if is_advance and party_type in ["Customer", "Supplier"]: - return get_party_advance_account(party_type, party, company) - account = frappe.db.get_value( "Party Account", {"parenttype": party_type, "parent": party, "company": company}, "account" ) @@ -409,6 +406,9 @@ def get_party_account(party_type, party=None, company=None, is_advance=False): if (account and account_currency != existing_gle_currency) or not account: account = get_party_gle_account(party_type, party, company) + if include_advance and party_type in ["Customer", "Supplier"]: + advance_account = get_party_advance_account(party_type, party, company) + return [account, advance_account] return account diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js index 6da47c5c69..50f9bb6cb9 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -8,7 +8,7 @@ frappe.ui.form.on("Supplier", { frm.set_value("represents_company", ""); } frm.set_query('account', 'accounts', function (doc, cdt, cdn) { - var d = locals[cdt][cdn]; + let d = locals[cdt][cdn]; return { filters: { 'account_type': 'Payable', @@ -19,7 +19,7 @@ frappe.ui.form.on("Supplier", { }); frm.set_query('advance_account', 'accounts', function (doc, cdt, cdn) { - var d = locals[cdt][cdn]; + let d = locals[cdt][cdn]; return { filters: { "root_type": 'Asset', diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 0589f4a89a..d8c4135292 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -885,18 +885,15 @@ class AccountsController(TransactionBase): amount_field = "credit_in_account_currency" order_field = "sales_order" order_doctype = "Sales Order" - party_account = [ - get_party_account(party_type, party=party, company=self.company, is_advance=True) - ] else: party_type = "Supplier" party = self.supplier amount_field = "debit_in_account_currency" order_field = "purchase_order" order_doctype = "Purchase Order" - party_account = [ - get_party_account(party_type, party=party, company=self.company, is_advance=True) - ] + party_account = [ + get_party_account(party_type, party=party, company=self.company, include_advance=True)[1] + ] order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field))) diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 35cf8c53ee..8f828807a5 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -20,8 +20,8 @@ frappe.ui.form.on("Customer", { frm.set_query('customer_group', {'is_group': 0}); frm.set_query('default_price_list', { 'selling': 1}); frm.set_query('account', 'accounts', function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - var filters = { + let d = locals[cdt][cdn]; + let filters = { 'account_type': 'Receivable', 'company': d.company, "is_group": 0 @@ -36,7 +36,7 @@ frappe.ui.form.on("Customer", { }); frm.set_query('advance_account', 'accounts', function (doc, cdt, cdn) { - var d = locals[cdt][cdn]; + let d = locals[cdt][cdn]; return { filters: { "root_type": 'Liability', diff --git a/erpnext/setup/doctype/customer_group/customer_group.js b/erpnext/setup/doctype/customer_group/customer_group.js index 3a71b43ee8..ed9893346c 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.js +++ b/erpnext/setup/doctype/customer_group/customer_group.js @@ -16,34 +16,35 @@ cur_frm.cscript.set_root_readonly = function(doc) { } } -//get query select Customer Group -cur_frm.fields_dict['parent_customer_group'].get_query = function(doc,cdt,cdn) { - return { - filters: { - 'is_group': 1, - 'name': ['!=', cur_frm.doc.customer_group_name] - } - } -} +frappe.ui.form.on("Customer Group", { + setup: function(frm){ + frm.set_query('parent_customer_group', function (doc) { + return { + filters: { + 'is_group': 1, + 'name': ['!=', cur_frm.doc.customer_group_name] + } + } + }); -cur_frm.fields_dict['accounts'].grid.get_field('account').get_query = function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - filters: { - "account_type": 'Receivable', - "company": d.company, - "is_group": 0 - } - } -} + frm.set_query('account', 'accounts', function (doc, cdt, cdn) { + return { + filters: { + "account_type": 'Receivable', + "company": locals[cdt][cdn].company, + "is_group": 0 + } + } + }); -cur_frm.fields_dict['accounts'].grid.get_field('advance_account').get_query = function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - filters: { - "root_type": 'Liability', - "company": d.company, - "is_group": 0 - } + frm.set_query('advance_account', 'accounts', function (doc, cdt, cdn) { + return { + filters: { + "root_type": 'Liability', + "company": locals[cdt][cdn].company, + "is_group": 0 + } + } + }); } -} \ No newline at end of file +}); diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.js b/erpnext/setup/doctype/supplier_group/supplier_group.js index 58ab7fa928..ac5904f4d2 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.js +++ b/erpnext/setup/doctype/supplier_group/supplier_group.js @@ -16,34 +16,35 @@ cur_frm.cscript.set_root_readonly = function(doc) { } }; -// get query select Customer Group -cur_frm.fields_dict['parent_supplier_group'].get_query = function() { - return { - filters: { - 'is_group': 1, - 'name': ['!=', cur_frm.doc.supplier_group_name] - } - }; -}; +frappe.ui.form.on("Supplier Group", { + setup: function(frm){ + frm.set_query('parent_supplier_group', function (doc) { + return { + filters: { + 'is_group': 1, + 'name': ['!=', cur_frm.doc.supplier_group_name] + } + } + }); -cur_frm.fields_dict['accounts'].grid.get_field('account').get_query = function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - filters: { - 'account_type': 'Payable', - 'company': d.company, - "is_group": 0 - } - }; -}; + frm.set_query('account', 'accounts', function (doc, cdt, cdn) { + return { + filters: { + 'account_type': 'Payable', + 'company': locals[cdt][cdn].company, + "is_group": 0 + } + } + }); -cur_frm.fields_dict['accounts'].grid.get_field('advance_account').get_query = function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - filters: { - "root_type": 'Asset', - "company": d.company, - "is_group": 0 - } + frm.set_query('advance_account', 'accounts', function (doc, cdt, cdn) { + return { + filters: { + "root_type": 'Asset', + "company": locals[cdt][cdn].company, + "is_group": 0 + } + } + }); } -}; \ No newline at end of file +}); From a06017c2c3b0db520c86229619cd1c173d0a234c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 12 Jun 2023 15:24:53 +0530 Subject: [PATCH 05/40] fix: Use advance account from Reconciliation document for fetching Payment Entries --- .../doctype/payment_entry/payment_entry.py | 8 +++- .../payment_reconciliation.js | 2 +- .../payment_reconciliation.py | 11 +---- .../purchase_invoice/purchase_invoice.py | 19 +++++---- .../purchase_invoice/test_purchase_invoice.py | 3 +- .../doctype/sales_invoice/sales_invoice.py | 19 +++++---- .../sales_invoice/test_sales_invoice.py | 6 ++- erpnext/accounts/party.py | 7 +++- erpnext/controllers/accounts_controller.py | 40 ++++++++++++++----- erpnext/setup/doctype/company/company.js | 3 +- erpnext/setup/doctype/company/company.json | 17 ++++++-- 11 files changed, 91 insertions(+), 44 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 5249f171fb..7012eacb61 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -106,6 +106,12 @@ class PaymentEntry(AccountsController): root_type == "Asset" and self.party_type == "Supplier" ): return + if self.unallocated_amount == 0: + for d in self.references: + if d.reference_doctype in ["Sales Order", "Purchase Order"]: + break + else: + return liability_account = get_party_account( self.party_type, self.party, self.company, include_advance=True )[1] @@ -1694,7 +1700,7 @@ def get_outstanding_on_journal_entry(name): @frappe.whitelist() def get_reference_details(reference_doctype, reference_name, party_account_currency): - total_amount = outstanding_amount = exchange_rate = None + total_amount = outstanding_amount = exchange_rate = account = None ref_doc = frappe.get_doc(reference_doctype, reference_name) company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency( diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index a40e6f274f..e46c81b9ed 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -34,7 +34,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo filters: { "company": this.frm.doc.company, "is_group": 0, - "root_type": (this.frm.party_type == 'Customer') ? "Liability": "Asset" + "root_type": this.frm.doc.party_type == 'Customer' ? "Liability": "Asset" } }; }); diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index e7d7f2c1e4..e5fb3df1a4 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -60,16 +60,7 @@ class PaymentReconciliation(Document): self.add_payment_entries(non_reconciled_payments) def get_payment_entries(self): - advance_accounts = [] - if self.party_type == "Customer": - advance_accounts = frappe.db.get_list( - "Account", filters={"root_type": "Liability", "company": self.company}, pluck="name" - ) - elif self.party_type == "Supplier": - advance_accounts = frappe.db.get_list( - "Account", filters={"root_type": "Asset", "company": self.company}, pluck="name" - ) - party_account = [self.receivable_payable_account] + advance_accounts + party_account = [self.receivable_payable_account, self.default_advance_account] order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order" condition = frappe._dict( diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index dfea8e9049..0f3b12b3ef 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -33,8 +33,10 @@ from erpnext.accounts.utils import get_account_currency, get_fiscal_year from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.buying.utils import check_on_hold_or_closed_status -from erpnext.controllers.accounts_controller import validate_account_head -from erpnext.controllers.accounts_controller import make_advance_liability_entry +from erpnext.controllers.accounts_controller import ( + check_advance_liability_entry, + validate_account_head, +) from erpnext.controllers.buying_controller import BuyingController from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( @@ -581,11 +583,14 @@ class PurchaseInvoice(BuyingController): gl_entries = [] self.make_supplier_gl_entry(gl_entries) - - advance_payments_as_liability = frappe.db.get_value("Company", {"company_name": self.company}, "book_advance_payments_as_liability") - if advance_payments_as_liability: - for advance_entry in self.advances: - make_advance_liability_entry(gl_entries, advance_entry.reference_name, advance_entry.allocated_amount, invoice=self.name, party_type="Supplier") + + check_advance_liability_entry( + gl_entries, + company=self.company, + advances=self.advances, + invoice=self.name, + party_type="Supplier", + ) self.make_item_gl_entries(gl_entries) self.make_precision_loss_gl_entry(gl_entries) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 9ade46e045..c15692b751 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1669,7 +1669,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): frappe.db.set_value( "Company", "_Test Company", - {"book_advance_payments_as_liability": 1, "default_advance_account": "Debtors - _TC"}, + {"book_advance_payments_as_liability": 1, "default_advance_paid_account": "Debtors - _TC"}, ) pe = create_payment_entry( company="_Test Company", @@ -1722,6 +1722,7 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): 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 create_tax_witholding_category(category_name, company, account): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 419628eaff..d43e6e8c59 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -32,8 +32,10 @@ from erpnext.assets.doctype.asset.depreciation import ( reset_depreciation_schedule, reverse_depreciation_entry_made_after_disposal, ) -from erpnext.controllers.accounts_controller import validate_account_head -from erpnext.controllers.accounts_controller import make_advance_liability_entry +from erpnext.controllers.accounts_controller import ( + check_advance_liability_entry, + validate_account_head, +) from erpnext.controllers.selling_controller import SellingController from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data from erpnext.setup.doctype.company.company import update_company_current_month_sales @@ -1065,11 +1067,14 @@ class SalesInvoice(SellingController): gl_entries = [] self.make_customer_gl_entry(gl_entries) - - advance_payments_as_liability = frappe.db.get_value("Company", {"company_name": self.company}, "book_advance_payments_as_liability") - if advance_payments_as_liability: - for advance_entry in self.advances: - make_advance_liability_entry(gl_entries, advance_entry.reference_name, advance_entry.allocated_amount, invoice=self.name, party_type="Customer") + + check_advance_liability_entry( + gl_entries, + company=self.company, + advances=self.advances, + invoice=self.name, + party_type="Customer", + ) self.make_tax_gl_entries(gl_entries) self.make_exchange_gain_loss_gl_entries(gl_entries) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 364bf6898a..54c8b8472e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3320,7 +3320,10 @@ class TestSalesInvoice(unittest.TestCase): frappe.db.set_value( "Company", "_Test Company", - {"book_advance_payments_as_liability": 1, "default_advance_account": "Creditors - _TC"}, + { + "book_advance_payments_as_liability": 1, + "default_advance_received_account": "Creditors - _TC", + }, ) pe = create_payment_entry( company="_Test Company", @@ -3408,6 +3411,7 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): 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 create_sales_invoice(**args): diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index ccd54b344f..1e38217a16 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -429,7 +429,12 @@ def get_party_advance_account(party_type, party, company): ) if not account: - account = frappe.get_cached_value("Company", company, "default_advance_account") + account_name = ( + "default_advance_received_account" + if party_type == "Customer" + else "default_advance_paid_account" + ) + account = frappe.get_cached_value("Company", company, account_name) return account diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d8c4135292..3fac56cfa2 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2908,16 +2908,6 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.create_stock_reservation_entries() -@erpnext.allow_regional -def validate_regional(doc): - pass - - -@erpnext.allow_regional -def validate_einvoice_fields(doc): - pass - - def make_advance_liability_entry( gl_entries, pe, allocated_amount, invoice, party_type, references=False ): @@ -2990,3 +2980,33 @@ def make_advance_liability_entry( item=invoice, ) ) + + +def check_advance_liability_entry(gl_entries, company, advances, invoice, party_type): + advance_payments_as_liability = frappe.db.get_value( + "Company", {"company_name": company}, "book_advance_payments_as_liability" + ) + if advance_payments_as_liability: + for advance_entry in advances: + make_advance_liability_entry( + gl_entries, + advance_entry.reference_name, + advance_entry.allocated_amount, + invoice=invoice, + party_type=party_type, + ) + + +@erpnext.allow_regional +def validate_regional(doc): + pass + + +@erpnext.allow_regional +def validate_einvoice_fields(doc): + pass + + +@erpnext.allow_regional +def update_gl_dict_with_regional_fields(doc, gl_dict): + pass diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index fb0ee7f07a..436fe0595a 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -227,7 +227,8 @@ erpnext.company.setup_queries = function(frm) { ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}], ["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}], ["default_provisional_account", {"root_type": ["in", ["Liability", "Asset"]]}], - ["default_advance_account", {"root_type": ["in", ["Liability", "Asset"]]}], + ["default_advance_received_account", {"root_type": "Liability"}], + ["default_advance_paid_account", {"root_type": "Asset"}], ], function(i, v) { erpnext.company.set_custom_query(frm, v); }); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 5b3d3bb086..3523af1988 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -72,7 +72,8 @@ "default_finance_book", "advance_payments_section", "book_advance_payments_as_liability", - "default_advance_account", + "default_advance_received_account", + "default_advance_paid_account", "column_break_cui0", "auto_accounting_for_stock_settings", "enable_perpetual_inventory", @@ -716,9 +717,17 @@ }, { "depends_on": "eval:doc.book_advance_payments_as_liability", - "fieldname": "default_advance_account", + "fieldname": "default_advance_received_account", "fieldtype": "Link", - "label": "Default Account", + "label": "Default Advance Received Account", + "mandatory_depends_on": "book_advance_payments_as_liability", + "options": "Account" + }, + { + "depends_on": "eval:doc.book_advance_payments_as_liability", + "fieldname": "default_advance_paid_account", + "fieldtype": "Link", + "label": "Default Advance Paid Account", "mandatory_depends_on": "book_advance_payments_as_liability", "options": "Account" } @@ -728,7 +737,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2023-06-05 14:12:37.946451", + "modified": "2023-06-12 12:51:12.007410", "modified_by": "Administrator", "module": "Setup", "name": "Company", From 5e9821dce24fdbca59a11f9763b8b96df2b0d3bd Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 12 Jun 2023 18:00:15 +0530 Subject: [PATCH 06/40] test: modify test to check posting date --- .../doctype/payment_entry/payment_entry.py | 19 ++++++++++++++++--- .../purchase_invoice/test_purchase_invoice.py | 11 ++++++++--- .../sales_invoice/test_sales_invoice.py | 14 +++++++++++--- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 7012eacb61..7a31dc5885 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -909,6 +909,7 @@ class PaymentEntry(AccountsController): item=self, ) for d in self.get("references"): + gle = party_dict.copy() book_advance_payments_as_liability = frappe.get_value( "Company", {"company_name": self.company}, "book_advance_payments_as_liability" ) @@ -917,17 +918,27 @@ class PaymentEntry(AccountsController): and book_advance_payments_as_liability ): self.make_invoice_liability_entry(gl_entries, d) + gle.update( + { + "against_voucher_type": "Payment Entry", + "against_voucher": self.name, + } + ) allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d) - gle = party_dict.copy() gle.update( { dr_or_cr: allocated_amount_in_company_currency, dr_or_cr + "_in_account_currency": d.allocated_amount, - "against_voucher_type": "Payment Entry", - "against_voucher": self.name, } ) + if not gle.get("against_voucher_type"): + gle.update( + { + "against_voucher_type": d.reference_doctype, + "against_voucher": d.reference_name, + } + ) gl_entries.append(gle) @@ -940,6 +951,8 @@ class PaymentEntry(AccountsController): { dr_or_cr + "_in_account_currency": self.unallocated_amount, dr_or_cr: base_unallocated_amount, + "against_voucher_type": "Payment Entry", + "against_voucher": self.name, } ) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index c15692b751..1ac231bc19 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1694,13 +1694,18 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): pi.save() pi.submit() expected_gle = [ - ["Creditors - _TC", 50, 100], - ["Debtors - _TC", 0.0, 50], - ["Stock Received But Not Billed - _TC", 100, 0.0], + ["Creditors - _TC", 50, 100, nowdate()], + ["Debtors - _TC", 0.0, 50, nowdate()], + ["Stock Received But Not Billed - _TC", 100, 0.0, nowdate()], ] check_gl_entries(self, pi.name, expected_gle, nowdate()) self.assertEqual(pi.outstanding_amount, 200) + frappe.db.set_value( + "Company", + "_Test Company", + {"book_advance_payments_as_liability": 0, "default_advance_paid_account": ""}, + ) def check_gl_entries(doc, voucher_no, expected_gle, posting_date): diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 54c8b8472e..8ab7fd7cd0 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3348,13 +3348,21 @@ class TestSalesInvoice(unittest.TestCase): si.save() si.submit() expected_gle = [ - ["Creditors - _TC", 50, 0.0], - ["Debtors - _TC", 100, 50], - ["Sales - _TC", 0.0, 100], + ["Creditors - _TC", 50, 0.0, nowdate()], + ["Debtors - _TC", 100, 50, nowdate()], + ["Sales - _TC", 0.0, 100, nowdate()], ] check_gl_entries(self, si.name, expected_gle, nowdate()) self.assertEqual(si.outstanding_amount, 50) + frappe.db.set_value( + "Company", + "_Test Company", + { + "book_advance_payments_as_liability": 0, + "default_advance_received_account": "", + }, + ) def get_sales_invoice_for_e_invoice(): From 17341adf1cb385e3326552d95dc1bcd00e4b938a Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 13 Jun 2023 15:00:46 +0530 Subject: [PATCH 07/40] fix: calculate outstanding amount on reconcile correctly --- .../doctype/payment_entry/payment_entry.py | 21 ++++--- .../purchase_invoice/test_purchase_invoice.py | 58 ++++++++++------- .../sales_invoice/test_sales_invoice.py | 62 ++++++++++--------- erpnext/accounts/utils.py | 2 +- erpnext/buying/doctype/supplier/supplier.js | 2 +- erpnext/controllers/accounts_controller.py | 6 +- erpnext/selling/doctype/customer/customer.js | 2 +- erpnext/setup/doctype/company/company.js | 4 +- 8 files changed, 85 insertions(+), 72 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 7a31dc5885..dd3490936f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -872,12 +872,12 @@ class PaymentEntry(AccountsController): self.set("remarks", "\n".join(remarks)) - def build_gl_map(self): + def build_gl_map(self, is_reconcile=False): if self.payment_type in ("Receive", "Pay") and not self.get("party_account_field"): self.setup_party_account_field() gl_entries = [] - self.add_party_gl_entries(gl_entries) + self.add_party_gl_entries(gl_entries, is_reconcile) self.add_bank_gl_entries(gl_entries) self.add_deductions_gl_entries(gl_entries) self.add_tax_gl_entries(gl_entries) @@ -888,7 +888,7 @@ class PaymentEntry(AccountsController): gl_entries = process_gl_map(gl_entries) make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj) - def add_party_gl_entries(self, gl_entries): + def add_party_gl_entries(self, gl_entries, is_reconcile): if self.party_account: if self.payment_type == "Receive": against_account = self.paid_to @@ -917,13 +917,14 @@ class PaymentEntry(AccountsController): d.reference_doctype in ["Sales Invoice", "Purchase Invoice"] and book_advance_payments_as_liability ): - self.make_invoice_liability_entry(gl_entries, d) - gle.update( - { - "against_voucher_type": "Payment Entry", - "against_voucher": self.name, - } - ) + if not is_reconcile: + self.make_invoice_liability_entry(gl_entries, d) + gle.update( + { + "against_voucher_type": "Payment Entry", + "against_voucher": self.name, + } + ) allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d) gle.update( diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 1ac231bc19..6ebb6afe6e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1664,13 +1664,9 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): def test_advance_entries_as_liability(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry - from erpnext.accounts.party import get_party_account - frappe.db.set_value( - "Company", - "_Test Company", - {"book_advance_payments_as_liability": 1, "default_advance_paid_account": "Debtors - _TC"}, - ) + set_advance_flag(company="_Test Company", flag=1, default_account="Debtors - _TC") + pe = create_payment_entry( company="_Test Company", payment_type="Pay", @@ -1678,34 +1674,48 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): party="_Test Supplier", paid_from="Cash - _TC", paid_to="Creditors - _TC", - paid_amount=1000, + paid_amount=500, ) pe.submit() pi = make_purchase_invoice( - company="_Test Company", customer="_Test Supplier", do_not_save=True, do_not_submit=True + company="_Test Company", + customer="_Test Supplier", + do_not_save=True, + do_not_submit=True, + rate=1000, + price_list_rate=1000, + qty=1, ) - pi.base_grand_total = 100 - pi.grand_total = 100 + pi.base_grand_total = 1000 + pi.grand_total = 1000 pi.set_advances() - self.assertEqual(pi.advances[0].allocated_amount, 100) - pi.advances[0].allocated_amount = 50 - pi.advances = [pi.advances[0]] + for advance in pi.advances: + advance.allocated_amount = 500 if advance.reference_name == pe.name else 0 pi.save() pi.submit() - expected_gle = [ - ["Creditors - _TC", 50, 100, nowdate()], - ["Debtors - _TC", 0.0, 50, nowdate()], - ["Stock Received But Not Billed - _TC", 100, 0.0, nowdate()], - ] + self.assertEqual(pi.advances[0].allocated_amount, 500) + expected_gle = [ + ["Creditors - _TC", 500, 1000, nowdate()], + ["Debtors - _TC", 0.0, 500, nowdate()], + ["Stock Received But Not Billed - _TC", 1000, 0.0, nowdate()], + ] check_gl_entries(self, pi.name, expected_gle, nowdate()) - self.assertEqual(pi.outstanding_amount, 200) - frappe.db.set_value( - "Company", - "_Test Company", - {"book_advance_payments_as_liability": 0, "default_advance_paid_account": ""}, - ) + self.assertEqual(pi.outstanding_amount, 500) + + set_advance_flag(company="_Test Company", flag=0, default_account="") + + +def set_advance_flag(company, flag, default_account): + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_as_liability": flag, + "default_advance_paid_account": default_account, + }, + ) def check_gl_entries(doc, voucher_no, expected_gle, posting_date): diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 8ab7fd7cd0..067808d9a1 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3315,16 +3315,10 @@ class TestSalesInvoice(unittest.TestCase): def test_advance_entries_as_liability(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry - from erpnext.accounts.party import get_party_account + from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute + + set_advance_flag(company="_Test Company", flag=1, default_account="Creditors - _TC") - frappe.db.set_value( - "Company", - "_Test Company", - { - "book_advance_payments_as_liability": 1, - "default_advance_received_account": "Creditors - _TC", - }, - ) pe = create_payment_entry( company="_Test Company", payment_type="Receive", @@ -3337,32 +3331,42 @@ class TestSalesInvoice(unittest.TestCase): pe.submit() si = create_sales_invoice( - company="_Test Company", customer="_Test Customer", do_not_save=True, do_not_submit=True + company="_Test Company", + customer="_Test Customer", + do_not_save=True, + do_not_submit=True, + rate=500, + price_list_rate=500, ) - si.base_grand_total = 100 - si.grand_total = 100 + si.base_grand_total = 500 + si.grand_total = 500 si.set_advances() - self.assertEqual(si.advances[0].allocated_amount, 100) - si.advances[0].allocated_amount = 50 - si.advances = [si.advances[0]] + for advance in si.advances: + advance.allocated_amount = 500 if advance.reference_name == pe.name else 0 si.save() si.submit() - expected_gle = [ - ["Creditors - _TC", 50, 0.0, nowdate()], - ["Debtors - _TC", 100, 50, nowdate()], - ["Sales - _TC", 0.0, 100, nowdate()], - ] + self.assertEqual(si.advances[0].allocated_amount, 500) + expected_gle = [ + ["Creditors - _TC", 500, 0.0, nowdate()], + ["Debtors - _TC", 500, 500, nowdate()], + ["Sales - _TC", 0.0, 500, nowdate()], + ] check_gl_entries(self, si.name, expected_gle, nowdate()) - self.assertEqual(si.outstanding_amount, 50) - frappe.db.set_value( - "Company", - "_Test Company", - { - "book_advance_payments_as_liability": 0, - "default_advance_received_account": "", - }, - ) + self.assertEqual(si.outstanding_amount, 0) + + set_advance_flag(company="_Test Company", flag=0, default_account="") + + +def set_advance_flag(company, flag, default_account): + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_as_liability": flag, + "default_advance_received_account": default_account, + }, + ) def get_sales_invoice_for_e_invoice(): diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index c1d365304f..728a6d7a96 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -472,7 +472,7 @@ def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # n doc.save(ignore_permissions=True) # re-submit advance entry doc = frappe.get_doc(entry.voucher_type, entry.voucher_no) - gl_map = doc.build_gl_map() + gl_map = doc.build_gl_map(is_reconcile=True) create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1) # Only update outstanding for newly linked vouchers diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js index 50f9bb6cb9..a5bf4b246b 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -22,7 +22,7 @@ frappe.ui.form.on("Supplier", { let d = locals[cdt][cdn]; return { filters: { - "root_type": 'Asset', + "account_type": "Receivable", "company": d.company, "is_group": 0 } diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3fac56cfa2..7b2039d193 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2940,10 +2940,8 @@ def make_advance_liability_entry( dr_or_cr + "_in_account_currency": allocated_amount, rev: 0, rev + "_in_account_currency": 0, - "against_voucher": invoice.return_against - if cint(invoice.is_return) and invoice.return_against - else invoice.name, - "against_voucher_type": invoice.doctype, + "against_voucher": pe.name, + "against_voucher_type": "Payment Entry", "cost_center": invoice.cost_center, "project": invoice.project, "voucher_type": voucher_type, diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 8f828807a5..408e89b0ef 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -39,7 +39,7 @@ frappe.ui.form.on("Customer", { let d = locals[cdt][cdn]; return { filters: { - "root_type": 'Liability', + "account_type": 'Payable', "company": d.company, "is_group": 0 } diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 436fe0595a..089e20d442 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -227,8 +227,8 @@ erpnext.company.setup_queries = function(frm) { ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}], ["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}], ["default_provisional_account", {"root_type": ["in", ["Liability", "Asset"]]}], - ["default_advance_received_account", {"root_type": "Liability"}], - ["default_advance_paid_account", {"root_type": "Asset"}], + ["default_advance_received_account", {"account_type": "Payable"}], + ["default_advance_paid_account", {"account_type": "Receivable"}], ], function(i, v) { erpnext.company.set_custom_query(frm, v); }); From ba4ab06ae3024d387570ef6779ab218f72518ed1 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 14 Jun 2023 12:39:16 +0530 Subject: [PATCH 08/40] fix: changed account types in controller method --- .../doctype/payment_entry/payment_entry.py | 34 +++++++++++++------ erpnext/accounts/utils.py | 6 ++-- erpnext/controllers/accounts_controller.py | 2 +- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index dd3490936f..7c813eb329 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -99,11 +99,11 @@ class PaymentEntry(AccountsController): ) if not book_advance_payments_as_liability: return - root_type = frappe.get_value( - "Account", {"name": self.party_account, "company": self.company}, "root_type" + account_type = frappe.get_value( + "Account", {"name": self.party_account, "company": self.company}, "account_type" ) - if (root_type == "Liability" and self.party_type == "Customer") or ( - root_type == "Asset" and self.party_type == "Supplier" + if (account_type == "Payable" and self.party_type == "Customer") or ( + account_type == "Receivable" and self.party_type == "Supplier" ): return if self.unallocated_amount == 0: @@ -908,6 +908,8 @@ class PaymentEntry(AccountsController): }, item=self, ) + is_advance = self.get_advance_flag() + for d in self.get("references"): gle = party_dict.copy() book_advance_payments_as_liability = frappe.get_value( @@ -916,13 +918,14 @@ class PaymentEntry(AccountsController): if ( d.reference_doctype in ["Sales Invoice", "Purchase Invoice"] and book_advance_payments_as_liability + and is_advance ): if not is_reconcile: self.make_invoice_liability_entry(gl_entries, d) gle.update( { - "against_voucher_type": "Payment Entry", - "against_voucher": self.name, + "voucher_type": "Payment Entry", + "voucher_no": self.name, } ) @@ -940,7 +943,6 @@ class PaymentEntry(AccountsController): "against_voucher": d.reference_name, } ) - gl_entries.append(gle) if self.unallocated_amount: @@ -952,13 +954,19 @@ class PaymentEntry(AccountsController): { dr_or_cr + "_in_account_currency": self.unallocated_amount, dr_or_cr: base_unallocated_amount, - "against_voucher_type": "Payment Entry", - "against_voucher": self.name, } ) gl_entries.append(gle) + def get_advance_flag(self): + for d in self.get("references"): + if d.reference_doctype == "Sales Order": + return True + if self.unallocated_amount > 0: + return True + return False + def make_invoice_liability_entry(self, gl_entries, invoice): args_dict = { "party_type": self.party_type, @@ -967,8 +975,6 @@ class PaymentEntry(AccountsController): "cost_center": self.cost_center, "voucher_type": invoice.reference_doctype, "voucher_no": invoice.reference_name, - "against_voucher_type": invoice.reference_doctype, - "against_voucher": invoice.reference_name, } dr_or_cr = "credit" if invoice.reference_doctype == "Sales Invoice" else "debit" @@ -987,6 +993,12 @@ class PaymentEntry(AccountsController): args_dict["account"] = self.party_account args_dict[dr_or_cr] = invoice.allocated_amount args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount + args_dict.update( + { + "against_voucher_type": "Payment Entry", + "against_voucher": self.name, + } + ) gle = self.get_gl_dict( args_dict, item=self, diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 728a6d7a96..abf9b5e9b2 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -436,7 +436,9 @@ def add_cc(args=None): return cc.name -def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # nosemgrep +def reconcile_against_document( + args, skip_ref_details_update_for_pe=False, is_reconcile=False +): # nosemgrep """ Cancel PE or JV, Update against document, split if required and resubmit """ @@ -472,7 +474,7 @@ def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # n doc.save(ignore_permissions=True) # re-submit advance entry doc = frappe.get_doc(entry.voucher_type, entry.voucher_no) - gl_map = doc.build_gl_map(is_reconcile=True) + gl_map = doc.build_gl_map(is_reconcile) create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1) # Only update outstanding for newly linked vouchers diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 7b2039d193..578b26a2cd 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1078,7 +1078,7 @@ class AccountsController(TransactionBase): if lst: from erpnext.accounts.utils import reconcile_against_document - reconcile_against_document(lst) + reconcile_against_document(lst, is_reconcile=True) def on_cancel(self): from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries From 033e4e84f5c91ef1e6a36ffb9ff66cd41a1fd108 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 14 Jun 2023 14:20:42 +0530 Subject: [PATCH 09/40] fix: modify voucher details for liability entries --- .../doctype/payment_entry/payment_entry.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 7c813eb329..40dcc8cc68 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -924,8 +924,8 @@ class PaymentEntry(AccountsController): self.make_invoice_liability_entry(gl_entries, d) gle.update( { - "voucher_type": "Payment Entry", - "voucher_no": self.name, + "against_voucher_type": "Payment Entry", + "against_voucher": self.name, } ) @@ -973,14 +973,20 @@ class PaymentEntry(AccountsController): "party": self.party, "account_currency": self.party_account_currency, "cost_center": self.cost_center, - "voucher_type": invoice.reference_doctype, - "voucher_no": invoice.reference_name, + "voucher_type": "Payment Entry", + "voucher_no": self.name, } dr_or_cr = "credit" if invoice.reference_doctype == "Sales Invoice" else "debit" args_dict["account"] = invoice.account args_dict[dr_or_cr] = invoice.allocated_amount args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount + args_dict.update( + { + "against_voucher_type": invoice.reference_doctype, + "against_voucher": invoice.reference_name, + } + ) gle = self.get_gl_dict( args_dict, item=self, From 442e3f2aa271454ff6b6001bc773df86196b0880 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 16 Jun 2023 13:38:47 +0530 Subject: [PATCH 10/40] fix: update outstanding amount and unpaid status on cancellation of payment entry --- .../doctype/payment_entry/payment_entry.py | 25 ++++++++-------- erpnext/accounts/general_ledger.py | 1 + erpnext/accounts/utils.py | 2 +- erpnext/controllers/accounts_controller.py | 29 +++++++------------ erpnext/setup/doctype/company/company.json | 15 +++++----- 5 files changed, 33 insertions(+), 39 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 40dcc8cc68..d901faaed3 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -872,7 +872,7 @@ class PaymentEntry(AccountsController): self.set("remarks", "\n".join(remarks)) - def build_gl_map(self, is_reconcile=False): + def build_gl_map(self, is_reconcile=True): if self.payment_type in ("Receive", "Pay") and not self.get("party_account_field"): self.setup_party_account_field() @@ -918,16 +918,15 @@ class PaymentEntry(AccountsController): if ( d.reference_doctype in ["Sales Invoice", "Purchase Invoice"] and book_advance_payments_as_liability - and is_advance + and (is_advance or is_reconcile) ): - if not is_reconcile: - self.make_invoice_liability_entry(gl_entries, d) - gle.update( - { - "against_voucher_type": "Payment Entry", - "against_voucher": self.name, - } - ) + self.make_invoice_liability_entry(gl_entries, d) + gle.update( + { + "against_voucher_type": "Payment Entry", + "against_voucher": self.name, + } + ) allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d) gle.update( @@ -939,8 +938,8 @@ class PaymentEntry(AccountsController): if not gle.get("against_voucher_type"): gle.update( { - "against_voucher_type": d.reference_doctype, - "against_voucher": d.reference_name, + "against_voucher_type": d.reference_doctype if is_advance else "Payment Entry", + "against_voucher": d.reference_name if is_advance else self.name, } ) gl_entries.append(gle) @@ -954,6 +953,8 @@ class PaymentEntry(AccountsController): { dr_or_cr + "_in_account_currency": self.unallocated_amount, dr_or_cr: base_unallocated_amount, + "against_voucher_type": "Payment Entry", + "against_voucher": self.name, } ) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index a929ff17b0..a0954a955a 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -223,6 +223,7 @@ def check_if_in_list(gle, gl_map, dimensions=None): "party_type", "project", "finance_book", + "voucher_no", ] if dimensions: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index abf9b5e9b2..e69dcd4199 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -437,7 +437,7 @@ def add_cc(args=None): def reconcile_against_document( - args, skip_ref_details_update_for_pe=False, is_reconcile=False + args, skip_ref_details_update_for_pe=False, is_reconcile=True ): # nosemgrep """ Cancel PE or JV, Update against document, split if required and resubmit diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 578b26a2cd..f564840251 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1021,7 +1021,7 @@ class AccountsController(TransactionBase): ) ) - def update_against_document_in_jv(self): + def update_against_document_in_jv(self, is_reconcile=True): """ Links invoice and advance voucher: 1. cancel advance voucher @@ -1078,7 +1078,7 @@ class AccountsController(TransactionBase): if lst: from erpnext.accounts.utils import reconcile_against_document - reconcile_against_document(lst, is_reconcile=True) + reconcile_against_document(lst, is_reconcile) def on_cancel(self): from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries @@ -2919,7 +2919,6 @@ def make_advance_liability_entry( rev = "credit" against = invoice.debit_to party = invoice.customer - voucher_type = "Sales Invoice" else: invoice = frappe.get_doc("Purchase Invoice", invoice) account = pe.paid_to @@ -2927,9 +2926,8 @@ def make_advance_liability_entry( rev = "debit" against = invoice.credit_to party = invoice.supplier - voucher_type = "Purchase Invoice" gl_entries.append( - invoice.get_gl_dict( + pe.get_gl_dict( { "account": account, "party_type": party_type, @@ -2940,42 +2938,35 @@ def make_advance_liability_entry( dr_or_cr + "_in_account_currency": allocated_amount, rev: 0, rev + "_in_account_currency": 0, - "against_voucher": pe.name, - "against_voucher_type": "Payment Entry", "cost_center": invoice.cost_center, "project": invoice.project, - "voucher_type": voucher_type, - "voucher_no": invoice.name, + "against_voucher_type": "Payment Entry", + "against_voucher": pe.name, }, invoice.party_account_currency, - item=invoice, + item=pe, ) ) (dr_or_cr, rev) = ("credit", "debit") if party_type == "Customer" else ("debit", "credit") gl_entries.append( - invoice.get_gl_dict( + pe.get_gl_dict( { "account": against, "party_type": party_type, "party": party, "due_date": invoice.due_date, - "against": account, dr_or_cr: allocated_amount, dr_or_cr + "_in_account_currency": allocated_amount, rev: 0, rev + "_in_account_currency": 0, - "against_voucher": invoice.return_against - if cint(invoice.is_return) and invoice.return_against - else invoice.name, - "against_voucher_type": invoice.doctype, "cost_center": invoice.cost_center, "project": invoice.project, - "voucher_type": "Payment Entry" if references else voucher_type, - "voucher_no": pe.name if references else invoice.name, + "against_voucher_type": invoice.doctype, + "against_voucher": invoice.name, }, invoice.party_account_currency, - item=invoice, + item=pe, ) ) diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 3523af1988..611e2ab221 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -72,9 +72,9 @@ "default_finance_book", "advance_payments_section", "book_advance_payments_as_liability", + "column_break_fwcf", "default_advance_received_account", "default_advance_paid_account", - "column_break_cui0", "auto_accounting_for_stock_settings", "enable_perpetual_inventory", "enable_provisional_accounting_for_non_stock_items", @@ -702,19 +702,16 @@ }, { "default": "0", + "description": "Enabling this option will allow you to record -

1. Advances Received in a Liability Account instead of the Receivable Account

2. Advances Paid in an Asset Account instead of the Payable Account", "fieldname": "book_advance_payments_as_liability", "fieldtype": "Check", - "label": "Book Advance Payments as Liability" + "label": "Book Advance Payments in Separate Party Account" }, { "fieldname": "advance_payments_section", "fieldtype": "Section Break", "label": "Advance Payments" }, - { - "fieldname": "column_break_cui0", - "fieldtype": "Column Break" - }, { "depends_on": "eval:doc.book_advance_payments_as_liability", "fieldname": "default_advance_received_account", @@ -730,6 +727,10 @@ "label": "Default Advance Paid Account", "mandatory_depends_on": "book_advance_payments_as_liability", "options": "Account" + }, + { + "fieldname": "column_break_fwcf", + "fieldtype": "Column Break" } ], "icon": "fa fa-building", @@ -737,7 +738,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2023-06-12 12:51:12.007410", + "modified": "2023-06-16 13:32:48.790947", "modified_by": "Administrator", "module": "Setup", "name": "Company", From 016ed951da02ca87e735f035f5c6663cc113ad76 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 20 Jun 2023 13:22:32 +0530 Subject: [PATCH 11/40] test: Update tests --- .../doctype/payment_entry/payment_entry.py | 42 ++++++++++--------- .../purchase_invoice/test_purchase_invoice.py | 16 ++++--- .../sales_invoice/test_sales_invoice.py | 17 ++++---- erpnext/accounts/utils.py | 6 +-- erpnext/controllers/accounts_controller.py | 4 +- 5 files changed, 46 insertions(+), 39 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index d23afb17d9..752d22a353 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -97,29 +97,37 @@ class PaymentEntry(AccountsController): book_advance_payments_as_liability = frappe.get_value( "Company", {"company_name": self.company}, "book_advance_payments_as_liability" ) + if not book_advance_payments_as_liability: return + account_type = frappe.get_value( "Account", {"name": self.party_account, "company": self.company}, "account_type" ) + if (account_type == "Payable" and self.party_type == "Customer") or ( account_type == "Receivable" and self.party_type == "Supplier" ): return + if self.unallocated_amount == 0: for d in self.references: if d.reference_doctype in ["Sales Order", "Purchase Order"]: break else: return + liability_account = get_party_account( self.party_type, self.party, self.company, include_advance=True )[1] + self.set(self.party_account_field, liability_account) + msg = "Book Advance Payments as Liability option is chosen. Paid From account changed from {0} to {1}.".format( frappe.bold(self.party_account), frappe.bold(liability_account), ) + frappe.msgprint(_(msg), alert=True) def on_cancel(self): @@ -921,12 +929,12 @@ class PaymentEntry(AccountsController): self.set("remarks", "\n".join(remarks)) - def build_gl_map(self, is_reconcile=True): + def build_gl_map(self): if self.payment_type in ("Receive", "Pay") and not self.get("party_account_field"): self.setup_party_account_field() gl_entries = [] - self.add_party_gl_entries(gl_entries, is_reconcile) + self.add_party_gl_entries(gl_entries) self.add_bank_gl_entries(gl_entries) self.add_deductions_gl_entries(gl_entries) self.add_tax_gl_entries(gl_entries) @@ -937,7 +945,7 @@ class PaymentEntry(AccountsController): gl_entries = process_gl_map(gl_entries) make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj) - def add_party_gl_entries(self, gl_entries, is_reconcile): + def add_party_gl_entries(self, gl_entries): if self.party_account: if self.payment_type == "Receive": against_account = self.paid_to @@ -957,7 +965,7 @@ class PaymentEntry(AccountsController): }, item=self, ) - is_advance = self.get_advance_flag() + is_advance = self.is_advance_entry() for d in self.get("references"): gle = party_dict.copy() @@ -967,30 +975,24 @@ class PaymentEntry(AccountsController): if ( d.reference_doctype in ["Sales Invoice", "Purchase Invoice"] and book_advance_payments_as_liability - and (is_advance or is_reconcile) + and is_advance ): self.make_invoice_liability_entry(gl_entries, d) - gle.update( - { - "against_voucher_type": "Payment Entry", - "against_voucher": self.name, - } - ) + against_voucher_type = "Payment Entry" + against_voucher = self.name + else: + against_voucher_type = d.reference_doctype + against_voucher = d.reference_name allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d) gle.update( { dr_or_cr: allocated_amount_in_company_currency, dr_or_cr + "_in_account_currency": d.allocated_amount, + "against_voucher_type": against_voucher_type, + "against_voucher": against_voucher, } ) - if not gle.get("against_voucher_type"): - gle.update( - { - "against_voucher_type": d.reference_doctype if is_advance else "Payment Entry", - "against_voucher": d.reference_name if is_advance else self.name, - } - ) gl_entries.append(gle) if self.unallocated_amount: @@ -1009,9 +1011,9 @@ class PaymentEntry(AccountsController): gl_entries.append(gle) - def get_advance_flag(self): + def is_advance_entry(self): for d in self.get("references"): - if d.reference_doctype == "Sales Order": + if d.reference_doctype in ("Sales Order", "Purchase Order"): return True if self.unallocated_amount > 0: return True diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 02d60dccab..569a48acad 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1698,12 +1698,16 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): pi.submit() self.assertEqual(pi.advances[0].allocated_amount, 500) + + # Check GL Entry against payment doctype expected_gle = [ - ["Creditors - _TC", 500, 1000, nowdate()], + ["Cash - _TC", 0.0, 500, nowdate()], + ["Creditors - _TC", 500, 0.0, nowdate()], + ["Creditors - _TC", 500, 0.0, nowdate()], ["Debtors - _TC", 0.0, 500, nowdate()], - ["Stock Received But Not Billed - _TC", 1000, 0.0, nowdate()], ] - check_gl_entries(self, pi.name, expected_gle, nowdate()) + + check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry") self.assertEqual(pi.outstanding_amount, 500) set_advance_flag(company="_Test Company", flag=0, default_account="") @@ -1735,18 +1739,18 @@ def set_advance_flag(company, flag, default_account): ) -def check_gl_entries(doc, voucher_no, expected_gle, posting_date): +def check_gl_entries(doc, voucher_no, expected_gle, posting_date, voucher_type="Purchase Invoice"): gl = frappe.qb.DocType("GL Entry") q = ( frappe.qb.from_(gl) .select(gl.account, gl.debit, gl.credit, gl.posting_date) .where( - (gl.voucher_type == "Sales Invoice") + (gl.voucher_type == voucher_type) & (gl.voucher_no == voucher_no) & (gl.posting_date >= posting_date) & (gl.is_cancelled == 0) ) - .orderby(gl.posting_date, gl.account) + .orderby(gl.posting_date, gl.account, gl.creation) ) gl_entries = q.run(as_dict=True) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index c23ef34759..4cf3abaad1 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3312,7 +3312,6 @@ class TestSalesInvoice(unittest.TestCase): def test_advance_entries_as_liability(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry - from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute set_advance_flag(company="_Test Company", flag=1, default_account="Creditors - _TC") @@ -3344,12 +3343,16 @@ class TestSalesInvoice(unittest.TestCase): si.submit() self.assertEqual(si.advances[0].allocated_amount, 500) + + # Check GL Entry against payment doctype expected_gle = [ + ["Cash - _TC", 1000, 0.0, nowdate()], ["Creditors - _TC", 500, 0.0, nowdate()], - ["Debtors - _TC", 500, 500, nowdate()], - ["Sales - _TC", 0.0, 500, nowdate()], + ["Debtors - _TC", 0.0, 1000, nowdate()], + ["Debtors - _TC", 0.0, 500, nowdate()], ] - check_gl_entries(self, si.name, expected_gle, nowdate()) + + check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry") self.assertEqual(si.outstanding_amount, 0) set_advance_flag(company="_Test Company", flag=0, default_account="") @@ -3401,18 +3404,18 @@ def get_sales_invoice_for_e_invoice(): return si -def check_gl_entries(doc, voucher_no, expected_gle, posting_date): +def check_gl_entries(doc, voucher_no, expected_gle, posting_date, voucher_type="Sales Invoice"): gl = frappe.qb.DocType("GL Entry") q = ( frappe.qb.from_(gl) .select(gl.account, gl.debit, gl.credit, gl.posting_date) .where( - (gl.voucher_type == "Sales Invoice") + (gl.voucher_type == voucher_type) & (gl.voucher_no == voucher_no) & (gl.posting_date >= posting_date) & (gl.is_cancelled == 0) ) - .orderby(gl.posting_date, gl.account) + .orderby(gl.posting_date, gl.account, gl.creation) ) gl_entries = q.run(as_dict=True) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index e69dcd4199..c1d365304f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -436,9 +436,7 @@ def add_cc(args=None): return cc.name -def reconcile_against_document( - args, skip_ref_details_update_for_pe=False, is_reconcile=True -): # nosemgrep +def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # nosemgrep """ Cancel PE or JV, Update against document, split if required and resubmit """ @@ -474,7 +472,7 @@ def reconcile_against_document( doc.save(ignore_permissions=True) # re-submit advance entry doc = frappe.get_doc(entry.voucher_type, entry.voucher_no) - gl_map = doc.build_gl_map(is_reconcile) + gl_map = doc.build_gl_map() create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1) # Only update outstanding for newly linked vouchers diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index dc499b9155..80f8430724 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1025,7 +1025,7 @@ class AccountsController(TransactionBase): ) ) - def update_against_document_in_jv(self, is_reconcile=True): + def update_against_document_in_jv(self): """ Links invoice and advance voucher: 1. cancel advance voucher @@ -1082,7 +1082,7 @@ class AccountsController(TransactionBase): if lst: from erpnext.accounts.utils import reconcile_against_document - reconcile_against_document(lst, is_reconcile) + reconcile_against_document(lst) def on_cancel(self): from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries From 92f845c0e1093ec1eeeb6e3bcd66d27ca4f5ca29 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Jun 2023 12:21:19 +0530 Subject: [PATCH 12/40] chore: Advance fetching order --- erpnext/accounts/party.py | 6 +- erpnext/controllers/accounts_controller.py | 164 +++++++++++---------- 2 files changed, 91 insertions(+), 79 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index d6aa7d8402..03cf82a2b0 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -410,7 +410,11 @@ def get_party_account(party_type, party=None, company=None, include_advance=Fals if include_advance and party_type in ["Customer", "Supplier"]: advance_account = get_party_advance_account(party_type, party, company) - return [account, advance_account] + if advance_account: + return [account, advance_account] + else: + return [account] + return account diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 80f8430724..eec58e853d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -892,9 +892,10 @@ class AccountsController(TransactionBase): amount_field = "debit_in_account_currency" order_field = "purchase_order" order_doctype = "Purchase Order" - party_account = [ - get_party_account(party_type, party=party, company=self.company, include_advance=True)[1] - ] + + party_account = get_party_account( + party_type, party=party, company=self.company, include_advance=True + ) order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field))) @@ -2203,37 +2204,59 @@ def get_advance_payment_entries( condition=None, ): - q = build_query( - party_type, - party, - party_account, - order_doctype, - order_list, - include_unallocated, - against_all_orders, - limit, - condition, - ) + payment_entries = [] + payment_entry = frappe.qb.DocType("Payment Entry") - payment_entries = q.run(as_dict=True) + if order_list or against_all_orders: + q = get_common_query( + party_type, + party, + party_account, + limit, + condition, + ) + payment_ref = frappe.qb.DocType("Payment Entry Reference") - return list(payment_entries) + q = q.inner_join(payment_ref).on(payment_entry.name == payment_ref.parent) + q = q.select( + (payment_ref.allocated_amount).as_("amount"), + (payment_ref.name).as_("reference_row"), + (payment_ref.reference_name).as_("against_order"), + payment_ref.reference_doctype == order_doctype, + ) + + if order_list: + q = q.where(payment_ref.reference_name.isin(order_list)) + + allocated = list(q.run(as_dict=True)) + payment_entries += allocated + + if include_unallocated: + q = get_common_query( + party_type, + party, + party_account, + limit, + condition, + ) + q = q.select((payment_entry.unallocated_amount).as_("amount")) + q = q.where(payment_entry.unallocated_amount > 0) + + unallocated = list(q.run(as_dict=True)) + payment_entries += unallocated + + return payment_entries -def build_query( +def get_common_query( party_type, party, party_account, - order_doctype, - order_list, - include_unallocated, - against_all_orders, limit, condition, ): payment_type = "Receive" if party_type == "Customer" else "Pay" payment_entry = frappe.qb.DocType("Payment Entry") - payment_ref = frappe.qb.DocType("Payment Entry Reference") q = ( frappe.qb.from_(payment_entry) @@ -2259,68 +2282,53 @@ def build_query( q = q.where(payment_entry.paid_to.isin(party_account)) if payment_type == "Receive": - q = q.select(payment_entry.source_exchange_rate) + q = q.select((payment_entry.source_exchange_rate).as_("exchange_rate")) else: - q.select(payment_entry.target_exchange_rate) + q = q.select((payment_entry.target_exchange_rate).as_("exchange_rate")) - if include_unallocated: - q = q.select((payment_entry.unallocated_amount).as_("amount")) - q = q.where(payment_entry.unallocated_amount > 0) - - if condition: - q = q.where(payment_entry.company == condition["company"]) - q = ( - q.where(payment_entry.posting_date >= condition["from_payment_date"]) - if condition.get("from_payment_date") - else q - ) - q = ( - q.where(payment_entry.posting_date <= condition["to_payment_date"]) - if condition.get("to_payment_date") - else q - ) - if condition.get("get_payments") == True: - q = ( - q.where(payment_entry.cost_center == condition["cost_center"]) - if condition.get("cost_center") - else q - ) - q = ( - q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"]) - if condition.get("minimum_payment_amount") - else q - ) - q = ( - q.where(payment_entry.unallocated_amount <= condition["maximum_payment_amount"]) - if condition.get("maximum_payment_amount") - else q - ) - else: - q = ( - q.where(payment_entry.total_debit >= condition["minimum_payment_amount"]) - if condition.get("minimum_payment_amount") - else q - ) - q = ( - q.where(payment_entry.total_debit <= condition["maximum_payment_amount"]) - if condition.get("maximum_payment_amount") - else q - ) - - elif order_list or against_all_orders: - q = q.inner_join(payment_ref).on(payment_entry.name == payment_ref.parent) - q = q.select( - (payment_ref.allocated_amount).as_("amount"), - (payment_ref.name).as_("reference_row"), - (payment_ref.reference_name).as_("against_order"), - payment_ref.reference_doctype == order_doctype, + if condition: + q = q.where(payment_entry.company == condition["company"]) + q = ( + q.where(payment_entry.posting_date >= condition["from_payment_date"]) + if condition.get("from_payment_date") + else q ) - - if order_list: - q = q.where(payment_ref.reference_name.isin(order_list)) + q = ( + q.where(payment_entry.posting_date <= condition["to_payment_date"]) + if condition.get("to_payment_date") + else q + ) + if condition.get("get_payments") == True: + q = ( + q.where(payment_entry.cost_center == condition["cost_center"]) + if condition.get("cost_center") + else q + ) + q = ( + q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"]) + if condition.get("minimum_payment_amount") + else q + ) + q = ( + q.where(payment_entry.unallocated_amount <= condition["maximum_payment_amount"]) + if condition.get("maximum_payment_amount") + else q + ) + else: + q = ( + q.where(payment_entry.total_debit >= condition["minimum_payment_amount"]) + if condition.get("minimum_payment_amount") + else q + ) + q = ( + q.where(payment_entry.total_debit <= condition["maximum_payment_amount"]) + if condition.get("maximum_payment_amount") + else q + ) q = q.orderby(payment_entry.posting_date) q = q.limit(limit) if limit else q + return q From b64ebc6fcc4130dfeb8d83f44072a4dd01eca42a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Jun 2023 17:49:45 +0530 Subject: [PATCH 13/40] test: fix payment reco tests --- .../test_process_deferred_accounting.py | 4 ++-- erpnext/controllers/accounts_controller.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py index 5a0aeb7284..83646c90ba 100644 --- a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py +++ b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py @@ -38,7 +38,7 @@ class TestProcessDeferredAccounting(unittest.TestCase): si.save() si.submit() - process_deferred_accounting = doc = frappe.get_doc( + process_deferred_accounting = frappe.get_doc( dict( doctype="Process Deferred Accounting", posting_date="2019-01-01", @@ -56,7 +56,7 @@ class TestProcessDeferredAccounting(unittest.TestCase): ["Sales - _TC", 0.0, 33.85, "2019-01-31"], ] - check_gl_entries(self, si.name, expected_gle, "2019-01-10") + check_gl_entries(self, si.name, expected_gle, "2019-01-31") def test_pda_submission_and_cancellation(self): pda = frappe.get_doc( diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index eec58e853d..3dadd46ee8 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2273,11 +2273,11 @@ def get_common_query( ) if party_type == "Customer": - q = q.select(payment_entry.paid_from_account_currency) + q = q.select((payment_entry.paid_from_account_currency).as_("currency")) q = q.select(payment_entry.paid_from) q = q.where(payment_entry.paid_from.isin(party_account)) else: - q = q.select(payment_entry.paid_to_account_currency) + q = q.select((payment_entry.paid_to_account_currency).as_("currency")) q = q.select(payment_entry.paid_to) q = q.where(payment_entry.paid_to.isin(party_account)) From 3aead05f42ec62ee0edd2d1e9ca205e8ca6b0f2f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Jun 2023 11:41:43 +0530 Subject: [PATCH 14/40] fix: Test related errors --- .../doctype/payment_entry/payment_entry.py | 7 +++++-- .../payment_reconciliation.py | 10 +++++++-- erpnext/accounts/utils.py | 2 +- erpnext/controllers/accounts_controller.py | 21 +++++++------------ 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 752d22a353..f6c6bce5bc 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -949,10 +949,8 @@ class PaymentEntry(AccountsController): if self.party_account: if self.payment_type == "Receive": against_account = self.paid_to - dr_or_cr = "credit" else: against_account = self.paid_from - dr_or_cr = "debit" party_dict = self.get_gl_dict( { @@ -965,6 +963,11 @@ class PaymentEntry(AccountsController): }, item=self, ) + + dr_or_cr = ( + "credit" if erpnext.get_party_account_type(self.party_type) == "Receivable" else "debit" + ) + is_advance = self.is_advance_entry() for d in self.get("references"): diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 9d869f21ee..e9cc3902d3 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -59,7 +59,10 @@ class PaymentReconciliation(Document): self.add_payment_entries(non_reconciled_payments) def get_payment_entries(self): - party_account = [self.receivable_payable_account, self.default_advance_account] + if self.default_advance_account: + party_account = [self.receivable_payable_account, self.default_advance_account] + else: + party_account = [self.receivable_payable_account] order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order" condition = frappe._dict( @@ -352,7 +355,10 @@ class PaymentReconciliation(Document): for row in self.get("allocation"): reconciled_entry = [] if row.invoice_number and row.allocated_amount: - if row.invoice_type in ["Sales Invoice", "Purchase Invoice"]: + if ( + row.invoice_type in ["Sales Invoice", "Purchase Invoice"] + and row.reference_type == "Payment Entry" + ): gl_entries = [] make_advance_liability_entry( gl_entries, row.reference_name, row.allocated_amount, row.invoice_number, self.party_type diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index c1d365304f..d9561ad468 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -500,7 +500,7 @@ def check_if_advance_entry_modified(args): q = ( frappe.qb.from_(journal_entry) - .innerjoin(journal_acc) + .inner_join(journal_acc) .on(journal_entry.name == journal_acc.parent) ) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3dadd46ee8..72c93261aa 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2156,11 +2156,7 @@ def get_advance_journal_entries( ConstantColumn("Journal Entry").as_("reference_type"), (journal_entry.name).as_("reference_name"), (journal_entry.remark).as_("remarks"), - ( - journal_acc.debit_in_account_currency - if party_type == "Supplier" - else journal_acc.credit_in_account_currency - ).as_("amount"), + (journal_acc[amount_field]).as_("amount"), (journal_acc.name).as_("reference_row"), (journal_acc.reference_name).as_("against_order"), (journal_acc.exchange_rate), @@ -2179,12 +2175,13 @@ def get_advance_journal_entries( else: q = q.where(journal_acc.debit_in_account_currency > 0) + if include_unallocated: + q = q.where((journal_acc.reference_name.isnull()) | (journal_acc.reference_name == "")) + if order_list: - q = q.where(journal_acc.reference_type == order_doctype) - if include_unallocated: - q = q.where(journal_acc.reference_name.isin(order_list) | (journal_acc.reference_name == "")) - else: - q = q.where(journal_acc.reference_name.isin(order_list)) + q = q.where( + (journal_acc.reference_type == order_doctype) & ((journal_acc.reference).isin(order_list)) + ) q = q.orderby(journal_entry.posting_date) @@ -2222,15 +2219,14 @@ def get_advance_payment_entries( (payment_ref.allocated_amount).as_("amount"), (payment_ref.name).as_("reference_row"), (payment_ref.reference_name).as_("against_order"), - payment_ref.reference_doctype == order_doctype, ) + q = q.where(payment_ref.reference_doctype == order_doctype) if order_list: q = q.where(payment_ref.reference_name.isin(order_list)) allocated = list(q.run(as_dict=True)) payment_entries += allocated - if include_unallocated: q = get_common_query( party_type, @@ -2244,7 +2240,6 @@ def get_advance_payment_entries( unallocated = list(q.run(as_dict=True)) payment_entries += unallocated - return payment_entries From 6d121ae6e42b678a2879e6278e58e3441b2787d7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Jun 2023 13:03:09 +0530 Subject: [PATCH 15/40] chore: fix typo --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 72c93261aa..02fef4d31b 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2180,7 +2180,7 @@ def get_advance_journal_entries( if order_list: q = q.where( - (journal_acc.reference_type == order_doctype) & ((journal_acc.reference).isin(order_list)) + (journal_acc.reference_type == order_doctype) & ((journal_acc.reference_type).isin(order_list)) ) q = q.orderby(journal_entry.posting_date) From d81d6069fb8688223fd5e777c781347990100aa7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Jun 2023 18:28:16 +0530 Subject: [PATCH 16/40] fix: JV query --- erpnext/accounts/utils.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index d9561ad468..f92e2c7cba 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -502,24 +502,19 @@ def check_if_advance_entry_modified(args): frappe.qb.from_(journal_entry) .inner_join(journal_acc) .on(journal_entry.name == journal_acc.parent) - ) - - if args.get("dr_or_cr") == "debit_in_account_currency": - q = q.select(journal_acc.debit_in_account_currency) - else: - q = q.select(journal_acc.credit_in_account_currency) - - q = q.where( - (journal_acc.account == args.get("account")) - & ((journal_acc.party_type == args.get("party_type"))) - & ((journal_acc.party == args.get("party"))) - & ( - (journal_acc.reference_type == None) - | (journal_acc.reference_type.isin(["", "Sales Order", "Purchase Order"])) + .select(journal_acc[args.get("dr_or_cr")]) + .where( + (journal_acc.account == args.get("account")) + & ((journal_acc.party_type == args.get("party_type"))) + & ((journal_acc.party == args.get("party"))) + & ( + (journal_acc.reference_type.isnull()) + | (journal_acc.reference_type.isin(["", "Sales Order", "Purchase Order"])) + ) + & ((journal_entry.name == args.get("voucher_no"))) + & ((journal_acc.name == args.get("voucher_detail_no"))) + & ((journal_entry.docstatus == 1)) ) - & ((journal_entry.name == args.get("voucher_no"))) - & ((journal_acc.name == args.get("voucher_detail_no"))) - & ((journal_entry.docstatus == 1)) ) else: From b101dceb2a047731b8bd9931639d3ece6b66087e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Jun 2023 19:38:33 +0530 Subject: [PATCH 17/40] test: GL Entry order --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4cf3abaad1..6599e3960a 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3252,9 +3252,9 @@ class TestSalesInvoice(unittest.TestCase): si.submit() expected_gle = [ - ["_Test Receivable USD - _TC", 7500.0, 500], ["Exchange Gain/Loss - _TC", 500.0, 0.0], ["Sales - _TC", 0.0, 7500.0], + ["_Test Receivable USD - _TC", 7500.0, 500], ] check_gl_entries(self, si.name, expected_gle, nowdate()) From 11a9bd523dd33430e18916e527abdbbc7a89d09f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Jun 2023 20:26:12 +0530 Subject: [PATCH 18/40] test: Add posting date parameter --- .../accounts/doctype/sales_invoice/test_sales_invoice.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 6599e3960a..a5280bcbf4 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3252,9 +3252,10 @@ class TestSalesInvoice(unittest.TestCase): si.submit() expected_gle = [ - ["Exchange Gain/Loss - _TC", 500.0, 0.0], - ["Sales - _TC", 0.0, 7500.0], - ["_Test Receivable USD - _TC", 7500.0, 500], + ["_Test Receivable USD - _TC", 7500.0, 0.0, nowdate()], + ["_Test Receivable USD - _TC", 0.0, 500.0, nowdate()], + ["Exchange Gain/Loss - _TC", 500.0, 0.0, nowdate()], + ["Sales - _TC", 0.0, 7500.0, nowdate()], ] check_gl_entries(self, si.name, expected_gle, nowdate()) From 05c2198569f88785432c4ca6fee49fa04e268b03 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Jun 2023 21:08:58 +0530 Subject: [PATCH 19/40] test: Update order --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index a5280bcbf4..e8e81fd82f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3252,9 +3252,9 @@ class TestSalesInvoice(unittest.TestCase): si.submit() expected_gle = [ + ["_Test Exchange Gain/Loss - _TC", 500.0, 0.0, nowdate()], ["_Test Receivable USD - _TC", 7500.0, 0.0, nowdate()], ["_Test Receivable USD - _TC", 0.0, 500.0, nowdate()], - ["Exchange Gain/Loss - _TC", 500.0, 0.0, nowdate()], ["Sales - _TC", 0.0, 7500.0, nowdate()], ] From da6bc1a13eb0effeb3f5e307d91ed6380e6dd823 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 23 Jun 2023 20:57:51 +0530 Subject: [PATCH 20/40] refactor: Redo workflows --- .../doctype/payment_entry/payment_entry.json | 12 +- .../doctype/payment_entry/payment_entry.py | 108 +++++++------ .../payment_reconciliation.py | 15 +- .../purchase_invoice/purchase_invoice.py | 14 +- .../purchase_invoice/test_purchase_invoice.py | 17 +- .../doctype/sales_invoice/sales_invoice.py | 16 +- .../sales_invoice/test_sales_invoice.py | 17 +- erpnext/accounts/general_ledger.py | 10 +- erpnext/accounts/utils.py | 4 +- erpnext/buying/doctype/supplier/supplier.js | 3 +- erpnext/controllers/accounts_controller.py | 147 +++++++++--------- erpnext/selling/doctype/customer/customer.js | 3 +- erpnext/setup/doctype/company/company.js | 4 +- erpnext/setup/doctype/company/company.json | 22 +-- .../doctype/customer_group/customer_group.js | 1 + .../doctype/supplier_group/supplier_group.js | 1 + 16 files changed, 208 insertions(+), 186 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 6224d4038d..d7b6a198df 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -19,6 +19,7 @@ "party_type", "party", "party_name", + "book_advance_payments_in_separate_party_account", "column_break_11", "bank_account", "party_bank_account", @@ -735,12 +736,21 @@ "fieldname": "get_outstanding_orders", "fieldtype": "Button", "label": "Get Outstanding Orders" + }, + { + "default": "0", + "fetch_from": "company.book_advance_payments_in_separate_party_account", + "fieldname": "book_advance_payments_in_separate_party_account", + "fieldtype": "Check", + "hidden": 1, + "label": "Book Advance Payments in Separate Party Account", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-06-19 11:38:04.387219", + "modified": "2023-06-23 18:07:38.023010", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f6c6bce5bc..8141d0519e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -21,7 +21,11 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_ban from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, ) -from erpnext.accounts.general_ledger import make_gl_entries, process_gl_map +from erpnext.accounts.general_ledger import ( + make_gl_entries, + make_reverse_gl_entries, + process_gl_map, +) from erpnext.accounts.party import get_party_account from erpnext.accounts.utils import get_account_currency, get_balance_on, get_outstanding_invoices from erpnext.controllers.accounts_controller import ( @@ -88,17 +92,14 @@ class PaymentEntry(AccountsController): if self.difference_amount: frappe.throw(_("Difference Amount must be zero")) self.make_gl_entries() + self.make_advance_gl_entries() self.update_outstanding_amounts() self.update_advance_paid() self.update_payment_schedule() self.set_status() def set_liability_account(self): - book_advance_payments_as_liability = frappe.get_value( - "Company", {"company_name": self.company}, "book_advance_payments_as_liability" - ) - - if not book_advance_payments_as_liability: + if not self.book_advance_payments_in_separate_party_account: return account_type = frappe.get_value( @@ -139,6 +140,7 @@ class PaymentEntry(AccountsController): "Repost Payment Ledger Items", ) self.make_gl_entries(cancel=1) + self.make_advance_gl_entries(cancel=1) self.update_outstanding_amounts() self.update_advance_paid() self.delink_advance_entry_references() @@ -212,7 +214,8 @@ class PaymentEntry(AccountsController): "party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to, "get_outstanding_invoices": True, "get_orders_to_be_billed": True, - } + }, + validate=True, ) # Group latest_references by (voucher_type, voucher_no) @@ -417,6 +420,13 @@ class PaymentEntry(AccountsController): elif self.party_type == "Employee": ref_party_account = ref_doc.payable_account + if ref_party_account != self.party_account: + frappe.throw( + _("{0} {1} is associated with {2}, but Party Account is {3}").format( + d.reference_doctype, d.reference_name, ref_party_account, self.party_account + ) + ) + if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"): frappe.throw( _("{0} {1} is on hold").format(d.reference_doctype, d.reference_name), @@ -952,7 +962,7 @@ class PaymentEntry(AccountsController): else: against_account = self.paid_from - party_dict = self.get_gl_dict( + party_gl_dict = self.get_gl_dict( { "account": self.party_account, "party_type": self.party_type, @@ -968,32 +978,21 @@ class PaymentEntry(AccountsController): "credit" if erpnext.get_party_account_type(self.party_type) == "Receivable" else "debit" ) - is_advance = self.is_advance_entry() - for d in self.get("references"): - gle = party_dict.copy() - book_advance_payments_as_liability = frappe.get_value( - "Company", {"company_name": self.company}, "book_advance_payments_as_liability" - ) - if ( - d.reference_doctype in ["Sales Invoice", "Purchase Invoice"] - and book_advance_payments_as_liability - and is_advance - ): - self.make_invoice_liability_entry(gl_entries, d) - against_voucher_type = "Payment Entry" - against_voucher = self.name - else: - against_voucher_type = d.reference_doctype - against_voucher = d.reference_name + cost_center = self.cost_center + if d.reference_doctype == "Sales Invoice" and not cost_center: + cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center") + + gle = party_gl_dict.copy() allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d) gle.update( { dr_or_cr: allocated_amount_in_company_currency, dr_or_cr + "_in_account_currency": d.allocated_amount, - "against_voucher_type": against_voucher_type, - "against_voucher": against_voucher, + "against_voucher_type": d.reference_doctype, + "against_voucher": d.reference_name, + "cost_center": cost_center, } ) gl_entries.append(gle) @@ -1002,25 +1001,44 @@ class PaymentEntry(AccountsController): exchange_rate = self.get_exchange_rate() base_unallocated_amount = self.unallocated_amount * exchange_rate - gle = party_dict.copy() + gle = party_gl_dict.copy() gle.update( { dr_or_cr + "_in_account_currency": self.unallocated_amount, dr_or_cr: base_unallocated_amount, - "against_voucher_type": "Payment Entry", - "against_voucher": self.name, } ) gl_entries.append(gle) - def is_advance_entry(self): - for d in self.get("references"): - if d.reference_doctype in ("Sales Order", "Purchase Order"): - return True - if self.unallocated_amount > 0: - return True - return False + def make_advance_gl_entries(self, against_voucher_type=None, against_voucher=None, cancel=0): + if self.book_advance_payments_in_separate_party_account: + gl_entries = [] + for d in self.get("references"): + if d.reference_doctype in ("Sales Invoice", "Purchase Invoice"): + if not (against_voucher_type and against_voucher) or ( + d.reference_doctype == against_voucher_type and d.reference_name == against_voucher + ): + self.make_invoice_liability_entry(gl_entries, d) + + if cancel: + for entry in gl_entries: + frappe.db.set_value( + "GL Entry", + { + "voucher_no": self.name, + "voucher_type": self.doctype, + "voucher_detail_no": entry.voucher_detail_no, + "against_voucher_type": entry.against_voucher_type, + "against_voucher": entry.against_voucher, + }, + "is_cancelled", + 1, + ) + + make_reverse_gl_entries(gl_entries=gl_entries, partial_cancel=True) + else: + make_gl_entries(gl_entries) def make_invoice_liability_entry(self, gl_entries, invoice): args_dict = { @@ -1030,6 +1048,7 @@ class PaymentEntry(AccountsController): "cost_center": self.cost_center, "voucher_type": "Payment Entry", "voucher_no": self.name, + "voucher_detail_no": invoice.name, } dr_or_cr = "credit" if invoice.reference_doctype == "Sales Invoice" else "debit" @@ -1391,7 +1410,7 @@ def validate_inclusive_tax(tax, doc): @frappe.whitelist() -def get_outstanding_reference_documents(args): +def get_outstanding_reference_documents(args, validate=False): if isinstance(args, str): args = json.loads(args) @@ -1511,13 +1530,14 @@ def get_outstanding_reference_documents(args): elif args.get("get_orders_to_be_billed"): ref_document_type = "orders" - frappe.msgprint( - _( - "No outstanding {0} found for the {1} {2} which qualify the filters you have specified." - ).format( - ref_document_type, _(args.get("party_type")).lower(), frappe.bold(args.get("party")) + if not validate: + frappe.msgprint( + _( + "No outstanding {0} found for the {1} {2} which qualify the filters you have specified." + ).format( + ref_document_type, _(args.get("party_type")).lower(), frappe.bold(args.get("party")) + ) ) - ) return data diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index e9cc3902d3..8e2f0e5232 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -12,16 +12,12 @@ import erpnext from erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation import ( is_any_doc_running, ) -from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.utils import ( QueryPaymentLedger, get_outstanding_invoices, reconcile_against_document, ) -from erpnext.controllers.accounts_controller import ( - get_advance_payment_entries, - make_advance_liability_entry, -) +from erpnext.controllers.accounts_controller import get_advance_payment_entries class PaymentReconciliation(Document): @@ -355,15 +351,6 @@ class PaymentReconciliation(Document): for row in self.get("allocation"): reconciled_entry = [] if row.invoice_number and row.allocated_amount: - if ( - row.invoice_type in ["Sales Invoice", "Purchase Invoice"] - and row.reference_type == "Payment Entry" - ): - gl_entries = [] - make_advance_liability_entry( - gl_entries, row.reference_name, row.allocated_amount, row.invoice_number, self.party_type - ) - make_gl_entries(gl_entries) if row.reference_type in ["Sales Invoice", "Purchase Invoice"]: reconciled_entry = dr_or_cr_notes else: diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 68fa7bf1c9..230a8b3c58 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -33,10 +33,7 @@ from erpnext.accounts.utils import get_account_currency, get_fiscal_year from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.buying.utils import check_on_hold_or_closed_status -from erpnext.controllers.accounts_controller import ( - check_advance_liability_entry, - validate_account_head, -) +from erpnext.controllers.accounts_controller import validate_account_head from erpnext.controllers.buying_controller import BuyingController from erpnext.stock import get_warehouse_account_map from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( @@ -576,15 +573,6 @@ class PurchaseInvoice(BuyingController): gl_entries = [] self.make_supplier_gl_entry(gl_entries) - - check_advance_liability_entry( - gl_entries, - company=self.company, - advances=self.advances, - invoice=self.name, - party_type="Supplier", - ) - self.make_item_gl_entries(gl_entries) self.make_precision_loss_gl_entry(gl_entries) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 569a48acad..8c96480478 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1664,10 +1664,17 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): self.assertTrue(return_pi.docstatus == 1) - def test_advance_entries_as_liability(self): + def test_advance_entries_as_asset(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry - set_advance_flag(company="_Test Company", flag=1, default_account="Debtors - _TC") + account = create_account( + parent_account="Current Assets - _TC", + account_name="Advances Paid", + company="_Test Company", + account_type="Receivable", + ) + + set_advance_flag(company="_Test Company", flag=1, default_account=account) pe = create_payment_entry( company="_Test Company", @@ -1701,13 +1708,15 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): # Check GL Entry against payment doctype expected_gle = [ + ["Advances Paid - _TC", 0.0, 500, nowdate()], ["Cash - _TC", 0.0, 500, nowdate()], ["Creditors - _TC", 500, 0.0, nowdate()], ["Creditors - _TC", 500, 0.0, nowdate()], - ["Debtors - _TC", 0.0, 500, nowdate()], ] check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry") + + pi.load_from_db() self.assertEqual(pi.outstanding_amount, 500) set_advance_flag(company="_Test Company", flag=0, default_account="") @@ -1733,7 +1742,7 @@ def set_advance_flag(company, flag, default_account): "Company", company, { - "book_advance_payments_as_liability": flag, + "book_advance_payments_in_separate_party_account": flag, "default_advance_paid_account": default_account, }, ) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index d2cd95fe9e..25553cff0c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -32,10 +32,7 @@ from erpnext.assets.doctype.asset.depreciation import ( reset_depreciation_schedule, reverse_depreciation_entry_made_after_disposal, ) -from erpnext.controllers.accounts_controller import ( - check_advance_liability_entry, - validate_account_head, -) +from erpnext.controllers.accounts_controller import validate_account_head from erpnext.controllers.selling_controller import SellingController from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data from erpnext.setup.doctype.company.company import update_company_current_month_sales @@ -1055,21 +1052,12 @@ class SalesInvoice(SellingController): elif self.docstatus == 2 and cint(self.update_stock) and cint(auto_accounting_for_stock): make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) - def get_gl_entries(self, warehouse_account=None): + def get_gl_entries(self): from erpnext.accounts.general_ledger import merge_similar_entries gl_entries = [] self.make_customer_gl_entry(gl_entries) - - check_advance_liability_entry( - gl_entries, - company=self.company, - advances=self.advances, - invoice=self.name, - party_type="Customer", - ) - self.make_tax_gl_entries(gl_entries) self.make_exchange_gain_loss_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index e8e81fd82f..69ddfaac8d 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -6,7 +6,6 @@ import unittest import frappe from frappe.model.dynamic_links import get_dynamic_link_map -from frappe.model.naming import make_autoname from frappe.tests.utils import change_settings from frappe.utils import add_days, flt, getdate, nowdate, today @@ -35,7 +34,6 @@ from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle get_serial_nos_from_bundle, make_serial_batch_bundle, ) -from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError from erpnext.stock.doctype.stock_entry.test_stock_entry import ( get_qty_after_transaction, make_stock_entry, @@ -3314,7 +3312,14 @@ class TestSalesInvoice(unittest.TestCase): def test_advance_entries_as_liability(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry - set_advance_flag(company="_Test Company", flag=1, default_account="Creditors - _TC") + account = create_account( + parent_account="Current Liabilities - _TC", + account_name="Advances Received", + company="_Test Company", + account_type="Receivable", + ) + + set_advance_flag(company="_Test Company", flag=1, default_account=account) pe = create_payment_entry( company="_Test Company", @@ -3347,13 +3352,15 @@ class TestSalesInvoice(unittest.TestCase): # Check GL Entry against payment doctype expected_gle = [ + ["Advances Received - _TC", 500, 0.0, nowdate()], ["Cash - _TC", 1000, 0.0, nowdate()], - ["Creditors - _TC", 500, 0.0, nowdate()], ["Debtors - _TC", 0.0, 1000, nowdate()], ["Debtors - _TC", 0.0, 500, nowdate()], ] check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry") + + si.load_from_db() self.assertEqual(si.outstanding_amount, 0) set_advance_flag(company="_Test Company", flag=0, default_account="") @@ -3364,7 +3371,7 @@ def set_advance_flag(company, flag, default_account): "Company", company, { - "book_advance_payments_as_liability": flag, + "book_advance_payments_in_separate_party_account": flag, "default_advance_received_account": default_account, }, ) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index a0954a955a..22f39596e6 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -501,7 +501,12 @@ def get_round_off_account_and_cost_center( def make_reverse_gl_entries( - gl_entries=None, voucher_type=None, voucher_no=None, adv_adj=False, update_outstanding="Yes" + gl_entries=None, + voucher_type=None, + voucher_no=None, + adv_adj=False, + update_outstanding="Yes", + partial_cancel=False, ): """ Get original gl entries of the voucher @@ -528,7 +533,8 @@ def make_reverse_gl_entries( is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries) validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"]) - set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"]) + if not partial_cancel: + set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"]) for entry in gl_entries: new_gle = copy.deepcopy(entry) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index f92e2c7cba..5c256b730f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -474,6 +474,7 @@ def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # n doc = frappe.get_doc(entry.voucher_type, entry.voucher_no) gl_map = doc.build_gl_map() create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1) + doc.make_advance_gl_entries(entry.against_voucher_type, entry.against_voucher) # Only update outstanding for newly linked vouchers for entry in entries: @@ -731,8 +732,9 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no): for pe in linked_pe: try: - pe_doc = frappe.get_doc("Payment Entry", pe) + pe_doc = frappe.get_doc("Payment Entry", pe, cache=True) pe_doc.set_amounts() + pe_doc.make_advance_gl_entries(against_voucher_type=ref_type, against_voucher=ref_no, cancel=1) pe_doc.clear_unallocated_reference_document_rows() pe_doc.validate_payment_type_with_outstanding() except Exception as e: diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js index a5bf4b246b..c9ac279722 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -22,7 +22,8 @@ frappe.ui.form.on("Supplier", { let d = locals[cdt][cdn]; return { filters: { - "account_type": "Receivable", + "account_type": "Payable", + "root_type": "Asset", "company": d.company, "is_group": 0 } diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 02fef4d31b..0d9ab6498d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -756,6 +756,7 @@ class AccountsController(TransactionBase): "party": None, "project": self.get("project"), "post_net_value": args.get("post_net_value"), + "voucher_detail_no": args.get("voucher_detail_no"), } ) @@ -2915,82 +2916,82 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.create_stock_reservation_entries() -def make_advance_liability_entry( - gl_entries, pe, allocated_amount, invoice, party_type, references=False -): - pe = frappe.get_doc("Payment Entry", pe) - if party_type == "Customer": - invoice = frappe.get_doc("Sales Invoice", invoice) - account = pe.paid_from - dr_or_cr = "debit" - rev = "credit" - against = invoice.debit_to - party = invoice.customer - else: - invoice = frappe.get_doc("Purchase Invoice", invoice) - account = pe.paid_to - dr_or_cr = "credit" - rev = "debit" - against = invoice.credit_to - party = invoice.supplier - gl_entries.append( - pe.get_gl_dict( - { - "account": account, - "party_type": party_type, - "party": party, - "due_date": invoice.due_date, - "against": against, - dr_or_cr: allocated_amount, - dr_or_cr + "_in_account_currency": allocated_amount, - rev: 0, - rev + "_in_account_currency": 0, - "cost_center": invoice.cost_center, - "project": invoice.project, - "against_voucher_type": "Payment Entry", - "against_voucher": pe.name, - }, - invoice.party_account_currency, - item=pe, - ) - ) +# def make_advance_liability_entry( +# gl_entries, pe, allocated_amount, invoice, party_type +# ): +# pe = frappe.get_doc("Payment Entry", pe) +# if party_type == "Customer": +# invoice = frappe.get_doc("Sales Invoice", invoice) +# account = pe.paid_from +# dr_or_cr = "debit" +# rev = "credit" +# against = invoice.debit_to +# party = invoice.customer +# else: +# invoice = frappe.get_doc("Purchase Invoice", invoice) +# account = pe.paid_to +# dr_or_cr = "credit" +# rev = "debit" +# against = invoice.credit_to +# party = invoice.supplier +# gl_entries.append( +# pe.get_gl_dict( +# { +# "account": account, +# "party_type": party_type, +# "party": party, +# "due_date": invoice.due_date, +# "against": against, +# dr_or_cr: allocated_amount, +# dr_or_cr + "_in_account_currency": allocated_amount, +# rev: 0, +# rev + "_in_account_currency": 0, +# "cost_center": invoice.cost_center, +# "project": invoice.project, +# "against_voucher_type": "Payment Entry", +# "against_voucher": pe.name, +# }, +# invoice.party_account_currency, +# item=pe, +# ) +# ) - (dr_or_cr, rev) = ("credit", "debit") if party_type == "Customer" else ("debit", "credit") - gl_entries.append( - pe.get_gl_dict( - { - "account": against, - "party_type": party_type, - "party": party, - "due_date": invoice.due_date, - dr_or_cr: allocated_amount, - dr_or_cr + "_in_account_currency": allocated_amount, - rev: 0, - rev + "_in_account_currency": 0, - "cost_center": invoice.cost_center, - "project": invoice.project, - "against_voucher_type": invoice.doctype, - "against_voucher": invoice.name, - }, - invoice.party_account_currency, - item=pe, - ) - ) +# (dr_or_cr, rev) = ("credit", "debit") if party_type == "Customer" else ("debit", "credit") +# gl_entries.append( +# pe.get_gl_dict( +# { +# "account": against, +# "party_type": party_type, +# "party": party, +# "due_date": invoice.due_date, +# dr_or_cr: allocated_amount, +# dr_or_cr + "_in_account_currency": allocated_amount, +# rev: 0, +# rev + "_in_account_currency": 0, +# "cost_center": invoice.cost_center, +# "project": invoice.project, +# "against_voucher_type": invoice.doctype, +# "against_voucher": invoice.name, +# }, +# invoice.party_account_currency, +# item=pe, +# ) +# ) -def check_advance_liability_entry(gl_entries, company, advances, invoice, party_type): - advance_payments_as_liability = frappe.db.get_value( - "Company", {"company_name": company}, "book_advance_payments_as_liability" - ) - if advance_payments_as_liability: - for advance_entry in advances: - make_advance_liability_entry( - gl_entries, - advance_entry.reference_name, - advance_entry.allocated_amount, - invoice=invoice, - party_type=party_type, - ) +# def check_advance_liability_entry(gl_entries, company, advances, invoice, party_type): +# advance_payments_as_liability = frappe.db.get_value( +# "Company", {"company_name": company}, "book_advance_payments_as_liability" +# ) +# if advance_payments_as_liability: +# for advance_entry in advances: +# make_advance_liability_entry( +# gl_entries, +# advance_entry.reference_name, +# advance_entry.allocated_amount, +# invoice=invoice, +# party_type=party_type, +# ) @erpnext.allow_regional diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 408e89b0ef..c3bd753f1b 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -39,7 +39,8 @@ frappe.ui.form.on("Customer", { let d = locals[cdt][cdn]; return { filters: { - "account_type": 'Payable', + "account_type": 'Receivable', + "root_type": "Liability", "company": d.company, "is_group": 0 } diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 089e20d442..333538722e 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -227,8 +227,8 @@ erpnext.company.setup_queries = function(frm) { ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}], ["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}], ["default_provisional_account", {"root_type": ["in", ["Liability", "Asset"]]}], - ["default_advance_received_account", {"account_type": "Payable"}], - ["default_advance_paid_account", {"account_type": "Receivable"}], + ["default_advance_received_account", {"root_type": "Liability", "account_type": "Receivable"}], + ["default_advance_paid_account", {"root_type": "Asset", "account_type": "Payable"}], ], function(i, v) { erpnext.company.set_custom_query(frm, v); }); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 611e2ab221..6292ad7349 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -71,7 +71,7 @@ "cost_center", "default_finance_book", "advance_payments_section", - "book_advance_payments_as_liability", + "book_advance_payments_in_separate_party_account", "column_break_fwcf", "default_advance_received_account", "default_advance_paid_account", @@ -700,20 +700,13 @@ "no_copy": 1, "options": "Account" }, - { - "default": "0", - "description": "Enabling this option will allow you to record -

1. Advances Received in a Liability Account instead of the Receivable Account

2. Advances Paid in an Asset Account instead of the Payable Account", - "fieldname": "book_advance_payments_as_liability", - "fieldtype": "Check", - "label": "Book Advance Payments in Separate Party Account" - }, { "fieldname": "advance_payments_section", "fieldtype": "Section Break", "label": "Advance Payments" }, { - "depends_on": "eval:doc.book_advance_payments_as_liability", + "depends_on": "eval:doc.book_advance_payments_in_separate_party_account", "fieldname": "default_advance_received_account", "fieldtype": "Link", "label": "Default Advance Received Account", @@ -721,7 +714,7 @@ "options": "Account" }, { - "depends_on": "eval:doc.book_advance_payments_as_liability", + "depends_on": "eval:doc.book_advance_payments_in_separate_party_account", "fieldname": "default_advance_paid_account", "fieldtype": "Link", "label": "Default Advance Paid Account", @@ -731,6 +724,13 @@ { "fieldname": "column_break_fwcf", "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Enabling this option will allow you to record -

1. Advances Received in a Liability Account instead of the Asset Account

2. Advances Paid in an Asset Account instead of the Liability Account", + "fieldname": "book_advance_payments_in_separate_party_account", + "fieldtype": "Check", + "label": "Book Advance Payments in Separate Party Account" } ], "icon": "fa fa-building", @@ -738,7 +738,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2023-06-16 13:32:48.790947", + "modified": "2023-06-23 18:22:27.219706", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/setup/doctype/customer_group/customer_group.js b/erpnext/setup/doctype/customer_group/customer_group.js index ed9893346c..49a90f959d 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.js +++ b/erpnext/setup/doctype/customer_group/customer_group.js @@ -41,6 +41,7 @@ frappe.ui.form.on("Customer Group", { return { filters: { "root_type": 'Liability', + "account_type": "Receivable", "company": locals[cdt][cdn].company, "is_group": 0 } diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.js b/erpnext/setup/doctype/supplier_group/supplier_group.js index ac5904f4d2..b2acfd7355 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.js +++ b/erpnext/setup/doctype/supplier_group/supplier_group.js @@ -41,6 +41,7 @@ frappe.ui.form.on("Supplier Group", { return { filters: { "root_type": 'Asset', + "account_type": "Payable", "company": locals[cdt][cdn].company, "is_group": 0 } From 1894dc8197c568936d839ef50fa65879c7ce9ac7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 23 Jun 2023 21:53:34 +0530 Subject: [PATCH 21/40] fix: Test case and code cleanup --- .../payment_reconciliation.js | 1 + .../purchase_invoice_advance.json | 12 +-- .../doctype/sales_invoice/sales_invoice.py | 3 +- .../sales_invoice_advance.json | 12 +-- erpnext/accounts/utils.py | 6 +- erpnext/controllers/accounts_controller.py | 78 ------------------- 6 files changed, 11 insertions(+), 101 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index bd931f1a2b..16e3f95ee6 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -34,6 +34,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo filters: { "company": this.frm.doc.company, "is_group": 0, + "account_type": this.frm.doc.party_type == 'Customer' ? "Receivable": "Payable", "root_type": this.frm.doc.party_type == 'Customer' ? "Liability": "Asset" } }; diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json index 9082115f23..4db531eac9 100644 --- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json +++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json @@ -14,8 +14,7 @@ "advance_amount", "allocated_amount", "exchange_gain_loss", - "ref_exchange_rate", - "account" + "ref_exchange_rate" ], "fields": [ { @@ -112,20 +111,13 @@ "label": "Reference Exchange Rate", "non_negative": 1, "read_only": 1 - }, - { - "fieldname": "account", - "fieldtype": "Link", - "label": "Account", - "options": "Account", - "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-06-01 16:56:48.530169", + "modified": "2023-06-23 21:13:18.013816", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Advance", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 25553cff0c..7ab1c89397 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1052,12 +1052,13 @@ class SalesInvoice(SellingController): elif self.docstatus == 2 and cint(self.update_stock) and cint(auto_accounting_for_stock): make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) - def get_gl_entries(self): + def get_gl_entries(self, warehouse_account=None): from erpnext.accounts.general_ledger import merge_similar_entries gl_entries = [] self.make_customer_gl_entry(gl_entries) + self.make_tax_gl_entries(gl_entries) self.make_exchange_gain_loss_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json index aa52b1cac2..0ae85d9000 100644 --- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json +++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json @@ -14,8 +14,7 @@ "advance_amount", "allocated_amount", "exchange_gain_loss", - "ref_exchange_rate", - "account" + "ref_exchange_rate" ], "fields": [ { @@ -113,20 +112,13 @@ "label": "Reference Exchange Rate", "non_negative": 1, "read_only": 1 - }, - { - "fieldname": "account", - "fieldtype": "Link", - "label": "Account", - "options": "Account", - "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-05-31 11:47:00.191681", + "modified": "2023-06-23 21:12:57.557731", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Advance", diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 5c256b730f..046711532f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -474,7 +474,9 @@ def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # n doc = frappe.get_doc(entry.voucher_type, entry.voucher_no) gl_map = doc.build_gl_map() create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1) - doc.make_advance_gl_entries(entry.against_voucher_type, entry.against_voucher) + + if voucher_type == "Payment Entry": + doc.make_advance_gl_entries(entry.against_voucher_type, entry.against_voucher) # Only update outstanding for newly linked vouchers for entry in entries: @@ -732,7 +734,7 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no): for pe in linked_pe: try: - pe_doc = frappe.get_doc("Payment Entry", pe, cache=True) + pe_doc = frappe.get_doc("Payment Entry", pe) pe_doc.set_amounts() pe_doc.make_advance_gl_entries(against_voucher_type=ref_type, against_voucher=ref_no, cancel=1) pe_doc.clear_unallocated_reference_document_rows() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 0d9ab6498d..4193b5327d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2916,84 +2916,6 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.create_stock_reservation_entries() -# def make_advance_liability_entry( -# gl_entries, pe, allocated_amount, invoice, party_type -# ): -# pe = frappe.get_doc("Payment Entry", pe) -# if party_type == "Customer": -# invoice = frappe.get_doc("Sales Invoice", invoice) -# account = pe.paid_from -# dr_or_cr = "debit" -# rev = "credit" -# against = invoice.debit_to -# party = invoice.customer -# else: -# invoice = frappe.get_doc("Purchase Invoice", invoice) -# account = pe.paid_to -# dr_or_cr = "credit" -# rev = "debit" -# against = invoice.credit_to -# party = invoice.supplier -# gl_entries.append( -# pe.get_gl_dict( -# { -# "account": account, -# "party_type": party_type, -# "party": party, -# "due_date": invoice.due_date, -# "against": against, -# dr_or_cr: allocated_amount, -# dr_or_cr + "_in_account_currency": allocated_amount, -# rev: 0, -# rev + "_in_account_currency": 0, -# "cost_center": invoice.cost_center, -# "project": invoice.project, -# "against_voucher_type": "Payment Entry", -# "against_voucher": pe.name, -# }, -# invoice.party_account_currency, -# item=pe, -# ) -# ) - -# (dr_or_cr, rev) = ("credit", "debit") if party_type == "Customer" else ("debit", "credit") -# gl_entries.append( -# pe.get_gl_dict( -# { -# "account": against, -# "party_type": party_type, -# "party": party, -# "due_date": invoice.due_date, -# dr_or_cr: allocated_amount, -# dr_or_cr + "_in_account_currency": allocated_amount, -# rev: 0, -# rev + "_in_account_currency": 0, -# "cost_center": invoice.cost_center, -# "project": invoice.project, -# "against_voucher_type": invoice.doctype, -# "against_voucher": invoice.name, -# }, -# invoice.party_account_currency, -# item=pe, -# ) -# ) - - -# def check_advance_liability_entry(gl_entries, company, advances, invoice, party_type): -# advance_payments_as_liability = frappe.db.get_value( -# "Company", {"company_name": company}, "book_advance_payments_as_liability" -# ) -# if advance_payments_as_liability: -# for advance_entry in advances: -# make_advance_liability_entry( -# gl_entries, -# advance_entry.reference_name, -# advance_entry.allocated_amount, -# invoice=invoice, -# party_type=party_type, -# ) - - @erpnext.allow_regional def validate_regional(doc): pass From 62c3ca8286e131d9d4072e9c8d3d298133c755d5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Jun 2023 23:53:55 +0530 Subject: [PATCH 22/40] fix: Paid invoice in AR report --- .../accounts/doctype/payment_entry/payment_entry.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 8141d0519e..cbf8c0865f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -986,12 +986,20 @@ class PaymentEntry(AccountsController): gle = party_gl_dict.copy() allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d) + + if self.book_advance_payments_in_separate_party_account: + against_voucher_type = "Payment Entry" + against_voucher = self.name + else: + against_voucher_type = d.reference_doctype + against_voucher = d.reference_name + gle.update( { dr_or_cr: allocated_amount_in_company_currency, dr_or_cr + "_in_account_currency": d.allocated_amount, - "against_voucher_type": d.reference_doctype, - "against_voucher": d.reference_name, + "against_voucher_type": against_voucher_type, + "against_voucher": against_voucher, "cost_center": cost_center, } ) From f2edc91dc6ce60fae598f537634430a8da7dd453 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Jun 2023 18:11:47 +0530 Subject: [PATCH 23/40] fix: Multi invoice reconciliation --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 7 ++----- erpnext/accounts/utils.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index cbf8c0865f..a0f638246f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1019,15 +1019,12 @@ class PaymentEntry(AccountsController): gl_entries.append(gle) - def make_advance_gl_entries(self, against_voucher_type=None, against_voucher=None, cancel=0): + def make_advance_gl_entries(self, cancel=0): if self.book_advance_payments_in_separate_party_account: gl_entries = [] for d in self.get("references"): if d.reference_doctype in ("Sales Invoice", "Purchase Invoice"): - if not (against_voucher_type and against_voucher) or ( - d.reference_doctype == against_voucher_type and d.reference_name == against_voucher - ): - self.make_invoice_liability_entry(gl_entries, d) + self.make_invoice_liability_entry(gl_entries, d) if cancel: for entry in gl_entries: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 046711532f..1fa0e86c26 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -476,7 +476,7 @@ def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # n create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1) if voucher_type == "Payment Entry": - doc.make_advance_gl_entries(entry.against_voucher_type, entry.against_voucher) + doc.make_advance_gl_entries() # Only update outstanding for newly linked vouchers for entry in entries: From 7312827d4dd06aa89d338b6eb3d63ed4f8d64c8f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Jun 2023 18:25:10 +0530 Subject: [PATCH 24/40] fix: On cancel flow --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index a0f638246f..cbf8c0865f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1019,12 +1019,15 @@ class PaymentEntry(AccountsController): gl_entries.append(gle) - def make_advance_gl_entries(self, cancel=0): + def make_advance_gl_entries(self, against_voucher_type=None, against_voucher=None, cancel=0): if self.book_advance_payments_in_separate_party_account: gl_entries = [] for d in self.get("references"): if d.reference_doctype in ("Sales Invoice", "Purchase Invoice"): - self.make_invoice_liability_entry(gl_entries, d) + if not (against_voucher_type and against_voucher) or ( + d.reference_doctype == against_voucher_type and d.reference_name == against_voucher + ): + self.make_invoice_liability_entry(gl_entries, d) if cancel: for entry in gl_entries: From 1e078d03bb6c8b4abc74254b0daff4edfdcc2c2b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 29 Jun 2023 12:18:25 +0530 Subject: [PATCH 25/40] fix: Partial PLE cancellation --- .../payment_ledger_entry/payment_ledger_entry.json | 8 +++++++- erpnext/accounts/general_ledger.py | 6 +++++- erpnext/accounts/utils.py | 11 ++++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json index 22842cec0f..9cf2ac6c2a 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json @@ -13,6 +13,7 @@ "party_type", "party", "due_date", + "voucher_detail_no", "cost_center", "finance_book", "voucher_type", @@ -142,12 +143,17 @@ "fieldname": "remarks", "fieldtype": "Text", "label": "Remarks" + }, + { + "fieldname": "voucher_detail_no", + "fieldtype": "Data", + "label": "Voucher Detail No" } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2022-08-22 15:32:56.629430", + "modified": "2023-06-29 12:24:20.500632", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Ledger Entry", diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 22f39596e6..f1dad875fa 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -526,7 +526,11 @@ def make_reverse_gl_entries( if gl_entries: create_payment_ledger_entry( - gl_entries, cancel=1, adv_adj=adv_adj, update_outstanding=update_outstanding + gl_entries, + cancel=1, + adv_adj=adv_adj, + update_outstanding=update_outstanding, + partial_cancel=partial_cancel, ) validate_accounting_period(gl_entries) check_freezing_date(gl_entries[0]["posting_date"], adv_adj) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 1fa0e86c26..2a40a3abaf 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1467,6 +1467,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0): due_date=gle.due_date, voucher_type=gle.voucher_type, voucher_no=gle.voucher_no, + voucher_detail_no=gle.voucher_detail_no, against_voucher_type=gle.against_voucher_type if gle.against_voucher_type else gle.voucher_type, @@ -1488,7 +1489,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0): def create_payment_ledger_entry( - gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0 + gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False ): if gl_entries: ple_map = get_payment_ledger_entries(gl_entries, cancel=cancel) @@ -1498,7 +1499,7 @@ def create_payment_ledger_entry( ple = frappe.get_doc(entry) if cancel: - delink_original_entry(ple) + delink_original_entry(ple, partial_cancel=partial_cancel) ple.flags.ignore_permissions = 1 ple.flags.adv_adj = adv_adj @@ -1545,7 +1546,7 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa ref_doc.set_status(update=True) -def delink_original_entry(pl_entry): +def delink_original_entry(pl_entry, partial_cancel=False): if pl_entry: ple = qb.DocType("Payment Ledger Entry") query = ( @@ -1565,6 +1566,10 @@ def delink_original_entry(pl_entry): & (ple.against_voucher_no == pl_entry.against_voucher_no) ) ) + + if partial_cancel: + query = query.where(ple.voucher_detail_no == pl_entry.voucher_detail_no) + query.run() From 904ca746a69e3e53fd8469fadac56d18d5af9e27 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 30 Jun 2023 12:11:23 +0530 Subject: [PATCH 26/40] fix: project filtering in P&L Report --- .../profit_and_loss_statement/profit_and_loss_statement.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js index 1c461efbcd..298d83894c 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js @@ -14,8 +14,10 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "label": __("Project"), "fieldtype": "MultiSelectList", get_data: function(txt) { - return frappe.db.get_link_options('Project', txt); - } + return frappe.db.get_link_options('Project', txt, { + company: frappe.query_report.get_filter_value("company") + }); + }, }, { "fieldname": "include_default_book_entries", From 90a77030a71fc7697f05a274a09bf543682d8023 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 30 Jun 2023 12:34:39 +0530 Subject: [PATCH 27/40] fix: add filter for disabled batch --- .../serial_and_batch_bundle/serial_and_batch_bundle.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 57bb71ef1e..2776a74c2d 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -1312,7 +1312,10 @@ def get_available_batches(kwargs): batch_ledger.warehouse, Sum(batch_ledger.qty).as_("qty"), ) - .where(((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull()))) + .where( + (batch_table.disabled == 0) + & ((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull())) + ) .where(stock_ledger_entry.is_cancelled == 0) .groupby(batch_ledger.batch_no, batch_ledger.warehouse) ) From ce252a0d45918f9cb03626bad9521e92df2be260 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 30 Jun 2023 12:42:19 +0530 Subject: [PATCH 28/40] fix: show projects with no company value set --- .../profit_and_loss_statement/profit_and_loss_statement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js index 298d83894c..6fda7b5220 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js @@ -15,7 +15,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldtype": "MultiSelectList", get_data: function(txt) { return frappe.db.get_link_options('Project', txt, { - company: frappe.query_report.get_filter_value("company") + company: ["in", [frappe.query_report.get_filter_value("company"), ""]], }); }, }, From 84d4888f5fcd8ff98e308b501b63f28d890dd861 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 30 Jun 2023 12:54:45 +0530 Subject: [PATCH 29/40] fix: make company field mandatory in project doctype --- .../profit_and_loss_statement/profit_and_loss_statement.js | 2 +- erpnext/projects/doctype/project/project.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js index 6fda7b5220..298d83894c 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js @@ -15,7 +15,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldtype": "MultiSelectList", get_data: function(txt) { return frappe.db.get_link_options('Project', txt, { - company: ["in", [frappe.query_report.get_filter_value("company"), ""]], + company: frappe.query_report.get_filter_value("company") }); }, }, diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index f007430ab3..502ee57415 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -289,7 +289,8 @@ "fieldtype": "Link", "label": "Company", "options": "Company", - "remember_last_selected_value": 1 + "remember_last_selected_value": 1, + "reqd": 1 }, { "fieldname": "column_break_28", From bbb6ebb84ec5ae2cd360c98ab279e1a3cd0aa202 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Jun 2023 13:25:22 +0530 Subject: [PATCH 30/40] fix: Outstanding amount validation --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 37459e3cf0..c0fd63e9a2 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -420,7 +420,10 @@ class PaymentEntry(AccountsController): elif self.party_type == "Employee": ref_party_account = ref_doc.payable_account - if ref_party_account != self.party_account: + if ( + ref_party_account != self.party_account + and not self.book_advance_payments_in_separate_party_account + ): frappe.throw( _("{0} {1} is associated with {2}, but Party Account is {3}").format( d.reference_doctype, d.reference_name, ref_party_account, self.party_account @@ -1482,7 +1485,7 @@ def get_outstanding_reference_documents(args, validate=False): outstanding_invoices = get_outstanding_invoices( args.get("party_type"), args.get("party"), - args.get("party_account"), + get_party_account(args.get("party_type"), args.get("party"), args.get("company")), common_filter=common_filter, posting_date=posting_and_due_date, min_outstanding=args.get("outstanding_amt_greater_than"), From 0a49213338f6dac2418d5bc789b984293bb1cc84 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Jun 2023 17:32:42 +0530 Subject: [PATCH 31/40] test: Update test records --- erpnext/accounts/doctype/sales_invoice/test_records.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json index 3781f8ccc9..61e5219c80 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_records.json +++ b/erpnext/accounts/doctype/sales_invoice/test_records.json @@ -6,7 +6,7 @@ "cost_center": "_Test Cost Center - _TC", "customer": "_Test Customer", "customer_name": "_Test Customer", - "debit_to": "_Test Receivable - _TC", + "debit_to": "Debtors - _TC", "doctype": "Sales Invoice", "items": [ { @@ -78,7 +78,7 @@ "currency": "INR", "customer": "_Test Customer", "customer_name": "_Test Customer", - "debit_to": "_Test Receivable - _TC", + "debit_to": "Debtors - _TC", "doctype": "Sales Invoice", "cost_center": "_Test Cost Center - _TC", "items": [ @@ -137,7 +137,7 @@ "currency": "INR", "customer": "_Test Customer", "customer_name": "_Test Customer", - "debit_to": "_Test Receivable - _TC", + "debit_to": "Debtors - _TC", "doctype": "Sales Invoice", "cost_center": "_Test Cost Center - _TC", "items": [ @@ -265,7 +265,7 @@ "currency": "INR", "customer": "_Test Customer", "customer_name": "_Test Customer", - "debit_to": "_Test Receivable - _TC", + "debit_to": "Debtors - _TC", "doctype": "Sales Invoice", "cost_center": "_Test Cost Center - _TC", "items": [ From 7e7737d6926ed94b891556b7df4e46b1e87673f6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Jun 2023 18:37:52 +0530 Subject: [PATCH 32/40] test: Update test account --- erpnext/accounts/doctype/finance_book/test_finance_book.py | 2 +- .../accounts/doctype/journal_entry/test_journal_entry.py | 4 ++-- erpnext/accounts/doctype/journal_entry/test_records.json | 4 ++-- .../accounts/doctype/sales_invoice/test_sales_invoice.py | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/finance_book/test_finance_book.py b/erpnext/accounts/doctype/finance_book/test_finance_book.py index 7b2575d2c3..42c0e51238 100644 --- a/erpnext/accounts/doctype/finance_book/test_finance_book.py +++ b/erpnext/accounts/doctype/finance_book/test_finance_book.py @@ -13,7 +13,7 @@ class TestFinanceBook(unittest.TestCase): finance_book = create_finance_book() # create jv entry - jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable - _TC", 100, save=False) + jv = make_journal_entry("_Test Bank - _TC", "Debtors - _TC", 100, save=False) jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer"}) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 73b1911543..e7aca79d08 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -43,7 +43,7 @@ class TestJournalEntry(unittest.TestCase): frappe.db.sql( """select name from `tabJournal Entry Account` where account = %s and docstatus = 1 and parent = %s""", - ("_Test Receivable - _TC", test_voucher.name), + ("Debtors - _TC", test_voucher.name), ) ) @@ -273,7 +273,7 @@ class TestJournalEntry(unittest.TestCase): jv.submit() # create jv in USD, but account currency in INR - jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable - _TC", 100, save=False) + jv = make_journal_entry("_Test Bank - _TC", "Debtors - _TC", 100, save=False) jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer USD"}) diff --git a/erpnext/accounts/doctype/journal_entry/test_records.json b/erpnext/accounts/doctype/journal_entry/test_records.json index 5077305cf2..dafcf56abd 100644 --- a/erpnext/accounts/doctype/journal_entry/test_records.json +++ b/erpnext/accounts/doctype/journal_entry/test_records.json @@ -6,7 +6,7 @@ "doctype": "Journal Entry", "accounts": [ { - "account": "_Test Receivable - _TC", + "account": "Debtors - _TC", "party_type": "Customer", "party": "_Test Customer", "credit_in_account_currency": 400.0, @@ -70,7 +70,7 @@ "doctype": "Journal Entry", "accounts": [ { - "account": "_Test Receivable - _TC", + "account": "Debtors - _TC", "party_type": "Customer", "party": "_Test Customer", "credit_in_account_currency": 0.0, diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 69ddfaac8d..0280c3590c 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1724,7 +1724,7 @@ class TestSalesInvoice(unittest.TestCase): # Party Account currency must be in USD, as there is existing GLE with USD si4 = create_sales_invoice( customer="_Test Customer USD", - debit_to="_Test Receivable - _TC", + debit_to="Debtors - _TC", currency="USD", conversion_rate=50, do_not_submit=True, @@ -1737,7 +1737,7 @@ class TestSalesInvoice(unittest.TestCase): si3.cancel() si5 = create_sales_invoice( customer="_Test Customer USD", - debit_to="_Test Receivable - _TC", + debit_to="Debtors - _TC", currency="USD", conversion_rate=50, do_not_submit=True, @@ -1816,7 +1816,7 @@ class TestSalesInvoice(unittest.TestCase): "reference_date": nowdate(), "received_amount": 300, "paid_amount": 300, - "paid_from": "_Test Receivable - _TC", + "paid_from": "Debtors - _TC", "paid_to": "_Test Cash - _TC", } ) From 80e6c90740d50919806e76183e061247e7c2d08a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Jun 2023 19:35:22 +0530 Subject: [PATCH 33/40] chore: precision in test --- erpnext/accounts/doctype/payment_entry/test_payment_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index ae2625b653..70cc4b3d34 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -932,7 +932,7 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(pe.cost_center, si.cost_center) self.assertEqual(flt(expected_account_balance), account_balance) self.assertEqual(flt(expected_party_balance), party_balance) - self.assertEqual(flt(expected_party_account_balance), party_account_balance) + self.assertEqual(flt(expected_party_account_balance, 2), flt(party_account_balance, 2)) def test_multi_currency_payment_entry_with_taxes(self): payment_entry = create_payment_entry( From d54f52474af27d129fc1bcbb166933bf0fc94fbc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Jun 2023 20:02:46 +0530 Subject: [PATCH 34/40] fix: Expense Account filter in Sales Invoice (#35944) --- .../accounts/doctype/sales_invoice/sales_invoice.js | 13 ------------- erpnext/public/js/controllers/transaction.js | 4 +++- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 68407e0221..8753ebc3ba 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -674,19 +674,6 @@ frappe.ui.form.on('Sales Invoice', { } } - // expense account - frm.fields_dict['items'].grid.get_field('expense_account').get_query = function(doc) { - if (erpnext.is_perpetual_inventory_enabled(doc.company)) { - return { - filters: { - 'report_type': 'Profit and Loss', - 'company': doc.company, - "is_group": 0 - } - } - } - } - // discount account frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) { return { diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 0d92683f21..543d0e9790 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -193,7 +193,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.frm.set_query("expense_account", "items", function(doc) { return { filters: { - "company": doc.company + "company": doc.company, + "report_type": "Profit and Loss", + "is_group": 0 } }; }); From b77a8089217a6b1e552622b0fed5b3fac23aca94 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 1 Jul 2023 11:30:46 +0530 Subject: [PATCH 35/40] fix: reposting has not changed valuation rate (cherry picked from commit c0c693d8b0faf42089314cf43f4fc117f45de4b4) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py --- .../purchase_receipt/test_purchase_receipt.py | 117 ++++++++++++++++++ erpnext/stock/stock_ledger.py | 6 + 2 files changed, 123 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index c6c84cadc8..76ba738a99 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1834,6 +1834,7 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(abs(data["stock_value_difference"]), 400.00) +<<<<<<< HEAD def test_return_from_rejected_warehouse(self): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_return_against_rejected_warehouse, @@ -1860,6 +1861,122 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(pr_return.items[0].qty, 2.0 * -1) self.assertEqual(pr_return.items[0].rejected_qty, 0.0) self.assertEqual(pr_return.items[0].rejected_warehouse, "") +======= + def test_purchase_receipt_with_backdated_landed_cost_voucher(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( + create_landed_cost_voucher, + ) + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item_code = "_Test Purchase Item With Landed Cost" + create_item(item_code) + + warehouse = create_warehouse("_Test Purchase Warehouse With Landed Cost") + warehouse1 = create_warehouse("_Test Purchase Warehouse With Landed Cost 1") + warehouse2 = create_warehouse("_Test Purchase Warehouse With Landed Cost 2") + warehouse3 = create_warehouse("_Test Purchase Warehouse With Landed Cost 3") + + pr = make_purchase_receipt( + item_code=item_code, + warehouse=warehouse, + posting_date=add_days(today(), -10), + posting_time="10:59:59", + qty=100, + rate=275.00, + ) + + pr_return = make_return_doc("Purchase Receipt", pr.name) + pr_return.posting_date = add_days(today(), -9) + pr_return.items[0].qty = 2 * -1 + pr_return.items[0].received_qty = 2 * -1 + pr_return.submit() + + ste1 = make_stock_entry( + purpose="Material Transfer", + posting_date=add_days(today(), -8), + source=warehouse, + target=warehouse1, + item_code=item_code, + qty=20, + company=pr.company, + ) + + ste1.reload() + self.assertEqual(ste1.items[0].valuation_rate, 275.00) + + ste2 = make_stock_entry( + purpose="Material Transfer", + posting_date=add_days(today(), -7), + source=warehouse, + target=warehouse2, + item_code=item_code, + qty=20, + company=pr.company, + ) + + ste2.reload() + self.assertEqual(ste2.items[0].valuation_rate, 275.00) + + ste3 = make_stock_entry( + purpose="Material Transfer", + posting_date=add_days(today(), -6), + source=warehouse, + target=warehouse3, + item_code=item_code, + qty=20, + company=pr.company, + ) + + ste3.reload() + self.assertEqual(ste3.items[0].valuation_rate, 275.00) + + ste4 = make_stock_entry( + purpose="Material Transfer", + posting_date=add_days(today(), -5), + source=warehouse1, + target=warehouse, + item_code=item_code, + qty=20, + company=pr.company, + ) + + ste4.reload() + self.assertEqual(ste4.items[0].valuation_rate, 275.00) + + ste5 = make_stock_entry( + purpose="Material Transfer", + posting_date=add_days(today(), -4), + source=warehouse, + target=warehouse1, + item_code=item_code, + qty=20, + company=pr.company, + ) + + ste5.reload() + self.assertEqual(ste5.items[0].valuation_rate, 275.00) + + create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=2500 * -1) + + pr.reload() + valuation_rate = pr.items[0].valuation_rate + + ste1.reload() + self.assertEqual(ste1.items[0].valuation_rate, valuation_rate) + + ste2.reload() + self.assertEqual(ste2.items[0].valuation_rate, valuation_rate) + + ste3.reload() + self.assertEqual(ste3.items[0].valuation_rate, valuation_rate) + + ste4.reload() + self.assertEqual(ste4.items[0].valuation_rate, valuation_rate) + + ste5.reload() + self.assertEqual(ste5.items[0].valuation_rate, valuation_rate) +>>>>>>> c0c693d8b0 (fix: reposting has not changed valuation rate) def prepare_data_for_internal_transfer(): diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index b3ed220680..7b1eae5545 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -646,6 +646,7 @@ class update_entries_after(object): def update_distinct_item_warehouses(self, dependant_sle): key = (dependant_sle.item_code, dependant_sle.warehouse) val = frappe._dict({"sle": dependant_sle}) + if key not in self.distinct_item_warehouses: self.distinct_item_warehouses[key] = val self.new_items_found = True @@ -657,6 +658,9 @@ class update_entries_after(object): val.sle_changed = True self.distinct_item_warehouses[key] = val self.new_items_found = True + elif self.distinct_item_warehouses[key].get("reposting_status"): + self.distinct_item_warehouses[key] = val + self.new_items_found = True def process_sle(self, sle): # previous sle data for this warehouse @@ -1362,6 +1366,8 @@ def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None): [ "item_code", "warehouse", + "actual_qty", + "qty_after_transaction", "posting_date", "posting_time", "timestamp(posting_date, posting_time) as timestamp", From 232dfad13a49d47f828a907ec8799979ebf97d21 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 2 Jul 2023 10:45:12 +0530 Subject: [PATCH 36/40] fix: conflicts --- .../stock/doctype/purchase_receipt/test_purchase_receipt.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 76ba738a99..07d6e86795 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1834,7 +1834,6 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(abs(data["stock_value_difference"]), 400.00) -<<<<<<< HEAD def test_return_from_rejected_warehouse(self): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_return_against_rejected_warehouse, @@ -1861,7 +1860,7 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(pr_return.items[0].qty, 2.0 * -1) self.assertEqual(pr_return.items[0].rejected_qty, 0.0) self.assertEqual(pr_return.items[0].rejected_warehouse, "") -======= + def test_purchase_receipt_with_backdated_landed_cost_voucher(self): from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( @@ -1976,7 +1975,6 @@ class TestPurchaseReceipt(FrappeTestCase): ste5.reload() self.assertEqual(ste5.items[0].valuation_rate, valuation_rate) ->>>>>>> c0c693d8b0 (fix: reposting has not changed valuation rate) def prepare_data_for_internal_transfer(): From e05b33a6c23cf9d21ec687a383cf937bc5767ad9 Mon Sep 17 00:00:00 2001 From: Vishnu VS Date: Mon, 3 Jul 2023 09:23:27 +0530 Subject: [PATCH 37/40] feat: add method for ordered quantity in supplier scorecard (#35930) fix: add method for getting ordered quantity in the supplier scorecard variable. Co-authored-by: vishnu --- .../supplier_scorecard/supplier_scorecard.py | 5 +++++ .../supplier_scorecard_variable.py | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py index 486bf23e90..58da851295 100644 --- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py @@ -329,6 +329,11 @@ def make_default_records(): "variable_label": "Total Shipments", "path": "get_total_shipments", }, + { + "param_name": "total_ordered", + "variable_label": "Total Ordered", + "path": "get_ordered_qty", + }, ] install_standing_docs = [ { diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py index fb8819eaf8..4080d1fde0 100644 --- a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py +++ b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py @@ -7,6 +7,7 @@ import sys import frappe from frappe import _ from frappe.model.document import Document +from frappe.query_builder.functions import Sum from frappe.utils import getdate @@ -422,6 +423,23 @@ def get_total_shipments(scorecard): return data +def get_ordered_qty(scorecard): + """Returns the total number of ordered quantity (based on Purchase Orders)""" + + po = frappe.qb.DocType("Purchase Order") + + return ( + frappe.qb.from_(po) + .select(Sum(po.total_qty)) + .where( + (po.supplier == scorecard.supplier) + & (po.docstatus == 1) + & (po.transaction_date >= scorecard.get("start_date")) + & (po.transaction_date <= scorecard.get("end_date")) + ) + ).run(as_list=True)[0][0] or 0 + + def get_rfq_total_number(scorecard): """Gets the total number of RFQs sent to supplier""" supplier = frappe.get_doc("Supplier", scorecard.supplier) From 544885925480bcaea382357600f22d7c134ab6f2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 3 Jul 2023 13:03:52 +0530 Subject: [PATCH 38/40] fix: Update no copy for received_qty field (#35965) --- .../doctype/purchase_invoice_item/purchase_invoice_item.json | 3 ++- .../doctype/purchase_receipt_item/purchase_receipt_item.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index deb202d145..c5187a2f46 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -178,6 +178,7 @@ "fieldname": "received_qty", "fieldtype": "Float", "label": "Received Qty", + "no_copy": 1, "read_only": 1 }, { @@ -903,7 +904,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-04-01 20:08:54.545160", + "modified": "2023-07-02 18:39:41.495723", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index e576ab789a..3929616f7c 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -213,6 +213,7 @@ "fieldname": "received_qty", "fieldtype": "Float", "label": "Received Quantity", + "no_copy": 1, "oldfieldname": "received_qty", "oldfieldtype": "Currency", "print_hide": 1, @@ -1057,7 +1058,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-03-12 13:37:47.778021", + "modified": "2023-07-02 18:40:48.152637", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From dedb5e23f7b3e1de5389989fbf98f9a821860500 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Mon, 3 Jul 2023 13:06:46 +0530 Subject: [PATCH 39/40] fix: delete loan module workspace properly after separation (#35971) * fix: delete loan module workspace properly after separation * chore: run remove_loan_management_module patch again --- erpnext/patches.txt | 2 +- erpnext/patches/v15_0/remove_loan_management_module.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index dc056635ed..b3b9bc60b7 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -331,6 +331,6 @@ execute:frappe.delete_doc('DocType', 'Cash Flow Mapper', ignore_missing=True) execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Template', ignore_missing=True) execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Accounts', ignore_missing=True) erpnext.patches.v14_0.cleanup_workspaces -erpnext.patches.v15_0.remove_loan_management_module +erpnext.patches.v15_0.remove_loan_management_module #2023-07-03 erpnext.patches.v14_0.set_report_in_process_SOA erpnext.buying.doctype.supplier.patches.migrate_supplier_portal_users \ No newline at end of file diff --git a/erpnext/patches/v15_0/remove_loan_management_module.py b/erpnext/patches/v15_0/remove_loan_management_module.py index 6f08c361ba..8242f9cce5 100644 --- a/erpnext/patches/v15_0/remove_loan_management_module.py +++ b/erpnext/patches/v15_0/remove_loan_management_module.py @@ -7,7 +7,7 @@ def execute(): frappe.delete_doc("Module Def", "Loan Management", ignore_missing=True, force=True) - frappe.delete_doc("Workspace", "Loan Management", ignore_missing=True, force=True) + frappe.delete_doc("Workspace", "Loans", ignore_missing=True, force=True) print_formats = frappe.get_all( "Print Format", {"module": "Loan Management", "standard": "Yes"}, pluck="name" From 1e8f6c0840b0ea3f77df0c2e63e551c689b1b5a4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 3 Jul 2023 17:28:55 +0530 Subject: [PATCH 40/40] fix: reserve the pos invoice batches --- .../doctype/pos_invoice/pos_invoice.js | 2 +- .../doctype/pos_invoice/pos_invoice.py | 2 +- .../doctype/pos_invoice/test_pos_invoice.py | 33 +++++ .../serial_and_batch_bundle.py | 134 +++++++++++++----- 4 files changed, 135 insertions(+), 36 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js index cced37589b..32e267f33c 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js @@ -20,7 +20,7 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex onload(doc) { super.onload(); - this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log', 'POS Closing Entry']; + this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log', 'POS Closing Entry', 'Serial and Batch Bundle']; if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') { this.frm.script_manager.trigger("is_pos"); diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index bf393c0d29..4b2fcec757 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -93,7 +93,7 @@ class POSInvoice(SalesInvoice): ) def on_cancel(self): - self.ignore_linked_doctypes = "Payment Ledger Entry" + self.ignore_linked_doctypes = ["Payment Ledger Entry", "Serial and Batch Bundle"] # run on cancel method of selling controller super(SalesInvoice, self).on_cancel() if not self.is_return and self.loyalty_program: diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index f842a16b74..0fce61f1e7 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -767,6 +767,39 @@ class TestPOSInvoice(unittest.TestCase): ) self.assertEqual(rounded_total, 400) + def test_pos_batch_reservation(self): + from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( + get_auto_batch_nos, + ) + from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( + create_batch_item_with_batch, + ) + + create_batch_item_with_batch("_BATCH ITEM Test For Reserve", "TestBatch-RS 02") + make_stock_entry( + target="_Test Warehouse - _TC", + item_code="_BATCH ITEM Test For Reserve", + qty=20, + basic_rate=100, + batch_no="TestBatch-RS 02", + ) + + pos_inv1 = create_pos_invoice( + item="_BATCH ITEM Test For Reserve", rate=300, qty=15, batch_no="TestBatch-RS 02" + ) + pos_inv1.save() + pos_inv1.submit() + + batches = get_auto_batch_nos( + frappe._dict( + {"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"} + ) + ) + + for batch in batches: + if batch.batch_no == "TestBatch-RS 02" and batch.warehouse == "_Test Warehouse - _TC": + self.assertEqual(batch.qty, 5) + def test_pos_batch_item_qty_validation(self): from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( BatchNegativeStockError, diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 57bb71ef1e..a5c7879b17 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -1241,59 +1241,125 @@ def get_reserved_serial_nos_for_pos(kwargs): return list(set(ignore_serial_nos) - set(returned_serial_nos)) +def get_reserved_batches_for_pos(kwargs): + pos_batches = frappe._dict() + pos_invoices = frappe.get_all( + "POS Invoice", + fields=[ + "`tabPOS Invoice Item`.batch_no", + "`tabPOS Invoice`.is_return", + "`tabPOS Invoice Item`.warehouse", + "`tabPOS Invoice Item`.name as child_docname", + "`tabPOS Invoice`.name as parent_docname", + "`tabPOS Invoice Item`.serial_and_batch_bundle", + ], + filters=[ + ["POS Invoice", "consolidated_invoice", "is", "not set"], + ["POS Invoice", "docstatus", "=", 1], + ["POS Invoice Item", "item_code", "=", kwargs.item_code], + ["POS Invoice", "name", "!=", kwargs.ignore_voucher_no], + ], + ) + + ids = [ + pos_invoice.serial_and_batch_bundle + for pos_invoice in pos_invoices + if pos_invoice.serial_and_batch_bundle + ] + + if not ids: + return [] + + if ids: + for d in get_serial_batch_ledgers(kwargs.item_code, docstatus=1, name=ids): + if d.batch_no not in pos_batches: + pos_batches[d.batch_no] = frappe._dict( + { + "qty": d.qty, + "warehouse": d.warehouse, + } + ) + else: + pos_batches[d.batch_no].qty += d.qty + + for row in pos_invoices: + if not row.batch_no: + continue + + if row.batch_no in pos_batches: + pos_batches[row.batch_no] -= row.qty * -1 if row.is_return else row.qty + else: + pos_batches[row.batch_no] = frappe._dict( + { + "qty": (row.qty * -1 if row.is_return else row.qty), + "warehouse": row.warehouse, + } + ) + + return pos_batches + + def get_auto_batch_nos(kwargs): available_batches = get_available_batches(kwargs) qty = flt(kwargs.qty) + pos_invoice_batches = get_reserved_batches_for_pos(kwargs) stock_ledgers_batches = get_stock_ledgers_batches(kwargs) - if stock_ledgers_batches: - update_available_batches(available_batches, stock_ledgers_batches) + if stock_ledgers_batches or pos_invoice_batches: + update_available_batches(available_batches, stock_ledgers_batches, pos_invoice_batches) available_batches = list(filter(lambda x: x.qty > 0, available_batches)) - if not qty: return available_batches + return get_qty_based_available_batches(available_batches, qty) + + +def get_qty_based_available_batches(available_batches, qty): batches = [] for batch in available_batches: - if qty > 0: - batch_qty = flt(batch.qty) - if qty > batch_qty: - batches.append( - frappe._dict( - { - "batch_no": batch.batch_no, - "qty": batch_qty, - "warehouse": batch.warehouse, - } - ) + if qty <= 0: + break + + batch_qty = flt(batch.qty) + if qty > batch_qty: + batches.append( + frappe._dict( + { + "batch_no": batch.batch_no, + "qty": batch_qty, + "warehouse": batch.warehouse, + } ) - qty -= batch_qty - else: - batches.append( - frappe._dict( - { - "batch_no": batch.batch_no, - "qty": qty, - "warehouse": batch.warehouse, - } - ) + ) + qty -= batch_qty + else: + batches.append( + frappe._dict( + { + "batch_no": batch.batch_no, + "qty": qty, + "warehouse": batch.warehouse, + } ) - qty = 0 + ) + qty = 0 return batches -def update_available_batches(available_batches, reserved_batches): - for batch_no, data in reserved_batches.items(): - batch_not_exists = True - for batch in available_batches: - if batch.batch_no == batch_no: - batch.qty += data.qty - batch_not_exists = False +def update_available_batches(available_batches, reserved_batches=None, pos_invoice_batches=None): + for batches in [reserved_batches, pos_invoice_batches]: + if batches: + for batch_no, data in batches.items(): + batch_not_exists = True + for batch in available_batches: + if batch.batch_no == batch_no and batch.warehouse == data.warehouse: + batch.qty += data.qty + batch_not_exists = False - if batch_not_exists: - available_batches.append(data) + if batch_not_exists: + available_batches.append(data) def get_available_batches(kwargs):