mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-07 07:02:54 +00:00
Merge pull request #42448 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -200,6 +200,7 @@ class PaymentReconciliation(Document):
|
|||||||
conditions.append(doc.docstatus == 1)
|
conditions.append(doc.docstatus == 1)
|
||||||
conditions.append(doc[frappe.scrub(self.party_type)] == self.party)
|
conditions.append(doc[frappe.scrub(self.party_type)] == self.party)
|
||||||
conditions.append(doc.is_return == 1)
|
conditions.append(doc.is_return == 1)
|
||||||
|
conditions.append(doc.outstanding_amount != 0)
|
||||||
|
|
||||||
if self.payment_name:
|
if self.payment_name:
|
||||||
conditions.append(doc.name.like(f"%{self.payment_name}%"))
|
conditions.append(doc.name.like(f"%{self.payment_name}%"))
|
||||||
|
|||||||
@@ -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.
|
# Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again.
|
||||||
pr.reconcile()
|
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):
|
def make_customer(customer_name, currency=None):
|
||||||
if not frappe.db.exists("Customer", customer_name):
|
if not frappe.db.exists("Customer", customer_name):
|
||||||
|
|||||||
@@ -428,11 +428,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
frappe.msgprint(__("Please specify Company to proceed"));
|
frappe.msgprint(__("Please specify Company to proceed"));
|
||||||
} else {
|
} else {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
const for_validate = me.frm.doc.is_return ? true : false;
|
||||||
return this.frm.call({
|
return this.frm.call({
|
||||||
doc: me.frm.doc,
|
doc: me.frm.doc,
|
||||||
method: "set_missing_values",
|
method: "set_missing_values",
|
||||||
args: {
|
args: {
|
||||||
for_validate: true,
|
for_validate: for_validate,
|
||||||
},
|
},
|
||||||
callback: function (r) {
|
callback: function (r) {
|
||||||
if (!r.exc) {
|
if (!r.exc) {
|
||||||
|
|||||||
@@ -288,13 +288,13 @@ class ReceivablePayableReport:
|
|||||||
|
|
||||||
must_consider = False
|
must_consider = False
|
||||||
if self.filters.get("for_revaluation_journals"):
|
if self.filters.get("for_revaluation_journals"):
|
||||||
if (abs(row.outstanding) > 0.0 / 10**self.currency_precision) or (
|
if (abs(row.outstanding) >= 0.0 / 10**self.currency_precision) or (
|
||||||
abs(row.outstanding_in_account_currency) > 0.0 / 10**self.currency_precision
|
abs(row.outstanding_in_account_currency) >= 0.0 / 10**self.currency_precision
|
||||||
):
|
):
|
||||||
must_consider = True
|
must_consider = True
|
||||||
else:
|
else:
|
||||||
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
|
if (abs(row.outstanding) >= 1.0 / 10**self.currency_precision) and (
|
||||||
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
(abs(row.outstanding_in_account_currency) >= 1.0 / 10**self.currency_precision)
|
||||||
or (row.voucher_no in self.err_journals)
|
or (row.voucher_no in self.err_journals)
|
||||||
):
|
):
|
||||||
must_consider = True
|
must_consider = True
|
||||||
|
|||||||
@@ -955,3 +955,32 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
expected_data, [row.invoiced, row.outstanding, row.remaining_balance, row.future_amount]
|
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],
|
||||||
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
"fieldname": "supplier_type",
|
"fieldname": "supplier_type",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Supplier Type",
|
"label": "Supplier Type",
|
||||||
"options": "Company\nIndividual\nProprietorship\nPartnership",
|
"options": "Company\nIndividual\nPartnership",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.update_reference_due_date_in_journal_entry
|
||||||
erpnext.patches.v14_0.france_depreciation_warning
|
erpnext.patches.v14_0.france_depreciation_warning
|
||||||
erpnext.patches.v14_0.clear_reconciliation_values_from_singles
|
erpnext.patches.v14_0.clear_reconciliation_values_from_singles
|
||||||
|
erpnext.patches.v14_0.update_proprietorship_to_individual
|
||||||
|
|
||||||
[post_model_sync]
|
[post_model_sync]
|
||||||
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
|
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
"label": "Customer Type",
|
"label": "Customer Type",
|
||||||
"oldfieldname": "customer_type",
|
"oldfieldname": "customer_type",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "Company\nIndividual\nProprietorship\nPartnership",
|
"options": "Company\nIndividual\nPartnership",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user