From 8c1f6196b82f024f8f768ee767705b86e9ba89f5 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Fri, 12 Jul 2024 18:58:14 +0530 Subject: [PATCH 1/9] fix: remove proprietorship and update it with individual (backport #42307) --- erpnext/buying/doctype/supplier/supplier.json | 2 +- erpnext/patches.txt | 1 + .../patches/v14_0/update_proprietorship_to_individual.py | 7 +++++++ erpnext/selling/doctype/customer/customer.json | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v14_0/update_proprietorship_to_individual.py diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 944eb985246..5ee1604415d 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -170,7 +170,7 @@ "fieldname": "supplier_type", "fieldtype": "Select", "label": "Supplier Type", - "options": "Company\nIndividual\nProprietorship\nPartnership", + "options": "Company\nIndividual\nPartnership", "reqd": 1 }, { diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 25f038173d6..1f6363d56cb 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -271,6 +271,7 @@ erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair erpnext.patches.v14_0.update_reference_due_date_in_journal_entry erpnext.patches.v14_0.france_depreciation_warning erpnext.patches.v14_0.clear_reconciliation_values_from_singles +erpnext.patches.v14_0.update_proprietorship_to_individual [post_model_sync] execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings') diff --git a/erpnext/patches/v14_0/update_proprietorship_to_individual.py b/erpnext/patches/v14_0/update_proprietorship_to_individual.py new file mode 100644 index 00000000000..dc4ec15e86b --- /dev/null +++ b/erpnext/patches/v14_0/update_proprietorship_to_individual.py @@ -0,0 +1,7 @@ +import frappe + + +def execute(): + for doctype in ["Customer", "Supplier"]: + field = doctype.lower() + "_type" + frappe.db.set_value(doctype, {field: "Proprietorship"}, field, "Individual") diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index e272f09679c..ecf164496bb 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -131,7 +131,7 @@ "label": "Customer Type", "oldfieldname": "customer_type", "oldfieldtype": "Select", - "options": "Company\nIndividual\nProprietorship\nPartnership", + "options": "Company\nIndividual\nPartnership", "reqd": 1 }, { From 90ee21f86897c7ec647195929b39f72a52ef00b0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 17 Jul 2024 15:27:58 +0530 Subject: [PATCH 2/9] fix: missing cr/dr notes on payment reconciliation (cherry picked from commit a30af68e9e71752f8317e23c9865d0f38d18512e) --- .../doctype/payment_reconciliation/payment_reconciliation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index b0347514905..9480aeb7746 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -200,6 +200,7 @@ class PaymentReconciliation(Document): conditions.append(doc.docstatus == 1) conditions.append(doc[frappe.scrub(self.party_type)] == self.party) conditions.append(doc.is_return == 1) + conditions.append(doc.outstanding_amount != 0) if self.payment_name: conditions.append(doc.name.like(f"%{self.payment_name}%")) From 3109efaf099a916d25bcd35b44c06cbb60c51a07 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 17 Jul 2024 17:51:11 +0530 Subject: [PATCH 3/9] test: payment filter should not affect dr/cr notes (cherry picked from commit 2d686c06ea213cbd6e7a5cf7173a5a4bddbcc67b) # Conflicts: # erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py --- .../test_payment_reconciliation.py | 497 ++++++++++++++++++ 1 file changed, 497 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 22f05957a79..623230fd486 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -1335,6 +1335,503 @@ class TestPaymentReconciliation(FrappeTestCase): # Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again. pr.reconcile() +<<<<<<< HEAD +======= + def test_reverse_payment_against_payment_for_supplier(self): + """ + Reconcile a payment against a reverse payment, for a supplier. + """ + self.supplier = "_Test Supplier" + amount = 4000 + + pe = self.create_payment_entry(amount=amount) + pe.party_type = "Supplier" + pe.party = self.supplier + pe.payment_type = "Pay" + pe.paid_from = self.cash + pe.paid_to = self.creditors + pe.save().submit() + + reverse_pe = self.create_payment_entry(amount=amount) + reverse_pe.party_type = "Supplier" + reverse_pe.party = self.supplier + reverse_pe.payment_type = "Receive" + reverse_pe.paid_from = self.creditors + reverse_pe.paid_to = self.cash + reverse_pe.save().submit() + + pr = self.create_payment_reconciliation(party_is_customer=False) + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + self.assertEqual(pr.invoices[0].invoice_number, reverse_pe.name) + self.assertEqual(pr.payments[0].reference_name, pe.name) + + invoices = [invoice.as_dict() for invoice in pr.invoices] + payments = [payment.as_dict() for payment in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + pe.reload() + self.assertEqual(len(pe.references), 1) + self.assertEqual(pe.references[0].exchange_rate, 1) + # There should not be any Exc Gain/Loss + self.assertEqual(pe.references[0].exchange_gain_loss, 0) + self.assertEqual(pe.references[0].reference_name, reverse_pe.name) + + journals = frappe.db.get_all( + "Journal Entry", + filters={ + "voucher_type": "Exchange Gain Or Loss", + "reference_type": "Payment Entry", + "reference_name": ("in", [pe.name, reverse_pe.name]), + }, + ) + # There should be no Exchange Gain/Loss created + self.assertEqual(journals, []) + + def test_advance_reverse_payment_against_payment_for_supplier(self): + """ + Reconcile an Advance payment against reverse payment, for a supplier. + """ + frappe.db.set_value( + "Company", + self.company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_paid_account": self.advance_payable_account, + }, + ) + + self.supplier = "_Test Supplier" + amount = 4000 + + pe = self.create_payment_entry(amount=amount) + pe.party_type = "Supplier" + pe.party = self.supplier + pe.payment_type = "Pay" + pe.paid_from = self.cash + pe.paid_to = self.advance_payable_account + pe.save().submit() + + reverse_pe = self.create_payment_entry(amount=amount) + reverse_pe.party_type = "Supplier" + reverse_pe.party = self.supplier + reverse_pe.payment_type = "Receive" + reverse_pe.paid_from = self.advance_payable_account + reverse_pe.paid_to = self.cash + reverse_pe.save().submit() + + pr = self.create_payment_reconciliation(party_is_customer=False) + pr.default_advance_account = self.advance_payable_account + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + self.assertEqual(pr.invoices[0].invoice_number, reverse_pe.name) + self.assertEqual(pr.payments[0].reference_name, pe.name) + + invoices = [invoice.as_dict() for invoice in pr.invoices] + payments = [payment.as_dict() for payment in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + pe.reload() + self.assertEqual(len(pe.references), 1) + self.assertEqual(pe.references[0].exchange_rate, 1) + # There should not be any Exc Gain/Loss + self.assertEqual(pe.references[0].exchange_gain_loss, 0) + self.assertEqual(pe.references[0].reference_name, reverse_pe.name) + + journals = frappe.db.get_all( + "Journal Entry", + filters={ + "voucher_type": "Exchange Gain Or Loss", + "reference_type": "Payment Entry", + "reference_name": ("in", [pe.name, reverse_pe.name]), + }, + ) + # There should be no Exchange Gain/Loss created + self.assertEqual(journals, []) + + # Assert Ledger Entries + gl_entries = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pe.name}, + fields=["account", "voucher_no", "against_voucher", "debit", "credit"], + order_by="account, against_voucher, debit", + ) + expected_gle = [ + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher": pe.name, + "debit": 0.0, + "credit": amount, + }, + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher": pe.name, + "debit": amount, + "credit": 0.0, + }, + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher": reverse_pe.name, + "debit": amount, + "credit": 0.0, + }, + { + "account": "Cash - _PR", + "voucher_no": pe.name, + "against_voucher": None, + "debit": 0.0, + "credit": amount, + }, + ] + self.assertEqual(gl_entries, expected_gle) + pl_entries = frappe.db.get_all( + "Payment Ledger Entry", + filters={"voucher_no": pe.name}, + fields=["account", "voucher_no", "against_voucher_no", "amount"], + order_by="account, against_voucher_no, amount", + ) + expected_ple = [ + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": -amount, + }, + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": amount, + }, + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher_no": reverse_pe.name, + "amount": -amount, + }, + ] + self.assertEqual(pl_entries, expected_ple) + + def test_advance_payment_reconciliation_date(self): + frappe.db.set_value( + "Company", + self.company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_paid_account": self.advance_payable_account, + "reconcile_on_advance_payment_date": 1, + }, + ) + + self.supplier = "_Test Supplier" + amount = 1500 + + pe = self.create_payment_entry(amount=amount) + pe.posting_date = add_days(nowdate(), -1) + pe.party_type = "Supplier" + pe.party = self.supplier + pe.payment_type = "Pay" + pe.paid_from = self.cash + pe.paid_to = self.advance_payable_account + pe.save().submit() + + pi = self.create_purchase_invoice(qty=10, rate=100) + self.assertNotEqual(pe.posting_date, pi.posting_date) + + pr = self.create_payment_reconciliation(party_is_customer=False) + pr.default_advance_account = self.advance_payable_account + pr.from_payment_date = pe.posting_date + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + invoices = [invoice.as_dict() for invoice in pr.invoices] + payments = [payment.as_dict() for payment in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + # Assert Ledger Entries + gl_entries = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pe.name, "is_cancelled": 0, "posting_date": pe.posting_date}, + ) + self.assertEqual(len(gl_entries), 4) + pl_entries = frappe.db.get_all( + "Payment Ledger Entry", + filters={"voucher_no": pe.name, "delinked": 0, "posting_date": pe.posting_date}, + ) + self.assertEqual(len(pl_entries), 3) + + def test_advance_payment_reconciliation_against_journal_for_customer(self): + frappe.db.set_value( + "Company", + self.company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_received_account": self.advance_receivable_account, + "reconcile_on_advance_payment_date": 0, + }, + ) + amount = 200.0 + je = self.create_journal_entry(self.debit_to, self.bank, amount) + je.accounts[0].cost_center = self.main_cc.name + je.accounts[0].party_type = "Customer" + je.accounts[0].party = self.customer + je.accounts[1].cost_center = self.main_cc.name + je = je.save().submit() + + pe = self.create_payment_entry(amount=amount).save().submit() + + pr = self.create_payment_reconciliation() + pr.default_advance_account = self.advance_receivable_account + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + invoices = [invoice.as_dict() for invoice in pr.invoices] + payments = [payment.as_dict() for payment in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + # Assert Ledger Entries + gl_entries = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pe.name, "is_cancelled": 0}, + ) + self.assertEqual(len(gl_entries), 4) + pl_entries = frappe.db.get_all( + "Payment Ledger Entry", + filters={"voucher_no": pe.name, "delinked": 0}, + ) + self.assertEqual(len(pl_entries), 3) + + gl_entries = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pe.name, "is_cancelled": 0}, + fields=["account", "voucher_no", "against_voucher", "debit", "credit"], + order_by="account, against_voucher, debit", + ) + expected_gle = [ + { + "account": self.advance_receivable_account, + "voucher_no": pe.name, + "against_voucher": pe.name, + "debit": 0.0, + "credit": amount, + }, + { + "account": self.advance_receivable_account, + "voucher_no": pe.name, + "against_voucher": pe.name, + "debit": amount, + "credit": 0.0, + }, + { + "account": self.debit_to, + "voucher_no": pe.name, + "against_voucher": je.name, + "debit": 0.0, + "credit": amount, + }, + { + "account": self.bank, + "voucher_no": pe.name, + "against_voucher": None, + "debit": amount, + "credit": 0.0, + }, + ] + self.assertEqual(gl_entries, expected_gle) + + pl_entries = frappe.db.get_all( + "Payment Ledger Entry", + filters={"voucher_no": pe.name}, + fields=["account", "voucher_no", "against_voucher_no", "amount"], + order_by="account, against_voucher_no, amount", + ) + expected_ple = [ + { + "account": self.advance_receivable_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": -amount, + }, + { + "account": self.advance_receivable_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": amount, + }, + { + "account": self.debit_to, + "voucher_no": pe.name, + "against_voucher_no": je.name, + "amount": -amount, + }, + ] + self.assertEqual(pl_entries, expected_ple) + + def test_advance_payment_reconciliation_against_journal_for_supplier(self): + self.supplier = make_supplier("_Test Supplier") + frappe.db.set_value( + "Company", + self.company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_paid_account": self.advance_payable_account, + "reconcile_on_advance_payment_date": 0, + }, + ) + amount = 200.0 + je = self.create_journal_entry(self.creditors, self.bank, -amount) + je.accounts[0].cost_center = self.main_cc.name + je.accounts[0].party_type = "Supplier" + je.accounts[0].party = self.supplier + je.accounts[1].cost_center = self.main_cc.name + je = je.save().submit() + + pe = self.create_payment_entry(amount=amount) + pe.payment_type = "Pay" + pe.party_type = "Supplier" + pe.paid_from = self.bank + pe.paid_to = self.creditors + pe.party = self.supplier + pe.save().submit() + + pr = self.create_payment_reconciliation(party_is_customer=False) + pr.default_advance_account = self.advance_payable_account + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + invoices = [invoice.as_dict() for invoice in pr.invoices] + payments = [payment.as_dict() for payment in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + # Assert Ledger Entries + gl_entries = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pe.name, "is_cancelled": 0}, + ) + self.assertEqual(len(gl_entries), 4) + pl_entries = frappe.db.get_all( + "Payment Ledger Entry", + filters={"voucher_no": pe.name, "delinked": 0}, + ) + self.assertEqual(len(pl_entries), 3) + + gl_entries = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pe.name, "is_cancelled": 0}, + fields=["account", "voucher_no", "against_voucher", "debit", "credit"], + order_by="account, against_voucher, debit", + ) + expected_gle = [ + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher": pe.name, + "debit": 0.0, + "credit": amount, + }, + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher": pe.name, + "debit": amount, + "credit": 0.0, + }, + { + "account": self.creditors, + "voucher_no": pe.name, + "against_voucher": je.name, + "debit": amount, + "credit": 0.0, + }, + { + "account": self.bank, + "voucher_no": pe.name, + "against_voucher": None, + "debit": 0.0, + "credit": amount, + }, + ] + self.assertEqual(gl_entries, expected_gle) + + pl_entries = frappe.db.get_all( + "Payment Ledger Entry", + filters={"voucher_no": pe.name}, + fields=["account", "voucher_no", "against_voucher_no", "amount"], + order_by="account, against_voucher_no, amount", + ) + expected_ple = [ + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": -amount, + }, + { + "account": self.advance_payable_account, + "voucher_no": pe.name, + "against_voucher_no": pe.name, + "amount": amount, + }, + { + "account": self.creditors, + "voucher_no": pe.name, + "against_voucher_no": je.name, + "amount": -amount, + }, + ] + self.assertEqual(pl_entries, expected_ple) + + def test_cr_note_payment_limit_filter(self): + transaction_date = nowdate() + amount = 100 + + for _ in range(6): + self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date) + cr_note = self.create_sales_invoice( + qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True + ) + cr_note.is_return = 1 + cr_note = cr_note.save().submit() + + pr = self.create_payment_reconciliation() + + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 6) + self.assertEqual(len(pr.payments), 6) + invoices = [x.as_dict() for x in pr.get("invoices")] + payments = [x.as_dict() for x in pr.get("payments")] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + pr.get_unreconciled_entries() + self.assertEqual(pr.get("invoices"), []) + self.assertEqual(pr.get("payments"), []) + + self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date) + cr_note = self.create_sales_invoice( + qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True + ) + cr_note.is_return = 1 + cr_note = cr_note.save().submit() + + # Limit should not affect in fetching the unallocated cr_note + pr.invoice_limit = 5 + pr.payment_limit = 5 + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + +>>>>>>> 2d686c06ea (test: payment filter should not affect dr/cr notes) def make_customer(customer_name, currency=None): if not frappe.db.exists("Customer", customer_name): From fac22e93d008d6e22b5003b244b2480e03a04a5e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 18 Jul 2024 12:44:34 +0530 Subject: [PATCH 4/9] chore: resolve conflict --- .../test_payment_reconciliation.py | 457 ------------------ 1 file changed, 457 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 623230fd486..b25f7b6b4d9 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -1335,462 +1335,6 @@ class TestPaymentReconciliation(FrappeTestCase): # Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again. pr.reconcile() -<<<<<<< HEAD -======= - def test_reverse_payment_against_payment_for_supplier(self): - """ - Reconcile a payment against a reverse payment, for a supplier. - """ - self.supplier = "_Test Supplier" - amount = 4000 - - pe = self.create_payment_entry(amount=amount) - pe.party_type = "Supplier" - pe.party = self.supplier - pe.payment_type = "Pay" - pe.paid_from = self.cash - pe.paid_to = self.creditors - pe.save().submit() - - reverse_pe = self.create_payment_entry(amount=amount) - reverse_pe.party_type = "Supplier" - reverse_pe.party = self.supplier - reverse_pe.payment_type = "Receive" - reverse_pe.paid_from = self.creditors - reverse_pe.paid_to = self.cash - reverse_pe.save().submit() - - pr = self.create_payment_reconciliation(party_is_customer=False) - pr.get_unreconciled_entries() - self.assertEqual(len(pr.invoices), 1) - self.assertEqual(len(pr.payments), 1) - self.assertEqual(pr.invoices[0].invoice_number, reverse_pe.name) - self.assertEqual(pr.payments[0].reference_name, pe.name) - - invoices = [invoice.as_dict() for invoice in pr.invoices] - payments = [payment.as_dict() for payment in pr.payments] - pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) - pr.reconcile() - - pe.reload() - self.assertEqual(len(pe.references), 1) - self.assertEqual(pe.references[0].exchange_rate, 1) - # There should not be any Exc Gain/Loss - self.assertEqual(pe.references[0].exchange_gain_loss, 0) - self.assertEqual(pe.references[0].reference_name, reverse_pe.name) - - journals = frappe.db.get_all( - "Journal Entry", - filters={ - "voucher_type": "Exchange Gain Or Loss", - "reference_type": "Payment Entry", - "reference_name": ("in", [pe.name, reverse_pe.name]), - }, - ) - # There should be no Exchange Gain/Loss created - self.assertEqual(journals, []) - - def test_advance_reverse_payment_against_payment_for_supplier(self): - """ - Reconcile an Advance payment against reverse payment, for a supplier. - """ - frappe.db.set_value( - "Company", - self.company, - { - "book_advance_payments_in_separate_party_account": 1, - "default_advance_paid_account": self.advance_payable_account, - }, - ) - - self.supplier = "_Test Supplier" - amount = 4000 - - pe = self.create_payment_entry(amount=amount) - pe.party_type = "Supplier" - pe.party = self.supplier - pe.payment_type = "Pay" - pe.paid_from = self.cash - pe.paid_to = self.advance_payable_account - pe.save().submit() - - reverse_pe = self.create_payment_entry(amount=amount) - reverse_pe.party_type = "Supplier" - reverse_pe.party = self.supplier - reverse_pe.payment_type = "Receive" - reverse_pe.paid_from = self.advance_payable_account - reverse_pe.paid_to = self.cash - reverse_pe.save().submit() - - pr = self.create_payment_reconciliation(party_is_customer=False) - pr.default_advance_account = self.advance_payable_account - pr.get_unreconciled_entries() - self.assertEqual(len(pr.invoices), 1) - self.assertEqual(len(pr.payments), 1) - self.assertEqual(pr.invoices[0].invoice_number, reverse_pe.name) - self.assertEqual(pr.payments[0].reference_name, pe.name) - - invoices = [invoice.as_dict() for invoice in pr.invoices] - payments = [payment.as_dict() for payment in pr.payments] - pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) - pr.reconcile() - - pe.reload() - self.assertEqual(len(pe.references), 1) - self.assertEqual(pe.references[0].exchange_rate, 1) - # There should not be any Exc Gain/Loss - self.assertEqual(pe.references[0].exchange_gain_loss, 0) - self.assertEqual(pe.references[0].reference_name, reverse_pe.name) - - journals = frappe.db.get_all( - "Journal Entry", - filters={ - "voucher_type": "Exchange Gain Or Loss", - "reference_type": "Payment Entry", - "reference_name": ("in", [pe.name, reverse_pe.name]), - }, - ) - # There should be no Exchange Gain/Loss created - self.assertEqual(journals, []) - - # Assert Ledger Entries - gl_entries = frappe.db.get_all( - "GL Entry", - filters={"voucher_no": pe.name}, - fields=["account", "voucher_no", "against_voucher", "debit", "credit"], - order_by="account, against_voucher, debit", - ) - expected_gle = [ - { - "account": self.advance_payable_account, - "voucher_no": pe.name, - "against_voucher": pe.name, - "debit": 0.0, - "credit": amount, - }, - { - "account": self.advance_payable_account, - "voucher_no": pe.name, - "against_voucher": pe.name, - "debit": amount, - "credit": 0.0, - }, - { - "account": self.advance_payable_account, - "voucher_no": pe.name, - "against_voucher": reverse_pe.name, - "debit": amount, - "credit": 0.0, - }, - { - "account": "Cash - _PR", - "voucher_no": pe.name, - "against_voucher": None, - "debit": 0.0, - "credit": amount, - }, - ] - self.assertEqual(gl_entries, expected_gle) - pl_entries = frappe.db.get_all( - "Payment Ledger Entry", - filters={"voucher_no": pe.name}, - fields=["account", "voucher_no", "against_voucher_no", "amount"], - order_by="account, against_voucher_no, amount", - ) - expected_ple = [ - { - "account": self.advance_payable_account, - "voucher_no": pe.name, - "against_voucher_no": pe.name, - "amount": -amount, - }, - { - "account": self.advance_payable_account, - "voucher_no": pe.name, - "against_voucher_no": pe.name, - "amount": amount, - }, - { - "account": self.advance_payable_account, - "voucher_no": pe.name, - "against_voucher_no": reverse_pe.name, - "amount": -amount, - }, - ] - self.assertEqual(pl_entries, expected_ple) - - def test_advance_payment_reconciliation_date(self): - frappe.db.set_value( - "Company", - self.company, - { - "book_advance_payments_in_separate_party_account": 1, - "default_advance_paid_account": self.advance_payable_account, - "reconcile_on_advance_payment_date": 1, - }, - ) - - self.supplier = "_Test Supplier" - amount = 1500 - - pe = self.create_payment_entry(amount=amount) - pe.posting_date = add_days(nowdate(), -1) - pe.party_type = "Supplier" - pe.party = self.supplier - pe.payment_type = "Pay" - pe.paid_from = self.cash - pe.paid_to = self.advance_payable_account - pe.save().submit() - - pi = self.create_purchase_invoice(qty=10, rate=100) - self.assertNotEqual(pe.posting_date, pi.posting_date) - - pr = self.create_payment_reconciliation(party_is_customer=False) - pr.default_advance_account = self.advance_payable_account - pr.from_payment_date = pe.posting_date - pr.get_unreconciled_entries() - self.assertEqual(len(pr.invoices), 1) - self.assertEqual(len(pr.payments), 1) - invoices = [invoice.as_dict() for invoice in pr.invoices] - payments = [payment.as_dict() for payment in pr.payments] - pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) - pr.reconcile() - - # Assert Ledger Entries - gl_entries = frappe.db.get_all( - "GL Entry", - filters={"voucher_no": pe.name, "is_cancelled": 0, "posting_date": pe.posting_date}, - ) - self.assertEqual(len(gl_entries), 4) - pl_entries = frappe.db.get_all( - "Payment Ledger Entry", - filters={"voucher_no": pe.name, "delinked": 0, "posting_date": pe.posting_date}, - ) - self.assertEqual(len(pl_entries), 3) - - def test_advance_payment_reconciliation_against_journal_for_customer(self): - frappe.db.set_value( - "Company", - self.company, - { - "book_advance_payments_in_separate_party_account": 1, - "default_advance_received_account": self.advance_receivable_account, - "reconcile_on_advance_payment_date": 0, - }, - ) - amount = 200.0 - je = self.create_journal_entry(self.debit_to, self.bank, amount) - je.accounts[0].cost_center = self.main_cc.name - je.accounts[0].party_type = "Customer" - je.accounts[0].party = self.customer - je.accounts[1].cost_center = self.main_cc.name - je = je.save().submit() - - pe = self.create_payment_entry(amount=amount).save().submit() - - pr = self.create_payment_reconciliation() - pr.default_advance_account = self.advance_receivable_account - pr.get_unreconciled_entries() - self.assertEqual(len(pr.invoices), 1) - self.assertEqual(len(pr.payments), 1) - invoices = [invoice.as_dict() for invoice in pr.invoices] - payments = [payment.as_dict() for payment in pr.payments] - pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) - pr.reconcile() - - # Assert Ledger Entries - gl_entries = frappe.db.get_all( - "GL Entry", - filters={"voucher_no": pe.name, "is_cancelled": 0}, - ) - self.assertEqual(len(gl_entries), 4) - pl_entries = frappe.db.get_all( - "Payment Ledger Entry", - filters={"voucher_no": pe.name, "delinked": 0}, - ) - self.assertEqual(len(pl_entries), 3) - - gl_entries = frappe.db.get_all( - "GL Entry", - filters={"voucher_no": pe.name, "is_cancelled": 0}, - fields=["account", "voucher_no", "against_voucher", "debit", "credit"], - order_by="account, against_voucher, debit", - ) - expected_gle = [ - { - "account": self.advance_receivable_account, - "voucher_no": pe.name, - "against_voucher": pe.name, - "debit": 0.0, - "credit": amount, - }, - { - "account": self.advance_receivable_account, - "voucher_no": pe.name, - "against_voucher": pe.name, - "debit": amount, - "credit": 0.0, - }, - { - "account": self.debit_to, - "voucher_no": pe.name, - "against_voucher": je.name, - "debit": 0.0, - "credit": amount, - }, - { - "account": self.bank, - "voucher_no": pe.name, - "against_voucher": None, - "debit": amount, - "credit": 0.0, - }, - ] - self.assertEqual(gl_entries, expected_gle) - - pl_entries = frappe.db.get_all( - "Payment Ledger Entry", - filters={"voucher_no": pe.name}, - fields=["account", "voucher_no", "against_voucher_no", "amount"], - order_by="account, against_voucher_no, amount", - ) - expected_ple = [ - { - "account": self.advance_receivable_account, - "voucher_no": pe.name, - "against_voucher_no": pe.name, - "amount": -amount, - }, - { - "account": self.advance_receivable_account, - "voucher_no": pe.name, - "against_voucher_no": pe.name, - "amount": amount, - }, - { - "account": self.debit_to, - "voucher_no": pe.name, - "against_voucher_no": je.name, - "amount": -amount, - }, - ] - self.assertEqual(pl_entries, expected_ple) - - def test_advance_payment_reconciliation_against_journal_for_supplier(self): - self.supplier = make_supplier("_Test Supplier") - frappe.db.set_value( - "Company", - self.company, - { - "book_advance_payments_in_separate_party_account": 1, - "default_advance_paid_account": self.advance_payable_account, - "reconcile_on_advance_payment_date": 0, - }, - ) - amount = 200.0 - je = self.create_journal_entry(self.creditors, self.bank, -amount) - je.accounts[0].cost_center = self.main_cc.name - je.accounts[0].party_type = "Supplier" - je.accounts[0].party = self.supplier - je.accounts[1].cost_center = self.main_cc.name - je = je.save().submit() - - pe = self.create_payment_entry(amount=amount) - pe.payment_type = "Pay" - pe.party_type = "Supplier" - pe.paid_from = self.bank - pe.paid_to = self.creditors - pe.party = self.supplier - pe.save().submit() - - pr = self.create_payment_reconciliation(party_is_customer=False) - pr.default_advance_account = self.advance_payable_account - pr.get_unreconciled_entries() - self.assertEqual(len(pr.invoices), 1) - self.assertEqual(len(pr.payments), 1) - invoices = [invoice.as_dict() for invoice in pr.invoices] - payments = [payment.as_dict() for payment in pr.payments] - pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) - pr.reconcile() - - # Assert Ledger Entries - gl_entries = frappe.db.get_all( - "GL Entry", - filters={"voucher_no": pe.name, "is_cancelled": 0}, - ) - self.assertEqual(len(gl_entries), 4) - pl_entries = frappe.db.get_all( - "Payment Ledger Entry", - filters={"voucher_no": pe.name, "delinked": 0}, - ) - self.assertEqual(len(pl_entries), 3) - - gl_entries = frappe.db.get_all( - "GL Entry", - filters={"voucher_no": pe.name, "is_cancelled": 0}, - fields=["account", "voucher_no", "against_voucher", "debit", "credit"], - order_by="account, against_voucher, debit", - ) - expected_gle = [ - { - "account": self.advance_payable_account, - "voucher_no": pe.name, - "against_voucher": pe.name, - "debit": 0.0, - "credit": amount, - }, - { - "account": self.advance_payable_account, - "voucher_no": pe.name, - "against_voucher": pe.name, - "debit": amount, - "credit": 0.0, - }, - { - "account": self.creditors, - "voucher_no": pe.name, - "against_voucher": je.name, - "debit": amount, - "credit": 0.0, - }, - { - "account": self.bank, - "voucher_no": pe.name, - "against_voucher": None, - "debit": 0.0, - "credit": amount, - }, - ] - self.assertEqual(gl_entries, expected_gle) - - pl_entries = frappe.db.get_all( - "Payment Ledger Entry", - filters={"voucher_no": pe.name}, - fields=["account", "voucher_no", "against_voucher_no", "amount"], - order_by="account, against_voucher_no, amount", - ) - expected_ple = [ - { - "account": self.advance_payable_account, - "voucher_no": pe.name, - "against_voucher_no": pe.name, - "amount": -amount, - }, - { - "account": self.advance_payable_account, - "voucher_no": pe.name, - "against_voucher_no": pe.name, - "amount": amount, - }, - { - "account": self.creditors, - "voucher_no": pe.name, - "against_voucher_no": je.name, - "amount": -amount, - }, - ] - self.assertEqual(pl_entries, expected_ple) - def test_cr_note_payment_limit_filter(self): transaction_date = nowdate() amount = 100 @@ -1831,7 +1375,6 @@ class TestPaymentReconciliation(FrappeTestCase): self.assertEqual(len(pr.invoices), 1) self.assertEqual(len(pr.payments), 1) ->>>>>>> 2d686c06ea (test: payment filter should not affect dr/cr notes) def make_customer(customer_name, currency=None): if not frappe.db.exists("Customer", customer_name): From 2936988cc6953ee4e969efedf5fa2f9bbd812c7e Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 17 Jul 2024 21:23:56 +0530 Subject: [PATCH 5/9] fix: Show the rows in AR/AP report where outstanding equals to 0.01 (cherry picked from commit e1dedc5402896e07c8392e0ac3160b5b98a26222) # Conflicts: # erpnext/patches.txt --- .../report/accounts_receivable/accounts_receivable.py | 8 ++++---- erpnext/patches.txt | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index be42da8423f..f17cab8d112 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -288,13 +288,13 @@ class ReceivablePayableReport: must_consider = False if self.filters.get("for_revaluation_journals"): - if (abs(row.outstanding) > 0.0 / 10**self.currency_precision) or ( - abs(row.outstanding_in_account_currency) > 0.0 / 10**self.currency_precision + if (abs(row.outstanding) >= 0.0 / 10**self.currency_precision) or ( + abs(row.outstanding_in_account_currency) >= 0.0 / 10**self.currency_precision ): must_consider = True else: - if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and ( - (abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision) + if (abs(row.outstanding) >= 1.0 / 10**self.currency_precision) and ( + (abs(row.outstanding_in_account_currency) >= 1.0 / 10**self.currency_precision) or (row.voucher_no in self.err_journals) ): must_consider = True diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1f6363d56cb..bc5236d15fb 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -268,12 +268,18 @@ erpnext.patches.v13_0.show_india_localisation_deprecation_warning erpnext.patches.v13_0.show_hr_payroll_deprecation_warning erpnext.patches.v13_0.reset_corrupt_defaults erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair +<<<<<<< HEAD +======= +erpnext.patches.v15_0.delete_taxjar_doctypes +erpnext.patches.v15_0.delete_ecommerce_doctypes +>>>>>>> e1dedc5402 (fix: Show the rows in AR/AP report where outstanding equals to 0.01) erpnext.patches.v14_0.update_reference_due_date_in_journal_entry erpnext.patches.v14_0.france_depreciation_warning erpnext.patches.v14_0.clear_reconciliation_values_from_singles erpnext.patches.v14_0.update_proprietorship_to_individual [post_model_sync] +erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings') erpnext.patches.v14_0.update_posting_datetime_and_dropped_indexes #22-02-2024 erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents From 9b828b829a09801ee33432cf6538159b79c59c40 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 18 Jul 2024 12:58:09 +0530 Subject: [PATCH 6/9] test: AR/AP report on miniscule outstanding (cherry picked from commit bb9e42cce278c1ad6760f721cf8417c041c85b4a) --- .../test_accounts_receivable.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index a65e424173c..5e2adc42d84 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -955,3 +955,32 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): self.assertEqual( expected_data, [row.invoiced, row.outstanding, row.remaining_balance, row.future_amount] ) + + def test_accounts_receivable_output_for_minor_outstanding(self): + """ + AR/AP should report miniscule outstanding of 0.01. Or else there will be slight difference with General Ledger/Trial Balance + """ + filters = { + "company": self.company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + } + + # check invoice grand total and invoiced column's value for 3 payment terms + si = self.create_sales_invoice(no_payment_schedule=True) + + pe = get_payment_entry("Sales Invoice", si.name, bank_account=self.cash, party_amount=99.99) + pe.paid_from = self.debit_to + pe.save().submit() + report = execute(filters) + + expected_data_after_payment = [100, 100, 99.99, 0.01] + self.assertEqual(len(report[1]), 1) + row = report[1][0] + self.assertEqual( + expected_data_after_payment, + [row.invoice_grand_total, row.invoiced, row.paid, row.outstanding], + ) From 4fa9626de00052b35c6a878ddcce1c2679257ccf Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 18 Jul 2024 14:40:26 +0530 Subject: [PATCH 7/9] chore: resolve conflict --- erpnext/patches.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index bc5236d15fb..1f6363d56cb 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -268,18 +268,12 @@ erpnext.patches.v13_0.show_india_localisation_deprecation_warning erpnext.patches.v13_0.show_hr_payroll_deprecation_warning erpnext.patches.v13_0.reset_corrupt_defaults erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair -<<<<<<< HEAD -======= -erpnext.patches.v15_0.delete_taxjar_doctypes -erpnext.patches.v15_0.delete_ecommerce_doctypes ->>>>>>> e1dedc5402 (fix: Show the rows in AR/AP report where outstanding equals to 0.01) erpnext.patches.v14_0.update_reference_due_date_in_journal_entry erpnext.patches.v14_0.france_depreciation_warning erpnext.patches.v14_0.clear_reconciliation_values_from_singles erpnext.patches.v14_0.update_proprietorship_to_individual [post_model_sync] -erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings') erpnext.patches.v14_0.update_posting_datetime_and_dropped_indexes #22-02-2024 erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents From 6ecb064264cd554d95c56dee1e9ed6af271915aa Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 18 Jul 2024 17:40:19 +0530 Subject: [PATCH 8/9] fix: set pos data if not return doc (cherry picked from commit 65d672da651bacfe847e26c4d19e4c7a4b11a634) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 76b4f595a00..4c899b901ca 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -428,11 +428,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e frappe.msgprint(__("Please specify Company to proceed")); } else { var me = this; + const for_validate = me.frm.doc.is_return ? true : false; return this.frm.call({ doc: me.frm.doc, method: "set_missing_values", args: { - for_validate: true, + for_validate: for_validate, }, callback: function (r) { if (!r.exc) { From 2466e28bf54018bf49373ce276c6d5620af7ce1a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 19 Jul 2024 16:31:58 +0530 Subject: [PATCH 9/9] test: basic test case for item-wise purchase register (cherry picked from commit c3c5d3f61563e1915f07f2a95a43b5aa3393dfb8) --- .../test_item_wise_purchase_register.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 erpnext/accounts/report/item_wise_purchase_register/test_item_wise_purchase_register.py diff --git a/erpnext/accounts/report/item_wise_purchase_register/test_item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/test_item_wise_purchase_register.py new file mode 100644 index 00000000000..3884854ba7f --- /dev/null +++ b/erpnext/accounts/report/item_wise_purchase_register/test_item_wise_purchase_register.py @@ -0,0 +1,63 @@ +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import getdate, today + +from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice +from erpnext.accounts.report.item_wise_purchase_register.item_wise_purchase_register import execute +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin + + +class TestItemWisePurchaseRegister(AccountsTestMixin, FrappeTestCase): + def setUp(self): + self.create_company() + self.create_supplier() + self.create_item() + + def tearDown(self): + frappe.db.rollback() + + def create_purchase_invoice(self, do_not_submit=False): + pi = make_purchase_invoice( + item=self.item, + company=self.company, + supplier=self.supplier, + is_return=False, + update_stock=False, + do_not_save=1, + rate=100, + price_list_rate=100, + qty=1, + ) + + pi = pi.save() + if not do_not_submit: + pi = pi.submit() + return pi + + def test_basic_report_output(self): + pi = self.create_purchase_invoice() + + filters = frappe._dict({"from_date": today(), "to_date": today(), "company": self.company}) + report = execute(filters) + + self.assertEqual(len(report[1]), 1) + + expected_result = { + "item_code": pi.items[0].item_code, + "invoice": pi.name, + "posting_date": getdate(), + "supplier": pi.supplier, + "credit_to": pi.credit_to, + "company": self.company, + "expense_account": pi.items[0].expense_account, + "stock_qty": 1.0, + "stock_uom": pi.items[0].stock_uom, + "rate": 100.0, + "amount": 100.0, + "total_tax": 0, + "total": 100.0, + "currency": "INR", + } + + report_output = {k: v for k, v in report[1][0].items() if k in expected_result} + self.assertDictEqual(report_output, expected_result)