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}%")) diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index 22f05957a79..b25f7b6b4d9 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -1335,6 +1335,46 @@ class TestPaymentReconciliation(FrappeTestCase): # Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again. pr.reconcile() + 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) + def make_customer(customer_name, currency=None): if not frappe.db.exists("Customer", customer_name): 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) { 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/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], + ) 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) 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 }, {