mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-29 09:54:47 +00:00
Merge branch 'frappe:version-15-hotfix' into version-15-hotfix
This commit is contained in:
@@ -209,6 +209,11 @@ frappe.query_reports["General Ledger"] = {
|
|||||||
label: __("Ignore Exchange Rate Revaluation Journals"),
|
label: __("Ignore Exchange Rate Revaluation Journals"),
|
||||||
fieldtype: "Check",
|
fieldtype: "Check",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldname: "ignore_cr_dr_notes",
|
||||||
|
label: __("Ignore System Generated Credit / Debit Notes"),
|
||||||
|
fieldtype: "Check",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -230,12 +230,28 @@ def get_conditions(filters):
|
|||||||
"company": filters.get("company"),
|
"company": filters.get("company"),
|
||||||
"docstatus": 1,
|
"docstatus": 1,
|
||||||
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
|
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
|
||||||
|
"posting_date": ["between", [filters.get("from_date"), filters.get("to_date")]],
|
||||||
},
|
},
|
||||||
as_list=True,
|
as_list=True,
|
||||||
)
|
)
|
||||||
if err_journals:
|
if err_journals:
|
||||||
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
|
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
|
||||||
|
|
||||||
|
if filters.get("ignore_cr_dr_notes"):
|
||||||
|
system_generated_cr_dr_journals = frappe.db.get_all(
|
||||||
|
"Journal Entry",
|
||||||
|
filters={
|
||||||
|
"company": filters.get("company"),
|
||||||
|
"docstatus": 1,
|
||||||
|
"voucher_type": ("in", ["Credit Note", "Debit Note"]),
|
||||||
|
"is_system_generated": 1,
|
||||||
|
"posting_date": ["between", [filters.get("from_date"), filters.get("to_date")]],
|
||||||
|
},
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
if system_generated_cr_dr_journals:
|
||||||
|
filters.update({"voucher_no_not_in": [x[0] for x in system_generated_cr_dr_journals]})
|
||||||
|
|
||||||
if filters.get("voucher_no_not_in"):
|
if filters.get("voucher_no_not_in"):
|
||||||
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,32 @@
|
|||||||
# MIT License. See license.txt
|
# MIT License. See license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import qb
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import flt, today
|
from frappe.utils import flt, today
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.accounts.report.general_ledger.general_ledger import execute
|
from erpnext.accounts.report.general_ledger.general_ledger import execute
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|
||||||
|
|
||||||
class TestGeneralLedger(FrappeTestCase):
|
class TestGeneralLedger(FrappeTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.company = "_Test Company"
|
||||||
|
self.clear_old_entries()
|
||||||
|
|
||||||
|
def clear_old_entries(self):
|
||||||
|
doctype_list = [
|
||||||
|
"GL Entry",
|
||||||
|
"Payment Ledger Entry",
|
||||||
|
"Sales Invoice",
|
||||||
|
"Purchase Invoice",
|
||||||
|
"Payment Entry",
|
||||||
|
"Journal Entry",
|
||||||
|
]
|
||||||
|
for doctype in doctype_list:
|
||||||
|
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
|
||||||
|
|
||||||
def test_foreign_account_balance_after_exchange_rate_revaluation(self):
|
def test_foreign_account_balance_after_exchange_rate_revaluation(self):
|
||||||
"""
|
"""
|
||||||
Checks the correctness of balance after exchange rate revaluation
|
Checks the correctness of balance after exchange rate revaluation
|
||||||
@@ -248,3 +267,68 @@ class TestGeneralLedger(FrappeTestCase):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data]))
|
self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data]))
|
||||||
|
|
||||||
|
def test_ignore_cr_dr_notes_filter(self):
|
||||||
|
si = create_sales_invoice()
|
||||||
|
|
||||||
|
cr_note = make_return_doc(si.doctype, si.name)
|
||||||
|
cr_note.submit()
|
||||||
|
|
||||||
|
pr = frappe.get_doc("Payment Reconciliation")
|
||||||
|
pr.company = si.company
|
||||||
|
pr.party_type = "Customer"
|
||||||
|
pr.party = si.customer
|
||||||
|
pr.receivable_payable_account = si.debit_to
|
||||||
|
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
|
||||||
|
invoices = [invoice.as_dict() for invoice in pr.invoices if invoice.invoice_number == si.name]
|
||||||
|
payments = [payment.as_dict() for payment in pr.payments if payment.reference_name == cr_note.name]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
system_generated_journal = frappe.db.get_all(
|
||||||
|
"Journal Entry",
|
||||||
|
filters={
|
||||||
|
"docstatus": 1,
|
||||||
|
"reference_type": si.doctype,
|
||||||
|
"reference_name": si.name,
|
||||||
|
"voucher_type": "Credit Note",
|
||||||
|
"is_system_generated": True,
|
||||||
|
},
|
||||||
|
fields=["name"],
|
||||||
|
)
|
||||||
|
self.assertEqual(len(system_generated_journal), 1)
|
||||||
|
expected = set([si.name, cr_note.name, system_generated_journal[0].name])
|
||||||
|
# Without ignore_cr_dr_notes
|
||||||
|
columns, data = execute(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"company": si.company,
|
||||||
|
"from_date": si.posting_date,
|
||||||
|
"to_date": si.posting_date,
|
||||||
|
"account": [si.debit_to],
|
||||||
|
"group_by": "Group by Voucher (Consolidated)",
|
||||||
|
"ignore_cr_dr_notes": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
actual = set([x.voucher_no for x in data if x.voucher_no])
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
# Without ignore_cr_dr_notes
|
||||||
|
expected = set([si.name, cr_note.name])
|
||||||
|
columns, data = execute(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"company": si.company,
|
||||||
|
"from_date": si.posting_date,
|
||||||
|
"to_date": si.posting_date,
|
||||||
|
"account": [si.debit_to],
|
||||||
|
"group_by": "Group by Voucher (Consolidated)",
|
||||||
|
"ignore_cr_dr_notes": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
actual = set([x.voucher_no for x in data if x.voucher_no])
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from itertools import groupby
|
|||||||
import frappe
|
import frappe
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, flt
|
from frappe.utils import cint, flt, getdate
|
||||||
|
|
||||||
from erpnext.setup.utils import get_exchange_rate
|
from erpnext.setup.utils import get_exchange_rate
|
||||||
|
|
||||||
@@ -21,7 +21,15 @@ class SalesPipelineAnalytics:
|
|||||||
def __init__(self, filters=None):
|
def __init__(self, filters=None):
|
||||||
self.filters = frappe._dict(filters or {})
|
self.filters = frappe._dict(filters or {})
|
||||||
|
|
||||||
|
def validate_filters(self):
|
||||||
|
if not self.filters.from_date:
|
||||||
|
frappe.throw(_("From Date is mandatory"))
|
||||||
|
|
||||||
|
if not self.filters.to_date:
|
||||||
|
frappe.throw(_("To Date is mandatory"))
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
self.validate_filters()
|
||||||
self.get_columns()
|
self.get_columns()
|
||||||
self.get_data()
|
self.get_data()
|
||||||
self.get_chart_data()
|
self.get_chart_data()
|
||||||
@@ -185,7 +193,7 @@ class SalesPipelineAnalytics:
|
|||||||
count_or_amount = info.get(based_on)
|
count_or_amount = info.get(based_on)
|
||||||
|
|
||||||
if self.filters.get("pipeline_by") == "Owner":
|
if self.filters.get("pipeline_by") == "Owner":
|
||||||
if value == "Not Assigned" or value == "[]" or value is None:
|
if value == "Not Assigned" or value == "[]" or value is None or not value:
|
||||||
assigned_to = ["Not Assigned"]
|
assigned_to = ["Not Assigned"]
|
||||||
else:
|
else:
|
||||||
assigned_to = json.loads(value)
|
assigned_to = json.loads(value)
|
||||||
@@ -227,10 +235,9 @@ class SalesPipelineAnalytics:
|
|||||||
|
|
||||||
def get_month_list(self):
|
def get_month_list(self):
|
||||||
month_list = []
|
month_list = []
|
||||||
current_date = date.today()
|
current_date = getdate(self.filters.get("from_date"))
|
||||||
month_number = date.today().month
|
|
||||||
|
|
||||||
for _month in range(month_number, 13):
|
while current_date < getdate(self.filters.get("to_date")):
|
||||||
month_list.append(current_date.strftime("%B"))
|
month_list.append(current_date.strftime("%B"))
|
||||||
current_date = current_date + relativedelta(months=1)
|
current_date = current_date + relativedelta(months=1)
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
from erpnext.crm.report.sales_pipeline_analytics.sales_pipeline_analytics import execute
|
from erpnext.crm.report.sales_pipeline_analytics.sales_pipeline_analytics import execute
|
||||||
|
|
||||||
|
|
||||||
class TestSalesPipelineAnalytics(unittest.TestCase):
|
class TestSalesPipelineAnalytics(FrappeTestCase):
|
||||||
@classmethod
|
def setUp(self):
|
||||||
def setUpClass(self):
|
|
||||||
frappe.db.delete("Opportunity")
|
frappe.db.delete("Opportunity")
|
||||||
create_company()
|
create_company()
|
||||||
create_customer()
|
create_customer()
|
||||||
create_opportunity()
|
create_opportunity()
|
||||||
|
|
||||||
def test_sales_pipeline_analytics(self):
|
def test_sales_pipeline_analytics(self):
|
||||||
|
self.from_date = "2021-01-01"
|
||||||
|
self.to_date = "2021-12-31"
|
||||||
self.check_for_monthly_and_number()
|
self.check_for_monthly_and_number()
|
||||||
self.check_for_monthly_and_amount()
|
self.check_for_monthly_and_amount()
|
||||||
self.check_for_quarterly_and_number()
|
self.check_for_quarterly_and_number()
|
||||||
@@ -28,6 +30,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -43,6 +47,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -59,6 +65,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -74,6 +82,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -90,6 +100,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -105,6 +117,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -121,6 +135,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -136,6 +152,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -153,8 +171,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
"opportunity_source": "Cold Calling",
|
"opportunity_source": "Cold Calling",
|
||||||
"from_date": "2021-08-01",
|
"from_date": self.from_date,
|
||||||
"to_date": "2021-08-31",
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
|
|||||||
Reference in New Issue
Block a user